I2C reading strategy: Software timer vs millis() recipe in loop()?

Hello everyone!

I am planning on using this board (below) for sampling a few analog sensors and monitoring 4 dry contacts with a Tracker One:

Regarding the analog sensors, I can read them once or twice a second with a millis recipe on the loop() function. All good, the input signals I’m looking to measure don’t change rapidly.

Regarding the dry contacts, I’m worried about what can be done in the callback of a software timer.

Imagine I want to sample (or read the I2C register) 10 times per second.

If I go with the millis recipe, since the timing of it may not be accurate, I’m not sure how to go about debouncing the inputs (let’s say for 300msec).

If I go with the software timer, I’m not sure if I2C calls made from the timer callback have the potential to create trouble in my or the tracker firmware.

Have you done something similar in the past?
Do you have any thoughts?
Please reply below.

EDIT: I suspect I need an I2C level shifter between the tracker and this board since this board might use 5V I2C levels. To be confirmed…

The Tracker itself does not use the primary I2C interface (Wire or Wire3) so it won’t conflict, but if you also access I2C devices from your main application loop you’ll need to lock the interface (Wire.lock() and Wire.unlock()) in both the main loop code and the software timer code.

10 times per second will probably be OK with I2C.

The best solution would be to use interrupt mode of the MCP23017 but it doesn’t look like that board exposes the pin. It requires a pin from the MCP23008/MCP23017 to go to a GPIO input on the MCU, which allows you to check for any interrupt-enabled pin change without having to continuously poll by I2C.

Thank you for the tips. I’ll pay attention to the lock()/unlock() mechanism so I stay out of trouble.

Yes, I read how you did it in one app note, but the pin seems not exposed on this board.


Sometimes you can get by without level-shifting I2C as long as the 5V I2C board you are using does not have on-board terminators, or you remove them. You instead add pull-ups to 3V3 off-board on SDA and SCL.

This works because most 5V I2C devices will treat 3.3V as logic high. And because I2C never drives the bus high - it only pulls it low with an open-collector driver, the SDA and SCL pins won’t exceed 3.3V.

If you do need level-shifting, primarily because you have a long I2C bus that really needs to run at 5V, you can use something like a PCA9306, or there’s a really clever technique that uses N-channel MOSFET transistors, one each on SDA and SCL. Adafruit likes to use this technique on their boards that have I2C level shifting.


It appears that you can use the interrupt on change mode on the MCP23008/MCP20017 without the hardware interrupt pin. Instead of polling the actual GPIO values hoping to catch when the GPIO changes, you poll the INTF register to see if an interrupt has occurred. This has the advantage of being able to catch a small pulse without having to poll very frequently.

1 Like

interesting twist, thanks for the heads up!