Is this topping maximum Arduino ADC sample/serial send to windows speed?

Hello, I've been researching ADC sampling on the Arduino, and after reading a lot I came with the following codes for the Arduino (in my case, a Nano clone with a CH340 serial-USB converter) and for a VERY SIMPLE 'scope' app on Processing (don't hit me too hard, I'm not a professional programmer in any way lol).

I'm using a sine wave ( 0.5 - 4.5Vpp) with variable frequency as input. After doing a lot of optimizations, I've been able to reach a reasonably good sampling of around 16 data points for each cycle of a 4kHz sine wave, which I think it's ok from what I've been reading. After that, there's too few data points and the sampling starts to distort and become unusable.

I'll state what I think it's happening on both sides based on the codes.

A) Arduino side: I disabled interrupts, programmed the USART to send 8N1 data, programmed the UART to send at 2Mbps, programmed the ADC prescaler to 16 (which I read it's the minimum speed without distorting conversion), and just 'spit' bits at the USART as soon as 1) the AD finishes converting, 2) The USART buffer is available:

#define BAUD    1000000
#define FCPU 16000000UL
#define BRC     ((FCPU/16/BAUD) - 1)
void UART_Init(){
    UBRR0H = (unsigned char)(BRC >> 8);
    UBRR0L = (unsigned char) BRC;
    bitSet(UCSR0A, U2X0);
    UCSR0B = (0<<RXEN0) |(1<<TXEN0) | (0 << RXCIE0);
    UCSR0C = (1 << UCSZ01) | (3<<UCSZ00);

int main() {  
  uint32_t ct;
  cli();//interrupts disable
  UART_Init(); // initialize the UART 
  // prescaler AD=16
  ADCSRA = 0b10000100;
  // Channel
  ADMUX  = 0b01000001;
  //while(ct<=4096) {
  while(1) {
    ADCSRA |= 0b00010000;    
    ADCSRA |= bit(ADSC);
    while( !( ADCSRA & 0b00010000));
    while( !( UCSR0A & (1<<UDRE0)) );
    UDR0 = (ADC >> 8) & 0x03;
    while( !( UCSR0A & (1<<UDRE0)) );
    UDR0 = (ADC & 0x00FF)+4;

A little explanation : on the last UDR0 = (ADC & 0x00FF)+4 line, I sum 4 to the value so I can differentiate which byte is the High AD byte (which ranges from 0 to 3 only), and the lower byte. I did that because sometimes, at this speed some data is just lost on the line, and when this happens it 'flips' the code on Processing to use high bytes as low and low as high. Doing this, I alway have a way to know which bytes are high (0 to 3) or low (anything else). Since I'm using a 4,5Vpp sine, I never reach 1024 as a value for the AD too.

B) Processing side: I create a 'data in' buffer and program the serial library to call the 'serial event' routine every 'chunkSize' bytes to fill this buffer; in parallel, I created a circular (ring) bigger buffer to receive the data from the 'data in' buffer, and I use this buffer to draw the samples on screen non-stop. Since the 'data in' and the ring buffer are being filled asynchronously, the program can deal with the speed the bytes are coming (2Mbps) without having to wait for the drawing, so everything that comes via serial is shown real-time on the screen:

import processing.serial.*;

Serial myPort;  // The serial port

int dataBufferSize=32768;
int[] dataBuffer = new int[dataBufferSize];

int chunkSize=4096;
byte[] inBuffer = new byte[chunkSize];

int bytesRead=0;

int x, y, ly = 0;
int r1, r2 ,vh, vl;
int trgValue = 350;
boolean trg1=false;
int step=20;

long t1,t2;

int k, j, p, writeBufferPos=0;

void setup() {
  myPort = new Serial(this, Serial.list()[2], 2000000);

  size(1024, 512);
  background(0, 0, 0);
  frameRate(20000); // Obviously impossible, just to assure maximum screen refreshing

  ly = height/2;

void draw() {
  for (j=writeBufferPos; j<writeBufferPos+bytesRead; j=j+2) {

    if (j < dataBufferSize)   { p = j; } else { p = j - dataBufferSize; }
    if (p+1 < dataBufferSize) { r2=dataBuffer[p+1]; } else { r2=dataBuffer[0]; }   
    if      (r1 >= 0 && r1 <=3 && r2>=4)     { vh=r1; vl=r2; }
    else if (r1 >= 0 && r1 <=3 && r2<0)      { vh=r1; vl=r2+256; }
    else if ((r2 >= 0 && r2 <=3 && r1>=4) || (r2 >= 0 && r2 <=3 && r1<0)) { j++; } // Discard sample
    y = 512 - ((vh << 8 | vl) >> 1);
    if (y > 511) { y=511; }
    if (y < 0) { y=0; }
    if (y == trgValue && y>ly) { trg1 = true; } // Very simple triggering, ramp-up
    if ( trg1 == true) {
      stroke(255, 255, 255);
      for (p=1; p<=step; p++) { line(x+p, 0, x+p, 512); }
      stroke(0, 0, 0);
      line(x, ly, x+step, y);
      stroke(255, 0, 0);
    if (x >= width) { 
      x = 0; 
      trg1 = false;

void serialEvent(Serial p) {
  print("Serial Event called - buffer = "); println(chunkSize);
  t1 = System.nanoTime();
  bytesRead = myPort.readBytes(inBuffer);
  for (k=0; k<bytesRead; k++) {
    if (writeBufferPos == dataBufferSize) { 
      writeBufferPos = 0;
      t2 = System.nanoTime();
      print("Circular buffer of "); print(dataBufferSize); print(" bytes filled in ");
      print(t2-t1); println (" nanosec");

With all that, I've been able to achieve around 16 data points per cycle for a 4kHz sine wave, which I think it's pretty good as I already said. But...

There's scope projects like this one (fantastic project by the way, much respect to the coder (ZaidaTek) who did it):

Which claims to be able to achieve 100kHz sampling rate for a single channel, MUCH faster than my tests.

As far as I understand, he didn't do things too much differently at the Arduino side. In my limited understanding, the only place where things could be speed up are at windows side, but to me my buffer routines seen to be able to handle the data in speeds.

I'd appreciate if someone could give me some insights on why the speeds are SO MUCH different, and suggestions on what could be done to speed up my conversion. Thanks a lot! :slight_smile:

Some measurings attached at 400Hz, 1kHz and 4kHz. The red circles are the data points drawn by the program (The attachments are a little bit distorted, but you can see a lot of red points at 400Hz and just a few (~16) points for each cycle at 4kHz).

Thank you!

They have a version of A/D converter called a Sigma Delta converter, you might investigate those, I think you will be pleasantly surprised when you find out what they can do.

@gilshultz: the ads1115 has


Sigma deltas are SLOW.

Looking at the link it seems to use a seperate comms board which may explain the difference in speed?

The AT328P used on this board is an Arduino Pro Mini as well as an FTDI232 USB/Serial converter for communication

Sigma deltas are SLOW.

Well there are 16 bit sigma-delta ADCs at 20MSPS, and even a 24 bit sigma-delta ADC at 4MSPS (ADS1675), so I'm not sure where you get that idea. I2S audio ADCs are sigma-delta too and these
normally support upto 192kSPS.

Thanks Mark, very interesting I'd never come across those, more familiar with the ADS1015 / 1115. Can you give me links?

192kSPS audio I2S ADCs:


. . .
I'd appreciate if someone could give me some insights on why the speeds are SO MUCH different, and suggestions on what could be done to speed up my conversion. Thanks a lot! :slight_smile:
. . .

The difference is not that big. 16 data points at 4 kHz is equivalent to 64k samples per second. If you don't need 10 bit resolution, you can halve the amount of data sent to the USART by specifying a shifted 8 bit resolution. Then you will probably approach the 100k samples per second that the other project claims.

The picture of the oscilloscope screen shot in that project: ZaidaScope - Arduino Oscilloscope, 8-Ch, max 100 kHz - Arduino Project Hub is a bit confusing. It is a 440Hz signal derived from a sample rate of approx 40k samples per second. The labelling is misleading in that it implies the input signal (actually an audio signal) is ~40kHz when that is infact the sample rate.

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