Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

millis() using timer 0 #67

Open
MCUdude opened this issue Jul 15, 2018 · 11 comments
Open

millis() using timer 0 #67

MCUdude opened this issue Jul 15, 2018 · 11 comments

Comments

@MCUdude
Copy link
Owner

MCUdude commented Jul 15, 2018

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
Copy link
Owner Author

MCUdude commented Jul 15, 2018

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

@nerdralph
Copy link
Contributor

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.

@Jasdoge
Copy link

Jasdoge commented May 30, 2019

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

@imayoda
Copy link

imayoda commented Jan 6, 2020

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 :)

@nerdralph
Copy link
Contributor

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
Copy link
Contributor

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.

@imayoda
Copy link

imayoda commented Jan 6, 2020

thanks for the fast response and explanation 👍

@positron96
Copy link

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.

@MCUdude
Copy link
Owner Author

MCUdude commented Sep 10, 2021

@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 😄

@positron96
Copy link

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.

@aldolo69
Copy link

aldolo69 commented Jan 28, 2024

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();
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants