Adjusting for variable rollover?

Im using an arduino sketch that samples an AC waveform.

Problem: Accuracy; when my variables go to rollover to their min values, my sketch loses a large portion of the waveform giving me inaccurate results. I also cannot increase the variable count from 700, as if i go higher by much then my sketch stops working (im assuming theres a max variable limit somewhere between 800-1000).

I was just wondering if there was any way to account for this, so i could get a continuous, accurate representation of the measured waveform.

[u]Current code:[/u] EDIT: CODE WRONG, refer to code listed in reply.

Graph showing the rollover:

Why are you taking a lot of readings from one input then a lot of readings from the other. They are never going to add up like that. I can't see any weighting applied to the second reading either. Basically I think you are going about it the wrong way, how have you wired things up?

Yeah i am going about it the wrong way in this case, i see that now, they wont add up due to timing issues.

HOWEVER, regardless of my coding and incorrect values, i still get that variable rollover on all of my tests and setups and i still need a way to overcome it.

For testing/modification purposes i can just use the following code instead of the wrong code listed above:

const int numReadings = 700; 
int inputReading [numReadings];

void setup() 
{
  Serial.begin(115200);
}


void loop()
{
    
  for (int i = 0; i < numReadings; i++) {
    inputReading [i]= analogRead(0); 
  }
  
  
  for (int i = 0; i < numReadings; i++) {    
    float voltage = ((inputReading[i]/1023.0)*4.945);                             
    Serial.println(voltage);  
  }
}

i still get that variable rollover on all of my tests and setups and i still need a way to overcome it.

I don’t think you do.
When you use float I front of the variable name you are creating a new variable with the same name. I expect that code to just print out the value of the last reading. What are you expecting it to do?

The readings take time and when you are in the second loop (filling inputReading2) the first loop does nothing.

The numReadings is max 375 as it then uses 375 x2 x2 bytes of memory = 1500 bytes of the 2000 you have.

Why not send the measurements directly?

void setup() 
{
  Serial.begin(115200);
}

void loop()
{
  float voltage1 = analogRead(0) * 0.004833822; // simplified the math as floating divisions are very expensive
  Serial.println(voltage1, 2);  
  float voltage2 = analogRead(2) * 0.004833822; 
  Serial.println(voltage2, 2);  
}

Im was expecting the code to read and print a continuous AC waveform (Which i KNOW it is as I can see it via a oscilloscope AND scopemeter).

Also when the numReadings cap is reached (700, 250 in the graph) the rollover occurs and i clearly lose a portion of my waveform (ie: miss a pointion due to the rollover time delay).

@robtillaart My code listed at the top originally was wrong, i listed the proper code in my first reply, i can get my project to work with the single input as seen above in my first reply.

EDIT: @robtillaart i cant send them directly as i need the highest sample rate i can get (~9k) and the analog read default frequency is a lot less then that.

due to the rollover time delay

What is this? There is no roll over occurring in the arduino code. You could be asking for an array that is too big. The arduino only has 2K of memory and you are asking for 1400 bytes of that. Leaving little for anything else.

Grumpy_Mike:

due to the rollover time delay

What is this? There is no roll over occurring in the arduino code. You could be asking for an array that is too big. The arduino only has 2K of memory and you are asking for 1400 bytes of that. Leaving little for anything else.

I was just making assumptions due to the fact that that skip in the waveform occurs right when my numReadings count hits 700. regardless, for any values of numReadings, i still see that skip, the only thing that changes is how many samples i get before the skip occurs. But if its not due to the rollover of the variable then i have no clue what it could be, as i said before i know the waveform is continuous, and not exceeding the arduinos range, so it pretty much just leaves coding.

Remove that variable defination float from the for loop and put it at the start of the function. Then I bet you will get further before you see your "roll over"

Grumpy_Mike:
Remove that variable defination float from the for loop and put it at the start of the function.
Then I bet you will get further before you see your “roll over”

