Zephyr Sensor IO - Part 1
Being relatively new to the Internet-of-Things on the truely compact, battery powered, and consumer delivered device, I had quite the task ahead of me.
I needed bluetooth, a decent chunk of memory (for a micro), reasonably fast IO to support a wide berth of sensors, and battery life that lasts for days.
A quick search and discovery led to the most excellent Nordic Semiconductor NRF52840 SoC, which has all of the above in spades.
What I didn't expect coming from the world of Atmegas with its relatively straightfoward avr-libc and bare metal programming was just how big the mental burden would be to develop on such a device. Bluetooth alone is very much like a network stack. While the NRF SDK is excellent, it would take quite some time to develop the application I needed. Then to debug my one off superloop and device drivers seemed like a poor route to go.
It was time to look at the array of RTOSes available to me for this chip. Zephyr was a clear winner to me after a quick search and discovery. The banner image showcasing my hometown had nothing to do with Zephyr winning me over, I swear. A quick git clone and I was running the sample apps on a dev board in less than an hour. Record breaking time as these things tend to go.
Trouble in Paradise
While most of the Zephyr API's are quite good the sensor API left me scratching my head a bit as I tried to create my own drivers for the TDK ICM20649, a 6 axis accelerometer/gyro combo with great ranges and sampling rates for my use case. This very fast Arm was failing to do the simplest of things, take in a stream of 6 16 bit samples at 1KHz over SPI and save it to memory. It turns out that the way the driver API is written leads to a wide variance in latency from the time the GPIO pin is triggered to the time the SPI transfer even begins. At first the fix was to use the built in FIFO to simply avoid the latency issue altogether by allowing a wider variance in interrupt servicing times, all was wonderful again. I worked around the API issue.
While this was fine at first there was yet another problem. Fusing the data of the TDK ICM20649 with an ST 3 axis magnetometer also communicating over SPI. Also at 1KHz. This chip does not happen to have a hardware FIFO on it. On the surface it seems relatively simple to align these samples in time over time. In reality the sample rates of the two chips varied enough over the 250ms of FIFO buffer the TDK chip would accumulate to add up to 10s of milliseconds over a 1 or 2 second recording. This was problematic for my use case.
I went back to pulling samples one by one every trigger, and the samples were once again all time aligned. But the problem remained, the time from the trigger interrupt to the time my k_work or k_thread got around to reading from the device varied.
I tried adjusting the priorities, which failed to change much. I tried to read directly in the interrupt handler, which had other poor consequences and caused other parts of my program to fail in surprising ways. There wasn't a good solution unfortunately, I just accepted I would be missing some data over time.
It turns out none of these problems were not new and an issue from 2017 by Anas Nashif, the chair of the TSC of Zephyr, described almost exactly the problems I had ran into. https://github.com/zephyrproject-rtos/zephyr/issues/1387
Short term I found ways of working around the general problem, longer term I really wanted a better way of using sensors in Zephyr. For this project and future ideas I have for products.