[AVR] WTF is going on with this timer overflow interrupt behaviour? - Page 1
Forum section: electronics / general

author: HwAoRrDk
author_rank: Super Contributor
timestamp: September 26, 2017, 03:17:51 pm
content: I'm writing some code for an ATmega328P to transmit some data using the NEC IR protocol. It uses a 38 kHz carrier frequency, so I have Timer 0 set up to produce a PWM output at this frequency (Fast PWM mode with TOP = OCR0A). The timer is simply turned on and off to produce the pulse for each bit. Each bit pulse should be approximately 570-ish microsecs, which is about 22 cycles, so I have put an overflow interrupt on the timer which counts down those cycles and turns off the timer when zero is reached.So far so good, right? Not quite... I'm getting some bizarre behaviour that I'm struggling to comprehend or explain.Because the overflow interrupt takes a few microsecs to execute, I get a small 'runt' cycle from the timer before it is shut off. So in order to eliminate this, I actually decreased my required cycle count by one (to 21), and added a small delay (a few us) before shutting off the timer, so the runt cycle can complete.But, with this delay included, for some unknown reason, the overflow interrupt is firing when the timer isstarted!Literally, I comment the single line of code for the delay: no OVF interrupt at timer turn-on; with delay, spurious interrupt at turn-on.See attached screenshot from logic analyser captures.(By the way, I'm measuring this by having the first line of code in the overflow interrupt toggle a separate pin, and I'm capturing both with my logic analyser, so I can see the timing of carrier frequency output versus interrupt execution.)

author: technix
author_rank: Super Contributor
timestamp: September 26, 2017, 05:38:54 pm
content: Can you combine two timers with some external logic circuitry? This way you may get some better results.If you can switch microcontroller families, some STM32L0 and F0 parts comes with a special timer mode intended for generating those NEC codes.

author: HwAoRrDk
author_rank: Super Contributor
timestamp: September 26, 2017, 06:49:57 pm
content: It's just some hobby experimentation I'm doing, messing around on an Arduino board. It's not worth switching to a completely different MCU family just to solve this issue.I doubt not having a 'clean' carrier signal is even an actual problem in terms of transmission, as it's my understanding that receivers that implement the NEC IR protocol usually have very loose tolerances, and that the low-pass filtering that removes the carrier frequency will probably produce a perfectly adequate bit pulse.But that doesn't take away that this behaviour is bugging me. Why does a piece of code, within the overflow interrupt, executed before turning the timer off, cause that same interrupt to be spuriously triggered next time the timer is turned back on? It doesn't make sense.

author: HwAoRrDk
author_rank: Super Contributor
timestamp: September 26, 2017, 08:26:51 pm
content: I did a bit more experimentation, and found that I was mistaken earlier when I thought the overflow interrupt was spuriously firing straight away when the timer was started.What actually appears to be happening is that the first cycle of the timer produces no output on OC0B! I found this by hooking up a third I/O pin to my logic analyser and toggling that when my code was starting the timer. So in fact, before, the overflow interrupt was occurring at the correct time, but I was missing the timer output.WTF is going on? Why does one line of code (simply_delay_us(13.5)) called before the previous turn-off of the timer, screw up the output on the next re-start of that timer?

author: cv007
author_rank: Frequent Contributor
timestamp: September 27, 2017, 02:33:51 am
content: You sure those traces are labeled correctly? it seems opposite of what it is.Assuming top trace is tovf irq pin toggle and middle trace is ir out-your 'without delay' traces show the tovf must be set when you enable the timer otherwise why get that interrupt immediately?your 'with delay' shows that it takes a half cycle to fire the irq, which seems correct.If you swap those with/without labels it makes more sense-in the irq where you delay, as you are delaying through another overflow the tovf flag is set again before you turn the timer off (and presumably disable the timer irq), also presumably you are enabling the timer irq right after enabling the timer (and clearing t0 counter?), and with the tovf flag set jump right into the irq.In the 'no delay' irq, the tovf flag is not set so when starting again, no immediate irq (since you presumably clear the timer in the enabling process).If you have them labeled correctly, then explain the immediate irq after enabling the timer.Without changing what you already have, one simple solution-instead of delay_us, just wait for tovf flag to set again, then clear it manually, then whatever else you do (turn off irq, turn off timer)- the tovf flag will be clear when starting again.