Nope still getting it for the code below, also still getting it while in the void loop, but outside the for loop:

const int numReadings = 700; 
int inputReading [numReadings];
float voltage;

void setup() 
{
  Serial.begin(115200);
}


void loop()
{   
  
  for (int i = 0; i < numReadings; i++) {
    inputReading [i]= analogRead(0); 
  }
  
  for (int i = 0; i < numReadings; i++) { 
    voltage = ((inputReading[i]/1023.0)*4.945); 
    Serial.println(voltage);  
  }
}

also cant move the = ((inputReading[i]/1023.0)*4.945); outside the for loop as its dependent on i’s count number.

Ok I don't get this bit:-

I was just making assumptions due to the fact that that skip in the waveform occurs right when my numReadings count hits 700. regardless, for any values of numReadings

I still don't see why this is not array size related.

Grumpy_Mike: Ok I don't get this bit:-

I was just making assumptions due to the fact that that skip in the waveform occurs right when my numReadings count hits 700. regardless, for any values of numReadings

I still don't see why this is not array size related.

Well when i set numReadings to 100 for example, i still see it (every 2/3 of a cycle), and isnt 100 low enough for it not to take up a lot of memory?

Unless im understanding the array sizing wrong.

Just to resummarize, all i want to achieve is constant 9k sampling, why is that so hard to do :/

[some exploration]

The analogRead() waits time in its call until the measurement is ready.
I’ve split analogRead into 3 calls that allows you to send a measurement while a next analogRead is in progress.

you should convert the raw measurement to float on the receiving side (probably a PC that is fast enough to handle that)

Don’t know if it is fast enough for you - this makes 10K integer samples in 3.64 seconds [UNO 16Mhz]

//
//    FILE: asyncAnalogRead.pde
//  AUTHOR: Rob Tillaart
//    DATE: 09-jun-2012
//
// PUPROSE: experimental async analogRead
//

unsigned long start = 0;
unsigned int counter = 0;

void setup()
{
  Serial.begin(115200);
  
  start = millis();
  counter++;
  AR_Start(0);            // start the analogRead
}

void loop()
{
  while (counter < 10000)
  {
    while(!AR_Ready());      // wait until ready
    int val = AR_Value();    // read its value
    AR_Start(0);             // start a new reading
    counter++;
    Serial.println(val);
  }
  Serial.println(millis() - start);
  while(1);
}

//
// This is the code of analogRead split in 3 separate functions 
// not nice but it works
//

#include "wiring_private.h"
#include "pins_arduino.h"

void AR_Start(uint8_t pin)
{
#if defined(ADCSRB) && defined(MUX5)
  // the MUX5 bit of ADCSRB selects whether we're reading from channels
  // 0 to 7 (MUX5 low) or 8 to 15 (MUX5 high).
  ADCSRB = (ADCSRB & ~(1 << MUX5)) | (((pin >> 3) & 0x01) << MUX5);
#endif
  // set the analog reference (high two bits of ADMUX) and select the
  // channel (low 4 bits).  this also sets ADLAR (left-adjust result)
  // to 0 (the default).
#if defined(ADMUX)
  ADMUX = (DEFAULT << 6) | (pin & 0x07);
#endif

  // without a delay, we seem to read from the wrong channel
  //delay(1);
  sbi(ADCSRA, ADSC);
}

boolean AR_Ready()
{
  // ADSC is cleared when the conversion finishes
  return bit_is_set(ADCSRA, ADSC)==0;
}


int AR_Value()
{
  // we have to read ADCL first; doing so locks both ADCL
  // and ADCH until ADCH is read.  reading ADCL second would
  // cause the results of each conversion to be discarded,
  // as ADCL and ADCH would be locked when it completed.
  int low  = ADCL;
  int high = ADCH;
  // combine the two bytes
  return (high << 8) | low;
}

The real performance eater is the communication.

