Technical Blog
Engineering notes from Alpental Systems projects. The articles are written in Korean; each entry has an English summary and a link to the code.
-
STM32F3 Compass, Part 2: Signed Boot, USB Updates and Automatic Rollback with MCUboot
Part 2 of the STM32F3 Discovery compass series adds MCUboot (v2.4.0, Zephyr v4.4.2 sysbuild) with ECDSA P-256 signatures, firmware updates over the ST-LINK USB serial port with mcumgr (SMP), and automatic rollback: a new image is test-booted and confirms itself only after a health check (sensors ready, a valid sample within 5 s, no calibration I/O error). A small Python tool on smpclient uploads, test-boots and verifies an image and reports success, rejected, rolled back or refused. On the real board all nine scenarios passed, including tampered images, a foreign key, a failing health check, a refused downgrade, an interrupted upload and resets during the swap; a good update takes about 32 s. The post also covers what only the hardware revealed (MCUboot v2.4.0's swap using move refused every upgrade with the board's flash map; swap using offset fixed it), a missing module that silently disabled mcumgr, and a review finding where the tool could report "rolled back" while the new image ran. It ends with what the demo does not protect and how real products close those gaps.
-
CI/CD for Embedded Firmware: Builds, Software Integration Tests, HIL Tests and Releases with GitHub Actions
Embedded teams often assume CI/CD is for web services because firmware needs a board to test. This post shows that most of the work runs without hardware, and the rest needs one board and a small computer. It walks through a GitHub Actions pipeline for firmware: reproducible builds with pinned toolchains, unit tests of pure logic on a PC, software integration tests on Zephyr native_sim, QEMU or a simulator, hardware-in-the-loop (HIL) tests on a self-hosted runner that flashes a real board, and signed releases with staged OTA rollout. Examples come from earlier projects on this site (a compass on an STM32F3, a simulated drone flight termination system, a redundant controller bench). It covers the main risk of self-hosted runners on public repositories, why signing keys should not live in CI, a table of benefits and costs, and a suggested order to adopt the stages, starting with builds alone.
-
OTA Updates for Millions of Devices: Design, Hurdles and How to Handle Them
A safe update on one device is the foundation, but a fleet of millions brings different problems. This design post, with no implementation, walks through them: millions of connections and a download stampede (split a small MQTT control channel from a CDN data channel, add jitter, rate limits and maintenance windows; one 60 MB release to a million devices is about 60 TB); a bad release reaching everyone (staged waves with automatic abort on failure and rollback rates, where success means the device passed its own health check); bandwidth and cost (delta updates, streaming installs, site caches); unreliable networks and power (resumable downloads, install conditions, separating install from reboot); security (per-device identity, signing keys in an HSM with approvals, anti-rollback, key rotation, frameworks such as TUF and Uptane); device variety and version drift; fleet visibility; and regulation. It compares building versus using platforms such as AWS IoT Jobs, Azure Device Update, Mender and Eclipse hawkBit, and suggests an order to adopt the pieces.
-
A/B Updates with Automatic Rollback on a Raspberry Pi 5: Brick-Safe Embedded Linux Updates with Yocto and RAUC
A failed update must never leave a device in the field unbootable. This post builds signed A/B updates for a Raspberry Pi 5 with Yocto (scarthgap), RAUC and U-Boot: two read-only root file system slots, a data partition that survives every update, bundles that are refused unless signed by our CA, and a boot health check that gates RAUC's "mark good". A new slot that fails the check, hangs or panics is retried three times and then U-Boot boots the previous slot. On the bench, good updates, a tampered signature, a tampered payload, a bundle signed by another CA, a broken service, a kernel panic and a reset during install all behaved as designed, three runs each; a broken slot fell back in about five minutes. The post covers lessons found only on real hardware: certificates rejected offline because the Pi's clock falls back to 2025, a kernel option that never reached updated devices because the boot partition is shared, a partition table rewritten on every boot, and a "no network" bug that turned out to be the Mac's USB Ethernet adapter. The Pi's one-time-programmable secure boot was deliberately not used.
-
Building and Testing a Drone Flight Termination System Without Hardware: Zephyr, PX4, Gazebo, ROS 2
This post builds a flight termination system (FTS) for a small drone and tests it end to end in simulation, with no hardware. The FTS runs as Zephyr 4.4.2 firmware on native_sim, uses its own IMU, GNSS and barometer, and never trusts the autopilot: it watches PX4's heartbeat and the ground station link from the outside. On a geofence breach, altitude ceiling, loss of control, frozen autopilot, lost link, lost GNSS fix or a manual command, it opens the motor relay and fires a parachute 0.3 s later. PX4 SITL flies an x500 in Gazebo; a C++ bridge plays the role of the wiring and relay; a ROS 2 ground station arms the FTS and runs scripted scenarios. All seven fault scenarios terminated with the right cause and landed at about 5 m/s under the parachute; none of the three false-alarm cases terminated. The post covers the design rules, bugs found in review (a clock step back that terminated at once, a termination hidden by a disarm), a lost fault injection that would have produced a false pass, and why a drone terminated at a fence can still land 22 m outside it.
-
Redundant Controller, Part 3: Replaying Failures with a Ring-Buffer Log and a Mac Viewer
Part 3 of the redundant controller series adds a black-box style log. Each Raspberry Pi records a 64-byte status snapshot every 100 ms and an event record at every event into a 10 MiB ring file, and a Mac viewer merges both controllers' logs into one timeline with filters and CSV export. Records carry the I/O card's clock and boot ID, so the two logs line up to about 1 ms even when the Pis' own clocks are wrong after a power-on, which the bench showed. The main lesson: writing the log from the real-time loop let a busy SD card block it for 3.7 s and caused a real handover. File I/O moved to a separate log server that receives records over a non-blocking socket, and a second fix kept it from dropping records while the SD card is slow. The post reconstructs two failovers from the logs and lists where this kind of recorder is used.
-
Redundant Controller, Part 2: Catching a Controller That Is Alive but Broken with Built-In Test
Part 2 of the redundant controller series adds built-in test to the two Raspberry Pi controllers and the STM32 I/O card: a startup test (PBIT), a check every second (CBIT), a button-controlled test mode, and a TCP command server with a Python client. Part 1 switched over only when the Active went silent. A controller that is alive but broken, such as one whose receive wire from the I/O card is cut, kept sending heartbeats and stayed Active. Now each controller reports its health in its heartbeat, and the I/O card, still the only referee, hands Active to a healthy Standby after a 1.2 s hold. Pulling that wire moved control in about 1.7 s every time. The post covers why the hold is needed when the I/O card itself restarts, a bug found in review where a short glitch could be seen by only one controller, a TCP server that cannot stall the control loop, and where this structure is used in real products.
-
Building a Redundant (Active/Standby) Controller with Two Raspberry Pis and an STM32
This post builds a redundant (Active/Standby) controller from two Raspberry Pi 3B boards and an STM32F3 Discovery running Zephyr 4.4.2. The STM32 is the I/O card and the referee: it grants Active to exactly one controller and applies output commands only from that controller, so a broken link between the controllers can never produce two Actives at the outputs. The Active shares its state with the Standby over a direct Ethernet link, so a takeover continues without a jump. Pulling the Active's power moved control to the Standby in about 101 ms each time. The post covers why the controllers do not decide alone, the problems found on the bench and in code review (a false double-Active alarm, a cross-link dropping every 45 seconds, a frame parser that lost frames after noise), a design assumption that the simultaneous-boot test proved wrong, and products where the same structure applies.
-
Building a Tilt-Compensated Compass with STM32F3 Discovery and Zephyr
This post builds a tilt-compensated compass on the STM32F3 Discovery board (revision E) with Zephyr 4.4.2: the LED that points to magnetic north lights up. It covers confirming the board revision and sensors, a west workspace pinned to a Zephyr release, mapping the sensor axes to the board from measurements, tilt compensation tested on the host, and a hard-iron calibration bug that raw sensor data exposed and a span check fixed. Along the way, a regression in Zephyr's I3G4250D gyroscope driver was found on the board and submitted upstream as a one-line fix. The post ends with products where the same building blocks apply, such as antenna pointing aids, drone and robot heading sensors, and tilt monitoring.