Hardware timer interrupts
Table of Contents
:ID: ADF4BA86-E350-441C-89C3-327BB269CEEA
Need an accurate clock. Try using a hardware timer interrupt. Set a period of time, the completion of which will pause the program to execute some operation (like a callback). I needed this for an Arduino midi clock project.
# Configuration
This example is for setting up Timer1 on AVR micro controller. There’s a bunch of annoying and weird boiler plate to configuring a timer. There are calculators online that make it easy:
https://deepbluembedded.com/arduino-timer-calculator-code-generator/
- Determine the period. The timer after which some code should run.
- From that period, determine what prescaler you need to set. Use the TCCR1B control register bits to set this
CS12 | CS11 | CS10 | Description |
---|---|---|---|
0 | 0 | 0 | No clock source (Timer/Counter stopped) |
0 | 0 | 1 | clkI/O/(No prescaling) |
0 | 1 | 0 | clkI/O/8 (From prescaler) |
0 | 1 | 1 | clkI/O/64 (From prescaler) |
1 | 0 | 0 | clkI/O/256 (From prescaler) |
1 | 0 | 1 | clkI/O/1024 (From prescaler) |
1 | 1 | 0 | External clock source on T0 pin. Clock on falling edge. |
1 | 1 | 1 | External clock source on T0 pin. Clock on rising edge. |
- Set up the clock in a setup() function. Turn on Clear Time on Compare CTC. Again using the TCCR1B control register, turn on WGM12.
- Define the interrupt function. Keep it as simple as possible. The more it does, the more the timing can be affected. If possible just set variables and let the loop function do that rest based on the values set for those variables.
ISR(TIMER1_COMPA_vect) { // Code for what you want to do when the program is interrupted. } void setup() { TCCR1A = 0; // Control Register A TCCR1B = 0; // Control Register B (for setting prescaler and CTC mode) TCNT1 = 0; // initialize counter value to 0 TCCR1B |= (0 << CS12) | (1 << CS11) | (0 << CS10); // Prescaler 8 // Set tick that triggers the interrupt OCR1A = 4000; // Enable Clear Time on Compare match (reset clock back to 0 on compare match) TCCR1B |= (1 << WGM12); // Enable timer overflow interrupt TIMSK1 |= (1 << OCIE1A); } void loop() { // ... }
If the period is determined at run time by some inputs to the program, the prescaler can be computed and timer configured in a helper function. This example assumes the Timer has already been setup like above.
void configureTimer(float intervalMicros) { unsigned long ocr; byte tccr; if ((ocr = (CPU_FREQ * intervalMicros) / (1 * 1000000)) < 65535) { tccr |= (0 << CS12) | (0 << CS11) | (1 << CS10); } else if ((ocr = (CPU_FREQ * intervalMicros) / (8 * 1000000)) < 65535) { tccr |= (0 << CS12) | (1 << CS11) | (0 << CS10); } else if ((ocr = (CPU_FREQ * intervalMicros) / (64 * 1000000)) < 65535) { tccr |= (0 << CS12) | (1 << CS11) | (1 << CS10); } else if ((ocr = (CPU_FREQ * intervalMicros) / (256 * 1000000)) < 65535) { tccr |= (1 << CS12) | (0 << CS11) | (0 << CS10); } else if ((ocr = (CPU_FREQ * intervalMicros) / (1024 * 1000000)) < 65535) { tccr |= (1 << CS12) | (0 << CS11) | (1 << CS10); } else { // Exceeds timer's maxium interval, which is 4.19 seconds return; } ocr = ocr - 1; // timer is 0 indexed if (ocr) { CONFIGURE_TIMER1( TCCR1B = 0; // Reset control register OCR1A = ocr; // Period TCCR1B |= (1 << WGM12); // CTC mode TCCR1B |= tccr; // Prescaler ) } }
# Prescaler
A Timer (8 or 16bit for most AVR boards) can only measure up to a certain period before resetting back to 0 based on the microcontroller’s frequency. The prescaler divides by some number to allow for timing longer periods. The limit of the number of periods is that largest value that can be represented in 16 bits (65535) or 8 bits (256).
(timer speed (Hz)) = (Arduino clock speed (16MHz)) / prescaler
See also https://www.instructables.com/Arduino-Timer-Interrupts/
An interesting discussion on how the prescaler affects the clock resolution: https://arduino.stackexchange.com/q/4022