February 1, 2015

The first part of the OScope project is to implement the Arduino sketch to read the input values from an analog pin. In this article will describe how to achieve a reliable sampling of analog signals up to 615 KHz using some advanced techniques.

Arduino provides an convenient way to read analog input this using the analogRead() function. Without going into much details the analogRead() function takes 100 microseconds leading to a theoretical sampling rate of 9600 Hz. You can read more about this topic here.

The following piece of code takes 1000 samples using the analogRead() calculates some statistics.

void setup()
{
Serial.begin(115200);
pinMode(A0, INPUT);
}

void loop()
{
long t0, t;

t0 = micros();
for(int i=0; i<1000; i++) {
}
t = micros()-t0;  // calculate elapsed time

Serial.print("Time per sample: ");
Serial.println((float)t/1000);
Serial.print("Frequency: ");
Serial.println((float)1000*1000000/t);
Serial.println();
delay(2000);
}

This code gives 112us per sample for a 8928 Hz sampling rate.

So how can we increase sampling rate?

Speedup the analogRead() function

We now need a little more details. The ADC clock is 16 MHz divided by a 'prescale factor'. The prescale is set by default to 128 which leads to 16MHz/128 = 125 KHz ADC clock. Since a conversion takes 13 ADC clocks, the default sample rate is about 9600 Hz (125KHz/13).
Adding few lines of code in the setup() function we can set an ADC prescale to 16 to have a clock of 1 MHz and a sample rate of 76.8KHz.

#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))

