Timer in CTC mode

Zápisník experimentátora

Hierarchy: Časovač (timer)

Each Arduino contains several timers. They have different uses. In this article, we will focus on a timer that interrupts intermittently at intervals to allow us to perform the desired action in an interruption. As a result, the LED on pin 13 will blink by 1 Hz. We get the same function as in the classic Blink example. But we will not unnecessarily burden the microcontroller with a wait and release his hands for another activity.

Reminders

The classic example Blink looks like this. Thanks to the delay function, it will work well, but we will have no time to do anything else, because the microcontroller is busy. This example can also be written using timer and interrupt.

// 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 mode

Different timer modes can induce a reluctance to study the datasheet, but you really need to learn just one. It is a CTC mode, which counts gradually in the 0-OCRA interval and causes the interrupt when reaching the maximum. We'll show an example on 16-bit Timer1.

  • It is advisable to disable interrupts before each timer adjustment.
  • It is then also advisable to reset all the timer registers. In our case, they are the TCCR1A, TCCR1B, and TCNT1 registers.
  • The frequency of the ATmega328P microcontroller is 16 MHz. This can be too high for our timing. That's why we have frequency dividers available. For each timer, these dividers are slightly different, but we can say that dividers 8, 64, 256, and 1024 are usually available. For example, divider 8 will reduce the base frequency from 16 to 2 MHz.
  • Every timer supports multiple modes of work. We need to enable CTC mode with the appropriate modification of the registers.
  • In the CTC mode, the timer runs from 0 to the OCRA value. So it will acquire OCRA + 1 values. The interrupt can occur when OCRA is reached. However, it needs to be set by modifying the registers.
  • All the magic settings are hidden in the OCRA registry. Depending on the set value, we reach the desired frequency. In order to be able to do our business, we need to allow the interrupt and write our interrupt handler.

The formula for calculating the frequency is fOCXA = fclk / (1 x N x (1 + OCRXA)), where N is the value of the divider. For a desired frequency of 1 Hz this may be for example 1 = 16000000 / (1 x 1024 x (1 + 15624)).

Note that the datasheet You will read about the formula fOCXA = fclk / (2 x N x (1 + OCRXA)). However, the formula only applies to the attached table (CTC Mode, Timing Diagram). It clearly shows that interrupts occur 2 times more often and therefore we use the first formula.

Example with digitalWrite

I wrote two examples. This first uses pinMode and 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...
}

Example with direct access to ports

The other directly handles the ports. It does exactly the same thing as the previous ones.

#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...
}

Source code

For the source code for the article, visit GitHub:


29.12.2017


Menu