I am using an Arduino Uno to send time and voltage information over the serial port to Python to plot. However the interval timings between successive time stamps appears to be increasing over time, affecting my plotting. This is especially true when the baud rate is set to 9600, where my initial time differences maybe 1320 and increases to 16400 after a relatively short period of time. When this rate is put to maximum of 115200 bps the change is slower and less noticeable, from around 1340 to 1500 even after a relatively long run of sending. All times are given in microseconds.
I would like to know if I can reduce or eliminate this effect, and if not understand why it exists. I have read things about interrupts and delays causing this, but I do not fully appreciate the complexity of the electronics at hand and would like to know:
- Can I get greater precision in the timing?
- What causes this change in timing?
Here is what I currently have:
#include <eHealth.h>
extern volatile unsigned long timer0_overflow_count;
float fanalog0;
int analog0;
unsigned long time;
byte serialByte;
void setup() {
Serial.begin(9600);
}
void loop() {
while (Serial.available()>0){
serialByte=Serial.read();
if (serialByte=='S'){
while(1){
fanalog0=eHealth.getECG();
// Use the timer0 => 1 tick every 4 us
time=(timer0_overflow_count << 8) + TCNT0;
// Microseconds conversion.
time=(time*4);
//Print in a file for simulation
//Serial.print(time);
//Serial.print(" ");
Serial.print(fanalog0,5);
Serial.print("\n");
if (Serial.available()>0){
serialByte=Serial.read();
if (serialByte=='F') break;
}
}
}
}
}
3 Answers 3
Use a timer and ISR (interrupt service routine) to make timing more accurate.
Take a look at my 1ms timed interrupt Proof of Concept. The idea is to have a reasonably accurate 1ms 'heartbeat' in the system that can be used to trigger other events. In the PoC it is used to blink an LED at 1⁄2Hz, but having access to the new variables millisecondCounter
and secondCounter
enables you to trigger events in the main loop at arbitrary (but accurately timed) moments.
-
2Your PoC is very interesting but it has a flaw (easy to fix) in the fact that it reads a 2-byte value while interrupts are enabled (in
loop()
), this value being modified by an ISR. It can happen thatloop()
reads a bad value (in the middle of a modification by the ISR). I've posted a comment on your blog about it.jfpoilpret– jfpoilpret2014年03月12日 06:32:18 +00:00Commented Mar 12, 2014 at 6:32 -
@jfpoilpret interesting point you make there, never thought of an interrupt occuring half way retrieving the value from RAM. I'm going to check the disassembly this evening and update the article. Maybe a good reason to write another article too :o)jippie– jippie2014年03月12日 06:49:22 +00:00Commented Mar 12, 2014 at 6:49
-
I created a sample from your PoC and could see the problem occur at least once every 10 seconds on my UNO. But of course in reality it highly depends on what you do in your
loop()
: my sample just got the milliseconds value, ompare it to the previous read value and if difference > 0 (other than reset counter to 0) , display a message.jfpoilpret– jfpoilpret2014年03月12日 06:52:16 +00:00Commented Mar 12, 2014 at 6:52 -
@jfpoilpret never really noticed it. I just use it as a heartbeat to monitor the food buckets for my cats and make an LED flash when my cats will be potentially disappointed ... ;o) It definitely will change the way how I use ISR's in future.jippie– jippie2014年03月12日 06:56:31 +00:00Commented Mar 12, 2014 at 6:56
-
1It shows a crystal connected to a block ATMEGA16U2 and a resonator connected to ATMEGA328P-PU. The 16U2 is for the serial interface the 328P is "The Arduino". Interestingly enough the 16U2 would be able to push its clock to another chip, e.g. the 328P.Udo Klein– Udo Klein2014年04月28日 13:58:06 +00:00Commented Apr 28, 2014 at 13:58
I can think of a few things that can impact the "consistency" of the serial write timings:
- size of the data to be printout
this may be the most obvious thing to think of, but indeed the more you print, the more it'll take to handle it.
Solution: print format the string into a string of known length.
- using buffered serial
on unix you can access the serial port using a buffered or an unbuffered way. Using the buffered way for a long time may make it a bit slower as the buffer fills, usually it happens when data is incoming faster than you're reading it...
Solution: use the unbuffered serial line (e.g.: on Darwin/OSX it's /dev/cu.usbmodemXXX
instead of /dev/tty.usbmodemXXX
)
- priority of the timers
it looks like your using a TC interrupt, and AVRs have priorities in the way interrupts are handled, I don't know the order of priority for the Atmega328, and it's not one of the most documented feature around, so I don't know how safe is TC0 versus the UART interrupt.
Solution: look up further in the documentation/datasheet about interrupt priorities and change the timer if needed ; and/or do a test without having the other timer running.
- the data you're reading from takes more time to read from over time
some drivers need to average or do some operations over the previous values, so the more values you measure, the longer the buffer is, and the longer it takes to calculate the value, until you've reached the maximum size of the buffer.
Solution: look at the source code of the library you're using, and either optimize it, remove the calcul if there's one or take that increasing processing time into account.
- avoiding the arduino framework overhead
but if you really want to optimize serial output from the arduino, you should avoid using the arduino overhead... But it's way less elegant and comfortable to use.
I'm pretty sure there are other points I'm missing, but that's the first things that I'd check before digging further.
HTH
Your code includes the duration of the output in subsequent measurements. Thus depending on the length of the output you will measure different times. This can be fixed by formating to fixed length output.
The next issue is that the UNO has a very poor timebase. Have a look here for a comparison of different Arduino types vs. the DCF77 time reference.
Conclusion: if you need precise timing either get an Arduino with crystal or go for an RTC. I can highly recommend DS3231 / DS3232 RTCs since these usually achieve 2 ppm accuracy out of the box.
eHealth.getECG()
do? Does that call always last the same amount of time?