This is an old revision of the document!
Счётчики (англ. counter), в некотором смысле таймеры (англ. timer), являются одними из важнейших дополнительных функций микроконтроллеров. С их помощью можно регулировать процессы точно по времени, генерировать сигналы и считывать события. Рабочий принцип счётчика состоит в том, что он преобразовывает число входных тактов в бинарное значение при помощи цепочки триггеров. От длины цепочки зависит максимальное считываемое количество тактов, которое обозначается длинной двоичного кода. Счётчики микроконтроллера AVR 8- и 16-битные. Если счётчик достигает максимального значения (у 8-битных 255, 16-битных 65535), то при следующем такте происходит переполнение (англ. overflow) и счётчик обнуляется. Тактовый сигнал может приходить из рабочего такта микроконтроллера и в этом случае можно уменьшить его частоту при помощи делителя частоты (англ. prescaler). У некоторых AVR имеется отдельный внутренний генератор тактовых сигналов, частоту которого можно увеличить с помощью умножителя частоты. Счётчики различаются так же по возможностям применения и рабочим режимам.
В стандартном режиме счётчик не выполняет других функций кроме постоянного считывания последовательных номеров. Значение счётчика в программе можно в любой момент считать и изменить. Единственная дополнительная возможность в стандартном режиме счётчика – это вызвать прерывание при переполнении счётчика. Стандартный режим обычно используется для заполнения какого-либо отрезка программы в определённые интервалы времени.
Пример
Требуется ATmega128, работающий на частоте 8 MHz, заставить совершать прерывания через каждые 10 ms (частота 100 Hz). Для задания подходит 8-битный счётчик 0.
#include <avr/interrupt.h> 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 есть возможность выбора между двумя событиями: изменение логического значения результатов сравнения специального входного вывода или аналогового компаратора. Если происходит выбранное событие, значение счётчика записывается в специальный регистр, где его можно вычитать в желаемое время. Если время происхождения события длиннее, чем время переполнения счётчика, следует программно считать и переполнения счётчика (например, прерыванием переполнений) и учесть их в конечном результате.
Пример
Vaja on 8 MHz taktsagedusel töötava ATmega128-ga mõõta välise 122 Hz - 100 kHz loogilise nelinurksignaali sagedust 1 Hz täpsusega. Programm on tehtud 16-bitise loendur 1 sündmuste püüdjaga.
#include <avr/interrupt.h> unsigned long frequency; // Sündmuse toimumise katkestus ISR(TIMER1_CAPT_vect) { // Loenduri nullimine TCNT1 = 0; // Tulemus on ainult siis arvestatav, kui // loendur pole vahepeal üle täitunud if (!(TIFR & (1 << TOV1))) { // Sageduse arvutamine perioodi pöördväärtusest. frequency = (unsigned long)8000000 / (unsigned long)ICR1; } else { // Sagedus on vähem kui 122 Hz frequency = 0; // Loenduri ületäitumise lipukese nullimine TIFR &= ~(1 << TOV1); } } int main() { // Tõusva frondi registreerimine, sagedusjaguri tegur 1 TCCR1B = (1 << ICES1) | (1 << CS10); // Sündmuse toimumise katkestuse lubamine TIMSK = (1 << TICIE1); // Globaalne katkestuste lubamine sei(); // Lõputu programmitsükkel while (1) continue; }
Programmis tekib välise signaali tõusva frondi ajal sündmuse katkestus. Katkestuse jooksul kontrollitakse, ega loenduri ületäitumine pole toimunud - see saab juhtuda, kui signaali sagedus on alla 122 Hz (8 MHz / 216), ja sel juhul ei kajasta loenduri väärtus reaalset perioodi. Sagedus arvutatakse 32-bitiste arvudega pöördväärtusena perioodist. Esimese asjana aga nullitakse loendur, sest taimer töötab samal taktil mis protsessor ja iga instruktsiooni täitmine, mis toimub pärast välist sündmust, lühendab mõõdetavat perioodi ning sellest tulenevalt rikub ka mõõtetulemust. Maksimaalsele mõõdetavale sagedusele seab piiri katkestuse programmiosa tööaeg.
Sündmuste püüdmist ning nende aja registreerimist saab teha ka tarkvaraliselt. Saab kasutada väliseid või muid katkestusi ja nende tekkimise ajal lugeda loenduri väärtus. Riistvaraline sündmuste püüdmine on siiski mõeldud eeskätt programmist sõltumatuks töötamiseks ja suhteliselt lühiajaliste (või tihedate) sündmuste mõõtmiseks.
Keerukamate loenduritega saab peale signaali pikkuse mõõtmise ka signaali tekitada. Selleks on loenduril väärtuse võrdlemise üksus (inglise keeles output compare unit) ja võrdlustulemuse väljastusüksus (inglise keeles compare match output unit). Võrdlusüksusesse kuuluvad registrid sama bitilaiusega kui loendur ise ja nende registrite väärtusi võrreldakse loenduri väärtusega selle töö ajal. Hetkel, mil loenduri väärtus saab võrdseks võrdlusüksuse registri väärtusega, saab tekitada katkestuse ja spetsiaalsete väljundviikude oleku muutuse. Valida on võrdusmomendil viigu kõrgeks muutumise, madalaks muutumise ja ümbermuutumise vahel. Väljundviigu oleku muutused tekitavadki signaali.
Mõnedel signaali genereerimise režiimidel on määratav ka loenduri suurim väärtus - loenduri füüsiline suurus jääb küll samaks, kuid mängus on võrdlusregister, mille väärtust ületades loendur nullitakse. Seda võimalust kasutades saab eespool toodud ülesandeid täpse ajalise katkestuse tekitamise kohta lahendada, kuid mõeldud on see pigem signaali perioodi muutmiseks. Lisaks sellele on võimalik loendurit seadistada režiimi, kus see toimib nii juurde- kui mahalugemisel.
Loendurid ja nende abil genereeritavate signaalide režiimid on ühed keerulisemad perifeeriamoodulid AVR-il. Kõigist neist kirjutamine läheks pikaks ja enamasti pole nende kasutamisel kõike detailselt teada. Seetõttu on järgnevalt kirjeldatud vaid üht levinuimat PWM signaali robootikas. Ülejäänut saab juba AVR dokumentatsioonist järgi uurida.
Pulsilaius-modulatsioon (inglise keeles pulse width modulation, lühend PWM) on signaali tüüp, mille sagedus ja ühtlasi ka perioodid on konstantne (enamasti), kuid mõlema poolperioodi pikkus on muutuv. PWM signaale kasutatakse elektromehaaniliste, optiliste jms. seadmete juhtimiseks. Näiteks mudelismist tuntud servomootorite PWM signaal on 50 Hz sagedusega ja 1 ms kuni 2 ms pikkuse kõrge poolperioodiga.
Näide
Vaja on 8 MHz taktsagedusel töötava ATmega128-ga genereerida kaks kiirusreguleeritavat servomootori signaali. Viiguga PB5 (OC1A) tuleb genereerida pulsipikkus 1 ms ja viiguga PB6 (OC1B) pulsipikkus 2 ms.
#include <avr/io.h> int main() { // Viigud väljundiks DDRB |= (1 << PIN5) | (1 << PIN6); // Väljundid A ja B võrdusmomendil madalaks, // "Fast PWM" režiim, sagedusjagur 8 TCCR1A = (1 << COM1A1) | (1 << COM1B1) | (1 << WGM11); TCCR1B = (1 << WGM13) | (1 << WGM12) | (1 << CS11); // Suurim loenduri väärtus. Valem: // TOP = 8 MHz / 8 / 50 Hz ICR1 = 20000; // Esimese mootori poolperiood 1 ms, teisel 2 ms OCR1A = 1000; OCR1B = 2000; // Lõputu programmitsükkel while (1) continue; }