Can I use millis() to increment a data type every millisecond..?

I would like to increment a data type (for eg. unsigned int counter) to increment every millisecond using millis() function. Is it practically correct..?

I would like to use the following code below

unsigned long previousMillis = 0; 
const long interval = 1; 
unsigned int counter = 0;

void setup() {
}

void loop() {
  unsigned long currentMillis = millis();

  if (currentMillis - previousMillis >= interval) {
    previousMillis = currentMillis;
    counter++;

// I would like to put few codes here so that they run only every millisecond.
  }
}

The idea actually came from using the timer 0 as I am used to other compiler (mplab)

if (TOIF){
 // all code goes here which needs to run once when the TOIF overflows at the defined speed

TOIF=0;
}

Did you try the code?
Why asking the thing which could be tested easily?

Yes.. It off course works fine. but I am worried what happen when millis() wraps up to 0

Nothing.
This code works fine before, during and after millis() rollover because an unsigned arithmetic.

(Do not forget define the interval as unsigned too)

2 Likes

Just be aware that the millis() value does not change 1000 times per second. It's more like 976 times per second, but on some interrupts the value is incremented by 2. So if you do the same thing to your variable, it will increment every millisecond on average, in the long run, but not in the short run.

1 Like

You chould be worried that millis() updates at 960Hz 16MHz/64/256= 976.5625Hz and occasionally increments by 2 to keep in sync:

unsigned long previousMillis = 0; 
const long interval = 1; 
unsigned int counter = 0;

void setup() {
  Serial.begin(115200);
}

void loop() {
  unsigned long currentMillis = millis();

  if (currentMillis - previousMillis >= interval) {
    if(currentMillis-previousMillis >1) { 
      Serial.print(currentMillis-previousMillis);
      Serial.print(' ');
    }
    previousMillis = currentMillis;
    counter++;

// I would like to put few codes here so that they run only every millisecond.
  }
}

... which gives:

2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 

Also, if you use the previousMillis += interval, it will give 1000 triggers/second by not skipping a step when millis() double-counts.

If you need the accuracy, try it with micros() to catch every millisecond (with an occasional miss by 0.8% error):

unsigned long previousMillis = 0; 
const long interval = 1000; 
unsigned int counter = 0;

void setup() {
  Serial.begin(115200);
}

void loop() {
  unsigned long currentMillis = micros();

  if (currentMillis - previousMillis >= interval) {
    if(currentMillis-previousMillis >interval){
    Serial.print(' ');
    Serial.print(currentMillis-previousMillis);
    }
    previousMillis += interval;
    counter++;

// I would like to put few codes here so that they run only every millisecond.
  }
}
1 Like

But if interval = 1, then you don't need an additional counter, millis() itself would be the counter. It start counting when your program starts.
And if interval is different than 1, you can just divide millis() by the interval.

If you need to execute something once per millisecond, then check when previousMillis != millis()

That will only get you ~977 somethings per second.

unsigned long previousMillis = 0; 
const long interval = 1; 
unsigned int counter = 0;

unsigned long lastSecondMs = 0;

void setup() {
  Serial.begin(115200);
}

void loop() {
  unsigned long currentMillis = millis();

  if (currentMillis - previousMillis >= interval) {
    if(currentMillis-previousMillis >1) { 
      Serial.print(currentMillis-previousMillis);
      Serial.print(' ');
    }
    previousMillis = currentMillis;
    counter++;

// I would like to put few codes here so that they run only every millisecond.
  }

  if(currentMillis - lastSecondMs >= 1000){
    lastSecondMs +=1000;
    Serial.println(counter);
  }
}
2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 977
2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1954
2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2930
2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3907
2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 4883
2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 5860

Those are 977 somethings + 23 skips or 976 somethings + 24 skips.

1 Like

Yes, even analog data at once per ms has lots of room to process.

However, millis() is literally +/- 1. When close counts, use micros()!

