Необходимые знания: [HW] Kontrollermoodul, [AVR] Loendurid/Taimerid, [LIB] Sisend-väljundviigud, [LIB] Viide, [LIB] Taimerid, [PRT] Tarkvaraline viide
Программная задержка не единственный метод создания пауз. То же самое можно сделать и при помощи таймера. Таймер – это аппаратный счетчик, увеличивающийся или уменьшающийся с определенной частотой. Тактовый сигнал счетчика, как правило, можно создать из рабочего такта микроконтроллера или из другого внешнего такта. В целом, частоту тактового сигнала можно поделить каким-либо умножителем частоты для получения меньшей тактовой частоты – это делается тактовым делителем, который на английском языке называется prescaler. Важным фактом является то, что значение счётчика с фиксированной тактовой частотой находится в линеарной зависимости от времени. Время можно рассчитать, умножив тактовый сигнал счетчика на значение периода счетчика.
AVR счетчик можно заставить сообщать о переполнении счетчика (на английском overflow) или при достижении определенного значения (на английском compare match).Переполнение создается в тот момент, когда счетчик приобрел максимально возможное значение и начинает заново считать от нуля. При достижении конкретного значения, в момент увеличения счётчика, происходит сравнение нового значения со значением, заданным пользователем. В момент возникновения события, соответствующие биты в регистре состояния AVR автоматически настраиваются верхним.
Для того, чтобы создать задержку таймером, достаточно лишь настроить счетчик и ожидать достижения верхнего значения разряда состояния. В отличии от программных задержек, работа таймера не зависит от компилятора, что делает их использование надежней. Наряду с этим, по причине разнообразия (или сложности) счётчиков AVR их налаживание может показаться затруднительным. В зависимости от сигнала такта микроконтроллера может так же случиться то, что он не делится на желаемый период задержки и задержка не точна.
Находящийся ниже программный код является функцией задержки основанной на таймере, которая немного упрощена. Принцип считывания такой же, как и при программной функции задержки – создается желаемое число задержек длинной в 1 мс. Для создания задержки используется 8-битный ATmega128 счетчик 0. Ранее уже было вычислено, что при тактовой частоте 14,7456 Mhz тактовый сигнал счетчика должен быть разделен по крайней мере на 64, чтобы в течении 1 мс 8-битный счетчик не переполнился. То, каким значением должен обладать счетчик, чтобы переполнение происходило через 1 мс, представлено в виде выражения и присвоено переменной timer_start. F_CPU на макро языке является константой, которая показывает тактовую частоту в герцах. В случае упомянутой тактовой частоты значение счетчика должно быть 25,6, но т.к. число с дробью использовать нельзя, то начальное значение счетчика будет 26. К сожалению, здесь возникает ошибка во времени задержки, но она довольно маленькая (-1,7 μs).
В цикле происходит инициализация счетчика и обнуление флага переполнения (вписывая туда 1). В этом случае ожидается пока счетчик считает начальное значение до 256, т.е. до переполнения. В момент переполнения, флаг переполнения идет вверх и происходит 1 мс задержка. В конце функции таймер останавливается.
// // Аппаратная задержка в миллисекундах // void hw_delay_ms(unsigned short count) { // Вычисление начального значения таймера register unsigned char timer_start = 256 - F_CPU / 1000 / 64; // Запуск таймера timer0_init_normal(TIMER0_PRESCALE_64); // Счет задержки переменной до нуля while (count-- > 0) { // Инициализация счетчика timer0_set_value(timer_start); // Обнуление флага переполнения timer0_overflow_flag_clear(); // Ожидание переполнения while (!timer0_overflow_flag_is_set()) { asm volatile ("nop"); } } // Обнуление флага переполнения timer0_overflow_flag_clear(); // Остановка таймера timer0_stop(); }
Представленная функция задержки использует библиотеку таймеров, исходный код которого выглядит следующим образом:
// // Выбор типа делителя такта таймера 0 // typedef enum { TIMER0_NO_PRESCALE = 0x01, TIMER0_PRESCALE_8 = 0x02, TIMER0_PRESCALE_32 = 0x03, TIMER0_PRESCALE_64 = 0x04, TIMER0_PRESCALE_128 = 0x05, TIMER0_PRESCALE_256 = 0x06, TIMER0_PRESCALE_1024 = 0x07 } timer0_prescale; // // Настройка таймера 0 в нормальный режим // inline void timer0_init_normal(timer0_prescale prescale) { TCCR0 = prescale & 0x07; } // // Остановка таймера 0 // inline void timer0_stop() { TCCR0 = 0x00; } // // Обозначение значения счетчика таймера 0 // inline void timer0_set_value(unsigned char value) { TCNT0 = value; } // // Обнуление флага переполнения таймера 0 // inline void timer0_overflow_flag_clear(void) { bit_set(TIFR, TOV0); } // // Считывание состояния флага переполнения таймера 0 // inline bool timer0_overflow_flag_is_set(void) { return (bit_is_set(TIFR, TOV0) ? true : false); }
Далее приведена такая же программа как и в примере программной задержки. В коротком 100 мс полупериоде загорается LED, в длинном 900 мс полупериоде он потухает. В результате мигает LED через каждую секунду. К сожалению, и в этом примере период не равен 1 секунде, потому что заполнение других функций программы в каждом цикле занимает время. Для точного расчёта времени придется использовать 16-битовый таймер вместе с перерывами.
// // Демонстрационная программа аппаратной задержки Домашней Лаборатории. // Программма мигает каждую секунду LED-ом. // #include <homelab/pin.h> #include <homelab/delay.h> // // Тест обозначение вывода LED-а // pin debug_led = PIN(B, 7); // // Основная программа // int main(void) { // Настройка вывода LED-а выходом pin_setup_output(debug_led); // Бесконечный цикл while (true) { // Зажигание LED-а pin_clear(debug_led); // Аппаратная задержка в 100 миллисекунд hw_delay_ms(100); // Выключение LED-а pin_set(debug_led); // Аппаратная задержка в 900 миллисекунд hw_delay_ms(900); } }