Is it possible to have 2 pins analogread sequentially non blocking?

This is for a at328p nano

I am trying to have 2 analogread pins read one after another in the background while other code runs to speed up loop times.
Using this code on one analog input (of 2) I can get my loop frequency up to 8khz from 6khz (using analogreadfast library)

I have spent about 8 hrs (Im not skilled) trying about 100 different variations, but cant get both inputs to run in the background.

I would like to know if its even possible before spending even more time trying.

Thanks

const byte adcPin = 0;
volatile int adcReading;
volatile boolean adcDone;
boolean adcStarted;

int   =   Vin;

void setup ()
{
  Serial.begin (115200);
  ADCSRA =  bit (ADEN);                      // turn ADC on
  ADCSRA |= bit (ADPS0) |  bit (ADPS1) | bit (ADPS2);  // Prescaler of 128
  ADMUX  =  bit (REFS0) | (adcPin & 0x07);    // AVcc and select input port

}  // end of setup

// ADC complete ISR
ISR (ADC_vect)
  {
  adcReading = ADC;
  adcDone = true;  
  }  // end of ADC_vect
  
void loop ()
{
  // if last reading finished, process it
  if (adcDone)
    {
    adcStarted = false;

    // do something with the reading, for example, print it
   Vin   =  (adcReading);
   

    adcDone = false;
    }
    
  // if we aren't taking a reading, start a new one
  if (!adcStarted)
    {
    adcStarted = true;
    // start the conversion
    ADCSRA |= bit (ADSC) | bit (ADIE);
    }    
  
  // main code here

}  // end of loop

Remember that an analogRead() takes time to execute.Also, most Arduino boards only have a single ADC that is switched between analogue inputs. This means that a setting time must be allowed between readings on more than a single pin

do both inputs need to be read each iteration of loop()?

The ADC can only convert one channel at a time. So if you set the ADC to free-run mode, then alternate between inputs using ADMUX, you can get the fastest rate on 2 inputs.

I created a small library for this! :slight_smile:
https://github.com/sherzaad/AnalogRead_Advanced/blob/master/examples/NonBlocking_AnalogRead/NonBlocking_AnalogRead.ino

example modified as per your requirement.

#include "AnalogReadEx.h"

int16_t adcRead = ADC_BUSY;

uint8_t adc_ch[2] = { A0, A1 };
uint8_t ch_select = 1;

void setup() {

  Serial.begin(115200);
  Analog.PinSelect(adc_ch[0]);  //Select ADC Channel
  Analog.adcStart();            //Starts initial the ADC conversion
}

void loop() {
  adcRead = Analog.adcComplete();  //Get ADC status

  if (adcRead != ADC_BUSY) {              ////Check if conversion is complete
    Serial.println(adcRead);              //print out ADC value
    Analog.PinSelect(adc_ch[ch_select]);  //change selected ADC Channel
    ch_select ^= 1;                       //alternate channel selection
    Analog.adcStart();                    //Starts next the ADC conversion
  } else {
    Serial.println("ADC BUSY!");
  }
}

hopefully that is useful to you!

good luck!

1 Like

don't understand what your code is trying to do, don't understand what adcReading is. shouldn't it be

        return (ADCH << 8) | ADCL;

it's pretty common to immediately start the next conversion after the previous one completes and the value is read. so your code should just test to see if the conversion is complete, process it and restart

Not exactly clear from your question, are you concerned about how fast the code in loop runs, or how many samples you can get from the ADC?

As @gcjr points out, if you want the ADC readings to go faster, don't mess with starting the reading in the main code, have the interrupt switch inputs and start the next conversion. This will require separate flags and storage for the data from each ADC input.

Additionally, interrupts need to be disabled while accessing the int value from the interrupt.

This code works and only takes 2.12us to analogread.
I am trying to get this speed benefit while reading analog5 and a seperate measurement on analog6.

serial print isnt used, it represents the other irrelevant code running.

to give a bigger picture I want to:

read voltage in (takes 16us with analogreadfast) would like to make it 2us
read voltage out (takes 16us with analogreadfast)
use readings to adjust pwm duty cycle (which influence both vin and vout) takes aprox 20us
repeat as fast as possible

