Timing - delay() and millis()

Apologies if this is a FAQ - I have looked on this forum and elsewhere but haven't yet found an answer that isn't bogged down with the details of individual projects.

My need is simple . I want to read a temperature sensor and log the reading alongside a timestamp. So far, in mangled C/Arduino pseudocode:

unsigned long seconds=0;
float t;
loop()
{
  led_on();
  t=read_temperature();
  led_off();
  Serial.printf("%lu  %.2f\n",seconds,t);
  delay(1000); //1 second delay
  seconds++ ;
}

Of course it doesn't work because the actual loop() time is the sum of led_on() , read_temperature(), led_off(), Serial.printf() and delay() plus overheads. So a 'second' is more like 1.2 seconds.

I'm wondering if I can improve things by using millis() to make a timestamp, but even after consulting the Arduino doc for millis() I'm not sure - is millis() guaranteed to be accurate within the limits of the processor clock?

Bob.

Go to the IDE and get out the examlke sketch called Blink Without Delay.

It will reveal the basic pattern for millis() based timing.

There is an article that goes along with it

https://docs.arduino.cc/built-in-examples/digital/BlinkWithoutDelay/

Now… conceptually, you could use this pattern: just replace the blink with whatever you want to do on a periodic basis.

So it will be "read and display temperature without delay".

Give it the time it might need; come back with questions or your doesn't-work-yet attempts if you don't get it right away.

It's a new way of thinking if you've never seen it, and it is a basic pattern that it would be good to master.

a7

Here is some code using the techniques mentioned in the 'BlinkWithoutDelay' example sketch:

int ledPin = 13;
unsigned long previousMicros = 0;
const long interval = 1000000;
float temperature = 20.0;
float time = 0;

void setup() {
  pinMode(ledPin, OUTPUT);
  Serial.begin(115200);
}

void loop() {
  unsigned long currentMicros = micros();
  if (currentMicros - previousMicros >= interval) {

    digitalWrite(ledPin, HIGH);
    temperature = 20.0,   // dummy temperature read
    time = micros() / 1000000.0;
    digitalWrite(ledPin, LOW);
    Serial.print(" temperature: ");
    Serial.print(temperature);
    Serial.print("°C\ttime: ");
    Serial.print(time, 3);
    Serial.println(" seconds");

    previousMicros = previousMicros + interval;
  }
}

Here are the results on the Serial Monitor:

I've used micros() to do the timing, for the resolution, but using millis() would be acceptable.

I've reported the time to the nearest millisecond. You can see that the results give the time in whole seconds, as you require.

EDIT,
I've changed the line:
time = micros() / 1000000;
to
time = micros() / 1000000.0;
to ensure that that the maths is done in floating point.

No, millis() timing is +/- 1ms without slowing the chip down. It's a speed trick, the low 8 bits never count 6 values including xFF to get 250 steps of 1024 us = 256 us with a non-binary clock -- the second millis() byte is true to the bits, the low byte is 'funny'.

If you want accurate, use micros() but be aware that the micros() return advances 4 every 4 microseconds = 64 MCU cycles. That is still 250 ticks per ms with no +/- except clock source... Uno has a resonator instead of crystal and capacitors, temperature affects the precision of micros(), it's like up to 3 or so minutes a day.

I don't know what you need in the way of time but if your code doesn't block void loop() then an Uno can do a few simple things like sense, process, and react >50 times per ms on average while showing you a loop count once a second.

Your loop() turns the led on then off too fast to see.

Nothing will be done with t, the compiler will ignore it.

The delay(1000) blocks. Speed goes from >50K loops/sec to <1K.

Every ms delayed is 16000 lost clock cycles. Is that worth saving?

Nick Gammon's Do Multiple Things At Once teaches non-blocking 101.

look this over


const unsigned long OneMinute = 5000L;  //  60000L;     for testing
      unsigned long msec0;
      unsigned long msec;

char s [90];

// -----------------------------------------------------------------------------
float read_temperature ()
{
    return 22.3;
}

// -----------------------------------------------------------------------------
unsigned minute;
unsigned hour;

void reportTemp ()
{
    // check if time to report
    if (msec - msec0 < OneMinute)
        return;

    // update time variables
    msec0 += OneMinute;

    if (60 <= ++minute) {
        minute = 0;
        hour++;
    }

    // read, format and output report
    sprintf(s, " %2d:%02d  %.2f", hour, minute, read_temperature ());
    Serial.println (s);

    // flash LED each report event
    digitalWrite (LED_BUILTIN, LOW);
    delay        (1000);
    digitalWrite (LED_BUILTIN, HIGH);
}

// -----------------------------------------------------------------------------
void loop ()
{
    msec = millis ();
    reportTemp ();
}

// -----------------------------------------------------------------------------
void setup ()
{
    Serial.begin (9600);
    pinMode      (LED_BUILTIN, OUTPUT);
}

what makes you think millis() won't be as accurate as the processor clock over the long term?

So make the delay 800

Does the datasheet (or a library example) for your DHT state the data is ready to read every one second? Usually DHT data is ready to read every two seconds.

Be aware that millis() usually advances in steps of one, but sometimes takes a step of 2.

why would it do that unless the time between calls is > ~1.5?

millis timer ticks every 1024 micros

The low byte of millis() return Skips 6 Values including 255 to count 250 instead of 256 noting that 250x1.024=256. Bit 8 will toggle 256 ms +/- 0 ms, it's just the low byte that skips values and makes +/- 1 ms.

does it actually "skip" values or simply doesn't report them?

in other words if it returns a value of 1M, is it actually 1M +/- 1 or can it be off by more than1 and the error will accumulate? 2M +/-1, 3M +/-1

how accurate is ardiuino millis. seems that code accounts for the 1.024 tics.

@gcjr - take a minute and do what many of us had to:

Write a sketch and see.

a7

what would i use as my reference?

I am not sure if I have understood your question correctly.

from init() of int main():

 // Set prescaler = 64
#if defined(TCCR0B) && defined(CS01) && defined(CS00)
    sbi(TCCR0B, CS01);
    sbi(TCCR0B, CS00);
    cbi(TCCR0B, CS02);
#endif

    // Enable Timer0 Overflow Interrupt
#if defined(TIMSK0) && defined(TOIE0)
    sbi(TIMSK0, TOIE0);

If mills() never skips a count, then if you write a tight loop comparing millis() to the last observation, the difference will never be greater than 1. If it ever skips, the difference will be greater than 1.

spoiler

Spoiler: It will periodically be greater than 1.

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

void loop() {
   static uint32_t lastMs = 0;
   uint32_t now = millis();
   uint32_t diff = now-lastMs;
   if(diff){
      if (diff > 1){
         Serial.print(now);
         Serial.print(" ");
         Serial.println(diff);
      }
      lastMs = now;
   }
}

i'm past that. i'm wondering if it's accurate long term.

that link says it skips but the value is a adjusted

how would you verify that millis() is long term accurate.

would you need to use a real time clock and after a day or so verify that millis is still synchonized, +/-1 to the RTC?

If you are saying about the drift in the exteranl "ceramic resonator" of UNO R3, then I would say that this is not accurate as accurate the crystal is.

part of the question was is millis() not accurate because it counts 1.024 msec clock tics instead of 1.000 msec clock tics, or because it doesn't have a very accurate time base, a resonator instead of a crystal, or both.

it seems the biggest source of error is the lack of a crystal.

and then the question is how inaccurate is it ... that link said ~1.7 sec /day. is that accurate enough for your application?

The error is 2.4% which should be acceptable.