using millis() function on atmel studio as uint64_t

Go To Last Post
21 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hello,I am using attiny84 microcontroller and atmel studio. And I am using the millis function in my project. I don't want the millis() function to reset after 50 days. I have to use uint64_t instead of unsigned long.I will make all variables that write unsigned long int below, uint64_t.There are 4 variables.I did not see any problems in my experiments, but I want to ask maybe there is something I do not know.If I change the original millis code to uint64_t, will it be a problem?To answer this, you can check the millis settings in the int main () part of my code.
 

Orginal millis code and millis settings for my microcontroller in my project:
 

#define F_CPU 8000000UL
#include <stdlib.h>
#include <stdio.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <math.h>
#define clockCyclesToMicroseconds(a) ( ((a) * 1000L) / (F_CPU / 1000L) )
#define MICROSECONDS_PER_TIMER0_OVERFLOW (clockCyclesToMicroseconds(64 * 256))
#define MILLIS_INC (MICROSECONDS_PER_TIMER0_OVERFLOW / 1000)
#define FRACT_INC ((MICROSECONDS_PER_TIMER0_OVERFLOW % 1000) >> 3)
#define FRACT_MAX (1000 >> 3)
volatile unsigned long int timer0_overflow_count = 0;
volatile unsigned long int timer0_millis = 0;
static unsigned char timer0_fract = 0;
unsigned long int starttime,endtime;

ISR (TIM0_OVF_vect)
{
    unsigned long int m = timer0_millis;
    unsigned char f = timer0_fract;

    m += MILLIS_INC;
    f += FRACT_INC;
    if (f >= FRACT_MAX) {
        f -= FRACT_MAX;
        m += 1;
    }

    timer0_fract = f;
    timer0_millis = m;
    timer0_overflow_count++;
}

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

int main(void)
{
    //Settings for timer
    TCNT0 = 0;
    TCCR0B |= (0<<CS02) | (1<<CS01) | (1<<CS00);
    TIMSK0 = (1<<TOIE0);
    sei();

    while(1)
    {
        //clasic millis code is below

        starttime = millis();
        endtime = starttime;

        while ((endtime - starttime)<=60000)
        {

             endtime = millis();

             //the code to run...

        }
    }
}

 

Last Edited: Mon. Aug 24, 2020 - 11:31 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Certainly millis will overflow in about 50 days.
By the way do you want to measure more than 50 days?
Otherwise 64bit is not needed.

 

while ((endtime - starttime)<=60000)

This code works fine even through an overflow.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The subtraction of "starttime" from the "endtime" time will always work, for a duration that is 60000. It will work for durations up to 4,294,967,296. You probably want to read up on how two's complement works, since that is how the processor does subtraction, and the reason this trick works.

 

https://en.wikipedia.org/wiki/Two's_complement#Subtraction

 

Consider when the starttime value is at 2147483648 ('0x80000000'), and I count 4,294,967,296 ticks through rollover to where "endtime" is at 2147483647 ('0x7fffffff'). If I subtract (endtime - starttime) I get the correct value (4,294,967,296). One more tick though and I got a problem.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

yes it works correctly, but I don't want any problems with other codes.so I want to change this variables as uint64_t:

timer0_overflow_count,

timer0_millis,

endtime,

starttime
millis function

I did not find a problem when I made changes like this. But why is the original millis code set to unsigned long instead of uint64_t?Does a problem occur?Please look to my millis settings.

Last Edited: Mon. Aug 24, 2020 - 02:29 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

hrn97ta wrote:
But why is the original millis code set to unsigned long instead of uint64_t?

Probably because it is a sensible compromise.

If it used uint16_t, the maximum time delta you can uniquely measure between event A (starttime) and event B (endtime), assuming you know that B is later than A, would be 65535 ticks. It doesn't matter if the count wraps during this period as long as you do uint16_t delta = endtime - starttime, but you have a fundamental limit at 65535 ticks, ie. 65535ms or 65 seconds. There will be applications where this is too short.

 

So go up to uint32_t. You can now uniquely measure a time delta up to 4294967295ms, which is 49.7 days.

 

In your case, unless you need to be measuring time delta more than 49.7 days, you don't need 64 bits.

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

 

You are assuming that, outside of a controlled desktop environment,  an ATtiny84 will go longer than 50 days without an asynchronous-with-unknown-cause reset.

If the briefest reset happens, your timer0_millis count goes back to zero.

  Do you know how to do I2C on the tiny84? If so then I suggest that any time interval that is longer than 50 days should rely on a Real-Time Clock module board based on the DS3231 IC.

These sell on eBay for about $1.30 USD each.  They are accurate to seconds-per-year and have several alarms that can be set down to the minute for intervals as long as years.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

An external chip is not likely to be useful.

'Twould add both hardware and software complexity.

OP's purpose is not clear.

 

Measuring 50 day intervals to the millisecond requires better than a ppb.

OP's crystal is unlikely to cut it.

Assuming a task OP's hardware could actually accomplish,

OP more likely wants to measure hour intervals more than 50 days in the future.

For that, OP just needs to trust unsigned long arithmetic.

 

