Interrupts, critical sections, and timing

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

For the impatient, my question is at the bottom of the post. For the patient, read on for some background.

Recently I've had to perform some maintenance and feature additions on some old code and after beginning some testing to ensure that I haven't screwed anything up, I notice that the timer I use to kick off an ADC conversion suffers from a lot of jitter. Analysis of the problem, both by poring over my source code and having an oscilloscope give me some insight as to what's going on, shows that my problem seems to be either other interrupts causing delays or my critical sections screwing things up. (I tried out some old code that had previously passed, but I either had a bad test setup or some undocumented code changes occured between then then that cause it to fail.) Due to the nature of the processing I'm doing, having a fairly accurate sampling rate for the ADC is important, thus I would like to minimize jitter: 190-200us between executions is OK but 250-400us is not; neither is missing a sample.

I have critical sections in my code since I have data buffers and flags that can be modified by both the ADC ISR and application code, and are polled by the application code. I used to disable only the relevant interrupts (in this case the ADC ISR and the ISR for the timer kicking off a conversion) but after reading the datasheet and a couple of posts in the forum, it seems like using cli() and sei() is the way to go, not only because its easier but because it seems that ISRs will execute if their flag was enabled while interrupts were turned off. (For example, if i call cli() to do a quick data copy but the ADC interrupt occurs before I re-enable interrupts, the ISR will execute after calling sei()). I do declare things volatile, but I question whether or not this protects against synchronization issues (I'm paranoid like that), thus the critical sections. After review of all my ISRs and critical section code, it seems to be pared down about as far as it can be; generally a flag is read/cleared or a value copied out of a buffer, occasionally some of both.

GCC optimization is set to -O3

Bottom line:
Is using cli() and sei() the best method to ensure that interrupts are always serviced after I access shared data or do I still run the risk of missing some?

Surely I'm not the first one to face this type of problem. Does anyone have any words of wisdom?

Thanks.

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

Quote:
Is using cli() and sei() the best method to ensure that interrupts are always serviced after I access shared data or do I still run the risk of missing some?

Yes it is for the reasons you gave (single opcode versus code to read/modify/write enable bits) and the only risk of missing some is if TWO interrupts from the same source occur while I is 0.

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

Quote:

thus I would like to minimize jitter: 190-200us between executions is OK but 250-400us is not; neither is missing a sample.

I guess the short answer is "don't spend so long in the bathroom"--errr, in the other ISRs.

Another approach that could well solve the problem is to send your AVR on a date--search your AVR datasheet for "ADATE". Most modern models have a selection of timer events that can be used to kick off a conversion. This should keep your sample time consistent.

But I'll go back to my first answer: if your max observed time is 2x or more your desired time, then you will never get to >>use<< the value in time anyway in all cases. So you've got to cut down everything else.

Now, tell more about these samples. Single channel? If not, how many channels? If a single channel then free-run and there will alwas be the most recent conversion result waiting for you. If multiple, select the next channel and restart in the ADC ISR (there are very recent threads on this). It won't solve the problem you posed, but the most recent result for all the channels will be available.

As 100us is about the practical fastest full res conversion speed you are somewhat stuck if other ISRs can take way longer.

Lee

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

Quote:
I guess the short answer is "don't spend so long in the bathroom"--errr, in the other ISRs.

Indeed a good lesson and one hard learned when I took my college embedded systems course. :wink:

Examination of time spent in ISRs using an oscilloscope and toggling a pin upon entry/exit shows that ISR execution time is minimal. Really other than my time keeping routine (which goes off once a second and increments a variable) the only interrupts that go off regularly are the ADC and the timer that starts a conversion. There are some others that go off when a pin changes state but since I'm not toggling their associated pins, one would think they'd be inactive for the time being.

Quote:
Now, tell more about these samples. Single channel? If not, how many channels?

I'm converting 5 channels. The goal is to have each channel convert once every millisecond so I have a timer set up to go off every 200us or so (1/5 = 0.2). Said timer ISR (which is Timer2 on an ATmega324P) takes care of deciding which channel should be sampled, writing to ADMUX, then starting the conversion. As far as I know, since I have to manually switch which channel I'm sampling, I can't use ADATE to time my conversions.

clawson: I'll be sure to use cli() and sei() for my critical sections from now on.

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

Quote:

Really other than my time keeping routine (which goes off once a second and increments a variable) the only interrupts that go off regularly are the ADC and the timer that starts a conversion.

Then why are you seeing so much jitter?

I'll go back and recommend my tried-and-true "run continuous conversions on all enabled A/D channels". Have the ADC ISR grab the conversion value, store it in an array, set for the next channel, and start the next conversion.

When you want to use a "set" of values, or just one, the latest round-robin conversion value is always available. If using 10 bits be sure to protect the read with cli/sei, and be "volatile".

I've posted round-robin before--see my posts in this thread and follow the links to prior discussions.
https://www.avrfreaks.net/index.p...

Lee

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.