4

Ultimately I will move to a generic smart phone for this project. I'm trying to read 4 analog inputs from my arduino with a fixed 1kHz sampling frequency. I managed to set it to read at 1kHz as you can see from this link. The issue is that after running several times, I kept testing the frequency by logging the time vector (produced by arduino to the serial terminal) into Excel and calculating the frequency using the equation: f=1/(t2-t1)

I did this to the entire column and I noticed that I got a changing frequency. The difference is very big as it ranges from 780 to 1kHz. I need a fixed sampling rate because we will be applying some digital signal processing on the obtained results which obviously requires a sampling frequency. My code to send the data is the following:

#define INTERVAL_LENGTH_US 1000UL

unsigned long previousMicros;
#define FASTADC 1
int recValue;
// 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(115200) ;
}



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, 5 ); // 1 is the number of decimals to print
    Serial.print('\t');

    Serial.print(val_a0);
    Serial.print(',');

    Serial.print(val_a1);
    Serial.print(',');

    Serial.print(val_a2);
    Serial.print(',');

    Serial.println(val_a3);


  }
}

Ps. I'm using Arduino Leonardo, What's the right way to achieve this?

EDIT: Thank you all for responding, As you wanted to see my data, here is a screenshot of the results after I applied Jwpat7's suggestion: enter image description here

The yello shaded column is the frequency calculated, the first and 2nd columns represent T1 and T1 (T2 is just T1 shifted upward for easier calculation). You may notice the Matlab section, this is because I'm trying to plot the data in Matlab and use it to do the digital signal processing after transmission. The plotting will be real time (I'll the data in txt files for later processing). the bottom potion of the figure shows the frequency after a while....as you can see it's decreasing!

Thanks

1
  • Comments are not for extended discussion; this conversation has been moved to chat. Comments are also not for answering the question, or for discussing various possible solutions. Comments on questions are for clarifying the question. That's it!
    – Nick Gammon
    Commented Feb 2, 2017 at 8:08

3 Answers 3

3

You can speed your code up slightly by sending a raw microseconds reading, rather than divided by 1000000. Floating arithmetic on typical Arduino devices can be quite slow. Also, the time to complete a divide is data dependent, so may occasionally but not always delay the next reading; that is, the division may contribute to jitter as well as overall slowness.

Another source of jitter is polling via loop() rather than within loop [see below]. However, this probably adds only a few microseconds of jitter.

To test if too-slow serial transmission is the problem, you could comment out all the serial printing and substitute Serial.println(currentMicros). (Or print the low two bytes of currentMicros.)


Polling within loop(): You could replace

currentMicros = micros();
if ((currentMicros - previousMicros) >= INTERVAL_LENGTH_US)
  {  ...  }

with

while (micros() - previousMicros < INTERVAL_LENGTH_US) ;
currentMicros = micros();
...

This would tighten up the busy-wait loop and reduce jitter slightly.

If small changes as suggested above don't resolve the problem, you might need to use timer-based interrupts interrupt flags to schedule trigger ADC reads (as suggested in comments) and/or create a circular data buffer to make data collection timing less dependent upon data transmisssion timing.

The ADC Auto Trigger Source Selections table in the ATmega32U4 datasheet shows the ADTS bit settings to select various ADC trigger sources, which include Free Running, Analog Comparator, External Interrupt Request 0, and various Compare/Overflow/Capture events on Timer/Counter0, 1, and 4. Your ADC conversion-complete interrupt handler would need to read the ADC, store the reading, set a volatile flag to tell loop() which reading is ready, and set the ADC input selection to the next channel.


Edit 2: Where the code spends its time: In the version that busy-waits until the next time-mark arrives, and then takes four ADC readings, the process sits and waits for the ADC to complete. With a standard prescale factor of 128 and 104 μs per conversion, about 416 microseconds are spent waiting on ADC completion. If FASTADC is set and prescale is 16, giving 13 μs per conversion, then only 52 μs is spent in that waiting. In brief: use of a standard prescale factor will probably use up too much time if the polling method is used. But if readings are timer-interrupt triggered and ADC-ready-interrupt stored, a standard prescale factor is ok.

Serial transmission rate estimates: As noted in Edgar Bonet's answer, 115200 bps isn't fast enough to support 1000 Ascii-data serial transmissions per second. If working at 100% throughput, that data rate allows only 115 bits per transmission, or 11.5 characters, which typically is not enough for the data you are sending. Additionally, sending the time in Ascii adds another digit to the transmission length at the 10 second and 100 second marks, which may account for the observed slowdown in the "Test ARDUINO" scenario. (The slowdown in the Matlab case may be due to rapidly-growing memory use by Matlab; possibly try less-accurate plotting, or narrower plots, or a specialized graphing program.)