BTW the high bit of an unsigned type is  (timer_t)-1 - (((timer_t)-1)>>1)

 

Iluvatar is the better part of Valar.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

my goal is to get things done over a period of time.My concern is that the reset of the millis function occurs in a loop.I know it's ok for this code again. But can a 64-bit number be used to ensure it is never reset? I was wondering. Is this okay? Why is the original millis function used unsigned long instead of uint64_t?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

hrn97ta wrote:
Why is the original millis function used unsigned long instead of uint64_t?

MrKendo wrote:
Probably because it is a sensible compromise.

 

Letting the smoke out since 1978

 

 

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

hrn97ta wrote:
my goal is to get things done over a period of time.My concern is that the reset of the millis function occurs in a loop.I know it's ok for this code again. But can a 64-bit number be used to ensure it is never reset? I was wondering. Is this okay? Why is the original millis function used unsigned long instead of uint64_t?

These are MICRO controllers.  Why do you want to burden your whole app with 64-bit arithmetic?  Why do you want to make an ISR running many times per second take a significant amount of resources?  100s of cycles instead of 10s.

 

If it were my app and a clean sheet, I'd probably never extend millis.  I'd makeit it a 16-bit seconds counter, probably.  Can't quite get 86400 seconds into 16 bits, so then make an hours counter.

 

