Speed of analog read

This code reads analog data continually 400 times (lines 86 - 91), then takes an average (line 92).

  1. Is it wise to read the analog input so fast?
  2. If this code needs to be modified to read 4 analog inputs, would it be a good idea to use an array and read consecutively in the same for-loop all 4 analog inputs?
  3. does reading from different analog inputs not require more time for the ADC to settle, and how much more time?

/*
Version DCC_Booster_v1-4
http://www.trainelectronics.com/DCC_Arduino/DCC_Booster/
  
The H-Bridge can act as a DCC booster without much more than the Opto Coupler circuit
(top left of the schematic) and the one transistor converter (lower left).
The H-Bridge does hove an over current shutdown capability but it doesn't react until
well over 30 amps are being drawn, way more than we are going to use.
To get around this problem so that the system shuts down immediately if the track is
shorted an Arduino Pro Mini has been added.  The Arduino monitors the current sensing
pin on the H-Bridge and can turn it off should it detect a high reading.
  
*/
#include<EEPROM.h>
int LED = 13; // LED to blink when DCC packets are sent in loop
int PowerOn = 3; // pin to turn booster on/off
int Pot1 = 1;
float PotReading = 0;
int CurrentPin = 0;
int CurrentPinReading = 0;
int SetPotFlag = 0;

float version = 1.4;

#include <Wire.h>
#include <LCD.h>
#include <LiquidCrystal_I2C.h>
#define I2C_ADDR    0x27 // <<----- Add your address here.  Find it from I2C Scanner
#define BACKLIGHT_PIN     3
#define En_pin  2
#define Rw_pin  1
#define Rs_pin  0
#define D4_pin  4
#define D5_pin  5
#define D6_pin  6
#define D7_pin  7

LiquidCrystal_I2C	lcd(I2C_ADDR, En_pin, Rw_pin, Rs_pin, D4_pin, D5_pin, D6_pin, D7_pin);
unsigned long time;
unsigned long now;
long cAverage = 0;
int avgTimes = 400;
int lastAverage = 0;
int resetFlag = 0;
float percentage = 0;
int temp1 = 0;
void setup() {
  delay(500);
  pinMode(LED, OUTPUT);
  pinMode(PowerOn, OUTPUT );
  lcd.begin (16, 2); //  LCD is 16 characters x 2 lines
  lcd.setBacklightPin(BACKLIGHT_PIN, POSITIVE);
  lcd.setBacklight(HIGH);  // Switch on the backlight
  lcd.home (); // go home
  Serial.begin (115200);
  lcd.setCursor(0, 0);
  lcd.print("DCC Booster");
  lcd.setCursor(0, 1);
  lcd.print("2-11-16 - v");
  lcd.print(version, 1);
  Serial.print("2-11-2016  version  ");
  Serial.println(version, 1);
  delay(1000);
  lcd.clear();
  turnPowerOn();
  delay(500);
  now = millis();
}

// NOTE:  about 0.014 amps / digit of reading with 10K resistor on sense line
void loop() {
  PotReading = analogRead(Pot1);
  PotReading = PotReading / 100;
  lcd.setCursor(12, 0);
  lcd.print(PotReading, 1);
  lcd.print("   ");
  lcd.setCursor(7, 0);
  lcd.print("MaxA=");
  showPercentage();
  resetFlag++;
  if (resetFlag >= 100) {
    resetFlag = 0;
  }
  cAverage = 0;
  for (int xx = 0; xx < avgTimes ; xx++) {
    CurrentPinReading = analogRead(CurrentPin);
    if (CurrentPinReading >= 1000) {
      Serial.println("STOPPPPPPPPPPPPPPPPPPPPPPING");
      turnPowerOff();
    }
    cAverage = cAverage + CurrentPinReading;
  }
  CurrentPinReading = cAverage / avgTimes;
  if (CurrentPinReading != lastAverage) {
    if (millis() - now >= 450) {  // only update LCD every 1/2 second to limit flicker
      lcd.setCursor(0, 0);
      lcd.print("C=");
      lcd.print(CurrentPinReading);
      lcd.print("  ");
    }
    turnPowerOn();
  }
  lastAverage = CurrentPinReading; // keep for compare & print
}  //END LOOP

void showPercentage()
{
  percentage = (CurrentPinReading * 0.0105) / PotReading; // was 0.014
  percentage = percentage * 100;
  if (millis() - now >= 500)   // only update LCD every 1/2 second to limit flicker
  {
    lcd.setCursor(9, 1);
    lcd.print(percentage,1);
    lcd.print("%  ");
    now = millis();
  }
  if (percentage > 100) turnPowerOff();
}

void turnPowerOff() {
  digitalWrite(PowerOn, LOW);
  lcd.setCursor(0, 1);
  lcd.print("PWR OFF-2 sec");
  delay(2000);
  turnPowerOn();
  lcd.setCursor(0, 1);
  lcd.print("               ");
}

void turnPowerOn() {
  digitalWrite(PowerOn, HIGH);
  lcd.setCursor(0, 1);
  lcd.print("PWR On");
}

Nothing against it from a hardware perspective.

It makes sense to use arrays and it will work. See comment below.