If your receiving program can handle 500000 baud, you set Serial.begin(500000); // or 230400 345600 works quite well too
The Arduino can send it at that speed, but be aware the IDE monitor cannot handle it

I measured ~1.75 seconds for 10K reads == 5710 samples per second

For reference

unsigned long start = 0;
unsigned int counter = 0;

void setup()
{
  Serial.begin(500000);
  start = millis();
}

void loop()
{
  while (counter < 10000)
  {
    Serial.println(analogRead(0));
    counter++;
  }
  Serial.println(millis() - start);
  while(1);
}

The normal analogRead() make 10K samples in 2.8 seconds == 3570 samples/sec


A next optimization would be sending the raw samples in binary format

unsigned long start = 0;
unsigned int counter = 0;

void setup()
{
  Serial.begin(500000);
  start = millis();
}

void loop()
{
  while (counter < 10000)
  {
    int x = analogRead(0);
    Serial.write(x >> 8);
    Serial.write(x & 255);
    counter++;
  }
  Serial.println();
  Serial.println(millis() - start);
  while(1);
}

10K samples in 1.44 second == 6944 samples per second

doing this with the async way … it makes 10K samples in 1.119 second = 8936 samples per second (now we are getting somewhere)

//
//    FILE: asyncAnalogRead.pde
//  AUTHOR: Rob Tillaart
//    DATE: 09-jun-2012
//
// PUPROSE: experimental async analogRead
//

unsigned long start = 0;
unsigned int counter = 0;

void setup()
{
  Serial.begin(500000);
  
  start = millis();
  counter++;
  AR_Start(0);            // start the analogRead
}

void loop()
{
  while (counter < 10000)
  {
    while(!AR_Ready());      // wait until ready
    int val = AR_Value();    // read its value
    AR_Start(0);             // start a new reading
    counter++;
    Serial.write(val >> 8);
    Serial.write(val & 255);
  }
  Serial.println();
  Serial.println(millis() - start);
  while(1);
}

//
// This is the code of analogRead split in 3 separate functions 
// not nice but it works
//

#include "wiring_private.h"
#include "pins_arduino.h"

void AR_Start(uint8_t pin)
{
#if defined(ADCSRB) && defined(MUX5)
  // the MUX5 bit of ADCSRB selects whether we're reading from channels
  // 0 to 7 (MUX5 low) or 8 to 15 (MUX5 high).
  ADCSRB = (ADCSRB & ~(1 << MUX5)) | (((pin >> 3) & 0x01) << MUX5);
#endif
  // set the analog reference (high two bits of ADMUX) and select the
  // channel (low 4 bits).  this also sets ADLAR (left-adjust result)
  // to 0 (the default).
#if defined(ADMUX)
  ADMUX = (DEFAULT << 6) | (pin & 0x07);
#endif

  // without a delay, we seem to read from the wrong channel
  //delay(1);
  sbi(ADCSRA, ADSC);
}


boolean AR_Ready()
{
  // ADSC is cleared when the conversion finishes
  return bit_is_set(ADCSRA, ADSC)==0;
}


int AR_Value()
{
  // we have to read ADCL first; doing so locks both ADCL
  // and ADCH until ADCH is read.  reading ADCL second would
  // cause the results of each conversion to be discarded,
  // as ADCL and ADCH would be locked when it completed.
  int low  = ADCL;
  int high = ADCH;
  // combine the two bytes
  return (high << 8) | low;
}

If eight bit is enough one can reduce the communication with 50% (and improving it too as a missing byte will not screw the signal!)
A quick test did not show improvements.


but you can tweak the AD conversion itself, ==> probably loosing accuracy and precision in the analogRead().

unsigned long start = 0;
unsigned int counter = 0;

void setup()
{
  Serial.begin(500000);
  
  // prescale clock to 16
  bitClear(ADCSRA,ADPS0);
  bitClear(ADCSRA,ADPS1);
  bitSet(ADCSRA,ADPS2);

  start = millis();
}

