Skip to content

How Do You Prove a Rust Rewrite Matches the C Firmware It Replaces?

One industrial gateway written in C and again in Rust on the same STM32F407 and W5500, reconciled by the same 93-test e2e suite.

Grace_Koo

Published September 03, 2026

Original author: kabirzOriginal source (new tab)

How Do You Prove a Rust Rewrite Matches the C Firmware It Replaces?

Project description

io-edge-hub-rust โ€” The Same Device Built Twice, Reconciled by 93 Tests

#Rust #embassy #W5500 #MACRAW #STM32F407 #MCUboot #ModbusTCP #littlefs

๐Ÿ“š Context: kabirz/io-edge-hub-rust โ€” a Rust/embassy rewrite of an industrial IO gateway firmware. Hardware is an STM32F407VET6 with a W5500 on SPI2 and a W25Q128 NOR on SPI1. Last pushed 2026-09-01, MIT declared in Cargo. โœ… Verification: Read from the repository at main. The figures are its own, measured 2026-08-23, and were not reproduced here.


01 โ€” This is the third implementation of one device

The repository is not a new product. It is an existing device written again. io-edge-hub is an industrial acquisition node reading and driving 16 digital inputs, 8 digital outputs and 4 analogue inputs.

What the device exposes:

ServicePortNotes
UDP config8600config commands, v2 bulk upgrade channel, broadcast on 8601
Modbus TCP502FC01-08/15/16, up to 2 masters
Modbus RTUUSART2DE on PA1, 9600 by default
HTTP80gzip SPA, JSON API, 2 connections
WebSocket/ws1 s IO and registers, 10 s info push
FTP21RFC 959, 3 sessions
ShellUSART1115200

History is written as data_MMDD_HHMMSS.raw in a 1 MB by 10 rotation, config and filesystem live on the W25Q128 NOR, and a 30 s IWDG watchdog runs. Firmware upgrades arrive over three channels: UDP, WebSocket and CAN.

The README names its references directly.

ImplementationRole
C / FreeRTOSreference firmware
Zephyrprotocol authority
Rust / embassythis repository

And all three drive the W5500 the same way. The first line of the feature table:

Network โ€” W5500 MACRAW โ†’ embassy-net (smoltcp), static IP (default 192.168.12.101), MAC derived from the UID

crates/firmware/src/net.rs opens with //! W5500 MACRAW + embassy-net bring-up.

  • Driver โ€” chip::W5500 from embassy-net-wiznet = "0.3.0"
  • Bus โ€” SPI2 at 21 MHz on a DMA shared bus
  • Addressing โ€” static IPv4, MAC from XOR-folding the STM32's 96-bit UID onto the WIZnet OUI

02 โ€” A rewrite you can swap in and out

Most rewrites stop at "rewritten". This one set out to keep the boundaries identical.

  • Same MCUboot bootloader, same RSA-2048 signing key. boot.bin is untouched.
  • Same NOR partition layout on the W25Q128: IOCF A/B config (32K ร— 2) plus littlefs from 0xF0000.
  • It mounts what the C build wrote and keeps going. The comparison document records the Rust firmware continuing the same history file the C firmware had been writing.
  • The upgrade channels work in both directions. The Rust firmware can be reflashed through its own UDP v2, WebSocket and CAN channels โ€” and can be flashed back to C.

So either build can go onto a field device with the disk and bootloader intact. That is what makes this a replaceable alternative rather than an experiment.


03 โ€” Reconciled against 93 tests

The verification method is the centre of this repository. The C repository's 93-item e2e suite was reused as-is: same hardware, same Windows/Python host, same list.

SuiteItemsCRust
basic9passpass
udp (8600)8passpass
modbus_tcp14passpass
modbus_rtu3passpass
web (:80)17passpass
websocket3passpass
history (littlefs)5passpass
uart shell6passpass
ftp (:21, 3 concurrent)14passpass
stress10passpass
reboot3passpass
fw_upgrade3passpass
Total939393

All 93 passed in a single pytest run of 5 minutes 18 seconds. The stress suite includes three concurrent FTP sessions, 1 MB transfers and a 30-second mixed load; fw_upgrade includes two real MCUboot swaps.


