# AGENTS.md PlatformIO / Arduino-framework firmware for a NUCLEO-F042K6 reading two MS5611 barometers (one I2C, one SPI) and emitting validated CSV over the ST-LINK VCP. See `README.md` for wiring tables, CSV format and status codes. ## Commands ```sh pio run # build (the only real verification gate) pio run -t upload # flash over ST-LINK pio device monitor # 115200 baud pio run -t clean ``` There is one env, `nucleo_f042k6`. No lint, format, or typecheck step exists. **Always read the size report at the end of `pio run`.** It is the acceptance criterion for any change (see below). Current: flash 88.8% (29108 / 32768), RAM 32.6% (2004 / 6144). ## Flash budget is the dominant constraint 3.6 KB of flash headroom. This shapes almost every design decision here, and it is the single easiest thing to break without noticing. `-flto` and `-fsingle-precision-constant` in `platformio.ini` are load-bearing. Verified: building without them overflows `FLASH` by **7252 bytes** and fails to link. Both MS5611 driver libraries write their compensation maths with unsuffixed double literals, which otherwise links the double soft-float helpers. Consequences for new code: - Never introduce `double`, unsuffixed floating literals, or `` calls on doubles. Use `float` and `f`-suffixed constants. - Never call `Serial.print(someFloat, digits)` — it resolves to `Print::print(double, int)` and pulls in soft-float. `main.cpp::printFixed2()` exists solely to format floats via scaled integers; use it. - Wrap string literals in `F()` so they stay in flash. - No `String`, no `new`/`delete`. `~BaroChannel()` is deliberately non-virtual and `protected` to keep a destructor slot out of the vtable and avoid linking `operator delete`. Do not "fix" this into a virtual destructor. - `lib_deps` pin exact driver tags (`MS5611#0.5.2`, `MS5611_SPI#0.4.3`). Bumping them can blow the budget; rebuild and check size if you do. ## Architecture - `include/config.h` — every pin, address, rate, threshold and validation limit. Change hardware behaviour **here**, not in the `.cpp` files. - `include/baro_channel.h` / `src/baro_channel.cpp` — `BaroChannel`, the shared state machine: init + PROM/CRC handshake, per-poll validation, error streaks, re-init, and the 1 s error LED pulse. Five pure-virtual `driver*()` hooks are the only per-bus surface. - `src/baro_i2c.cpp` / `src/baro_spi.cpp` — one concrete subclass each, plus a file-static instance exposed through `baroI2cChannel()` / `baroSpiChannel()`. - `src/ms5611_crc.cpp` — standalone PROM CRC-4 (AN520). - `src/main.cpp` — `setup()`/`loop()`, CSV formatting, 10 Hz report scheduling. **The two-file split is mandatory, not stylistic.** `MS5611.h` and `MS5611_SPI.h` each define their own `enum osr_t` and `MS5611_READ_OK`, so they cannot be included in the same translation unit. `baro_channel.h` therefore includes neither driver header, and the channels are handed out via accessor functions. Do not merge these TUs or hoist a driver include into the header. ## Conventions that differ from defaults - **millis() rollover safety**: deadline comparisons use a signed difference — `if ((int32_t)(now - deadline) >= 0)`. Never write `now >= deadline`. - **`poll()` blocks** for ~2 ADC conversions (~5 ms/sensor at OSR 1024). `main.cpp` re-reads `millis()` after polling before evaluating LED and report deadlines. Preserve that if you touch the loop. - **Validation belongs in `BaroChannel`, not the drivers.** Neither library checks the PROM CRC, and `MS5611::read()` returns `MS5611_READ_OK` for an ADC read taken before the conversion completed (which yields 0). Do not simplify the range / stale / all-zero-PROM checks away by trusting driver return codes. - Style is Allman braces, 2-space indent, `_camelCase` private members, `// ` (two spaces) block comments. There is **no `.clang-format`** in the repo — running `clang-format` would reformat everything into LLVM style. Don't. - `SPI` and `I2C` appear in `lib_deps`, but the code uses `Wire` directly and nothing includes the third-party `I2C` library. ## Testing There is no automated test suite. `test/` holds only the stock PlatformIO README, and `pio test` collects 0 cases. Do not claim tests pass. `ms5611_crc.{h,cpp}` is intentionally free of Arduino headers so it can be exercised on the host: ```sh clang++ -std=c++17 -Wall -Wextra -c src/ms5611_crc.cpp -Iinclude -o /tmp/crc.o ``` The differential check against the NuttX reference described in `README.md` was a host-side exercise and is not committed. Anything beyond the CRC needs real hardware: build, flash, and watch the CSV stream. Unplugging a sensor is the quick way to exercise the error paths (`ERR_INIT` / `ERR_CRC`, LED pulse, auto re-init after 10 consecutive failures). ## Notes `.pio/`, `.omo/`, `.junie/` and `.idea/` are gitignored tooling state, not project sources.