В Arduino на основе ATmega328 используются все три имеющихся в микроконтроллере таймера. Таймер 0 задействован в функциях «определения времени» (millis(), micros()). Таймер 1 и Таймер 2 вместе с Таймер 0 используются в процедурах analogWrite() для формирования на некоторых выводах микроконтроллера ШИМ импульсов.
Может быть полезным — знать в каком режиме работают таймеры после включения питания платы Arduino, или подачи сигнала сброса…
В «становлении» работы таймеров принимает участие процедура init(), описанная в файле wiring.c
void init() { sei(); //---------------- Timer0 ------------------------ // on the ATmega168, timer 0 is also used for fast hardware pwm sbi(TCCR0A, WGM01); sbi(TCCR0A, WGM00); // set timer 0 prescale factor to 64 sbi(TCCR0B, CS01); sbi(TCCR0B, CS00); // enable timer 0 overflow interrupt sbi(TIMSK0, TOIE0); //-------------------------------------------------- // timers 1 and 2 are used for phase-correct hardware pwm //---------------- Timer1 ------------------------- TCCR1B = 0; // set timer 1 prescale factor to 64 sbi(TCCR1B, CS11); sbi(TCCR1B, CS10); // put timer 1 in 8-bit phase correct pwm mode sbi(TCCR1A, WGM10); //---------------- Timer2 ------------------------- // set timer 2 prescale factor to 64 sbi(TCCR2B, CS22); // configure timer 2 for phase correct pwm (8-bit) sbi(TCCR2A, WGM20); //---------------- USART -------------------------- // the bootloader connects pins 0 and 1 to the USART; disconnect them // here so they can be used as normal digital i/o; they will be // reconnected in Serial.begin() UCSR0B = 0; }
Выше приведенный текст — это «очищенная от иных, чем ATmega48/88/168/328 версий микроконтроллера» процедура init() — так легче в ней разобраться. Процедура init() «срабатывает» перед запуском процедуры setup() любого скетча, написанного для Arduino. Посмотрим, как она определяет работу таймеров микроконтроллера:
- как ни странно, первым делом в системе разрешаются прерывания…
- затем начинается настройка Таймера 0. Установкой битов WGM00 и WGM01 в регистре TCCR0A включается режим работы Fast PWM. В таком режиме счетчик TCNT0 непрерывно считает импульсы, увеличивая значение с 0 до 0xFF, затем сбрасывая его в 0 и т.д. Это позволяет использовать прерывания по переполнению счетчика для работы millis() и, одновременно, не мешает грузить в регистр OCR0A какое-либо значение, добиваясь формирования ШИМ импульсов на выводах микроконтроллера (процедура analogWrite()).
- установка младших битов CS00 и CS01 регистра TCCR0B в единицу включает «предделитель» частоты на входе счетчика таймера в значение 64. Т.е. на вход счетчика идут импульсы с частотой 16МГц поделенной на 64, т.е. 250КГц (период следования равен 4 мкс). Сам счетчик TCNT0 байтовый, таким образом получаем период переполнений счетчика равный 1024 мкс — для millis() почти что «самое то!» — как это преобразуется в 1 мс описано тут.
- в конце настройки Таймера 0 устанавливается маска прерываний от таймера на срабатывание по переполнению.
- после нулевого, процедура init() занимается настройкой Таймера 1. В-общем, все тоже самое, что и с Таймером 0, с поправкой на использование режима Phase Correct PWM вместо Fast PWM. Отметим, что прерывания от Таймера 1 запрещены.
- второй таймер в настройке аналогичен Таймеру 1 (со своей спецификой имен регистров). Прерывания от Таймера 2 также запрещены.
- нижняя строчка процедуры «выключает» USART микроконтроллера, чтобы можно было пользоваться pin0 и pin1 как обычными цифровыми линиями. Вспомним, что эти линии в процессе загрузки программы принимают самое активное участие, как линии RxD и TxD USART.
Таким образом, после включения питания, перед началом работы скетча (его начальной процедуры setup()), таймеры микроконтроллера платы Arduino работают почти одинаково: «весело» считая 250 килогерцовые импульсы своими 8-битными счетчиками. При этом Таймер 0 после переполнения вызывает прерывание, которое обрабатывается ранее описанной процедурой. Все три таймера готовы к восприятию в регистры OCRnA байта формирующего ШИМ на некоторых выводах платы (об этом как-нибудь в следующий раз)…