void setup()
{
...

The real frequency measured with the test program is 17us per sample for a 58.6 KHz sampling rate.

The following table shows prescale values with registers values and theoretical sample rates. Note that prescale values below 16 are not recommended because the ADC clock is rated.

 Prescale ADPS2 ADPS1 ADPS0 Clock freq (MHz) Sampling rate (KHz) 2 0 0 1 8 615 4 0 1 0 4 307 8 0 1 1 2 153 16 1 0 0 1 76.8 32 1 0 1 0.5 38.4 64 1 1 0 0.25 19.2 128 1 1 1 0.125 9.6

Interrupts

A better strategy is to avoid calling the analogRead() function and use the 'ADC Free Running mode'. This is a mode in which the ADC continuously converts the input and throws an interrupt at the end of each conversion. This approach has two major advantages:
1. Do not waste time waiting for the next sample allowing to execute additional logic in the loop function.
2. Improve accuracy of sampling reducing jitter.
In this new test program I set the prescale to 16 as the example above getting a 76.8 KHz sampling rate.

int numSamples=0;
long t, t0;

void setup()
{
Serial.begin(115200);

ADCSRA = 0;             // clear ADCSRA register
ADCSRB = 0;             // clear ADCSRB register
ADMUX |= (0 & 0x07);    // set A0 analog input pin
ADMUX |= (1 << REFS0);  // set reference voltage
ADMUX |= (1 << ADLAR);  // left align ADC value to 8 bits from ADCH register

// sampling rate is [ADC clock] / [prescaler] / [conversion clock cycles]
// for Arduino Uno ADC clock is 16 MHz and a conversion takes 13 clock cycles
//ADCSRA |= (1 << ADPS2) | (1 << ADPS0);    // 32 prescaler for 38.5 KHz
ADCSRA |= (1 << ADPS2);                     // 16 prescaler for 76.9 KHz
//ADCSRA |= (1 << ADPS1) | (1 << ADPS0);    // 8 prescaler for 153.8 KHz

ADCSRA |= (1 << ADATE); // enable auto trigger
ADCSRA |= (1 << ADIE);  // enable interrupts when measurement complete
}

{
byte x = ADCH;  // read 8 bit value from ADC
numSamples++;
}

void loop()
{
if (numSamples>=1000)
{
t = micros()-t0;  // calculate elapsed time

Serial.print("Sampling frequency: ");
Serial.print((float)1000000/t);
Serial.println(" KHz");
delay(2000);

// restart
t0 = micros();
numSamples=0;
}
}

If you want to learn more on ADC Free Running mode and tweaking ADC register you can look at the following pages.

Instructables - Girino - Fast Arduino Oscilloscope
Instructables - Arduino Audio Input
Arduino Forum - Faster Analog Read

1. How are you transferring the samples to your computer ?
I'm using serial for that and that's seriously affecting the sample rate.

1. You can set a higher bitrate for the serial port.
Try - Serial.begin(115200);
I have changed the example.

2. Hi how can i transfer live data from arduino to matlab in PC and make live plot

2. Thanks for your prompt reply. I made the following changes to your code to achieve the desired result.
(1)
ADMUX |= (1 << REFS0); // set reference voltage to internal
ADMUX |= (1 << REFS1);

(2)
{
byte x = ADCH; // read 8 bit value from ADC
numSamples++;
}

The result of this is that I'm getting constant value(255) through the serial even though different voltages is being applied through the serial. What I'm thinking is that maybe the ADC is putting out samples to fast for the Serial to handle or maybe there is some other problem.
My main objective is to get the samples to my laptop as soon as they are put into the register by ADC. If there is any alternative way to do that please suggest. If not, please suggest changes in the code to fix the serial problem.
Thanks!

1. Edit:
(2)
{
byte x = ADCH; // read 8 bit value from ADC
Serial.println(x);
numSamples++;
}

The result of this is that I'm getting constant value(255) through the serial even though different voltages is being applied to the ANALOG A0 pin

2. I have found the problem.
Replace
ADMUX |= (A0 & 0x07);
to
ADMUX |= (0 & 0x07);

3. I have also updated my OScope project (http://yaab-arduino.blogspot.it/p/oscope.html)
Can you try to download the new version and tell me what do you think?

4. Thank you for your help. It worked! I used your code to sample data for the myograph me and my team created as a part of course project. I appreciate your help.
Sure! I'll give it a try.

3. How could we adapt the code for A0 and A1

4. Hello Bruno, Very interesting and useful article! Complimenti!
Do you think that it's possible to sample the data at 8khz and save all on the sd card?
Thanks
Stefano

5. I don't have an SD card reader so I cannot try.
I think it should be possible but I don't think you can sample 'exactly' at 8KHz.
You can use the free running mode to sample at 9.6Khz and write to SD when a new sample is available.

6. How to change ADMUX |= (0 & 0x07); if i want to read from all 6 analog pins?,thank you

1. This comment has been removed by the author.

2. Did you find the answer?

3. se the last part of https://sites.google.com/site/qeewiki/books/avr-guide/analog-input

7. Why is is x local?
And shouldn't numsamples be declared volatile?

8. hi bruno, how can u acces the analog variable?

9. How I use the analog data?

10. How I use this in an arduino due?

11. Can I use this for nodeMCU (development board based on esp8266)?

12. You made this type of fascinating piece to peruse, giving each subject illumination for people to pick up learning. A debt of gratitude is in order for imparting the such data to us to peruse thisbeta afp browser

13. This is seriously nice stuff, but if I want to check a different input pin to A0 then what do I say? Your "ADMUX |= (0 & 0x07); // set A0 analog input pin" line seems to set A0, but how could I set A1, A2, A3, A4 or A5?
Thank you

14. Best Category of classy and glamorous Female Models Lahore Escorts A stunning Girls who make your time more pleasurable We are famous for our Reasonable Rate and High Class Service Escorts Services in Lahore are well- educated and very well- mannered you will feel very comfortable with her Call us now and get Best Price for your fantasy night.

15. hi sir i could not understand what is the usage of the void loop() function for taking value from arduino and transfer it to the pc

16. This is the best post about ADC that I have ever read. Congratulations

17. Hi, Thanks for this useful post. How can I read 10bit instead of 8bit even it is less fast. For me, it can be little bit more slow but I need V/1023 resolution.