swRTC

I developed swRTC, a library to implement a software Real-Time Clock based on a timer.
The library is suitable for Arduino boards (2009/UNO/Mega) and for several Atmel microcontrollers:

  • Atmega640/1280/1281/2560/2561 and Arduino MEGA boards
  • Atmega48/88/168/328 and Arduino 2009/UNO boards
  • Attiny24/44/84
  • Attiny25/45/85
  • Attiny2313
  • Atmega644/1284 and Sanguino boards

The library sets up a timer to use as a counter and to have an overflow every 1 ms that calls an ISR The interrupt routine increments an internal counter and after 1000 increments it starts to update the time registers that keep the time.
The library adapt itself to the micro on which it runs because it need an 8-bit timer, so for every micro it choosed the correct timer. It can also run with different clock frequencies by computing the correct prescaler and starting value for the timer. At the moment, 1/8/16 MHz are supported.

There is also a function to introduce deltaT, a timing correction that can be used to correct some software (i.e. another library that uses interrupts) or hardware (i.e. when you use an external resonator or the internal oscillator, that aren't so accurate like a crystal) issues.

The library has been tested for a couple of months and it seems to work well. :wink:
Of course it can archive maximum precision only when you use an external crystal. The library has a limited consumption of memory and is a good replacement of external RTCs when the tolerance between the RTC and the real time is not a problem.

To download the lib and to read more about it, please go to:
http://www.leonardomiliani.com/?p=411&lang=en

I'm intruigued... What is the accuracy of it? I know other libraries using software timekeeping are extremely in-accurate, hence the use of RTCs.
Interesting project, though!

Onions.

Consider this: the internal oscillator of a micro is said to have an accuracy of +/-10% over the nominal frequency clock... External resonators have similar problems.

Using the swRTC on an Arduino UNO with a resonator will result in differences that can reach the value of minutes/day. But you can use the deltaT to try to adjust them. I think that with a 2009, that has an external crystal for the Atmega, you will reach more precision.

This lib is intended to be useful for people that need to track the time without the necessity to have a big accuracy.

Hi Onions,

i used and helped to test this swRTC, the DeltaT is a great idea, whit it you can adjust large error, so the only important think to do is a long test for 24hours, after this you know your error/day and start to adjust the deltaT
Whit a few test a reach the precision af a real RTC like a DS1307

to Leo: you forgotten to mention my preferred mcu, the Atmega8 on old ArduinoNG :slight_smile:

UPDATE:
new version 0.8.4, with suppot to timestamps.
New functions:

getTimestamp([epoch]);
returns a timestamp representing the actual time of the internal clock based since epoch (it can be 1900 or 1970, the latter is the UNIX epoch)

setClockWithTimestamp(timestamp[, epoch]);
this function sets the internal clock using a timestamp. If specified, epoch will be used.

EDIT:
to download it,
http://www.leonardomiliani.com/?p=411&lang=en

Hi Leo,

I really love swRTC. I recently added a getDeltaT method (and issued a pull request on github).
Reason is that I integrated swRTC with panstamp SWAP stack from Daniel Berenguer, and I need to be able to read that and report it over RF network. I didn't test it yet though.

I also added an overloaded setDeltaT method using a int instead of float because float is supposed to be quite inefficient. But the weird thing is that when I substitute it to your routine, I get about 300 bytes MORE with my sketch. Very strange. Anybody have an idea on why and what did I do wrong ?

I also noted that the maximum deviation supported is 1% (840.0 second a day). Is there something specific that defineds that as a hard limit ? Because it pretty much looks like my device (using the internal 8MHz clock) derives more than that, my latest calculation would say around 1512 seconds too much a day (or 1.75%). Can this limitation be relaxed to 2-3% without any side effect ?

(840.0 second a day). Is there something specific that defineds that as a hard limit

I haven't seen the code but if it uses a hardware timer then the best you can do is the accuracy of the CPU's oscillator. In most cases that's a crystal (not bad) or a ceramic oscillator (not great) but in your case the internal oscillator it rated (IIRC) at 10% from the factory so you're doing well.

Can this limitation be relaxed to 2-3% without any side effect ?

Not sure what you mean there, you can't make it LESS accurate unless you add random values in the code somehow, you can tune the internal oscillator and get MORE accurate, but I've not done that.


Rob

The internal oscillator of the 328p is factory set to aprox.(+/-10%) 8MHz at 3Volts and 25degC. So at 3V3 or 5V it will be far off. You may compensate for temperature as well (328p has got an internal diode for chip's temp measurement). In past I did some measurements and based on OSCCAL the internal oscillator ran from ~5MHZ-15.5MHz.

tochinet:
Hi Leo,

I really love swRTC. I recently added a getDeltaT method (and issued a pull request on github).
Reason is that I integrated swRTC with panstamp SWAP stack from Daniel Berenguer, and I need to be able to read that and report it over RF network. I didn't test it yet though.

I also added an overloaded setDeltaT method using a int instead of float because float is supposed to be quite inefficient. But the weird thing is that when I substitute it to your routine, I get about 300 bytes MORE with my sketch. Very strange. Anybody have an idea on why and what did I do wrong ?

Hi tochinet, sorry if I read and answer a little bit late :sweat_smile:
We had a msg exchange over Github. Just to inform you that the new code I've put online a week ago seems to me that is more efficent and generates a smaller code.
Can you confirm this?

I also noted that the maximum deviation supported is 1% (840.0 second a day). Is there something specific that defineds that as a hard limit ? Because it pretty much looks like my device (using the internal 8MHz clock) derives more than that, my latest calculation would say around 1512 seconds too much a day (or 1.75%). Can this limitation be relaxed to 2-3% without any side effect ?

That's only a choice of mine. I decided not to use bigger values.
With the last revision of swRTC I used an int type so you can extend the range between -16384 and +16383 tenths of second a day.
If you want a bigger range, try using a long int, but it doubles the RAM usage (4 bytes) and, probably, the size of the sketch.

Hi, leo:

First of all, thank you for developing and releasing such a great library.
I'm a beginner of Arduino and trying to build a LED clock using your swRTC library.
My LED clock is operated with Atmega8, and a 4-digit LED display is drived by MAX7219.
I also connected a DHT11 sensor for temperature and humidity report.
My sketch is running well in this hardware combination except one thing -- the deltaT value.
I found that the negative value of deltaT will be turned into positive spontaneously after calling rtc.setDeltaT() function.
I'm not sure if I made a stupid mistake in my sketch. Maybe you can give me some advice. Thank you!

#include <swRTC.h>
#include <LedControl.h>
#include <dht11.h>

#define DHT11PIN 2

#define SW_TimeSet_Pin 3
#define SW_H_Pin 4
#define SW_M_Pin 5
#define SW_S_Pin 6
#define SW_DeltaTSet_Pin 7

#define LedDataPIN 9
#define LedClkPIN 10
#define LedCsPIN 11
#define MAX7219Num 1

#define Analog_CdS_Pin 0

swRTC rtc; 
LedControl lc=LedControl(LedDataPIN,LedClkPIN,LedCsPIN,MAX7219Num);
dht11 DHT11;

byte HUR,hour12F,MIN,SEC,TenHURDigit,HURDigit,TenMINDigit,MINDigit,TenSECDigit,SECDigit;
byte HunDTDigit,TenDTDigit,DTDigit;
byte ReadT,ReadH,TenTempDigit,TempDigit,TenHumidDigit,HumidDigit;
byte CdS;
int DeltaT;
char TenHURChar;
boolean set_status = false;
boolean set_DeltaT = false;
boolean Blink = true;
boolean Bol_HUR = false;
boolean Bol_MIN = false;
boolean Bol_SEC = false;

void setup() {
    
     // Initial time value
     rtc.stopRTC(); //stop the RTC
     rtc.setTime(12,0,0); //set the time here
     rtc.setDate(23,4,2013); //set the date here
     rtc.startRTC(); //start the RTC

    lc.shutdown(0,false);
    lc.clearDisplay(0);
    
    pinMode(SW_TimeSet_Pin,INPUT);
    pinMode(SW_H_Pin,INPUT);
    pinMode(SW_M_Pin,INPUT);
    pinMode(SW_S_Pin,INPUT);
    pinMode(SW_DeltaTSet_Pin,INPUT);

}

void loop() {
     CdS = analogRead(Analog_CdS_Pin)/102;
     lc.setIntensity(0,CdS);
     
     HUR = rtc.getHours();
     MIN = rtc.getMinutes();
     LED_Display_Hour_Minute();
     
     Blink = !Blink;
     delay(200);

     while (digitalRead(SW_S_Pin)){  // display second
        MIN = rtc.getMinutes();
        SEC = rtc.getSeconds();
        LED_Display_Second();
     } 
   
    while (digitalRead(SW_M_Pin)){   // display temperature
        DHT11.read(DHT11PIN);
        ReadT = DHT11.temperature;
        LED_Display_temperature();
        delay(1000);
    } 
   
    while (digitalRead(SW_H_Pin)){  // display humidity
       DHT11.read(DHT11PIN);
       ReadH = DHT11.humidity;
       LED_Display_humidity();
       delay(1000);
    } 
   
   // Manual time adjust
    while (digitalRead(SW_TimeSet_Pin)) {
       set_status = true;
       LED_Display_Hour_Minute();
    
       while (digitalRead(SW_M_Pin)) {
          Bol_MIN = true;
          MIN = MIN++;
          if (MIN >59) MIN =0;
          LED_Display_Hour_Minute();
          delay(100);
       }
       while (digitalRead(SW_H_Pin)) {
          Bol_HUR = true;
          HUR = HUR++;
          if (HUR >23) HUR =0;
          LED_Display_Hour_Minute();
          delay(200);
       }
       while (digitalRead(SW_S_Pin)) {
          Bol_SEC = true;
          SEC = 0;
          LED_Display_Second();
          delay(300);
       }
     
     // DeltaT adjust
     while (digitalRead(SW_DeltaTSet_Pin)) {
        DeltaT = rtc.getDeltaT();
        LED_Display_DeltaT();
    
        while (digitalRead(SW_H_Pin)) {
          set_DeltaT = true;
          DeltaT = DeltaT+10;
          if (DeltaT > 8400) DeltaT = 0;
          LED_Display_DeltaT();
          delay(150);
        }
        while (digitalRead(SW_M_Pin)) {
          set_DeltaT = true;
          DeltaT = DeltaT-10;
          if (DeltaT < -8400) DeltaT = 0;
          LED_Display_DeltaT();
          delay(150);
        }
        while (digitalRead(SW_S_Pin)) {
          set_DeltaT = true;
          DeltaT = 0;
          LED_Display_DeltaT();
          delay(300);
        }
        if (set_DeltaT){
          rtc.setDeltaT(DeltaT); 
          set_DeltaT = false;
       }
     }
   }
   if (Bol_HUR | Bol_MIN | Bol_SEC){
     rtc.stopRTC(); //stop the RTC
     rtc.setTime(HUR,MIN,SEC); //set the time here
     rtc.startRTC();
     set_status = false;
     Bol_MIN = false;
     Bol_HUR = false;
     Bol_SEC = false;
   }
   else if (set_status) set_status = false;
  
}

//////////////////////////////////////////////////////////

void LED_Display_Hour_Minute(){ //LED Clock Display
    // Transform 24H to 12H
    if(HUR>12) hour12F = HUR-12; 
    else if(HUR == 0) hour12F = 12;
    else hour12F = HUR;
    
    // Let the Ten-Digit of Hour not to display '0' on LED  
    if(hour12F > 9) TenHURChar = '1';
    else TenHURChar = ' ';
     
    //   
     TenHURDigit = hour12F/10;
     HURDigit = hour12F % 10;
     TenMINDigit = MIN/10;
     MINDigit = MIN % 10;
     
     lc.setChar(0,0,TenHURChar,set_status);
     lc.setDigit(0,1,HURDigit,set_status | Blink); // F2481AH CB 
     lc.setDigit(0,2,TenMINDigit,set_status | Blink); // F2481AH CB
     lc.setDigit(0,3,MINDigit,set_status);
      
     if (HUR <12) lc.setLed(0,0,6,true); // AM sign
     else  lc.setLed(0,0,5,true); // PM sign
     delay(300);
}

void LED_Display_Second(){
      
   TenMINDigit = MIN/10;
   MINDigit = MIN % 10;
   TenSECDigit = SEC/10;
   SECDigit = SEC % 10;
      
   lc.setDigit(0,0,TenMINDigit,false);
   lc.setDigit(0,1,MINDigit,true); // F2481AH CB on
   lc.setDigit(0,2,TenSECDigit,true); // F2481AH CB on
   lc.setDigit(0,3,SECDigit,false);
}   

void LED_Display_DeltaT(){
   HunDTDigit = abs(DeltaT / 1000);
   TenDTDigit = abs(DeltaT /100 % 10);
   DTDigit = abs(DeltaT % 100 /10);
   
   lc.setDigit(0,1,HunDTDigit,false);// F2481AH CB off
   lc.setDigit(0,2,TenDTDigit,false);// F2481AH CB off
   lc.setDigit(0,3,DTDigit,true);
   
   if (DeltaT < 0)
      lc.setChar(0,0,'-',true);
   else
      lc.setChar(0,0,' ',true);
}

void LED_Display_temperature(){
    TenTempDigit = ReadT/10;
    TempDigit = ReadT % 10;
    
    if (ReadT<0) lc.setChar(0,0,'-',false);
    else lc.setChar(0,0,' ',false);
    lc.setDigit(0,1,TenTempDigit,false);
    lc.setDigit(0,2,TempDigit,false);
    lc.setChar(0,3,'c',false);
}

void LED_Display_humidity(){
    TenHumidDigit = ReadH/10;
    HumidDigit = ReadH % 10;
    lc.setChar(0,0,' ',false);
    lc.setDigit(0,1,TenHumidDigit,false);
    lc.setDigit(0,2,HumidDigit,false);
    lc.setChar(0,3,'h',false);
}

That's my mistake :sweat_smile:
I forgot to return the correct sign of deltaT with getDeltaT(). But deltaT was used correctly into the function.
BTW, now it's fixed in rev. 1.1.2:

Graynomad:
I haven't seen the code but if it uses a hardware timer then the best you can do is the accuracy of the CPU's oscillator.


Rob

Not really. I'm trying to explain the principle of why in another thread, here's a try from there:

It's this simple: if I measure a real hour to the second and find that instead of 3,600,000 millis() were counted it was 3,743,219, in the main sketch I would use 1040 millis per real second (interval) instead of 1000. That's all. 3,744,000 total an hour would be closer to the reality than 3,600,000.

So my BWD 1 second interval would be 1040 or some other tested against reality value.