Журнал LinuxFormat - перейти на главную

LXF159:Arduino: Управляем временем

Материал из Linuxformat
Перейти к: навигация, поиск


Вла­ст­вуй­те над сво­ей пла­той с по­мо­щью пре­ры­ва­ний тай­ме­ра

Содержание

Arduino: Управляем вре­ме­нем

Ник Вейч при­ни­ма­ет вы­зов вре­ме­ни и ов­ла­де­ва­ет по­сто­ян­но лязгаю­щи­ми ча­са­ми, что­бы при­ду­мать нечто хит­роумное с ти­ка­ми и та­ка­ми.

(thumbnail)
Наш эксперт. Ко­гда LXF толь­ко поя­вил­ся, его дер­жа­ли на пла­ву исключительно скрип­ты Bash от Ни­ка Вей­ча. По­том их за­ме­ни­ли «лю­ди», и это, по мне­нию Ни­ка, ста­ло ша­гом на­зад...

Вре­мя те­чет неумо­ли­мо. Из­ме­ряе­те ли вы его в на­но­се­кун­дах или в сутках до сле­дую­ще­го дня ро­ж­дения, вам не по­ме­ша­ет быть точ­ным, и это од­на из при­чин, по ко­то­рым в стан­дарт­ной схе­ме ATmega 328 (ис­поль­зуе­мой в пла­тах Arduino Duemilanove и Uno) уста­нов­ле­ны не один, а три от­дель­ных тай­ме­ра. И не глу­по ли, что с по­мо­щью стан­дарт­но­го ко­да Arduino мож­но де­лать немно­гим боль­ше, чем счи­тать с их по­мо­щью мил­ли­се­кун­ды? При­ро­да встро­ен­ных тай­ме­ров – од­но из ве­ли­ких и кра­си­вых чу­дес, не го­во­ря уже об их немалой поль­зе. На этом уро­ке мы зай­мем­ся ми­гаю­щи­ми све­то­дио­да­ми, при­чем сде­ла­ем это пра­виль­но. В кон­це мы по­лу­чим фе­но­ме­наль­но точ­но ми­гаю­щий свет, ко­то­рый бу­дет ми­гать по­сто­ян­но, что бы вы ни вы­тво­ря­ли в осталь­ной час­ти ко­да. И вы уз­нае­те, как ис­крив­лять са­му ткань вре­мени. На­вер­ное.

Что нам на­до

» Arduino Uno или Duemilanove.

» Мак­си­маль­но со­сре­до­то­чить­ся.

По­раз­ряд­ная ариф­ме­ти­ка

В лю­бом ко­де, ко­то­рый име­ет де­ло с эти­ми нуд­ны­ми ре­ги­ст­ра­ми, ря­дом с обо­зна­чения­ми ре­ги­ст­ров вы час­то встре­ти­те стран­ные за­ко­рюч­ки, ка­жет­ся, при­зван­ные еще боль­ше за­пу­тать смысл. Не бой­тесь! Это про­сто опе­ра­то­ры по­раз­ряд­ной дво­ич­ной ариф­ме­ти­ки

| = OR (ИЛИ)

& = AND (И)

~ = NOT (НЕ)

^ = XOR (ИЛИ-НЕ), или «ис­клю­чаю­щее ИЛИ» (eXclusive OR)

Ес­ли они идут пе­ред зна­ком ра­вен­ст­ва, это оз­на­ча­ет, что пер­вый опе­ранд – ис­ход­ная пе­ре­мен­ная. На­при­мер, вы­ра­же­ние

PORTB |= B00100000; 


ана­ло­гич­но

PORTB = PORTB | B00100000;

А по­че­му мы во­об­ще восполь­зо­ва­лись опе­ра­то­ром OR? По­то­му что с его по­мо­щью мож­но уста­но­вить один бит, не тро­гая дру­гие би­ты это­го бай­та. Ес­ли нуж­но сбро­сить бит, восполь­зуй­тесь опе­ра­то­ром AND с ну­лем:

PORTB &= B11011111;

На прак­ти­ке дво­ич­ные зна­чения обыч­но так не за­пи­сы­ва­ют­ся, по­то­му что при ра­бо­те с ре­ги­ст­ра­ми про­ще и безо­паснее поль­зо­вать­ся со­кра­щения­ми, опи­сан­ны­ми в стан­дарт­ных за­го­ло­воч­ных фай­лах. Так, на­при­мер, циф­ро­вой вы­вод 13 Arduino (так­же из­вест­ный в до­ку­мен­та­ции по Atmel как PORTB, бит 5) со­кра­щен­но обо­зна­ча­ет­ся как PB5. Есть два об­щих спо­со­ба их ис­поль­зо­вания. На схе­ме сле­ва внизу по­ка­зан пер­вый спо­соб.

