Serial Communication and interrupts triggered by ADC

Hi! I am trying is to read the analog values of 4 different sensors and send to MATLAB for processing via serial communication at a frequency of 5 kHz (or 1 sample from each of the 4 sensor every 200 uS). This only happens when the switch in digital3 is turned on. Due to the slow time of analogRead(), returning the analog values via the registers is the fastest way. In addition, I believe serial.Write() will have to be used over serial.Print() because of its slow return time however this limits the output values from 1023 to only 255 (so I also need a way to overcome this issue. I am assuming sending a high and low byte so it is read at uint-16 instead of uint-8 in MATLAB). I have implemented an interrupt which triggers from the ADC clock. I have included my code below which is running on my Uno and can only return the value of 1 sensor. The problem I am having is how to read the values of 4 sensors while still sampling fast enough for 5 kHz. In addition, an LCD will be implemented later on to readout these sensor values however I should be able to figure that out. Thanks for the help in advance.

#include <Arduino.h>
int value1;

void setup() {
  Serial.begin(115200);
  ADMUX = B01000000;   //Read A0, REFS0 equal to 1 AVcc
  ADCSRA = B10101111;  //starts adc with prescaler = 128
  ADCSRB = B00000101;
  ADCSRA |= 0x40;
  TCCR1A = B11110000;  //sets output register
  TCCR1B = B00001001;
  OCR1A = 3200;  //top threshold
  OCR1B = 3200;
  TIMSK1 = (1 << OCIE1B);  //
}

ISR(_VECTOR(21)) {
  if (digitalRead(3) == 1) {
    value1 = ADCL | (ADCH << 8);
    Serial.println(value1);
  }
}

ISR(TIMER1_COMPB_vect) {  //resets interupt flag
  TCNT1 = 0;
}

void loop() {
}

So 50 microseconds per sample. If each sample is one serial byte that's 5 microseconds per bit (10 bits per serial byte) or 200000 baud (115200 is not even close to fast enough). Try it at 250000 baud.

If you want to send 16-bit samples, try 500000 baud.

Since your actual samples are only 10 bits, it might be good to set the top 6 bits to a fixed pattern so you can recover sync if a byte goes missing.

1 Like

As Other people are far more knowledgable than I am. But I see the below problem; maybe I see it wrong or mybe I misunderstand your requirement.

For 10-bit ADC, the maximum sampling rate is 15kS/second (see datasheet). So 66 microseconds per sample. If I understand you correctly you want to do 4 samples (one for every channel), so that will be 264 microSeconds (not taking into account that you actually have to do a second read for each input to make sure that the sample/hold capacitor reflects the input voltage after switching).

1 Like