Yes, it does take more time. It's usually advised to do a dummy read first (just ignore the result) and next do the actual read.

For question 3 the dummy read configures the ADC to take its input from the given pin (load a small cap) which lets the voltage settle if there is a large change from the previous pin (edit: and impedance is not in usual range)

The spec has all the detailed information

For question 1 I’m not sure what you expect from so many reads…

1 Like

I had been thinking the same thing: the purpose is to be able to react fast as soon as overcurrent is detected but without false detections causing a shutdown.
I thought more something like this: take very fast 10 readings, eliminate the 2 outliers (min and max from the 10 readings) and divide by 8. Or something like that.
Other, better ideas?

What would be an acceptable time? You can do some timed readings using millis(); read the 4 inputs (will take approx. 800 microseconds including the dummies), wait e.g. 100 milliseconds and do a second reading of the 4 inputs. You can speed up the reading by using 8-bit resolution instead of 10-bit; for the given purpose, I don't think that that will pose a problem.

1 Like

Is less readings with higher resolution not better than less resolution with more readings?

4 Readings take approx. 800us incl. dummies, that is ok; but why wait 100ms for the next set of readings?

If this can be sped up to 10ms for example then 10 readings would take about 110ms, that is a bit long in my case.

There is no need to do that if the input impedance of the thing driving the analogue inputs is 10K or so. You only need a double read when the driving impedance is a lot higher.

I can't see anything in that original code that reads the A/D faster than the normal speed. There are tricks where you can speed up the read by altering the prescaler values.

Remember any D/A is always subject to +/- 1 least significant bit, taking the avrage of 400 readings seems to me to be a bit of an overkill, especial as you are only using ints in your averaging.

1 Like

How long do you want to wait to determine if it's really an overload? I stated "e.g.", if you think that 5 milliseconds will be sufficient, you can use that. The advantage that I see is that your code can do other things and does not have to wait for N readings to be completed.

1 Like

Oh good to know too. the driving impedance is about 1k.
400 readings indeed are unnecessary; I will rather take ten, discard 2 outliers and average over 8.
And use long unsigned int.

@ sterretje: wait should be as short as possible but with the premisse that unnecessary shutdowns should be avoided. So 10ms wait is good enough.

If you want an average then you should be using a float to find the average. As I said the digital readings should be all the same except for the least significant bit.

Are your readings more noisy than that? With 1K drive they shouldn't be unless you have a source of interference or have wired the grounds in series with a high current load, that is causing ground bounce.
Or the voltage on the inputs are not stable.

1 Like

No noise, just the statistician in me wanting to apply some of its science :slight_smile:
Thanks for the float idea.

Instead of just averaging and if going to use floats anyways, use a Kalman Filter instead.

SimpleKalmanFilter - Arduino Reference

An example of my use of the filter.

#include <SimpleKalmanFilter.h>

void fReadBattery( void * parameter )
{
  float adcValue = 0.0f;
  const float r1 = 50500.0f; // R1 in ohm, 50K
  const float r2 = 10000.0f; // R2 in ohm, 10k potentiometer
  float Vbatt = 0.0f;
  int printCount = 0;
  float vRefScale = (3.3f / 4096.0f) * ((r1 + r2) / r2);
  uint64_t TimePastKalman  = esp_timer_get_time(); // used by the Kalman filter UpdateProcessNoise, time since last kalman calculation
  SimpleKalmanFilter KF_ADC_b( 1.0f, 1.0f, .01f );
  TickType_t xLastWakeTime = xTaskGetTickCount();
  const TickType_t xFrequency = 1000; //delay for mS
  for (;;)
  {
    adc1_get_raw(ADC1_CHANNEL_0); //read and discard
    adcValue = float( adc1_get_raw(ADC1_CHANNEL_0) ); //take a raw ADC reading
    KF_ADC_b.setProcessNoise( (esp_timer_get_time() - TimePastKalman) / 1000000.0f ); //get time, in microsecods, since last readings
    adcValue = KF_ADC_b.updateEstimate( adcValue ); // apply simple Kalman filter
    Vbatt = adcValue * vRefScale;
    xSemaphoreTake( sema_CalculatedVoltage, portMAX_DELAY );
    CalculatedVoltage = Vbatt;
    xSemaphoreGive( sema_CalculatedVoltage );
    
      printCount++;
      if ( printCount == 3 )
      {
      log_i( "Vbatt %f", Vbatt );
      printCount = 0;
      }
    
    TimePastKalman = esp_timer_get_time(); // time of update complete
    xLastWakeTime = xTaskGetTickCount();
    vTaskDelayUntil( &xLastWakeTime, xFrequency );
    //log_i( "fReadBattery %d",  uxTaskGetStackHighWaterMark( NULL ) );
  }
  vTaskDelete( NULL );
}
////
1 Like

Top!! Very useful.

Hi

Ref: analogRead() - Arduino Reference

On ATmega based boards (UNO, Nano, Mini, Mega), it takes about 100 microseconds (0.0001 s) to read an analog input, so the maximum reading rate is about 10,000 times a second.

The Arduino Nano ADC is set at 10 ksps, but can be much faster if you're willing to sacrifice a little accuracy. I used ATmega88 with ADCSRA register set up for faster ADC reading.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.