void loop()
{
  while (counter < 10000)
  {
    int x = analogRead(0);
    Serial.write(x >> 8);
    Serial.write(x & 255);
    counter++;
  }
  Serial.println();
  Serial.println(millis() - start);
  Serial.println(millis() - start);
  while(1);
}

10K samples in 0.522 seconds … almost 20K samples per second (I expect missing bytes, all disclaimers apply!!)

Note this is about the maximum # samples you can send over 500Kbaud!

The ultimate test, tweak the prescaler clock, do it async, and only 8 bit … ==> terminal app could not handle it anymore.

unsigned long start = 0;
unsigned int counter = 0;

void setup()
{
  Serial.begin(500000);
  
  // prescale clock to 16
  bitClear(ADCSRA,ADPS0);
  bitClear(ADCSRA,ADPS1);
  bitSet(ADCSRA,ADPS2);
  
  start = millis();
  counter++;
  AR_Start(0);            // start the analogRead
}

void loop()
{
  while (counter < 10000)
  {
    while(!AR_Ready());      // wait until ready
    int val = AR_Value();    // read its value
    AR_Start(0);             // start a new reading
    counter++;
    Serial.write(val);
    //Serial.write(val & 255);
  }
  Serial.println();
  Serial.println(millis() - start);
  while(1);
}

None of the code above is tested (extensively), but it shows there is room to improve the speed of your sampling application.

updated a few texts for readablility

I agree that free ram is a concern. Search the forums for “free memory function” for the magic code that will report how much is free at startup. Keep it above 200 or thy stack will invade thy heap.

I would also be wondering whether background interrupts like the millis() increment might be affecting the time interval between some samples. An interrupt will displace all subsequent readings by a certain phase. Perhaps it could create artifacts like we see in the chart of values.

You could test this second hypothesis by turning off interupts around the sampling loop, like this:

  cli();
  for (int i = 0; i < numReadings; i++) {
    inputReading [i]= analogRead(0); 
  }
  sei();

Note that nothing is free; if you keep interrupts off long enough you will lose millis(). Doesn’t seem critical in the program we see so far.

-br

@robtillaart wow that is a hell of a response, going to take me a while to go through it all, although something i should have mentioned earlier is that accuracy is critical in my program so the programs you listed that sacrificed accuracy for speed would not be viable, however thank you for that amazing response, im going to try out some of that code and see what i can do. Still wish there was a simpler way of sampling at a speed of at least 6k or so.

@Billroy unfortunately turning off interrupts had no effect.

Welcome, it was an adventure to go through my snippets and experimental folders and combine things.

(almost) 6K is feasable, raw values, async modus, 500Kbaud ==> I got ~5700 samples@10 bit. that is 95% of your requested speed

Note that to get 6K samples your minimum baudrate is 6K x 2(bytes/int) = 12Kbytes = 120Kbit including start stop bits. That is at 100% communication. mind you, you need also time to measure ;) ==> So 115200 is allready too slow.

What I did not explore was compression of the communication as I do not know the signal. You could send the relative value compared to the previous one. If the signal is smooth (sine), one byte as delta would be sufficient. max +-127 (== int8_t) But if the signal is smooth you do not need so much samples in the first place...

Succes!

Next background interrupt thing I would eliminate is the Serial.print background output buffer handling. Perhaps a delay(1000); at the start and end of loop to give the serial port time to quiesce before another cycle of readings starts.

-br

Unfortunately the college doors are closing soon so i cannot test anything more today, but i will be in again tomorrow so i will update then about what did/didnt work.

That sketch will read 700 values from A0 as fast as it can (and this is uncontrolled, and may not be consistent) and then print them all out (and this will take a huge time relative to the capture). Then it will do that again.

The result should be 700 consecutive samples, then a break of unknown length, then a further 700 samples, and so on.

Is this not what you see?