Enabling TIMER0 Interrupt breaks the timer

Hello,

I try to learn how to use interrupts and I use an Arduino Uno R3. I code in C.
I managed to use the INT0 and INT1 interrupt with buttons but I now want to use the timer interrupts.
The goal is to replicate the millis() function with the TIMER0.

Here is the first approach without an interrupt which works :

#include <avr/io.h>
#define F_CPU 16000000UL
#include <avr/interrupt.h>

unsigned long millis = 0;

int main () {
	// Declare LED as output
	DDRB |= (1 << PORTB3);

	// Set timer0
	// Set CTC mode
	TCCR0A |= (1 << WGM01);
	// 16 000 000 / (64 * 250) = 1000 Hz
	// Set top value
	OCR0A = 250;
	// Prescaler 64
	TCCR0B |= ((1 << CS01) | (1 << CS00));

	while (1) {
		if ((TIFR0 & (1 << OCF0A)) != 0) {
			// Reset the timer
			TIFR0 |= (1 << OCF0A);
			// Increase millis
			millis++;
		}
		if (millis >= 1000) {
			millis = 0;
			// Switch the state of the LED
			PORTB ^= (1 << PORTB3);
		}
	}
}

Now the second approach with the interrupt which doesn't work :

#include <avr/io.h>
#define F_CPU 16000000UL
#include <avr/interrupt.h>

unsigned long millis = 0;

// Timer0
ISR (TIMER0_COMPA_vect) {
	millis++;
	TIFR0 |= (1 << OCF0A);
}

int main () {
	// Declare LED as output
	DDRB |= (1 << PORTB3);

	// Set timer0
	// Set CTC mode
	TCCR0A |= (1 << WGM01);
	// 16 000 000 / (64 * 250) = 1000 Hz
	// Set top value
	OCR0A = 250;
	// Prescaler 64
	TCCR0B |= ((1 << CS01) | (1 << CS00));

	// Enable interrupt for timer0
	TIMSK0 |= (1 << OCIE0A);
	// Enable global interrupts
	SREG |= 0b10000000; 

	while (1) {
		if (millis >= 1000) {
			millis = 0;
			// Switch the state of the LED
			PORTB ^= (1 << PORTB3);
		}
	}
}

It doesn't works because the led never turns on. Also, I tried to change the code in the ISR (to turn on a LED for example) and it is never ran.

I don't know why this doesn't work because the documentation of the atmega328p says :
"When the OCIE0A bit is written to one, and the I-bit in the Status Register is set, the Timer/Counter0 Compare Match A interrupt is enabled. The corresponding interrupt is executed if a Compare Match in Timer/Counter0 occurs, i.e., when the OCF0A bit is set in the Timer/Counter 0 Interrupt Flag Register – TIFR0."
It seems to me like the OCIE0A is set to 1, the I-bit in the SREG is set to 1 and the CTC is enable for counter 0, match A.

Also, I found out that the first code breaks as soon as I add this line :

// Enable interrupt for timer0
TIMSK0 |= (1 << OCIE0A);

It means to me that the OCF0A flag is not longer even set to 1.

I searched for an answer for a long time but I couldn't find anything.

I hope I have been understandable enough and I stay at your disposal for any question.

Thank you for reading,
Pauloux

change that to: volatile unsigned long millis = 0;

in addition to making millis volatile you need to turn off interrupts when accessing it in your main code since the Uno can only atomically access an 8 bit variable and millis is 4 bytes. If you don't, it can eventually bite you.

Here is how to make that work:

This action is not required in an ISR:

	TIFR0 |= (1 << OCF0A);

Thank you and the other who told me to add volatile. I tried it and I now have this code :

#include <avr/io.h>
#define F_CPU 16000000UL
#include <avr/interrupt.h>

volatile unsigned long millis = 0;

// Timer0
ISR (TIMER0_COMPA_vect) {
	millis++;
}

int main () {
	// Declare LED as output
	DDRB |= (1 << PORTB3);

	// Set timer0
	// Set CTC mode
	TCCR0A |= (1 << WGM01);
	// 16 000 000 / (64 * 250) = 1000 Hz
	// Set top value
	OCR0A = 250;
	// Prescaler 64
	TCCR0B |= ((1 << CS01) | (1 << CS00));

	// Enable interrupt for timer0
	TIMSK0 |= (1 << OCIE0A);
	// Enable global interrupts
	sei();

	while (1) {
		// Disable global interrupts
		cli();
		if (millis >= 1000) {
			millis = 0;
			// Switch the state of the LED
			PORTB ^= (1 << PORTB3);
		}
		// Enable global interrupts
		sei();
	}
}

As you can see I also use the cli() and sei() function to be certain that this part works as expected.

However, it still doesn't work :frowning_face:

Thank you for the link but I can't see want I did differently. Also I used this post in the first place to better understand the timer interrupts.

Thank you ! I found on the internet some code with and some without so I added it to be sure. It still doesn't work when I remove it

Try Adafruit's code and make sure it works on your setup. Then compare your and their code line by line to find the differences.

Hint: the only modifications made by Adafruit to the Timer0 registers are these:

  // Timer0 is already used for millis() - we'll just interrupt somewhere
  // in the middle and call the "Compare A" function below
  OCR0A = 0xAF;
  TIMSK0 |= _BV(OCIE0A);
1 Like

The Adafruit code is adding to the timer functionality already enabled by the Arduino init code. OP here is replacing main(), so it isn't running any arduino code and may need to do more complete timer initialization.

The hint can be taken in several different ways.

Insert the cli() function to the line before the ddrb comment, just after the int main().