author: HwAoRrDk
author_rank: Super Contributor
timestamp: September 27, 2017, 10:13:39 am
content: Quote from: cv007 on September 27, 2017, 02:33:51 amYes, they are correct.Top trace is the IR output (pin 11, PD5/OC0B), edges on the second trace are timer overflow interrupt, edges on the third trace correspond to the timer being started.Quote from: cv007 on September 27, 2017, 02:33:51 amWhat IRQ immediately after enabling the timer? The traces in the latest screenshot above show no such thing.Quote from: cv007 on September 27, 2017, 02:33:51 amI'm not sure I understand what you're suggesting. Within the OVF interrupt, when I've determined I need to stop the timer, instead of a delay, wait for the TOV0 flag (in TIFR0) to be set once again, and then stop the timer? How will that solve anything? Doing that, I'd still get an extra incomplete 'runt' output from the timer, no different to if I let the interrupt fire one more time.

author: cv007
author_rank: Frequent Contributor
timestamp: September 27, 2017, 12:16:53 pm
content: I would suggest adding more info to get better or any answers-I was guessing the pwm mode was set to toggle its output and the second trace was its output. I was guessing the first trace was the overflow interrupt where a pin was set high, then low at the end of the interrupt. I was guessing oc0A output was used as toggle mode does not seem to be available on oc0B. Lots of guessing on my part.You are saying the first trace is the ir output? and the second trace is the overflow interrupt pin toggle (it would then seem you just invert the pin in the interrupt?)QuoteBecause you delay long enough to get the overflow flag set again. If still set when the timer is re-enabled the interrupt fires right away (or more accurately, it fires when the timer irq is enabled again).How about some info-which timer mode- it seems mode 7com0 bits set to ?enable timer, how?disable, how?is the timer cleared?any manual clearing of tovf flag?

author: Buriedcode
author_rank: Super Contributor
timestamp: September 27, 2017, 12:48:35 pm
content: Some code would be helpful to see exactly what you're doing.I agree with cv007, I too assumed (rightly or wrongly) the top trace was the OVF interrupt firing, and the lower trace was the output compare output toggling.

