1

I'm trying to read 4 analog inputs (1kHz speed). I changed the prescaler to 16 for my Arduino Leonardo according to this link. I then tried to read the analog pins and I used the following code to display the time and the readings:


#define FASTADC 1
// defines for setting and clearing register bits
#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif
double t = 0;
void setup() {
  int start;
  int i;
  #if FASTADC
  // set prescale to 16
  sbi(ADCSRA, ADPS2);
  cbi(ADCSRA, ADPS1);
  cbi(ADCSRA, ADPS0);
  #endif

  Serial.begin(9600);
  Serial.print("ADCTEST: ");
  start = millis();
  for (i = 0 ; i < 1000 ; i++)
    analogRead(0);
  Serial.print(millis() - start);
  Serial.println(" msec (1000 calls)");
}

// the loop routine runs over and over again forever:
void loop() {
  // print out the value you read:
  t = millis() / 1000;
  Serial.print(t);
  Serial.print('\t');
  Serial.print(analogRead(A0)); delay(1);
  Serial.print('\t');
  Serial.print(analogRead(A1)); delay(1);
  Serial.print('\t');
  Serial.print(analogRead(A2)); delay(1);
  Serial.print('\t');
  Serial.println(analogRead(A3)); delay(1);
}

I connected 2V from the DC supply to all the 4 channels and the readings were similar. I used the output from the serial monitor and pasted the readings for 1 sec in Excel to see the frequency. With a 9600 baud rate and no delay between the readings I got 200 Hz (for the 4 analog inputs).

Why is that happening? And is there a relationship between the sampling frequency and the baud rate? Is my way of finding the frequency correct? If not what's the right way?

Finally, I'll be plotting the readings in Matlab, so I guess this will affect the sampling frequency. How can I control it so that I'm sampling with 1kHz rate for all the channels?

Here is the screenshot of the readings from the 4 channels (2V input).

enter image description here

Summarizing everything, I need to know the following:

  1. The effect of adding a delay between every two readings on the accuracy of the readings and the sampling frequency.

  2. How to find the best sampling frequency for every input and how to control it?

  3. Will using Matlab to plot the data affect the sampling frequency?

Thanks.

EDIT: I accepted the answer below, if anyone has the same problem , please make sure that you change the prescaler as I mentioned above for the best results.

Isra
  • 95
  • 3
  • 11
  • Taking a reading on a floating pin is meaningless. You can get anything. Please, connect something to the pins before doing the experiment. – Edgar Bonet Jan 23 '17 at 08:46

4 Answers4

2

At first I thought that the main problem was not that you too slow at sampling, but that you were too slow at transmitting the data. As Edgar already stated, at 9600 b/s you need 22.88ms to send a line, so the maximum frequency is less than 50Hz.

However as you experienced, the speed you are getting is 200Hz. This is because that calculation is right in the case of a real serial interface. Arduino Leonardo, on the other hand, emulates the serial interface. But.. There is no such thing as baud rate. If you look at the CDC emulation source code you will see that the baud rate you pass to the begin function is ignored.

So... Why is it still slow? Because you are using the delay function! You are waiting for 1ms every time you perform an acquisition, and then you still have to perform all the sending. This has two errors embedded: 1) using the delay function breaks the timing - better use a timer - and 2) you are too slow.

Now, if you want a 1kHz sampling (or any delay multiple of 1ms) you already have a timer running (so you can just use that):

#define INTERVAL_LENGTH_US 1000UL

unsigned long previousMicros;

void loop()
{
    unsigned long currentMicros = micros();

    if ((currentMicros - previousMicros) >= INTERVAL_LENGTH_US)
    {
        previousMicros += INTERVAL_LENGTH_US;

        int val_a0 = analogRead(A0);
        int val_a1 = analogRead(A1);
        int val_a2 = analogRead(A2);
        int val_a3 = analogRead(A3);

        Serial.print(((double)currentMicros) / 1000000UL, 1); // 1 is the number of decimals to print
        Serial.print('\t');
        Serial.print(val_a0);
        Serial.print('\t');
        Serial.print(val_a1);

        Serial.print('\t');
        Serial.print(val_a2);
        Serial.print('\t');
        Serial.println(val_a3);
    }
}

NOTE: The millis() function is replaced by micros() according to Edgar's and Nick's comments.

Change just the define to change the sample time. Now its set to 1000us (i.e. 1ms, so 1kHz), but don't go much higher otherwise you will have to check accurately the sampling time and the transfer speed. The t variable is not needed anymore.

Just a side note: this will also fix the bug in your code that prevents the program to display the current timing (instead of printing 2.5 it prints 2.0).

If you need a faster sample rate you should use another timer to get the correct timing, but again avoid delays. Not needed anymore

Edgar Bonet
  • 43,033
  • 4
  • 38
  • 76
