MicroCore icon indicating copy to clipboard operation
MicroCore copied to clipboard

millis() using timer 0

Open MCUdude opened this issue 6 years ago • 11 comments

It's almost ready for a new release, but before we do that I want to give the user the ability to use timer0 with millis(). This will most likely load the CPU more that the WDT interrupts, but on the other side millis will be much more accurate. My thought is that the user may select WDT or timer0 in the core_settings.h file.

Help is always welcome, so feel free to join 🙂

MCUdude avatar Jul 15 '18 14:07 MCUdude

It's also important that the "new" millis implementation is fairly accurate no matter if you're running the MCU at 8 or 9.6 MHz

MCUdude avatar Jul 15 '18 14:07 MCUdude

I've always disliked the Arduino core millis implementation, as it is quite bloated and all the ifdef code makes it messy. It's even trickier with the t13 since you only have timer0 for both millis and PWM. I've got a couple other MCU projects I'm working on right now, so I probably won't help much with this. You might get some ideas from a 1-second timer I wrote a few years back: http://nerdralph.blogspot.com/2014/07/writing-avr-interrupt-service-routines.html

I think a prescaler of 8 is a reasonable trade-off between PWM speed and millis overhead. To work well with different core clocks, you'll probably want to use an 8-bit overflow counter, adding a binary fraction that can be computed at compile time. Whenever that overflows, increment millis. Here's a rough outline: INC_INTERVAL = F_CPU/PRESCALE/256 OVFL_ADD = (uint8_t)256/INC_INTERVAL

In the timer0 overflow interrupt, add OVFL_ADD to the overflow counter, and if that counter overflows, add 1 to the millis counter. Remember to add 0.5 where necessary before dividing to avoid rounding error.

With this technique at 9.6Mhz millis gets incremented every 55th time the timer0 overflow ISR triggers, vs an ideal of 54.6133, so millis would be only 0.7% slow. Below 2Mhz this won't work since OVFL_ADD would be >255. You could go with a prescaler of 1, but that would mean a large percentage of CPU cycles being spent in the millis ISR.

If you sacrifice PWM, you could use CTC mode where the prescaler * count = 1ms, so at 1.2Mhz you'd use a prescaler of 8 and a compare/match count value of 150.

nerdralph avatar Jul 15 '18 16:07 nerdralph

Did this get implemented? I'd love to use millis, plus WDT for sleep mode, but can live without PWM.

Jasdoge avatar May 30 '19 02:05 Jasdoge

Hit the problem last night, when I realized that I can't use millis() + Wdt operations.. would be nice to have timer0 millis() + wdt operative to kill the tiny :)

imayoda avatar Jan 06 '20 03:01 imayoda

Did this get implemented? I'd love to use millis, plus WDT for sleep mode, but can live without PWM.

Sleep mode works fine with millis. https://gist.github.com/nerdralph/8af5ade84839f4cc3256faa470434db8

nerdralph avatar Jan 06 '20 11:01 nerdralph

Hit the problem last night, when I realized that I can't use millis() + Wdt operations.. would be nice to have timer0 millis() + wdt operative to kill the tiny :)

I can't say when I'll get to it. There's a few other issues I'd like to tackle first. And before I'd start coding, Hans and I would have to decide if this would replace millis using WDT, or if it should be another option in the core to choose which millis implementation.

nerdralph avatar Jan 06 '20 11:01 nerdralph

thanks for the fast response and explanation :+1:

imayoda avatar Jan 06 '20 13:01 imayoda

Hi. Has there been progress on this? I'm trying to seed the RNG by using WDT jitter and this clever trick: https://sites.google.com/site/astudyofentropy/project-definition/timer-jitter-entropy-sources. It occupies WDT interrupt (at start of the program), and I'd like to use millis as well.

positron96 avatar Sep 09 '21 12:09 positron96

@positron96 sorry, I haven't done anything more. Ideally, it should be implemented in assembly, just like the current WDT implementation, but I'm not very good at assembly, and I can't really force anyone to do it for me either 😄

MCUdude avatar Sep 10 '21 17:09 MCUdude

Hi, thank you for response and good work on the core!

After some consideration, I realized that I can utilize the same idea using millis() and timer0. It's not a good quality RNG and gives something like 10 different numbers, but is sufficient for the job.

positron96 avatar Sep 10 '21 17:09 positron96

i've spent some time on the subject because i need wdt and millis. this is the result, a blick by millis with wdt enabled: /************************************************************* aldo buson rework of the blink_using_timer0 example

timer0 used for millis counting instead of the watchdog timer let's suppose 1.2Mhz clock frequency prescaler set to 1024 so 1.2M/1024=1170hz, a little more then 1000 so millis is not a millisecond but something less. 0.8 milliseconds **************************************************************/

#include <avr/io.h> #include <avr/wdt.h>

volatile uint32_t __millis = 0;

int main (void) { wdt_enable(WDTO_8S); // Enable WDT with 8 second timeout

TCCR0B = _BV(CS02) | _BV(CS00); // clock frequency / 1024 1200000/1024=1170HZ TCNT0 = 0; // Set counter 0 to zero. interrupt appens at 256 so there is plenty of time for interrupt setup TIMSK0 = _BV(TOIE0); // Enable overflow interrupt at value 256 of timer 0 sei(); //Enable global interrupts

setup(); while (1) loop(); // Infinite loop }

//replace millis ISR(TIM0_OVF_vect) //Timer 0 overflow vector - this runs every time timer0 overflows { __millis++; TCNT0 = 255;//1 CLOCK TICK UNTIL NEXT INTERRUPT }

void setup() { pinMode(2, OUTPUT); }

void loop(void) { static uint32_t last = 0; if ((__millis - last) > 1170) { digitalWrite(2,digitalRead(2)==1?0:1);// PORTB ^= _BV(PB2); last = __millis; } wdt_reset(); }

aldolo69 avatar Jan 28 '24 06:01 aldolo69