Функ­ция _BV() – про­сто мак­рос (со­кра­щение от “Bit Value” – «зна­чение би­та»); он воз­вра­ща­ет зна­чение ука­зан­но­го в скоб­ках со­кра­щения би­та в ви­де дво­ич­но­го чис­ла. На схе­ме внизу спра­ва по­ка­зан дру­гой спо­соб их ис­поль­зо­вания. Это та же са­мая опе­ра­ция. Вме­сто ис­поль­зо­вания за­ранее за­дан­но­го мак­ро­са в ней про­сто сдви­га­ет­ся «единица» на ко­ли­че­­ст­во бит, определяе­мое зна­чением со­кра­щения. Так, (1 << PB5) ста­но­вит­ся «00100000». Вот в чем удоб­ство со­кра­щен­ных обо­зна­чений. Будь код за­со­рен двоич­ны­ми зна­чения­ми, его бы­ло бы труднее по­нять. Что­бы осоз­нать позицию бита в чис­ле «00100000», явно нуж­но по­тра­тить боль­ше вре­мени, чем взгля­нув на PB5.

Ес­ли вы хо­ти­те га­ран­ти­ро­ван­но сбро­сить бит, мож­но восполь­зо­вать­ся опе­ра­то­ром NOT в со­че­тании с лю­бым ме­то­дом:

PORTB &= ~_BV(PB5); 


Здесь бе­рет­ся двоичное зна­чение PB5 (00100000) и ин­вер­ти­ру­ет­ся (11011111), а за­тем объ­е­ди­ня­ет­ся по «И» с те­ку­щим со­дер­жи­мым ре­ги­ст­ра, т. е. все уста­нов­лен­ные би­ты, кро­ме би­та 5, не изменятся.

Еще од­но важ­ное пре­ду­пре­ж­дение. Од­на из при­чин ме­нять толь­ко необ­хо­ди­мые би­ты в том, что мно­гим би­там отведена особая роль. На­при­мер, в PORTD по­следний бит – это линия, ис­поль­зуе­мая для прие­ма дан­ных. Слу­чай­но на­пра­вив ее на вы­вод, вы оп­ре­де­лен­но нарушите ра­бо­ту схе­мы.

(thumbnail)
> Ана­то­мия по­раз­ряд­ной опе­ра­ции с по­мо­щью мак­ро­са.
(thumbnail)
> По­раз­ряд­ные опе­ра­ции со сдви­гом зна­че­ний.

Как ра­бо­та­ют тай­ме­ры?

Ра­бо­та с тай­ме­ра­ми ка­жет­ся слож­но­ва­той, но по сути они уст­рое­ны до­воль­но про­сто. Тай­мер 1 (Timer1) – 16-бит­ный. В сво­ем са­мом про­стом ре­жи­ме он про­сто счи­та­ет цик­лы; достигнув максимума, уста­нав­ли­ва­ет бит пе­ре­полнения и на­чи­на­ет от­счет зано­ве.

Скорая помощь

Код этого урока ра­бо­та­ет с Тай­ме­ром 1 (Timer1). Мож­но бы взять и Тай­ме­р 2 (Timer2), но ре­ги­ст­ры не­мно­го от­ли­ча­ют­ся, и это все­го лишь 8-бит­ный счет­чик. Вам по­мо­жет ру­ко­вод­ство http://www.atmel.com/dyn/resources/prod_documents/doc2545.pdf.

Бу­ду­чи 16-бит­ным, он счи­та­ет до 65 535. Тай­мер в Arduino ра­бо­та­ет на час­то­те 16 МГц, то есть обыч­но пе­ре­полнение на­сту­па­ет око­ло 244 раз в се­кун­ду, или при­мер­но ка­ж­дые 4 милли­се­кун­ды. Это не ка­жет­ся слиш­ком удоб­ным, но мы еще не за­кон­чи­ли. До­ба­вим кое-ка­кие важ­ные де­та­ли. Флаг, уста­нав­ли­вае­мый при за­полнении тай­ме­ра, на­зы­ва­ет­ся весь­ма ло­гич­но – флаг пе­ре­полнения тай­ме­ра (Timer Overflow Flag), и для Тай­ме­ра 1 обо­зна­ча­ет­ся как TOV1. Этот флаг на­хо­дит­ся в спе­ци­аль­ном ре­ги­ст­ре (ячей­ке па­мя­ти) мик­ро­кон­трол­ле­ра, на­зы­вае­мой TIFR, со­кра­щение от “Timer Interrupt Flag Register [Ре­гистр фла­гов пре­ры­ваний тай­ме­ра]”. Про­сти­те за все эти на­звания, но их важ­но знать, по­сколь­ку они бу­дут ис­поль­зо­вать­ся в ко­де. Для уп­ро­щения чтения ко­да эти со­кра­щения оп­ре­де­ля­ют­ся в стан­дар­ных за­го­лов­ках AVR (это удобнее, так как ад­ре­са мо­гут раз­ли­чать­ся от уст­рой­ст­ва к уст­рой­ст­ву), и OCR1A вы­гля­дит по­нятнее, чем про­сто ад­рес 0x88. В кон­це этой ста­тьи есть неболь­шой сло­варь со­кра­щений, и мы бу­дем по­яс­нять их по ме­ре по­яв­ления.