frarugi87
  • 2,721
  • 10
  • 19
  • Note that millis() sometimes skips a millisecond, e.g. it jumps from 41 straight to 43. You can use micros() instead of millis() to avoid the problem: it gives a 4 µs resolution. – Edgar Bonet Jan 23 '17 at 20:52
  • @EdgarBonet this sounds new to me... What are the cases when it skips (apart from when your loop code actually is longer than 1ms)? – frarugi87 Jan 23 '17 at 20:55
  • 2
    On the 16 KHz AVRs, the millis counter is updated every 1024 µs: the millisecond count is incremented by one and the fractional count is incremented by 3 125ths of a millisecond. Whenever this fraction reaches a full millisecond, the millisecond count is incremented by two instead of one. This happens roughly once every 1000/24 ≈ 41.67 updates. – Edgar Bonet Jan 23 '17 at 21:20
  • @frarugi87 I tried your code and it's working! I needed a way to fix my sampling rate and this does it. As you said, the baud rate doesn't affect anything. I tried 9600 and 250000 and both gave me 1kHz. Recieving the readings in Matlab instead of the serial monitor won't affect the frequency right? – Isra Jan 24 '17 at 04:33
  • @frarugi87 Edgar Bonet is right, I cover this issue here. As Edgar said, because of the timer prescaler, the millis "tick" is actually slightly slow. The code adjusts for that every 41/42 updates. – Nick Gammon Jan 24 '17 at 05:03
  • @EdgarBonet I make a (quick) search on google and couldn't find this behavior, so I tested it and... Well, 42 can be the Ultimate Answer to Life, The Universe and Everything, but not for millis... Thank you for pointing this out: I updated the answer with the code I think fixes this (so micros() / prescaler) – frarugi87 Jan 24 '17 at 08:22
  • @NickGammon I read a lot of wonderful replies on SO and here by you, but I didn't know you had a forum where you put a lot of things. Thank you for your link: I'll read your website – frarugi87 Jan 24 '17 at 08:24
  • @Isra please update the code with the one in the answer, since it fixes Edgar's issue. This one is a bit more flexible than the previous one and updates exactly (well, more exactly than millis) every ms. As for matlab, the data source is the same, so the speed is the same, provided that matlab is fast enough to read all the data – frarugi87 Jan 24 '17 at 08:26
  • thank you @frarugi87 for pointing that out, I fixed it. But when I read the serial data in matlab, it gives me this error "Warning: Unsuccessful read: A timeout occurred before the Terminator was reached.. " Do you have an idea on why is that happening? I'm using fscanf(s, '%d')*(5.0 / 1023.0)); to read the values. One last point, if the analog readings are 10 bits wide, how is that transmitted ? I know serial communication's data width is only 8 bits (I set the baud rate to 250000 on both ends just in case) – Isra Jan 24 '17 at 09:04
  • While replacing millis() with micros(), you introduced an erroneous division by INTERVAL_LENGTH_US. I took the liberty to edit your code to restore its previous logic, which was good. Note that the test if (((currentMicros - previousMicros) / INTERVAL_LENGTH_US) > 0) is not technically wrong, and the compiler would optimize it into if (currentMicros - previousMicros >= INTERVAL_LENGTH_US) anyway. But I think it's clearer without the division. @Isra: you may try this new version. – Edgar Bonet Jan 24 '17 at 09:07
  • @EdgarBonet I already noticed that, and fixed it as you fixed before writing the comments, but maybe I made a mess with the back button in the browser and the old version remained online. Thank you for fixing it... – frarugi87 Jan 24 '17 at 09:10
  • @EdgarBonet Thanks very much, I tried it but I still get the same error: "Warning: Unsuccessful read: The input buffer was filled before the Terminator was reached.. " I've tried reading the pins with the regular rate and I was able to read it in matlab. This error shows up only when I set the rate to 1kHz. – Isra Jan 24 '17 at 09:16
  • @Isra try to use a step by step approach: first open and read only the strings you receive (with fscanf(s)), then read the integer value and finally convert and use it. See when you block and try to find a solution (I'm not very much into matlab's serial port interface, so I don't know how to debug it properly). As for the 10 bits, well you are printing to serial, so the value you receive (for instance 1001) is transferred in ascii coding "1001", so four bytes ('1', which is 49, '0', which is 48, then '0' and '0'). If the error is the speed try lowering it to 100Hz (set the define to 10000) – frarugi87 Jan 24 '17 at 09:20
  • @frarugi87 I did what you suggested, it turned out that I have to print the readings each in a single line (from Arduino), Matlab couldn't see the terminator (line end). Another issue, when reading the time in Matlab, you have to use fscanf(s,%f) instead of fscanf(s,%d) because it's a double number. Thank you all for your MASSIVE support and I hope this helps others as well! – Isra Jan 24 '17 at 10:53
1

Two ways to sample at or near a given frequency:

  1. Select the right ADC clock prescaler. This has some limitations and may not get you too the precise sampling frequency.

  2. Use a timer to trigger the sampling. Can be done in either. Hardware or software. It is the most flexible way here, especially if coupled with the ADC isr.

dannyf
  • 2,770
  • 10
  • 13
0

I tried your program on my Arduino Uno with similar results : it prints about 44 lines per second. Then I did the math:

Each line consists of 22 characters, including the CR and LF at the end. Each character is transmitted as 10 bits (8 data bits, one start bit and one stop bit). At 9600 bits/second, each bit takes 0.104 ms to be transmitted. All this amounts to 22.88 ms per line (22 × 10 × 0.104), or about 43.7 printed lines per second, on average.

Now, since you are using a Leonardo, things could be a little different, as the Leonardo does not use a real serial port, but a virtual one. You may still try to increase the baud rate to see if it makes any difference.

Edgar Bonet
  • 43,033
  • 4
  • 38
  • 76
  • I was absolutely sure that this was the correct interpretation, but.. Well, the Leonardo (and all the arduino boards that emulate the serial port) ignores the baud rate you set. So the transfer speed is not the issue in THIS particular case (see my answer) – frarugi87 Jan 23 '17 at 11:39
  • When a CDC/virtual comm port is involved, the setting of baud rate is done in the CDC device driver, on the host side. That would be windows or Linux or .... Or it wouldn't hand shake with your host terminally program. – dannyf Jan 23 '17 at 12:37
  • @dannyf I never used a leonardo or micro, so I never used the CDC emulation. But... Is it really set on the host? I made a quick research on internet and I found this thread about microchip CDC. They say that there is no such thing as baud rate, since the data is transferred in blocks whenever there is the time, so there isn't a "baud rate"; you can set any value and it gets ignored on both the host and arduino... Is this wrong? – frarugi87 Jan 23 '17 at 21:03
  • @Edgar I tried frarugi87's method and it worked with several baud rates! I never knew that Leonardo uses a virtual serial communication. I'd like to try this on an Arduino pro Micro (also Atmega32u4) and Arduino DUE (because it has a 12 bits ADC). I think with the pro micro it's going to be the same, but will it be the case with the DUE? unfortunately I don't have the two boards yet to test them myself. – Isra Jan 24 '17 at 04:40
  • @Isra: The Pro Micro should indeed behave like the Leonardo. The Due, however, seems to have an extra ATmega16U2 as an USB-to-serial chip. Thus I would expect it to behave like the Uno (i.e. the baudrate is relevant). – Edgar Bonet Jan 24 '17 at 09:15
0

A few recommendations to get good performance out of the Arduino boards.

  • Maximize your analogResolution if the default is not already the max (as in the case of the Due.)
  • If possible, use a simple oscillator instead of a constant Voltage source. If you can create a sine or triangle wave with its peaks within the Arduino's input voltage range, you can plot something more interesting than a straight line.
  • There is no need to do any analogReads in setup(), and you DON'T want to do anything with the Serial object in setup() except configure it. (There is an under-the-hood reason for that.)
  • To get good sample rates from the Arduino boards, don't convert to text until MUCH later in the data flow. TO try this SIGNIFICANT speed optimization, declare an array of 4 elements of type unsigned int, another array of 12 elements of type unsigned char, and an unsigned long to hold the time.
  • In loop(), start by assigning micros() to the unsigned long
  • Then call the four analogReads in an uninterrupted sequence, assigning them to the four elements of the unsigned int array.
  • Then copy the four bytes from the unsigned long and the eight bytes from the four unsigned ints into the 12 elements of the unsigned char array. You will need to use the C++ shift right operator ">> 4" and the "(unsigned char)" type conversion to do this part.
  • Then, still in loop(), call Serial.write(byteArray, 12) once to send the time and digital representations all at once (in the byte order you specified) through the serial connection.
  • Place delayMicroseconds at the very end of the loop to get a higher resolution delay between loop iterations.
  • On the other side of the serial connection (the workstation or laptop) use dd or some other low level transfer program (instead of a terminal emulator) to copy from the binary 12-byte sample vectors from the serial device that your workstation or laptop uses to access the serial connection to a binary file.

The above eliminates several bottlenecks typical of the newbie example programs. The issue remaining is that, to optimize speed, you never converted the raw binary data vectors to a tab-delimited text file that MATLAB or GnuPlot can read with ease.

A simple C, Java, or Python program can then read the 12 byte vectors and convert the byte groups of 4, 2, 2, 2, and 2 back into the 32 bit time stamp and the four 16 bit unsigned channel values in a loop. The byte order is what you chose in your Arduino sketch. You will need to use the left shift operator and bitwise and and or operations to reconstruct the time stamp and four channel values. At the end of this loop, you can write these values as a single tab-delimited line coresponding to the current binary input vector. Iterate through all the vectors to get a complete tab-delimited data file.

You can experiment with different delayMicrosecond values to get different sample rates, and you can calculate our baud rate to stay ahead of the sampling so that the program could theoretically run indefinitely without backing up the Serial buffer on the Arduino side. Each Arduino analogRead and Serial object has its own maximum speed, which you can find by experimentation.

Douglas Daseeco
  • 274
  • 1
  • 7
  • You wrote “_you DON'T want to do anything with the Serial object in setup() except configure it. (There is an under-the-hood reason for that.)_”. Could you elaborate? 2) You cannot get good control of the sampling rate with delayMicroseconds(), because the loop time is the delay plus the time needed to execute the code. Using micros(), like in @frarugi87's answer, is the standard method and it works well. Only a timer-triggered analog read will give you better accuracy (less jitter).
  • – Edgar Bonet Jan 24 '17 at 09:25