Whats the fastest way to do an analog read?

Hey guys,

So I’m building an impact data logger. Using an UNO, as it sits I use to following code to get data during the impact.

for(i=0; i<=255; i++){
Data=analogRead(A5);
DataString += String(Temp);
DataString += “,”;
}

The trouble is at the 16 MHz the UNO runs at I get a ~4 KHz sampling rate. I’m considering buying an Arduino Due, which has an 84 MHz (?) clock. With that I could get a ~21 KHz sampling rate. However I’m wondering if there is a faster way to sample the analog input and then store it into a string.

Thanks!

With a DUE, you can sample an ADC channel up to 1 Msps in Free Running Mode while logging conversions into a buffer thru a DMA. Of course, you can also set a lower sampling frequency by using a Timer Counter to trigger conversions. Search in the DUE sub forum for example sketches.

Have a look at this post. You can increase sample speed at the cost of some accuracy and go beyond this by using non blocking sampling code where you request the ADC takes a reading and then continue with other code until the ADC has finished. This latter method may require hitting the hardware directly unless someone has written suitable functions/library to do the heavy lifting for you.

Braeden:
The trouble is at the 16 MHz the UNO runs at I get a ~4 KHz sampling rate.

The stock runtime of analogRead on the Uno/Mega is about 110µs, so 9 kHz sample rate is
possible. You measured 4 kHz, you presumably have something else taking 150µs or so per sample
anyway, so check that first as its dominating the runtime currently.

[ It may be your concatenation of Strings - lose all the String code, use arrays of raw values to
store things, convert to strings, not Strings, before outputing as text.

The String type makes constant use of malloc and free and will cause heap fragmentation slowing
everything down. Repeated string concatenation to build a long string is not how to do things
efficiently - use an array, output one after the other. ]

MarkT:
[ It may be your concatenation of Strings - lose all the String code, use arrays of raw values to
store things, convert to strings, not Strings, before outputing as text.

The String type makes constant use of malloc and free and will cause heap fragmentation slowing
everything down. Repeated string concatenation to build a long string is not how to do things
efficiently - use an array, output one after the other. ]

I absolutely agree.

@OP, why do you want to store results in a string? What happens to the string later?

Perfect this was the information I was looking for.

PaulRB, I was using a string because I'm writing the data to an SD card. The example code I worked off of to make my sketch used a string. But I think using an array is a very good idea. As Mark suggested Seeing as I don't need the set to add a "," to each datum.

Riva, I might give you suggestion a try. However the more accurate my data the better.

I think I'll shell out the $12 for a Due, and get the mythical ~1 MSPS that Ard mentioned.

Braeden:
I think I'll shell out the $12 for a Due, and get the mythical ~1 MSPS that Ard mentioned.

Not sure how much that will help. If the SD card is the bottleneck, the Due may make little or no difference.

The SD isn’t a bottle neck. The data logging takes place over <0.5 seconds. So I write to the internal memory. The SD card is only written to after the loop, I’ve listed here, finishes.

Awesome! I just tested my loop with the following format.

for(i=0; i<=255; i++){
data=analogRead(A5);
dataArray*=data;*

  • }*
    When I used the code which wrote to a string the loop too 56,000 µS Now it takes 28,000 µS.

Braeden:
Awesome! I just tested my loop with the following format.

for(i=0; i<=255; i++){
data=analogRead(A5);
dataArray*=data;*

  • }*
    When I used the code which wrote to a string the loop too 56,000 µS Now it takes 28,000 µS.
    [/quote]
    If that is the code you used then it might take a bit longer once you make it write the data sequentially to the array instead of to the beginning only.

Braeden:
PaulRB, I was using a string

You were using a String (as in the String class), not a string (as in c-strings aka char arrays).
Yes, that capitalisation makes a world of difference.

Braeden:
Perfect this was the information I was looking for.

PaulRB, I was using a string because I'm writing the data to an SD card. The example code I worked off of to make my sketch used a string. But I think using an array is a very good idea. As Mark suggested Seeing as I don't need the set to add a "," to each datum.

Riva, I might give you suggestion a try. However the more accurate my data the better.

I think I'll shell out the $12 for a Due, and get the mythical ~1 MSPS that Ard mentioned.

If you do switch to the DUE, try using a RTOS such as freeRTOS or uMT; I tend to like uMT for the DUE and freeRTOS for the multi core devices like the ESP32. I find the greater control and task switching to be worth the trade off of the overhead of the RTOS and with the clock of the DUE and a RTOS loaded performance is still better then the Uno or Mega.

Also, for the String, in the setup, reserve(), a String buffer and use the contact() to add data to the string buffer. Using a string buffer gets rid of the string being moved around in memory with each lengthening, which creates memory holes. A "" to the string buffer, clears the buffer for new contact()'s. You will also realize a small performance increase with a string buffer and contact() over sX= sX + "more stuff", which has to do a memory copy from a small space to a larger space. As the sX grows in size the memory copies take just a bit longer. When the string in the buffer reaches a size, length(), a write, and "" can be done.

https://www.arduino.cc/reference/en/language/variables/data-types/stringobject/

Good tips about Strings, @idahowalker. Not sure I would use an RTOS for this.

Using code tags to unhide the index variable:

Braeden:
Awesome! I just tested my loop with the following format.

for(i=0; i<=255; i++){

data=analogRead(A5);
   dataArray[i]=data;
 }




When I used the code which wrote to a string the loop too 56,000 µS Now it takes 28,000 µS.

Hi guys,

reading your code-snippets, I always see utilisation of the analogRead() function.
This function takes ~150µs for execution, as it wait for the result until it terminates.
Doing this in a sequence with writing results to the SD card, you will never get a real fast result.
Did you ever consider to use the ADC interrupt (=smart realtime programming)?
So you can make better use of the time instead of waiting for the ADC-results.
You can find all the details for doing so in the Atmel datasheets

In general I recommend to avoid waiting and any use of "delay()"-function, if you really want to have a maximum sample rate.

kr, sepp2gl

You can overclock the ADC at the cost of reduced accuracy. analogRead() is about 110us by
default, ie 9kSPS, on ATmega based Arduinos. Other processors have different ADC performance.

Hi MarkT,
yes, you can do some limited overclocking the Controller...
...but why doing brute-force, if there is a smart way for doing so?
Interrupts have been established long time ago to tackle speed issues, right?

Yes situation is gradually different on other controllers.
My point is that independent from the pure ADC-performance,
the issue normally lies with the processing of the results.
And here interrupts pay off very well, when timimg matters.

BTW: do you know of any substancial backdraw using interrupts?

kr, sepp2gl

P.S.:Sorry for late response: I noticed your reply just now.
Will update my notification settings right away.

In this case the only help by interrupts is to have them fire upon completion of an ADC conversion. Still your program itself has to be fast enough to deal with it, and you have to have a way to do something useful with all that data.

Exactly! Interrupts help to GET the data without having to wait for it. No delay(), no waiting loop:
You save the waiting time and can use them for processing the data.
If the program isn't fast enough to "deal with the data and do something useful", the controller might be too slow anyway or the intended sample rate might be higher than needed.
But this isn't due to the utilisation of interupts then.

Coming back to the question of this thread, about the fastest way to do an analog read:

  1. Interrupt solution:
    After the AD-conversion is started, the controller continues to do something else. When the AD-conversion, the respective interrupt allows for immediate capturing and preprocessing of the value. After that the controller continues with something else.

  2. Main-loop solution:
    After the AD-conversion is started, the controller waits for the end of AD-conversion (no interrupt) and after that doing the m.a. capturing and preprocessing and continues with "something else". This approach is as fast as 1., but it wastes the waiting time, during which nothing else can be calculated. If there is much to do during the main-loop, this will lead to a very fast way to make a AD-conversion, but the conversion-rate will go down because it can only be started once per loop.

  3. Using Timer Interrupt for starting the conversion and AD-interupt for getting the value
    This approach guarantees an intended AD-sample rate by Timer-Int and highes speed for capturing the AD value.
    In the meantime the controller can to the rest of the work. In general, care must be taken to keept Interrupt-code short to avoid stack-overflow.

kr, sepp2gl

You forget solution 4, which is about as fast as but does not need an interrupt, and also does not sit and wait for a conversion to complete.

Poll the ADIF bit in the ADCSRA register: it gets set when a value is ready. Then you can read the value and handle it. Overall about as fast as using an interrupt to read the value and set a flag for loop() to deal with it. Instead of polling for that flag, you poll the bit in the register.