С по­мо­щью неко­то­рых из этих ре­ги­ст­ров мы смо­жем за­дать па­ра­мет­ры ра­бо­ты тай­ме­ра, по­это­му начнем с ре­ги­ст­ров управ­ления счет­чи­ком тай­ме­ра (Timer Counter Control Register) – TCCR1A и TCCR1B.

В TCCR1A нам по­ка мож­но не вникать – он управ­ля­ет вы­во­дом в ре­жи­мах сравнения и ши­рот­но-им­пульс­ной мо­ду­ля­ции (ШИМ), ко­то­рые до­воль­но слож­ны. Мы про­сто уста­но­вим все би­ты это­го ре­ги­ст­ра в 0:

TCCR1A = 0 × 00;

А вот дру­гой ре­гистр управ­ления нам при­го­дит­ся. По­следние три би­та это­го ре­ги­ст­ра управ­ля­ют пред­ва­ри­тель­ным де­ли­те­лем так­тов. Что это та­кое? Пред­ва­ри­тель­ный де­ли­тель час­то­ты по­хож на циф­ро­вой де­ли­тель, ко­то­рый на­хо­дит­ся ме­ж­ду тай­ме­ром про­цес­со­ра и счет­чи­ком. Ес­ли уста­но­вить все би­ты в 0, тай­мер оста­нав­ли­ва­ет­ся. Ес­ли уста­но­вить толь­ко наи­мень­ший бит, сиг­нал тай­ме­ра пе­ре­да­ст­ся на счет­чик без из­менений. При дру­гих зна­чениях этих би­тов в де­ло всту­па­ет де­ли­тель – вме­сто пе­ре­да­чи ка­ж­дого им­пуль­са сис­тем­но­го тай­ме­ра он пе­ре­да­ет толь­ко один из ка­ж­дых 8, 64, 256 или 1024. Таб­ли­ца зна­чений би­тов и ра­бо­ты де­ли­те­ля вы­гля­дит так:

(thumbnail)
> До­ку­мен­та­ция по AVR, воз­мож­но, не луч­шее чте­ние на сон гря­ду­щий, но очень по­мо­га­ет по­нять на­зна­че­ние ре­ги­ст­ров, от­но­ся­щих­ся к тай­ме­ру.

000 Счет­чик ос­та­нов­лен

001 Сиг­нал тай­ме­ра

010 Сиг­нал тай­ме­ра/8

011 Сиг­нал тай­ме­ра/64

100 Сиг­нал тай­ме­ра/256

101 Сиг­нал тай­ме­ра/1024

110 Внеш­ний сиг­нал, спа­даю­щий фронт

111 Внеш­ний сиг­нал, на­рас­таю­щий фронт

Два по­следних зна­чения пред­на­зна­че­ны для ис­поль­зо­вания внешнего сиг­на­ла с тай­ме­ра, под­клю­чае­мо­го к вы­во­ду T1 (в тер­ми­но­ло­гии Atmel) или D5 (в тер­ми­но­ло­гии Arduino). Так мож­но реа­ли­зо­вать весьма ин­те­рес­ные схе­мы, но мы оста­вим их на бу­ду­щее. Итак, ес­ли эти би­ты уста­нов­ле­ны в 0x01, счет­чик счи­та­ет 16 000 000 раз в се­кун­ду. Это мно­го. Это оз­на­ча­ет, что он пе­ре­пол­ня­ет­ся 16 000 000/65 536 раз в се­кун­ду, или 244,14 раза в се­кун­ду.

Ус­та­но­вим пред­ва­ри­тель­ный де­ли­тель так­тов в 256:

TCCR1B=0x04;

В этом слу­чае он бу­дет пе­ре­пол­нять­ся (16 000 000/256)/65 536 раз в се­кун­ду, или 0,9536 раза в се­кун­ду. Ес­ли соз­дать про­це­ду­ру об­ра­бот­ки пре­ры­вания (см. на­ше ру­ко­во­дство по пре­ры­ваниям в LXF154), вы­зы­вае­мую при пе­ре­полнении тай­ме­ра, наш код бу­дет вы­пол­нять­ся где-то раз в се­кун­ду. Как это сде­лать?

К сча­стью, у мик­ро­схе­мы ATmega есть фла­ги и ре­ги­ст­ры имен­но для этих це­лей. Нам нуж­но сде­лать две ве­щи. Во-пер­вых, вклю­чим пре­ры­вание спе­ци­аль­но для тай­ме­ра, дости­гаю­ще­го сво­его мак­си­маль­но­го зна­чения. За­тем мик­ро­кон­трол­лер про­ве­рит флаг пе­ре­полнения, и ес­ли он уста­нов­лен, сбро­сит его и вы­зо­вет про­це­ду­ру об­ра­бот­ки пре­ры­вания. Для ре­шения пер­вой за­да­чи уста­но­вим со­от­вет­ст­вую­щий бит в мас­ке пре­ры­ваний тай­ме­ра:

TIMSK1=0 × 01;

Или вос­поль­зу­ем­ся мак­ро­сом и сде­ла­ем это безо­пас­нее:

TIMSK1 |= _BV(TOIE1);

Вто­рой ва­ри­ант уста­нов­ки би­та не сбро­сит ни од­но из су­ще­ст­вую­щих пре­ры­ваний, но их и не долж­но быть. И хо­тя с мак­ро­са­ми код про­ще чи­та­ет­ся (и по нему лег­че про­из­во­дить по­иск), иногда бы­ст­рее и про­ще за­пи­сать весь ре­гистр, осо­бен­но ес­ли нуж­но из­менить несколь­ко бит. Ус­та­нов­ка это­го би­та оз­на­ча­ет, что при пе­ре­полнении тай­ме­ра про­цес­сор про­ве­рит, есть ли про­це­ду­ра об­ра­бот­ки пре­ры­вания или нет.

По­след­няя часть этой за­да­чи – на­пи­сание про­це­ду­ры об­ра­бот­ки пре­ры­вания. Это обыч­ное оп­ре­де­ление функ­ции, но при ее ком­пи­ля­ции ис­поль­зу­ет­ся мак­рос. При­чи­на в том, что ад­рес этой функ­ции в па­мя­ти дол­жен по­пасть в таб­ли­цу век­то­ров пре­ры­ваний, что­бы ее мож­но бы­ло вы­звать. Что­бы объ­я­вить функ­цию об­ра­бот­ки пре­ры­вания пе­ре­полнения тай­ме­ра, нам требуется на­пи­сать сле­дую­щий код:

ISR(TIMER1_OVF_vect) {

… код об­ра­бот­ки …

}

ISR() – мак­рос, под­став­ляе­мый на эта­пе ком­пи­ля­ции, а TIMER1_OVF_vect оз­на­ча­ет, что ад­рес это­го пре­ры­вания нуж­но за­гру­зить в век­тор пе­ре­полнения Тай­ме­ра 1. Этот ад­рес из­вес­тен ком­пи­ля­то­ру, и вам неза­чем бес­по­ко­ить­ся о точ­ном мес­те его пропис­ки.

