Atmega324P timer interrupt is delayed by mainloop

Go To Last Post
25 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi all,

 

My code, which is using software PWM generated by Timer1, freq is 41kHz, CPU using external 14.7MHz clock. Only Timer1 using interrupt, nowhere disable global interrupt.

I set the PWM pulse width with a fixed value (here are 62%duty).

If my mainloop code is simple, the PWM generated stable (max positive pulse and min positive pulse is 80ns different, max negative pulse and min negative pulse is 83ns different). But if mainloop code is longer (with some SPI read, ADC read, all polling), the PWM generated is not stable (max positive and min positive is 333ns different, max negative and min negative is 500ns different).

 

My question is, why the interrupt time (timer1 interrupts, which used to generate PWM wave) is affected by none interrupt code (mainloop code)?

 

If you have any idea, please share. Thank you.

Last Edited: Mon. Jun 12, 2017 - 07:27 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

nvl1109 wrote:
But if mainloop code is longer (with some SPI read, ADC read, all polling), the PWM generated is not stable
Which suggests one of those SPI_read or ADC_read operations must be disabling interrupts.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hello clawson,

 

I do SPI and ADC reads by polling model, not interrupt. And nowhere in code disable interrupts.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Well nothing will stop interrupts except (a) the module's xxIE bit is cleared or (b) the global I bit in SREG is cleared.

 

Otherwise the interrupts will always have the priority.

 

I guess it may be time to show your code.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I will create an simple application to demo my problem, then post my code.
In that time, if you have any other ideas, please share. Thank you.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

