Timer blocking application loop

On a Boron running DeviceOS 1.5.2, timers do not block the application loop, only other timers as expected. When upgraded to DeviceOS 2.0.0, timers using I2C seem to block the application loop, perhaps because of clock stretching.

For example, the timer below wraps a call to SparkFun_SHTC3_Arduino_Library which commands a measurement over I2C that takes about 10 to 12 ms. I see log warning “delay 13 ms” every 5 seconds, corresponding to the timer, on 2.0.0 but not on 1.5.2.

SYSTEM_MODE(MANUAL);
SYSTEM_THREAD(ENABLED);

#include "SparkFun_SHTC3.h"

SHTC3 shtc3;
Timer sensorTimer(5000, readSensor);
SerialLogHandler logHandler;
unsigned long now = millis();

void setup() {
  Cellular.off();
  Wire.begin();
  if (shtc3.begin() == SHTC3_Status_Nominal) {
    sensorTimer.start();
  } else {
    Log.error("failed to initialize");
  }
}

void loop() {
  if (millis() - now > 1) {
    Log.warn("delay %lu ms", millis() - now);
  }
  now = millis();
}

void readSensor() {
  WITH_LOCK(Wire) {
    if (shtc3.update() != SHTC3_Status_Nominal) {
      Log.error("measurement failed");
    }
  }
}

@daradib I wouldn’t structure the readSensor() as a timer handler - timer handlers should be very short and quick - 12mS isn’t! Rather I would use a millis() based timer in the loop thus;

unsigned long readsensorupdate = 0UL; //global declaration

in setup()
readsensorupdate = millis();

in loop()

if (millis() - readsensorupdate >= 5000UL)
{
  readsensorupdate = millis();
  readSensor();
}

As others have pointed out - with OS 2.0.0 it is much tighter managing shared resources. Have you tried running the same without WITH_LOCK(Wire)?

1 Like

To add to @armor’s comments, Software Timers run in a single thread and are daisy-chained so if one timer blocks, the others won’t run. Also, the timer thread doesn’t have a large stack so you have to be careful with the calls you make.

@armor I’m trying to avoid blocking the application loop for more than ~5 ms while commanding an I2C measurement. A millis() based timer is indeed preferable because I wouldn’t have to consider thread safety and the constraints @peekay123 mentioned, but it blocks the thread by design.

I tried removing WITH_LOCK, but the timer still blocks the application loop. These lines in the sensor library seem to block regardless of a lock:

_wire->beginTransmission(SHTC3_ADDR_7BIT);
_wire->write((((uint16_t)cmd) >> 8));
_wire->write((((uint16_t)cmd) & 0x00FF));
res = _wire->endTransmission();

A timer running delayMicroseconds also blocks, but delay does not.

I replaced the timer with a thread and now neither the I2C calls nor delayMicroseconds seem to block:

SYSTEM_MODE(MANUAL);
SYSTEM_THREAD(ENABLED);

#include "SparkFun_SHTC3.h"

SHTC3 shtc3;
Thread *thread = NULL;
system_tick_t lastThreadTime = 0;
SerialLogHandler logHandler;
unsigned long now = 0;

void setup() {
  Cellular.off();
  Wire.begin();
  if (shtc3.begin() == SHTC3_Status_Nominal) {
    thread = new Thread("sensorThread", readSensor, OS_THREAD_PRIORITY_DEFAULT,
                        1024);
  } else {
    Log.error("Failed to initialize");
  }
  now = millis();
}

void loop() {
  if (millis() - now > 1) {
    Log.warn("Delay %lu ms", millis() - now);
  }
  now = millis();
}

void readSensor() {
  while (true) {
    WITH_LOCK(Wire) {
      if (shtc3.update() == SHTC3_Status_Nominal) {
        Log.info("Measurement OK");
      } else {
        Log.error("Measurement failed");
      }
    }
    os_thread_delay_until(&lastThreadTime, 5000);
  }
}

It seems if the thread priority is higher than the application thread (OS_THREAD_PRIORITY_DEFAULT), the thread takes precedence and effectively blocks. So I’d speculate that the software timer thread runs at a higher priority in 2.0.0.