author: HwAoRrDk
author_rank: Super Contributor
timestamp: September 27, 2017, 12:50:58 pm
content: Quote from: cv007 on September 27, 2017, 12:16:53 pmYes. I am toggling the pins on traces 2 & 3 to indicate the points of call to the overflow interrupt and start of timer. (Toggling because it's fast - single instruction.)Quote from: cv007 on September 27, 2017, 12:16:53 pmThat was from my earlier post, before I realised itwasn'tthat the overflow interrupt was occurring at the wrong time. Please forget about the timing of the overflow interrupt, that's not the issue. Therealissue is that the timer is not immediately producing any output on the OC0B pin after the timer is started.Quote from: cv007 on September 27, 2017, 12:16:53 pm- Yes, timer mode 7 (fast PWM with TOP = OCR0A). I already mentioned this.- COM0B1 only (non-inverting mode, clear on OCR0B match, set at BOTTOM).- Timer is enabled by setting COM0B1 and setting the appropriate CS bits in TCCR0B.- Timer is disabled by doing the opposite.- If by "cleared", you mean reset the timer counter value, then yes, I am always setting TCNT0 to zero before enabling the timer.- No manual clearing of the TOV0 flag.I might as well just post my entire code:Code:uint8_t carrier_clk_src;volatile uint16_t carrier_cycles;int main(void) {DDRD |= _BV(DDD5) | _BV(DDD4) | _BV(DDD3);carrier_init(CARRIER_FREQ_HZ, CARRIER_DUTY_CYCLE);sei();uint8_t addr[] = { 0x86, 0x72 };uint8_t data_mute[] = { 0x16, 0xE9 };while(1) {transmit_code(addr, sizeof(addr), data_mute, sizeof(data_mute));_delay_ms(50);}}void transmit_code(uint8_t * addr, const size_t addr_len, uint8_t * data, const size_t data_len) {transmit_leader();for(size_t i = 0; i < addr_len; i++) transmit_byte(addr);for(size_t i = 0; i < data_len; i++) transmit_byte(data);transmit_stop();}void transmit_leader() {carrier_pulse(LEADER_PULSE_CYCLES);_delay_us(LEADER_TOTAL_PERIOD_US);}void transmit_byte(const uint8_t data) {for(uint8_t mask = 0x01; mask > 0x00; mask <<= 1) {carrier_pulse(BIT_PULSE_CYCLES);if(data & mask) {_delay_us(BIT_PULSE_ONE_PERIOD_US);} else {_delay_us(BIT_PULSE_ZERO_PERIOD_US);}}}void transmit_stop() {carrier_pulse(BIT_PULSE_CYCLES);_delay_us(BIT_PULSE_ZERO_PERIOD_US);}void carrier_pulse(const uint16_t cycles) {ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {carrier_cycles = cycles;}PIND |= _BV(PIND3);TCNT0 = 0;TCCR0A |= _BV(COM0B1);TCCR0B = (TCCR0B & ~(_BV(CS00) | _BV(CS01) | _BV(CS02))) | carrier_clk_src;}void carrier_init(const uint16_t frequency, const float duty_cycle) {const uint16_t prescalers = { 1, 8, 64, 256, 1024 };const uint8_t clk_srcs = { _BV(CS00), _BV(CS01), _BV(CS00) | _BV(CS01), _BV(CS02), _BV(CS00) | _BV(CS02) };TCCR0A |= _BV(WGM01) | _BV(WGM00);TCCR0B |= _BV(WGM02);TIMSK0 |= _BV(TOIE0);for(size_t i = 0; i < 5; i++) {uint16_t ocr = (F_CPU / frequency / prescalers) - 1;if(ocr <= UINT8_MAX) {OCR0A = ocr;carrier_clk_src = clk_srcs;break;}}OCR0B = OCR0A * duty_cycle;}ISR(TIMER0_OVF_vect) {PIND |= _BV(PIND4);if(--carrier_cycles == 0) {/* ---------- *//* With delay here, no output on OC0B for first cycle when timer is next started. Without, expected behaviour. */TIFR0 |= _BV(OCF0B);loop_until_bit_is_set(TIFR0, OCF0B);/* ---------- */TCCR0A &= ~(_BV(COM0B1));TCCR0B = (TCCR0B & ~(_BV(CS00) | _BV(CS01) | _BV(CS02)));PIND |= _BV(PIND3);}}By the way, thanks for the mention of waiting for the TOV flag - it indirectly inspired me to think of a better technique for my completion delay before stopping the timer. Instead of delaying for an arbitrary period of microseconds, I'm now instead clearing OCF0B and waiting for it to set again - i.e. at the point OCR0B matches and the carrier wave high period is over.

author: cv007
author_rank: Frequent Contributor
timestamp: September 27, 2017, 03:03:50 pm
content: I guess I'm not seeing how the debugging pins are being used (D3/D4), as I only see them being set in that code so am not sure where/how they are cleared.Anyway, from my limited abilities it seems your problem boils down to why is the OC0B pin not set right away in one case, and in the other it appears the OC0B pin is set right away.From the datasheet-QuoteI'm not sure if that means the SET on OC0B (pwm non-inverting) will NOT occur when the timer is manually set to 0, then enabled? (since there is no 'changes from TOP to BOTTOM' when you enable the pwm again. That would explain the 'missing' pulse, but then it would seem that should happen every time.additional info, and I think it makes sense if the datasheet says what I think it says-Code:carrier_cycles = 2oc0b = 1 (PWM SET)ocr0b matchoc0b = 0 (PWM CLEAR)timer TOP->BOTTOMoc0b = 1 (PWM SET)timer overflow irqcarrier_cycles = 1irq exitocr0b matchoc0b = 0 (PWM CLEAR)timer TOP->BOTTOMoc0b = 1 (PWM SET)timer overflow irqcarrier_cycles = 0clear ocf0bwait for ocf0boc0b = 0 (PWM CLEAR)stop timer, disable outputexit irqwaiting to start timer againoc0b = 0 (PWM CLEAR) still clearset timer to 0, start timerNO TOP->BOTTOM, so NO oc0b SETHERE IS THE 'MISSING' PULSEsame but WITHOUT delay in irq, the oc0b output remains SET when the timer is stopped- when timer starts again oc0b output is still SETI'm quite confused at this point, but I think the above is correct, or should help I think.summary- with delay, oc0b pwm remains CLEAR and starting the timer at 0 produces no SETwithout delay, timer is stopped while oc0b is SET, so when timer started at 0, the setting of the com bit re-enables the last state of 0c0b which was SETHere is an alternate idea (only because I recently created an LG 2 button remote using a pic10lf322)-I simply leave the pwm enabled, and change the duty to 0 or 25% as needed. The timer overflow is manually counted (I have nothing better to do, so do not need irq). Since the compare is double buffered, I just set the pwm back to 0% at the end of every bit and the next bit will override it long before the buffered reload occurs. The advantage is I never have to worry about that last pwm. One other tip I discovered by accident- leave off that last end pulse to 'invalidate' the previous frame (I was using long button presses for other functions but for faster response I wanted to get the frame going in case it was a short press- before the last end pulse, I check the button again, if button still pressed I don't send the end pulse, but instead wait for the frame time to expire and send a new code.Code:#define IR_T1 21 //first half of every bit, seond half of bit0#define IR_T2 63 //second half of bit1#define IR_TS1 339 //start- on#define IR_TS2 170 //start- off#define IR_TIDLE 1492 //idle time#define IR_TIDLER 3630 //idle time after repeat#define IR_TFRAME 4077 //total frame time (108ms)void ir_pulse(uint16_t c, bool b){if(b){ //on//25% duty (will be loaded at next overflow)pwm_duty(1);}for( ; c; c--, TMR2IF = 0 ) while( !TMR2IF );//0% duty - output off (will be loaded at next overflow)//any other value loaded before tmr2if will override itpwm_duty(0);}//******************************************************************************// nec ir frame// start pulse, address, naddress, data, ndata, end pulse, idle// nec start pulse - 9ms on, 4.5ms off// nec bit 0 pulse - 562.5us on 562.5us off - total 1.125ms// nec bit 1 pulse - 562.5us on 1.675ms off - total 2.225ms//******************************************************************************void ir_frame_nec(uint8_t a, uint8_t d){union { uint8_t dat4; uint32_t dat32; } u;u.dat4 = a; u.dat4 = ~a; u.dat4 = d; u.dat4 = ~d;pwm_init( IR_NEC_PR2 );ir_pulse( IR_TS1, 1 ); //start 9ms onir_pulse( IR_TS2, 0 ); //4.5ms offfor( uint8_t i = 32; i; i--, u.dat32 >>= 1 ){ //datair_pulse( IR_T1, 1 ); //on time always sameir_pulse( u.dat32 & 1 ? IR_T2 : IR_T1, 0 ); //off time}ir_pulse( IR_T1, 1 ); //end pulseir_pulse( IR_TIDLE, 0 ); //idle}

author: HwAoRrDk
author_rank: Super Contributor
timestamp: September 27, 2017, 03:52:11 pm
content: Quote from: cv007 on September 27, 2017, 03:03:50 pmThe PINx registers are normally used to only read the pin state, but when you write a bit, it toggles that pin's output. Normally you use the PORTx register to set output state - which is probably what you were thinking of. I was only interested in the edge transition for debugging, and didn't want to bother with setting/clearing logic.Quote from: cv007 on September 27, 2017, 03:03:50 pmYes, precisely.Quote from: cv007 on September 27, 2017, 03:03:50 pmI had that thought in my head too, but because in both scenarios I'm doing the exact same thing with regard to resetting the counter to zero, like you say, you'd think it would happen every time.Here's a though that just occurred to me: perhaps the OC0B output gets 'stuck' in the state it was in when it was last disabled, such that when it is re-enabled, it maintains its previous state until the next TOP->BOTTOM transition or OCR0B match. And it just so happens that without the delay code, when it gets disabled it's in the set state, which happens to be the same state immediately required upon next timer re-start.Quote from: cv007 on September 27, 2017, 03:03:50 pmThanks for the tips. Haven't even got round to considering long-vs-short button presses yet, though.

author: cv007
author_rank: Frequent Contributor
timestamp: September 27, 2017, 04:10:49 pm
content: I had updated my post with additional info (in the middle somewhere), you may have missed it. I think it explains what is happening.

author: HwAoRrDk
author_rank: Super Contributor
timestamp: September 27, 2017, 04:36:26 pm
content: Quote from: cv007 on September 27, 2017, 04:10:49 pmQuote from: cv007 on September 27, 2017, 03:03:50 pmI think we just simultaneously reached the same conclusion.Further reading of the datasheet seems to back up the theory that the state of the OC0x outputs is maintained independently. The first paragraph in section 15.6 says that it is a separate register, and seems to imply its state is only ever changed by compare matches, forcing the output (FOC0x) or by a system reset.It also says further on that "the design of the Output Compare pin logic allows initialization of the OC0x state before the output is enabled", so perhaps I could use FOC0B to set OC0B before starting the timer to ensure an initial carrier pulse? But... on the other hand, the datasheet also says of FOC0B that "the FOC0B bit is only active when the WGM bits specify a non-PWM mode", so I don't know whether that may work. I suppose I shall have to try it.