Then remove the sei() and cli() functions in the while loop.

To be honest I'm a beginner so I don't really want to install a c++ compiler and learn OOP just for this example.

I tried putting only those lines but it still doesn't work.

I tried changing the interrupt to the OVF interrupt but it doesn't change anything.

Also, I found this in the documentation :
Talking of the TIFR0 register :
"The OCF0A bit is set when a Compare Match occurs between the Timer/Counter0 and the data in OCR0A – Output Compare Register0. OCF0A is cleared by hardware when executing the corresponding interrupt handling vector. Alternatively, OCF0A is cleared by writing a logic one to the flag. When the I-bit in SREG, OCIE0A (Timer/Counter0 Compare Match Interrupt Enable), and OCF0A are set, the Timer/Counter0 Compare Match Interrupt is executed."
Maybe I understand badly because English isn't my native language but this extract explains that the OCR0A is set to 1 when there is a Compare Match (so in CTC mode ?) with the value inside OCR0A. Also, "when the OCF0A is set, the Compare Match Interrupt is executed" so I don't understand why it doesn't work. Maybe because the OCF0A is never set ? But the OCF0A is set when I remove the line TIMSK0 |= (1 << OCIE0A); which should just enable the interrupt.

In order for everybody to better understand the conversation, here is my current program based on all of your responses :

#include <avr/io.h>
#define F_CPU 16000000UL
#include <avr/interrupt.h>

volatile unsigned long millis = 0;

// Timer0
ISR (TIMER0_OVF_vect) {
	millis++;
}

int main () {
	// Disable global interrupts
	cli();
	// Declare LED as output
	DDRB |= (1 << PORTB3);

	// Set timer0
	// Set CTC mode
	TCCR0A |= (1 << WGM01);
	// 16 000 000 / (64 * 250) = 1000 Hz
	// Set top value
	OCR0A = 250;
	// Prescaler 64
	TCCR0B |= ((1 << CS01) | (1 << CS00));

	// Enable interrupt for timer0
	TIMSK0 |= (1 << OCIE0A);
	// Enable global interrupts
	sei();

	while (1) {
		if (millis >= 1000) {
			millis = 0;
			// Switch the state of the LED
			PORTB ^= (1 << PORTB3);
		}
	}
}

Thank you again for your time and I am still open to new idea to correct my program or at least some explanations on why it doesn't work.

The following sketch uses TC0 of ATmega328P MCU of Arduino UNOR3 Board to generate 992 ms TimeTick which is indicated by flashing onBoard LED of UNOR3 Board.

#include <util/delay.h>
#define F_CPU 16000000UL

#define LED 13
byte tickCount = 0;

void setup()
{
  Serial.begin(9600);
  pinMode(LED, OUTPUT);
  bitClear(TIMSK0, TOIE0); //overflow interrupt is disabled
  bitClear(TIMSK0, OCIE0A); //compare-A interrut is inactive
  bitClear(TIMSK0, OCIE0B);
  TCNT0 = 6;    //preset count for 16 ms tick
  TCCR0B = 0x05;   //statrt with clkTC0 = 15625 Hz
}

void loop()
{
  while (bitRead(TIFR0, TOV0) != HIGH)
  {
    ;    //wait until 16 ms has elapsed
  }
  bitSet(TIFR0, TOV0);
  TCNT0 = 6;
  tickCount++;
  if (tickCount == 62) //992 ms has gone
  {
    tickCount = 0;
    digitalWrite(LED, HIGH);
    _delay_ms(100);
    digitalWrite(LED, LOW);
    _delay_ms(100);
  }
}

What? If you are using the standard Arduino IDE, that is C++.

The points made by @westfw and @Delta_G above are that the Arduino IDE sets up Timer0 for you in the appropriate way. You don't need to change the timer operating mode if you use Adafruit's approach.

1 Like

You defined a handler for overflow interrupt, but enabled a compare one....

I found the problem !
First of all, the working code :

#include <avr/io.h>
#define F_CPU 16000000UL
#include <avr/interrupt.h>

volatile unsigned long millis = 0;

// Timer0
ISR (TIMER0_COMPA_vect) {
	millis++;
}

int main () {
	// Declare LED as output
	DDRB |= (1 << PORTB3);

	// Set timer0
	// Set CTC mode
	TCCR0A |= (1 << WGM01);
	// 16 000 000 / (64 * 250) = 1000 Hz
	// Set top value
	OCR0A = 250;
	// Prescaler 64
	TCCR0B |= ((1 << CS01) | (1 << CS00));

	// Enable interrupt for timer0
	TIMSK0 |= (1 << OCIE0A);
	// Enable global interrupts
	SREG |= (1 << 7);

	while (1) {
		if (millis >= 1000) {
			millis = 0;
			// Switch the state of the LED
			PORTB ^= (1 << PORTB3);
		}
	}
}

So what have changed ? The compilation... Indeed, I code in C, compile with avr-gcc and upload the program with avrdude.
However, I reused the compilation command from another project which used another card. I didn't have any problem until here but now it matters.

Thank you everybody contributing to this post for your time, talent and kindness.

Pauloux :heart:

The above is your goal. The millis() function reads the content of miilisCounter which is advanced by 1 ms in the background using Timer0 interrupt.

I am confused as to what you have achived vs your goal.

Which differs from the non-working code.

1 Like

I use the timer 0 to track the time spend since the beginning of the program. I store this information in the variable millis.
So yes it is not exactly like the millis() function but the result is the same since I can acces the time in milliseconds since the program started.

Please don't get confused with the popular millis() functionon of Arduino UNO. It is a function and its name should not be used by the user as an identfier for any other variable.