Pages: [1]   Go Down
Author Topic: ADC sampling rate doesn't tally with sampling rate calculated from plot  (Read 2037 times)
0 Members and 1 Guest are viewing this topic.
Melbourne, Australia
Offline Offline
Newbie
*
Karma: 0
Posts: 4
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Hi everyone. I'm working on a project which measures the aggregated power consumption (other parameters like reactive power and harmonics too) of the house and then using these parameters and some neural network algorithms (back propagation classifiers etc), perform appliances classification and individual load monitoring. I'm now at the first stage in which I'm getting signals in from the arduino and I am able to pipe them over to the PC via USB/Serial. To do a quick validation on whether the samples make sense, I fire up putty and the sample values are received in the form of "<sample1> <sample2> <sample3> ...". In other words, each sample value which is sent from the Arduino is delimited with a space. I then copy all values in the putty window and paste them into MATLAB to produce a plot.

The problem that I'm facing is that the ADC sampling rate doesn't tally with the sampling rate calculated from sampling points collected at the PC. For example, the sampling rate is set such that the ADC clock is 125kHz (using a prescaler of 128). In this case, the maximum sampling rate should be (125kHz/13 = 9615.4 samples/s) since the conversion takes 13 ADC clock cycles from the start of the conversion. However, when taking the number of samples in one AC voltage cycle (1/(50Hz) = 0.02s) and dividing it with the period of the AC waveform to obtain the sampling rate, it doesn't match with the sampling rate configured at the ADC. The calculated one is slower.

I should mentioned that I need a strict sampling rate because I would be including current harmonics as one component of the feature vector used as an appliance signature.

Here's the code that is running on the 328P:

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

int sensorValue = 0;
int sensorPin = A0;

volatile unsigned char badc0;
volatile unsigned char badc1;
//volatile unsigned short channel;

void setup() {
 
 Serial.begin(1000000); // Setup serial connection with baud rate of 1000000 baud/s
 
 // Initialize the ADC and enable interrupts
 // I bit in the SREG is already enabled in wiring.c's init()
 // Disable ADC before enabling ADC interrupts.
 cbi(ADCSRA,ADEN);
 sbi(ADCSRA,ADIE); // Enable interrupt for ADC.
 cbi(ADCSRA,ADIF); // Clear the interrupts flag for ADC.
 
 
 sbi(ADMUX,REFS0);  // VCC Reference
 cbi(ADMUX,REFS1);
 
 // Convert voltage of channel 0 first.
 cbi(ADMUX,MUX0);
 cbi(ADMUX,MUX1);
 cbi(ADMUX,MUX2);
 cbi(ADMUX,MUX3);
 
 // Set registers for free running mode.
 sbi(ADCSRA,ADATE);
 // No need to set register ADCSRB for the trigger source because it is by default all zero and uses ADIF as the trigger source.
 
 // Enable ADC.
 sbi(ADCSRA,ADEN);
 
 // Start the conversion.
 sbi(ADCSRA,ADSC);
 
}

void loop() {
   
  //sensorValue = analogRead(sensorPin);
 
  //Serial.print(sensorValue);
  //Serial.print(" ");
 
  //delayMicroseconds(50);
 
}

ISR(ADC_vect) {
 
  badc0 = ADCL;
  badc1 = ADCH;
 
  Serial.print((badc1 << 8) + badc0);
  Serial.print(" ");
 
}

I started off using the basic analogRead(). The problem exist and I thought the reason is because analogRead() uses polling to check if conversion is completed. So I moved on to using interrupts and configure the ADC for free-running mode but to no avail. I did a little debugging and I thought the problem is related to using Serial.print() in the ISR. For instance, when execution moves inside the ISR, interrupt is disabled as the I-flag in SREG is cleared by the hardware. And in this context, if Serial.print() takes some time to return and the next conversion is completed, the new conversion value will not be printed. Overall, the effect is some samples are missed and not being sent over the serial connection. Does this make sense? Do you think this is the problem?

Where can I find the implementation(source) of Serial.print()?

Thanks in advance. I really appreciate any suggestions on what went wrong. smiley
« Last Edit: December 05, 2011, 06:42:31 am by mysticalzero » Logged