Есть и дру­гие стан­дарт­ные век­то­ры пре­ры­ваний. Мы об­су­дим их поз­же, или за­гляните в до­ку­мен­та­цию по avr-libc (http://www.nongnu.org/avr-libc/user-manual/group__avr__interrupts.html).

Как и в слу­чае с обыч­ны­ми пре­ры­вания­ми, эта функ­ция не воз­вра­ща­ет зна­чений. Она мо­жет уста­нав­ли­вать гло­баль­ные пе­ре­мен­ные, но об­ра­щай­тесь с ними очень осто­рож­но – пре­ры­вание мо­жет про­изой­ти в лю­бой мо­мент, и ес­ли пе­ре­мен­ная ис­поль­зу­ет­ся где-ли­бо еще, воз­мож­ны по­те­ри дан­ных. Со­единив все это в ко­де про­ек­та ми­гаю­ще­го све­то­дио­да, мы по­лу­чим при­мер­но та­кое:

const int LedPin = 13;

boolean value=false;

ISR(TIMER1_OVF_vect) {

//toggle the value

Скорая помощь

Пе­ред из­ме­не­ни­ем зна­че­ний тай­ме­ра или со­от­вет­ст­вую­щих ре­ги­ст­ров, ко­то­рые мо­гут при­вес­ти к ошиб­кам, ес­ли вы­пол­не­ние ко­да пре­рвет­ся, сле­ду­ет за­пре­щать пре­ры­ва­ния вы­зо­вом cli();. Вы­зов sei(); вклю­чит их сно­ва.

value=!value;

}

void setup() {

pinMode(LedPin, OUTPUT);

TIMSK1=0x01; // включить прерывание по переполнению счета

TCNT1=0x0000; // сброс счетчика в 0 при старте

TCCR1A = 0x00; // задать TCCR1A- отключить PWM и OCR;

TCCR1B = 0x04; // задать TCCR1B - де­ли­тель для /256

// запуск последовательного доступа - чтобы обойти ошибку

// систем, неверно инициализирующих сегмент данных

Serial.begin(9600);

};

void loop () {

digitalWrite(LedPin,value);

};

На­ша про­це­ду­ра об­ра­бот­ки пре­ры­вания весь­ма ком­пакт­на – она про­сто пе­ре­клю­ча­ет зна­чение гло­баль­ной бу­ле­вой пе­ре­мен­ной. В ко­де уста­нов­ки кон­такт 13 вы­би­ра­ет­ся в ка­че­­ст­ве вы­во­да, уста­нав­ли­ва­ет­ся тай­мер, за­тем он инициа­ли­зи­ру­ет­ся ну­ле­вым зна­чением. В основ­ном цик­ле ко­да зна­чение по­сто­ян­но вы­во­дит­ся на кон­такт све­то­дио­да. Вы мо­же­те по­ду­мать, что это несколь­ко из­бы­точ­но, и бу­де­те пра­вы! Ус­та­но­вить вы­вод в «единицу» мож­но и в са­мой про­це­ду­ре об­ра­бот­ки пре­ры­вания; так­же мож­но от­ка­зать­ся и от функ­ции digitalWrite. В этом слу­чае на­ша но­вая про­це­ду­ра пре­ры­вания бу­дет вы­гля­деть при­мер­но так:

ISR(TIMER1_OVF_vect) {

PORTB ^= _BV(PB5);

}

Этот код мо­жет по­ка­зать­ся вам со­вер­шен­ной та­ра­бар­щи­ной. Ес­ли так, про­чти­те врез­ку «По­раз­ряд­ные опе­ра­ции». Про­чли? Хо­ро­шо. Все рав­но раз­бе­рем этот код под­робнее. PORTB – еще од­но из со­кра­щений. На язы­ке фир­мы Atmel, ко­то­рая про­из­во­дит мик­ро­схе­мы ATmega168 и 368, ле­жа­щие в осно­ве Arduino, PORTB – на­бор дву­на­прав­лен­ных пор­тов, под­клю­чен­ных к неко­то­рым вы­во­дам пла­ты. Два верхних би­та от­ра­жа­ют вы­вод, ис­поль­зуе­мый для генера­то­ра, по­это­му ими поль­зо­вать­ся нель­зя. Вы­во­ды с 0 по 5 – циф­ро­вые вы­во­ды с 8 по 13 в тер­ми­но­ло­гии Arduino, по­это­му вы­вод 13 (к ко­то­ро­му под­клю­чен све­то­ди­од) – это бит 5 ре­ги­ст­ра PortB. Его со­кра­щен­ное обо­зна­чение – PB5.

Опе­ра­ция, ко­то­рую мы вы­пол­ня­ем здесь – ис­клю­чаю­щее «ИЛИ». Она оз­на­ча­ет, что ес­ли бит уста­нов­лен (1), он бу­дет сбро­шен (1 ^ 1 = 0), а ес­ли не уста­нов­лен (0), то уста­но­вит­ся (0 ^ 1 = 1). Эта опе­ра­ция хит­ро­ум­но ме­ня­ет зна­чение имен­но нуж­ным нам об­ра­зом, и нам нече­го бес­по­ко­ить­ся о функ­ции digitalWrite(). Нуж­но толь­ко убе­дить­ся, что вы­вод 13 на­стро­ен на вы­вод дан­ных. Обыч­но так и есть, но яс­но­сти ра­ди мож­но за­дать это яв­но. Так как мы освои­ли ра­бо­ту с ре­ги­ст­ра­ми, это мож­но сде­лать в ко­де на­строй­ки:

DDRB |= _BV(PB5);

Это вы­ра­жение уста­нав­ли­ва­ет бит 5 ре­ги­ст­ра в единицу, не ме­няя дру­гие би­ты; то же са­мое де­ла­ет функ­ция pinMode(). (Мы вклю­чили эту вер­сию в Лис­тинг 2 на DVD.) Что же ка­са­ет­ся основ­но­го цик­ла – кста­ти, при же­лании ту­да мож­но вста­вить лю­бой код – для ми­гания све­то­дио­да ниче­го до­бав­лять не нуж­но, те­перь оно пол­но­стью неза­ви­си­мо от осталь­но­го ко­да, и све­то­ди­од бу­дет на се­кун­ду вклю­чать­ся и на се­кун­ду вы­клю­чать­ся, хо­тя пи­тание на него по­да­но. Или поч­ти на се­кун­ду.

Ос­та­но­ви­те ча­сы

Ес­ли помните, со­глас­но на­шим рас­че­там пре­ры­вание возника­ет с час­то­той чуть ре­же чем раз в се­кун­ду: се­кун­да – это 62 500 так­тов, а не 65 536. У этой про­бле­мы есть два оче­вид­ных ре­шения. Пер­вое – в про­це­ду­ре об­ра­бот­ки пре­ры­вания уста­нав­ли­вать тай­мер в 3036: тогда он начнет от­счет не с ну­ля, а с это­го зна­чения, и до кон­ца ему останет­ся 62 500 так­тов. Эта идея не так уж пло­ха, но с ней свя­за­но несколь­ко про­блем. Са­ма ра­бо­та с тай­ме­ром, по­ка он за­пу­щен, при­во­дит к ошиб­кам – на запись но­во­го зна­чения уй­дет несколь­ко цик­лов, по­это­му такой спо­соб не со­всем то­чен. Это мо­жет не иг­рать осо­бой ро­ли с несколь­ки­ми мил­ли­се­кун­да­ми от се­кун­ды, но при бо­лее ко­рот­ком ин­тер­ва­ле вре­мени уже вы­зо­вет про­бле­мы. К сча­стью, есть луч­шее ре­шение.

Бой­тесь Тай­ме­ра 0!

Со все­ми эти­ми тай­ме­ра­ми в вашем рас­по­ря­жении вы мо­же­те за­хо­теть восполь­зо­вать­ся ими для сво­их це­лей. Тай­ме­ры 1 и 2 в этом слу­чае толь­ко по­те­ря­ют воз­мож­ность управ­лять ШИМ на вы­во­дах 9 и 10 и 3 и 11 со­от­вет­ст­вен­но. Од­на­ко, ес­ли вы не знае­те, что де­лае­те, из­бе­гай­те ра­бо­ты с Тай­ме­ром 0. Пе­ре­груз­ка его на­стро­ек мо­жет при­вес­ти к непред­ска­зуе­мым ре­зуль­та­там: на­при­мер, оста­но­вить мил­ли­се­кунд­ный счет­чик, ис­поль­зуе­мый стан­дарт­ным ко­дом Arduino. И ес­ли вы не уве­ре­ны в том что де­лае­те, луч­ше оста­вить его в по­кое.

У тай­ме­ра есть спе­ци­аль­ный ре­гистр сравнения. В него за­гру­жа­ет­ся зна­чение, и при ка­ж­дом из­менении зна­чения тай­ме­ра оно сравнива­ет­ся с со­дер­жи­мым ре­ги­ст­ра. Ес­ли они сов­па­да­ют и в TIMSK уста­нов­лен флаг пре­ры­вания, мож­но сгенери­ро­вать пре­ры­вание. Вот как уста­но­вить бит в ре­ги­ст­ре сравнения:

TIMSK1 |= _BV(OCIE1A)

Это со­кра­щение оз­на­ча­ет «Output Compare Interrupt Enable (Timer)1 (Register)A» (Вклю­чить ре­гистр (А) сравнения для пре­ры­ваний (тай­ме­ра) 1), ес­ли это по­мо­жет вам его за­помнить.

Но пре­ж­де чем за­гру­жать зна­чение в ре­гистр сравнения, по­ду­ма­ем еще кое о чем. Когда зна­чения сов­па­да­ют и вы­зы­ва­ет­ся пре­ры­вание, мы пе­ре­клю­ча­ем све­то­ди­од. Но что про­ис­хо­дит с тай­ме­ром? Он от­счи­ты­ва­ет еще 3036 так­тов до кон­ца и сбра­сы­ва­ет­ся в ноль, за­тем от­чи­ты­ва­ет оче­ред­ные 62 500 так­тов и сно­ва вы­зы­ва­ет пре­ры­вание. Вы­хо­дит, мы не из­менили пе­ри­од ми­гания, а толь­ко сдви­ну­ли мо­мент его возник­но­вения.

Что все это зна­чит...
  • DDRA Data Direction Register (A,B,C,D) [ре­гистр на­прав­ления дан­ных] оп­ре­де­ля­ет, ка­кие вы­во­ды яв­ля­ют­ся вход­ны­ми (0), а ка­кие вы­ход­ны­ми (1) на че­ты­рех пор­тах.
  • ISR(век­тор) Interrupt Service Routine [про­це­ду­ра об­слу­жи­вания пре­ры­вания]. Это не ад­рес, а мак­рос; он ком­пи­ли­ру­ет век­то­ры так, что­бы изо­ли­ро­ван­ная функ­ция кор­рект­но об­ра­батывала пре­ры­вания.
  • TIMSK1 Timer Interrupt MaSK [мас­ка пре­ры­ваний тай­ме­ра]. В этом ре­ги­ст­ре вклю­ча­ют­ся и вы­клю­ча­ют­ся пре­ры­вания для со­бы­тий тай­ме­ра.
  • TCNT1 16-бит­ный ре­гистр, хра­ня­щий те­ку­щее зна­чение тай­ме­ра – от­чет на­чи­на­ет­ся с 0 и про­дол­жа­ет­ся до 65535, за­тем счет­чик сно­ва сбра­сы­ва­ет­ся в ноль. В хо­де это­го про­цес­са уста­нав­ли­ва­ет­ся бит пе­ре­полнения.
  • OCR1A/OCR1B On Compare Registers A and B [ре­ги­ст­ры сравнения A и B]. Это про­сто две ячей­ки па­мя­ти, со­дер­жа­щие 16-бит­ные зна­чения, с ко­то­ры­ми сравнива­ет­ся те­ку­щее зна­чение тай­ме­ра (TCNT1).
  • TCCR1 Timer Counter Control Register [ре­гистр управ­ления счет­чи­ком тай­ме­ра]. В этом ре­ги­ст­ре есть би­ты для управ­ления воз­мож­но­стя­ми и ре­жи­ма­ми ра­бо­ты счет­чи­ка (CTC, PWM и пред­ва­ри­тель­ный де­ли­тель так­тов).

Как вы, воз­мож­но, до­га­да­лись, у этой про­бле­мы есть про­стое ре­шение – CTC (Clear Timer on Compare – сброс тай­ме­ра при сравнении). В этом ре­жи­ме при сов­па­дении зна­чений тай­мер сбра­сы­ва­ет­ся в ноль, что и убира­ет на­шу ма­лень­кую трудность. При уста­нов­ке ре­жи­ма CTC ис­поль­зу­ют­ся те же би­ты ре­ги­ст­ра TCCR1, что и для функ­ций ШИМ, и по­де­ле­ны ме­ж­ду дву­мя по­ло­вин­ка­ми ре­ги­ст­ра. Что­бы вклю­чить ре­жим CTC, нуж­но сбро­сить эти би­ты в верх­ней по­ло­вин­ке ре­ги­ст­ра и один из них в нижней по­ло­вин­ке. Как вы, воз­мож­но, помните, здесь же мы за­да­ем зна­чение пред­ва­ри­тель­но­го де­ли­те­ля так­тов. Так как мы из­ме­ня­ем все би­ты, удобнее на­пи­сать:

TCCR1A= B00000000; // от­клю­чить PWM

TCCR1B= B00001100; // включить CTC и де­ли­тель /256

Да­лее нуж­но за­гру­зить ре­гистр сравнения. Мы всегда де­ла­ем это по­сле уста­нов­ки TCCR, так как при из­менении TCCR возника­ет пло­хо за­до­ку­мен­ти­ро­ван­ная ошиб­ка – ре­гистр сравнения сбра­сы­ва­ет­ся. Для это­го нуж­но про­сто за­пи­сать зна­чение в ре­гистр сравнения (On Compare Register):

OCR1A = 62499;

От­чет на­чи­на­ет­ся с ну­ля, и для по­лу­чения точ­но­го ре­зуль­та­та на­до сравнивать зна­чение счет­чи­ка с 62 499. Ре­гистр сравнения 16-бит­ный, как и счет­чик, по­это­му убе­ди­тесь, что вы за­пи­сы­вае­те в него 16-бит­ное зна­чение. Ес­ли вы за­пи­сы­вае­те зна­чение мень­ше 255, де­лай­те это в ше­ст­на­дца­те­рич­ном фор­ма­те (на­при­мер, 127 = 0 × 007F). Опять же, нуж­но за­пи­сать пре­ры­вание:

ISR(TIMER1_COMPA_vect) {

PORTB ^= _BV(PB5);

}

Оно от­ли­ча­ет­ся от ис­ход­но­го пре­ры­вания толь­ко тем, что мы из­менили век­тор, к ко­то­ро­му оно об­ра­ща­ет­ся – ес­ли мы за­бу­дем за­пи­сать пре­ры­вание, то при его вы­зо­ве век­тор пре­ры­ваний бу­дет ра­вен ну­лю. Про­цес­сор по­счи­та­ет это пло­хим пре­ры­ванием, и ес­ли мы не да­дим ему дру­гих ука­заний, вы­полнит сброс. Ес­ли Arduino не про­из­во­дит ника­ких дей­ст­вий, про­верь­те, что вы за­пи­са­ли зна­чение в нуж­ный век­тор. Наш код те­перь дол­жен вы­гля­деть так (Лис­тинг 3 на DVD):

void setup() {

DDRB |= _BV(PB5); // задать контакт LED как выход

cli(); // остановка прерываний

TCNT1=0x0000; // сброс счетчика в 0 при старте

TCCR1A= B00000000; // отключение PWM

TCCR1B= B00001100; // включить CTC и де­ли­тель /256

TIMSK1 |= _BV(OCIE1A); // включить прерывание по сравнению с A

OCR1A = 62499; //пусть компаратор A срабатывает на 1 секунду

sei(); // снова включить прерывания

};

void loop () {

///ваш полезный код

};

ISR(TIMER1_COMPA_vect) {

PORTB ^= _BV(PB5);

}

Срав­ни­те это

При за­пи­си зна­чения в ре­гистр сравнения, вы мог­ли за­ме­тить мно­же­ст­во ссы­лок на ре­гистр сравнения A. Де­ло в том, что есть еще и ре­гистр B, зна­чение ко­то­ро­го так­же мож­но из­ме­нять, сравнивать и вы­зы­вать пре­ры­вание – но тай­мер при этом не сбра­сы­ва­ет­ся. В этом слу­чае пре­ры­вание сгенери­ру­ет­ся в те­чение од­но­се­кунд­но­го ин­тер­ва­ла, но тай­мер про­дол­жит ра­бо­тать до дости­жения зна­чения 62 499. Это мо­жет быть удоб­но, ес­ли вы хо­ти­те генери­ро­вать со­бы­тия, на­при­мер, ка­ж­дую се­кун­ду и пол­се­кун­ды.

Впро­чем, мы восполь­зу­ем­ся этим немно­го ина­че – бу­дем вы­зы­вать ту же са­мую про­це­ду­ру об­ра­бот­ки пре­ры­вания, но в точ­ке бли­же к кон­цу од­но­се­кунд­но­го цик­ла. Это вы­зо­вет крат­ко­вре­мен­ное вклю­чение све­то­дио­да пе­ред за­вер­шением се­кун­ды и соз­даст эф­фект пуль­са­ции, ко­то­рый ма­лость по­ин­те­реснее, чем про­стое вклю­чение-вы­клю­чение на се­кун­ду. Весь код под­роб­но опи­сы­вать не бу­дем; глав­ное, что нуж­но сде­лать – за­дать пре­ры­вания для OCR1A и OCR1B и на­пи­сать но­вый об­ра­бот­чик пре­ры­ваний:

TIMSK1 |= _BV(OCIE1A) | _BV(OCIE1B) ;

OCR1A=62499;

OCR1B=60000;

Так как мы хо­тим восполь­зо­вать­ся той же про­це­ду­рой об­ра­ботки пре­ры­вания, нам при­го­дит­ся еще один хит­рый при­ем, свя­зан­ный с ком­пи­ля­ци­ей. Оба пре­ры­вания долж­ны на­хо­дить­ся в таб­ли­цах век­то­ров, но мы хо­тим, что­бы они ука­зы­ва­ли на од­но и то же ме­сто. Для это­го по­слу­жит спе­ци­аль­ный мак­рос ISR_ALIAS(). Он принима­ет два век­то­ра – пер­вый, ко­то­рый вы хо­ти­те пе­ре­на­пра­вить, и вто­рой, в ко­то­рый нуж­но пе­ре­на­пра­вить:

ISR_ALIAS(TIMER1_COMPB_vect, TIMER1_COMPA_vect );

Од­на эта стро­ка де­ла­ет все необ­хо­ди­мое. Пол­ный лис­тинг этой вер­сии при­ве­ден на DVD как Лис­тинг 4.

Та­ко­вы осно­вы ис­поль­зо­вания тай­ме­ров. По­на­ча­лу они мо­гут немно­го за­пу­гать, и не об­хо­дит­ся без под­вод­ных камней, ко­то­рых сто­ит опа­сать­ся, но пре­ры­вания на осно­ве тай­ме­ра очень удоб­ны – и вско­ре мы рас­ска­жем еще о несколь­ких прие­мах их ис­поль­зо­ва­ния

Персональные инструменты
купить
подписаться
Яндекс.Метрика