Pardon my incorrect use/understanding: I have configured my ADC clock to operate at 5 kHz. 16 MHz clock / 5 kHz = 3200=OCR1A/B. This means my interrupt will trigger every 200 uS. If I want to read all 4 at a time (5kHz*4=20kHz) so 16 MHz/20kHz=800. When timing each loop (with 4 Serial.write(value1) and a few other lines of code. I am getting a time of around 160 uS. So I am not too concerned about the timing and speed of things but more configuring a switch statement which can read the analog values via the registers.

ISR(_VECTOR(21)) {
  if (digitalRead(3) == 1) {
    switch (cycle) {
      case 1:
      startTime = micros();
        value1 = ADCL | (ADCH << 8);
        Serial.write(value1);
        Serial.write(" ");
        cycle = 2;
        break;
      case 2:
        //ADMUX = B01000001;  //Read A1, REFS0 equal to 1 AVcc
        //value2 = ADCL | (ADCH << 8);
        Serial.write(value1);
        Serial.write(value1);
        Serial.write(value1);
        endTime = micros();
        elapsed = (endTime - startTime);
        Serial.println(elapsed);
        cycle = 1;
        break;
    }
  }
}

Where this switch statement is implement with 4 cases, 1 for each "analog read".

That is only possible when the system clock is at 20 MHz. With a 16 MHz clock you can only get a little under 10 ksps at full resolution (not enough for 4*5k sample per second).

At the loss of some resolution, you can crank the ADC clock up to 1 MHz which gets close to 80 ksps (12.5 microseconds per sample).

IF you did that, you'd probably get away with keeping only the top 8 conversion bits, discarding the noise at the bottom; that would allow the op to transmit a single byte. Of course, he must decide the degraded resolution is acceptable, which I doubt.

1 Like

The degraded resolution is acceptable. I have changed the ADC clock to 20 ksps ( I believe this is correct, setting OCR1A to 800 = 16 MHz/20kHz) in which it will sample 1 sensor every 50 microseconds. Every clock cycle will trigger an interrupt to read a new sensor value so that after 200 microseconds, all 4 sensor values are read and sent to serial. Again, apologize for incorrect wording as I am still trying to comprehend everything.

1.
The following interrupt driven sketch demonstrates the technique of simultaneous acquisition of four channels (tested on UNO using test signals). The ADC works well for ADC-clock frequency up to 1 MHz (this brings the conversion time to: 13 us) though the recommended ADC-clock frequency is 125 kHz (conversion time: 104 us).

volatile int analogVal[4]; //volatile tells compiler not to keep variable in register
volatile bool flag = false; //helps to execute print() method in loop() function
volatile byte i = 0;  //array index

void setup()
{
  Serial.begin(9600);
  ADCSRA = 0x00;  //reset
  ADMUX = 0x00;   //reset

  ADMUX |= 1 << REFS0; //Ch-0, right adjust, Vref = 5V
  ADCSRA |= 1 << ADPS2 | 0 << ADPS1 | 0 << ADPS0;//1 MHz adcClk
  ADCSRA |= 1 << ADIE;  //ADC EOC interupt enable
  ADCSRA |= 1 << ADEN;  //ADC Module is active
  interrupts();  //Global interrupt logic is enabled
}

void loop()
{
  bitWrite(ADMUX, 0, bitRead(i, 0)); //select channel
  bitWrite(ADMUX, 1, bitRead(i, 1));
  ADCSRA |= 1 << ADSC;  //Start ADC
  if (flag == true)
  {
    analogVal[i] = ADCL | (ADCH << 8); //read ADCL first
    Serial.print("Ch-"); Serial.print(i);
    Serial.print(" = "); Serial.println(analogVal[i]);
    flag = false;
    i++;
    if (i == 4)
    {
      i = 0;
      Serial.println("=================");
    }
  }
  delay(1000); //test interval
}

ISR(ADC_vect)//conversion ends, ADSC goes LOW, ADIF goes HIGH and interrupts MCU
{
  flag = true;
}

Output:

Ch-0 = 663    //3.3V
Ch-1 = 1023  //5V
Ch-2 = 0        //0V
Ch-3 = 1023  //5V
=================

2.

In Serial Communication, the speed is measured in Bd (bits/sec) and not in kHz/Hz.

You have 50 us interval among the four sensors; so, you can go well with ADC-clock frequency of 1 MHz (above that my above sketch does not work!). My experiment on the above sketch shows that about 30 us time is required to acquire/save (no dsiplay on Serial Monitor) a signal from a channel with adcClk = 1 MHz.

3.

What is 20 ksps?

ADC's conversion clock frequency has the unit of Hz/kHz/MHz.

4.

You can not operate the ADC at 5 kHz. The minimum frequency that you can select is 125 kHz with division factor 128.

5.

You may use TC1 to generate 50 us Time Tick to drive the ADC for acquiring signals from the four sensors.
Test Sketch:

volatile int analogVal[4]; //volatile tells compiler not to keep variable in register
volatile bool flag = false; //helps to execute print() method in loop() function
volatile byte i = 0;  //array index
byte chCounter = 0;

void setup()
{
  Serial.begin(9600);
  ADCSRA = 0x00;  //reset
  ADMUX = 0x00;   //reset

  ADMUX |= 1 << REFS0; //Ch-0, right adjust, Vref = 5V
  ADCSRA |= 1 << ADPS2 | 0 << ADPS1 | 0 << ADPS0;//1 MHz adcClk
  ADCSRA |= 1 << ADIE;  //ADC EOC interupt enable
  ADCSRA |= 1 << ADEN;  //ADC Module is active
  //----------------------------
  TCCR1A = 0x00;
  TCCR1B = 0x00;
  TCNT1 = 65436; //preset value for 50 us rollover
  TCCR1B |= 1 << CS11;  //start TC1 with division factor 8 (0x02)
  interrupts();  //Global interrupt logic is enabled
}

void loop()
{
  while(bitRead(TIFR1, TOV1) !=HIGH)
  {
    ;  //wait until 50 us has gone
  }
  bitSet(TIFR1, TOV1);
  TCNT1 = 65436;
  chCounter++;
  if(chCounter == 4)
  {
    chCounter = 0;
  }
  acqSignal();
  delay(1000); //test interval
}

void acqSignal()
{
  bitWrite(ADMUX, 0, bitRead(i, 0)); //select channel
  bitWrite(ADMUX, 1, bitRead(i, 1));
  ADCSRA |= 1 << ADSC;  //Start ADC
  if (flag == true)
  {
    analogVal[i] = ADCL | (ADCH << 8); //read ADCL first
    Serial.print("Ch-"); Serial.print(i);
    Serial.print(" = "); Serial.println(analogVal[i]);
    flag = false;
    i++;
    if (i == 4)
    {
      i = 0;
      Serial.println("=================");
    }
  }
  //delay(1000); //test interval
}

ISR(ADC_vect)//conversion ends, ADSC goes LOW, ADIF goes HIGH and interrupts MCU
{
  flag = true;
}
1 Like

Interesting, no one has commented on the earlier mention of back-to-back sampling of each input to reduce charge transfer noise.
?
C

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