Difference between revisions of "Arduino: realtime clock test"

From Luky-Wiki
Jump to: navigation, search
(Result)
 
(21 intermediate revisions by the same user not shown)
Line 1: Line 1:
 
=== Description of test ===
 
=== Description of test ===
In past I created programs only for PC like architecture. I think my knowledge of C language is almost on expert level. I also know assembler, internal details about microprocessing architecture (AVR) and details about ISP communication. Apparently I should have all required knowledge to program Arduino UNO board with Atmel microprocessor but it is first time for me. Therefore I created "small" program to test "real time" clock configuration and simple UART communication.
+
There is only one oscillator for primary mCPU on Arduino UNO board. It is running on 16 MHz frequency and have decent stability. The questions is: Is this enough precise to measure wall clock using Arduino UNO board? The best way how to get answer is to test it.
 +
 
 +
Code described in this article is measuring time starting at pre-set value. Idea is to keep this running for some time and then check difference between precise clock and clock measured by Arduino UNO.
 +
 
 +
''Note:'' I recommend external RTC for time critical applications.
 +
 
 
=== Technical description ===
 
=== Technical description ===
* Arduino UNO board contain 16MHz crystal. Timer1 is 16bit. Once configured to auto-reload at 15624 and take clock source via 1/1024 pre-scaler I get interrupt exactly at one second time (16Mhz / 1024 = 15625 ticks per second, Timer1 is calculating from zero). Clock update routine is invoked via interrupt handler.
+
* Arduino UNO board have 16MHz crystal. Timer1 is 16bit. Seconds are measured using following configuration: auto-reload at 62500 + 1/256 pre-scaler. (16MHz / 256 = 62500 ticks per second, Timer1 is calculating from zero). Clock update routine is invoked via interrupt handler.
* To see "current" time I configured UART. Data are send on each update.
+
* Data are send-out after each update using serial line.
* Currently I don't know how long it will take final code to configure registers and start measuring time so I created small routine for character receive. Each received character reset time to predefined value (in code 23:30:00 August 10th)
+
* Reset routine ensure that counting start exactly when I would like to start test. It is invoked by "s" character send over serial line.
* Reset of device is indicated by rapid blinking "L" diode. On each update diode change state (e.g. from On to Off and vice versa).
+
* "L" diode change status on each update (just to indicate that code is running).
 +
* I cut-out "reset-en" to ensure that mCPU is not accidentally reset by connected computer.
 +
* Pre-scaler running at 1/256 is required for fine tuning (Changing top value by "1" when prescaller is 1/1024 cause approximately 4 seconds difference per day).
 +
 
 
=== Source code ===
 
=== Source code ===
Here is source code of my test. Please note that this code is "dirty" and designed only for this test. There are several parts which can result in race condition or are creating additional load to CPU. For example there is no need to update whole date/time string each second.
+
Here is source code of my test. Please note that this code is designed for this test only. There are several parts which can result in race condition or are creating additional load to CPU. For example there is no need to update whole date/time string each second.
 
<pre>
 
<pre>
 
#define F_CPU 16000000
 
#define F_CPU 16000000
Line 29: Line 37:
  
 
//                          012345678901234567890123456789
 
//                          012345678901234567890123456789
volatile uint8_t buf[30] = "2012-00-00 00:00:00       \r\n";
+
volatile uint8_t buf[30] = "2012-00-00 00:00:00         \r";
 
volatile uint8_t i;
 
volatile uint8_t i;
  
Line 35: Line 43:
 
         uint8_t tmp = UDR0;
 
         uint8_t tmp = UDR0;
  
         // set initial time
+
         if (tmp == 's') {
        time.month = 8;
+
                // set initial time
        time.day = 10;
+
                time.month = 8;
        time.hour = 23;
+
                time.day = 14;
        time.minute = 30;
+
                time.hour = 20;
        time.second = 0;
+
                time.minute = 30;
 +
                time.second = 0;
 +
        };
  
 
}
 
}
Line 94: Line 104:
 
void main (void) {
 
void main (void) {
 
         // setup serial port
 
         // setup serial port
 +
        // 9600, 8 bit, 1 stop, no parity, interupt handler on RX/TX
 
         UBRR0H = UBRRH_VALUE;
 
         UBRR0H = UBRRH_VALUE;
 
         UBRR0L = UBRRL_VALUE;
 
         UBRR0L = UBRRL_VALUE;
Line 99: Line 110:
 
         UCSR0C = _BV(UCSZ01) | _BV(UCSZ00);
 
         UCSR0C = _BV(UCSZ01) | _BV(UCSZ00);
  
         // setup realtime interupt
+
         // Timer1 top value (for autoreload)
         //ATOMIC_BLOCK(ATOMIC_FORCEON) {
+
         // Note: this is two byte operation
                OCR1A = 15624;
+
        //      it should be secured by atomic block
 +
        //      when accessing while interupts are enabled
 +
        OCR1A = 62500;
  
         //}
+
         // Time1, 1/256, autoreload, interupt on compare
         TCCR1B = _BV(CS12) | _BV(CS10) | _BV(WGM12);
+
         TCCR1B = _BV(CS12) | _BV(WGM12);
 
         TIMSK1 = _BV(OCIE1A);
 
         TIMSK1 = _BV(OCIE1A);
  
Line 129: Line 142:
 
}
 
}
 
</pre>
 
</pre>
I'll keep ruining this code for several days to see what will be difference between "atomic" time and "my" time.
+
 
 +
=== Result ===
 +
After 24 hours I get following result:
 +
 
 +
Measured time using Arduino UNO was +5 second off compared to stratum 1 NTP server.
 +
 
 +
It looks like crystal is of by ~926 Hz (approximately 0.006 % ).
 +
 
 +