PS, you can use unsigned int instead of unsigned long to time that.

unsigned int mytimer;

mytimer = micros(); // gets the low 16 bits, max interval is 65 ms.. forget the 535 micros left over!

2 Likes

Interesting, I didn't know. But with micros() I think that it is more precise.
Actually this week I did some tests with the oscilloscope because I was curious to see how precise and fast can be the loop generating a square wave: Pulse output timing accuracy? - #22 by gromit1

Using micros the error seems to be below 4 us.
I tested also the loop in 'free run' and it takes about 3.2 us to switch. The overhead of the few instructions, interrupts and other MCU entertainments.

1 Like

They get micros() on an Uno/AVR from the 16MHz clock divided by the Timer0 /64 prescaler, so Timer0's TCNT0 only counts up at 16000000/64= 250000Hz or once each 4us. So micros() can't do better than 4us resolution.

If you could make use of a Timer with a /1 prescaler for your problem, you might be able to get higher resolution results.

To get millis() to increment exactly once per millisecond, you just need to have timer0 interrupt at 250 overflow rather than 256 (actually 249 rather than 255). Here's a blink.ino that uses that method.

/*
This features a replacement for the ISR(TIMER0_OVF_vect)
interrupt that drives millis(). It disables the OVF
interrupt, enables the COMPA interrupt, sets OCR0A to 249,
and changes Timer0 to CTC mode.  This results in an
interrupt every 250 timer clock cycles, which is exactly 1ms
for a 16MHz crystal.  For 8MHz, OCR0A is set to 124.  The new
ISR increments millis, but since the interrupt rate is exactly
correct, no periodic double increment of millis is needed.

Using this code probably means you can't do any analog
writes that use Timer0, which would include pins 5 and 6.

Millis() should work as normal at 16MHz or 8MHz.
The effect on micros() is unknown.
*/

extern volatile unsigned long timer0_millis;   //these defined in wiring.c
extern volatile unsigned long timer0_overflow_count;

byte MILLIS_INCB = (64 * 250) / (F_CPU / 1000);  // ms to 250 ticks

const int cycleTime = 500;                 // flash LED every second
unsigned long oldMillis = millis();
unsigned long newMillis = 0;

void setup() {                             //Set up alternate interrupt
                                           //   at 249 on timer0

  cli();                                   // disable interrupts while doing this

  TCCR0A = 0;                              // set entire TCCR0A register to 0
  TCCR0B = 0;                              // same for TCCR0B
  TCNT0  = 0;                              // initialize timer0 count to 0

  OCR0A   = (250/MILLIS_INCB) - 1;         // set top of counter (249 for 16MHz, 124 for 8MHz)
  TIMSK0 &= ~bit(TOIE0);                   // disable overflow interrupt
  TCCR0A |= bit(WGM01);                    // turn on CTC mode
  TCCR0B |= (bit(CS01)+bit(CS00));         // Set CS00&CS01 bits for prescaler = 64
  TIMSK0 |= bit(OCIE0A);                   // enable timer compare interrupt

  sei();                                   // enable interrupts

  pinMode(13,OUTPUT);
  digitalWrite(13,HIGH);

}


void loop() {                               // flashes LED for 1/2 second
                                            //    every second

  newMillis = millis();
  if ((newMillis - oldMillis) == cycleTime) {
    oldMillis = newMillis;
    digitalWrite(13,!digitalRead(13));    // invert pin 13 state
  }
}


ISR(TIMER0_COMPA_vect) {                    // this is the new ISR - much
                                            //   simpler than original

  timer0_millis++;
  timer0_overflow_count++;                  // probably not needed

}

With non-blocking code you can update a few digital pin's status 50 or more times a ms on average. Why a pin needs 1 ms timing should be interesting...

  unsigned long currentMicros = micros();

  if (currentMicros - previousMicros >= interval) {
    previousMicros += interval;
    counter++;
    Serial.println(counter);


  }

