2

I'm using this piece of code to try to poll an IMU sensor at 100 Hz (for a AHRS sensor fusion library).

void loop(void)
{
  // nonblocking code variables
  static uint32_t last_ms;
  uint32_t ms;
  // delay between samples
  ms = millis();
  if (ms - last_ms < 10) return;
  last_ms = ms;

  sensor_data (); //...

My 1st question is should I be declaring last_ms as a global variable instead of static?

Also, should this code be at the beginning or end of loop? My understanding is we are basically checking how long the previous loop took. And if it took less than 10 ms (100 Hz) we do nothing. But doesn't that mean that we might still get deviation from 100 Hz due to the time it takes to complete loop (about 2-5ms in my case), so effectively it's 10 ms + loop complete time?

Sorry if this is a stupid question.

6
  • see the BlinkWithoutDelay example. I guess it is easy to modify it from 1 Hz to 100Hz
    – Juraj
    Commented Jun 22, 2021 at 11:47
  • 3
    If you want to run something at 100Hz you probably want to use a timer interrupt rather than fiddling with delay values. Commented Jun 22, 2021 at 12:19
  • I will experiment with that thank you Commented Jun 22, 2021 at 12:44
  • 1
    1. static/global; meh, same thing really. 2. it should be 100Hz since you restart the clock before running the task, so it won't jitter unles the task takes longer than 10ms, but if that's the case there's no way it will ever work anyway.
    – dandavis
    Commented Jun 22, 2021 at 19:11
  • Can you configure this particular device to use interrupts rather than polling? Commented Jun 23, 2021 at 13:43

2 Answers 2

6

should I be declaring last_ms as a global variable instead of static?

Local static is better, as it limits the scope of the variable to just where it is needed. Consider making it global only if the code is part of a tutorial meant for beginners which may be confused by the keyword static.

should this code be at the beginning or end of loop?

The conditional early return only makes sense if loop() has nothing more to do once the sensor data is handled, thus this should go at the very end of loop(). However, if loop() does more than just handle this data, I think it would be cleaner to avoid the early return: either put this in a separate function (which can return early), or condition the handling of the sensor on ms - last_ms >= 10.

My understanding is we are basically checking how long the previous loop took.

No. The test is looking at how much time has elapsed since the previous time last_ms was updated, which could be many loop() iterations ago.

But doesn't that mean that we might still get deviation from 100 Hz due to the time it takes to complete loop (about 2-5ms in my case), so effectively it's 10 ms + loop complete time?

Indeed. With this logic, 10 ms is the minimum time between calls to sensor_data(). If you want to ensure an accurate average polling time, you should update last_ms by adding 10. You will still have sole jitter though.

A small note about millis(): since the value is updated every 1.024 ms, it occasionally has to jump by two units at once, and has therefore 1 ms of jitter. For a period as short as 10 ms, I would rather use micros(). For example:

const uint32_t POLLING_PERIOD = 10000;  // 1e4 us = 10 ms

// Call this from loop()
void handle_sensor()
{
    static uint32_t last_us;
    if (micros() - last_us < POLLING_PERIOD) return;
    last_us += POLLING_PERIOD;
    sensor_data();
    // ...
}

Edit: About why last_us has to be incremented by a constant amount, consider this alternative:

void handle_sensor()
{
    static uint32_t last_us;
    uint32_t now = micros()
    if (now - last_us < POLLING_PERIOD) return;
    last_us = now;
    sensor_data();
    // ...
}

The code following the if ... return; line will be executed when the tested condition is false, i.e. when now - last_us >= POLLING_PERIOD. It may happen that we sometimes have strict equality (now - last_us == POLLING_PERIOD) but, since any code execution takes time, sometimes now - last_us will be strictly larger than POLLING_PERIOD. Every time this happens last_us will be incremented by more than POLLING_PERIOD. This means that the small timing errors accumulate.

If, instead, last_us is updated by adding a constant amount, we still have timing errors (because of the time needed to execute any code), but they are not cumulative. In other words, we have jitter, but the average polling frequency is right.

2
  • Thank you very much for the detailed answer Edgar. Good to see that you're still helping out newbies on here. I'll try the updated code using micros. To check I understood, we are using micros to avoid jitters. Still a bit confused on the difference between adding the polling period to our comparison variable, instead of comparing Millis to Millis. I feel I'm missing something, because to me it looks like they both achieve the same thing which is: wait until 10 ms have passed and only then start processing data.. Although you imply there is a difference. Commented Jun 22, 2021 at 18:44
  • Thank you for the edit, I understand now. Commented Jun 23, 2021 at 11:15
3

The time range your sensor code will be executed is 10ms to 10ms + the execution time of the first part of the loop (until the return) and the execution time outside of loop(). These additional times don't always apply, but in the worst case you can have up to that additional delay (because it means you are checking the millis() value only so often).

The mentioned additional delay is rather short (microseconds realm). Your sensor code is only executed, when the time is reached. As long as you don't disable interrupts, the Timer0 interrupt will continue to count further for millis(), so its value will increase independently from the execution time of your sensor code.

That is as long as the sensor code also doesn't take longer than 10ms (or a bit less, accounting for some overhead). As your sensor code only takes up to 5ms (according to your question), you should be safe here.

If millis() is accurate enough for you I cannot say. Thats up to your exact requirements. For more accurate timing an own timer interrupt would be better, though that also limits you with what you can do. Inside an interrupt you cannot execute code, that relies on interrupts (like communication over Serial or I2C). Depending on your sensors using your above code is already enough and should work good.

should I be declaring last_ms as a global variable instead of static?

There is no definitive answer to that, but generally it is good to limit the scope of variables to the minimum needed (to avoid possible conflicts). last_ms is not needed anywhere else, so creating it locally and static is a good way to do it. Memory wise it doesn't really make a difference.

1
  • Hi Chris, thank you for another detailed answer. Commented Jun 22, 2021 at 12:46

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.