Таймеры T0 и T2 являются восьмиразрядными и присутствуют во всех моделях ATmega. Счётные регистры таймеров - TCNTn (здесь и далее n - номер счётчика).
Тактирование таймеров
По умолчанию тактируются от основного генератора через предделитель, при этом коэффициент деления частоты задаётся битами CSn0, CSn1 и CSn2 регистра TCCRn:
Коды режимов тактирования:
Для таймеров исполнения №2 (таймер T0 в ATmega 8515x, 16x, 32x и T2 в ATmega 64x, 128x) и №1 (T0 в ATmega 8x):
TCCRn|=(1<<CSn0); // Тактировать без деления частоты.
TCCRn|=(1<<CSn1); // Тактировать с коэффициентом деления 8.
TCCRn|=(1<<CSn0)|(1<<CSn1); // Тактировать с коэффициентом 64.
TCCRn|=(1<<CSn2); // Тактировать с коэффициентом 256.
TCCRn|=(1<<CSn0)|(1<<CSn2); // Тактировать с коэффициентом 1024.
TCCRn&=~(1<<CSn0|1<<CSn1|1<<CSn2); // Остановить счётчик.
Для таймеров в исполнении №3 (T0 в ATmega 64x,128x, T2 в ATmega8x,16x,32x):
TCCRn|=(1<<CSn0); // Тактировать без деления частоты.
TCCRn|=(1<<CSn1); // Тактировать с коэффициентом деления 8.
TCCRn|=(1<<CSn0)|(1<<CSn1); // Тактировать с коэффициентом 32.
TCCRn|=(1<<CSn2); // Тактировать с коэффициентом 64.
TCCRn|=(1<<CSn0)|(1<<CSn2); // Тактировать с коэффициентом 128.
TCCRn|=(1<<CSn1)|(1<<CSn2); // Тактировать с коэффициентом 256.
TCCRn|=(1<<CSn0)|(1<<CSn1)|(1<<CSn2); // Тактировать с коэффициентом 1024.
TCCRn&=~(1<<CSn0|1<<CSn1|1<<CSn2); // Остановить счётчик.
Прерывание по переполнению
При переходе значения счётчика из максимального в нулевое устанавливается флаг переполнения TOVn:
Если при этом установлен флаг разрешения прерывания по переполнению TOIEn регистра TIMSK и прерывания разрешены глобально, произойдёт вызов обработчика.
Пример кода для ATmega16 - прерывание по T2 каждые 100 тактов генератора:
Все таймеры кроме T0 в Atmega8, способны формировать прерывание при совпадении текущего значения счётчика TCNTn с содержимым регистра OCRn. Сравнение указанных регистров происходит в каждом машинном цикле. В случае их равенства устанавливается флаг OCFn регистра TIFR и генерируется соответствующее прерывание, если оно разрешено установкой бита OCIEn того же регистра:
Пример: Прерывания каждые 10мс. ATmega64 c кварцем 11,059 МГц.
// Инициализация Т0. OCR0=(108-1); // При частоте 11,059 МГц и делителе 1024, 10ms - 108 тактов (OCR0=107!). TCCR0|=(1<<CS00)|(1<<CS01)|(1<<CS02); // Делить частоту на 1024. SREG|=(1<<7); // Разрешить прерывания.
TIMSK|=(1<<OCIE0); // Разрешить прерывание по совпадению. // Обработчик прерывания по совпадению с OCR0. #pragma vector=TIMER0_COMP_vect __interrupt void T0_COMPARE() { TCNT0=0; // Периодическая инициализация счётчика. }
Изменение состояния вывода OCn при совпадении с регистром сравнения
Момент совпадения содержимого регистров счётчика и сравнения можно использовать для управления состоянием внешнего вывода OC0(OC2). Для этого необходимо:
- установить вывод OCn в состояние выхода;
- разрешить данный режим установкой бита FOCn регистра TCCRn;
- определить действия над выводом комбинацией битов COMn0 и COMn1 в регистре TCCRn:
COMn1=0, COMn0=0 - таймер отключен от вывода OCn;
COMn1=0, COMn0=1 - состояние вывода меняется на противоположное;
COMn1=1, COMn0=0 - вывод сбрасывается в ноль;
COMn1=1, COMn0=1 - вывод устанавливается в единицу.
Нужно отметить, что в данном режиме вызов прерывания блокируется. Это затрудняет сброс счётчика после совпадения, что неудобно при генерировании внешнего сигнала с заданной частотой. Решить эту проблему можно, разрешив режим CTC (сброс при совпадении) установкой бит WGMn1 в TCCRn. При этом счётчик после совпадения с регистром сравнения будет сбрасываться автоматически.
Пример: генерировать частоту 1кГц на выводе OC2 ATmega 8 с генератором на 1 МГц
OCR2=(125-1); // При частоте 1 МГц и делителе 8 для 1ms - 125 тактов таймера. TCCR2|=(1<<CS01); // Тактировать с коэффициентом деления 8. DDRB|=(1<<3);// Установить вывод OC2 (PortB.3) на выход
// Разрешить изменение вывода OC2. Включить режим CTC.
TCCR2|=(1<<FOC2)|(1<<COM20)|(1<<WGM21);
Асинхронный режим тактирования
Таймер T0 в ATmega 64, 128 и таймер T2 в остальных моделях имеют возможность тактирования от собственного асинхронного генератора с выводами для подключения внешнего кварцевого резонатора - TOSC1 и TOSC2. Переключение в асинхронный режим производится установкой бита ASn в регистре ASSR. Подключив к указанным выводам часовой кварц 32768Гц можно организовать простейший подсчёт времени с начала работы программы:
ASSR|=(1<<AS2); // Тактировать T2 от асинхронного генератора таймера
// Тактировать частотой 32768/1024=32 Гц, режим сброса по совпадению.
TCCR2|=(1<<CS20)|(1<<CS21)|(1<<CS22)|(1<<WGM21);
OCR2=(32-1); // (для 32 тактов OCR0=31!), совпадения с частотой 1 Гц.
TIMSK|=(1<<OCIE2); // Разрешить прерывание по совпадению Т2.
SREG |= (1<<7); // Разрешить прерывания.
while (1) {}
}
// Обработчик ежесекундных прерываний Т2.
#pragma vector=TIMER2_COMP_vect
__interrupt void T2_COMP()
{
PORTA^=(1<<LED); // Новая секунда.
if (++Sec==60)
{
Sec=0; // Новая минута.
if (++Min==60)
{Min=0;Hour++;} // Новый час.
}
}
Режим Fast PWM – быстродействующий ШИМ. Совместно с выводами OC0/OC2, таймеры T0/T2 могут использоваться для генерирования сигнала с широтно-импульсной модуляцией. Такой сигнал характеризуется постоянной частотой и изменяющейся скважностью (коэффициентом заполнения) импульсов (соотношением длительности единичного и нулевого уровней сигнала). В режиме Fast PWM происходит автоматическая установка в единицу вывода OCn при переполнении счётчика и его сброс при совпадении счётчика с регистром сравнения OCRn. Таким образом, частота сигнала ШИМ может регулироваться только частотой генератора и подбором коэффициента делителя таймера и не может быть выше 62,5 кГц (16МГц / 256). Для перевода таймера в режим "Быстродействующий ШИМ” достаточно выбрать его установкой битов WGMn0 и WGMn1 в регистре TCCRn:
TCCRn|=(1<<WGMn0)|(1<<WGMn1);
Поведение вывода OCn при этом задаётся в том же регистре битами COMn0 и COMn1: COMn1=0, COMn0=0 - таймер отключен от вывода OCn; COMn1=0, COMn0=1 – зарезервированная комбинация; COMn1=1, COMn0=0 – нормальный ШИМ; COMn1=1, COMn0=1 – инвертированный ШИМ (сброс выхода при переполнении таймера и установка при совпадении с регистром сравнения);
В данном режиме доступны как прерывание по переполнению так и по совпадению с регистром сравнения, в обработчиках которых можно регулировать скважность выходного сигнала от 0 до 100%, изменяя содержимое регистра сравнения OCRn. При этом: скважность = OCRn/256.
Такая классификация предлагается как минимум в книге Евстифеева А.В. «Микроконтроллеры AVR семейств Tiny и Mega фирмы Atmel», подробнее смотрите раздел 3.5. Возможно где-то ещё. Исполнения отличаются набором функций, например, асинхронное тактирование доступно только в исполнении №3 и параметрами, например, набором коэффициентов деления предделителя.
Конечно, возможно. На 16-и разрядных таймерах на прерываниях от нескольких регистров сравнения организуется многоканальный ШИМ. Например, на таймере T3 в atmega64/128 аж три регистра сравнения и каждый связан с отдельным выходом, а значит можно получить три независимых канала ШИМ на одном таймере. Что же касается ручного использования нескольких совпадений, то это вопрос Вашего личного творчества, мне такой вариант реализовывать просто не доводилось. Пример кода тут ничего интересного из себя не представляет, просто нужно разрешить не одно, а сразу несколько прерываний ну и прописать соответствующие обработчики.
Вы немогли бы разяснить почему в Вашем примере кода для ATmega16 - прерывание по T2 каждые 100 тактов генератора, регистру TCNT2 присваивается значение (256-100)+3? 256-100 это понятно, а плюс три непонятно.
Если всё правильно помню, три такта МК тратит на сложение и запись регистра счётчика при выполнении инициализации TCNT2+=(256-100)+3; P.S. Работу таймера очень удобно проверять в симуляторе.
Разумеется, таймер тикает аппаратно и независимо от чего бы то ни было. При делителе равном 1, синхронно с тактовыми импульсами. Но задача записи TCNT2+=(256-100)+3; в том, чтобы прибавить к текущему значению TCNT2 число 156 и записать его обратно в TCNT2. Задача выполняется в три действия: 1. Чтение текущего значения TCNT2. 2. Сложение считанного значения с константой 256-100. 3. Запись результата сложения в TCNT2. На шаге 3 значение TCNT2 считанное на шаге 1 успевает устареть на 3 такта, это и учитывает константа 3.
Но при больших значениях делителя таймера, он уже не успеет увеличить своё значение за три такта. Все частные случаи надёжнее и быстрее проверять в симуляторе. Режим “сброс при совпадении” самый простой с этой точки зрения, там инициализация таймера (сброс в ноль) происходит автоматически в следующем после совпадения такте таймера (отсюда минус 1 в значении регистра совпадения).
По-моему, в выше приведенном примере нет смысла считывать значение TCNT2, поскольку оно предопределено при вызове прерывания и имеет фиксированное значение (прерывание по переполнению вызывается при переходе от 255 к 0). лучше сразу загонять в TCNT2 значение (255 - Delay) минус задержки на исполнение кода.
...лучше сразу загонять в TCNT2 значение (255 - Delay) минус задержки на исполнение кода.
А если непосредственно перед пере-инициализацией счётчика вызовется другое прерывание?! Тогда к моменту возврата в текущий обработчик и записи TCNT2 счётчик накрутит неизвестное и неопределённое количество тактов, которые тоже нужно будет учесть.
Пардон к предыдущему посту. задержку (вызов прерывания - 4 такта) надо плюсовать и не минусовать. Т.е., если надо задержку в 100 тактов загоняем в прерывании TCNT2=255-100+4=159. Через 96 тактов счетчик переполниться, еще через 4 такта попадем в обработчик прерывания. Кстати, если используется предделитель, его тоже нужно сбрасывать и кроме того, задержку в этом случае прямо плюсовать нельзя, надо прибавлять (4 div Коэф_деления_предделителя).
..А если непосредственно перед пере-инициализацией счётчика вызовется другое прерывание?!
При входе в прерывание все остальные прерывания автоматически запрещаются. Поэтому перед пере-инициализацией счётчика в самом прерывании никакое прерывание произойти не может. Другое дело, если произойдет задержка вызова прерывания таймера из-за того, что в момент его вызова обрабатывалось другое прерывание и в нем принудительно не были разрешены другие прерывания (с помощью команды asm SEI). Но в этом случае уже ничего не поможет- момент вызова прерывания всё равно испорчен, независимо от того учитывали мы текущее состояние счетчика или нет, да и учитывать несколько тактов задержки вообще тогда теряет смысл. Симулятор тоже не поможет, если мешающие прерывания происходят асинхронно в неизвестный момент времени.
Я как раз и имел в виду вариант с разрешением вложенных прерываний в начале обработчика… Всех этих проблем нет в режиме автоматического сброса при совпадении, естественно лучше пользоваться им.
P.S. Кстати почему бесполезно, если вызов задержится в другом прерывании? Таймер всё это время будет тикать, ему безразлично вызвалось ли прерывание, а чтение счётчика тут как раз и поможет учесть задержку, которую таймер натикал в чужом прерывании.
..P.S. Кстати почему бесполезно, если вызов задержится в другом прерывании? Таймер всё это время будет тикать, ему безразлично вызвалось ли прерывание, а чтение счётчика тут как раз и поможет учесть задержку, которую таймер натикал в чужом прерывании.
Я просто имел ввиду ту задержку, которая может возникнуть ДО входа в прерывание счетчика, а не внутри (насчет внутренней задержки - любой, не толька по прерыванию, из-за исполнения кода в обработчике тоже - согласен). Если в момент вызова прерывания счетчика будет обрабатываться другое прерывание и прерывания в нем не будут разрешены, то обработчик таймера вызовется с неконтролируемой задержкой. Если такая ситуация потенциально возможна, то как точно ты чего не рассчитывай - будет ошибка в моменте ВХОДА в прерывание (что у меня, что у тебя - ну тут уже клинический случай - надо оптимизировать алгоритм работы всей системы).
Вообще я просто предлагаю чуть упростить обработчик и всё (иногда сэкономить несколько байт может и не особо надо - но так приятно). Смысл в том, что при входе в прерывание таймера прерывания запрещены и значение счетчика ФИКСИРОВАНО, т.е const, тогда какой смысл его считывать (оно всегда одинаково)? - клинический случай, описанный выше я тут не рассматриваю по причинам изложенным там же выше. Т.Е. предлагаю: СРАЗУ ПРИ ВХОДЕ в обработчик таймера: 1. останов счетчика 2. сброс предделителя 3. запись нового значения в счётчик 4. запуск счетчика
пп. 1, 2, 4 только, если используется предделитель.
Ну и оч.желательно, где можно, разрешать вложенные прерывания в других обработчиках.
PS. Формально учет текущего значения вроде улучшает ситацию - время запроса на следующее прерывание при наличии задержки во вход в текущее прерывание таймера будет отсчитываться правильно . Но в такой ситуации код, находящийся в обработчике (а мы ведь пишем обработчик для какой-то конкретной цели, не так ли?), всё равно выполнится с непредсказуемой задержкой. Какой смысл от точного таймера, если код выполниться неизвестно когда? В моем варианте один цикл счетчика удлиниться, но зато между полезным кодом текущего обработчика и последующего пройдет одинаковое количество времени (если опять откуда-нибудь что-нибудь не прилетит).
По-моему так, хотя, конечно могу ошибаться. Конечно, надо смотреть конкретные варианты.