I can only use 1 reading per variable per loop, if those variables arent ready I have to wait for them.

I already have lots of different code that does that with analogreadfast, I want to learn how to do it faster.


const byte adcPin = 5;
volatile int adcReading;
volatile boolean adcDone;
boolean adcStarted;
int Vin = 0;

  void setup() {

    DDRB |= (1 << DDB5); // Set pin 13 to toggle to time loop
  Serial.begin(19200);

  ADCSRA = bit(ADEN);                              // turn ADC on
  ADCSRA |= bit(ADPS0) | bit(ADPS1) | bit(ADPS2);  // Prescaler of 128
  ADMUX = bit(REFS0) | (adcPin & 0x07);            // AVcc and select input port

}  // end of setup

// ADC complete ISR
ISR(ADC_vect) {
  adcReading = ADC;
  adcDone = true;
}  // end of ADC_vect

void loop() {
  // if last reading finished, process it
  if (adcDone) {
    adcStarted = false;

    // do something with the reading, for example, print it
    Vin = (adcReading);

    adcDone = false;
  }

  // if we aren't taking a reading, start a new one
  if (!adcStarted) {
    adcStarted = true;
    // start the conversion
    ADCSRA |= bit(ADSC) | bit(ADIE);
  }

  PORTB &= ~(1 << PORTB5); // Set pin 13 LOW

/////////////////////////////////////////////////////////////////////////////////// main code from here 

  Serial.print("Vin:");
  Serial.print(Vin);
  Serial.println(" ");

//////////////////////////////////////////////////////////////////////////////////// to here

  PORTB |= (1 << PORTB5); // Set pin 13 HIGH
}  

This looks like is has potential, but mixing both readings into 1 output makes it kind of difficult to do anything with

Use a faster microcontroller

1 Like

I am exploring that option and have a few different ones on the way, but it in my case it dosent make sense to use something else unless I need to.

This isnt for one specific project, its for a set of boards I built that are sort of "4 sizes fits all" kind of thing where I can control a PWM from 8v to 600v off of whatever sensors I want, so versatility and ease of use are #1.

edit, from what I can tell the teensy 4.0 takes 18.6us to do a reading compared to 16us I am currently getting from the nano, that and considering I fried 3 nanos today I cant afford to do that for the marginal increase in speed.
I guess the teensy could go faster, but will need fancy programming to do.

Did you try the code and library @sherzaad provided?