Edgar Bonet's code sidesteps this problem by transmitting data from a fixed-length (8 byte) buffer in binary, hence the 80000 bps figure he mentions (8 bytes times 10 bits per serially-transmitted byte times 1000 readings per second). Since ADC readings are timer-triggered, there probably is no real need to transmit a clock time with each reading, but resynchronization (in case of lost bytes) might need to be added somehow. Possibly one could send a 9-byte packet at each transmission; the extra byte would allow use of a protocol to support error-detection, resynchronization, periodic clock readings, and packet counts.

8
  • I am not suggesting timer-based interrupts, I suggest using the “auto-trigger” feature of the ADC to trigger the conversion on a timer event. This is based on the timer interrupt flag (i.e. event detection) and works irrespective of whether the corresponding interrupt is enabled or not. Commented Feb 1, 2017 at 18:47
  • @EdgarBonet, I've edited accordingly Commented Feb 1, 2017 at 20:25
  • @jwpat7 That's what I was doing, I did the time and voltage conversions in Matlab since I will be plotting my data there. I added the division to Arduino's sketch just when I was trying to find out if the jitter was from Arduino's side or from Matlab. I edited the question to explain what I mean on the screenshot. As you can see the freuqency starts high then it gets lower. Even worse, the readings from Matlab (when I read via Matlab the second time) have lower frequency with the same decreasing frequency after a while.
    – Isra
    Commented Feb 2, 2017 at 4:41
  • I know Matlab is not the best option, I'm open to use a faster multi channel plotting tool if it's going to help but I need to make sure that the results are solid from Arduino's side first.
    – Isra
    Commented Feb 2, 2017 at 4:41
  • @Isra, I edited answer. Note, if you don't have a convenient way to read binary data, to have enough serial throughput for Ascii data, slow things down to 800 or 750 Hz instead of 1000, or try some of the higher bps settings like 250000 or 1000000. However, general purpose PCs may have trouble keeping up at those rates. Commented Feb 2, 2017 at 6:25
2

Expanding on my previous comments...

The only reliable way to get jitter-free ADC readings is to trigger the ADC from a timer event. I wrote the following program to demonstrate the approach on an Arduino Uno, as I don't have a Leonardo. The program configures the Timer 1 to set it's overflow flag every 250 µs, and the ADC to start a conversion on every rising edge of that flag. When the conversion is done (104 µs after it started), the ADC ISR fires. It's job is to retrieve the conversion result and set the analog multiplexer for the next channel, switching round-robin through channels 0 – 3.

volatile int adc_readings[4];
volatile bool fresh_readings;

// Used only to clear the interrupt flag.
EMPTY_INTERRUPT(TIMER1_OVF_vect);

ISR(ADC_vect)
{
    static int buffered_readings[3];
    static uint8_t channel;

    if (channel == 3) {

        // "Export" the 4 readings at once.
        adc_readings[0] = buffered_readings[0];
        adc_readings[1] = buffered_readings[1];
        adc_readings[2] = buffered_readings[2];
        adc_readings[3] = ADC;
        fresh_readings = true;
    } else {

        // Buffer the current reading.
        buffered_readings[channel] = ADC;
    }

    // Move to next channel.
    channel = (channel + 1) % 4;
    ADMUX = _BV(REFS0)  // reference = Vcc
          | channel;
}

void setup()
{
    Serial.begin(115200);
    Serial.write("================");

    // Configure the ADC.
    ADMUX  = _BV(REFS0)   // reference = Vcc
           | 0;           // channel = ADC0
    ADCSRA = _BV(ADEN)    // enable
           | _BV(ADSC)    // start first conversion
           | _BV(ADPS0)   // prescaler = 128
           | _BV(ADPS1)   // ...ditto
           | _BV(ADPS2);  // ...ditto
    ADCSRB = _BV(ADTS1)   // trigger source = Timer 1 overflow
           | _BV(ADTS2);  // ...ditto

    // Wait for and discard the first conversion.
    loop_until_bit_is_set(ADCSRA, ADIF);

    // Configure Timer 1 to overflow every 250 us.
    TCCR1A = _BV(WGM11);  // fast PWM mode, TOP = ICR1
    TCCR1B = _BV(WGM12)   // ...ditto
           | _BV(WGM13)   // ...ditto
           | _BV(CS11);   // clock at F_CPU/8
    TIMSK1 = _BV(TOIE1);  // enable overflow interrupt
    ICR1   = 500 - 1;     // period = 500 * 8 CPU cycles

    // Set the ADC to auto-trigger mode.
    ADCSRA |= _BV(ADATE)  // enable auto-trigger
            | _BV(ADIF)   // clear interrupt flag
            | _BV(ADIE);  // enable interrupt
}

