Author Topic: [AVR] WTF is going on with this timer overflow interrupt behaviour?  (Read 2653 times)

0 Members and 1 Guest are viewing this topic.

Offline HwAoRrDkTopic starter

  • Super Contributor
  • ***
  • Posts: 1477
  • Country: gb
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 is started! :wtf: 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. |O 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.)
« Last Edit: September 26, 2017, 03:23:12 pm by HwAoRrDk »
 

Offline technix

  • Super Contributor
  • ***
  • Posts: 3507
  • Country: cn
  • From Shanghai With Love
    • My Untitled Blog
Re: [AVR] WTF is going on with this timer overflow interrupt behaviour?
« Reply #1 on: September 26, 2017, 05:38:54 pm »
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.
 

Offline HwAoRrDkTopic starter

  • Super Contributor
  • ***
  • Posts: 1477
  • Country: gb
Re: [AVR] WTF is going on with this timer overflow interrupt behaviour?
« Reply #2 on: September 26, 2017, 06:49:57 pm »
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.
 

Offline HwAoRrDkTopic starter

  • Super Contributor
  • ***
  • Posts: 1477
  • Country: gb
Re: [AVR] WTF is going on with this timer overflow interrupt behaviour?
« Reply #3 on: September 26, 2017, 08:26:51 pm »
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? :rant:
 

Online cv007

  • Frequent Contributor
  • **
  • Posts: 826
Re: [AVR] WTF is going on with this timer overflow interrupt behaviour?
« Reply #4 on: September 27, 2017, 02:33:51 am »
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.
« Last Edit: September 27, 2017, 02:51:32 am by cv007 »
 

Offline HwAoRrDkTopic starter

  • Super Contributor
  • ***
  • Posts: 1477
  • Country: gb
Re: [AVR] WTF is going on with this timer overflow interrupt behaviour?
« Reply #5 on: September 27, 2017, 10:13:39 am »
You sure those traces are labeled correctly? it seems opposite of what it is.

Yes, 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.

If you have them labeled correctly, then explain the immediate irq after enabling the timer.

What IRQ immediately after enabling the timer? The traces in the latest screenshot above show no such thing. ???

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.

I'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.
 

Online cv007

  • Frequent Contributor
  • **
  • Posts: 826
Re: [AVR] WTF is going on with this timer overflow interrupt behaviour?
« Reply #6 on: September 27, 2017, 12:16:53 pm »
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?)

Quote
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.
Because 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 7
com0 bits set to ?
enable timer, how?
disable, how?
is the timer cleared?
any manual clearing of tovf flag?
 

Offline Buriedcode

  • Super Contributor
  • ***
  • Posts: 1611
  • Country: gb
Re: [AVR] WTF is going on with this timer overflow interrupt behaviour?
« Reply #7 on: September 27, 2017, 12:48:35 pm »
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.

 

Offline HwAoRrDkTopic starter

  • Super Contributor
  • ***
  • Posts: 1477
  • Country: gb
Re: [AVR] WTF is going on with this timer overflow interrupt behaviour?
« Reply #8 on: September 27, 2017, 12:50:58 pm »
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?)

Yes. 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
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.
Because 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).

That was from my earlier post, before I realised it wasn't that the overflow interrupt was occurring at the wrong time. Please forget about the timing of the overflow interrupt, that's not the issue. The real issue is that the timer is not immediately producing any output on the OC0B pin after the timer is started.

How about some info-
which timer mode- it seems mode 7
com0 bits set to ?
enable timer, how?
disable, how?
is the timer cleared?
any manual clearing of tovf flag?

