Update Millis and Delay

Doing some test, it was discovered that the “millis()” function was wrong. It was returning a value in milliseconds of the running time but 33% faster. So if we ask, for example, for a delay of 1 second, what we were actually getting was 0.75 second.

Analyzing the LithneDuino files, it was discovered that the millis() function works in the following way:

ISR(RTC_OVF_vect)
{
rtc_millis = rtc_millis+4;
}
unsigned long millis()
{
unsigned long m;
uint8_t oldSREG = SREG;
// disable interrupts while we read rtc_millis or we might get an
// inconsistent value (e.g. in the middle of a write to rtc_millis)
cli();
m = rtc_millis;
SREG = oldSREG;
return m;
}

This code can be found in the file (line 60): “LithneDuino\hardware\xmegaduino\cores\xmega\wiring.c”

So, the millis function uses the Real-Time Counter to provide a interruption that will add 4 to rtc_millis. Initial value of rtc_millis is zero.

At the same file, it was find how the internal registers are preset. The following values for the registers are shown below:

OSC.CTRL.SCLKSEL = 0b(1xx) //enable the internal clock of 32.768kHz
CLK.RTCCTRL = 0b((010)1) //choose 1024Hz from the 32.768kHZ crystal oscillator
RTC.CTRL = 0b0001 //prescaler of 1
RTC.PER = 0;
RTC.CNT = 0;
RTC.COMP = 0;

The first 3 registers were explained in the commentary. The last 3 ones control the interruption frequency. RTC.CNT counts every clock pulse and every time it reaches RTC.PER or RTC.COMP, the register becomes zero again. If it reaches RTC.PER, the interruption RTC_OVF_vect is activated.      

But the problem is that PER and CNT are the same, so it is really hard to define what is the interruption frequency.

The first tool it will be used to solve this problem is change the RTC clock frequency. 1024Hz is too little, so I will choose to work with the full internal frequency of 32.768 kHz:

CLK.RTCCTRL = CLK_RTCSRC_RCOSC32_gc | CLK_RTCEN_bm;//32,768kHz
CLK_RTCSRC_RCOSC32_gc is defined as 0x06.

With this frequency, the value of PER is calculated using the formula:

How we want a time of 4ms, PER will be 129. We subtract 2 of PER because, from the datasheet, it took two clock pulses to update the flag value.

RTC.PER = 129;
RTC.CNT = 0;
RTC.COMP = 129;

Doing some more tests, it can be noticed that the delay functions and millis() are much more precise, but it is still not perfect. The precision was calculated as 99,95%.

Code parts coppied to help in the problem search solution!

 

volatile unsigned long millis_count = 0;
volatile unsigned long rtc_millis = 0;
unsigned long millis()
{
unsigned long m;
uint8_t oldSREG = SREG;
// disable interrupts while we read rtc_millis or we might get an
// inconsistent value (e.g. in the middle of a write to rtc_millis)
cli();
m = rtc_millis;
SREG = oldSREG;
return m;
}
ISR(RTC_OVF_vect)
{
rtc_millis = rtc_millis+4;
}
/* Turn on internal 32kHz. */
OSC.CTRL |= OSC_RC32KEN_bm;
do {
/* Wait for the 32kHz oscillator to stabilize. */
} while ( ( OSC.STATUS & OSC_RC32KRDY_bm ) == 0);
/* Set internal 32kHz oscillator as clock source for RTC. */
CLK.RTCCTRL = CLK_RTCSRC_RCOSC_gc | CLK_RTCEN_bm;//1kHz
#define OSC_PLLEN_bm 0x10 /* PLL Enable bit mask. */
#define OSC_PLLEN_bp 4 /* PLL Enable bit position. */
#define OSC_XOSCEN_bm 0x08 /* External Oscillator Enable bit mask. */
#define OSC_XOSCEN_bp 3 /* External Oscillator Enable bit position. */
#define OSC_RC32KEN_bm 0x04 /* Internal 32.768 kHz RC Oscillator Enable bit mask. */
#define OSC_RC32KEN_bp 2 /* Internal 32.768 kHz RC Oscillator Enable bit position. */
#define OSC_RC32MEN_bm 0x02 /* Internal 32 MHz RC Oscillator Enable bit mask. */
#define OSC_RC32MEN_bp 1 /* Internal 32 MHz RC Oscillator Enable bit position. */
#define OSC_RC2MEN_bm 0x01 /* Internal 2 MHz RC Oscillator Enable bit mask. */
#define OSC_RC2MEN_bp 0 /* Internal 2 MHz RC Oscillator Enable bit position. */
CLK_RTCSRC_RCOSC_gc = (0x02<<1), /* 1.024 kHz from internal 32.768 kHz RC oscillator */
#define CLK_RTCEN_bm 0x01 /* Clock Source Enable bit mask. */
#define RTC_PRESCALER_gm 0x07 (0b0111)
RTC_PRESCALER_DIV1_gc = (0x01<<0)
RTC.PER = 0;//1ms
RTC.CNT = 0;
RTC.COMP = 0;
RTC.CTRL = ( RTC.CTRL & ~RTC_PRESCALER_gm ) | RTC_PRESCALER_DIV1_gc;
/* Enable overflow interrupt. */
RTC.INTCTRL = ( RTC.INTCTRL & ~( RTC_COMPINTLVL_gm | RTC_OVFINTLVL_gm ) ) | RTC_OVFINTLVL_LO_gc | RTC_COMPINTLVL_OFF_gc;
OSC.CTRL.SCLKSEL = 0b(1xx) -> enable the internal clock of 32.768kHz
CLK.RTCCTRL = 0b((010)1) -> choose 1024Hz from the 32.768kHZ crystal oscillator
RTC.CTRL = 0b0001 -> prescaler of 1