The time arithmetic when needed should be occasional.  [and probably I'd investigate how to use time.h implementations]

 

 

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

hrn97ta wrote:
my goal is to get things done over a period of time.
Not all that informative.

I infer that printing the time of year in milliseconds is not required,

but beyond that, I am not sure.

What time-related tasks do you need to accomplish?

If the next task is always at most 47 days away, 32 bits is enough.

Iluvatar is the better part of Valar.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

hrn97ta wrote:
But why is the original millis code set to unsigned long instead of uint64_t?

 

Options include uint16_t, uint32_t, and uint64_t. They each have a burden that is a huge increase. The question is how much work (time) is spent in the ISR that tracks time. The desire is to keep the ISR short, but also reasonable. My experiments some time back suggest that it takes over 50 machine cycles to enter an ISR and the another 50+ to return (I don't recall the actual numbers it was an experiment with input capture).  If I can keep the ISR to about 100 machine cycles that would seem best, that way, I am allowing some work to be done, but not causing my application thread to block for overly long. I am not sure if a uin64_t would stall my application enough to be a concern, but it is easy enough to track a longer time duration in the main thread, so why add the unnecessary blocking delay to the timer ISR (with a uin64_t).

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

>> I was wondering. Is this okay? 

 

It's ok (for certain small values of ok) but not recommended unless you have an absolute requirement for it. It's rather like reading the same value from EEPROM every time around the main loop. Perfectly ok, but there are good reasons not to do it, and probably a better way to achieve the same end.

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Options include uint16_t, uint32_t, and uint64_t.

With slightly more effort, you could implement a 1-byte overflow counter (40 bits total - about 34 years worth of milliseconds), and only have it accessible "upon request", which would make things much more efficient, and more backward compatible as well.  You wouldn't have to double the size of every bit of code that uses the millisecond count.

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

>yes it works correctly, but I don't want any problems with other codes.so I want to change this variables as uint64_t:

 

Your 'other codes' will have the same problem unless they are informed they are now dealing with 64bits. If the 'other codes' do not know how to do time calculations correctly, then they will require fixing no matter if you use a 32 or 64bit millis var. They either need to be changed to deal with 64bits, or changed to handle time calculations correctly, and as long as a change is needed, just fix the calculation instead of moving everything to 64bits.

 

For the example below, the otherCodes will have to switch to using uint64_t if that is what your milli var now uses (and you do not want to change the way the time is calculated), or you simply correct the way it does its time calculations. Something has to change or else it is still using 32bits, and doing it incorrectly.

 

You can also get rid of 'timer0_overflow_count' unless you have some need to keep a 2048us count.

 

/*-----------------------------------------------------------
  'other codes' doing incorrect time calculation
   we want to delay by ms, but will be a problem since
   the time is calcuated assuming it never overflows
-----------------------------------------------------------*/
void otherCodes(uint32_t ms){ //assume ms=1000
    uint32_t starttime = millis(); //let's say is 0xFFFFFF00

 

    //wrong
    uint32_t endtime = starttime + ms; // = 744
    while( millis() < endtime ){} // 0xFFFFFF00 < 744 ? NO
    //we did not delay 1000ms because calculation is wrong

 

    //correct
    while( millis() - starttime < ms ){}

    //  0xFFFFFF00 - 0xFFFFFF00 = 0, 0 < 1000 ? yes
    //  0x00000001 - 0xFFFFFF00 = 0x0101, 257 < 1000 ? yes 
    //  0x000002E8 - 0xFFFFFF00 = 0x03E8, 1000 < 1000 ? no
    //we waited 1000ms because we took advantage of
    //unsigned arithmetic to handle the oveflow
}

 

https://godbolt.org/z/qWor5j

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks everyone for the answers.if the millis function resets in the loop ; 
 

starttime = millis();        //starrtime=4294949296 18 seconds to reset
        endtime = starttime; //endtime  =4294949296 

        while ((endtime - starttime)<=60000)
        {

             endtime = millis();       //If the millis function is reset at this place,endtime=0 so,while Condition expression
                                       //((0-4294949296)<=60000)   

             //the code to run...

        }

        //

((0-4294949296)<=60000)  I was worried about the result of this statements.(the number zero here is increasing over time. like 1000,2000...)
But 0-4294949296=18000
(1000-4294949296)=19000
(2000-4294949296)=20000
so no problem occurs.

but I also use millis loops nested.How would it be to reset the attiny microcontroller to eliminate the possibility of risk?Example If millis()==4294949296,than hard reset attiny microcontroller.How can I simply reset the attiny microcontroller in this way?The disadvantage here could be to constantly control the millis function?

Last Edited: Wed. Aug 26, 2020 - 08:44 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Silly question but unless you really need 1ms resolution over the entire 50 day period why don't you simply slow down (prescale) the timer so it ticks less often and thus the count will last for a longer period?

 

In simple maths even if you slowed from 1ms to 10ms then 50 days would become 500 days.

 

(though of course scaling tends to be binary divisors so rather than 1ms to 10ms it would more likely be 1ms to 8ms or 16ms or 32ms or 64ms or whatever)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

>but I also use millis loops nested.How would it be to reset the attiny microcontroller to eliminate the possibility of risk?

 

Not quite sure what the 'risk' is about as its hard to imagine anything close to 49 days is needed for any single timing event. If you need to blink an led for only 90 days, or need to blink an led 90 days from now, you may want to get a seconds count going, along with an accurate clock source, and a way to make sure you do not lose power (in other words an rtc).

 

For what you already have, you could make it easier to use-

 

typedef struct {
    uint32_t start;
    uint32_t ms;
    bool expired;
} delay_t;

 

bool isExpired(delay_t* d){

    //if ms==0, or not expired and is now expired, expire
    if( d->ms == 0 || (!d->expired && (millis() - d->start >= d->ms)) ){
        d->expired = true;
    }
    return d->expired;
}

void delayRestart(delay_t* d){
    d->start = millis();
    d->expired = false;
}

void delaySet(delay_t* d, uint32_t ms){
    d->ms = ms;
    delayRestart(d);
}

delay_t delay1;
delay_t delay2;
delay_t delay3;

delay_t delay4;

 

int main{

    ...

    delaySet( &delay1, 500 ); //500ms

    delaySet( &delay2, 60000 ); //60 seconds

    delaySet( &delay3, 600000 ); //10 minutes

    delaySet( &delay4, 86400*30*1000ul ); //30 days

 

    while(1){

        if( isExpired(&delay1) ){ toggleLed(); delayRestart( &delay1 ); } //blink led 1Hz

        if( ! isExpired(&delay2) ){ /* do something that takes ~short period of time */ } //run this code for 60 seconds

        if( isExpired(&delay3) ){ delayRestart(&delay2); delayRestart(&delay3); } //restart 60 second task every 10 minutes

        if( isExpired(&delay4) ){ mcuReset(); } //reset mcu every 30 days

    }

 

}

 

As long as any delay is less than ~49 days, you have no 'risk'.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

To be clear, if now is known to be later, but not too much later, than earlier,

then now-earlier will give the interval between now and earlier even if

earlier> now.  That is how unsigned arithmetic works.

Regardless of their values now-earlier will never produce a negative value.

Unsigned arithmetic does not produce negative values.

 

If you wish to know which of timeA and timeB occurs first

one can test the high bit of timeB-timeA.

If timeA and timeB are known to be close enough together,

then timeA is later iff the sign bit is set.

 

Note that I have assumed unsigned arithmetic.

If the time type is smaller than int,

the arithmetic will be done on ints.

Iluvatar is the better part of Valar.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:
Silly question but unless you really need 1ms resolution over the entire 50 day period why don't you simply slow down (prescale) the timer so it ticks less often and thus the count will last for a longer period?

 

Good point, Arduino is using a prescale factor to 64 for Timer 0, that could be increased.

 

https://github.com/arduino/ArduinoCore-avr/blob/master/cores/arduino/wiring.c#L247

 

As it is the Timer0 ISR will fire every 16384 clock cycles. which gives an ISR update about once per millisecond with a 16MegHz clock. I am not going to look at the prescale options but maybe 256 is one, which would have the ISR fire every 65536 clock cycles and the total role over time would be almost 200 days.

 

For reference the Arduino Timer0 overflow ISR is at this link (it keeps track of the fractions of a millisecond)

 

https://github.com/arduino/ArduinoCore-avr/blob/master/cores/arduino/wiring.c#L45

 

 

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

by Zak Kemble

GitHub - zkemble/millis: Lightweight millisecond tracking library

[second paragraph]

Even though Arduino has its own millis() time keeping, this library may be handy if running at clock frequencies at or below 8MHz or for timing for longer than 50 days.

 

"Dare to be naïve." - Buckminster Fuller