Twenty bench labs on one parts bin and four Linux hosts, with every pairing checked against headers, voltages and silicon before it was written down. One 40-pin HAT per host. One instrument per lab. An acceptance test you can fail.
158 pages, 81 figures, 20 labs. Each lab stands on its own: what it alone owns, what the earlier draft of this material got wrong, the kit it takes from the bin, a system architecture figure, a schematic with a wiring table, a bench layout, a UML view, a data-flow sketch, numbered steps with real commands, an acceptance test, the practices that keep it honest, its pitfalls and its sources.
Read it. The whole volume is in chapters/ as Markdown with
its figures beside it. Start with
About this edition, or take a lab from the
table below.
Or build it. The PDF and a single self-contained HTML file come from the same source and stay local:
python build.py --chapter 5
That writes lab-05-mcc-118-100-ks-s-voltage-recorder.pdf and a matching
self-contained .html with its four figures inlined.
Contents Read it · What has been run · What this is and is not · The rules the labs obey · The twenty labs · Building · Checks · Layout · Requirements · Licence
This is an audited cookbook, not a tested codebase. It began as two documents: a kit-only twenty-lab split, and a longer architectural draft that reused the same parts bin and proposed pairings that cannot be built from it. This edition keeps the ambition of the second and throws out its impossible wiring, lab by lab, with the correction printed beside the lab it belongs to rather than hidden in a changelog. Thirteen of the twenty carry such a correction.
What each lab commits to is its acceptance test, written to be checkable on hardware and to be failable. "A continuous ping is a fail" is a real line in one of them. A lab that cannot reach its test because the bench lacks something says so in place of a number, because a blank is honest and an unmeasured number is a claim the first careful reader will check.
The parts constrain the labs. Every lab was written against a fixed inventory, so none opens with a shopping list. Where a lab would be easier with an instrument that is not here, it names the substitute.
This is the companion to a volume of a different kind. The twenty projects in embedded-linux-projects are software engineering depth: the Yocto Project, kernel drivers, real time, over-the-air updates, trusted execution. These twenty are bench integration discipline, and the two share a parts bin without sharing a lab.
Six rules decide what a lab may do, and every lab in the table was checked against them. They are in full in About this edition.
- No new parts. Antennas are in scope. A data SIM is assumed for four labs only, and without one those labs stop at bring-up, which is still a pass.
- HAT exclusivity. Six boards each own the 40-pin header. Fit one. The official display does not consume the header.
- The NanoPi is 24-pin. It cannot accept any 40-pin HAT.
- 3.3 V logic. Every host pin is 3.3 V, and the breadboard supply is a labelled rail splitter rather than a licence to mix.
- One instrument per lab. Six sensor boards are six different instruments, never interchangeable.
- Linux owns the system. A microcontroller speaks over USB, a serial line or a bus to a host that owns storage, time, the network and the interface. A bare-metal sketch is allowed only where a Linux host still records it.
The numbering is the cookbook's own, so the labs appear here in reading order and keep their P-numbers. The parts are the cellular group, the instruments, the sensor shields on the microcontroller, the hosts and displays, and the rails, sidecars and fleet that close it.
Written is not run. Every lab below ends in an acceptance test that can be
failed, and STATUS.md says which of them have actually been
compiled, wired and passed, with the board revision and the date. At the time
of writing one lab has been run on hardware and one has been exercised on
synthetic data. Read that page before trusting any step here.
| # | Lab | Host and exclusive part | Owns |
|---|---|---|---|
| P01 | LTE Cat-4 motion-triggered gateway | Pi 4, SIM7600E-H, ADXL345 | the only Cat-4 default route |
| P02 | NB-IoT PSM/eDRX CoAP field node | Pi 3, SIM7020E | NB-IoT and the duty cycle |
| P03 | Cat-M MQTT node with GNSS stamp | Pi 3B+, SIM7070G | Cat-M and the timestamp rule |
| P04 | STWIN.box vibration and ultrasound USB gateway | Pi 4, STWIN.box | 6 kHz vibration and the microphones |
| P05 | MCC 118 100 kS/s voltage recorder | Pi 4, MCC 118 | the calibrated analogue path |
| P06 | 8x8 ToF occupancy kiosk | Pi 3, LCD, Nucleo, 53L8A1 | multi-zone ranging |
| P07 | Consumer 9-DoF plus climate | Nucleo, IKS4A1 | consumer MEMS and humidity |
| P08 | Industrial high-g and dual-scale baro | Nucleo, IKS5A1 | simultaneous low-g and high-g |
| P09 | NanoPi NEO Air headless hub | NanoPi NEO Air, ADXL345 | the only non-Raspberry host |
| P10 | PPK2 energy characterisation bench | Pi 4, PPK2, ESP32 | microamp figures |
| P11 | Explorer700 field console | Pi 3B+, Explorer700 | the real-time clock and the overlay |
| P12 | Official DSI operator glass and broker | Pi 4, display, keyboard | the panel and the broker |
| P13 | Dual-radio signalling failover | two Pis, two modems | multi-homing with a source tag |
| P14 | USB-TTL provisioning jig | Pi 3, serial cable, two ESP boards | the cable as a factory tool |
| P15 | Mixed-voltage discipline | the breadboard supply, any host | the electrical safety lab |
| P16 | ESP32 Wi-Fi/BLE sidecar | Pi 4, ESP32 | the sidecar pattern |
| P17 | ESP8266 AT modem on the NanoPi | NanoPi, ESP8266 | the air-gap radio |
| P18 | Nucleo USB-CDC recorder | Pi 4, bare Nucleo | high-rate gadget traffic |
| P19 | SPI LCD tilt meter | Pi 3, LCD, ADXL345 | the display as a field instrument |
| P20 | Four-host fleet health | all four hosts | orchestration, no new physics |
The appendix carries the lab order and teardown, the command reference, both header pinouts, which lab owns which part, and a glossary.
Work in the order the appendix gives if the boards are bare. The rails lab is first and is not optional, and the fleet file is last so that it reflects a known sitting.
A lab's host side can be written and tested long before its hardware is wired, and one has been. It lives in this repository beside the chapter it belongs to, because a reader who has just read chapter 4 should not have to go looking.
| Path | What it is |
|---|---|
p04/ |
the host half of the vibration and ultrasound gateway, in C, with its own README |
p04/src/ |
the capture format, the transform, the classification, the invariants |
p04/tests/ |
the suite, four groups, no framework and no device |
make -C p04 check the suite: no hardware, no serial port, no network
make -C p04 demo the lab's own demonstration, on synthetic data
It is C because the lab is embedded Linux. The deliverable of a lab is the
program, not a description of one. No allocation on the data path, library code
that returns a status rather than exiting, and samples that stay int16_t
until the one place where they become floats.
Python here builds the book and nothing else. build.py, mdbuild.py and
lint.py turn the sources into chapters, figures and the reading editions.
They do not touch a bus, a device or a lab, and no lab depends on them.
Most of these labs are the same shape: something arrives over a line in
sequence-numbered pieces and a decision is made from it. P02 and P03 drive a
modem, P06 reads a ranging grid, P18 counts gaps in a sequence, P13 decides on
a timeout. When the second of them is written, the shared half moves out of
p04/src and into a library beside it, rather than being guessed at now.
Three rules hold across all of them. No default threshold anywhere, because a threshold without the baseline it was measured against is not a measurement. No invented wire format, so where a vendor owns the frame layout the program says so and stops. And transport before signal, because a spectrum computed over a stream with holes in it is a picture of the holes.
Nothing here has been run against hardware, and nothing has been compiled on the authoring laptop, which has no compiler. The workflow is the first compile. Each lab's README says so in its own words.
python build.py --chapter 5 one lab, PDF and self-contained HTML
python build.py --chapters all twenty, one file each
python build.py the whole volume, PDF and one HTML file
python mdbuild.py the Markdown edition, chapters and figures
Built output is not committed. The PDF and the HTML are reading editions, and they are rebuilt from this source rather than carried in it.
python lint.py house rules over every lab
The linter refuses dashes, non-ASCII inside a code block, a code line too long
to print, and a lab missing any part of its skeleton or any of its four
figures. The workflow in .github/workflows/ runs it on every push, checks
that every lab has its four figures and its lab line, and checks that the
Markdown edition is in step with the source rather than behind it.
It also refuses a STATUS.md that has lost a lab or grown a
state. The four states are written, checked, run and passed, and a row whose
state is anything else fails the run, because the usual way a status page rots
is a new word invented in passing to avoid writing down one of the four.
| Path | What it is |
|---|---|
chapters/ |
the Markdown edition, one file per lab, generated |
figures/ |
one TikZ or circuitikz source per figure, and its SVG |
sections/ |
the LaTeX source, one file per lab |
main.tex |
preamble, the lab macro, the callout boxes, part structure |
tikz_preamble.tex |
shared figure styles: blocks, UML, bench art |
build.py |
figures to SVG, the PDF, and the single-file HTML |
mdbuild.py |
the Markdown edition |
lint.py |
house-style check |
SOURCE.md |
the cookbook this edition illustrates, transcribed |
AUTHORING.md |
the contract every lab follows |
CONTENTS.md |
the lab table, generated, which the table above follows |
build/ |
scratch output, ignored, safe to delete |
Lab files use a p prefix so that a cross-reference or a copied figure can
never silently resolve against a sibling volume's files. The volume's identity
lives in exactly one DOC block in build.py, which refuses to run if the
folder name stops matching it.
MiKTeX or TeX Live with pdflatex, latex, dvisvgm, circuitikz,
tcolorbox and listings; Python 3.10 or newer. No Python package outside the
standard library is needed.
MIT, see LICENSE. Each lab's Sources section records where its
prior art came from, because a reader following a vendor wiki or a licence-gated
driver needs to know what that commits them to before writing code around it.