04 โ€” Measured side by side

From docs/comparison-c-vs-rust.md, dated 2026-08-23:

MetricCRust
Signed image size329,732 B205,708 B
FTP STOR 1 MiB42 KB/s14 KB/s
FTP RETR 1 MiB318 KB/s365 KB/s
3 clients storing 128 KiB eachabout 11 sabout 11 s
UDP v2 firmware push56 KB/s42 KB/s
MCUboot swap and reboot offline window24 s21 s

The image is 38% smaller, which the author attributes to dropping mbedtls and RSA.

The losses are not hidden either. FTP upload runs at a third of C's rate, and the cause is written down: w25q::wait_not_busy polls on a fixed ~1 ms interval after each page program, inflating a 4 KiB page write to about 18 ms per page, where C polls more tightly. The note says a short spin before falling back to a delay should close the gap. Download is slightly faster in Rust, and three concurrent uploads tie because NOR writes are the bottleneck.


05 โ€” Four things the port turned up

The reliability section is the most useful material here. All four were hit and fixed during this port, each with its cause recorded.

  1. Do not sync littlefs on every record. It rewrites the whole inline file tag roughly every eight records and triggers directory compaction, producing a NOR erase storm that starves the storage task. The symptom was every FTP and storage operation timing out with the IWDG resetting in a loop. Restored to C's behaviour: buffered writes with an event-level flush.
  2. Do not leave the session-limit rejecter listening. It should only listen while the limit is reached and stand down after 50 ms of idle; a permanent listener steals the next legitimate connection.
  3. Clean up FTP slot file handles on the next open. After an abnormal transfer, both read and write handles are closed before the slot is reopened, preventing a re-entrant self-loop in littlefs's mlist.
  4. Never run NOR I/O inside a critical section. Erase and program take 10โ€“100 ms, and masking interrupts that long loses UART, W5500 and upgrade-window data. That applies directly to any design with an SPI-attached Ethernet controller.

06 โ€” What is still open

  • The three implementations have not been cross-verified. The comparison document states that three-way interop with the Zephyr build was not done, as the repository has no Zephyr build artefacts. Only C and Rust are confirmed against each other.
  • One CAN direction does not work. Device-to-PCAN produces no waveform on either firmware. The author treats it as a physical-layer issue and leaves the transceiver, termination and wiring to be checked. Both builds behave the same, so it is not a port regression.
  • The README's reference paths are the author's local Windows paths, which nobody outside can follow.

07 โ€” Why it is worth reading

  • Rewrite stories are common; matching the bootloader and the on-disk format so the two builds can flash each other is not.
  • Publishing both the wins and the losses as numbers is rarer still.
  • And all three implementations drive the W5500 in MACRAW. Zephyr's in-tree driver defaults to it and so does embassy-net-wiznet. The same author wrote a separate Zephyr driver using the chip's hardware stack โ€” yet all three product firmwares still stand on MACRAW.

08 โ€” Similar Projects on WIZnet Makers


Q&A

Q. How is the W5500 used here? embassy-net-wiznet 0.3.0's chip::W5500 opened in MACRAW, with embassy-net (smoltcp) above it. SPI2 runs at 21 MHz on a DMA shared bus, the static address defaults to 192.168.12.101, and the MAC is derived from the STM32's 96-bit UID.

Q. How closely does it match the C build? Same MCUboot and same RSA-2048 signing key, and the NOR partition layout is identical, so it mounts the littlefs the C build wrote and continues the same history file. Its upgrade channels can also flash C back.

Q. Is the Rust build better? It depends on the metric. The signed image is 38% smaller, and FTP download and the MCUboot swap window are slightly faster. FTP upload is well behind at 14 KB/s against 42 KB/s, and the author points to the NOR page-program polling interval as the cause.

Q. Are the three implementations interchangeable? C and Rust are confirmed. Three-way verification including the Zephyr build has not been done, as the comparison document states.


Original Link: https://github.com/kabirz/io-edge-hub-rust

Comparison report: docs/comparison-c-vs-rust.md ยท Per-module docs: docs/embassy.md ยท Zephyr build: kabirz/iot-zephyr-app

Comments

Similar projects you might like

Comments