why is the increment of counter faster and not every millisecond..?
What am I missing in the calculation..?

Sorry.. I mean slower..
every second the counter should increment by 1000 but that is not the case in practical.

It seems like it is incrementing by 100 every millisecond.

Sorry, I was sloppy -- I was trying to show that the same exact math works for millis() and micros() by just subtituting in micros() and using 1000x the interval, but micros() and 1000x doesn't double-step milliseconds because it counts in (4)microseconds.

Heres a better version that actually prints counter, can work with millis or micros, and can ignore the double-steps or not:

unsigned long previous = 0;
const long interval = 1;
unsigned int counter = 0;
const bool IgnoreSkips = true; // <<<<<<<<<< change this value

void setup() {
  Serial.begin(115200);
}

void loop() {
  unsigned long current = millis();

  if (current - previous >= interval) {
    if (current - previous > interval) { // not exactly 1ms
      Serial.print(' ');
      Serial.print(current - previous);
    }
    if (IgnoreSkips) {
      previous = current;  // reset clock
    } else {
      previous += interval; // advance one step
    }
    counter++;

    // I would like to put few codes here so that they run only every millisecond.
  }
  report();
}

void report(void) {
  static uint32_t last = 0;
  const uint32_t interval = 1000;
  uint32_t now = millis();
  if (now - last < interval) return;
  last += interval;
  Serial.print("\ntime:");
  Serial.print(now);
  Serial.print(" counter:");
  Serial.print(counter);
  Serial.println();
}

In words, the problem with millis() is that the underlying value it returns changes at 976.5625Hz, sometimes increasing by 1 and sometimes increasing by 2, in a careful balance so that on average, it increases 1000 per second. If you increment counter each time millis changes, you get, on average, 976.5625 changes per second. On average, 23.4375 of those changes were +2.

The previous+= interval trick works by only advancing previous one step forward when you are increasing counter++. Afterwards, in cases where millis() advanced +2 instead of +1, the very next iteration immediately triggers the if statement again and also increments counter++ again.

Counter wasn't being printed in my earlier code, just imperfectly 1.000ms elapsed times.

Things get more complicated than I thought.

I am using an ESP32 and I believe it has 4 independent timers. So can I use any one of the timers to presale it to tick every 1 millisecond.
I want to do it as I want to keep track things like button presses so that I can create long press and short press and many other things..

Using millis() becomes very difficult.

This solution seems to be better for my application.

You don't need to count milliseconds for that. The main reason is....millis() already does this for you. It increments by 1 every millisecond (on average) and you can use the millis() function to get that timestamp. It seems like you're just duplicating its behavior.

For debouncing and determining long vs. short button presses, just use millis() to receive a timestamp and store that in a variable. Then when a later action happens (e.g. button released), get another millis() timestamp and compare with the previous one. The same as explained in the many 'blink without delay' examples etc.

If you work on a platform that doesn't have millis(), a timer can indeed be configured to replicate its behavior. However, ESP32 Arduino platforms have fully functional millis(). And even on other non-Arduino platforms, you often have an equivalent, such as SysTick on STM32 (and others).

1 Like

Ok, for completeness I have also tested that (is not that I don't trust you, but there is no cure for curiosity :nerd_face:) . So I have measured the time that takes millis() to switch its value, regardless of the returned value:

void loop()
{
  curr_time = millis();
  if( curr_time != prev_time){
    digitalWrite( PINOUT, high_low);
    high_low = !high_low;
    prev_time = curr_time;
  }
}

And effectively the frequency is... 976.6 Hz, instead of 1 KHz. So you are completely right:

Then I have done the same test but changing millis() by micros(), and the freq is then 128 Khz. So it switches once every 7.8 μs, aprox.

The way they compensate it with the returned value is good enough for this MCU, I think. With the tests I did previously you can get a precision of about +/- 4 μs.
At the end if you need higher precision nowadays, don't use this relative slow and old MCU. Or use an external timer module.

2 Likes