How to Use Arduino millis(): How it works and How to use it. Create Delays, One-Shots and simple Schedulers with simple analysis of millis() code; Plus - Find out why it Lies (a bit)!

Arduino millis():

  • How to create non blocking delays using millis().

  • How to make simple event schedulers.

  • Find out exactly how millis() works including empirical and simulation results.

  • Unlike the delay() function, the millis() function does not stop the processor.

  • Find out why millis() will never output the value 42ms (or approx. multiple of it)!

  • Why does millis() jitter in its output?

The millis() function returns milliseconds since reset. On this page you can find out how to use it effectively for non blocking event timing and delays, and scheduling as well as learning how it works in detail.

Using Arduino millis as a Delay Timer

The millis() function returns the current time in milliseconds (1/1000th of a second) from when you powered up the board (or reset it). It gives you a way of measuring time from within your program, which is quite different to the delay() function that gives no feedback about time at all.

Warning: Arduino millis uses a timer interrupt, interrupts must be on. They are on by default but you may need to turn them off when using pulsein.

How to use and understand the millis function

Measuring a time period using millis(), is simply a matter of comparing current time to the time value stored in a variable. As you go round a loop you continuously perform a simple bit of maths: 

millis() - stored_time

This gives you the elapsed time in milliseconds from the "stored time value".

This action is shown in the pseudo code below:

   // An event happens
   if (event==true) stored_time = millis();
   elapsed_time = millis() - stored_time;

The above pseudo code snippet results in the variable "elapsed_time" measuring time in milliseconds since the event occurred. An event could be a button press or some action from another part of the program.

Perhaps you want to send out a serial message at one second intervals so in this case the event would be message transmission and you would wait until elapsed time was greater than 1000 (1000ms = 1 second) before sending the next serial message (and storing the time again).

The millis() function is driven by a millisecond timer interrupt that increments an unsigned long every time it activates and just returns the value of that variable.

Arduino millis LED Flashing

The following example shows you how to use millis() to give a non blocking delay. A non blocking delay is a type of delay that allows other code to operate even though there is a delay operation elsewhere. This is very different to using the function "delay()" where your code stops processing (except for interrupts) and does nothing useful for the duration of the delay period.

