Časovač v CTC režimu

Zápisník experimentátora

Hierarchy: Časovač (timer)

Každé Arduino obsahuje několik časovačů (timerů). Ty mají různé využití. My se v tomto článku zaměříme na časovač, který v pravidelných intervalech vyvolává přerušení, které nám umožní v přerušení provést požadovanou akci. Výsledkem bude blikání led diodou 13 na frekvenci 1 Hz. Dosáhneme tak tutéž funkci, jako v klasickém příkladu Blink. Nebudeme ale zbytečně zatěžovat mikrokontrolér čekáním a uvolníme mu ruce i na jinou činnost.

Na připomenutí

Klasický příklad Blink vypadá takto. Díky funkci delay bude dobře fungovat, ale už nám nezbude čas na nic jiného, protože mikrokontrolér je vytížen čekáním. Celý tento příklad můžeme napsat také pomocí časovač a přerušení.

// the setup function runs once when you press reset or power the board
void setup() {
  // initialize digital pin LED_BUILTIN as an output.
  pinMode(LED_BUILTIN, OUTPUT);
}

// the loop function runs over and over again forever
void loop() {
  digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(1000);                       // wait for a second
  digitalWrite(LED_BUILTIN, LOW);    // turn the LED off by making the voltage LOW
  delay(1000);                       // wait for a second
}

CTC mód

Různé režimy časovače mohou vzbuzovat nechuť studovat datasheet, ale reálně se potřebujete naučit pouze jediný z nich. Je to režim CTC, který časuje postupně v intervalu 0-OCRA a při dosažení maxima vyvolá přerušení. Příklad si ukážeme na 16bitovém časovači Timer1.

  • Před každou úpravou časovače je vhodné zakázat přerušení.
  • Pak je vhodné i vynulovat všechny registry, které se týkají časovače. V našem případě jsou to registry TCCR1A, TCCR1B a TCNT1.
  • Frekvence mikrokontroléru ATmega328P je 16 MHz. To může být pro naše časování příliš vysoká hodnota. Proto máme k dispozici děličky frekvence. Pro každý časovač jsou tyto děličky mírně jiné, ale dá se říci, že obvykle jsou k dispozici děličky 8, 64, 256 a 1024. Například dělička 8 nám sníží základní frekvenci z 16 na 2 MHz.
  • aždý časovat podporuje několik módů práce. Pomocí vhodné modifikace registrů si musíme zapnout mód CTC.
  • V režimu CTC časovač časuje od 0 po hodnotu registru OCRA. Čili nabude OCRA + 1 hodnot. Přerušení může nastat v okamžiku dosažení hodnoty OCRA. Třeba to ale nastavit modifikací registrů.
  • Celá magie nastavení se skrývá v registru OCRA. Podle nastavené hodnoty dosáhneme požadovanou frekvenci. Abychom mohli vykonávat naši činnost, musíme si povolit příslušné přerušení a v něm napsat náš kód.

Vzorec pro výpočet frekvence je fOCXA = fclk / (1 x N x (1 + OCRXA)), kde N je hodnota děličky. V případě požadované frekvence 1 Hz to může být například 1 = 16000000 / (1 x 1024 x (1 + 15624)).

Pozor na to, že v datasheetu se dočtete o vzorci fOCXA = fclk / (2 x N x (1 + OCRXA)). Ten vzorec ale platí jen pro generování signálu podle přiložené tabulky (CTC Mode, Timing Diagram). Z ní jasně vidět, že přerušení nastávají 2x častěji a proto použijeme první vzorec.

Příklad s funkcí digitalWrite

Na ukázku jsem napsal dva příklady. Tento první využívá funkce pinMode a digitalWrite.

#define ledPin 13

void setup()
{
  pinMode(ledPin, OUTPUT);
  
  // initialize timer1 
  noInterrupts();           // disable all interrupts
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1  = 0;

  OCR1A = 15624;            // compare match register 16MHz/1024/1Hz
  TCCR1B |= (1 << WGM12);   // CTC mode
  TCCR1B |= (1 << CS12) | (0 << CS11) | (1 << CS10); // 1024 prescaler 
  TIMSK1 |= (1 << OCIE1A);  // enable timer compare interrupt
  interrupts();             // enable all interrupts
}

ISR(TIMER1_COMPA_vect)          // timer compare interrupt service routine
{
  digitalWrite(ledPin, digitalRead(ledPin) ^ 1);   // toggle LED pin
}

void loop()
{
  // your program here...
}

Příklad s přímým přístupem na porty

Tento druhý přímo manipuluje porty. Dělá přesně to samé, co předchozí.

#define ledPin 13

void setup()
{
  DDRB |= (1<<PB5);
  
  // initialize timer1 
  noInterrupts();           // disable all interrupts
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1  = 0;

  OCR1A = 15624;            // compare match register 16MHz/1024/1Hz
  TCCR1B |= (1 << WGM12);   // CTC mode
  TCCR1B |= (1 << CS12) | (0 << CS11) | (1 << CS10); // 1024 prescaler 
  TIMSK1 |= (1 << OCIE1A);  // enable timer compare interrupt
  interrupts();             // enable all interrupts
}

ISR(TIMER1_COMPA_vect)          // timer compare interrupt service routine
{
  PORTB ^= (1 << PB5);
}

void loop()
{
  // your program here...
}

Zdrojové kódy

Zdrojové kódy k článku naleznete na GitHub:


29.12.2017


Menu