hi community,
I am testing the precision of timing for both Uno and Mega board. I found the timing is unpredictable. after it goes beyond 25 samples, the timing became inaccurate. I am wondering if there is a hardware issue or something else. Please find the attached testing code and its output. The code basically print the time every 1ms.
Thank you in advance.
bool state=0;
int sampling=1; //1ms
unsigned long startTime;
int n=1;
void setup() {
// initialize serial communication at 9600 bits per second:
Serial.begin(9600);
}
void loop() {
read_serial();
if (state==1 && n<=100) {
Serial.println(millis()-startTime);
delay(sampling);
n=n+1;
}
}
void read_serial(){
if (Serial.available()>0) {
char var=Serial.read();
if (var=='e') {//stop serial print
state=0;
}
else if (var=='s') {//start serial print
state=1;
n=1;
Serial.print("started");
startTime=millis();
}
}
}
Output (time in 'ms' relative to the startTime):
0
You are filling the serial transmit buffer, after which print() becomes a blocking function, waiting for space in the buffer before returning. At 9600 baud, it takes approximately 1mS to send each character. Change the Serial baud rate to 115200 and you will see different results.
Also be aware that occasionally the millis() count will increment by 2 instead of 1. This is caused by the counter interrupt used by millis() being 1.024mS instead of exactly 1mS, requiring a periodic adjustment.
Arduino millis() uses a shortcut to make 250ms fit 8 bits.
Arduino millis math when the lower 8 bits is involved is +/-1ms or to cut to the conclusion of one big thread ---
If close timing matters, use micros()! They good for over 71 minute intervals.
Take from millis that the high 3 bytes do count 250ms intervals.. a decimal value and even fraction of one second at the same time, very useful!
Instead of subtracting start ms from end ms to see if 1 second has passed, I can watch bit 10 that changes every second and detect the change from last read.
Two things to consider, It is not on a crystal so you will get timing errors from that. Also background interring(s) are occurring which will also change timing.
You're kind-of on the right track there, but your explanation is flawed.
The creation of a Tx buffer does not block, but if you try to write to the buffer when it is full, then that will block
A while back, when I was first learning Arduino and working with an UNO, I also tested the timing precision. I found that I could accurately measure up to 8 MHz (50% duty) signals if they originate from the same MCU (UNO) but could only measure signals up to 3.2 MHz (as noted in AVR205) when originating from an external source.
//
// FILE: PulseMeasure.ino (Ver 03)
// AUTHOR: dlloyd
// DATE: 2016-aug-4
//
// LINK: http://forum.arduino.cc/index.php?topic=413133.0
//
// Measures/Calculates Frequency, Period, Width and Duty.
// For self testing, connect pin 3 (PWM output) to pin 8 (capture input)
//
// Input Frequency: 62,500 Hz to 8,000,000 Hz
// Input Period: 0.125 us to 16.000 us
// Input Width: 62.5 ns to 15.937 us
// Input Duty: 0.391 % to 99.609 %
//
byte printHeader = 1;
word hzControl, widthControl;
word data[100], periodCount, widthCount;
float period, width, duty, frequency;
void setup()
{
Serial.begin(115200);
}
void loop()
{
pwmSweep(0); // 0 = normal print, 1 = diagnostic print
while (1);
}
void pwmSweep(byte printMode) {
unsigned long startTime = millis();
word testCount = 0;
for (hzControl = 1; hzControl <= 255; hzControl++) {
for (widthControl = 0; widthControl <= (hzControl - 1); widthControl++) {
pwmBegin(hzControl, widthControl);
delayMicroseconds(20);
pulseRead();
pulseCalculate();
pulsePrint(printMode);
testCount++;
}
}
Serial.print("\nCompleted ");
Serial.print(testCount);
Serial.print(" tests, calculations and printing in ");
Serial.print(float(millis() - startTime) / 1000, 3);
Serial.println(" seconds.");
Serial.println();
}
void pwmBegin(byte ocra, byte ocrb) {
TCCR2A = 0; // TC2 Control Register A
TCCR2B = 0; // TC2 Control Register B
TIMSK2 = 0; // TC2 Interrupt Mask Register
TIFR2 = 0; // TC2 Interrupt Flag Register
TCCR2A |= (1 << COM2B1) | (1 << WGM21) | (1 << WGM20); // OC2B cleared/set on match when up/down counting, fast PWM
TCCR2B |= (1 << WGM22) | (1 << CS20); // no clock prescaler for maximum PWM frequency
OCR2A = ocra; // TOP overflow value (Hz)
OCR2B = ocrb; // PWM Width (duty)
pinMode(3, OUTPUT);
}
void pulseRead() {
TCCR1A = 0; // normal operation mode
TCCR1B = 0; // stop timer clock (no clock source)
TCNT1 = 0; // clear counter
TIFR1 = bit (ICF1) | bit (TOV1); // clear flags
TIMSK1 = bit (ICIE1); // interrupt on input capture
TCCR1B = bit (CS10) | bit (ICES1); // start clock with no prescaler, rising edge on pin D8
noInterrupts();
for (int i = 1; i <= 99; i += 2) { // read captured values
data[i - 1] = ICR1; // get two successive readings
data[i] = ICR1; // get two successive readings
if (i == 49) TCCR1B &= ~bit (ICES1); // capture on falling edge (pin D8)
__builtin_avr_delay_cycles (1); // to prevent reading in synch with input
}
TIMSK1 = 0;
interrupts();
}
void pulseCalculate() {
byte highHz = 1;
byte zeroCount = 0;
word dif, prevDif = 0;
for (int i = 1; i <= 99; i++) {
dif = data[i] - data[i - 1];
if (i > 1) data[i - 1] = prevDif;
if (i < 50 && dif == 0) zeroCount++; // check first half of array for no change in readings
prevDif = dif;
}
if (zeroCount > 15) highHz = 0;
if (highHz) { // high pulse rate at input
sort(data, 50); // for high input rates, sort first half of the diff array
word minDiff = 0xFFFF;
word secondNonZeroSortedDiff = data[zeroCount + 2];
word diffDiff = 0, previousDiffDiff = 0;
for (int i = 3; i <= 46; i++) {
// Get difference of differences of captured readings.
diffDiff = data[i] - data[i - 1];
// Normal condition
if (diffDiff > 0) {
periodCount = diffDiff;
// If the change in results is split between 2 readings, use both
if (previousDiffDiff > 0) periodCount = previousDiffDiff + diffDiff;
// If no duplicate readings (very high Hz)
if (zeroCount == 0) {
if (diffDiff < minDiff) minDiff = diffDiff; // use lowest non-zero difference
if (previousDiffDiff > 0) minDiff = previousDiffDiff + diffDiff; // if split reading, use the sum
periodCount = minDiff;
}
}
// when multiple readings can be taken within within 1 period, just use the second non-zero value
if (zeroCount > 5) periodCount = secondNonZeroSortedDiff;
previousDiffDiff = diffDiff;
}
} else { // low pulse rate at input
byte diffCount = 0;
for (int i = 1; i <= 49; i++) { // all rising to rising edge readings
if (data[i] > 0) diffCount++;
if (diffCount == 2) { // second non-zero change in reading
periodCount = data[i];
diffCount++;
}
}
}
period = periodCount * 0.0625;
frequency = 1000 / period;
byte testDone = 0;
for (int i = 50; i <= 99; i++) { // all falling to falling edge readings
if ((data[i] > 0) && !testDone) {
word widthRead = data[i];
for (int i = 0; i < 15; i++) { // subtract out all periods to get width
if (widthRead >= periodCount) widthRead -= periodCount;
}
if (widthRead) {
widthCount = widthRead;
testDone = 1;
}
}
}
width = widthCount * 0.0625;
duty = (width / period) * 100;
}
void pulsePrint(byte printMode) {
if (printHeader) {
Serial.print("\n\nOCR\tCount\tWidth us\tPeriod us\tDuty %\t\tFreq kHz\n");
printHeader = 0;
}
Serial.print(hzControl);
Serial.print(",");
Serial.print(widthControl);
Serial.print("\t");
Serial.print(periodCount);
Serial.print(",");
Serial.print(widthCount);
Serial.print("\t");
Serial.print(width, 3);
Serial.print("\t\t");
Serial.print(period, 3);
Serial.print("\t\t");
Serial.print(duty, 3);
Serial.print("\t\t");
Serial.print(frequency, 3);
Serial.println();
if (printMode) {
for (int i = 1; i <= 99; i++) {
Serial.print(i);
Serial.print("\t");
Serial.println(data[i]);
}
Serial.println();
}
}
void sort(word * ar, word n)
{
word i, j;
word gap;
word swapped = 1;
word temp;
gap = n;
while (gap > 1 || swapped == 1)
{
if (gap > 1)
{
gap = gap * 10 / 13;
if (gap == 9 || gap == 10) gap = 11;
}
swapped = 0;
for (i = 0, j = gap; j < n; i++, j++)
{
if (ar[i] > ar[j])
{
temp = ar[i];
ar[i] = ar[j];
ar[j] = temp;
swapped = 1;
}
}
}
}
ISR (TIMER1_CAPT_vect)
{
}