AVR Timer Interrupt Triggered Imediately? - Page 1
Forum section: electronics / beginners

author: benrr101
author_rank: Newbie
timestamp: July 25, 2023, 06:54:38 pm
content: Hello all, I've been slowly learning how to work with microcontrollers for the past few months. I elected to start with AVR and I'm determined to avoid the arduino platform if at all possible. For the most part, it's been successful, but I'm thoroughly stumped on getting timers and sleeping working properly. I think I've exhausted my searching skills to try and find an answer myself, so I'm reaching out to hopefully learn what I'm missing.This is a contrived example that's simplified from the project I hope to use it in. The expected behavior of this code is to put the microcontroller to sleep until a button is pressed. Once the button is pressed, it sets a timer for ~5ms, and waits for it to overflow with a busy loop (more on that in a second). The overflow interrupt sets the flag that the busy loop is waiting for and the microcontroller goes back to sleep to wait for a button press. Since I haven't quite figured out how to debug the microcontroller like I would software, I added some code to toggle a pin to track the execution of the interrupt. I expect the microcontroller to wake up, set the pin high for approximately 5ms, then set the pin low.Hooking up my oscilloscope to the button and the debug pin, I can see what actually happens is the microcontroller wakes up, sets the pin high for approximately 5us, then sets the pin low. This suggests to me that the interrupt is triggering immediately after enabling it. What's bizarre to me is that most of the time, this is the behavior, but sometimes the pin goes high and stays high for the full 5ms. This suggests to me there is some kind of race condition going on with setting the timer, but I am not sure what it could be. I've also tried using the output compare to trigger at 5ms and resetting the timer to 0, but this causes the same 5us triggering.What's more frustrating is that I don't seem to understand how timers can (or cannot) wake up the microcontroller from sleep. If I replace the busy wait loop with an instruction to put the microcontroller into idle sleep, I see the pin go high and then never go low. This suggests to me that the interrupt doesn't wake the microcontroller from idle, despite the datasheet saying at the very least overflows should wake the processor.I've also tried some more basic timer scenarios where the processor uses the _delay_ms utility to delay before calling the delay subroutine (ie, use a delay to simulate waiting for the button press). I've also tried using PWM modes of the timer, as well (to blink an LED). In these cases, the timer behaves as expected and triggers after the desired delay. I've also tried manually clearing the interrupt bit in the interrupt handler, but that doesn't make a difference (and I didn't expect it to since the interrupt handler is supposed to automatically clear the bit).Although I've had trouble with timers on both the ATmega48A and ATtiny44, this code was tested on the ATtiny44.Code:#include <avr/interrupt.h>#include <avr/io.h>#include <avr/sleep.h>void delay() {PORTA |= (1 << PORTA4); // Turn on diagnostic pinTCNT0 = (255 - 5); // Set counter for ~5msTIMSK0 |= (1 << TOIE0); // Enable timer0 overflow interruptGPIOR0 = 0; // Indicate delay time startedwhile((GPIOR0 & (1 << 1)) == 0) {} // Wait for delay time to end}ISR(PCINT0_vect) {PCMSK0 = 0; // Disable pin-change interrupts on port APORTA ^= (1 << PORTA0);}ISR(TIM0_OVF_vect) {TIMSK0 = 0; // Disable timer0 interruptsGPIOR0 |= (1 << 1); // Indicate delay time is overPORTA &= ~(1 << PORTA4); // Turn off diagnostic pin}int main() {DDRA = (1 << PORTA4) | (1 << PORTA0); // PA4, PA0 is outputPORTA |= (1 << PORTA5); // PA5 pull-up onGIMSK |= (1 << PCIE0); // Enable pin-change interrupts on port ATCCR0B = (1 << CS00) | (1 << CS02); // Timer prescaler is 1024TCCR0A = 0; // Normal modesei(); // Enable interruptsset_sleep_mode(SLEEP_MODE_IDLE); // Set sleep mode to idlewhile(1) {PCMSK0 |= (1 << PCINT5); // Enable pin-change interrupts on PA5sleep_mode(); // Go to sleepdelay(); // Run delay subroutine}return 0;}Thanks in advance for any help you can give me!!

author: Buriedcode
author_rank: Super Contributor
timestamp: July 25, 2023, 07:34:12 pm
content: Without going through your code completely - I can't see you making sure the interrupt flag is clear before enabling. It looks like you start the timer on startup, and this continues to run in the background so will set the overflow bit/flag (TOV0) every time it rolls over. This only causes the ISR to fire if global interrupts are enabled, and the TOIE0 is set.So if you set the TOIE0 after the timer has been running for a bit, it will immediately cause an interrupt because the timer has been running long enough for a rollover, and the overflow bit was set when it did.Either start and stop the timer as required, or when setting up the timer for an interrupt, write to the timer register to set the delay,then clear the overflow bit, TOV0, in case it has already been set, then enable the interrupt with TOIE0.Edited to be bit clearer: It might not be that obvious, but these interrupt bits/flags on peripherals tend to be set by the peripheral regardless of whether the interrupt isenabledby the enable bit. Enabling them simply allows them to cause the program to jump to the interrupt vector (assuming global interrupts is enabled as well). This means you can use these flags by polling them, without having to use them just for interrupts

author: benrr101
author_rank: Newbie
timestamp: July 26, 2023, 02:26:54 am
content: Thank you so very much! This is the exact thing I was missing from my understanding! I added a line to the `delay` subroutine before enabling the overflow interrupt mask that clears the overflow interrupt flag. I also added a line in `main` before enabling the PCINT5 interrupt to clear any pending pin-change interrupt. This also fixed my problem with sleeping in the `delay` subroutine!Obviously there's a lot of ways to skin a cat with a microcontroller, but going forward, I want to make sure I'm following best practices. Based on your comment that interrupt flags for peripherals tend to be set regardless of if the interrupt is enabled, it sounds like best practice is to clear any interrupt flag before enabling the interrupt (including the pin-change interrupt flag)? The other option, at least for the timer overflow, would be to stop the timer after it overflows and restart it next time I need the delay.As for the pin-change interrupt, it's a little unclear from the datasheet if pin-change interrupt flags are set regardless of whether the mask is enabled for the pin. I suppose if the pin is never expected to change, then it might be unnecessary to clear the pin-change interrupt flag. But, at least for this project, I'm expecting to have periods where I don't want to wake up the uc on pin-change. In that case, maybe it's better to just get into a habit of clearing the interrupt flag before enabling the interrupt? Wouldn't hurt anything, right?Again, thank you so much! I was really stuck on this. If you're ever in Texas, I'll buy you a beer or two

author: Ian.M
author_rank: Super Contributor
timestamp: July 26, 2023, 03:21:55 am
content: Beware: Some peripherals have interrupt flags that cant be directly cleared. The interrupt flag clears itself when you either read or write a data buffer register associated with the peripheral. A case in point is the ATmega USART transmitter UDRE interrupt flag and receiver RXC interrupt flag which are cleared by writing/ reading the UDR respectively. See ATmega328P full datasheet, sections 24.7.3 and 24.8.3. The USART is not the only peripheral with this slightly annoying 'locked' interrupt flag feature, so read your MCU's peripheral documentation carefully paying special attention to any mention of interrupts. Also, Atmel weren't the only MCU manufacturer to do this - its a commonly found pattern across many manufacturers for MCU peripherals that do buffered or serialized data transfer.

author: Faranight
author_rank: Unknown
timestamp: July 26, 2023, 04:56:40 am
content: Yep, some flags get cleared automatically when a certain condition is met, some get cleared automatically when the corresponding interrupt routine is executed, and some don't get cleared at all (you're supposed to clear them manually).I've had some really bizarre code behavior in the past because I didn't clear an interrupt flag manually where I was supposed to.Definitely check the microcontroller datasheet and the respective peripheral pages to see how the flag (and if) should be cleared.