Did you read it and see

  if (adcRead != ADC_BUSY) {              ////Check if conversion is complete
    Serial.println(adcRead);              //print out ADC value
    Analog.PinSelect(adc_ch[ch_select]);  //change selected ADC Channel
    ch_select ^= 1;                       //alternate channel selection
    Analog.adcStart();                    //Starts next the ADC conversion
  } else {

when the result is ready, it is printed. Then the channel is changed, and a new conversion initiated.

Perhaps the demo should have been more fleshy out. Use the ch_select variable to see which channel is being reported, viz:

  if (adcRead != ADC_BUSY) {              ////Check if conversion is complete

Serial.pirnt(" result for channel "); Serial.print(ch_select ? "one : " : "zero : ");

    Serial.println(adcRead);              //print out ADC value

    Analog.PinSelect(adc_ch[ch_select]);  //change selected ADC Channel
    ch_select ^= 1;                       //alternate channel selection
    Analog.adcStart();                    //Starts next the ADC conversion
  } else {

Or whatever needs be done for the channel 0 or 1.

HTH

a7

1 Like

Try something like this:

// non-blocking analog read multiple inputs
// Arduino IDE sets ADC with standard settings and enables it

#define numInputs 6                                  // number of analog inputs
uint8_t anaInput[numInputs] = { 0, 1, 2, 3, 4, 5 };  // A0, A1, A2, A3, A4, A5
volatile uint16_t anaValue[numInputs];               // to store results

ISR(ADC_vect) {                          // reset of flag happens automatically
  static uint8_t i = 0;                  // counter for input number
  anaValue[i] = ADC;                     // tranfer value to array
  if (++i == numInputs) i = 0;           // increment or reset counter
  ADMUX = (ADMUX & 0xF0) | anaInput[i];  // clear previous setting and select next input
  ADCSRA |= 1 << ADSC;                   // start ADC
}

void setup() {
  ADMUX = (ADMUX & 0xF0) | anaInput[0];  // clear previous setting and select first input
  ADCSRA |= 1 << ADSC;                   // start ADC
}

void loop() {
  // results from array anaValue[] can be used at anytime, the value will be less than 1ms old
}

Compiled, no tested

I have made a test run of the sketch of post #14 on UNO with 3.3V connected at A0-pin. Unfortunately, the program gives 0.0. I am investigating the missing points (if any!).

In the meantime:

Should the above be:
anaValue[i] = ADCW; //anyway, this has not solved the problem.

I have used the following codes to display the Ch-0 voltage at 2-sec interval using millis() function.

unsigned long prMillis = millis();

void loop()
{
  if (millis() - prMillis >= 2000)
  {
    prMillis = millis();
    noInterrupts();   //critical section
    Serial.println((5.0/1023.0)*anaValue[0], 1);  //expecting 3.3V test volt Ch-0
    interrupts();
   }
  else
  {
    //do something else
  }
}

I got the library @sherzaad working... but unless I am doing something wrong its no good since its significantly slower (110us) than just analogreadfast (50 us) for 2 readings.

Rookie mistake, I was testing with one input pulled high and the other left floating, which means I may have had a code that worked, but my testing was worthless

Got it working.

Took some time. Working through a disassembled program is slow. And something came up inbetween.

A number of mistakes:

  • I messed up the bounderies of the arrays

  • I forgot to enable ADC interrupt

  • I assumed the standard setup of Arduino IDE would set the reference voltage

The last one was the hard one to find (to confirm or disprove).

The algorithm was fine, the devil was in the details.

// non-blocking analog read multiple inputs
// Arduino IDE sets ADC with some settings and enables it

#define numInputs 6                                  // number of analog inputs
uint8_t anaInput[numInputs] = { 0, 1, 2, 3, 4, 5 };  // A0, A1, A2, A3, A4, A5
volatile uint16_t anaValue[numInputs];               // to store results

ISR(ADC_vect) {
  static uint8_t i = 0;         // counter for input number
  anaValue[i] = ADC;            // tranfer value to array
  if (++i == numInputs) i = 0;  // increment or reset counter (corrected)
  ADMUX = 0x40 | anaInput[i];   // set referencevoltage and select next input
  ADCSRA |= 1 << ADSC;          // start ADC
}

void setup() {
  ADMUX = 0x40 | anaInput[0];  // set referencevoltage and select first input (assumption failed)
  ADCSRA |= 1 << ADIE;         // enable ADC interrupt (added)
  ADCSRA |= 1 << ADSC;         // start ADC
  Serial.begin(9600);
}

void loop() {
  // results from array anaValue[] can be used at anytime, the value will be less than 1ms old
  static uint8_t i = 0;
  Serial.print(anaValue[i]);
  Serial.print(", ");
  if (++i == numInputs) {
    i = 0;
    Serial.println();
  }
  delay(500);
}

Do we need to include the above code in the ISR() function when the data sheets say the following?

"ADIF is cleared by hardware when executing the corresponding interrupt handling vector."

It works without manually resetting the interrupt flag. Hardware does do it. I’ll correct it here above.

1 Like

I have also wanted to know in my post #15 if the following both are correct? (I never used anaVal[i] = ADC;.)

anaValue[i] = ADC;     // anaValue[i] = ADCL | ADCH << 8
anaValue[i] = ADCW;    //anaValue[i] = ADCL | ADCH << 8;

ADC and ADCW are both defined by iom328p.h so you can use either.

For anaValue[i]: look at array - Arduino Reference

For ADCL | ADCH << 8: in this case the shift takes precedence over the bitwise OR, but braces are always a good idea. If you use ADC or ADCW you won’t need it.

1 Like