This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Field notes

Notes from real engagements — embedded systems, integration, and modernisation work.

Notes from real engagements — embedded systems, integration, and modernisation work. Written from the field, not the marketing department.

Zephyr is the DOS-to-Linux moment for embedded

I filed Zephyr under “alternatives watched” in May. Twenty-four days and 184 commits later the whole OGLAS sensor fleet runs on it — including one in-tree board whose LoRa radio could never have worked.

I’ve written embedded firmware for a long time — AVR, ESP32, nRF52, RP2040 — and mostly the default was bare metal or a tiny superloop. It worked. It was also, honestly, the DOS era of microcontrollers: you own the whole machine, you reinvent every driver, and “scaling up” means more #ifdef soup. FreeRTOS if you were feeling fancy, but the same shape.

Zephyr is the move to Linux. It brings what Linux got right down to a 64KB MCU.

I was late to this

I have the receipts. In May I wrote myself a note about build systems: PlatformIO under “Current”, Zephyr filed under “Alternatives watched” with the line “worth it if I move heavier on nRF.” The same note listed the trigger that would change my mind — “a specific project’s complexity outgrows PlatformIO’s project model.”

Then exactly that happened. I started the second-generation OGLAS repo on 23 July. Twenty-four days and 184 commits later, the whole sensor fleet — sensor, repeater and hub tiers, plus a handheld and a remote control — runs on Zephyr.

Devicetree, not #ifdef soup

This was the first thing that sold me. Hardware goes in devicetree overlays, features in Kconfig fragments, product tier in its own app directory. Three orthogonal axes, and variation stops turning into soup.

Concretely: I collapsed a pile of per-role firmware variants into one config-driven image with a runtime node config store. One binary, configured per node, instead of a build matrix that grew every time a customer wanted something slightly different.

The same app on ARM and RISC-V

The same OGLAS sensor code runs on nRF52840 (RAK4631, XIAO BLE, T-Echo Lite), ESP32-S3 (T3-S3, Heltec Wireless Tracker), ESP32-C6 and ESP32-C3. That last pair is RISC-V, not ARM.

Same application, different instruction set, because the HAL is Zephyr’s rather than the vendor’s. I did not expect that to just work. Changing silicon mid-project is normally a rewrite; here it was a board file.

CI for firmware

ztest and twister are the parts most people miss. Twelve test suites that build and run across a board matrix on every push — firmware gets the same regression discipline web code has had for years.

It still bit me, mind you. The platform list in CI has to name every board a sample.yaml mentions, or those tests skip silently rather than failing. That is how two of my apps went unbuilt for a week without anyone noticing. Silent skips are worse than red builds.

The rest is quieter but adds up. Networking ships in the box, so BLE, Thread, Wi-Fi and MQTT are maintained subsystems rather than copy-pasted vendor blobs. Power management has real policy hooks — I PM-gated a GNSS UART between acquisitions and stopped the GPS burning current between fixes. And there’s enough visibility into the build to notice a board sitting at 87.6% DRAM, move the big arrays into PSRAM, and get it back to 73.2%.

No flag day

The part I’m most pleased with isn’t a feature.

The interop contract for OGLAS is the wire protocol, not the code: a Zephyr node has to be byte-identical over the air to a legacy Arduino one. The binary framing and radio parameters are frozen invariants, so old and new firmware coexist in the field. There was no migration weekend, no fleet-wide reflash, no moment where the whole network had to be on the new thing at once.

That matters more than any single Zephyr feature. A rewrite you can deploy one node at a time is a rewrite you can actually finish.

The potholes are real

Bringing up a Heltec Wireless Tracker, the SX1262 ignored everything I sent it. The sync word read back 0x00 0x00 instead of 0x14 0x24, and every transmit timed out.

I worked down through the obvious suspects — software chip-select, reclaiming the CS pin from its pinmux, a push-pull reset, dropping SPI from 4 MHz to 1 MHz — and got nowhere. So I bit-banged a GetStatus on the same pins with plain GPIO, bypassing the driver entirely. It answered 0x52: chip mode RX. The radio had been in RX exactly as commanded. My writes had been landing all along; only the read path was dead.

Root cause: the MISO pad’s input buffer was never enabled (FUN_IE = 0 in IO_MUX), so the GPIO matrix was reading a constant zero. Boards where MISO happens to land on a strapping pin work by accident, because the boot ROM enables the input buffer for them — which is presumably why this had gone unnoticed. Between that and a board-file defect, that in-tree board’s LoRa radio could never have worked.

Two days of my life. But here’s the thing: I could read the driver, find it, fix it, and write it up for upstream. On a vendor SDK I’d have filed a ticket and waited.

Where the line falls

Kconfig, devicetree and west are a lot to swallow on day one. It’s overkill for a 2KB ATtiny or a one-off blinky — there I still reach for PlatformIO and a superloop, and I’d tell a client the same.

But the moment a project has more than one bus, more than one transport, or more than one person on it, Zephyr pays back fast. I resisted it for years and then did it in under a month.

If you’re weighing the same jump, the Zephyr work I take on covers firmware architecture, driver work, LoRa and mesh, low-power design, and getting a team onto Zephyr without the first-month pain. Happy to talk through where the line falls for your hardware — get in touch.

We open-sourced our Antarctic wave buoys

A decade of wave-measurement buoys frozen into Antarctic sea ice — firmware, hardware, and the wave-statistics maths — is now open source as WII5.

For over a decade I built and maintained the firmware for the Waves In Ice buoys — instruments that get deployed onto Antarctic sea ice, freeze in, and keep reporting wave motion back over Iridium satellite for months at a time. The data from the earlier generations contributed to peer-reviewed science, including a 2014 paper in Nature on storm-induced sea-ice breakup.

That work is now open source as WII5: wii5.sh3d.com.au — AVR firmware, schematics, PCB designs, enclosure notes, and the on-board wave-statistics mathematics, along with deployment logs going back to 2012.

Why open it up?

The honest answer is the bus factor. These buoys have outlived research grants, hardware generations, and more than one institution’s involvement. The knowledge of how they work — why the power budget is shaped the way it is, how the IMU maths turns accelerations into wave spectra on an 8-bit microcontroller, what actually fails in sea ice — lived in too few heads.

Publishing everything means the next research group doesn’t start from zero. They can read the firmware source, see a decade of deployment history, and build on hardware that has already survived the worst environment we could find for it.

What’s in it for an engineer?

Even if you never go near sea ice, the interesting bits are the constraints: ultra-low-power AVR design that runs for months on a fixed battery budget, on-board signal processing because satellite bandwidth is too expensive to ship raw data, and mechanical design that survives being frozen into moving pack ice.

If any of those problems sound like yours — remote monitoring, satellite telemetry, power-constrained firmware — that’s exactly the kind of work I consult on.