LXF159:Arduino: Управляем временем
Olkol (обсуждение | вклад) (→Как работают таймеры?) |
Olkol (обсуждение | вклад) (→Как работают таймеры?) |
||
Строка 19: | Строка 19: | ||
Работа с таймерами кажется сложноватой, но по сути они устроены довольно просто. Таймер 1 (Timer1) – 16-битный. В своем самом простом режиме он просто считает циклы; достигнув максимума, устанавливает бит переполнения и начинает отсчет занове. | Работа с таймерами кажется сложноватой, но по сути они устроены довольно просто. Таймер 1 (Timer1) – 16-битный. В своем самом простом режиме он просто считает циклы; достигнув максимума, устанавливает бит переполнения и начинает отсчет занове. | ||
+ | {{Врезка|left|Заголовок= |Ширина=20%|Содержание= Поразрядная арифметика | ||
+ | |||
+ | В любом коде, который имеет дело с этими нудными регистрами, рядом с обозначениями регистров вы часто встретите странные закорючки, кажется, призванные еще больше запутать смысл. Не бойтесь! Это просто операторы поразрядной двоичной арифметики: | ||
+ | |||
+ | | = OR (ИЛИ) | ||
+ | |||
+ | & = AND (И) | ||
+ | |||
+ | ~ = NOT (НЕ) | ||
+ | |||
+ | ^ = XOR (ИЛИ-НЕ), или «исключающее ИЛИ» (eXclusive OR) | ||
+ | |||
+ | Если они идут перед знаком равенства, это означает, что первый операнд – исходная переменная. Например, выражение | ||
+ | |||
+ | PORTB |= B00100000; | ||
+ | |||
+ | аналогично | ||
+ | |||
+ | PORTB = PORTB | B00100000; | ||
+ | |||
+ | А почему мы вообще воспользовались оператором OR? Потому что с его помощью можно установить один бит, не трогая другие биты этого байта. Если нужно сбросить бит, воспользуйтесь оператором AND с нулем: | ||
+ | |||
+ | PORTB &= B11011111; | ||
+ | |||
+ | На практике двоичные значения обычно так не записываются, потому что при работе с регистрами проще и безопаснее пользоваться сокращениями, описанными в стандартных заголовочных файлах. Так, например, цифровой вывод 13 Arduino (также известный в документации по Atmel как PORTB, бит 5) сокращенно обозначается как PB5. Есть два общих способа их использования. На схеме слева внизу показан первый способ. | ||
+ | |||
+ | Функция _BV() – просто макрос (сокращение от “Bit Value” – «значение бита»); он возвращает значение указанного в скобках сокращения бита в виде двоичного числа. На схеме внизу справа показан другой способ их использования. Это та же самая операция. Вместо использования заранее заданного макроса в ней просто сдвигается «единица» на количество бит, определяемое значением сокращения. Так, (1 << PB5) становится «00100000». Вот в чем удобство сокращенных обозначений. Будь код засорен двоичными значениями, его было бы труднее понять. Чтобы осознать позицию бита в числе «00100000», явно нужно потратить больше времени, чем взглянув на PB5. | ||
+ | |||
+ | Если вы хотите гарантированно сбросить бит, можно воспользоваться оператором NOT в сочетании с любым методом: | ||
+ | |||
+ | PORTB &= ~_BV(PB5); | ||
+ | |||
+ | Здесь берется двоичное значение PB5 (00100000) и инвертируется (11011111), а затем объединяется по «И» с текущим содержимым регистра, т. е. все установленные биты, кроме бита 5, не изменятся. | ||
+ | |||
+ | Еще одно важное предупреждение. Одна из причин менять только необходимые биты в том, что многим битам отведена особая роль. Например, в PORTD последний бит – это линия, используемая для приема данных. Случайно направив ее на вывод, вы определенно нарушите работу схемы. | ||
+ | |||
[[Файл:LXF154.tut_arduino.bitw_opt.png |left |400px |thumb|> Анатомия поразрядной операции с помощью макроса.]] | [[Файл:LXF154.tut_arduino.bitw_opt.png |left |400px |thumb|> Анатомия поразрядной операции с помощью макроса.]] | ||
− | [[Файл: LXF154.tut_arduino.bit_opt1.png | | + | [[Файл: LXF154.tut_arduino.bit_opt1.png |right |400px |thumb|> Поразрядные операции со сдвигом значений.]]}} |
Будучи 16-битным, он считает до 65 535. Таймер в Arduino работает на частоте 16 МГц, то есть обычно переполнение наступает около 244 раз в секунду, или примерно каждые 4 миллисекунды. Это не кажется слишком удобным, но мы еще не закончили. Добавим кое-какие важные детали. Флаг, устанавливаемый при заполнении таймера, называется весьма логично – флаг переполнения таймера (Timer Overflow Flag), и для Таймера 1 обозначается как TOV1. Этот флаг находится в специальном регистре (ячейке памяти) микроконтроллера, называемой TIFR, сокращение от “Timer Interrupt Flag Register [Регистр флагов прерываний таймера]”. Простите за все эти названия, но их важно знать, поскольку они будут использоваться в коде. Для упрощения чтения кода эти сокращения определяются в стандарных заголовках AVR (это удобнее, так как адреса могут различаться от устройства к устройству), и OCR1A выглядит понятнее, чем просто адрес 0x88. В конце этой статьи есть небольшой словарь сокращений, и мы будем пояснять их по мере появления. | Будучи 16-битным, он считает до 65 535. Таймер в Arduino работает на частоте 16 МГц, то есть обычно переполнение наступает около 244 раз в секунду, или примерно каждые 4 миллисекунды. Это не кажется слишком удобным, но мы еще не закончили. Добавим кое-какие важные детали. Флаг, устанавливаемый при заполнении таймера, называется весьма логично – флаг переполнения таймера (Timer Overflow Flag), и для Таймера 1 обозначается как TOV1. Этот флаг находится в специальном регистре (ячейке памяти) микроконтроллера, называемой TIFR, сокращение от “Timer Interrupt Flag Register [Регистр флагов прерываний таймера]”. Простите за все эти названия, но их важно знать, поскольку они будут использоваться в коде. Для упрощения чтения кода эти сокращения определяются в стандарных заголовках AVR (это удобнее, так как адреса могут различаться от устройства к устройству), и OCR1A выглядит понятнее, чем просто адрес 0x88. В конце этой статьи есть небольшой словарь сокращений, и мы будем пояснять их по мере появления. | ||
Версия 16:35, 24 сентября 2018
|
|
|
Властвуйте над своей платой с помощью прерываний таймера
Arduino: Рулим временем
Ник Вейч принимает вызов времени и овладевает постоянно лязгающими часами, чтобы придумать нечто хитроумное с тиками и таками.
Время течет неумолимо. Измеряете ли вы его в наносекундах или в сутках до следующего дня рождения, вам не помешает быть точным, и это одна из причин, по которым в стандартной схеме ATmega 328 (используемой в платах Arduino Duemilanove и Uno) установлены не один, а три отдельных таймера. И не глупо ли, что с помощью стандартного кода Arduino можно делать немногим больше, чем считать с их помощью миллисекунды? Природа встроенных таймеров – одно из великих и красивых чудес, не говоря уже об их немалой пользе. На этом уроке мы займемся мигающими светодиодами, причем сделаем это правильно. В конце мы получим феноменально точно мигающий свет, который будет мигать постоянно, что бы вы ни вытворяли в остальной части кода. И вы узнаете, как искривлять саму ткань времени. Наверное.
- Метамодернизм в позднем творчестве В.Г. Сорокина
- ЛитРПГ - последняя отрыжка постмодерна
- "Ричард III и семиотика"
- 3D-визуализация обложки Ridero создаем обложку книги при работе над самиздатом.
- Архитектура метамодерна - говоря о современном искусстве, невозможно не поговорить об архитектуре. В данной статье будет отмечено несколько интересных принципов, характерных для построек "новой волны", столь притягательных и скандальных.
- Литература
- Метамодерн
- Рокер-Прометей против изначального зла в «Песне про советскую милицию» Вени Дркина, Автор: Нина Ищенко, к.ф.н, член Союза Писателей ЛНР - перепубликация из журнала "Топос".
- Как избавиться от комаров? Лучшие типы ловушек.
- Что делать если роблокс вылетает на windows
- Что делать, если ребенок смотрит порно?
- Почему собака прыгает на людей при встрече?
- Какое масло лить в Задний дифференциал (мост) Visco diff 38434AA050
- О чем может рассказать хвост вашей кошки?
- Верветки
- Отчетность бюджетных учреждений при закупках по Закону № 223-ФЗ
- Срок исковой давности как правильно рассчитать
- Дмитрий Патрушев минсельхоз будет ли преемником Путина
- Кто такой Владислав Поздняков? Что такое "Мужское Государство" и почему его признали экстремистским в России?
- Как правильно выбрать машинное масло в Димитровграде?
- Как стать богатым и знаменитым в России?
- Почему фильм "Пипец" (Kick-Ass) стал популярен по всему миру?
- Как стать мудрецом?
- Как правильно установить FreeBSD
- Как стать таким как Путин?
- Где лучше жить - в Димитровграде или в Ульяновске?
- Почему город Димитровград так называется?
- Что такое метамодерн?
- ВАЖНО! Временное ограничение движения автотранспортных средств в Димитровграде
- Тарифы на электроэнергию для майнеров предложено повысить
Как работают таймеры?
Работа с таймерами кажется сложноватой, но по сути они устроены довольно просто. Таймер 1 (Timer1) – 16-битный. В своем самом простом режиме он просто считает циклы; достигнув максимума, устанавливает бит переполнения и начинает отсчет занове.
- Метамодернизм в позднем творчестве В.Г. Сорокина
- ЛитРПГ - последняя отрыжка постмодерна
- "Ричард III и семиотика"
- 3D-визуализация обложки Ridero создаем обложку книги при работе над самиздатом.
- Архитектура метамодерна - говоря о современном искусстве, невозможно не поговорить об архитектуре. В данной статье будет отмечено несколько интересных принципов, характерных для построек "новой волны", столь притягательных и скандальных.
- Литература
- Метамодерн
- Рокер-Прометей против изначального зла в «Песне про советскую милицию» Вени Дркина, Автор: Нина Ищенко, к.ф.н, член Союза Писателей ЛНР - перепубликация из журнала "Топос".
- Как избавиться от комаров? Лучшие типы ловушек.
- Что делать если роблокс вылетает на windows
- Что делать, если ребенок смотрит порно?
- Почему собака прыгает на людей при встрече?
- Какое масло лить в Задний дифференциал (мост) Visco diff 38434AA050
- О чем может рассказать хвост вашей кошки?
- Верветки
- Отчетность бюджетных учреждений при закупках по Закону № 223-ФЗ
- Срок исковой давности как правильно рассчитать
- Дмитрий Патрушев минсельхоз будет ли преемником Путина
- Кто такой Владислав Поздняков? Что такое "Мужское Государство" и почему его признали экстремистским в России?
- Как правильно выбрать машинное масло в Димитровграде?
- Как стать богатым и знаменитым в России?
- Почему фильм "Пипец" (Kick-Ass) стал популярен по всему миру?
- Как стать мудрецом?
- Как правильно установить FreeBSD
- Как стать таким как Путин?
- Где лучше жить - в Димитровграде или в Ульяновске?
- Почему город Димитровград так называется?
- Что такое метамодерн?
- ВАЖНО! Временное ограничение движения автотранспортных средств в Димитровграде
- Тарифы на электроэнергию для майнеров предложено повысить
Будучи 16-битным, он считает до 65 535. Таймер в Arduino работает на частоте 16 МГц, то есть обычно переполнение наступает около 244 раз в секунду, или примерно каждые 4 миллисекунды. Это не кажется слишком удобным, но мы еще не закончили. Добавим кое-какие важные детали. Флаг, устанавливаемый при заполнении таймера, называется весьма логично – флаг переполнения таймера (Timer Overflow Flag), и для Таймера 1 обозначается как TOV1. Этот флаг находится в специальном регистре (ячейке памяти) микроконтроллера, называемой TIFR, сокращение от “Timer Interrupt Flag Register [Регистр флагов прерываний таймера]”. Простите за все эти названия, но их важно знать, поскольку они будут использоваться в коде. Для упрощения чтения кода эти сокращения определяются в стандарных заголовках AVR (это удобнее, так как адреса могут различаться от устройства к устройству), и OCR1A выглядит понятнее, чем просто адрес 0x88. В конце этой статьи есть небольшой словарь сокращений, и мы будем пояснять их по мере появления.
С помощью некоторых из этих регистров мы сможем задать параметры работы таймера, поэтому начнем с регистров управления счетчиком таймера (Timer Counter Control Register) – TCCR1A и TCCR1B.
В TCCR1A нам пока можно не вникать – он управляет выводом в режимах сравнения и широтно-импульсной модуляции (ШИМ), которые довольно сложны. Мы просто установим все биты этого регистра в 0:
TCCR1A = 0 × 00;
А вот другой регистр управления нам пригодится. Последние три бита этого регистра управляют предварительным делителем тактов. Что это такое? Предварительный делитель частоты похож на цифровой делитель, который находится между таймером процессора и счетчиком. Если установить все биты в 0, таймер останавливается. Если установить только наименьший бит, сигнал таймера передастся на счетчик без изменений. При других значениях этих битов в дело вступает делитель – вместо передачи каждого импульса системного таймера он передает только один из каждых 8, 64, 256 или 1024. Таблица значений битов и работы делителя выглядит так:
000 Счетчик остановлен
001 Сигнал таймера
010 Сигнал таймера/8
011 Сигнал таймера/64
100 Сигнал таймера/256
101 Сигнал таймера/1024
110 Внешний сигнал, спадающий фронт
111 Внешний сигнал, нарастающий фронт
Два последних значения предназначены для использования внешнего сигнала с таймера, подключаемого к выводу T1 (в терминологии Atmel) или D5 (в терминологии Arduino). Так можно реализовать весьма интересные схемы, но мы оставим их на будущее. Итак, если эти биты установлены в 0x01, счетчик считает 16 000 000 раз в секунду. Это много. Это означает, что он переполняется 16 000 000/65 536 раз в секунду, или 244,14 раза в секунду.
Установим предварительный делитель тактов в 256:
TCCR1B=0x04;
В этом случае он будет переполняться (16 000 000/256)/65 536 раз в секунду, или 0,9536 раза в секунду. Если создать процедуру обработки прерывания (см. наше руководство по прерываниям в LXF154), вызываемую при переполнении таймера, наш код будет выполняться где-то раз в секунду. Как это сделать?
К счастью, у микросхемы ATmega есть флаги и регистры именно для этих целей. Нам нужно сделать две вещи. Во-первых, включим прерывание специально для таймера, достигающего своего максимального значения. Затем микроконтроллер проверит флаг переполнения, и если он установлен, сбросит его и вызовет процедуру обработки прерывания. Для решения первой задачи установим соответствующий бит в маске прерываний таймера:
TIMSK1=0 × 01;
Или воспользуемся макросом и сделаем это безопаснее:
TIMSK1 |= _BV(TOIE1);
Второй вариант установки бита не сбросит ни одно из существующих прерываний, но их и не должно быть. И хотя с макросами код проще читается (и по нему легче производить поиск), иногда быстрее и проще записать весь регистр, особенно если нужно изменить несколько бит. Установка этого бита означает, что при переполнении таймера процессор проверит, есть ли процедура обработки прерывания или нет.
Последняя часть этой задачи – написание процедуры обработки прерывания. Это обычное определение функции, но при ее компиляции используется макрос. Причина в том, что адрес этой функции в памяти должен попасть в таблицу векторов прерываний, чтобы ее можно было вызвать. Чтобы объявить функцию обработки прерывания переполнения таймера, нам требуется написать следующий код:
ISR(TIMER1_OVF_vect) {
… код обработки …
}
ISR() – макрос, подставляемый на этапе компиляции, а TIMER1_OVF_vect означает, что адрес этого прерывания нужно загрузить в вектор переполнения Таймера 1. Этот адрес известен компилятору, и вам незачем беспокоиться о точном месте его прописки.
Есть и другие стандартные векторы прерываний. Мы обсудим их позже, или загляните в документацию по avr-libc (http://www.nongnu.org/avr-libc/user-manual/group__avr__interrupts.html).
Как и в случае с обычными прерываниями, эта функция не возвращает значений. Она может устанавливать глобальные переменные, но обращайтесь с ними очень осторожно – прерывание может произойти в любой момент, и если переменная используется где-либо еще, возможны потери данных. Соединив все это в коде проекта мигающего светодиода, мы получим примерно такое:
const int LedPin = 13;
boolean value=false;
ISR(TIMER1_OVF_vect) {
//toggle the value
value=!value;
}
void setup() {
pinMode(LedPin, OUTPUT);
TIMSK1=0x01; // включить прерывание по переполнению счета
TCNT1=0x0000; // сброс счетчика в 0 при старте
TCCR1A = 0x00; // задать TCCR1A- отключить PWM и OCR;
TCCR1B = 0x04; // задать TCCR1B - делитель для /256
// запуск последовательного доступа - чтобы обойти ошибку
// систем, неверно инициализирующих сегмент данных
Serial.begin(9600);
};
void loop () {
digitalWrite(LedPin,value);
};
Наша процедура обработки прерывания весьма компактна – она просто переключает значение глобальной булевой переменной. В коде установки контакт 13 выбирается в качестве вывода, устанавливается таймер, затем он инициализируется нулевым значением. В основном цикле кода значение постоянно выводится на контакт светодиода. Вы можете подумать, что это несколько избыточно, и будете правы! Установить вывод в «единицу» можно и в самой процедуре обработки прерывания; также можно отказаться и от функции digitalWrite. В этом случае наша новая процедура прерывания будет выглядеть примерно так:
ISR(TIMER1_OVF_vect) {
PORTB ^= _BV(PB5);
}
Этот код может показаться вам совершенной тарабарщиной. Если так, прочтите врезку «Поразрядные операции». Прочли? Хорошо. Все равно разберем этот код подробнее. PORTB – еще одно из сокращений. На языке фирмы Atmel, которая производит микросхемы ATmega168 и 368, лежащие в основе Arduino, PORTB – набор двунаправленных портов, подключенных к некоторым выводам платы. Два верхних бита отражают вывод, используемый для генератора, поэтому ими пользоваться нельзя. Выводы с 0 по 5 – цифровые выводы с 8 по 13 в терминологии Arduino, поэтому вывод 13 (к которому подключен светодиод) – это бит 5 регистра PortB. Его сокращенное обозначение – PB5.
Операция, которую мы выполняем здесь – исключающее «ИЛИ». Она означает, что если бит установлен (1), он будет сброшен (1 ^ 1 = 0), а если не установлен (0), то установится (0 ^ 1 = 1). Эта операция хитроумно меняет значение именно нужным нам образом, и нам нечего беспокоиться о функции digitalWrite(). Нужно только убедиться, что вывод 13 настроен на вывод данных. Обычно так и есть, но ясности ради можно задать это явно. Так как мы освоили работу с регистрами, это можно сделать в коде настройки:
DDRB |= _BV(PB5);
Это выражение устанавливает бит 5 регистра в единицу, не меняя другие биты; то же самое делает функция pinMode(). (Мы включили эту версию в Листинг 2 на DVD.) Что же касается основного цикла – кстати, при желании туда можно вставить любой код – для мигания светодиода ничего добавлять не нужно, теперь оно полностью независимо от остального кода, и светодиод будет на секунду включаться и на секунду выключаться, хотя питание на него подано. Или почти на секунду.
Остановите часы
Если помните, согласно нашим расчетам прерывание возникает с частотой чуть реже чем раз в секунду: секунда – это 62 500 тактов, а не 65 536. У этой проблемы есть два очевидных решения. Первое – в процедуре обработки прерывания устанавливать таймер в 3036: тогда он начнет отсчет не с нуля, а с этого значения, и до конца ему останется 62 500 тактов. Эта идея не так уж плоха, но с ней связано несколько проблем. Сама работа с таймером, пока он запущен, приводит к ошибкам – на запись нового значения уйдет несколько циклов, поэтому такой способ не совсем точен. Это может не играть особой роли с несколькими миллисекундами от секунды, но при более коротком интервале времени уже вызовет проблемы. К счастью, есть лучшее решение. У таймера есть специальный регистр сравнения. В него загружается значение, и при каждом изменении значения таймера оно сравнивается с содержимым регистра. Если они совпадают и в TIMSK установлен флаг прерывания, можно сгенерировать прерывание. Вот как установить бит в регистре сравнения:
TIMSK1 |= _BV(OCIE1A)
Это сокращение означает «Output Compare Interrupt Enable (Timer)1 (Register)A» (Включить регистр (А) сравнения для прерываний (таймера) 1), если это поможет вам его запомнить.
Но прежде чем загружать значение в регистр сравнения, подумаем еще кое о чем. Когда значения совпадают и вызывается прерывание, мы переключаем светодиод. Но что происходит с таймером? Он отсчитывает еще 3036 тактов до конца и сбрасывается в ноль, затем отчитывает очередные 62 500 тактов и снова вызывает прерывание. Выходит, мы не изменили период мигания, а только сдвинули момент его возникновения.
Как вы, возможно, догадались, у этой проблемы есть простое решение – CTC (Clear Timer on Compare – сброс таймера при сравнении). В этом режиме при совпадении значений таймер сбрасывается в ноль, что и убирает нашу маленькую трудность. При установке режима CTC используются те же биты регистра TCCR1, что и для функций ШИМ, и поделены между двумя половинками регистра. Чтобы включить режим CTC, нужно сбросить эти биты в верхней половинке регистра и один из них в нижней половинке. Как вы, возможно, помните, здесь же мы задаем значение предварительного делителя тактов. Так как мы изменяем все биты, удобнее написать:
TCCR1A= B00000000; // отключить PWM
TCCR1B= B00001100; // включить CTC и делитель /256
Далее нужно загрузить регистр сравнения. Мы всегда делаем это после установки TCCR, так как при изменении TCCR возникает плохо задокументированная ошибка – регистр сравнения сбрасывается. Для этого нужно просто записать значение в регистр сравнения (On Compare Register):
OCR1A = 62499;
Отчет начинается с нуля, и для получения точного результата надо сравнивать значение счетчика с 62 499. Регистр сравнения 16-битный, как и счетчик, поэтому убедитесь, что вы записываете в него 16-битное значение. Если вы записываете значение меньше 255, делайте это в шестнадцатеричном формате (например, 127 = 0 × 007F). Опять же, нужно записать прерывание:
ISR(TIMER1_COMPA_vect) {
PORTB ^= _BV(PB5);
}
Оно отличается от исходного прерывания только тем, что мы изменили вектор, к которому оно обращается – если мы забудем записать прерывание, то при его вызове вектор прерываний будет равен нулю. Процессор посчитает это плохим прерыванием, и если мы не дадим ему других указаний, выполнит сброс. Если Arduino не производит никаких действий, проверьте, что вы записали значение в нужный вектор. Наш код теперь должен выглядеть так (Листинг 3 на DVD):
void setup() {
DDRB |= _BV(PB5); // задать контакт LED как выход
cli(); // остановка прерываний
TCNT1=0x0000; // сброс счетчика в 0 при старте
TCCR1A= B00000000; // отключение PWM
TCCR1B= B00001100; // включить CTC и делитель /256
TIMSK1 |= _BV(OCIE1A); // включить прерывание по сравнению с A
OCR1A = 62499; //пусть компаратор A срабатывает на 1 секунду
sei(); // снова включить прерывания
};
void loop () {
///ваш полезный код
};
ISR(TIMER1_COMPA_vect) {
PORTB ^= _BV(PB5);
}
Сравните это
При записи значения в регистр сравнения, вы могли заметить множество ссылок на регистр сравнения A. Дело в том, что есть еще и регистр B, значение которого также можно изменять, сравнивать и вызывать прерывание – но таймер при этом не сбрасывается. В этом случае прерывание сгенерируется в течение односекундного интервала, но таймер продолжит работать до достижения значения 62 499. Это может быть удобно, если вы хотите генерировать события, например, каждую секунду и полсекунды.
Впрочем, мы воспользуемся этим немного иначе – будем вызывать ту же самую процедуру обработки прерывания, но в точке ближе к концу односекундного цикла. Это вызовет кратковременное включение светодиода перед завершением секунды и создаст эффект пульсации, который малость поинтереснее, чем простое включение-выключение на секунду. Весь код подробно описывать не будем; главное, что нужно сделать – задать прерывания для OCR1A и OCR1B и написать новый обработчик прерываний:
TIMSK1 |= _BV(OCIE1A) | _BV(OCIE1B) ;
OCR1A=62499;
OCR1B=60000;
Так как мы хотим воспользоваться той же процедурой обработки прерывания, нам пригодится еще один хитрый прием, связанный с компиляцией. Оба прерывания должны находиться в таблицах векторов, но мы хотим, чтобы они указывали на одно и то же место. Для этого послужит специальный макрос ISR_ALIAS(). Он принимает два вектора – первый, который вы хотите перенаправить, и второй, в который нужно перенаправить:
ISR_ALIAS(TIMER1_COMPB_vect, TIMER1_COMPA_vect );
Одна эта строка делает все необходимое. Полный листинг этой версии приведен на DVD как Листинг 4.
Таковы основы использования таймеров. Поначалу они могут немного запугать, и не обходится без подводных камней, которых стоит опасаться, но прерывания на основе таймера очень удобны – и вскоре мы расскажем еще о нескольких приемах их использования