- 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: [Select]
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[i]);
for(size_t i = 0; i < data_len; i++) transmit_byte(data[i]);
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[5] = { 1, 8, 64, 256, 1024 };
const uint8_t clk_srcs[5] = { _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[i]) - 1;
if(ocr <= UINT8_MAX) {
OCR0A = ocr;
carrier_clk_src = clk_srcs[i];
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. :-+
 

Online cv007

  • Frequent Contributor
  • **
  • Posts: 826
Re: [AVR] WTF is going on with this timer overflow interrupt behaviour?
« Reply #9 on: September 27, 2017, 03:03:50 pm »
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-
Quote
The PWM waveform is generated by setting (or clearing) the OC0x Register at the compare
match between OCR0x and TCNT0, and clearing (or setting) the OC0x Register at the timer clock cycle
the counter is cleared (changes from TOP to BOTTOM).
I'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: [Select]
carrier_cycles = 2
oc0b = 1 (PWM SET)
ocr0b match
oc0b = 0 (PWM CLEAR)
timer TOP->BOTTOM
oc0b = 1 (PWM SET)
timer overflow irq
  carrier_cycles = 1
irq exit

ocr0b match
oc0b = 0 (PWM CLEAR)
timer TOP->BOTTOM
oc0b = 1 (PWM SET)
timer overflow irq
  carrier_cycles = 0
  clear ocf0b
  wait for ocf0b
  oc0b = 0 (PWM CLEAR)
  stop timer, disable output
exit irq

waiting to start timer again
oc0b = 0 (PWM CLEAR) still clear
set timer to 0, start timer
NO TOP->BOTTOM, so NO oc0b SET
HERE IS THE 'MISSING' PULSE

same but WITHOUT delay in irq, the oc0b output remains SET when the timer is stopped- when timer starts again oc0b output is still SET
I'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 SET
without 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 SET


Here 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: [Select]
#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 it
    pwm_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[4]; uint32_t dat32; } u;
    u.dat4[0] = a; u.dat4[1] = ~a; u.dat4[2] = d; u.dat4[3] = ~d;
    pwm_init( IR_NEC_PR2 );
    ir_pulse( IR_TS1, 1 ); //start 9ms on
    ir_pulse( IR_TS2, 0 ); //4.5ms off
    for( uint8_t i = 32; i; i--, u.dat32 >>= 1 ){ //data
        ir_pulse( IR_T1, 1 ); //on time always same
        ir_pulse( u.dat32 & 1 ? IR_T2 : IR_T1, 0 ); //off time
    }
    ir_pulse( IR_T1, 1 ); //end pulse
    ir_pulse( IR_TIDLE, 0 ); //idle
}


« Last Edit: September 27, 2017, 03:48:25 pm by cv007 »
 

Offline HwAoRrDkTopic starter

  • Super Contributor
  • ***
  • Posts: 1477
  • Country: gb
Re: [AVR] WTF is going on with this timer overflow interrupt behaviour?
« Reply #10 on: September 27, 2017, 03:52:11 pm »
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.

The 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.

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.

Yes, precisely. >:D

From the datasheet-
Quote
The PWM waveform is generated by setting (or clearing) the OC0x Register at the compare
match between OCR0x and TCNT0, and clearing (or setting) the OC0x Register at the timer clock cycle
the counter is cleared (changes from TOP to BOTTOM).
I'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.

I 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.

Here 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.

Thanks for the tips. Haven't even got round to considering long-vs-short button presses yet, though. :)
 

Online cv007

  • Frequent Contributor
  • **
  • Posts: 826
Re: [AVR] WTF is going on with this timer overflow interrupt behaviour?
« Reply #11 on: September 27, 2017, 04:10:49 pm »
I had updated my post with additional info (in the middle somewhere), you may have missed it. I think it explains what is happening.
 

Offline HwAoRrDkTopic starter

  • Super Contributor
  • ***
  • Posts: 1477
  • Country: gb
Re: [AVR] WTF is going on with this timer overflow interrupt behaviour?
« Reply #12 on: September 27, 2017, 04:36:26 pm »
I had updated my post with additional info (in the middle somewhere), you may have missed it. I think it explains what is happening.
summary- with delay, oc0b pwm remains CLEAR and starting the timer at 0 produces no SET
without 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 SET

I 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.
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf