Initial commit.

This commit is contained in:
conky
2026-08-31 15:45:58 +03:00
commit ec9805b01a
22 changed files with 1536 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
.pio
.omo
.junie
.idea

104
AGENTS.md Normal file
View File

@@ -0,0 +1,104 @@
# 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 `<math.h>` 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.

146
README.md Normal file
View File

@@ -0,0 +1,146 @@
# Barometric pressure test
## Goals
1. Read barometric pressure from two sensors. One connected to the MCU with SPI and the other connected with I2C bus.
2. The reading must be done in real time and data should be checked for validity.
3. The output should be sent to a serial port.
4. In case of reading error the LED should be turned on for 1 second. Each sensor has its own LED.
## Hardware
- MCU - STM32F042K6T6 (Nucleo-32, board MB1180) - 32 KB flash, 6 KB RAM
- Barometric pressure sensor - 2x GY-63 (MS5611)
## Wiring
Pin assignments follow ST UM1956 Table 10 and the MB1180 C.2 schematic, and match
the STM32duino defaults for this variant, so `Wire.begin()` and `SPI.begin()` need
no explicit pin overrides.
![Wiring diagram](docs/wiring.png)
Vector source: [`docs/wiring.svg`](docs/wiring.svg). Pins there are grouped by
function, not by physical header order.
The same wiring drawn physically, with the real header order from UM1956
Table 10, so it is clear which hole each jumper goes into:
![Physical wiring diagram](docs/wiring-physical.png)
Vector source: [`docs/wiring-physical.svg`](docs/wiring-physical.svg). Note that
SPI is split across both connectors: MOSI, MISO and CS are on CN3, while SCK is
CN4 pin 15.
### Baro0 - I2C (GY-63 <=> STM32F042)
| GY-63 | Nucleo | STM32 | Note |
|-------|--------|-------|------|
| VCC | 3V3 | - | |
| GND | GND | - | |
| PS | 3.3V | - | high selects I2C |
| SDA | A4 | PB7 | `PIN_WIRE_SDA`, reaches the A4 pad through SB18 |
| SCL | A5 | PB6 | `PIN_WIRE_SCL`, reaches the A5 pad through SB16 |
| CSB | GND | - | sets address `0x77`; tie to VCC for `0x76` |
The MS5611 address is `1110 11Cx`, where C is the **complement** of CSB
(datasheet p.12). `CSB` must not be left floating. If you strap it high, change
`BARO_I2C_ADDRESS` in [`include/config.h`](include/config.h).
### Baro1 - SPI (GY-63 <=> STM32F042)
| GY-63 | Nucleo | STM32 | Note |
|-------|--------|-------|------|
| VCC | 3V3 | - | |
| GND | GND | - | |
| PS | GND | - | low selects SPI |
| SCLK | D13 | PB3 | SPI1_SCK |
| SDI | D11 | PB5 | SPI1_MOSI |
| SDO | D12 | PB4 | SPI1_MISO |
| CSB | D10 | PA11 | chip select, `BARO_SPI_CS_PIN` |
SPI1 has to stay on this pin group. The alternative group (PA5/PA6/PA7) is
unusable while I2C is active: with the factory-default solder bridges SB16 and
SB18 closed, PA6 shares a net with PB6 (SCL) and PA5 shares a net with PB7 (SDA).
### Error LEDs
Two external LEDs, one per sensor, each in series with roughly 510 R to GND.
| Signal | Nucleo | STM32 |
|--------|--------|-------|
| I2C sensor error | D3 | PB0 |
| SPI sensor error | D6 | PB1 |
Both pins are set in [`include/config.h`](include/config.h).
## Build and run
```sh
pio run # build
pio run -t upload # flash over ST-LINK
pio device monitor # 115200 baud, ST-LINK Virtual COM Port
```
`Serial` is USART2 on PA2/PA15, wired to the ST-LINK VCP, so no USB-serial
adapter is needed.
The image is close to the flash ceiling (about 89% of 32 KB). `-flto` and
`-fsingle-precision-constant` in [`platformio.ini`](platformio.ini) are required,
not cosmetic: both MS5611 drivers write their compensation maths with unsuffixed
double literals, which otherwise links roughly 5 KB of double soft-float helpers
and overflows flash.
## Output
CSV at 10 Hz on the serial port, with a header line printed once at startup:
```
ms,i2c_status,i2c_p_mbar,i2c_t_c,spi_status,spi_p_mbar,spi_t_c
1043,OK,1013.24,24.31,OK,1013.19,24.44
1143,OK,1013.25,24.31,ERR_READ,1013.19,24.44
```
Pressure is in mbar, temperature in degrees C, both to two decimals. The status
column is authoritative: when it is not `OK`, the two values next to it are the
last ones successfully computed, not fresh readings.
| Status | Meaning |
|--------|---------|
| `INIT` | not initialised yet |
| `OK` | reading passed every check |
| `ERR_INIT` | reset / PROM handshake did not complete |
| `ERR_CRC` | factory calibration failed CRC-4, or is all-zero / all-ones |
| `ERR_READ` | driver reported a bus or ADC failure |
| `ERR_RANGE` | value outside the MS5611 operating envelope |
| `ERR_STALE` | sensor still answers but stopped producing new samples |
Sensors are polled continuously, as fast as the ADC conversions allow (roughly
90 Hz per sensor at OSR 1024); the serial report is throttled to 10 Hz.
## Validation
Neither driver library detects every failure on its own, so the checks live in
[`BaroChannel`](src/baro_channel.cpp):
- **PROM CRC-4** at init, per datasheet p.13 / AN520. Neither library verifies it.
- **All-zero / all-ones PROM screening**, because an absent part reads back as
one of those, `0xFFFF` is accepted by both libraries' `reset()`, and an
all-zero PROM satisfies the CRC.
- **Driver return code** on every read.
- **Range check** against 10..1200 mbar and -40..+85 C. This is the one that
catches the failure the I2C driver misses entirely: an ADC read taken before
the conversion completes returns 0 (datasheet p.11), and `MS5611::read()`
still reports `MS5611_READ_OK` for it.
- **Stale detection** - bit-identical pressure *and* temperature for 32
consecutive polls means the part stopped converting while still acknowledging.
Any failure lights that sensor's LED for 1 second (non-blocking, extended if
further errors follow). After 10 consecutive failures the channel is taken back
through the full reset and PROM handshake, so a sensor that was unplugged and
reconnected recovers on its own.
The CRC-4 implementation was verified differentially on the host against the
independent NuttX reference over 200000 randomised PROM images.
## References
- [Barometric Pressure Sensor MS5611](docs/datasheets/ENG_DS_MS5611-01BA03_B3.pdf)
- [STM32F042K6T6 Nucleo](docs/datasheets/DS_stm32f042k6.pdf)
- [STM32 Nucleo-32 boards (MB1180)](https://www.st.com/resource/en/user_manual/um1956-stm32-nucleo32-boards-mb1180-stmicroelectronics.pdf) - UM1956, LED and connector tables

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
docs/wiring-physical.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 637 KiB

330
docs/wiring-physical.svg Normal file
View File

@@ -0,0 +1,330 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1600 1120" width="1600" height="1120">
<defs>
<linearGradient id="sheen" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color="#ffffff" stop-opacity="0.10"/>
<stop offset="55%" stop-color="#ffffff" stop-opacity="0.02"/>
<stop offset="100%" stop-color="#000000" stop-opacity="0.10"/>
</linearGradient>
<linearGradient id="usb" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#e9ecef"/>
<stop offset="50%" stop-color="#c7ccd1"/>
<stop offset="100%" stop-color="#9ba2a9"/>
</linearGradient>
</defs>
<style>
text { font-family: "Helvetica Neue", Arial, sans-serif; fill: #1b2733; }
.mono { font-family: "SF Mono", "DejaVu Sans Mono", Menlo, Consolas, monospace; }
.title{ font-size: 25px; font-weight: 700; }
.sub { font-size: 13.5px; fill: #5a6b7d; }
.hdr { font-size: 16px; font-weight: 700; }
.spec { font-size: 12.5px; fill: #5a6b7d; }
.note { font-size: 12.5px; }
.silk { fill: #ffffff; font-size: 11px; font-weight: 600; }
.silkb{ fill: #ffffff; font-size: 15px; font-weight: 700; letter-spacing: 1px; }
.bsilk{ font-size: 10px; fill: #2f4f7f; font-weight: 600; }
.panel{ fill: #f4f6f8; stroke: #b6c1cc; stroke-width: 1.5; }
.jw { fill: none; stroke-width: 4.6; stroke-linecap: round; }
.c-v33 { stroke: #d62828; }
.c-gnd { stroke: #22252a; }
.c-sda { stroke: #1f6feb; }
.c-scl { stroke: #00a0b0; }
.c-sck { stroke: #e07b00; }
.c-mosi{ stroke: #b5179e; }
.c-miso{ stroke: #7209b7; }
.c-cs { stroke: #c99700; }
.c-led { stroke: #2a9d4a; }
.rail-g { fill: none; stroke: #22252a; stroke-width: 7; stroke-linecap: round; }
.rail-v { fill: none; stroke: #d62828; stroke-width: 7; stroke-linecap: round; }
</style>
<rect x="0" y="0" width="1600" height="1120" fill="#ffffff"/>
<text class="title" x="800" y="38" text-anchor="middle">Physical wiring - NUCLEO-F042K6 + 2 x GY-63 (MS5611)</text>
<text class="sub" x="800" y="62" text-anchor="middle">Board seen from the component side, USB at the top. Header order follows ST UM1956 Table 10. Highlighted pads are the ones this firmware uses.</text>
<!-- =================================================================== -->
<!-- NUCLEO-F042K6 -->
<!-- =================================================================== -->
<g id="nucleo">
<!-- USB Micro-B, CN1 -->
<rect x="762" y="122" width="66" height="34" rx="3" fill="url(#usb)" stroke="#7b8288" stroke-width="1.2"/>
<rect x="770" y="128" width="50" height="14" rx="2" fill="#6f767c"/>
<!-- PCB -->
<rect x="700" y="150" width="190" height="530" rx="8" fill="#f7f8f9" stroke="#c3c9ce" stroke-width="1.6"/>
<rect x="706" y="156" width="178" height="518" rx="5" fill="none" stroke="#3a7ac0" stroke-width="1"/>
<!-- ST-LINK section -->
<rect x="718" y="163" width="24" height="20" rx="3" fill="#2f9e44" stroke="#1e6f2f" stroke-width="1"/>
<text class="bsilk" x="730" y="196" text-anchor="middle">LD1</text>
<rect x="762" y="248" width="66" height="40" rx="2" fill="#1b1b1b"/>
<text x="795" y="272" text-anchor="middle" font-size="9" fill="#8a8a8a" class="mono">ST-LINK</text>
<text x="795" y="232" text-anchor="middle" font-size="19" font-weight="700" fill="#03234b">ST</text>
<!-- target MCU, LQFP32 -->
<rect x="757" y="424" width="76" height="76" rx="3" fill="#1b1b1b"/>
<circle cx="766" cy="433" r="3.4" fill="#4a4a4a"/>
<text x="795" y="466" text-anchor="middle" font-size="8.5" fill="#8a8a8a" class="mono">STM32F042</text>
<g fill="#5b6167">
<rect x="749" y="432" width="8" height="3"/><rect x="749" y="443" width="8" height="3"/>
<rect x="749" y="454" width="8" height="3"/><rect x="749" y="465" width="8" height="3"/>
<rect x="749" y="476" width="8" height="3"/><rect x="749" y="487" width="8" height="3"/>
<rect x="833" y="432" width="8" height="3"/><rect x="833" y="443" width="8" height="3"/>
<rect x="833" y="454" width="8" height="3"/><rect x="833" y="465" width="8" height="3"/>
<rect x="833" y="476" width="8" height="3"/><rect x="833" y="487" width="8" height="3"/>
</g>
<!-- bottom features -->
<rect x="716" y="640" width="20" height="16" rx="2" fill="#2f9e44" stroke="#1e6f2f" stroke-width="1"/>
<text class="bsilk" x="726" y="668" text-anchor="middle">LD2</text>
<rect x="854" y="640" width="20" height="16" rx="2" fill="#2f9e44" stroke="#1e6f2f" stroke-width="1"/>
<text class="bsilk" x="864" y="668" text-anchor="middle">LD3</text>
<rect x="778" y="638" width="34" height="20" rx="3" fill="#d9dde1" stroke="#9aa1a8" stroke-width="1"/>
<text class="bsilk" x="795" y="668" text-anchor="middle">B1</text>
<text class="bsilk" x="714" y="215" text-anchor="middle" font-size="9">CN3</text>
<text class="bsilk" x="876" y="215" text-anchor="middle" font-size="9">CN4</text>
</g>
<!-- header pads: generated positions, CN3 x=714 / CN4 x=876, pin i y = 240+(i-1)*28 -->
<g id="pads">
<!-- CN3 gold rings -->
<g fill="#d9b64a">
<circle cx="714" cy="240" r="7"/><circle cx="714" cy="268" r="7"/><circle cx="714" cy="296" r="7"/>
<circle cx="714" cy="324" r="7"/><circle cx="714" cy="352" r="7"/><circle cx="714" cy="380" r="7"/>
<circle cx="714" cy="408" r="7"/><circle cx="714" cy="436" r="7"/><circle cx="714" cy="464" r="7"/>
<circle cx="714" cy="492" r="7"/><circle cx="714" cy="520" r="7"/><circle cx="714" cy="548" r="7"/>
<circle cx="714" cy="576" r="7"/><circle cx="714" cy="604" r="7"/><circle cx="714" cy="632" r="7"/>
<circle cx="876" cy="240" r="7"/><circle cx="876" cy="268" r="7"/><circle cx="876" cy="296" r="7"/>
<circle cx="876" cy="324" r="7"/><circle cx="876" cy="352" r="7"/><circle cx="876" cy="380" r="7"/>
<circle cx="876" cy="408" r="7"/><circle cx="876" cy="436" r="7"/><circle cx="876" cy="464" r="7"/>
<circle cx="876" cy="492" r="7"/><circle cx="876" cy="520" r="7"/><circle cx="876" cy="548" r="7"/>
<circle cx="876" cy="576" r="7"/><circle cx="876" cy="604" r="7"/><circle cx="876" cy="632" r="7"/>
</g>
<g fill="#ffffff">
<circle cx="714" cy="240" r="3"/><circle cx="714" cy="268" r="3"/><circle cx="714" cy="296" r="3"/>
<circle cx="714" cy="324" r="3"/><circle cx="714" cy="352" r="3"/><circle cx="714" cy="380" r="3"/>
<circle cx="714" cy="408" r="3"/><circle cx="714" cy="436" r="3"/><circle cx="714" cy="464" r="3"/>
<circle cx="714" cy="492" r="3"/><circle cx="714" cy="520" r="3"/><circle cx="714" cy="548" r="3"/>
<circle cx="714" cy="576" r="3"/><circle cx="714" cy="604" r="3"/><circle cx="714" cy="632" r="3"/>
<circle cx="876" cy="240" r="3"/><circle cx="876" cy="268" r="3"/><circle cx="876" cy="296" r="3"/>
<circle cx="876" cy="324" r="3"/><circle cx="876" cy="352" r="3"/><circle cx="876" cy="380" r="3"/>
<circle cx="876" cy="408" r="3"/><circle cx="876" cy="436" r="3"/><circle cx="876" cy="464" r="3"/>
<circle cx="876" cy="492" r="3"/><circle cx="876" cy="520" r="3"/><circle cx="876" cy="548" r="3"/>
<circle cx="876" cy="576" r="3"/><circle cx="876" cy="604" r="3"/><circle cx="876" cy="632" r="3"/>
</g>
<!-- used pads get a coloured ring -->
<g fill="none" stroke-width="2.6">
<circle cx="714" cy="380" r="10.5" stroke="#2a9d4a"/>
<circle cx="714" cy="464" r="10.5" stroke="#2a9d4a"/>
<circle cx="714" cy="576" r="10.5" stroke="#c99700"/>
<circle cx="714" cy="604" r="10.5" stroke="#b5179e"/>
<circle cx="714" cy="632" r="10.5" stroke="#7209b7"/>
<circle cx="876" cy="268" r="10.5" stroke="#22252a"/>
<circle cx="876" cy="408" r="10.5" stroke="#00a0b0"/>
<circle cx="876" cy="436" r="10.5" stroke="#1f6feb"/>
<circle cx="876" cy="604" r="10.5" stroke="#d62828"/>
<circle cx="876" cy="632" r="10.5" stroke="#e07b00"/>
</g>
</g>
<!-- header silkscreen -->
<g class="bsilk">
<text x="729" y="244">D1</text> <text x="729" y="272">D0</text> <text x="729" y="300">RST</text>
<text x="729" y="328">GND</text> <text x="729" y="356">D2</text> <text x="729" y="384">D3</text>
<text x="729" y="412">D4</text> <text x="729" y="440">D5</text> <text x="729" y="468">D6</text>
<text x="729" y="496">D7</text> <text x="729" y="524">D8</text> <text x="729" y="552">D9</text>
<text x="729" y="580">D10</text> <text x="729" y="608">D11</text> <text x="729" y="636">D12</text>
<text x="861" y="244" text-anchor="end">VIN</text> <text x="861" y="272" text-anchor="end">GND</text>
<text x="861" y="300" text-anchor="end">RST</text> <text x="861" y="328" text-anchor="end">5V</text>
<text x="861" y="356" text-anchor="end">A7</text> <text x="861" y="384" text-anchor="end">A6</text>
<text x="861" y="412" text-anchor="end">A5</text> <text x="861" y="440" text-anchor="end">A4</text>
<text x="861" y="468" text-anchor="end">A3</text> <text x="861" y="496" text-anchor="end">A2</text>
<text x="861" y="524" text-anchor="end">A1</text> <text x="861" y="552" text-anchor="end">A0</text>
<text x="861" y="580" text-anchor="end">REF</text> <text x="861" y="608" text-anchor="end">3V3</text>
<text x="861" y="636" text-anchor="end">D13</text>
</g>
<!-- =================================================================== -->
<!-- GY-63 #2 - SPI, left -->
<!-- =================================================================== -->
<g id="gy63-spi" transform="translate(300,470)">
<rect x="0" y="0" width="150" height="190" rx="5" fill="#174a91" stroke="#0e2f5e" stroke-width="1.5"/>
<rect x="0" y="0" width="150" height="190" rx="5" fill="url(#sheen)"/>
<circle cx="26" cy="27" r="14" fill="#c9a227"/><circle cx="26" cy="27" r="10" fill="#ffffff"/>
<g fill="#d9b64a">
<circle cx="133" cy="24" r="8"/><circle cx="133" cy="47" r="8"/><circle cx="133" cy="70" r="8"/>
<circle cx="133" cy="93" r="8"/><circle cx="133" cy="116" r="8"/><circle cx="133" cy="139" r="8"/>
<circle cx="133" cy="162" r="8"/>
</g>
<g fill="#10305f">
<circle cx="133" cy="24" r="3.6"/><circle cx="133" cy="47" r="3.6"/><circle cx="133" cy="70" r="3.6"/>
<circle cx="133" cy="93" r="3.6"/><circle cx="133" cy="116" r="3.6"/><circle cx="133" cy="139" r="3.6"/>
<circle cx="133" cy="162" r="3.6"/>
</g>
<text class="silk" x="120" y="28" text-anchor="end">PS</text>
<text class="silk" x="120" y="51" text-anchor="end">SDO</text>
<text class="silk" x="120" y="74" text-anchor="end">CSB</text>
<text class="silk" x="120" y="97" text-anchor="end">SDA</text>
<text class="silk" x="120" y="120" text-anchor="end">SCL</text>
<text class="silk" x="120" y="143" text-anchor="end">GND</text>
<text class="silk" x="120" y="166" text-anchor="end">VCC</text>
<rect x="36" y="88" width="52" height="42" rx="3" fill="#b9bfc6" stroke="#7e878f" stroke-width="1.2"/>
<rect x="40" y="92" width="44" height="34" rx="2" fill="#a8aeb5"/>
<circle cx="62" cy="109" r="7" fill="#5c646c"/><circle cx="62" cy="109" r="4" fill="#3a4148"/>
<rect x="38" y="156" width="20" height="11" rx="1" fill="#1b1b1b"/>
<rect x="66" y="156" width="20" height="11" rx="1" fill="#1b1b1b"/>
<rect x="72" y="140" width="14" height="9" rx="1" fill="#d8801f"/>
<rect x="36" y="70" width="18" height="10" rx="1" fill="#1b1b1b"/>
<rect x="68" y="70" width="18" height="10" rx="1" fill="#1b1b1b"/>
<rect x="56" y="52" width="12" height="8" rx="1" fill="#1b1b1b"/>
<rect x="74" y="52" width="12" height="8" rx="1" fill="#1b1b1b"/>
<text class="silkb" x="14" y="150" transform="rotate(-90,14,150)">MS5611</text>
</g>
<text class="hdr" x="375" y="452" text-anchor="middle">GY-63 #2 - SPI</text>
<!-- =================================================================== -->
<!-- GY-63 #1 - I2C, right -->
<!-- =================================================================== -->
<g id="gy63-i2c" transform="translate(1150,330)">
<rect x="0" y="0" width="150" height="190" rx="5" fill="#174a91" stroke="#0e2f5e" stroke-width="1.5"/>
<rect x="0" y="0" width="150" height="190" rx="5" fill="url(#sheen)"/>
<circle cx="124" cy="163" r="14" fill="#c9a227"/><circle cx="124" cy="163" r="10" fill="#ffffff"/>
<g fill="#d9b64a">
<circle cx="17" cy="24" r="8"/><circle cx="17" cy="47" r="8"/><circle cx="17" cy="70" r="8"/>
<circle cx="17" cy="93" r="8"/><circle cx="17" cy="116" r="8"/><circle cx="17" cy="139" r="8"/>
<circle cx="17" cy="162" r="8"/>
</g>
<g fill="#10305f">
<circle cx="17" cy="24" r="3.6"/><circle cx="17" cy="47" r="3.6"/><circle cx="17" cy="70" r="3.6"/>
<circle cx="17" cy="93" r="3.6"/><circle cx="17" cy="116" r="3.6"/><circle cx="17" cy="139" r="3.6"/>
<circle cx="17" cy="162" r="3.6"/>
</g>
<text class="silk" x="30" y="28">VCC</text><text class="silk" x="30" y="51">GND</text>
<text class="silk" x="30" y="74">SCL</text><text class="silk" x="30" y="97">SDA</text>
<text class="silk" x="30" y="120">CSB</text><text class="silk" x="30" y="143">SDO</text>
<text class="silk" x="30" y="166">PS</text>
<rect x="62" y="58" width="52" height="42" rx="3" fill="#b9bfc6" stroke="#7e878f" stroke-width="1.2"/>
<rect x="66" y="62" width="44" height="34" rx="2" fill="#a8aeb5"/>
<circle cx="88" cy="79" r="7" fill="#5c646c"/><circle cx="88" cy="79" r="4" fill="#3a4148"/>
<rect x="64" y="22" width="20" height="11" rx="1" fill="#1b1b1b"/>
<rect x="92" y="22" width="20" height="11" rx="1" fill="#1b1b1b"/>
<rect x="64" y="40" width="14" height="9" rx="1" fill="#d8801f"/>
<rect x="64" y="110" width="18" height="10" rx="1" fill="#1b1b1b"/>
<rect x="96" y="110" width="18" height="10" rx="1" fill="#1b1b1b"/>
<rect x="64" y="128" width="12" height="8" rx="1" fill="#1b1b1b"/>
<rect x="82" y="128" width="12" height="8" rx="1" fill="#1b1b1b"/>
<text class="silkb" x="140" y="38" transform="rotate(90,140,38)">MS5611</text>
</g>
<text class="hdr" x="1225" y="312" text-anchor="middle">GY-63 #1 - I2C</text>
<!-- =================================================================== -->
<!-- POWER RAILS -->
<!-- =================================================================== -->
<path class="rail-g" d="M 300,940 L 1290,940"/>
<path class="rail-v" d="M 240,988 L 1400,988"/>
<text class="mono" x="1302" y="945" font-size="13" fill="#22252a">GND rail</text>
<text class="mono" x="1412" y="993" font-size="13" fill="#d62828">3V3 rail</text>
<!-- =================================================================== -->
<!-- JUMPER WIRES -->
<!-- =================================================================== -->
<!-- I2C signals: module #1 -> CN4 A5 / A4 -->
<path class="jw c-scl" d="M 1167,400 C 1080,398 970,406 883,408"/>
<path class="jw c-sda" d="M 1167,423 C 1080,422 970,432 883,436"/>
<!-- SPI signals: module #2 -> CN3 D10 / D11 / D12 -->
<path class="jw c-cs" d="M 433,540 C 540,545 610,570 707,576"/>
<path class="jw c-mosi" d="M 433,563 C 540,570 610,598 707,604"/>
<path class="jw c-miso" d="M 433,517 C 560,520 620,614 707,632"/>
<!-- SCK has to travel to the far side: CN4 pin 15 -->
<path class="jw c-sck" d="M 433,586 C 560,600 520,732 660,742 C 820,752 962,730 962,670 C 962,642 918,632 883,632"/>
<!-- power taps from the board -->
<path class="jw c-v33" d="M 883,604 C 940,616 946,860 1006,988"/>
<path class="jw c-gnd" d="M 883,268 C 1006,300 1028,760 1046,940"/>
<!-- module #1 power and straps -->
<path class="jw c-v33" d="M 1167,354 C 1062,404 1010,790 1120,988"/>
<path class="jw c-gnd" d="M 1167,377 C 1090,448 1064,824 1176,940"/>
<path class="jw c-gnd" d="M 1167,446 C 1122,600 1152,852 1244,940"/>
<path class="jw c-v33" d="M 1167,492 C 1206,650 1258,880 1334,988"/>
<!-- module #2 power and strap -->
<path class="jw c-v33" d="M 433,632 C 540,730 520,880 500,988"/>
<path class="jw c-gnd" d="M 433,609 C 510,700 410,860 380,940"/>
<path class="jw c-gnd" d="M 433,494 C 540,506 560,812 440,940"/>
<!-- LED drives -->
<path class="jw c-led" d="M 707,380 C 600,420 500,652 560,772"/>
<path class="jw c-led" d="M 707,464 C 648,512 662,664 700,772"/>
<!-- =================================================================== -->
<!-- LEDS -->
<!-- =================================================================== -->
<g id="led1">
<rect x="545" y="772" width="30" height="48" rx="2" fill="#ffffff" stroke="#22252a" stroke-width="2"/>
<line x1="560" y1="820" x2="560" y2="842" stroke="#2a9d4a" stroke-width="4.6" stroke-linecap="round"/>
<polygon points="542,842 578,842 560,872" fill="#ffffff" stroke="#22252a" stroke-width="2"/>
<line x1="540" y1="872" x2="580" y2="872" stroke="#22252a" stroke-width="2.6" stroke-linecap="round"/>
<path class="jw c-gnd" d="M 560,872 L 560,940"/>
<text class="mono" x="590" y="800" font-size="12">510 R</text>
<text x="590" y="862" font-size="12">I2C error</text>
</g>
<g id="led2">
<rect x="685" y="772" width="30" height="48" rx="2" fill="#ffffff" stroke="#22252a" stroke-width="2"/>
<line x1="700" y1="820" x2="700" y2="842" stroke="#2a9d4a" stroke-width="4.6" stroke-linecap="round"/>
<polygon points="682,842 718,842 700,872" fill="#ffffff" stroke="#22252a" stroke-width="2"/>
<line x1="680" y1="872" x2="720" y2="872" stroke="#22252a" stroke-width="2.6" stroke-linecap="round"/>
<path class="jw c-gnd" d="M 700,872 L 700,940"/>
<text class="mono" x="734" y="800" font-size="12">510 R</text>
<text x="734" y="862" font-size="12">SPI error</text>
</g>
<!-- junction dots where a wire really meets a rail -->
<g fill="#22252a">
<circle cx="1046" cy="940" r="5"/><circle cx="1176" cy="940" r="5"/><circle cx="1244" cy="940" r="5"/>
<circle cx="380" cy="940" r="5"/><circle cx="440" cy="940" r="5"/>
<circle cx="560" cy="940" r="5"/><circle cx="700" cy="940" r="5"/>
</g>
<g fill="#d62828">
<circle cx="1006" cy="988" r="5"/><circle cx="1120" cy="988" r="5"/>
<circle cx="1334" cy="988" r="5"/><circle cx="500" cy="988" r="5"/>
</g>
<!-- =================================================================== -->
<!-- LEGEND + NOTES -->
<!-- =================================================================== -->
<rect class="panel" x="60" y="742" width="300" height="176" rx="6"/>
<text class="hdr" x="76" y="768">WIRE COLOURS</text>
<g stroke-width="4.6" stroke-linecap="round">
<line x1="80" y1="790" x2="118" y2="790" stroke="#d62828"/><text x="128" y="795" font-size="12.5">3V3</text>
<line x1="80" y1="812" x2="118" y2="812" stroke="#22252a"/><text x="128" y="817" font-size="12.5">GND</text>
<line x1="80" y1="834" x2="118" y2="834" stroke="#00a0b0"/><text x="128" y="839" font-size="12.5">SCL</text>
<line x1="80" y1="856" x2="118" y2="856" stroke="#1f6feb"/><text x="128" y="861" font-size="12.5">SDA</text>
<line x1="80" y1="878" x2="118" y2="878" stroke="#2a9d4a"/><text x="128" y="883" font-size="12.5">LED drive</text>
<line x1="212" y1="790" x2="250" y2="790" stroke="#e07b00"/><text x="260" y="795" font-size="12.5">SCK</text>
<line x1="212" y1="812" x2="250" y2="812" stroke="#b5179e"/><text x="260" y="817" font-size="12.5">MOSI</text>
<line x1="212" y1="834" x2="250" y2="834" stroke="#7209b7"/><text x="260" y="839" font-size="12.5">MISO</text>
<line x1="212" y1="856" x2="250" y2="856" stroke="#c99700"/><text x="260" y="861" font-size="12.5">CS</text>
</g>
<rect class="panel" x="60" y="92" width="490" height="336" rx="6"/>
<text class="hdr" x="78" y="118">NOTES</text>
<text class="note" x="78" y="144">SPI is split across both headers: MOSI / MISO / CS sit on CN3</text>
<text class="note" x="78" y="162">(D11 / D12 / D10) but SCK is CN4 pin 15 (D13). One jumper has</text>
<text class="note" x="78" y="180">to cross to the far side of the board - that is normal here.</text>
<text class="note" x="78" y="206">I2C is wired to A5 / A4 on CN4. Solder bridges SB16 / SB18 are</text>
<text class="note" x="78" y="224">closed from the factory, which puts PB6 / PB7 on those pads.</text>
<text class="note" x="78" y="242">D5 / D4 on CN3 are the same two nets - either pair works.</text>
<text class="note" x="78" y="268">Do not power the sensors from 5V. MS5611 runs on 1.8 - 3.6 V.</text>
<text class="note" x="78" y="294">Both LEDs are external and active high: the GPIO drives the</text>
<text class="note" x="78" y="312">anode through 510 R. LD3 on the board cannot be used - it</text>
<text class="note" x="78" y="330">shares PB3 with SPI1 SCK.</text>
<text class="note" x="78" y="356">A dot means the wire connects to the rail. Wires that merely</text>
<text class="note" x="78" y="374">cross without a dot are not connected.</text>
<text class="note" x="78" y="400">Sensor #2 is drawn turned 180 deg so its pad row faces the board -</text>
<text class="note" x="78" y="418">that is why its pads read PS first and VCC last.</text>
</svg>

After

Width:  |  Height:  |  Size: 21 KiB

BIN
docs/wiring.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 392 KiB

226
docs/wiring.svg Normal file
View File

@@ -0,0 +1,226 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1500 1010" width="1500" height="1010">
<style>
.bg { fill: #ffffff; }
text { font-family: "Helvetica Neue", Arial, sans-serif; fill: #1b2733; }
.mono { font-family: "SF Mono", "DejaVu Sans Mono", Menlo, Consolas, monospace; }
.title { font-size: 25px; font-weight: 700; }
.sub { font-size: 13.5px; fill: #5a6b7d; }
.bname { font-size: 19px; font-weight: 700; }
.mname { font-size: 16px; font-weight: 700; }
.spec { font-size: 12.5px; fill: #5a6b7d; }
.pin { font-size: 13px; }
.net { font-size: 12px; }
.note { font-size: 12.5px; }
.lgd { font-size: 13px; }
.board { fill: #e8eef6; stroke: #2f4f7f; stroke-width: 2; }
.modu { fill: #faf3e6; stroke: #96703a; stroke-width: 2; }
.panel { fill: #f4f6f8; stroke: #b6c1cc; stroke-width: 1.5; }
.w { fill: none; stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round; }
.w-v33 { stroke: #cc2b2b; }
.w-gnd { stroke: #2b2f36; }
.w-i2c { stroke: #1668c4; }
.w-spi { stroke: #d98207; }
.w-led { stroke: #1f9350; }
.rail { fill: none; stroke: #cc2b2b; stroke-width: 3.4; stroke-linecap: round; }
.stub { stroke: #2f4f7f; stroke-width: 2; }
.pad { fill: #96703a; }
.jn { fill: #cc2b2b; }
.part { fill: #ffffff; stroke: #2b2f36; stroke-width: 2; }
.gsym { fill: none; stroke: #2b2f36; stroke-width: 2; stroke-linecap: round; }
</style>
<rect class="bg" x="0" y="0" width="1500" height="1010"/>
<text class="title" x="750" y="38" text-anchor="middle">NUCLEO-F042K6 + 2 x GY-63 (MS5611) - wiring</text>
<text class="sub" x="750" y="60" text-anchor="middle">Logical net diagram. Pins are grouped by function, not by physical header order - see UM1956 Table 10 for the real connector layout.</text>
<!-- ============================ POWER SOURCE ============================ -->
<rect class="panel" x="90" y="90" width="300" height="86" rx="6"/>
<text class="mname" x="106" y="114">POWER SOURCE</text>
<text class="spec" x="106" y="136">USB Micro-B to CN1, 5 V from the host PC</text>
<text class="spec" x="106" y="154">ST-LINK V2-1 + on-board 3.3 V LDO</text>
<text class="spec" x="106" y="170">same cable carries the serial VCP</text>
<!-- USB feed into the board, hopping over the 3V3 rail -->
<path class="w w-gnd" d="M 390,133 L 630,133 L 630,196 A 9,9 0 0,0 630,214 L 630,250"/>
<text class="net mono" x="452" y="126">5 V + serial</text>
<!-- ============================ 3V3 RAIL ============================ -->
<path class="rail" d="M 490,205 L 1350,205"/>
<text class="pin mono" x="1362" y="210" fill="#cc2b2b">+3V3</text>
<circle class="jn" cx="490" cy="205" r="4.5"/>
<circle class="jn" cx="720" cy="205" r="4.5"/>
<circle class="jn" cx="1050" cy="205" r="4.5"/>
<!-- ============================ NUCLEO BOARD ============================ -->
<rect class="board" x="600" y="250" width="300" height="540" rx="10"/>
<text class="bname" x="750" y="332" text-anchor="middle">NUCLEO-F042K6</text>
<text class="spec" x="750" y="354" text-anchor="middle">STM32F042K6T6 - board MB1180</text>
<text class="spec" x="750" y="372" text-anchor="middle">32 KB flash / 6 KB RAM</text>
<!-- top edge pins -->
<line class="stub" x1="630" y1="250" x2="630" y2="240"/>
<line class="stub" x1="720" y1="250" x2="720" y2="240"/>
<line class="stub" x1="790" y1="250" x2="790" y2="240"/>
<text class="pin mono" x="630" y="272" text-anchor="middle">CN1</text>
<text class="pin mono" x="720" y="272" text-anchor="middle">3V3</text>
<text class="pin mono" x="790" y="272" text-anchor="middle">GND</text>
<!-- left edge pins: I2C -->
<line class="stub" x1="600" y1="440" x2="590" y2="440"/>
<line class="stub" x1="600" y1="475" x2="590" y2="475"/>
<text class="pin mono" x="616" y="444">PB6 / D5 <tspan class="spec">(pad A5)</tspan></text>
<text class="pin mono" x="616" y="479">PB7 / D4 <tspan class="spec">(pad A4)</tspan></text>
<!-- right edge pins: SPI -->
<line class="stub" x1="900" y1="440" x2="910" y2="440"/>
<line class="stub" x1="900" y1="475" x2="910" y2="475"/>
<line class="stub" x1="900" y1="510" x2="910" y2="510"/>
<line class="stub" x1="900" y1="545" x2="910" y2="545"/>
<text class="pin mono" x="884" y="444" text-anchor="end">PB3 / D13</text>
<text class="pin mono" x="884" y="479" text-anchor="end">PB5 / D11</text>
<text class="pin mono" x="884" y="514" text-anchor="end">PA11 / D10</text>
<text class="pin mono" x="884" y="549" text-anchor="end">PB4 / D12</text>
<!-- bottom edge pins: LEDs -->
<line class="stub" x1="660" y1="790" x2="660" y2="800"/>
<line class="stub" x1="850" y1="790" x2="850" y2="800"/>
<text class="pin mono" x="660" y="778" text-anchor="middle">PB0 / D3</text>
<text class="pin mono" x="850" y="778" text-anchor="middle">PB1 / D6</text>
<text class="spec" x="750" y="702" text-anchor="middle">USART2 = PA2 / PA15 to ST-LINK VCP</text>
<text class="spec" x="750" y="720" text-anchor="middle">115200 8N1 - no USB-serial adapter needed</text>
<!-- ============================ GY-63 #1 : I2C ============================ -->
<rect class="modu" x="110" y="280" width="280" height="340" rx="8"/>
<text class="mname" x="250" y="312" text-anchor="middle">GY-63 #1</text>
<text class="spec" x="250" y="334" text-anchor="middle">MS5611-01BA03 - I2C mode</text>
<circle class="pad" cx="390" cy="370" r="4"/>
<circle class="pad" cx="390" cy="405" r="4"/>
<circle class="pad" cx="390" cy="440" r="4"/>
<circle class="pad" cx="390" cy="475" r="4"/>
<circle class="pad" cx="390" cy="510" r="4"/>
<circle class="pad" cx="390" cy="545" r="4"/>
<circle class="pad" cx="390" cy="580" r="4"/>
<text class="pin mono" x="376" y="374" text-anchor="end">VCC</text>
<text class="pin mono" x="376" y="409" text-anchor="end">GND</text>
<text class="pin mono" x="376" y="444" text-anchor="end">SCL</text>
<text class="pin mono" x="376" y="479" text-anchor="end">SDA</text>
<text class="pin mono" x="376" y="514" text-anchor="end">CSB</text>
<text class="pin mono" x="376" y="549" text-anchor="end">SDO</text>
<text class="pin mono" x="376" y="584" text-anchor="end">PS</text>
<text class="spec" x="284" y="549" text-anchor="end">not connected</text>
<!-- ============================ GY-63 #2 : SPI ============================ -->
<rect class="modu" x="1110" y="280" width="280" height="340" rx="8"/>
<text class="mname" x="1250" y="312" text-anchor="middle">GY-63 #2</text>
<text class="spec" x="1250" y="334" text-anchor="middle">MS5611-01BA03 - SPI mode</text>
<circle class="pad" cx="1110" cy="370" r="4"/>
<circle class="pad" cx="1110" cy="405" r="4"/>
<circle class="pad" cx="1110" cy="440" r="4"/>
<circle class="pad" cx="1110" cy="475" r="4"/>
<circle class="pad" cx="1110" cy="510" r="4"/>
<circle class="pad" cx="1110" cy="545" r="4"/>
<circle class="pad" cx="1110" cy="580" r="4"/>
<text class="pin mono" x="1124" y="374">VCC</text>
<text class="pin mono" x="1124" y="409">GND</text>
<text class="pin mono" x="1124" y="444">SCL <tspan class="spec">= SCLK</tspan></text>
<text class="pin mono" x="1124" y="479">SDA <tspan class="spec">= SDI / MOSI</tspan></text>
<text class="pin mono" x="1124" y="514">CSB <tspan class="spec">= CS</tspan></text>
<text class="pin mono" x="1124" y="549">SDO <tspan class="spec">= MISO</tspan></text>
<text class="pin mono" x="1124" y="584">PS</text>
<!-- ============================ POWER WIRING ============================ -->
<path class="w w-v33" d="M 390,370 L 490,370 L 490,205"/>
<path class="w w-v33" d="M 1110,370 L 1050,370 L 1050,205"/>
<path class="w w-v33" d="M 720,250 L 720,205"/>
<path class="w w-gnd" d="M 390,405 L 530,405 L 530,232"/>
<path class="w w-gnd" d="M 1110,405 L 1010,405 L 1010,232"/>
<path class="w w-gnd" d="M 790,250 L 790,232"/>
<g class="gsym"><line x1="516" y1="232" x2="544" y2="232"/><line x1="521" y1="238" x2="539" y2="238"/><line x1="526" y1="244" x2="534" y2="244"/></g>
<g class="gsym"><line x1="996" y1="232" x2="1024" y2="232"/><line x1="1001" y1="238" x2="1019" y2="238"/><line x1="1006" y1="244" x2="1014" y2="244"/></g>
<g class="gsym"><line x1="776" y1="232" x2="804" y2="232"/><line x1="781" y1="238" x2="799" y2="238"/><line x1="786" y1="244" x2="794" y2="244"/></g>
<!-- ============================ I2C SIGNALS ============================ -->
<path class="w w-i2c" d="M 390,440 L 600,440"/>
<path class="w w-i2c" d="M 390,475 L 600,475"/>
<text class="net mono" x="495" y="432" text-anchor="middle" fill="#1668c4">SCL</text>
<text class="net mono" x="495" y="467" text-anchor="middle" fill="#1668c4">SDA</text>
<!-- CSB -> GND (address 0x77) -->
<path class="w w-gnd" d="M 390,510 L 460,510 L 460,548"/>
<g class="gsym"><line x1="446" y1="548" x2="474" y2="548"/><line x1="451" y1="554" x2="469" y2="554"/><line x1="456" y1="560" x2="464" y2="560"/></g>
<text class="net" x="484" y="524" fill="#5a6b7d">0x77</text>
<!-- PS -> 3V3 (selects I2C) -->
<path class="w w-v33" d="M 390,580 L 545,580 L 545,556"/>
<line class="w w-v33" x1="531" y1="556" x2="559" y2="556"/>
<text class="net mono" x="545" y="546" text-anchor="middle" fill="#cc2b2b">3V3</text>
<!-- ============================ SPI SIGNALS ============================ -->
<path class="w w-spi" d="M 900,440 L 1110,440"/>
<path class="w w-spi" d="M 900,475 L 1110,475"/>
<path class="w w-spi" d="M 900,510 L 1110,510"/>
<path class="w w-spi" d="M 900,545 L 1110,545"/>
<text class="net mono" x="1005" y="432" text-anchor="middle" fill="#d98207">SCK</text>
<text class="net mono" x="1005" y="467" text-anchor="middle" fill="#d98207">MOSI</text>
<text class="net mono" x="1005" y="502" text-anchor="middle" fill="#d98207">CS</text>
<text class="net mono" x="1005" y="537" text-anchor="middle" fill="#d98207">MISO</text>
<!-- PS -> GND (selects SPI) -->
<path class="w w-gnd" d="M 1110,580 L 1040,580 L 1040,614"/>
<g class="gsym"><line x1="1026" y1="614" x2="1054" y2="614"/><line x1="1031" y1="620" x2="1049" y2="620"/><line x1="1036" y1="626" x2="1044" y2="626"/></g>
<!-- ============================ ERROR LEDS ============================ -->
<!-- branch 1 : PB0 -->
<path class="w w-led" d="M 660,800 L 660,832"/>
<rect class="part" x="645" y="832" width="30" height="50" rx="2"/>
<path class="w w-led" d="M 660,882 L 660,906"/>
<polygon class="part" points="642,906 678,906 660,936"/>
<line class="w w-led" x1="640" y1="936" x2="680" y2="936"/>
<path class="w w-led" d="M 660,936 L 660,964"/>
<g class="gsym"><line x1="646" y1="964" x2="674" y2="964"/><line x1="651" y1="970" x2="669" y2="970"/><line x1="656" y1="976" x2="664" y2="976"/></g>
<text class="pin mono" x="690" y="862">510 R</text>
<text class="pin" x="690" y="926">LED - I2C sensor error</text>
<!-- branch 2 : PB1 -->
<path class="w w-led" d="M 850,800 L 850,832"/>
<rect class="part" x="835" y="832" width="30" height="50" rx="2"/>
<path class="w w-led" d="M 850,882 L 850,906"/>
<polygon class="part" points="832,906 868,906 850,936"/>
<line class="w w-led" x1="830" y1="936" x2="870" y2="936"/>
<path class="w w-led" d="M 850,936 L 850,964"/>
<g class="gsym"><line x1="836" y1="964" x2="864" y2="964"/><line x1="841" y1="970" x2="859" y2="970"/><line x1="846" y1="976" x2="854" y2="976"/></g>
<text class="pin mono" x="880" y="862">510 R</text>
<text class="pin" x="880" y="926">LED - SPI sensor error</text>
<!-- ============================ LEGEND ============================ -->
<rect class="panel" x="90" y="660" width="290" height="196" rx="6"/>
<text class="mname" x="106" y="686">LEGEND</text>
<line class="w w-v33" x1="110" y1="712" x2="150" y2="712"/><text class="lgd" x="162" y="717">3.3 V</text>
<line class="w w-gnd" x1="110" y1="742" x2="150" y2="742"/><text class="lgd" x="162" y="747">GND</text>
<line class="w w-i2c" x1="110" y1="772" x2="150" y2="772"/><text class="lgd" x="162" y="777">I2C bus</text>
<line class="w w-spi" x1="110" y1="802" x2="150" y2="802"/><text class="lgd" x="162" y="807">SPI bus</text>
<line class="w w-led" x1="110" y1="832" x2="150" y2="832"/><text class="lgd" x="162" y="837">LED drive (active high)</text>
<!-- ============================ NOTES ============================ -->
<rect class="panel" x="1080" y="660" width="330" height="284" rx="6"/>
<text class="mname" x="1096" y="686">NOTES</text>
<text class="note" x="1096" y="712">MS5611 is 3.3 V only (1.8 - 3.6 V).</text>
<text class="note" x="1096" y="730">Never feed it 5 V.</text>
<text class="note" x="1096" y="756">GY-63 boards normally carry SDA / SCL</text>
<text class="note" x="1096" y="774">pull-ups - check yours before adding any.</text>
<text class="note" x="1096" y="800">PS high selects I2C, PS low selects SPI.</text>
<text class="note" x="1096" y="826">CSB low on module #1 sets address 0x77</text>
<text class="note" x="1096" y="844">(BARO_I2C_ADDRESS in include/config.h).</text>
<text class="note" x="1096" y="870">SPI1 must stay on PB3 / PB4 / PB5: bridges</text>
<text class="note" x="1096" y="888">SB16 / SB18 tie PA5 / PA6 to the I2C net.</text>
<text class="note" x="1096" y="914">The Nucleo-32 has a single 3V3 pin -</text>
<text class="note" x="1096" y="932">distribute it over a breadboard rail.</text>
</svg>

After

Width:  |  Height:  |  Size: 13 KiB

39
include/README Normal file
View File

@@ -0,0 +1,39 @@
This directory is intended for project header files.
A header file is a file containing C declarations and macro definitions
to be shared between several project source files. You request the use of a
header file in your project source file (C, C++, etc) located in `src` folder
by including it, with the C preprocessing directive `#include'.
```src/main.c
#include "header.h"
int main (void)
{
...
}
```
Including a header file produces the same results as copying the header file
into each source file that needs it. Such copying would be time-consuming
and error-prone. With a header file, the related declarations appear
in only one place. If they need to be changed, they can be changed in one
place, and programs that include the header file will automatically use the
new version when next recompiled. The header file eliminates the labor of
finding and changing all the copies as well as the risk that a failure to
find one copy will result in inconsistencies within a program.
In C, the usual convention is to give header files names that end with `.h'.
It is most portable to use only letters, digits, dashes, and underscores in
header file names, and at most one dot.
Read more about using header files in official GCC documentation:
* Include Syntax
* Include Operation
* Once-Only Headers
* Computed Includes
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html

100
include/baro_channel.h Normal file
View File

@@ -0,0 +1,100 @@
#pragma once
#include <Arduino.h>
#include <math.h>
#include "config.h"
#include "ms5611_crc.h"
//
// One validated MS5611 measurement channel.
//
// The class owns everything that is identical for both sensors - reset and
// PROM/CRC handshake, per-poll validation, error accounting and the 1 second
// error LED pulse. Only the five driver calls at the bottom are virtual, and
// they are implemented once for the I2C part and once for the SPI part.
//
enum class BaroStatus : uint8_t
{
NotInitialised = 0,
Ok,
InitFailed, // reset/handshake did not complete
PromCrcError, // factory calibration failed its CRC-4 or is implausible
ReadError, // the driver reported a bus/ADC failure
OutOfRange, // values outside the MS5611 operating envelope
Stale, // sensor answers but has stopped producing new samples
};
// Short, log-friendly name for a status - never returns NULL.
const char *baroStatusName(BaroStatus status);
class BaroChannel
{
public:
BaroChannel(const char *name, uint8_t ledPin)
: _name(name), _ledPin(ledPin) {}
// Configures the LED pin and makes the first initialisation attempt.
void begin(uint32_t now);
// One acquisition cycle: read, validate, update status and LED.
// Blocks for roughly 2x the ADC conversion time of the configured OSR.
void poll(uint32_t now);
// Releases the error LED once its hold time has elapsed. Cheap, call often.
void updateLed(uint32_t now);
const char *name() const { return _name; }
BaroStatus status() const { return _status; }
bool isOk() const { return _status == BaroStatus::Ok; }
float pressure() const { return _pressure; } // mbar
float temperature() const { return _temperature; } // degrees C
uint32_t errorCount() const { return _errorCount; }
protected:
// Not deleted through this type - keeps the vtable free of a destructor
// slot and avoids dragging in operator delete.
~BaroChannel() = default;
// Reset the part and apply the configured oversampling.
virtual bool driverBegin() = 0;
// Fill prom[0..7] with the factory calibration words.
virtual void driverReadProm(uint16_t *prom) = 0;
// True when the driver reported MS5611_READ_OK.
virtual bool driverRead() = 0;
virtual float driverPressure() = 0;
virtual float driverTemperature() = 0;
private:
bool tryInit(uint32_t now);
void fail(BaroStatus status, uint32_t now);
const char *_name;
uint8_t _ledPin;
BaroStatus _status = BaroStatus::NotInitialised;
bool _initialised = false;
uint32_t _lastInitAttempt = 0;
float _pressure = NAN;
float _temperature = NAN;
uint8_t _repeatCount = 0;
uint8_t _errorStreak = 0;
uint32_t _errorCount = 0;
bool _ledOn = false;
uint32_t _ledOffAt = 0;
};
//
// The two concrete channels are built in their own translation units on
// purpose: MS5611.h and MS5611_SPI.h each define `enum osr_t` and their own
// MS5611_READ_OK, so including both in one file does not compile. These
// accessors hand out the instances without leaking either driver header.
//
BaroChannel &baroI2cChannel();
BaroChannel &baroSpiChannel();

120
include/config.h Normal file
View File

@@ -0,0 +1,120 @@
#pragma once
#include <Arduino.h>
//
// Project-wide hardware and behaviour configuration.
//
// Board : NUCLEO-F042K6 (STM32F042K6T6, LQFP32, board MB1180)
// 32 KB flash / 6 KB RAM
// Sensor: 2x GY-63 breakout carrying a MS5611-01BA03
// one on I2C1, one on SPI1
//
// Pin facts below are taken from ST UM1956 "STM32 Nucleo-32 boards (MB1180)"
// Table 10 + the MB1180 C.2 schematic, cross-checked against the STM32duino
// variant files for NUCLEO_F042K6.
//
// ---------------------------------------------------------------------------
// I2C sensor ("baro0")
// ---------------------------------------------------------------------------
// STM32duino default Wire pins for this variant:
// PIN_WIRE_SDA = PB7 (Arduino D4, routed to the A4 header pad via SB18)
// PIN_WIRE_SCL = PB6 (Arduino D5, routed to the A5 header pad via SB16)
// Both are the STM32duino defaults, so plain Wire.begin() selects them and no
// explicit setSDA()/setSCL() call is required.
//
// MS5611 I2C address is 1110 11Cx where C is the COMPLEMENT of the CSB pin
// (datasheet p.12), therefore:
// CSB tied to GND -> 0x77
// CSB tied to VCC -> 0x76
// Change this if the GY-63 CSB pad is strapped high.
#define BARO_I2C_ADDRESS 0x77
// ---------------------------------------------------------------------------
// SPI sensor ("baro1")
// ---------------------------------------------------------------------------
// SPI1 MUST stay on the PB3/PB4/PB5 group on this board.
//
// The alternative SPI1 group (PA5 SCK / PA6 MISO / PA7 MOSI) is NOT usable
// here: with the factory-default solder bridges SB16 and SB18 closed, PA6 is
// tied to the same net as PB6 (I2C SCL) and PA5 is tied to the same net as
// PB7 (I2C SDA). Driving SPI on PA5/PA6 while I2C runs on PB6/PB7 would put
// two peripherals on one net. See UM1956 Table 8 (SB16/SB18).
//
// SCK = PB3 (Arduino D13) <- also the on-board user LED LD3, see below
// MISO = PB4 (Arduino D12) <- connect to sensor SDO
// MOSI = PB5 (Arduino D11) <- connect to sensor SDI
// These three are the STM32duino defaults (core falls back to Arduino pin
// numbers 13/12/11), so plain SPI.begin() selects them.
#define BARO_SPI_CS_PIN PA11 // Arduino D10, core default PIN_SPI_SS
// MS5611 accepts SPI mode 0 and mode 3, up to 20 MHz (datasheet p.5/p.6).
// The library hardcodes mode 0; 1 MHz is its default and is plenty here.
#define BARO_SPI_CLOCK_HZ 1000000UL
// ---------------------------------------------------------------------------
// Error indicator LEDs - one per sensor
// ---------------------------------------------------------------------------
// IMPORTANT: the NUCLEO-F042K6 has exactly ONE user-controllable LED, and it
// is unusable for this project:
//
// LD1 (COM, tricolor) - driven by the ST-LINK MCU, not by the target
// LD2 (PWR, red) - hardwired to the power rail, not by the target
// LD3 (user, green) - on PB3 via SB15 + R23, and PB3 is our SPI1 SCK
//
// So both indicators are external LEDs (LED + ~510R to GND) on free GPIOs.
// PB0/PB1 are plain GPIO on this board and collide with nothing we use.
#define LED_BARO_I2C_PIN PB0 // Arduino D3 - error LED for the I2C sensor
#define LED_BARO_SPI_PIN PB1 // Arduino D6 - error LED for the SPI sensor
// How long an LED stays lit after an error is detected.
#define LED_ERROR_HOLD_MS 1000UL
// ---------------------------------------------------------------------------
// UART
// ---------------------------------------------------------------------------
// `Serial` on this variant is USART2 (SERIAL_UART_INSTANCE 2) on PA2/PA15,
// which is wired to the ST-LINK Virtual COM Port. No extra wiring needed.
#define UART_BAUD 115200UL
// Measurement report rate: 10 Hz.
#define REPORT_PERIOD_MS 100UL
// ---------------------------------------------------------------------------
// Acquisition
// ---------------------------------------------------------------------------
// The driver's read() busy-waits through two conversions, so a poll of both
// sensors is the loop period, and the report deadline can only be evaluated
// on that grid. Keeping the cycle short keeps the 10 Hz output jitter small:
//
// OSR_STANDARD (1024) 2.28 ms/conv -> ~5 ms/sensor -> ~11 ms cycle
// OSR_ULTRA_HIGH (4096) 9.04 ms/conv -> ~19 ms/sensor -> ~37 ms cycle
//
// Conversion times are the datasheet p.3 maxima. OSR 1024 already resolves
// well under a mbar, so it is the better trade here; raise it if resolution
// matters more than tight report timing.
#define BARO_OVERSAMPLING OSR_STANDARD
// Retry interval for a sensor that failed to initialise.
#define BARO_INIT_RETRY_MS 2000UL
// Consecutive failed polls after which the channel is torn down and taken
// back through the full reset + PROM/CRC handshake. Covers a sensor that was
// unplugged, browned out, or otherwise lost its calibration constants.
#define BARO_REINIT_AFTER_ERRORS 10
// ---------------------------------------------------------------------------
// Validation limits
// ---------------------------------------------------------------------------
// MS5611-01BA03 operating ranges (datasheet p.2/p.4).
#define BARO_PRESSURE_MIN_MBAR 10.0f
#define BARO_PRESSURE_MAX_MBAR 1200.0f
#define BARO_TEMP_MIN_C (-40.0f)
#define BARO_TEMP_MAX_C 85.0f
// A healthy MS5611 dithers by well under a mbar but never repeats a 24-bit
// reading bit-for-bit many times running. Identical pressure AND temperature
// this many polls in a row means the sensor stopped converting while still
// answering on the bus.
#define BARO_STALE_LIMIT 32

11
include/ms5611_crc.h Normal file
View File

@@ -0,0 +1,11 @@
#pragma once
#include <stdint.h>
// MS5611 factory PROM CRC-4 (datasheet p.13, algorithm specified in AN520).
// `prom` holds the 8 PROM words as read from the part; word 7 carries the
// stored CRC in its low nibble. The array is restored before returning.
//
// Deliberately free of Arduino headers so it can be built and tested on the
// host against the AN520 reference vector.
bool ms5611PromCrcOk(uint16_t prom[8]);

46
lib/README Normal file
View File

@@ -0,0 +1,46 @@
This directory is intended for project specific (private) libraries.
PlatformIO will compile them to static libraries and link into executable file.
The source code of each library should be placed in an own separate directory
("lib/your_library_name/[here are source files]").
For example, see a structure of the following two libraries `Foo` and `Bar`:
|--lib
| |
| |--Bar
| | |--docs
| | |--examples
| | |--src
| | |- Bar.c
| | |- Bar.h
| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
| |
| |--Foo
| | |- Foo.c
| | |- Foo.h
| |
| |- README --> THIS FILE
|
|- platformio.ini
|--src
|- main.c
and a contents of `src/main.c`:
```
#include <Foo.h>
#include <Bar.h>
int main (void)
{
...
}
```
PlatformIO Library Dependency Finder will find automatically dependent
libraries scanning project source files.
More information about PlatformIO Library Dependency Finder
- https://docs.platformio.org/page/librarymanager/ldf.html

33
platformio.ini Normal file
View File

@@ -0,0 +1,33 @@
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[common]
framework = arduino
lib_deps =
SPI
I2C
https://github.com/RobTillaart/MS5611#0.5.2
https://github.com/RobTillaart/MS5611_SPI#0.4.3
[env:nucleo_f042k6]
extends = common
platform = ststm32
board = nucleo_f042k6
monitor_speed = 115200
; -flto and -fsingle-precision-constant are load-bearing, not tuning: the
; STM32F042K6 has only 32 KB of flash and the build overflows without them.
; Both MS5611 drivers write their compensation maths with unsuffixed double
; literals, which would otherwise link ~5 KB of double soft-float helpers.
build_flags =
-Wall
-Wextra
-flto
-fsingle-precision-constant

151
src/baro_channel.cpp Normal file
View File

@@ -0,0 +1,151 @@
#include "baro_channel.h"
const char *baroStatusName(BaroStatus status)
{
switch (status)
{
case BaroStatus::NotInitialised: return "INIT";
case BaroStatus::Ok: return "OK";
case BaroStatus::InitFailed: return "ERR_INIT";
case BaroStatus::PromCrcError: return "ERR_CRC";
case BaroStatus::ReadError: return "ERR_READ";
case BaroStatus::OutOfRange: return "ERR_RANGE";
case BaroStatus::Stale: return "ERR_STALE";
}
return "ERR_UNKNOWN";
}
void BaroChannel::begin(uint32_t now)
{
pinMode(_ledPin, OUTPUT);
digitalWrite(_ledPin, LOW);
_ledOn = false;
tryInit(now);
}
bool BaroChannel::tryInit(uint32_t now)
{
_lastInitAttempt = now;
_initialised = false;
_repeatCount = 0;
if (!driverBegin())
{
fail(BaroStatus::InitFailed, now);
return false;
}
uint16_t prom[8] = { 0 };
driverReadProm(prom);
// An absent part reads back as all-zero (I2C NACK) or all-ones (floating
// MISO). Neither driver rejects 0xFFFF, and an all-zero PROM would satisfy
// the CRC, so the calibration words are screened before the checksum runs.
for (uint8_t i = 1; i <= 6; i++)
{
if (prom[i] == 0x0000 || prom[i] == 0xFFFF)
{
fail(BaroStatus::PromCrcError, now);
return false;
}
}
if (!ms5611PromCrcOk(prom))
{
fail(BaroStatus::PromCrcError, now);
return false;
}
_initialised = true;
_errorStreak = 0;
return true;
}
void BaroChannel::poll(uint32_t now)
{
if (!_initialised)
{
if ((uint32_t)(now - _lastInitAttempt) < BARO_INIT_RETRY_MS) return;
if (!tryInit(now)) return;
}
if (!driverRead())
{
fail(BaroStatus::ReadError, now);
return;
}
const float pressure = driverPressure();
const float temperature = driverTemperature();
// Catches the failure the drivers miss: an ADC read taken before the
// conversion finished returns 0 (datasheet p.11), which the I2C driver
// still reports as MS5611_READ_OK but which lands far outside this envelope.
if (!isfinite(pressure) || !isfinite(temperature) ||
pressure < BARO_PRESSURE_MIN_MBAR || pressure > BARO_PRESSURE_MAX_MBAR ||
temperature < BARO_TEMP_MIN_C || temperature > BARO_TEMP_MAX_C)
{
_pressure = pressure;
_temperature = temperature;
fail(BaroStatus::OutOfRange, now);
return;
}
const bool repeated = (pressure == _pressure) && (temperature == _temperature);
_pressure = pressure;
_temperature = temperature;
if (repeated)
{
if (_repeatCount < BARO_STALE_LIMIT) _repeatCount++;
}
else
{
_repeatCount = 0;
}
if (_repeatCount >= BARO_STALE_LIMIT)
{
fail(BaroStatus::Stale, now);
return;
}
_status = BaroStatus::Ok;
_errorStreak = 0;
}
void BaroChannel::fail(BaroStatus status, uint32_t now)
{
_status = status;
_errorCount++;
_ledOn = true;
_ledOffAt = now + LED_ERROR_HOLD_MS;
digitalWrite(_ledPin, HIGH);
if (_errorStreak < BARO_REINIT_AFTER_ERRORS) _errorStreak++;
if (_errorStreak >= BARO_REINIT_AFTER_ERRORS)
{
_initialised = false;
_errorStreak = 0;
_lastInitAttempt = now;
}
}
void BaroChannel::updateLed(uint32_t now)
{
// Signed difference so the comparison survives the millis() rollover.
if (_ledOn && (int32_t)(now - _ledOffAt) >= 0)
{
_ledOn = false;
digitalWrite(_ledPin, LOW);
}
}

46
src/baro_i2c.cpp Normal file
View File

@@ -0,0 +1,46 @@
#include <Wire.h>
#include <MS5611.h>
#include "baro_channel.h"
namespace {
class BaroI2cChannel : public BaroChannel
{
public:
BaroI2cChannel()
: BaroChannel("i2c", LED_BARO_I2C_PIN), _driver(BARO_I2C_ADDRESS, &Wire) {}
protected:
bool driverBegin() override
{
if (!_busStarted)
{
Wire.begin();
_busStarted = true;
}
_driver.setOversampling(BARO_OVERSAMPLING);
return _driver.begin();
}
void driverReadProm(uint16_t *prom) override
{
for (uint8_t i = 0; i < 8; i++) prom[i] = _driver.getProm(i);
}
bool driverRead() override { return _driver.read() == MS5611_READ_OK; }
float driverPressure() override { return _driver.getPressure(); }
float driverTemperature() override { return _driver.getTemperature(); }
private:
MS5611 _driver;
bool _busStarted = false;
};
BaroI2cChannel instance;
} // namespace
BaroChannel &baroI2cChannel() { return instance; }

49
src/baro_spi.cpp Normal file
View File

@@ -0,0 +1,49 @@
#include <SPI.h>
#include <MS5611_SPI.h>
#include "baro_channel.h"
namespace {
class BaroSpiChannel : public BaroChannel
{
public:
BaroSpiChannel()
: BaroChannel("spi", LED_BARO_SPI_PIN), _driver(BARO_SPI_CS_PIN, &SPI) {}
protected:
bool driverBegin() override
{
if (!_busStarted)
{
SPI.begin();
_busStarted = true;
}
// Both must precede begin(), which re-applies the stored SPI speed and
// then talks to the part.
_driver.setSPIspeed(BARO_SPI_CLOCK_HZ);
_driver.setOversampling(BARO_OVERSAMPLING);
return _driver.begin();
}
void driverReadProm(uint16_t *prom) override
{
for (uint8_t i = 0; i < 8; i++) prom[i] = _driver.getProm(i);
}
bool driverRead() override { return _driver.read() == MS5611_READ_OK; }
float driverPressure() override { return _driver.getPressure(); }
float driverTemperature() override { return _driver.getTemperature(); }
private:
MS5611_SPI _driver;
bool _busStarted = false;
};
BaroSpiChannel instance;
} // namespace
BaroChannel &baroSpiChannel() { return instance; }

93
src/main.cpp Normal file
View File

@@ -0,0 +1,93 @@
#include <Arduino.h>
#include "baro_channel.h"
#include "config.h"
namespace {
uint32_t nextReportAt = 0;
// Serial.print(float, digits) resolves to Print::print(double, int), which
// would pull the double-precision soft-float routines into a 32 KB image.
// Formatting from a scaled integer keeps the whole build single-precision.
void printFixed2(float value)
{
if (!isfinite(value))
{
Serial.print(F("nan"));
return;
}
int32_t hundredths = (int32_t)(value * 100.0f + (value < 0.0f ? -0.5f : 0.5f));
if (hundredths < 0)
{
Serial.print('-');
hundredths = -hundredths;
}
const int32_t fraction = hundredths % 100;
Serial.print(hundredths / 100);
Serial.print('.');
if (fraction < 10) Serial.print('0');
Serial.print(fraction);
}
void printChannel(BaroChannel &channel)
{
Serial.print(',');
Serial.print(baroStatusName(channel.status()));
Serial.print(',');
printFixed2(channel.pressure());
Serial.print(',');
printFixed2(channel.temperature());
}
} // namespace
void setup()
{
Serial.begin(UART_BAUD);
const uint32_t now = millis();
baroI2cChannel().begin(now);
baroSpiChannel().begin(now);
Serial.println();
Serial.println(F("ms,i2c_status,i2c_p_mbar,i2c_t_c,spi_status,spi_p_mbar,spi_t_c"));
nextReportAt = millis() + REPORT_PERIOD_MS;
}
void loop()
{
uint32_t now = millis();
baroI2cChannel().poll(now);
baroSpiChannel().poll(now);
// poll() blocks for the ADC conversions, so re-read the clock before the
// LED and report deadlines are evaluated.
now = millis();
baroI2cChannel().updateLed(now);
baroSpiChannel().updateLed(now);
if ((int32_t)(now - nextReportAt) < 0) return;
// Advancing by a fixed period keeps the long-run average at exactly 10 Hz
// instead of drifting by one loop iteration per report.
nextReportAt += REPORT_PERIOD_MS;
// Unless a stall put us more than a full period behind, in which case the
// grid is re-based rather than emitting a burst of catch-up lines.
if ((int32_t)(now - nextReportAt) >= 0) nextReportAt = now + REPORT_PERIOD_MS;
Serial.print(now);
printChannel(baroI2cChannel());
printChannel(baroSpiChannel());
Serial.println();
}

27
src/ms5611_crc.cpp Normal file
View File

@@ -0,0 +1,27 @@
#include "ms5611_crc.h"
bool ms5611PromCrcOk(uint16_t prom[8])
{
const uint16_t crcRead = prom[7];
uint16_t remainder = 0;
// The 4 CRC bits live in the low nibble of word 7 and must read as zero
// while the remainder is computed, so the whole low byte is masked off.
prom[7] &= 0xFF00;
for (uint8_t i = 0; i < 16; i++)
{
if (i & 1) remainder ^= (uint16_t)(prom[i >> 1] & 0x00FF);
else remainder ^= (uint16_t)(prom[i >> 1] >> 8);
for (uint8_t bit = 8; bit > 0; bit--)
{
remainder = (remainder & 0x8000) ? (uint16_t)((remainder << 1) ^ 0x3000)
: (uint16_t)(remainder << 1);
}
}
prom[7] = crcRead;
return (uint16_t)((remainder >> 12) & 0x000F) == (uint16_t)(crcRead & 0x000F);
}

11
test/README Normal file
View File

@@ -0,0 +1,11 @@
This directory is intended for PlatformIO Test Runner and project tests.
Unit Testing is a software testing method by which individual units of
source code, sets of one or more MCU program modules together with associated
control data, usage procedures, and operating procedures, are tested to
determine whether they are fit for use. Unit testing finds problems early
in the development cycle.
More information about PlatformIO Unit Testing:
- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html