The pseudo code shown below gives a non blocking delay of 500ms and this delay is repeatedly triggered. This shows you how to create an Arduino millis timer that can have a delay time of any length (up to 49.7 days).

    if ( (millis()-oldtime) > 500) {
       oldtime = millis();

      // Do something every after 500ms

For an action every 12 hours you would use a delay interval of (12 * 60 * 60 * 1000) = 43200000ms replacing 500 with 43200000L (The L indicates a long value to the comiler). For days you need more milliseconds e.g. 3 days would require (3 * 24 * 60 * 60 * 1000) =259200000L again replacing 500 with 259200000L. To stop the repetition of the action see here.

Example 1 : Non Blocking Delay code

#define LED 13

void setup (void) {

void loop(void){
static uint8_t tog=0;
static uint32_t oldtime=millis();

    if ( (millis()-oldtime) > 500) {
       oldtime = millis();

       tog = ~tog; // Invert
       if (tog) digitalWrite(LED,HIGH); else digitalWrite(LED,LOW);


Code Operation : Arduino millis as delay operation

The Arduino code above shows the loop function which, in this case, is the only code required; Make the contents of the setup() function empty as you don't need to initialise anything i.e. you don't need to start the serial port or SPI interfaces in this instance.

Loop Code operation : For Non Blocking delays

The value of oldtime is set to a value of millis() at the start. if the value of millis(), which is increasing every millisecond, is greater than 500 counts above oldtime then the conditional expression in the if statement becomes true. This means 500 milliseconds have past since the value of oldtime was set i.e. a delay of 500ms.

Within the body of the if-statement the LED is toggled from its previous state. This means the LED is flashing at a rate of 1Hz (500ms on and 500ms off) or once a second.

Since millis() is only tested by the conditional if statement, you can add other code within the loop to do other useful work i.e. using Arduino millis() in this way does not stop the processor.

TIP: You must update the variable 'oldtime' within the if-statement to the millis() value so that the next delay period can be performed, otherwise the if-statement expression would hang (always true).

Note: The type uint32_t is the same type as "unsigned long". uint32_t is used in embedded programming as it directly specifies the number of bits within the type, whereas "unsigned long" may have a different number of bits for different compilers.

Arduino millis limit

So how long can you measure (and why would you care?). The function millis() returns a magic number that appears out of the depths of Arduino code but as an engineer you need to know what it is and how it is created.  You need to know because all systems have limits which trip you up and make your system fail.

The maximum time that can be measured depends on the type of variable used to store the millis() data which is an unsigned long and using this type allows you to measure just over 49 days. If your project will never be on for longer than 49 days then you don't have a problem.

For the Arduino the max value from millis() is :

4,294,967,295 or (0xffffffff)

This is because the Arduino millis data type is :

unsigned long (which can also be written as uint32_t)... which you can more easily see the number of bits in the type.

Maximum number of days for millis()

The count that an unsigned long is capable of holding is: pow(2,32)-1 or 4,294,967,295 or 4 billion 294 million 967 thousand and 295. So if every count is worth a millisecond then how many days is that?

First divide by 1000 for the seconds, then by 60 for the minutes then by 60 for the hours then by 24 for the days = ~ 49.71 days.

After approximately 50 days (or a bit more than 49.71 days) the timer wraps round to zero and this is the Arduino millis overflow problem.

Note: Arduinos may have a resonator (3 pin) instead of a crystal (2 pin) and these are not as accurate as crystals.

If you are designing a project that must time for more than 49 days then this could cause a problem because at the wrap around point, time that was increasing incrementally in milliseconds, suddenly goes to zero. If you recorded a time stamp for a data logging device using millis() after 49.71 days the time stamp would return to the start time i.e. wrong.

Another problem is using the timer for delays e.g. to flash an LED, where you wait for the timer to reach the current time plus 500ms say, if the timer wrapped within that 500ms then the time to wait would instead be 50 days which is a bit of a long time to flash an LED off!

(But see below for the solution to this problem).

TIP: If you want to go beyond 50 days, add another variable that acts as extra bits in front of the unsigned long e.g. an unsigned char would extend time by 256 * 50 days. Increment it every time the millis() time wraps around. The downside is that you would need to include that 8 bit quantity in all time calculations so you would need to make your own "greater than" operation etc.

How to avoid Arduino Millis overflow

Lets say that you want to have a repeated action every 100ms. So you write code similar to this:

    // LED Flash
    if ( (millis()-ledtime) > 100) {
       ledtime = millis();

       tog = ~tog; // Invert
       if (tog) digitalWrite(LED,HIGH); else digitalWrite(LED,LOW);

Usually you just assume that the board is not really going to be left on for greater than ~50 days! so you don't really consider overflow. Lets consider it and see what happens in this case.

Millis() overflow solution

The following solution avoids the overflow problem but only if you write the time dejection part exactly as shown - if you reverse the terms or move them around you will make it fail!

The overflow can be avoided but only for measuring periods smaller than the maximum overflow time (the use case) and this is due to a property of how the numbers are stored in memory i.e. modulo arithmetic and the way integers are calculated. For the unsigned long 4 bytes are used.

In modulo maths, (in C modulo is represented by the percent sign %) values are constrained to 1 less than the modulo number For example:

9 % 10 returns 9

but 10 % 10 returns 0


19 % 10 returns 9

and 20 % 10 returns 0

The numbers wrap around and this is exactly how to solve the overflow problem. But instead of actually invoking the modulo operator it is already active because of the constraint of the 4 byte memory storage for an unsigned long. So the unsigned long automatically constrains the value from 0 to pow(2,32)-1.

Since integer numbers also use two's complement representation you get the right numbers when subtracting and adding.

Example of unsigned long millis overflow

Lets say ledtime is set to the maximum value of the unsigned long:

ledtime = 0xffffffff  or pow(2,32)-1 or 4294967295

That means when the next timeout detection is performed then millis() will retrurn a positive value and lets say that millis() returns a value of 1 on the next detection then for normal maths you would have:

millis() - ledtime  == 1 - 4294967295 = -4294967294

The the twos complement and modulo maths means for an unsigned long this represents:

-4294967294 == 2

So this is the right answer even though millis() value is smaller than the real value of ledtime. Twos complement maths means that $fffffff is interpreted as a negative number ($fffffff == -1). So:

ledtime (1) - ($ffffffff) == (1) - (-1) == 2.

So the timer detection code has been written in such a way that overflows are ignored!

...with the caveat that this method can only be accurate for measuring delta periods and the code must be written as follows:
if ( (millis()-ledtime) > 100) 

Time Conversions for Arduino millis

Arduino millis to Seconds

Since millis is a shortened engineering term for milliseconds and milli stands for 1/1000th there are 1000 milliseconds in one second. Therefore to count seconds divide millis by 1000.

Seconds = millis()/1000;

Often you will want to work with millis() in fractions of a second e.g. for flashing an LED every 400ms you would compare millis() to 200 - using an on/off toggle for every 200ms results in a time period of 400ms.

Arduino millis to Hours

If you want to get to hours when using Arduino millis as a Timer you need to do some more division:

Minutes = ( millis()/1000 ) / 60;

Hours = ( ( millis()/1000 ) / 60 ) / 60;

Arduino millis to Days

The full set of time values is here:

Days = Hours/24;

Hours = Minutes / 60;

Minutes = Seconds / 60;

Seconds = millis()/1000;

As noted previously the variable days can only have a maximum value of 49.7 days.

Note: Since Hours. Minutes and Seconds and Days can only have a maximum value below 255 you can use type uint8_t for each of the above quantities. uint8_t is the same as "unsigned char".

Arduino millis vs delay

Lets just say at the start of this discussion - "Don't Use delay()". This will save you time. Read on to find out why...

Arduino milis() is an interrupt driven function meaning that it is always operating in the background while your code is working. Interrupts are used to update the value that millis() outputs so that after every millisecond that value will increase by one. You can access the current value of the millis() at any time simply by calling the function millis() and placing the result into your own unsigned long variable (or you can compare the millis() output to another constant).

The function delay() uses interrupts to calculate time but is actually "Do Nothing" loop to waste processor time - it does not return any value. The function delay() depends on the interrupt driven output from Timer0. Therefore  delay() can not be used within an interrupt service routine since within an ISR is interrupts are turned off.

It just effectively stops the processor from doing anything else while it counts down a delay time.

Note: The above discussion is talking about main code operation - interrupts still interrupt main code and operate e.g. timers or I2C interfaces etc. still work.
Note: millis() does not always increment by one due to the way the timing is calculated within the interrupt routine. The link takes you to a detailed simulation of millis() in this page.

Arduino millis not accurate

No it is not! Well, it does quite a good job.

There are two reasons :

  1. The clock source - This is true of all crystal oscillator based systems but some Arduino boards use resonator that performs worse than a crystal.
  2. The implementation - In general standard crystal oscilaltor frequences that are easy for humans to read e.g.16MHz are not divisible by a decimal number so you can't get an exact output period. Arduino code adjusts for this problem using a correction algorithm.

Reason One - The Clock Source

Arduino millis() is not accurate and one reason for this is that Arduino boards sometimes have a 16MHz resonator fitted. This is a cheap oscillation component that is OK for most applications. it will drift around with temperature but will always be approximately 16MHz.

If you are looking for accuracy, you will need an Arduino with a crystal on board (or make up your own bare-bones version with a crystal). You can tell if yours only has a resonator as resonators are three pin devices, whereas crystals are two pin devices.

The problem is that to retro fit a crystal (it can be done!) to an Arduino board requires two extra components to make the crystal oscillate correctly (capacitive loads are required). These are two capacitors, each connected to one pin of the crystal and then to ground. They range from 12pF to 33pf - check for the correct capacitor specified by the manufacturer - or just use a couple of 15pF. You of course also need to attach the crystal to the clock inputs of the microcontroller (same connections as the resonator - but not the middle ground one).

A better solution is to use an external RTC

The accuracy of the crystal is specified as ppm and is usually around 100ppm (a 32kHz watch crystal can get to 20ppm - not much use for a microcontroller unless you need low power operation).

If you really want an accurate timestamp then use an ovenised timekeeping chip (a cheap 2ppm one is the DS3232). This does not output a clock for direct use by the microcontroller so you could keep the resonator and use the DS3232 as the accurate timestamp device.

Reason Two - Timer 0 implementation

This second reason is not really that Arduino millis is not accurate (it becomes accurate) but more about the implementation of the timer which is clever but will cause a small jitter. The interrupt for the millisecond timer (using Timer 0 in wiring.c) uses prescalers to divide down the main clock 16MHz but the output of the timer is off by a small amount (you can not divide down a 16MHz clock using divide by 2 divider hardware to get an exact millisecond output - -the closest you can get is 1024us). When the error gets big enough a correction factor is used to adjust the timer value.

So in the long term the millis() timer is accurate but in the short term it could be out by a small amount.

How does millis() work in Arduino

Code location of millis() timer:

C:/Program Files (x86)/Arduino/hardware/arduino/avr/cores/arduino/wiring.c

If you have a 32bit machine (above is for 64bit) the path will be:

C:/Program Files/Arduino/hardware/arduino/avr/cores/arduino/wiring.c

Timer0 interrupt Clock Cycles

Timer 0 is setup so that it has a prescaler of 64. It is an 8 bit timer so overflows every 256 counts. Therefore for a 16MHz clock, the repeat time for timer 0 is (1.0/16e6)*256*64 = 0.001024 of 1024 us which is close to 1ms but not actually there.

Millis() Operation

At each interrupt of 1024us the millis() timer is incremented. Since 1024us is greater than 1000us, the millis() timer is too slow and needs correcting.

The idea is to store the error and accumulate it until it surpasses a threshold, upon which the millisecond timer output is corrected.

    So to correct to a 1ms output,
    The number of interrupts before a correction is needed is:

        1000.0/24 = 41.66.

The above equation represents the number of 24us periods that add up to 1ms i.e after 41.66 interrupts the error will be 1ms. Of course you can't get 41.66 interrupts so you have to wait for the following interrupt to detect when the error is greater than 1000us. This will be when 42 interrupts have occurred.

The clever part of the algorithm is that the error accumulator is incremented by 24 every interrupt (the division is not performed - that just lets you see the idea). When this variable is greater than 41.66*24 i.e 42*24  = 1008 then the error is corrected.

The next really, really, clever part of the algorithm is that the error variable is not reset to zero - you just subtract the 1ms value (since that was what was corrected) leaving the last value of the error in the accumulator i.e. it will be 8 on this occasion. This error then accumulates again and the millis() timer is again adjusted when the error is greater then 1ms.

From the analysis below the millis() timer will be continuously corrected and is not in error by more than 2ms (See simulation and real output results below).

You can explore this further by looking at the code that follows below. One thing to note is that the values are fitted into bytes because they are all multiples of 8 (but this only works for 16MHz and 8MHz clocks):

    1024.0 / 8 = 128.0 ;   1024 >> 3 is exactly 128  i.e. fits in a byte.

    1000.0 / 8 = 125.0 ;   1000 >> 3 is exactly 125  i.e. fits in a byte.

        24.0 / 8 = 3.0    ;       24 >> 3 is exactly 3      i.e. fits in a byte.

These numbers are used in the code below.

Arduino millis() Timer 0 code operation

There are two variables used in the correction and a few macro definitions:

clockCyclesPerMicrosecond gives a result of 16 for a 16MHz clock.

Two other macros are:

clockCyclesToMicroseconds(a) = ( (a) / clockCyclesPerMicrosecond )

microsecondsToClockCycles(a) = ( (a) * clockCyclesPerMicrosecond )

Within wiring.c the following definitions are made:

    clockCyclesToMicroseconds( 64 * 256) = 16384 / 16 = 1024

Since clockCyclesPerMicrosecond is 16


Timer 0 is prescaled by 64 and overflows after 256 prescaled clocks so it triggers an interrupt after 64 * 256 clocks cycles.

Which just shows it is a long winded way of doing a simple calculation that does have the advantage of being generic so it means for different clocks a different result will occur. However the code comments state:

// the fractional number of milliseconds per timer0 overflow. we shift right
// by three to fit these numbers into a byte. (for the clock speeds we care
// about - 8 and 16 MHz - this doesn't lose precision.)

So the comments state that the The fractional timings calculation (below) work for 8 and 8Mhz and 16MHz.

The following calculations are for a 16MHz clock.


The above when used later inserts 1.024 into the code at the point of use which is in fact when an unsigned long is incremented so that value would be incremented by one.

Comments in wiring.c (code location):

// the fractional number of milliseconds per timer0 overflow. we shift right
// by three to fit these numbers into a byte. (for the clock speeds we care
// about - 8 and 16 MHz - this doesn't lose precision.)

    = 24 >> 3 = 3 (16MHz)

FRACT_MAX = ( 1000 >>3 )
    = 125

The reason stated for doing the above right shifts is so that the numbers fit into a byte.

Three variables are used in the correction and output of the millis value (timer0_millis - below).

unsigned long timer0_overflow_count - only used in microseconds calculation.

unsigned long timer0_millis - the value output by millis().

Byte timer0_fract

Every time in the interrupt:

timer0_millis is increased by MILLIS_INC (or by 1) - this is the millis() output value.

timer0_fract is increased by FRACT_INC (or by 3).

timer0_overflow_count is increased by one - this is the unadjusted Timer0 interrupt count.

If necessary a correction is made to timer0_millis when the accumulated error gets too big.

Interrupt code (ISR) for TIMER0

The following code is contained within the Interrupt Service Routine and performs the millisecond update which updates the last 3 variables that are globals. The variable timer0_millis is the value returned by the millis() function.

Fractional adjustment in the interrupt vector:

// copy these to local variables so they can be stored in registers
// (volatile variables must be read from memory on every access)
unsigned long m = timer0_millis;
unsigned char f = timer0_fract;

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

timer0_fract = f;
timer0_millis = m;

In the above code, m and f are defined as local variables to allow the compiler to use registers which are faster (check this actually happens by examining output assembler code).

The fractional action is that if timer0_fract is greater or equal than FRACT_MAX (125) then subtract FRACT_MAX and increase timer0_millis by one (>= is used since 125 is not a multiple of 3).

So every time the interrupt fires, 3 is added until the accumulated error (timer0_fract) is greater or equal to 125 which will be when time0_fract 1st reaches 126 or 42 interrupt calls. Interrupts are called with a time period of 1024 microseconds therefore the time before a millisecond correction is made is 42 *1024 = 43008us.

Therefore you will get a correction of the Arduino millisecond timer every ~43ms.

Since the timer0 interrupt is running slow the millisecond timer is incremented by 1 every ~43ms

The first time round the loop after correction 1 is left in timer0_fract.

The second time 2 is left in timer0_fract.

So small fractional errors eventually get corrected after >125 times ~43ms.

TCL simulation

The following TCL program simulates the action of the interrupt code for Timer 0:

proc sim_arduino_timer0 {} {

console show
set c 0

 set timer0_millis 0
 set timer0_frac 0
 set FRACT_INC 3
 set FRACT_MAX 125
 set m 0
 set f 0

 set line ""

 for {set i 0} {$i<1100} {incr i} {

    puts "$m $f"

    set m [expr $m + $MILLIS_INC]
    set f [expr $f + $FRACT_INC]
       if {$f >= $FRACT_MAX} {
          set f [expr $f - $FRACT_MAX]
          incr m

  update idletasks



Some of the output it gererates is shown below (left is the millis increment and the right is the fractional part).:

0 0
1 3
2 6
3 9
4 12
5 15
6 18
7 21
8 24
9 27
10 30
11 33
12 36
13 39
14 42
15 45
16 48
17 51
18 54
19 57
20 60
21 63
22 66
23 69
24 72
25 75
26 78
27 81
28 84
29 87
30 90
31 93
32 96
33 99
34 102
35 105
36 108
37 111
38 114
39 117
40 120
41 123
43 1
44 4
45 7 

The first adjustment is when the millis output is at 42 and the fractional part has just passed 125. Instead of the expected output of 42 an adjusted output of 43 is output instead i.e the correction algorithm jumps past 42 and so millis() does not output the value of 42 at all. At about every 42 ms this correction is made again.

Arduino Program showing millis and micros output

The next program shows the actual output from an Arduino Uno. This is the physical result on real hardware showing the millisecond jitter at 42 ms.

The program takes samples of the current millis() and micros() timer output values sending them as fast as possible to the serial terminal.

It outputs more than one result since the interrupt timer is asynchronous to the main loop code i.e. you don't know when it is going to change, so to see changes you have to make the loop run faster than the millisecond timer (hence the high baud rate and higher sample number 2100 which terminates output).

void setup() {

void loop() {
static unsigned int c=0,stop=0;
unsigned long mil,mic;

// Note Reduce this parameter the more is done in the loop! 
// for 3~4 outputs per loop iteration // delayMicroseconds(50);
if (c<2100 && stop==0) { c++; mil = millis(); // Capture as close together as possible mic = micros(); Serial.print( mil ); Serial.print(" "); Serial.println( mic ); } else stop =1; }

The following table shows millis() on the left and micros() on the right.
The relevant section is here (the first adjustment at millis()==42 ):

40 41164
40 41604
41 42040
41 42480
41 42920 *** millis skips a beat here
43 43368 ***
43 43808
44 44244
44 44684
45 45132
45 45572
45 46016
46 46460
46 46892
47 47340

The micros() value is 4us inaccurate so is much better than millis(). Observe the transition point at 42 (where the expected value:42 which is not output by millis() at all) i.e. this is the same as the simulation output which is reassuring!

You can also see that the millis() output is quite a way out just before the transition i.e. reading millis() at the 41st change means you should be reading from 42.0 to 42.9 instead you get 41 (the worst value is just before the adjustment where millis() outputs 41ms and the actual time is 42.9ms.

You get a jump as the correction factor is added so the millis() function never outputs 42ms and instead outputs 43 as the next result - this brings the millis() output back into alignment with the correct time. This correction process repeats all the time millis() is operating.

However the correction factor at the 42nd interrupt corrects the millis() output to 43.3 for millis() output 43, so in the long term the millisecond timer is accurate. This oscillation around the correct output is called jitter.

Other values of Arduino Crystal Clock

Elsewhere in the code (microsecond delay) it talks about the following frequencies:

'Assumes a 1, 8, 12, 16, 20 or 24 MHz'

(the 24MHz clock is for overclocking - doing something not specified by the data sheet).

The question is: Would millis() be accurate for these crystals?

The following results show that for most cases the millis() output would be accurate but not for 12, 20 and 24MHz. You would need to change the operation of the Arduino code to make millis() output an accurate result.

Microseconds per timer0 overflow:

(64 * 256.0) / 1 = 16384  - 1MHz - no error. 

(64 * 256.0) / 2 = 8192  - 2MHz - no error. 

(64 * 256.0) / 4 = 4096  - 4MHz - no error. 

(64 * 256.0) / 8 = 2048  - 8MHz - no error.  

(64 * 256.0) / 12 = 1365.33 - 12Mhz - millis() would have an error.

(64 * 256.0) / 16 = 1024   - 16MHz - no error.

(64 * 256.0) / 20 = 819.2   - 20MHz - millis() would have an error.

(64 * 256.0) / 24 = 682.66 - 24MHz - millis() would have an error.

The calculation below is FRACT_INC (microseconds_per_interrupt % 1000) >> 3

Frequency (MHz)
FRACT_INC increments
45 (Not accurate millis o/p).
102 (Not accurate millis o/p).
85 (Not accurate millis o/p).

Note: Observe how FRACT_INC values blow up when the frequency used is not an exact factor of the Timer0 prescaler and overflow result. Since pre-scalers and timers use 8 or 16 bits it follows that for this scheme the clock frequency must be a base2 value i.e. 1,2,4,8,16,32 etc.

Note: At lower crystal frequencies the value of FRACT_INC is larger but the shifted right value of 1000 ms calculations remains the same - it has a value of 125 - to fit into a byte) . Therefore the jitter will be far greater using lower frequences i.e. the millisecond clock will be corrected more often. e.g. for 16MHz 125/3 is 41.6 but 125/48 is 2.6 - so at 16Mhz the clock is corrected every 42 ms while at 1MH it is corrected every 3ms.

What happens if you use Arduino millis long?

If you decide to use a "long" type definition for the comparison storage variable then you are using a signed quantity instead of the unsigned quantity output by the millis() function.

If you were to make a timer using "long" and output the value of long using a Serial command (or any command that converts an integer quality to a string) such as:


...where timer is specified as long and updates, perhaps to seconds as:

timer = millis()/1000;

Then at some point in the operation of your code (when the timer reaches the mid point) the leftmost bit of the unsigned output of millis() will become high and the print function will interpret this a the sign bit. Therefore it will output a negative number and Arduino millis() goes negative (or appears to be negative). For a signed calculation the value is interpreted as negative and the print function treats long as a signed quality and therefore outputs a negative number.

Try this code in your Arduino

uint32_t ulong;
int32_t slong,minus1,plus1,s1;
void setup(void) {


    minus1 = -1;
    plus1 = 1;

    Serial.println("\n View plus 1");
    s1 = plus1;
    ulong = (unsigned long) s1;
    Serial.print(" signed plus 1 dec :");Serial.println(s1);
    Serial.print(" signed plus 1 hex :");Serial.println(s1,HEX);
    Serial.print(" unsigned plus 1 dec :");Serial.println(ulong);
    Serial.print(" unsigned plus 1 hex :");Serial.println(ulong,HEX);

    Serial.println("\n View minus 1");
    s1 = minus1;
    ulong = (unsigned long) s1;
    Serial.print(" signed dec :");Serial.println(s1);
    Serial.print(" signed hex :");Serial.println(s1,HEX);
    Serial.print(" unsigned dec :");Serial.println(ulong);
    Serial.print(" unsigned hex :");Serial.println(ulong,HEX);

    Serial.println("\nif millis() reaches unsigned value 0xffffffff-1");
    Serial.println(" Observe the signed value");
    s1 = (long) (0xffffffff-1);
    ulong = (unsigned long) s1;
    Serial.print(" signed minus 1 dec :");Serial.println(s1);
    Serial.print(" signed hex :");Serial.println(s1,HEX);
    Serial.print(" unsigned dec :");Serial.println(ulong);
    Serial.print(" unsigned hex :");Serial.println(ulong,HEX);

    Serial.println("\n View signed value of 0x7fffffff - no problem");
    s1 = (long) (0x7fffffff);
    ulong = (unsigned long) s1;
    ulong = (unsigned long) s1;
    Serial.print(" signed dec :");Serial.println(s1);
    Serial.print(" signed hex :");Serial.println(s1,HEX);
    Serial.print(" unsigned dec :");Serial.println(ulong);
    Serial.print(" unsigned hex :");Serial.println(ulong,HEX);

    Serial.println("\n View signed value of 0x80000000 (The sign bit)");
    s1 = (long) (0x80000000);
    ulong = (unsigned long) s1;
    ulong = (unsigned long) s1;
    Serial.print(" signed dec :");Serial.println(s1);
    Serial.print(" signed hex :");Serial.println(s1,HEX);
    Serial.print(" unsigned dec :");Serial.println(ulong);
    Serial.print(" unsigned hex :");Serial.println(ulong,HEX);

void loop(void) {

This is the result you'll get:

 View plus 1
 signed dec :1
 signed hex :1
 unsigned dec :1
 unsigned hex :1

 View minus 1
 signed dec :-1
 signed hex :FFFFFFFF
 unsigned dec :4294967295
 unsigned hex :FFFFFFFF

 if millis() reaches unsigned value 0xffffffff-1
 Observe the signed value
 signed dec :-2
 signed hex :FFFFFFFE
 unsigned dec :4294967294
 unsigned hex :FFFFFFFE

 View signed value of 0x7fffffff - no problem
 signed dec :2147483647
 signed hex :7FFFFFFF
 unsigned dec :2147483647
 unsigned hex :7FFFFFFF

 View signed value of 0x80000000 (The sign bit)
 signed dec :-2147483648
 signed hex :80000000
 unsigned dec :2147483648
 unsigned hex :80000000

You can see that the sign bit is very important (the left most bit) and if you use signed types you will get negative output numbers displayed, even though the unsigned version is correct i.e. it has the expected bit value - or hex value shown.

Also shown is the flip over point where using signed long is OK until you reach 2147483647 (0x7fffffff) add one to that and you get -2147483648 (0x80000000). In terms of days a timer will appear to work fine for ~25 days and then adding one results in a negative output. The explanation of number of days that millis() covers is here.

The easy way round that is to use unsigned long (uint32_t) when dealing with millis().

More Examples

How to make a one-shot timer with Arduino millis

This code only does one serial output action action after a set time and then stops. Although using Arduino millis() allows other operations to continue i.e. the LED keeps flashing but only one message is output.

#define LED 13

void setup(void) {

     Serial.println("Simple Scheduler");


void loop(void){
static uint8_t tog=0,s1done=0;
static uint32_t ledtime= millis(),s1=ledtime;

    // LED Flash
    if ( (millis()-ledtime) > 500) {
       ledtime = millis();

       tog = ~tog; // Invert
       if (tog) digitalWrite(LED,HIGH); else digitalWrite(LED,LOW);

    // 3 second message - one shot
    if ( !s1done && (millis()-s1) > 3000) {
       s1 = millis();
       s1done = 1;

       Serial.println("-3- SECONDS ONE SHOT ONLY");

How to make a simple scheduler using Arduino millis

The aim of this Arduino millis example is to make a simple scheduler algorithm to start different actions at different times. This is only a simple example and you can find multitasking schedulers that transfer operation to a different task saving variables so that tasks can be interrupted stopped and restarted. There will also be the concept of flags that allow communication between tasks. This simple example is definitely not that type but it can be useful nevertheless.

#define LED 13

void setup(void) {
     Serial.println("Simple Scheduler");


void loop(void){
static uint8_t tog=0;
static uint32_t ledtime= millis(),s1=ledtime,s2=s1,s3=s1,s4=s1;

    // LED Flash
    if ( (millis()-ledtime) > 500) {
       ledtime = millis();

       tog = ~tog; // Invert
       if (tog) digitalWrite(LED,HIGH); else digitalWrite(LED,LOW);

    // 10 second message
    if ( (millis()-s1) > 10000) {
       s1 = millis();

       Serial.println("10 SECONDS");

    // 5 second message
    if ( (millis()-s2) > 5000) {
       s2 = millis();

       Serial.println("five SECONDS");

    // 3 second message
    if ( (millis()-s3) > 3000) {
       s3 = millis();

       Serial.println("-3- SECONDS");

    // 2 second message
    if ( (millis()-s4) > 2000) {
       s4 = millis();

       Serial.println("*2* SECONDS");


Scheduling initial times for offset start using Arduino millis

TIP: To avoid everything happening at the same time set the initial conditions to be offset from each othe.

You can offset the start times of each timer so that they are not all a multiple of 1000, because if they are then they will fire more or less at the same time and serial output will be generated, and serial output takes a bit of time thus changing the actual time that subsequent time matches happen.

For example you could write the following initialisation (using random offsets):

static uint32_t ledtime=millis(),s1=ledtime+66,s2=s1+90,s3=s1+187,s4=s1+231;

This would mean that the starting times of each timed output are offset from each other - only the start times - the subsequent repeat times would be at the repeat time specified in the code - they would still be offset so they would not happen at the exact same time.

So the processor would not have to do the actions associated with each timeout at the same time. Therefore the code will operate more smoothly and not have to do a big processing burp!


You can use the millis() function to create non-blocking delays so that the processor can carry on doing other operations even while the delay is in progress. This the opposite of using the delay() function where the processor has to stop and do nothing.

The problem is that delay() is easier to use whereas mllis() is a little bit more involved. However, once you are familiar with the operation it is quite easy.

The millis function in an 8 bit processor does skip a beat every now and then but the algorithm does catch up giving you an overall accurate millisecond timer. You can explore this missing value operation using this page.

Also on this page you can find code using millis for one shot timers and a simple scheduler operation.


Have your say about what you just read! Leave me a comment in the box below.

Don’t see the comments box? Log in to your Facebook account, give Facebook consent, then return to this page and refresh it.

Privacy Policy | Contact | About Me

Site Map | Terms of Use