====== Счётчики/Таймеры ====== Счётчики (англ. //counter//), в некотором смысле таймеры (англ. //timer//), являются одними из важнейших дополнительных функций микроконтроллеров. С их помощью можно регулировать процессы точно по времени, генерировать сигналы и считывать события. Рабочий принцип счётчика состоит в том, что он преобразовывает число входных тактов в бинарное значение при помощи цепочки триггеров. От длины цепочки зависит максимальное считываемое количество тактов, которое обозначается длинной двоичного кода. Счётчики микроконтроллера AVR 8- и 16-битные. Если счётчик достигает максимального значения (у 8-битных 255, 16-битных 65535), то при следующем такте происходит переполнение (англ. //overflow//) и счётчик обнуляется. Тактовый сигнал может приходить из рабочего такта микроконтроллера и в этом случае можно уменьшить его частоту при помощи делителя частоты (англ. //prescaler//). У некоторых AVR имеется отдельный внутренний генератор тактовых сигналов, частоту которого можно увеличить с помощью умножителя частоты. Счётчики различаются так же по возможностям применения и рабочим режимам. ===== Стандартный режим счётчика ===== В стандартном режиме счётчик не выполняет других функций кроме постоянного считывания последовательных номеров. Значение счётчика в программе можно в любой момент считать и изменить. Единственная дополнительная возможность в стандартном режиме счётчика – это вызвать прерывание при переполнении счётчика. Стандартный режим обычно используется для заполнения какого-либо отрезка программы в определённые интервалы времени. Требуется ATmega128, работающий на частоте 8 MHz, заставить совершать прерывания через каждые 10 ms (частота 100 Hz). Для задания подходит 8-битный счётчик 0. #include ISR(TIMER0_OVF_vect) { // Придание счётчику такого значения, // чтобы следующее переполнение происходило через 10 ms. // Формула: 256 - 8 MHz / 1024 / 100 Hz = 177,785 = ~178 TCNT0 = 178; } int main() { // Для того, чтобы первое прерывание переполнения произошло // через 10 ms, следует и здесь обнулить счётчик. TCNT0 = 178; // Коэффициент делителя частоты 1024 TCCR0 = 0x07; // Разрешение прерывания заполнения счётчика TIMSK |= (1 << TOIE0); // Разрешение глобального прерывания sei(); // Бесконечный цикл программы while (1) continue; } Счётчик в данном примере не будет совершать прерывания ровно через 10мс, так как для этого счётчику нужно присвоить десятичное значение, а это невозможно. Для того чтобы получить точный интервал прерывания, следует коэффициент делителя частоты и значение, получаемое счётчиком при заполнении выбирать так, чтобы их деление было точным. К сожалению, это не всегда возможно, особенно в случае с 8-битным счётчиком, так как его шкала значений достаточна мала. Для получения более точного и широкого интервала можно использовать 16-битный счётчик. ==== Счётчик с внешним тактом ==== В качестве тактового сигнала счётчика можно использовать внешний сигнал микроконтроллера (англ. //external clock source//). Для этого на AVR имеется вывод Tn, где n обозначает номер счётчика. Внешний сигнал такта и полярность можно выбрать с помощью регистра делителя частоты. ==== Измерение событий ==== Так как счётчики позволяют измерять время, на более сложных микроконтроллерах AVR есть возможность с помощью аппаратного обеспечения измерить время, в которое произошло какое-либо событие. Эта часть счётчика называется фиксатором событий (англ. //input capture unit//). В AVR есть возможность выбора между двумя событиями: изменение логического значения результатов сравнения специального входного вывода или аналогового компаратора. Если происходит выбранное событие, значение счётчика записывается в специальный регистр, где его можно вычитать в желаемое время. Если время происхождения события длиннее, чем время переполнения счётчика, следует программно считать и переполнения счётчика (например, прерыванием переполнений) и учесть их в конечном результате. Требуется при помощи работающего на тактовой частоте в 8 MHz ATmega128 измерить частоту внешнего 122 Hz - 100 kHz логического прямоугольного сигнала с точностью в 1 Hz. Программа использует фиксатор событий 16-битного счётчика 1. #include unsigned long frequency; // Прерывание совершения событий ISR(TIMER1_CAPT_vect) { // Обнуление счётчика TCNT1 = 0; // Результат является истинным только в случае, // если счётчик не переполнялся if (!(TIFR & (1 << TOV1))) { // Расчёт частоты из обратной величины периода. frequency = (unsigned long)8000000 / (unsigned long)ICR1; } else { // Частота меньше, чем 122 Hz frequency = 0; // Обнуление флага при переполнении счётчика TIFR &= ~(1 << TOV1); } } int main() { // Регистрация нарастающего фронта, коэффициент делителя частоты 1 TCCR1B = (1 << ICES1) | (1 << CS10); // Разрешение прерывания совершения события TIMSK = (1 << TICIE1); // Разрешение глобальных прерываний sei(); // Бесконечный цикл программы while (1) continue; } В программе происходит прерывание события во время нарастающего фронта внешнего сигнала. Во время прерывания проверяется, не произошло ли переполнение счётчика – это может случиться, если частота сигнала меньше 122 Hz (8 MHz / 216), и в таком случае значение счётчика не отображает реальный период. Частота высчитывается с помощью 32-битных чисел для получения обратной величиной периода. Первым делом обнуляется счётчик, так как таймер работает на том же такте, что и процессор и выполнение каждой инструкции, которая происходит после внешнего события, уменьшает измеряемый период и, исходя из этого, искажает результат измерения. Рабочее время отрезка программы прерывания ограничивает максимальную измеряемую частоту. Фиксацию событий и регистрацию их времени можно так же совершать программно. Можно использовать внешние и другие прерывания и во время их возникновения считывать значение счётчика. Аппаратная фиксация событий всё же, прежде всего, предназначена для работы вне зависимости от программы и для измерения достаточно кратковременных или частых событий. ===== Генерирование сигнала ===== С помощью более сложных счётчиков можно помимо измерения длинны сигнала так же создавать сигналы. Для этого у счётчика есть компаратор (блок сравнения) значений (англ. //output compare unit//) и блок вывода результатов сравнения (англ. //compare match output unit//). Регистры компаратора имеют ту же битовую ширину, что и сам счётчик и значения их регистров сравниваются со значениями счётчика во время его работы. В тот момент, когда значение регистра компаратора сравняется со значением счётчика, можно совершить прерывание и изменение состояния специальных выходных выводов. В момент сравнения вывод может быть установлен в высокое, низкое или обратное положение. Изменения состояния выходного вывода генерируют сигнал. В некоторых режимах генерирования сигнала максимальное значение счётчика может быть изменено. Физическая величина счётчика остаётся такой же, но он обнуляется при превышении значения регистра сравнения. Используя эту возможность, можно решить вышеприведённые задания, но это скорее предусмотрено для изменения периода сигнала. Так же счётчик можно настроить в режим прямого (увеличение) и обратного (уменьшение) счёта. Счётчики, и генерируемые с их помощью режимы сигналов, являются одними из самых сложных периферических модулей в AVR. Их описание выходит за рамки данного текста, и в большинстве случаев нет необходимости знать все аспекты их использования. В связи с этим, далее приведён только один из них, наиболее распространённый в роботике, сигнал ШИМ (широтно-импульсная модуляция). Информацию об остальных можно получить в документации к AVR. ==== Широтно-импульсная модуляция ==== ШИМ (англ. //pulse width modulation//) – это тип сигнала, частота и период (обычно), которого константен, но длинна полупериодов меняется. Сигналы ШИМ используются для управления электромеханическими, оптическими и другими приборами. Например, сервомотор, известный в моделировании, использует сигнал ШИМ с частотой в 50 Hz и высокий полупериод длинной от 1 ms до 2 ms. Используя 8 MHz ATmega128, требуется сгенерировать два регулирующих скорость сигнала сервомотора. Выводом PB5 (OC1A) следует сгенерировать длину пульса в 1 ms и выводом PB6 (OC1B) длину пульса в 2 ms. #include int main() { // Установить выводы выходами DDRB |= (1 << PIN5) | (1 << PIN6); // Выходы A и B во время сравнения низкими, // режим "Fast PWM", делитель частоты 8 TCCR1A = (1 << COM1A1) | (1 << COM1B1) | (1 << WGM11); TCCR1B = (1 << WGM13) | (1 << WGM12) | (1 << CS11); // Наибольшее значение счётчика. Формула: // TOP = 8 MHz / 8 / 50 Hz ICR1 = 20000; // Полупериод первого мотора 1 ms, второго 2 ms OCR1A = 1000; OCR1B = 2000; // Бесконечный цикл программы while (1) continue; }