This may be a silly question but why are you using a timer interrupt to generate PWM instead of using the hardware generated PWM?  This should be totally stable regardless of any other interrupts (even with interrupts disabled)

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Our product reuses the old hardware board which does not compatible with hardware pwm. That why I have to use software pwm :(

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

nvl1109 wrote:
And nowhere in code disable interrupts.
Perhaps not by you personally, but there are several cases where the generated code contains short periods of disabled interrupts.

More obvious cases, like using ATOMIC_BLOCK.

And less obvious cases, like a function using a "bigger" amount of local automatic data.

Stefan Ernst

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

From OP's description, something other than
SP manipulation (one cycle) is disabling interrupts.
Seven cycles is not even enough for an empty interrupt handler.

 

@OP:

 

Get the .lss or .s file and search for sei.
If you do not it, look at the logic for your software PWM.
Also, is the production volume low enough
for a trace cutter and some yellow wire?
My guess is that switching the connections of two port pins
would be enough to allow using hardware PWM.

 

My guess is that understanding your software issue
is worth the effort even if you change the hardware.
Changing the hardware might be worth the
effort even if you can fix your software PWM.
Hardware PWM will be more stable and more easily documented.
After getting rid of your last interrupt,
analysing the timing of the main loop will be simpler.

Iluvatar is the better part of Valar.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hello all, here is my code for demonstration my problem.

 

Here is my measurement with two cases: the mainloop does nothing (empty while(1) mainloop) and the mainloop reads ADC (as code above)

PWM measurement for 3 seconds
  Min positive pulse Max positive pulse Different Min negative pulse Max negative pulse Different
Empty mainloop 16.75us 16.83us 80ns 7.583us 7.667us 84ns
ADC read in mainloop 16.67us 16.92us 250ns 7.583us 7.833us 250ns

Through the measurement, we can see the different between min-max values are increased if the mainloop does other stuffs.

In this case, if empty mainloop, the max and min just about 1 machine cycle different. But if add ADC reading in mainloop, the max/min different increase to 3 machine cycle.

In my product's code, mainloop is more complex, so the different is 400ns different, with 41kHz frequency, this makes the output voltage un-stable.

So my goal is reduce this different without changing hardware and PWM frequency.

 

skeeve wrote:
Changing the hardware might be worth the effort even if you can fix your software PWM.
 

@skeeve: I wish I can change the hardware. With new hardware, everything are very simple to me.

 

PS: I can't include my code into post. So I attached it. Please see in attached file.

 

Any other reasons that you think they cause this interrupt delay? Please share your idea. Thank you.

Attachment(s): 

Last Edited: Tue. Jun 13, 2017 - 02:48 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Adding the ADC routine is using CALLs and RETs which take 4 cycles to execute. At 14.7456MHz, 4 cycles is going to be ~270nS, during which the interrupt could be delayed...

 

Edit: actually it is probably using RCALL @ 3 cycles; however, the RETurn is still 4 cycles.

David

Last Edited: Tue. Jun 13, 2017 - 04:21 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

frog_jr wrote:

Adding the ADC routine is using CALLs and RETs which take 4 cycles to execute. At 14.7456MHz, 4 cycles is going to be ~270nS, during which the interrupt could be delayed...

 

Edit: actually it is probably using RCALL @ 3 cycles; however, the RETurn is still 4 cycles.

Thanks, frog_jr.

 

If I remove the function call, just using directly the ADC read code inside mainloop. Still same result.

 

    while (1) {
		int i, tmp;
		
		for (i = 0; i < 100; ++i) {
			ADMUX = 2 | (0x1  << REFS0); // Channel 2, vref
			// Delay needed for the stabilization of the ADC input voltage
			_delay_us(10);
			// Start the AD conversion
			ADCSRA |= (1 << ADSC);

			while ((ADCSRA & (1 << ADSC))) {
				/* wait for conversion complete */
			}
			tmp = ADC;
			(void)tmp;
		}
	}

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Here is asm .lss of main function:

000000dc <main>:
  dc:	f8 94       	cli
  de:	8a ef       	ldi	r24, 0xFA	; 250
  e0:	8a b9       	out	0x0a, r24	; 10
  e2:	8a e0       	ldi	r24, 0x0A	; 10
  e4:	8b b9       	out	0x0b, r24	; 11
  e6:	10 92 80 00 	sts	0x0080, r1	; 0x800080 <__TEXT_REGION_LENGTH__+0x7e0080>
  ea:	10 92 85 00 	sts	0x0085, r1	; 0x800085 <__TEXT_REGION_LENGTH__+0x7e0085>
  ee:	10 92 84 00 	sts	0x0084, r1	; 0x800084 <__TEXT_REGION_LENGTH__+0x7e0084>
  f2:	8e e4       	ldi	r24, 0x4E	; 78
  f4:	91 e0       	ldi	r25, 0x01	; 1
  f6:	90 93 89 00 	sts	0x0089, r25	; 0x800089 <__TEXT_REGION_LENGTH__+0x7e0089>
  fa:	80 93 88 00 	sts	0x0088, r24	; 0x800088 <__TEXT_REGION_LENGTH__+0x7e0088>
  fe:	8c ed       	ldi	r24, 0xDC	; 220
 100:	90 e0       	ldi	r25, 0x00	; 0
 102:	90 93 8b 00 	sts	0x008B, r25	; 0x80008b <__TEXT_REGION_LENGTH__+0x7e008b>
 106:	80 93 8a 00 	sts	0x008A, r24	; 0x80008a <__TEXT_REGION_LENGTH__+0x7e008a>
 10a:	80 91 6f 00 	lds	r24, 0x006F	; 0x80006f <__TEXT_REGION_LENGTH__+0x7e006f>
 10e:	86 60       	ori	r24, 0x06	; 6
 110:	80 93 6f 00 	sts	0x006F, r24	; 0x80006f <__TEXT_REGION_LENGTH__+0x7e006f>
 114:	81 e0       	ldi	r24, 0x01	; 1
 116:	80 93 81 00 	sts	0x0081, r24	; 0x800081 <__TEXT_REGION_LENGTH__+0x7e0081>
 11a:	87 e8       	ldi	r24, 0x87	; 135
 11c:	80 93 7a 00 	sts	0x007A, r24	; 0x80007a <__TEXT_REGION_LENGTH__+0x7e007a>
 120:	78 94       	sei
 122:	32 e4       	ldi	r19, 0x42	; 66
 124:	84 e6       	ldi	r24, 0x64	; 100
 126:	90 e0       	ldi	r25, 0x00	; 0
 128:	30 93 7c 00 	sts	0x007C, r19	; 0x80007c <__TEXT_REGION_LENGTH__+0x7e007c>
 12c:	21 e3       	ldi	r18, 0x31	; 49
 12e:	2a 95       	dec	r18
 130:	f1 f7       	brne	.-4      	; 0x12e <main+0x52>
 132:	00 00       	nop
 134:	20 91 7a 00 	lds	r18, 0x007A	; 0x80007a <__TEXT_REGION_LENGTH__+0x7e007a>
 138:	20 64       	ori	r18, 0x40	; 64
 13a:	20 93 7a 00 	sts	0x007A, r18	; 0x80007a <__TEXT_REGION_LENGTH__+0x7e007a>
 13e:	20 91 7a 00 	lds	r18, 0x007A	; 0x80007a <__TEXT_REGION_LENGTH__+0x7e007a>
 142:	26 fd       	sbrc	r18, 6
 144:	fc cf       	rjmp	.-8      	; 0x13e <main+0x62>
 146:	40 91 78 00 	lds	r20, 0x0078	; 0x800078 <__TEXT_REGION_LENGTH__+0x7e0078>
 14a:	50 91 79 00 	lds	r21, 0x0079	; 0x800079 <__TEXT_REGION_LENGTH__+0x7e0079>
 14e:	01 97       	sbiw	r24, 0x01	; 1
 150:	59 f7       	brne	.-42     	; 0x128 <main+0x4c>
 152:	e8 cf       	rjmp	.-48     	; 0x124 <main+0x48>

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

In the absence of anything to disable interrupts,
I'd expect delay from multi-cycle instructions
to be the primary cause of jitter.
Accordingly, I'm surprised that removing the rcall
and return instructions did not produce improvement.
So far, I can think of one reason: the main loop time was just right to
ensure that the timer interrupt event never occurred during an rcall or ret.
As an experiment, you might go back to the function
call version and add a nop to the main loop.
That should break the synchronization and increase the jitter.

 

Eventually, you should find the reason for the jitter,
if only to find a bound on it.
One can do cycle-accurate PWM even in the presence of jitter.
The technique requires settng the interrupt for a few cycles early.
In the ISR, read the timer to calculate the number of cycles to delay.
One  needs an upper limit on the delay.

 

Does the ADC affect interrupts?
One way to check would be to replace all the ADC
"variable" references to references to volatile globals.

Iluvatar is the better part of Valar.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I'd expect delay from multi-cycle instructions
to be the primary cause of jitter.

That was my thought when looking at the table of numbers.

 

Indeed, whether old hardware or not, "real" timer-handled PWM is there to give the feature of jitter-free PWM or similar.  If that critical to the app (and it is just being added?) then maybe some trace cutting and jumper-adding is justified.

 

I've lost track -- is 41kHz the PWM frequency or the timer tick rate?  Adding a very fast soft PWM like that to an existing app is always going to be tricky, right?  And the AVR model chosen doesn't have "turbo" timer with PLL...

 

40kHz is 25us, right?  Yep, we 16us and 8us in the numbers table.  That is fast servicing.  And the ISR (only to be seen by looking at the main.c attachment, right?) is curious in itself, for fast processing:  What is that fussing with TCNT?  Why RMW on the port?  If I really had to do it I'd write a constant value to the PIN register to toggle, and ensure that things start out in sync.

 

[Interesting; another GCC app that seems to lean on the CodeVision Wizard]

 

If RET is the problem, there is really no need for calling an ADC routine, or sitting and waiting.  The complete flag can be polled; the next conversion can be started whenever.

 

If the times were a bit longer I'd be tempted to go to sleep, awaken for the timer interrupt, do a few microseconds of work in the main loop, go to sleep, awaken ... forever.  That should eliminate jitter, right?

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I do not understand any of the jitters OP is getting.

1./(14.7 MHz) is roughly 68.03 ns.

80 ns and 83 ns seem strange as do all OP's other numbers:

250 ns*14.7 MHz = 3.675 cycles.

I suspect OP's measuring technique or his crystal frequency.

 

@OP

Do you have another pin available?

You might add a microsecond pulse after starting the conversion.

Check whether it is a microsecond pulse.

Check its jitter.

Iluvatar is the better part of Valar.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

skeeve wrote:
I do not understand any of the jitters OP is getting. 1./(14.7 MHz) is roughly 68.03 ns. 80 ns and 83 ns seem strange as do all OP's other numbers: 250 ns*14.7 MHz = 3.675 cycles. I suspect OP's measuring technique or his crystal frequency.

Hi skeeve,

The 80ns and 83ns that because of my measurement device's clock. It is 12MHz, and it is 83ns for each measurement sample.

 

skeeve wrote:
@OP Do you have another pin available? You might add a microsecond pulse after starting the conversion. Check whether it is a microsecond pulse. Check its jitter.

Your purpose is verifying crystal/clock setting? If yes, the crystal and clock setting are correct, I have verified clocking before.

 

theusch wrote:
40kHz is 25us, right?  Yep, we 16us and 8us in the numbers table.  That is fast servicing.  And the ISR (only to be seen by looking at the main.c attachment, right?) is curious in itself, for fast processing:  What is that fussing with TCNT?  Why RMW on the port?  If I really had to do it I'd write a constant value to the PIN register to toggle, and ensure that things start out in sync.

About the TCNT, we need to reset it after each PWM period, if not it will continue increasing --> unexpected.

I agree that using pin toggle can save 1cycle for each interrupt. But we must keep the pins' level in sync.

 

theusch wrote:
If RET is the problem, there is really no need for calling an ADC routine, or sitting and waiting.  The complete flag can be polled; the next conversion can be started whenever.

In real application, will have many functions then the jitter by RETI instruction in code can be occurred.

 

theusch wrote:
If the times were a bit longer I'd be tempted to go to sleep, awaken for the timer interrupt, do a few microseconds of work in the main loop, go to sleep, awaken ... forever.  That should eliminate jitter, right?

Thank you for this suggestion. I have tried with simple code above, but no improvement, the delay/jitter is same as no sleep.

    while (1) {
		int i, tmp;
		
		for (i = 0; i < 100; ++i) {
			ADMUX = 2 | (0x1  << REFS0); // Channel 2, vref
			// Delay needed for the stabilization of the ADC input voltage
			__asm__ __volatile__ ( "sleep" "\n\t" :: );
			// Start the AD conversion
			ADCSRA |= (1 << ADSC);

			while ((ADCSRA & (1 << ADSC))) {
				__asm__ __volatile__ ( "sleep" "\n\t" :: );
			}
			tmp = ADC;
			(void)tmp;
			__asm__ __volatile__ ( "sleep" "\n\t" :: );
		}
		__asm__ __volatile__ ( "sleep" "\n\t" :: );
	}

And my product scenario, the mainloop is complex, then I think the CPU sleep isn't improvement also.

 

 

Last Edited: Wed. Jun 14, 2017 - 02:35 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hello all,

 

It seems not possible to truly resolve my problem due to multi-cycles instructions jitter with very high software PWM frequency :(.

I will report to my boss, hope he will approve the hardware changes this time.

 

Thank you so much for your time, and more idea are welcome. Thank you.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

nvl1109 wrote:
About the TCNT, we need to reset it after each PWM period, if not it will continue increasing --> unexpected.

No. The timer can take care of that for you if you use a mode with a TOP value.  As you are using channel A and channel B, that would be one with ICR1 as TOP.

 

Now I have to examine timer setup... which we don't see anywhere.  Aah, I see it in the main.c attachment.  Normal mode.  I'd suggest Mode 14 with ICR1 as TOP.  Hmmm--actually, you only need one channel, so Mode 15 could be used with OCR1A as TOP.

 

I wonder if the jitter due to main instruction finishing might introduce more with the TCNT clear.

 

 

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

As I noted earlier, one can get cycle-accurate pin toggles,
even in the presence of jitter.

 

Assigning to the timer count strongly suggests a bad design.
I'd suggest using CTC mode to clear the timer.

Iluvatar is the better part of Valar.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

theusch wrote:

Now I have to examine timer setup... which we don't see anywhere.  Aah, I see it in the main.c attachment.  Normal mode.  I'd suggest Mode 14 with ICR1 as TOP.  Hmmm--actually, you only need one channel, so Mode 15 could be used with OCR1A as TOP.

Hello theusch,

Because my board can not use hardware PWM, so mode 14 and mode 15 (Fast PWM) can't be used.

 

skeeve wrote:
Assigning to the timer count strongly suggests a bad design. I'd suggest using CTC mode to clear the timer.

Hello skeeve,

Thank you for this suggestion. It is helpful. the jitter of negative pulse is abit decrease.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

nvl1109 wrote:
Hello theusch, Because my board can not use hardware PWM, so mode 14 and mode 15 (Fast PWM) can't be used.

No, not correct.  Your board may not be able to use the particular pins (which I commented on earlier) which are "connected" with the COM bits.  But you can still use the PWM modes (or CTC as someone mentioned) without connecting the pins to the timer.

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

if you really want to change timer value that is used for a consistent ISR you need to do this :

 

stop timer

read timer value

add or sub the change (make sure that it always take the same clk's)

write timer value

start timer

 

but as theusch say why not set the timer top to the value you need, an if it's let say every 400 clk , then take a ISR for each 200 clk and do nothing (other than set a flag) in every 2. ISR.

Last Edited: Fri. Jun 16, 2017 - 03:32 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

sparrow2 wrote:
if you really want to change timer value that is used for a consistent ISR you need to do this :

 

stop timer

read timer value

add or sub the change (make sure that it always take the same clk's)

write timer value

start timer

How does stopping and starting the timer help?
Quote:
but as theusch say why not set the timer top to the value you need, an if it's let say every 400 clk , then take a ISR for each 200 clk and do nothing (other than set a flag) in every 2. ISR.
+1

 

OP has converted to CTC mode.

Now he just needs a little assembly to get rid of the other jitter.

*Reading* the timer is necessary, but writing it is usually a bad idea.

Iluvatar is the better part of Valar.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

How does stopping and starting the timer help?

Only needed if prescaler !=1