Hi guys. I am using an Arduino (Micro to be specific) for my final year project which involves sampling an analogue accelerometer to monitor the shock the device is subjected to.
Anyway I have all the hardware connected and running together but I am struggling at ensuring that the sampling happens at 10,000 Hz - I have been using a fast analogue read technique from the arduino cookbook so this question is more a case of timing.
My initial thought was to check each time within a while loop to see if 100 microseconds has passed (the period for a 10kHz sampling rate) however this produce wildly different values as the the micros() function has a resolution of 4 microseconds and introduced an error of over 4%. I have since tried to implement an internal interrupt using the timer1 library and have tested the theory using the example code below.
#include <TimerOne.h>
// This example uses the timer interrupt to blink an LED
// and also demonstrates how to share a variable between
// the interrupt and the main program.
boolean interrupt = false;
boolean displayed = false;
int i = 0;
unsigned long loopCount = 0;
unsigned long beforeTime = 0;
unsigned long afterTime = 0;
void setup(void)
{ Serial.begin(9600);
while(!Serial){
delay(1000);
}
Timer1.initialize(100);
Timer1.attachInterrupt(sensorInterrupt); // blinkLED to run every 0.15 seconds
}
void sensorInterrupt(void){
interrupt = true;
}
// The main program will print the blink count
// to the Arduino Serial Monitor
void loop(void)
{
if (i==0){
beforeTime = micros();
}
else if (i == 999){
afterTime = micros();
}
loopCount++;
if (interrupt){
interrupt = false;
if (i < 1000){
i++;
Can someone explain why I still get an error of 1% which whilst not huge is large than I would like!
Also any suggestions as to how I can improve on this (I was initially worried about the loop time not happening fast enough but I built in a little checker into the script and it is doing about 20,000 cycles per interrupt so is can't be contributing much at all to the error!
Period isn't 99.00. Do the calculation by hand. This calc:
float period = (afterTime - beforeTime)/1000;
has all integer components so the fractional part is truncated before putting it into a float. Use 1000.0 instead.
Holmes4 - I thought the same thing initially, but I think it's actually ok - the serial stuff is displayed once after all the rest of the calcs are done.
Not the issue apparently, but the interrupt variable should be volatile.
To get a more accurate timing use a timer and sample the ADC inside the timer overflow/match interrupt routine.
Your example code uses the timer interrupt to set a flag that loop() detects and acts upon but several other interrupts could have taken control before loop got a look in.
holmes4 - The serial only prints out once the 1000 "readings" have been done (if statement will be false for this until i am finished taking readings and it only prints once on completion)
Wildbill - You sir are correct! I completely forgot that everything would be treated as an int unless the ".0" was there. When i remove that the frequency varies slightly but is much closer to 10,000 Hz (normally 0-8Hz off). - No idea what a volitile variable is? (i'll have a look on google now)
Robin2 - I tried this originally but as micros only has a resolution of 4 it didn't get great results. It was up to about + or - 350 Hz each side of the 10,000 Hz aim. And it varied allot more than using interrupts appears to which is worse for my scenario than a fixed value which is slightly offset.
Riva - I planned on doing the analogue read within the interrupt script (I assume this is what you meant?) and storing it in a variable which the loop picks up and deals with appropriately - I included a loop counter to check that the entire loop is going through many times per interrupt so no values should be missed providing I keep my loop quite streamline correct?
slzer:
Robin2 - I tried this originally but as micros only has a resolution of 4 it didn't get great results. It was up to about + or - 350 Hz each side of the 10,000 Hz aim.
I must do some tests on this later.
I wonder if you test for
if (micros() - prevMicros >= 97)
would you trick it into always landing on the same interval? Of course you must increment prevMicros by 100, and not 97.
Also, with the code I proposed there would not be an accumulated error because all the times are measured from the start time.
If that does not work the simple option would be to program one of the hardware timers to trigger an interrupt at the appropriate intervals.
slzer:
I planned on doing the analogue read within the interrupt script (I assume this is what you meant?) and storing it in a variable which the loop picks up and deals with appropriately - I included a loop counter to check that the entire loop is going through many times per interrupt so no values should be missed providing I keep my loop quite streamline correct?
You could/should also use a variable for passing status information as well as data from the interrupt code.
When the interrupt fires it reads and stores the ADC value and sets a flag so the main loop knows new data has arrived. It (main loop) reads the data and clears the flag. If the interrupt code fires again before the values have been read & flag cleared by the main loop then set another flag to say data was missed. The main code can pickup this data missed flag and report errors to user.
Any variables that are shared between interrupt code and main code should be volatile and if they are bigger than byte size then you should disable/enable interrupts around there reading (or have interrupt code to prevent changes during read) as an interrupt could fire during a partial read and you get duff data.
Can you please edit your initial post . Click the More/MODIFY button, delete the QUOTES TAGES, highlight the code and click the CODE [<>] toolbutton just to the left of the QUOTES button, and click SAVE.
Use the CODE button in the future for posting code.
I did, and it didn't.
While the majority of the intervals were 100 usecs some of them were 104. I also discovered that adding analogRead() into my code pushed it beyond 100usecs per read. Obviously I could delete my time-keeping code and then analogRead() would probably fit - but it would be marginal.
My conclusion is that you would need to use one of the hardware timers to generate accurately timed interrupts and use port manipulation to read the ADC.
Where f = main clock (16MHz)
N = pre-scalar (set to 8 in my application)
TOP = the value in ICR1
Based on the values of ICR1 from 1 to 65535 we get a frequency range from 15Hz to 1MHz.
So for example:
ICR1 = 10,000 gives f = 100Hz
ICR1 = 5000 gives f = 200Hz
ICR1 = 2500 gives f = 500Hz
ICR1 = 1000 gives f = 1kHz
ICR1 = 500 gives f = 2kHz
ICR1 = 333 gives f = 3kHz
ICR1 = 250 gives f = 4kHz
ICR1 = 100 gives f = 10kHz
I now have to write the variable as an integer (something i should probably have been doing anyway as it was just the raw value from the analogue read) - I will have a seperate array for when i convert it from the ADC value to acceleration and if space allows (haven't checked yet) a final one for the Shock response spectrum values, else i'll have to clear the integers first.
I also had to reduce my history of readings buffer to 30 as even writing and moving the variables around on internal ram added up to enough that the loop speed was long enough to cause issues with the interrupts. On this note I will look more into good programming etiquette for interrupts so as not to run into any errors caused by the interrupt happening while i use the variable!!!