Calculated frequency of crystal is 16,000,925.93 Hz (this may change over time and also is specific for board used during test). It is good for mCPU itself but not so good to measure time. Correction can be made by altering Timer1 TOP value (OCR1A) but precision is not guaranteed over time.
 +
 
 +
If your project have access to precise time source or precise time measurement is not required then this is easiest way how to track "daytime". If precision is required and there is no way how to access better time source then dedicated RTC board is recommended.

Latest revision as of 18:59, 14 February 2016

Description of test

There is only one oscillator for primary mCPU on Arduino UNO board. It is running on 16 MHz frequency and have decent stability. The questions is: Is this enough precise to measure wall clock using Arduino UNO board? The best way how to get answer is to test it.

Code described in this article is measuring time starting at pre-set value. Idea is to keep this running for some time and then check difference between precise clock and clock measured by Arduino UNO.

Note: I recommend external RTC for time critical applications.

Technical description

  • Arduino UNO board have 16MHz crystal. Timer1 is 16bit. Seconds are measured using following configuration: auto-reload at 62500 + 1/256 pre-scaler. (16MHz / 256 = 62500 ticks per second, Timer1 is calculating from zero). Clock update routine is invoked via interrupt handler.
  • Data are send-out after each update using serial line.
  • Reset routine ensure that counting start exactly when I would like to start test. It is invoked by "s" character send over serial line.
  • "L" diode change status on each update (just to indicate that code is running).
  • I cut-out "reset-en" to ensure that mCPU is not accidentally reset by connected computer.
  • Pre-scaler running at 1/256 is required for fine tuning (Changing top value by "1" when prescaller is 1/1024 cause approximately 4 seconds difference per day).

Source code

Here is source code of my test. Please note that this code is designed for this test only. There are several parts which can result in race condition or are creating additional load to CPU. For example there is no need to update whole date/time string each second.

#define F_CPU 16000000
#define BAUD 9600

#include <avr/io.h>
#include <avr/interrupt.h>
#include <stdint.h>
#include <avr/sleep.h>
#include <util/setbaud.h>
#include <util/atomic.h>
#include <util/delay.h>

volatile struct {
        uint8_t month;
        uint8_t day;
        uint8_t hour;
        uint8_t minute;
        uint8_t second;
} time;

//                          012345678901234567890123456789
volatile uint8_t buf[30] = "2012-00-00 00:00:00          \r";
volatile uint8_t i;

ISR(USART_RX_vect) {
        uint8_t tmp = UDR0;

        if (tmp == 's') {
                // set initial time
                time.month = 8;
                time.day = 14;
                time.hour = 20;
                time.minute = 30;
                time.second = 0;
        };

}

ISR(USART_TX_vect) {
        if(i < 30) {
                UDR0 = buf[i];
                i++;
        } else {
                i = 0;
        }
}

ISR(TIMER1_COMPA_vect) {

        time.second++;
        if (time.second > 59) {
                time.second = 0;
                time.minute++;
        if (time.minute > 59) {
                time.minute = 0;
                time.hour++;
        if (time.hour > 23) {
                time.hour = 0;
                time.day++;
        if (time.day > 31) {
                time.day = 1;
                time.month++;
        if (time.month > 12) {
                time.month = 1;
        }}}}};

        buf[17] = time.second / 10 + '0';
        buf[18] = time.second % 10 + '0';

        buf[14] = time.minute / 10 + '0';
        buf[15] = time.minute % 10 + '0';

        buf[11] = time.hour / 10 + '0';
        buf[12] = time.hour % 10 + '0';

        buf[8] = time.day / 10 + '0';
        buf[9] = time.day % 10 + '0';

        buf[5] = time.month / 10 + '0';
        buf[6] = time.month % 10 + '0';

        UDR0 = ' ';
        PORTB = ( time.second & 0x01 ) ? _BV(PORTB5) : 0 ;

}

void main (void) {
        // setup serial port
        // 9600, 8 bit, 1 stop, no parity, interupt handler on RX/TX
        UBRR0H = UBRRH_VALUE;
        UBRR0L = UBRRL_VALUE;
        UCSR0B = _BV(RXCIE0) | _BV(TXCIE0) | _BV(RXEN0) | _BV(TXEN0);
        UCSR0C = _BV(UCSZ01) | _BV(UCSZ00);

        // Timer1 top value (for autoreload)
        // Note: this is two byte operation
        //       it should be secured by atomic block
        //       when accessing while interupts are enabled
        OCR1A = 62500;

        // Time1, 1/256, autoreload, interupt on compare
        TCCR1B = _BV(CS12) | _BV(WGM12);
        TIMSK1 = _BV(OCIE1A);

        //setup "L" led and blink to indicate startup
        DDRB = _BV(DDB5);
        {
          uint8_t i;
          for(i = 0; i < 10; i++){
                PORTB = _BV(PORTB5);
                _delay_ms ( 50 );
                PORTB = 0;
                _delay_ms ( 50 );
          }
        }

        // enable interupts
        sei();

        // send "start" character;
        UDR0= 's';

        // all done, wait for interupt
        for(;;) sleep_mode();
}

Result

After 24 hours I get following result:

Measured time using Arduino UNO was +5 second off compared to stratum 1 NTP server.

It looks like crystal is of by ~926 Hz (approximately 0.006 % ).

Calculated frequency of crystal is 16,000,925.93 Hz (this may change over time and also is specific for board used during test). It is good for mCPU itself but not so good to measure time. Correction can be made by altering Timer1 TOP value (OCR1A) but precision is not guaranteed over time.

If your project have access to precise time source or precise time measurement is not required then this is easiest way how to track "daytime". If precision is required and there is no way how to access better time source then dedicated RTC board is recommended.