void loop()
{
    if (fresh_readings) {
        Serial.write((uint8_t *) adc_readings, sizeof adc_readings);
        fresh_readings = false;
    }
}

A few comments:

  • The Timer 1 overflow interrupt is used only to clear the timer interrupt flag. The flag has to be cleared because the ADC is triggered only on it's rising edges. Alternatively, that flag could be cleared inside the ADC ISR.

  • I choose to deliver the readings by batches of 4 (one reading per channel), thus the buffering inside the ADC ISR. The choice is arbitrary: you can, if you wish, transmit the readings as they come, as suggested in jwpat's answer.

  • The very first ADC reading is manually started and discarded. The only reason for this is that it takes longer (200 µs instead of 104).

  • The readings are transmitted in binary form at 115.2 kb/s. The effective data rate is 80 kb/s. ASCII would not be possible at this sampling rate.

  • The loop() function must never take more than 1 ms to execute, otherwise there is a risk of it reading the adc_readings array while the ADC ISR is updating it, which would lead to corrupt data. Protecting the access to adc_readings inside a cli()/sei() pair would not significantly ease the timing constraint, as a long loop() execution would still carry the risk of loosing data points.

From a cursory look at the ATmega32u4 datasheet it would seem it's Timer 1 is identical to the one of the ATmega328P powering the Uno, save for an extra PWM channel. The ADC, however, is slightly different, but similar enough to make porting fairly straightforward. At least the setting of ADMUX would be different.

1
  • I have an Uno so I can try this and see how it works. I have little eperience in programming Atmega in the low level but I understood what you meant. One poit which wasn't clear to me though is 4th comment. I know that the ADC's readings re 10 bits each, so I will need to transmit 2 bytes for every reading and then reconstruct them in the recieving end (in my case Matlab). wouldn't that be slower than sending ASCII?
    – Isra
    Commented Feb 2, 2017 at 5:32
1

What you have here is a classic case of the measurement of something changing what is measured.

I guessed when I saw your question that the time taken to transmit all that serial data would be longer than 1 ms. If you really were transmitting at 115200 baud then it would take 2.6 ms to send the 30-odd bytes you are sending (30 * 1/11520). Since you are using a Leonardo you get USB speeds which is faster but not infinitely fast.

I could reproduce your figures when I tested.

However if I comment out the two lines that send the time:

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

And also measure separately by toggling pin 13:

  if ((currentMicros - previousMicros) >= INTERVAL_LENGTH_US)
  {
    previousMicros += INTERVAL_LENGTH_US;
    digitalWrite (13, !digitalRead (13));

Then I found that pin 13 toggled at exactly once per millisecond.

(edit) Close enough. See screenshot of logic analyser (0.996 ms):

Logic analyser

That is only 4 µs out - within error range for the processor clock.

Uncommenting the timing code I found that for a short time I still got 1 ms per iteration, but it then dropped down to around 1.04 ms (ie. 960 Hz). I am guessing that some USB buffer, somewhere, is filling up and not being emptied as fast as you would like.

Here is the screenshot of the code which sends the time (1.0366 ms):

Logic analyser - with debugging code

Notice how simply sending the number of microseconds has increased the time between toggling the pin?


It is helpful to have extra hardware to hand to measure things like this. Using the processor that you are trying to time, to send its own timing information is fraught with problems.

3
  • Can you explain to me how you found the frequency by toggling a digital pin? is there a counter that does that?.....I tried to go for 250000 baudrate and 500us sampling period (to get 500 Hz frequency) but the results were in the range of 900 Hz and also alternating between different values in this range. I've used Leonardo and Pro Micro to test several times, each trial gave me different results.
    – Isra
    Commented Feb 2, 2017 at 7:30
  • See amended answer.
    – Nick Gammon
    Commented Feb 2, 2017 at 7:58
  • I tried to go for 250000 baudrate - on the Leonardo and Micro the baud rate does not affect the results. I set the baudrate to 1 (lol) and still got the same results. The Serial output goes to the USB interface, bypassing the concept of baud rates.
    – Nick Gammon
    Commented Feb 2, 2017 at 8:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.