author: Buriedcode
author_rank: Super Contributor
timestamp: July 26, 2023, 06:41:22 pm
content: Quote from: benrr101 on July 26, 2023, 02:26:54 amGenerally yes, with the caveat mentioned by the other replies that certain bits/flags are "read only" and controlled by hardware. The datasheet should show the register with R or R/W below each bit indicating whether or not you can manually clear (or set!) it.Quote from: benrr101 on July 26, 2023, 02:26:54 amAgreed. I tend to assume the bit/flag is set when the condition is met regardless of whether the enable bit is set. It may not always be the case, but its a safe bet - again the datasheet should show this information, but I admit the AVR datasheet for the interrupt-on-change doesn't make this clear at all. Although looking on microchips website, I found the following image.Page 53 of the Atmega 328/168/88 datasheet I have (might not be the latest), figure 12-1 shows a rough schematic of the interrupt on change logic. I'll try to include the picture inline, but the forum doesn't always let me:That shows that the interrupt flag is only set if the mask is set - which is handy, because it means if you have more than one you don't have to completely disable all interrupt-on-change interrupts just for one pin, but rather just the mask for that pin. Of course, if its the only interrupt pin you use you can disable all IOC interrupts for PCINT0 to 7 with the PCIE0 bit in the PCICR register (1 = enabled).There's also this bit from section 12.2.5 describing the PCIFR – Pin Change Interrupt Flag Register:QuoteLooks like it is automatically cleared when the ISR is serviced,but I would still get into the habit of clearing the bit before enabling- because previous conditions may mean you didn't actually service the interrupt, perhaps because global wasn't enabled, so it wasn't automatically cleared.So the chain goes: the mask register determines which of the 8 pins/bits can set the PCIF0/1/2 bits/flags (3 registers for 24 pins total). The PCIE0/1/2 enables each of these flags to generate an interrupt, and finally global interrupts have to be enabled for it to cause the program to jump. If the interrupt fires (jumps to interrupt vector), it will automatically clear the flag.Generallyinterrupts on micros are just massive tree's of AND gates, where enables determine what signals are carried forward. To make matters more confusing, sometimes bits have to be cleared to enable funtions, rather than set, but I think AVR is pretty consistent in that '1' enables, and intrrupt flags/bits are set when active, and cleared when inactive.Quote from: benrr101 on July 26, 2023, 02:26:54 amAny time dude. This sort of thing is worth double cheking with others - trawling through datasheets can be pretty grueling at times.

author: benrr101
author_rank: Newbie
timestamp: July 27, 2023, 03:55:02 am
content: Thanks for the advice everyone! I think now that I've been bitten by dealing with interrupt flags, I'll definitely keep my eyes open for similar situations going forward. The note about the ATmega48/88/168/328 USART will be particularly important since a different project I paused to start this one will be using UART (very low speed, and no more than a few bytes at a time, but UART nonetheless).QuoteHaha, with the exception of manually clearing the interrupt flags by writing a 1 to them

author: Buriedcode
author_rank: Super Contributor
timestamp: July 27, 2023, 06:03:10 pm
content: Quote from: benrr101 on July 27, 2023, 03:55:02 amYes exactly! I forgot about that. - Been using PIC's more often than AVRs recently, another trap there, getting too used to one convention

author: Doctorandus_P
author_rank: Super Contributor
timestamp: July 28, 2023, 01:18:46 pm
content: If the interrupt flag is already set at the moment an interrupt is being enabled, the interrupt will trigger immediately.There is something I remember form clearing the interrupt flag by writing a one to it before an interrupt is enabled, but It's probably 10 years or so ago when I last did some serious work with the AVR uC's.