Peoples Republic of Cantabrigia
Offline Offline
God Member
*****
Karma: 6
Posts: 719
Arduino happiness
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Not sure what the problem is, but FWIW, in my experience the openenergymonitor gets about 2.8ksps on a 16Mhz Arduino assuming that's all you're doing. If there are ways to speed it up (for example ignore most ADC pins since they're not being used anyway), I'd like to know.
Logged

Brunsbüttel, SH, F.Rep.GERM
Offline Offline
God Member
*****
Karma: 4
Posts: 596
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

here is the source code for the Serial object:
./hardware/arduino/cores/arduino/HardwareSerial.h
./hardware/arduino/cores/arduino/HardwareSerial.cpp

u could try to use time slots by using micros() like this:

Code:
uint32_t last = 0;
void loop() {
  for (;;) {
    const uint32_t now = micros();
    if (now-last >= 1000) {
      last = now;
      break;
    }
  }
  analogRead...
  Serial.write...
}

1kSps (10bits payload + 5bit error detection + 1bit high/low byte = 2B/sample = 16kbit/sec) should be ok on a 115200kbit/sec link...
 
« Last Edit: December 05, 2011, 07:45:21 am by RIDDICK » Logged

-Arne

Montreal
Online Online
Faraday Member
**
Karma: 27
Posts: 2573
Per aspera ad astra.
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Start conversion ADSC executed only once, as it in setup instead main loop.
Here is great example how to work with ADC:
http://interface.khm.de/index.php/lab/experiments/arduino-realtime-audio-processing/
Logged

Melbourne, Australia
Offline Offline
Newbie
*
Karma: 0
Posts: 4
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Hey guys. Thanks for the replies.

I've got it working. As it turns out, the problem lies with using Serial.print() instead of Serial.write(). With Serial.print(), the ADC value will be converted to ASCII characters and therefore each digit consumes 8 bits. For example, an ADC value of 511 would take up 24 bits + another 8 bits for the space delimiter that I've chosen. In other words, a 10-bit ADC value is inflated to a total of 4 ASCII characters, which is 32-bits in size. This increase in bytes to be sent plus the processing time associated with the mapping between the ADC value and the corresponding ASCII characters lengthen the time before Serial.print() return while in the ISR. In contrast, with Serial.write(), the UART logic would only be required to send two 1-byte (8 bits is the size of the UART TX buffer). What I did was split the 10-bit ADC value into 2 parts. I first send the first byte containing the least significant 5 bits and then the second byte containing the most significant 5 bits of the ADC value. The leftover 3 bits from each byte is used for identification on whether the byte is the first part or the second part.

The results? Sampling rate as calculated from the plots is in fair agreement with the sampling rate configured at the ADC.

Before:


Sampling rate = (1702-1630)/0.02 = 3600 samples/s

After:


Sampling rate = (3467-3276)/0.02 = 9550 samples/s

@Magician:
Well, the ADC is not configured to run in single conversion mode. Therefore, ADSC is only required to be set once. Thanks for the helpful link by the way. Cheers.
« Last Edit: December 07, 2011, 05:47:56 am by mysticalzero » Logged

Peoples Republic of Cantabrigia
Offline Offline
God Member
*****
Karma: 6
Posts: 719
Arduino happiness
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Would you mind posting your final code?

Also, are you looking only at one channel or two? Power measurements usually require the latter approach.
Logged

Melbourne, Australia
Offline Offline
Newbie
*
Karma: 0
Posts: 4
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I'm looking into two channels of course. With two channels, the effective sampling rate for each channel is halved. I will be running the ADC at 250kHz (about 19 ksps). Although it exceeds the recommended frequency by 50kHz, the results are still excellent and 7800 ksps for each channel is achievable.

Here's the code if you are interested. I've have reverted back to single conversion mode. It's easier to time the change of channel this way.
Code:
/* Arduino sketch to sample the voltage and current waveform connected to the A0 and A1 pins of the Arduino Uno before packing
them into a compact serial byte format and sending it over to the PC for procesing via UART/USB.
analogRead() is not used due to its polling nature which causes non-deterministic resultant sampling rate as seen from plots of
Voltage/Current vs Samples.

Copyright (C) 2011 Ricky Wong Yung Fei (mysticalzero)

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.*/

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

int sensorValue = 0;
int sensorPin = A0;

volatile unsigned char badc0;
volatile unsigned char badc1;
volatile unsigned short channel;
volatile unsigned char frame_p1; // Contains first part of the sample to be sent over to the PC>
volatile unsigned char frame_p2; // Contains second part of the sample ....
volatile unsigned char done = 0;

// Note:
// Channel 0 -- AC voltage signal on A0
// Channel 1 -- AC current signal on A1

void setup() {
 
 Serial.begin(1000000); // Setup serial connection with baud rate of 1000000 baud/s
 
 // Initialize the ADC and enable interrupts
 // I bit in the SREG is already enabled in wiring.c's init()
 // Disable ADC before enabling ADC interrupts.
 cbi(ADCSRA,ADEN);
 sbi(ADCSRA,ADIE); // Enable interrupt for ADC.
 cbi(ADCSRA,ADIF); // Clear the interrupts flag for ADC.
 
 
 sbi(ADMUX,REFS0);  // VCC Reference
 cbi(ADMUX,REFS1);
 
 // Convert voltage of channel 0 first.
 cbi(ADMUX,MUX0);
 cbi(ADMUX,MUX1);
 cbi(ADMUX,MUX2);
 cbi(ADMUX,MUX3);
 
 // Set sampling frequency to 19ksps
 sbi(ADCSRA,ADPS2);
 sbi(ADCSRA,ADPS1);
 cbi(ADCSRA,ADPS0);
 
 // Enable ADC.
 sbi(ADCSRA,ADEN);
 
 // Start the conversion.
 sbi(ADCSRA,ADSC);
 
}

void loop() {
   
  //sensorValue = analogRead(sensorPin);
 
  //Serial.print(sensorValue);
  //Serial.print(" ");
 
  //delayMicroseconds(50);
  if(done == 1) {
   
   Serial.write(frame_p1);
   Serial.write(frame_p2);
   
   done = 0;
   
  }
 
}

ISR(ADC_vect) {
 
  channel = ADMUX & 0xF;
 
  badc0 = ADCL;
  badc1 = ADCH;
 
  // One sample is 10 bit in size. This is too big to fill into the TXBUF of UART, which is 8 bits.
  // So we send the sample in two goes. The first frame contains the least significant 5 bits of the sample
  // while the second frame contains the most significannt 5 bits of the sample.
 
  // First, we define the frame format:
  //         | Pt | Signal Type | Sample_Pt |
  //           1b       2b           5b
  //
  // where Pt => b0 : First Part
  //          => b1 : Second Part
  //       Signal Type => b00 : AC voltage signal.
  //                   => b01 : AC current signal.
  //       Sample_Pt : Either the least significant 5 bits of the sample or the most significant 5 bits.
  if(done == 0) {
   
    frame_p1 = badc0 & 0x1F; // Get the least significant 5 bits of the sample. e.g b000xxxxx
    frame_p1 = frame_p1 | (channel == 0 ? 0x00 : 0x20); // Set the signal type field appropriately.
   
    frame_p2 = ((badc0 & 0xE0) >> 5) | ((badc1 & 0x03) << 3) | 0x80;
    frame_p2 = frame_p2 | (channel == 0 ? 0x00 : 0x20); // Set the signal type field appropriately.
    //Serial.write(frame_p1);
    //Serial.write(frame_p2);
    //Serial.write(0xF8);

   
    if(channel == 0) { // If the last conversion is for voltage, the next conversion is for current. This alternates indifinitely.
     
       // Convert current of channel 1.
       sbi(ADMUX,MUX0);
       cbi(ADMUX,MUX1);
       cbi(ADMUX,MUX2);
       cbi(ADMUX,MUX3);
     
    } else if(channel == 1) {
     
       // Convert voltage of channel 0.
       cbi(ADMUX,MUX0);
       cbi(ADMUX,MUX1);
       cbi(ADMUX,MUX2);
       cbi(ADMUX,MUX3);
     
    }
   
    sbi(ADCSRA,ADSC);
   
    done = 1;
   
  }
 
}

For the code to work, you would need to code an appropriate receiver to decode the byte format to extract the voltage and current.

For the fun of it, here are the measured voltage and current waveforms for a desktop PC in my university's lab. The results are very similar to the output from an oscilloscope except for minor ADC quantization errors on the current plot. The quantization errors are expected because I've chosen a burden resistor for the CT such that the maximum measurable current is huge at the expense of current resolution.

Voltage:


Current:
Logged

Peoples Republic of Cantabrigia
Offline Offline
God Member
*****
Karma: 6
Posts: 719
Arduino happiness
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

That's amazing. You're sampling at about 3x of what I have been able to achieve with a simple readAnalog function. There are some multiplication and other things going on as well but as I understand it, the beauty of the ADC is that it runs independently of the main CPU core, so other stuff can happen while the ADC goes through it's sampling routine. If that's the case, then I may have to re-order the program such that it can execute lots of stuff between individual reads... and assemble the contents of the bytes into a number that my program understands...

Looks like a switch-mode power supply being sampled?

Thanks again!
« Last Edit: December 09, 2011, 10:06:52 am by Constantin » Logged

Peoples Republic of Cantabrigia
Offline Offline
God Member
*****
Karma: 6
Posts: 719
Arduino happiness
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

FWIW, I couldn't make the above code work for my application. Perhaps when I have more time, I'll be able to parse the code to allow me to extract the data into a form that my program can use.

That said, adjusting the pre-scaler on the ADC did have it's intended effect. Going down to 16 resulted in some garbage ADC results, so I used 32 instead, which had rock-solid results. So, the ADC is sampling both channels at about 4400 samples/s using the standard analog.read command, which seems fast enough for a 60Hz signal. As I recall, it came out to 37 slices per half-wave. That's good enough for my application...

Now if I could only figure out how to do decimation properly, I could be able to get 16 bit precision out of a 10 bit adc...
Logged

Brunsbüttel, SH, F.Rep.GERM
Offline Offline
God Member
*****
Karma: 4
Posts: 596
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

is decimation like oversampling?
here is a pdf from atmel about oversampling:
http://atmel.com/dyn/resources/prod_documents/doc8003.pdf
Logged

-Arne

Peoples Republic of Cantabrigia
Offline Offline
God Member
*****
Karma: 6
Posts: 719
Arduino happiness
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Yup, read that document a while ago, thanks! Decimation should work great on the power component under steady-state conditions, whereas voltage and current vary too much for decimation to work its magic. I will re-attempt to do decimation on the power components (real and apparent) at some point and compare the results to the straight averaging I am doing right now.

In theory, I should be able to get 16 bit precision since I am getting more than 4096 samples/s. In practice, I may settle for averaging six 15-bit results since the 328Ps sampling rate @ 16MHz is now about 5540 samples/s with a 64 pre-scaler and 4432 samples/s with the standard 128 pre-scaler. The latter is preferable since running the ADC at 250kHz will not produce 10bit results per Atmel. Getting to about 5500 samples per second is possible, however, just use a 20MHz resonator and the 128 pre-scaler for a 158kHz ADC clock. 

There is no change in terms of the samples/s when I switch from a ADC clock divisor below 64, i.e. the main CPU takes longer to do it's business than the ADC at that point. Since the program math is unlikely to be optimized further, 5500 samples/second (for each of the two channels, i.e. 11k samples/s total) is the current sampling limit - which is still plenty fast to characterize a 60Hz Voltage curve or the current (though some SMPS produce funky current draws as what I suspect is shown above).

With some additional work, I got mysticalzeros second program to work on my Arduino. At one point, the ISR was executing about 30,000 times a second, which is great if the data can be used that quickly, not so useful if it cannot. Because the program I am using needs to do some calculations on the data between each analog read, I discovered that using an ISR had practical limitations for my application. For the time being, I have moved the ADC commands from inside the ISR into the core of the program, with a while loop wait as insurance to hold off on reading ADC results until the ADC sends the all-clear after each conversion.

That said, I found that the use of UART transmissions as each second elapses had some impact on the number of samples my loop could capture. For example, removing the Serial.print commands for current voltage, amps, power (apparent and real) increased the sample rate by 20. Nothing huge, but it pointed to these print commands having an impact, so I may revisit doing all the heavy lifting inside the ISR to get closer to the actual sampling rate that the ADC can produce. I have also considered changing the UART transmission rate between my Arduinos to be higher than 115200 but then I cannot monitor it easily.

Thanks again for the note and a special thanks to mysticalzero for posting his code. I used parts of the first and the second example to put together my main loop, which works great.
« Last Edit: December 28, 2011, 11:25:55 am by Constantin » Logged

Pages: [1]   Go Up
Jump to: