analogRead() is not interrupt safe ?

Piero

Member
Hello Paul,

I noticed that my teensy gets stuck if analogRead() is called at high rate in the main loop() while analogRead() (on a different pin, with different variables) was called in an interrupt callback. This can be solved by deactivating interrupts temporarily around the analogRead() of the main loop… But is this behavior normal ?

Here is some testing code (type 'g' in the serial monitor to start and stop the interrupt)

Code:
#define EMG_PIN_1 A1
#define EMG_SAMPLE 'g'

#define EMG_SAMPLE_PERIOD 1000

#define BUFSIZE 64
volatile uint16_t emg1_buffer[BUFSIZE];
volatile uint32_t emg_time_buffer[BUFSIZE];
volatile uint8_t head=0;
volatile uint8_t tail=0;

IntervalTimer timer0;
volatile unsigned long emg_ref_time=0;

void emg_callback(void)
{
  uint8_t next_head = (head + 1) % BUFSIZE;
  if (next_head != tail) {
    /* there is room */
    emg1_buffer[head]=analogRead(EMG_PIN_1);
    emg_time_buffer[head]=micros()-emg_ref_time;
    head = next_head;
  } 
}



void setup()
{
  Serial.begin(115200);
  Serial.flush();
  analogReadRes(16);
  analogReadAveraging(8);
  pinMode(13,OUTPUT);
}

void loop()
{
  byte c=' ';
  if (Serial.available()>0) 
  {
    c = Serial.read();
  }
  static boolean sampling=false;
  if (c==EMG_SAMPLE)
  {
    if (sampling)
    {
      sampling=false;
      timer0.end();
      digitalWrite(13,LOW);
    }
    else
    {
      emg_ref_time=micros();
      timer0.begin(emg_callback,EMG_SAMPLE_PERIOD);
      sampling=true;
      digitalWrite(13,HIGH);
    }
  }

//**********COMMENT THIS PART TO REMOVE PROBLEM
//**********OR UNCOMMENT CLI() AND SEI()
static uint32_t k=0;
  if (sampling)
  {
    k++;
  }
  if ((k>10000) && (k<50000))
  {
    //cli();
    int a=analogRead(A0);
    //sei();
    k++;
  }
//****************************************


  uint16_t e1;
  uint32_t t;
  boolean data_to_send=false;
  cli();
  if (head != tail) 
  {
    t=emg_time_buffer[tail];
    e1=emg1_buffer[tail];
    tail = (tail + 1) % BUFSIZE;
    data_to_send=true;
  }
  sei();
  if (data_to_send)
  {
    Serial.print(t);
    Serial.print(",");
    Serial.println(e1);
  }


}
 
It is because all analog reads us the same ADC even if they use different pins.
If an interrupt calls an analog read while the main loop is already waiting for the result
of an ongoing read then the read in the main loop will fail since analog read doesn't save
and restore the state of an ongoing read.

The first read, main loop, will then never receive the analog finished flag from the ADC
that it is waiting for and the application will hang.

So as Piero noticed, you must ensure that the interrupt driven analog read will never get called
while an analog read is in progress in the main loop. This is perfectly normal for this
kind of single resource as the ADC.
 
It is because all analog reads us the same ADC even if they use different pins.
If an interrupt calls an analog read while the main loop is already waiting for the result
of an ongoing read then the read in the main loop will fail since analog read doesn't save
and restore the state of an ongoing read.

The first read, main loop, will then never receive the analog finished flag from the ADC
that it is waiting for and the application will hang.

So as Piero noticed, you must ensure that the interrupt driven analog read will never get called
while an analog read is in progress in the main loop. This is perfectly normal for this
kind of single resource as the ADC.

Ok so I can better understand this is referring to this portion of the code when you are accessing analog read:

Code:
void emg_callback(void)
{
  uint8_t next_head = (head + 1) % BUFSIZE;
  if (next_head != tail) {
    /* there is room */
    emg1_buffer[head]=analogRead(EMG_PIN_1);
    emg_time_buffer[head]=micros()-emg_ref_time;
    head = next_head;
  } 
}

This would be the interrupt that is using the analogRead correct?
 
Yes the callback is run from the interrupt.
Then there is another analogRead() in the main loop().
Turning off interrupts while this analogRead takes place removes the conflict,
but it also delays the interrupt driven analog read.

Code:
//**********COMMENT THIS PART TO REMOVE PROBLEM
//**********OR UNCOMMENT CLI() AND SEI()
static uint32_t k=0;
  if (sampling)
  {
    k++;
  }
  if ((k>10000) && (k<50000))
  {
    //cli();
    [B]int a=analogRead(A0);[/B]
    //sei();
    k++;
  }
//****************************************
 
I'm not sure if your problem lends to this solution, but this is what I do.
I sample at the highest sample rate that I need in the interrupt timer, and for slower sampling rates I use a divisor.

For example, sample at 1000Hz, and have signals that are at the following sample rates.
1000Hz (every interrupt)
500Hz (ever 2 interrupts)
100Hz (ever 10 interrupts)
10Hz (every 100 interrupts)
1Hz (every 1000 interrupts)

This way you are never calling the ADC at the same time, because they are sequential, but at an almost imperceptible delay between samples that occur at the same time. In fact, as close as possible, since the next ADC sample can't occur until the previous one is completed.
 
Last edited:
So for example if I wanted to view a signal and look at say for instance frequency components I could use this divisor and create x arrays to hold samples and on the overlap I could place values for that given array at that time sample... or are you saying that you enable those samples off the diffrent samples keeping each sample taken unique and not taking lets say that 1HZ sample would count for all of those above.


like this:

(1,2,3,4,5,6,7,8,9) 1000HZ Sampling
(1,1,3,3,5,5,7,7,9) 500HZ Sampling (Each number is an ADC reading....
 
Indeed there is only 1 ADC, so if you use it from an interrupt while it's already in use from your main program, of course there will be a conflict.

But the analogRead() code could do much better than crashing! Here's my first attempt to make it automatically restart the conversion that was in progress before the interrupt, well, rudely interrupts it. The conversion can take longer, because the interrupt takes time to make a full conversion and then the original one must be restarted from the beginning. But other than the timing, this should (hopefully) make analogRead "just work" for this types of scenarios.

This file replaces hardware/teensy/cores/teensy3/analog.c

Please give this code a try and let me know how it works?
 

Attachments

  • analog.c
    7.1 KB · Views: 428
Back
Top