← Commit history
design/stm32-adc-host-tests.logadded+12
@@ -0,0 +1,12 @@+STM32 adapter fake-HAL: pins, endian/address encoding, zero-only DAC, age and sticky faults passed.+load_core: safety policy tests passed (host C build; no MCU/HAL validation)+fan_tach: qualification, RPM, stale input, noise, slow fan, timer wrap and stale-history expiry passed+load_protocol: host safety/framing tests passed; no MCU or USB validation+ADC nominal conversion: divider, ratiometric NTC, invalid reference and saturation tests passed.+Measurement cache: conversion age, WAIT expiry, fault latch and clock wrap passed.+Instrument register, readback, signed conversion and transaction-failure tests passed.+.........................+----------------------------------------------------------------------+Ran 25 tests in 0.071s++OK
design/stm32-bringup-build.json+196−64
@@ -27,6 +27,8 @@     }   },   "sources": {+    "firmware/adc_conversion.c": "bc95ac8695bdc81958785fa99ec7200a84dbec35b49565604d731d5236209ced",+    "firmware/adc_conversion.h": "dcaa7b969111978f35d63b4ec4c6ed9f151bd16fa4b7744f7de114fe36414d9e",     "firmware/fan_tach.c": "844f00c1f77e39529bd8f667d543ecee187ff4fe91108aedb7a2c36eb22846fa",     "firmware/fan_tach.h": "20fc7b0bbb1a040dfd0453e3af22cf62e7a564241c28cf9cd08dd9b6d87ddf80",     "firmware/instruments.c": "8d14877861dc04ea88c21e2047df81dc6291e340783515c36bf417c32763b995",@@ -37,7 +39,9 @@     "firmware/load_protocol.h": "f64f6b689d57501031515e8ee9f8c8349a2cea63738e73db16f95e747caf91fb",     "firmware/measurement_cache.c": "5f2c1cee84c5118b7647e1b833924673b132c4d76a95b790671d2747209b9733",     "firmware/measurement_cache.h": "5de6ed33032a25f3a1b80331461b6ad43aa26374d18c03d4e458f13036694262",-    "firmware/stm32/README.md": "be8404a69783f1f5b359851b987fab36f92edd4a51ce881e3696f94f1a7c5e23",+    "firmware/stm32/README.md": "fba86ffec51742a14f8b93a5881fceebb9cd9226c86eaf478e6951c83c5ccc9d",+    "firmware/stm32/adc_input.c": "72f18414e5db99a90ca815ba7da9cf0414b0b58479c772de6cfbb0f509b0e0bb",+    "firmware/stm32/adc_input.h": "c39b651e4140b05f0693d3c2681cb15cf437395953f7b24db0d5e210b260450d",     "firmware/stm32/clock.c": "f42f5c1ff229b065a048218ecc575624276aaa229e0bc340d80c80eec35416d2",     "firmware/stm32/dependencies.json": "7e8f3c17a59c856371a9f411dce6a9dadafbb8c8edd5ede5e4e5cf46b1567e92",     "firmware/stm32/fan_input.c": "d4ebe5572dbbcd1a3ea84c4e31442732a7237e94d554ec8acdc07142486e92f8",@@ -48,19 +52,19 @@     "firmware/stm32/licenses/cmsis_device_g0-LICENSE.txt": "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4",     "firmware/stm32/licenses/stm32g0xx_hal_driver-LICENSE.txt": "3564c849dfd5e628732afb7933ccb1c983b53f7ac6bb237b6177691339082671",     "firmware/stm32/licenses/tinyusb-LICENSE.txt": "b01185698f530f4f61b28f42d5a4d6bbe7834bde6a63f00773235628a7bdce22",-    "firmware/stm32/main.c": "73b6deeffceedc87efcc8787a2ed17069f76d434c48413d7f7ebd9a50e2f0783",+    "firmware/stm32/main.c": "a132ee37a6e9a9261d70389fdf5d5d7d8d7e23f8c389ac1d840f26e891137cf1",     "firmware/stm32/stm32g0b1kc.ld": "1c938076e760300379115ee955589434dba7b84d1bb945dd41cf39abd342fd13",-    "firmware/stm32/stm32g0xx_hal_conf.h": "2e2a3aed4bf01ce3f890df69061e740369ed8839bb53d5b1be745be4de185b6f",+    "firmware/stm32/stm32g0xx_hal_conf.h": "bda0dc6b6c88d0b1b0e621d3ac39e035fa46f363f256654f2a338c04ba821321",     "firmware/stm32/syscalls.c": "bbfcedecfe5f33380424ab90cdb89c842c6a5717e01298a29ec43aef24c1524d",     "firmware/stm32/tusb_config.h": "dfbb343cc609aa2693058b7d7e6e6c487524222cbe3eab5296568eca0b7d3970",     "firmware/stm32/usb_descriptors.c": "ed2666d7115f05a07088dd92ebf35a600aadd8d9878fae2eb7635201b8f0b2a9",-    "tools/build_stm32_bringup.py": "2e7bb3c5482e0807ae138a6b70b0bdbce9c3b639cd68e7164b92c6af00f43fb4"+    "tools/build_stm32_bringup.py": "1899939e7e15cf915172249d724752ea49f54560fc077c138c165ab9a7fec9d4"   },   "artifacts": {-    "adom-load-bringup.hex": "4b0aa51d83e690e2e06ce89593660ad0017a78f0b2c70c8979870ed0387517d0",-    "adom-load-bringup.bin": "f152f6fbc0f937a7c9a07697e4f2cd580a3f96a748dfa9e7bc3a8015d67c3b62",-    "adom-load-bringup.map": "30380769911a514216121a4e00ddb296f420f7eaf7b06e1775e910d754ca35a5",-    "adom-load-bringup.elf": "719ac2dff3c397a92c866595abfae7bdbf74864fb652a93b54ed18e401a623fb"+    "adom-load-bringup.hex": "364abce4cf174d08caa9cfda064010fc256dcedb0a1cb617c9dc448fc88f8cb4",+    "adom-load-bringup.bin": "70080e6a1bbd3f91ae1f174ed5267001640b315bb2a84ce433e53800a20d37c2",+    "adom-load-bringup.map": "52a4fe36c8e2d586b7ebabd20fc48cd5d9ba3287fe682ad655ab34e70ca7638d",+    "adom-load-bringup.elf": "2d205abe811d9bb10a1e1292c22e253c2e2aa7ff6b8c690a26140dbc1a908001"   },   "commands": [     [@@ -95,6 +99,70 @@       "-o",       "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/00-load_core.o"     ],+    [+      "/home/adom/project/adom-load-firmware-toolchain/root/usr/bin/arm-none-eabi-gcc",+      "-mcpu=cortex-m0plus",+      "-mthumb",+      "-Os",+      "-g3",+      "-ffunction-sections",+      "-fdata-sections",+      "-fno-common",+      "-fstack-usage",+      "-Wall",+      "-Wextra",+      "-DSTM32G0B1xx",+      "-DUSE_HAL_DRIVER",+      "-DADOM_BRINGUP_OUTPUT_LOCK=1",+      "-DADOM_USB_VID=51966",+      "-DADOM_USB_PID=16385",+      "-DADOM_LAB_BOOT_ID=\"lab20261009bringup\"",+      "-I/home/adom/project/adom-custom-electronic-load/firmware",+      "-I/home/adom/project/adom-custom-electronic-load/firmware/stm32",+      "-I/home/adom/project/adom-load-firmware-deps/tinyusb/src",+      "-I/home/adom/project/adom-load-firmware-deps/tinyusb/lib/CMSIS_5/CMSIS/Core/Include",+      "-I/home/adom/project/adom-load-firmware-deps/tinyusb/hw/mcu/st/cmsis_device_g0/Include",+      "-I/home/adom/project/adom-load-firmware-deps/tinyusb/hw/mcu/st/stm32g0xx_hal_driver/Inc",+      "-I/home/adom/project/adom-load-firmware-toolchain/root/usr/include/newlib",+      "-std=c11",+      "-Werror",+      "-c",+      "/home/adom/project/adom-custom-electronic-load/firmware/adc_conversion.c",+      "-o",+      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/01-adc_conversion.o"+    ],+    [+      "/home/adom/project/adom-load-firmware-toolchain/root/usr/bin/arm-none-eabi-gcc",+      "-mcpu=cortex-m0plus",+      "-mthumb",+      "-Os",+      "-g3",+      "-ffunction-sections",+      "-fdata-sections",+      "-fno-common",+      "-fstack-usage",+      "-Wall",+      "-Wextra",+      "-DSTM32G0B1xx",+      "-DUSE_HAL_DRIVER",+      "-DADOM_BRINGUP_OUTPUT_LOCK=1",+      "-DADOM_USB_VID=51966",+      "-DADOM_USB_PID=16385",+      "-DADOM_LAB_BOOT_ID=\"lab20261009bringup\"",+      "-I/home/adom/project/adom-custom-electronic-load/firmware",+      "-I/home/adom/project/adom-custom-electronic-load/firmware/stm32",+      "-I/home/adom/project/adom-load-firmware-deps/tinyusb/src",+      "-I/home/adom/project/adom-load-firmware-deps/tinyusb/lib/CMSIS_5/CMSIS/Core/Include",+      "-I/home/adom/project/adom-load-firmware-deps/tinyusb/hw/mcu/st/cmsis_device_g0/Include",+      "-I/home/adom/project/adom-load-firmware-deps/tinyusb/hw/mcu/st/stm32g0xx_hal_driver/Inc",+      "-I/home/adom/project/adom-load-firmware-toolchain/root/usr/include/newlib",+      "-std=c11",+      "-Werror",+      "-c",+      "/home/adom/project/adom-custom-electronic-load/firmware/stm32/adc_input.c",+      "-o",+      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/02-adc_input.o"+    ],     [       "/home/adom/project/adom-load-firmware-toolchain/root/usr/bin/arm-none-eabi-gcc",       "-mcpu=cortex-m0plus",@@ -125,7 +193,7 @@       "-c",       "/home/adom/project/adom-custom-electronic-load/firmware/instruments.c",       "-o",-      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/01-instruments.o"+      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/03-instruments.o"     ],     [       "/home/adom/project/adom-load-firmware-toolchain/root/usr/bin/arm-none-eabi-gcc",@@ -157,7 +225,7 @@       "-c",       "/home/adom/project/adom-custom-electronic-load/firmware/measurement_cache.c",       "-o",-      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/02-measurement_cache.o"+      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/04-measurement_cache.o"     ],     [       "/home/adom/project/adom-load-firmware-toolchain/root/usr/bin/arm-none-eabi-gcc",@@ -189,7 +257,7 @@       "-c",       "/home/adom/project/adom-custom-electronic-load/firmware/stm32/instrument_input.c",       "-o",-      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/03-instrument_input.o"+      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/05-instrument_input.o"     ],     [       "/home/adom/project/adom-load-firmware-toolchain/root/usr/bin/arm-none-eabi-gcc",@@ -221,7 +289,7 @@       "-c",       "/home/adom/project/adom-custom-electronic-load/firmware/load_protocol.c",       "-o",-      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/04-load_protocol.o"+      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/06-load_protocol.o"     ],     [       "/home/adom/project/adom-load-firmware-toolchain/root/usr/bin/arm-none-eabi-gcc",@@ -253,7 +321,7 @@       "-c",       "/home/adom/project/adom-custom-electronic-load/firmware/fan_tach.c",       "-o",-      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/05-fan_tach.o"+      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/07-fan_tach.o"     ],     [       "/home/adom/project/adom-load-firmware-toolchain/root/usr/bin/arm-none-eabi-gcc",@@ -285,7 +353,7 @@       "-c",       "/home/adom/project/adom-custom-electronic-load/firmware/stm32/fan_input.c",       "-o",-      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/06-fan_input.o"+      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/08-fan_input.o"     ],     [       "/home/adom/project/adom-load-firmware-toolchain/root/usr/bin/arm-none-eabi-gcc",@@ -317,7 +385,7 @@       "-c",       "/home/adom/project/adom-custom-electronic-load/firmware/stm32/main.c",       "-o",-      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/07-main.o"+      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/09-main.o"     ],     [       "/home/adom/project/adom-load-firmware-toolchain/root/usr/bin/arm-none-eabi-gcc",@@ -349,7 +417,7 @@       "-c",       "/home/adom/project/adom-custom-electronic-load/firmware/stm32/clock.c",       "-o",-      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/08-clock.o"+      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/10-clock.o"     ],     [       "/home/adom/project/adom-load-firmware-toolchain/root/usr/bin/arm-none-eabi-gcc",@@ -381,7 +449,7 @@       "-c",       "/home/adom/project/adom-custom-electronic-load/firmware/stm32/usb_descriptors.c",       "-o",-      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/09-usb_descriptors.o"+      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/11-usb_descriptors.o"     ],     [       "/home/adom/project/adom-load-firmware-toolchain/root/usr/bin/arm-none-eabi-gcc",@@ -413,7 +481,7 @@       "-c",       "/home/adom/project/adom-custom-electronic-load/firmware/stm32/syscalls.c",       "-o",-      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/10-syscalls.o"+      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/12-syscalls.o"     ],     [       "/home/adom/project/adom-load-firmware-toolchain/root/usr/bin/arm-none-eabi-gcc",@@ -443,7 +511,7 @@       "-c",       "/home/adom/project/adom-load-firmware-deps/tinyusb/src/tusb.c",       "-o",-      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/11-tusb.o"+      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/13-tusb.o"     ],     [       "/home/adom/project/adom-load-firmware-toolchain/root/usr/bin/arm-none-eabi-gcc",@@ -473,7 +541,7 @@       "-c",       "/home/adom/project/adom-load-firmware-deps/tinyusb/src/common/tusb_fifo.c",       "-o",-      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/12-tusb_fifo.o"+      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/14-tusb_fifo.o"     ],     [       "/home/adom/project/adom-load-firmware-toolchain/root/usr/bin/arm-none-eabi-gcc",@@ -503,7 +571,7 @@       "-c",       "/home/adom/project/adom-load-firmware-deps/tinyusb/src/device/usbd.c",       "-o",-      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/13-usbd.o"+      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/15-usbd.o"     ],     [       "/home/adom/project/adom-load-firmware-toolchain/root/usr/bin/arm-none-eabi-gcc",@@ -533,7 +601,7 @@       "-c",       "/home/adom/project/adom-load-firmware-deps/tinyusb/src/class/cdc/cdc_device.c",       "-o",-      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/14-cdc_device.o"+      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/16-cdc_device.o"     ],     [       "/home/adom/project/adom-load-firmware-toolchain/root/usr/bin/arm-none-eabi-gcc",@@ -563,7 +631,7 @@       "-c",       "/home/adom/project/adom-load-firmware-deps/tinyusb/src/portable/st/stm32_fsdev/dcd_stm32_fsdev.c",       "-o",-      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/15-dcd_stm32_fsdev.o"+      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/17-dcd_stm32_fsdev.o"     ],     [       "/home/adom/project/adom-load-firmware-toolchain/root/usr/bin/arm-none-eabi-gcc",@@ -593,7 +661,7 @@       "-c",       "/home/adom/project/adom-load-firmware-deps/tinyusb/src/portable/st/stm32_fsdev/fsdev_common.c",       "-o",-      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/16-fsdev_common.o"+      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/18-fsdev_common.o"     ],     [       "/home/adom/project/adom-load-firmware-toolchain/root/usr/bin/arm-none-eabi-gcc",@@ -623,7 +691,7 @@       "-c",       "/home/adom/project/adom-load-firmware-deps/tinyusb/hw/mcu/st/stm32g0xx_hal_driver/Src/stm32g0xx_hal.c",       "-o",-      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/17-stm32g0xx_hal.o"+      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/19-stm32g0xx_hal.o"     ],     [       "/home/adom/project/adom-load-firmware-toolchain/root/usr/bin/arm-none-eabi-gcc",@@ -653,7 +721,7 @@       "-c",       "/home/adom/project/adom-load-firmware-deps/tinyusb/hw/mcu/st/stm32g0xx_hal_driver/Src/stm32g0xx_hal_cortex.c",       "-o",-      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/18-stm32g0xx_hal_cortex.o"+      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/20-stm32g0xx_hal_cortex.o"     ],     [       "/home/adom/project/adom-load-firmware-toolchain/root/usr/bin/arm-none-eabi-gcc",@@ -683,7 +751,7 @@       "-c",       "/home/adom/project/adom-load-firmware-deps/tinyusb/hw/mcu/st/stm32g0xx_hal_driver/Src/stm32g0xx_hal_pwr.c",       "-o",-      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/19-stm32g0xx_hal_pwr.o"+      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/21-stm32g0xx_hal_pwr.o"     ],     [       "/home/adom/project/adom-load-firmware-toolchain/root/usr/bin/arm-none-eabi-gcc",@@ -713,7 +781,7 @@       "-c",       "/home/adom/project/adom-load-firmware-deps/tinyusb/hw/mcu/st/stm32g0xx_hal_driver/Src/stm32g0xx_hal_pwr_ex.c",       "-o",-      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/20-stm32g0xx_hal_pwr_ex.o"+      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/22-stm32g0xx_hal_pwr_ex.o"     ],     [       "/home/adom/project/adom-load-firmware-toolchain/root/usr/bin/arm-none-eabi-gcc",@@ -743,7 +811,7 @@       "-c",       "/home/adom/project/adom-load-firmware-deps/tinyusb/hw/mcu/st/stm32g0xx_hal_driver/Src/stm32g0xx_hal_rcc.c",       "-o",-      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/21-stm32g0xx_hal_rcc.o"+      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/23-stm32g0xx_hal_rcc.o"     ],     [       "/home/adom/project/adom-load-firmware-toolchain/root/usr/bin/arm-none-eabi-gcc",@@ -773,7 +841,7 @@       "-c",       "/home/adom/project/adom-load-firmware-deps/tinyusb/hw/mcu/st/stm32g0xx_hal_driver/Src/stm32g0xx_hal_rcc_ex.c",       "-o",-      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/22-stm32g0xx_hal_rcc_ex.o"+      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/24-stm32g0xx_hal_rcc_ex.o"     ],     [       "/home/adom/project/adom-load-firmware-toolchain/root/usr/bin/arm-none-eabi-gcc",@@ -803,7 +871,7 @@       "-c",       "/home/adom/project/adom-load-firmware-deps/tinyusb/hw/mcu/st/stm32g0xx_hal_driver/Src/stm32g0xx_hal_gpio.c",       "-o",-      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/23-stm32g0xx_hal_gpio.o"+      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/25-stm32g0xx_hal_gpio.o"     ],     [       "/home/adom/project/adom-load-firmware-toolchain/root/usr/bin/arm-none-eabi-gcc",@@ -833,7 +901,7 @@       "-c",       "/home/adom/project/adom-load-firmware-deps/tinyusb/hw/mcu/st/stm32g0xx_hal_driver/Src/stm32g0xx_hal_i2c.c",       "-o",-      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/24-stm32g0xx_hal_i2c.o"+      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/26-stm32g0xx_hal_i2c.o"     ],     [       "/home/adom/project/adom-load-firmware-toolchain/root/usr/bin/arm-none-eabi-gcc",@@ -863,7 +931,7 @@       "-c",       "/home/adom/project/adom-load-firmware-deps/tinyusb/hw/mcu/st/stm32g0xx_hal_driver/Src/stm32g0xx_hal_i2c_ex.c",       "-o",-      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/25-stm32g0xx_hal_i2c_ex.o"+      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/27-stm32g0xx_hal_i2c_ex.o"     ],     [       "/home/adom/project/adom-load-firmware-toolchain/root/usr/bin/arm-none-eabi-gcc",@@ -893,7 +961,67 @@       "-c",       "/home/adom/project/adom-load-firmware-deps/tinyusb/hw/mcu/st/stm32g0xx_hal_driver/Src/stm32g0xx_hal_dma.c",       "-o",-      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/26-stm32g0xx_hal_dma.o"+      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/28-stm32g0xx_hal_dma.o"+    ],+    [+      "/home/adom/project/adom-load-firmware-toolchain/root/usr/bin/arm-none-eabi-gcc",+      "-mcpu=cortex-m0plus",+      "-mthumb",+      "-Os",+      "-g3",+      "-ffunction-sections",+      "-fdata-sections",+      "-fno-common",+      "-fstack-usage",+      "-Wall",+      "-Wextra",+      "-DSTM32G0B1xx",+      "-DUSE_HAL_DRIVER",+      "-DADOM_BRINGUP_OUTPUT_LOCK=1",+      "-DADOM_USB_VID=51966",+      "-DADOM_USB_PID=16385",+      "-DADOM_LAB_BOOT_ID=\"lab20261009bringup\"",+      "-I/home/adom/project/adom-custom-electronic-load/firmware",+      "-I/home/adom/project/adom-custom-electronic-load/firmware/stm32",+      "-I/home/adom/project/adom-load-firmware-deps/tinyusb/src",+      "-I/home/adom/project/adom-load-firmware-deps/tinyusb/lib/CMSIS_5/CMSIS/Core/Include",+      "-I/home/adom/project/adom-load-firmware-deps/tinyusb/hw/mcu/st/cmsis_device_g0/Include",+      "-I/home/adom/project/adom-load-firmware-deps/tinyusb/hw/mcu/st/stm32g0xx_hal_driver/Inc",+      "-I/home/adom/project/adom-load-firmware-toolchain/root/usr/include/newlib",+      "-c",+      "/home/adom/project/adom-load-firmware-deps/tinyusb/hw/mcu/st/stm32g0xx_hal_driver/Src/stm32g0xx_hal_adc.c",+      "-o",+      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/29-stm32g0xx_hal_adc.o"+    ],+    [+      "/home/adom/project/adom-load-firmware-toolchain/root/usr/bin/arm-none-eabi-gcc",+      "-mcpu=cortex-m0plus",+      "-mthumb",+      "-Os",+      "-g3",+      "-ffunction-sections",+      "-fdata-sections",+      "-fno-common",+      "-fstack-usage",+      "-Wall",+      "-Wextra",+      "-DSTM32G0B1xx",+      "-DUSE_HAL_DRIVER",+      "-DADOM_BRINGUP_OUTPUT_LOCK=1",+      "-DADOM_USB_VID=51966",+      "-DADOM_USB_PID=16385",+      "-DADOM_LAB_BOOT_ID=\"lab20261009bringup\"",+      "-I/home/adom/project/adom-custom-electronic-load/firmware",+      "-I/home/adom/project/adom-custom-electronic-load/firmware/stm32",+      "-I/home/adom/project/adom-load-firmware-deps/tinyusb/src",+      "-I/home/adom/project/adom-load-firmware-deps/tinyusb/lib/CMSIS_5/CMSIS/Core/Include",+      "-I/home/adom/project/adom-load-firmware-deps/tinyusb/hw/mcu/st/cmsis_device_g0/Include",+      "-I/home/adom/project/adom-load-firmware-deps/tinyusb/hw/mcu/st/stm32g0xx_hal_driver/Inc",+      "-I/home/adom/project/adom-load-firmware-toolchain/root/usr/include/newlib",+      "-c",+      "/home/adom/project/adom-load-firmware-deps/tinyusb/hw/mcu/st/stm32g0xx_hal_driver/Src/stm32g0xx_hal_adc_ex.c",+      "-o",+      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/30-stm32g0xx_hal_adc_ex.o"     ],     [       "/home/adom/project/adom-load-firmware-toolchain/root/usr/bin/arm-none-eabi-gcc",@@ -923,7 +1051,7 @@       "-c",       "/home/adom/project/adom-load-firmware-deps/tinyusb/hw/mcu/st/cmsis_device_g0/Source/Templates/system_stm32g0xx.c",       "-o",-      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/27-system_stm32g0xx.o"+      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/31-system_stm32g0xx.o"     ],     [       "/home/adom/project/adom-load-firmware-toolchain/root/usr/bin/arm-none-eabi-gcc",@@ -953,7 +1081,7 @@       "-c",       "/home/adom/project/adom-load-firmware-deps/tinyusb/hw/mcu/st/cmsis_device_g0/Source/Templates/gcc/startup_stm32g0b1xx.s",       "-o",-      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/28-startup_stm32g0b1xx.o"+      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/32-startup_stm32g0b1xx.o"     ],     [       "/home/adom/project/adom-load-firmware-toolchain/root/usr/bin/arm-none-eabi-gcc",@@ -961,34 +1089,38 @@       "-mthumb",       "-nostartfiles",       "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/00-load_core.o",-      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/01-instruments.o",-      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/02-measurement_cache.o",-      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/03-instrument_input.o",-      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/04-load_protocol.o",-      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/05-fan_tach.o",-      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/06-fan_input.o",-      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/07-main.o",-      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/08-clock.o",-      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/09-usb_descriptors.o",-      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/10-syscalls.o",-      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/11-tusb.o",-      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/12-tusb_fifo.o",-      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/13-usbd.o",-      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/14-cdc_device.o",-      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/15-dcd_stm32_fsdev.o",-      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/16-fsdev_common.o",-      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/17-stm32g0xx_hal.o",-      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/18-stm32g0xx_hal_cortex.o",-      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/19-stm32g0xx_hal_pwr.o",-      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/20-stm32g0xx_hal_pwr_ex.o",-      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/21-stm32g0xx_hal_rcc.o",-      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/22-stm32g0xx_hal_rcc_ex.o",-      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/23-stm32g0xx_hal_gpio.o",-      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/24-stm32g0xx_hal_i2c.o",-      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/25-stm32g0xx_hal_i2c_ex.o",-      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/26-stm32g0xx_hal_dma.o",-      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/27-system_stm32g0xx.o",-      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/28-startup_stm32g0b1xx.o",+      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/01-adc_conversion.o",+      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/02-adc_input.o",+      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/03-instruments.o",+      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/04-measurement_cache.o",+      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/05-instrument_input.o",+      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/06-load_protocol.o",+      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/07-fan_tach.o",+      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/08-fan_input.o",+      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/09-main.o",+      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/10-clock.o",+      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/11-usb_descriptors.o",+      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/12-syscalls.o",+      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/13-tusb.o",+      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/14-tusb_fifo.o",+      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/15-usbd.o",+      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/16-cdc_device.o",+      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/17-dcd_stm32_fsdev.o",+      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/18-fsdev_common.o",+      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/19-stm32g0xx_hal.o",+      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/20-stm32g0xx_hal_cortex.o",+      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/21-stm32g0xx_hal_pwr.o",+      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/22-stm32g0xx_hal_pwr_ex.o",+      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/23-stm32g0xx_hal_rcc.o",+      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/24-stm32g0xx_hal_rcc_ex.o",+      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/25-stm32g0xx_hal_gpio.o",+      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/26-stm32g0xx_hal_i2c.o",+      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/27-stm32g0xx_hal_i2c_ex.o",+      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/28-stm32g0xx_hal_dma.o",+      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/29-stm32g0xx_hal_adc.o",+      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/30-stm32g0xx_hal_adc_ex.o",+      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/31-system_stm32g0xx.o",+      "/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/32-startup_stm32g0b1xx.o",       "-T",       "/home/adom/project/adom-custom-electronic-load/firmware/stm32/stm32g0b1kc.ld",       "-Wl,--gc-sections,--print-memory-usage",
design/stm32-bringup-build.log+3−3
@@ -1,6 +1,6 @@ Memory region         Used Size  Region Size  %age Used-             RAM:       24072 B       144 KB     16.32%-           FLASH:       68196 B       256 KB     26.01%+             RAM:       24192 B       144 KB     16.41%+           FLASH:       74004 B       256 KB     28.23%    text	   data	    bss	    dec	    hex	filename-  66440	   1752	  22320	  90512	  16190	/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/adom-load-bringup.elf+  72252	   1752	  22440	  96444	  178bc	/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/adom-load-bringup.elf COMPILED ONLY: output permanently locked OFF; no flash or hardware validation performed.
docs/completion-plan.md+1−1
@@ -25,7 +25,7 @@ Sunon MF40200V1-1000U-G99 version 0.2.3 is published in source and registry: fac  ## Firmware continuation -Fresh latch acknowledgement, instrument readback/fault injection, and null-sensor diagnostic host connections are implemented and host-tested (25 Python tests plus C suites). MCU output stays locked; no flash occurred. Diagnostic I2C integration, conversion-age cache and fake-HAL tests now pass; continue with ADC/temperature adaptation and physical timing tests. Do not mistake these checks for physical qualification or use them to close thermal/finish gates. The reviewed fan-stall replay brings the raw inventory to 63 (the latest static fastener-page take is excluded from the action edit).+Fresh latch acknowledgement, instrument readback/fault injection, and null-sensor diagnostic host connections are implemented and host-tested (25 Python tests plus C suites). MCU output stays locked; no flash occurred. Diagnostic I2C integration, conversion-age cache and fake-HAL tests now pass; nominal ADC/temperature adaptation now compiles and its conversion math passes host tests. Continue with ADC adapter fault injection, board calibration and physical timing tests. Do not mistake these checks for physical qualification or use them to close thermal/finish gates. The reviewed fan-stall replay brings the raw inventory to 63 (the latest static fastener-page take is excluded from the action edit).  ## Browser recovery and fastener review 
docs/firmware-contract.md+1−1
@@ -48,4 +48,4 @@ The register model tests inject failure at every transaction in initialization,  Replies now include mandatory Boolean `sensors_valid` and `assembly_qualified`. Missing sensors may use null only when validity is false. The host accepts such a diagnostic connection only with OFF/FAULT, enable false, DAC zero and applied current zero. It preserves unavailable readings as null, does not calculate power, and refuses ARM in both server and UI. Valid telemetry still requires the existing 50 ms freshness bound. These fields are required by this draft protocol revision; update host and firmware together. A compiled C peer with missing sensors verifies the actual Python transport connection and rejected ARM. -The diagnostic I2C adapter and conservative measurement cache are compiled and host-tested together using a fake HAL. Current is available only from fresh successful conversions; post-diode voltage is not mislabeled as DUT voltage. The MCU still requires ADC/temperature integration and physical timing validation. See the STM32 README for configured timeout limits, including the HAL internal BUSY wait.+The diagnostic I2C adapter and conservative measurement cache are compiled and host-tested together using a fake HAL. Current is available only from fresh successful conversions; post-diode voltage is not mislabeled as DUT voltage. The MCU now includes nominal ADC/temperature diagnostics; board-specific calibration and physical timing validation remain required. See the STM32 README for configured timeout limits, including the HAL internal BUSY wait.
firmware/adc_conversion.cadded+17
@@ -0,0 +1,17 @@+#include "adc_conversion.h"+#include <math.h>+bool load_adc_convert(uint16_t cal,uint16_t ref,uint16_t dut,uint16_t ntc,float *v,float *t){+ if(!v||!t||v==t||cal<1000||cal>2000||ref==0||ref>4095||dut>=4095||ntc<16||ntc>4079)return false;+ float vdda=3.0f*(float)cal/ref;+ if(!isfinite(vdda)||vdda<2.7f||vdda>3.6f)return false;+ /* R24=100k / R25=10k; ADC at PA1 after the R26/C5 RC filter. */+ float volts=(float)dut/4095.0f*vdda*11.0f;+ /* R17=10k to VDDA; 10k NTC to GND. R/R25 = code/(fullscale-code).+    Vishay NTCLE100E3, material A Bn3977, inverse polynomial coefficients.+    This uses nominal resistances. Sensor tolerance/attachment are not calibrated. */+ float l=logf((float)ntc/(4095.0f-ntc));+ float inv=3.354016e-3f+2.569850e-4f*l+2.620131e-6f*l*l+6.383091e-8f*l*l*l;+ float temp=1.0f/inv-273.15f;+ if(!isfinite(volts)||!isfinite(temp)||temp< -40||temp>125)return false;+ *v=volts;*t=temp;return true;+}
firmware/adc_conversion.hadded+7
@@ -0,0 +1,7 @@+#ifndef LOAD_ADC_CONVERSION_H+#define LOAD_ADC_CONVERSION_H+#include <stdbool.h>+#include <stdint.h>+/* Nominal diagnostic conversions, not board/sensor calibration. */+bool load_adc_convert(uint16_t vref_cal,uint16_t vref,uint16_t dut,uint16_t ntc,float *volts,float *case_c);+#endif
firmware/stm32/README.md+13−4
@@ -4,7 +4,7 @@ This adapter compiles for the routed LQFP32 GP package. **Output is permanently  The adapter sets PA5 (MCU_ENABLE), PA4 (ARM_CLK), and PA6 (WDI) low before clock setup. PB3 (FAULT_CLEAR) is open drain and held low. It does not import the Nucleo LED code, because that example toggles our enable pin. Hard-fault and fatal initialization paths deassert enable. USB disconnect/suspend/DTR loss drops protocol ownership. USB attachment follows PB0 VBUS detection. The image never releases the hardware latch or writes a nonzero DAC command. -DUT voltage and case temperature remain unavailable (`null`, invalid sensors), and assembly qualification is false. Current can be reported only after a successful INA conversion and while its conservative age is at most 50 ms. Fan RPM is supplied by the PA7/TIM2 input once its pulse history qualifies. The portable protocol and ownership/replay checks run over CDC, with bounded input work and bounded reply buffering. This proves a linkable implementation, not boot timing, interrupt latency, USB compliance, or operation of the board's interlocks. Reset-time pulldowns and physical faults still require bench verification.+DUT voltage and case temperature can now be reported as nominal ADC diagnostics when acquisition succeeds. Sensor qualification and assembly qualification remain false. Current can be reported only after a successful INA conversion and while its conservative age is at most 50 ms. Fan RPM is supplied by the PA7/TIM2 input once its pulse history qualifies. The portable protocol and ownership/replay checks run over CDC, with bounded input work and bounded reply buffering. This proves a linkable implementation, not boot timing, interrupt latency, USB compliance, or operation of the board's interlocks. Reset-time pulldowns and physical faults still require bench verification.  ## Reproduce @@ -27,7 +27,7 @@ The explicitly supplied lab boot ID is **not fresh across resets**. Production f  ## Remaining adapter work -ADC calibration and temperature conversion; physical INA226/DAC60501 I2C validation and timing; physical tach timing and ISR-latency verification; physical watchdog/latch handshake; reset/brownout and auxiliary/USB power sequencing; production boot identity; integration of qualified assembly configuration; fault injection and USB reconnect tests on the actual PCB. Only then can a separately reviewed functional adapter enable the load.+board-specific ADC/sensor calibration and temperature attachment verification; physical INA226/DAC60501 I2C validation and timing; physical tach timing and ISR-latency verification; physical watchdog/latch handshake; reset/brownout and auxiliary/USB power sequencing; production boot identity; integration of qualified assembly configuration; fault injection and USB reconnect tests on the actual PCB. Only then can a separately reviewed functional adapter enable the load.  ## Fan tachometer bring-up @@ -58,6 +58,15 @@ PB6/PB7 (GP package pins 30/31) use AF6 I2C1 with external pull-ups, matching [S  The adapter performs register ID/config/readback checks at startup and permits only zero writes to the DAC data register. It samples the INA periodically; failure is sticky until reset. Main-loop output disable remains unconditional. Transfer timeout arguments are 5 ms, but the pinned ST HAL also has an internal 25 ms BUSY wait. A pre-check rejects an already busy bus. This does not establish a 5 ms overall deadline or measured worst-case execution time; production enabling requires a separately verified nonblocking/bounded control schedule. -The cache timestamps readings from before the averaging interval began, not when they were read. WAIT cannot refresh their age; errors invalidate the cache. Polling and reading expire values older than 50 ms, with unsigned timer-wrap handling. INA voltage is after the protection diode and is deliberately not sent as DUT voltage. The future PA1 ADC adapter must supply the DUT measurement, and PA0 must supply calibrated case temperature before sensors can be valid.+The cache timestamps readings from before the averaging interval began, not when they were read. WAIT cannot refresh their age; errors invalidate the cache. Polling and reading expire values older than 50 ms, with unsigned timer-wrap handling. INA voltage is after the protection diode and is deliberately not sent as DUT voltage. The PA1/PA0 ADC adapter supplies nominal diagnostics, but measured calibration and sensor attachment are still required before sensors can be valid. -Fake-HAL tests execute the real adapter, register driver and cache together: GPIO mapping, shifted bus addresses, byte order, timeout arguments, zero-only DAC, invalid startup, stale samples and sticky faults. They do not prove electrical timing, boot behavior or analog accuracy. Current image: 68,196 bytes flash; 24,072 bytes RAM including reservations. No device was flashed.+Fake-HAL tests execute the real adapter, register driver and cache together: GPIO mapping, shifted bus addresses, byte order, timeout arguments, zero-only DAC, invalid startup, stale samples and sticky faults. They do not prove electrical timing, boot behavior or analog accuracy. Current image: 74,004 bytes flash; 24,192 bytes RAM including reservations. No device was flashed.+++## ADC diagnostic acquisition++PA1 reads DUT voltage through the nominal 100k/10k divider; PA0 reads the 10k pull-up / NTC divider. ADC1 uses PCLK/4, 12-bit readings, 160.5-cycle sampling and one selected channel at a time. It runs peripheral calibration and reads the factory VREFINT calibration to estimate VDDA. Reference settling is given 1 ms; conversion polls use a 2 ms timeout. These settings still need source-impedance, reference, timing and noise verification on the physical PCB.++Temperature conversion uses the extended Steinhart–Hart coefficients for material A, Bn3977, in [Vishay NTCLE100E3 document 29049, revision 7 May 2025](https://www.vishay.com/docs/29049/ntcle100.pdf). The actual 5%-tolerance NTC and its case attachment are uncalibrated. Tests compare nominal table points at 0, 55 and 100 C, plus divider/reference scaling and saturation rejection. They do not certify temperature accuracy or shutdown temperature.++A failed conversion, implausible reference, saturated sensor or out-of-range nominal temperature invalidates ADC diagnostics until reset. Samples expire after 50 ms. The combined reply uses the older available ADC/INA timestamp. `sensors_valid` remains false even when numbers are available, and assembly qualification remains false. No production enable path was added. ADC peripheral behavior is cross-compiled but has not yet been exercised with a fake ADC HAL or on hardware.
firmware/stm32/adc_input.cadded+55
@@ -0,0 +1,55 @@+/* Nominal diagnostic ADC only. No board/sensor calibration or load enabling. */+#include "stm32g0xx_hal.h"+#include "stm32g0xx_ll_adc.h"+#include "adc_input.h"+#include "adc_conversion.h"+#if !defined(ADOM_BRINGUP_OUTPUT_LOCK) || ADOM_BRINGUP_OUTPUT_LOCK != 1+#error ADC diagnostic adapter requires permanent output lock+#endif+static ADC_HandleTypeDef adc;+static bool healthy,have;+static uint32_t stamp,last_poll;+static float volts,case_c;+static bool channel(uint32_t id,uint16_t *out){+ ADC_ChannelConfTypeDef c={0};c.Channel=id;c.Rank=ADC_RANK_CHANNEL_NUMBER;+ c.SamplingTime=ADC_SAMPLINGTIME_COMMON_1;+ if(HAL_ADC_ConfigChannel(&adc,&c)!=HAL_OK)return false;+ if(id==ADC_CHANNEL_VREFINT)HAL_Delay(1); /* conservative internal reference settling */+ if(HAL_ADC_Start(&adc)!=HAL_OK)return false;+ if(HAL_ADC_PollForConversion(&adc,2)!=HAL_OK){(void)HAL_ADC_Stop(&adc);return false;}+ *out=(uint16_t)HAL_ADC_GetValue(&adc);+ if(HAL_ADC_Stop(&adc)!=HAL_OK)return false;+ c.Rank=ADC_RANK_NONE;return HAL_ADC_ConfigChannel(&adc,&c)==HAL_OK;+}+bool load_adc_input_init(void){+ healthy=have=false;+ __HAL_RCC_GPIOA_CLK_ENABLE();__HAL_RCC_ADC_CLK_ENABLE();+ GPIO_InitTypeDef p={0};p.Pin=GPIO_PIN_0|GPIO_PIN_1;p.Mode=GPIO_MODE_ANALOG;p.Pull=GPIO_NOPULL;+ HAL_GPIO_Init(GPIOA,&p);+ adc.Instance=ADC1;adc.Init.ClockPrescaler=ADC_CLOCK_SYNC_PCLK_DIV4;+ adc.Init.Resolution=ADC_RESOLUTION_12B;adc.Init.DataAlign=ADC_DATAALIGN_RIGHT;+ adc.Init.ScanConvMode=ADC_SCAN_SEQ_FIXED;adc.Init.EOCSelection=ADC_EOC_SINGLE_CONV;+ adc.Init.NbrOfConversion=1;adc.Init.ExternalTrigConv=ADC_SOFTWARE_START;+ adc.Init.ExternalTrigConvEdge=ADC_EXTERNALTRIGCONVEDGE_NONE;+ adc.Init.Overrun=ADC_OVR_DATA_PRESERVED;+ adc.Init.SamplingTimeCommon1=ADC_SAMPLETIME_160CYCLES_5;+ adc.Init.SamplingTimeCommon2=ADC_SAMPLETIME_160CYCLES_5;+ adc.Init.TriggerFrequencyMode=ADC_TRIGGER_FREQ_LOW;+ if(HAL_ADC_Init(&adc)!=HAL_OK||HAL_ADCEx_Calibration_Start(&adc)!=HAL_OK)return false;+ last_poll=HAL_GetTick();healthy=true;return true;+}+void load_adc_input_poll(void){+ uint32_t now=HAL_GetTick();+ if(have&&(uint32_t)(now-stamp)>50)have=false;+ if(!healthy||(uint32_t)(now-last_poll)<10)return;+ last_poll=now;uint16_t ref,dut,ntc;+ if(!channel(ADC_CHANNEL_VREFINT,&ref)||!channel(ADC_CHANNEL_1,&dut)||!channel(ADC_CHANNEL_0,&ntc)||+    !load_adc_convert(*VREFINT_CAL_ADDR,ref,dut,ntc,&volts,&case_c)){+  healthy=have=false;return;+ }+ stamp=now;have=true;+}+bool load_adc_input_read(float *v,float *t,uint32_t *s){+ if(!healthy||!have||(uint32_t)(HAL_GetTick()-stamp)>50)return false;+ *v=volts;*t=case_c;*s=stamp;return true;+}
firmware/stm32/adc_input.hadded+8
@@ -0,0 +1,8 @@+#ifndef LOAD_ADC_INPUT_H+#define LOAD_ADC_INPUT_H+#include <stdbool.h>+#include <stdint.h>+bool load_adc_input_init(void);+void load_adc_input_poll(void);+bool load_adc_input_read(float *volts,float *case_c,uint32_t *sample_ms);+#endif
firmware/stm32/main.c+10−1
@@ -5,6 +5,7 @@ #include "load_protocol.h" #include "fan_input.h" #include "instrument_input.h"+#include "adc_input.h" #include <stdio.h> #include <math.h> #if !defined(ADOM_BRINGUP_OUTPUT_LOCK) || ADOM_BRINGUP_OUTPUT_LOCK != 1@@ -63,6 +64,7 @@ int main(void) {   safe_gpio_init(); /* do not wait for oscillator or USB setup to deassert enable */   if (HAL_Init() != HAL_OK || !load_clock_init()) load_fatal();   if (!load_fan_input_init()) load_fatal();+  (void)load_adc_input_init(); /* nominal ADC; no board calibration */   (void)load_instrument_input_init(); /* stay diagnostic/OFF if devices absent */   __HAL_RCC_SYSCFG_CLK_ENABLE(); __HAL_RCC_PWR_CLK_ENABLE();   GPIO_InitTypeDef gpio = {0};@@ -88,13 +90,20 @@ int main(void) {     }     tud_task();     load_instrument_input_poll();+    load_adc_input_poll();     bool online = vbus && tud_cdc_connected();     if (!online && connected) disconnect();     connected = online;     load_sample sample = {.now_ms = HAL_GetTick(), .volts = NAN, .amps = NAN,       .case_c = NAN, .fan_rpm = NAN, .assembly_qualified = false,       .sensors_valid = false, .hw_ok = false, .latch_q = false};-    (void)load_instrument_input_current(&sample.amps,&sample.sample_ms);+    bool current_ok=load_instrument_input_current(&sample.amps,&sample.sample_ms);+    uint32_t adc_stamp=0;+    bool adc_ok=load_adc_input_read(&sample.volts,&sample.case_c,&adc_stamp);+    uint32_t sampled_now=HAL_GetTick();+    if(adc_ok&&(!current_ok||(uint32_t)(sampled_now-adc_stamp)>(uint32_t)(sampled_now-sample.sample_ms)))sample.sample_ms=adc_stamp;+    /* Numeric diagnostics are nominal; sensors_valid stays false until actual+       board/sensor calibration and acquisition timing are qualified. */     sample.fan_rpm_valid=load_fan_input_read(&sample.fan_rpm,&sample.last_tach_ms);     sample.now_ms=HAL_GetTick();     load_protocol_poll(&protocol, &sample);
firmware/stm32/stm32g0xx_hal_conf.h+1−1
@@ -34,7 +34,7 @@ extern "C" {   * @brief This is the list of modules to be used in the HAL driver   */ #define HAL_MODULE_ENABLED-/* #define HAL_ADC_MODULE_ENABLED   */+#define HAL_ADC_MODULE_ENABLED /* #define HAL_CEC_MODULE_ENABLED   */ /* #define HAL_COMP_MODULE_ENABLED   */ /* #define HAL_CRC_MODULE_ENABLED   */
tests/test_adc_conversion.cadded+29
@@ -0,0 +1,29 @@+#include "../firmware/adc_conversion.h"+#include <assert.h>+#include <math.h>+#include <stdio.h>+int main(void){+ float v=-1,t=-1;+ assert(load_adc_convert(1650,1500,564,2048,&v,&t));+ assert(fabsf(v-5.0f)<.005f&&fabsf(t-25)<.03f);+ /* Independent nominal resistance points, Vishay 29049 p10 (2025-05-07). */+ const float resistance[]={32554,2989,677.3};const float temperature[]={0,55,100};+ for(unsigned i=0;i<3;i++){+  uint16_t code=(uint16_t)lroundf(4095*resistance[i]/(10000+resistance[i]));+  assert(load_adc_convert(1650,1500,564,code,&v,&t));assert(fabsf(t-temperature[i])<.12f);+ }+ assert(load_adc_convert(1650,1500,564,2048,&v,&t));+ /* Ratiometric NTC does not change with valid VDDA; voltage does. */+ float previous=t;assert(load_adc_convert(1650,1650,564,2048,&v,&t));+ assert(fabsf(v-4.545f)<.003f&&t==previous);+ float cool,hot;assert(load_adc_convert(1650,1500,0,3000,&v,&cool));+ assert(load_adc_convert(1650,1500,0,1000,&v,&hot));assert(cool<25&&hot>25&&v==0);+ const uint16_t rails[]={0,1,15,4080,4095,65535};+ for(unsigned i=0;i<sizeof rails/sizeof rails[0];i++){v=t=-1;assert(!load_adc_convert(1650,1500,564,rails[i],&v,&t)&&v==-1&&t==-1);}+ assert(!load_adc_convert(0,1500,564,2048,&v,&t));+ assert(!load_adc_convert(1650,0,564,2048,&v,&t));+ assert(!load_adc_convert(1650,1000,564,2048,&v,&t));+ assert(!load_adc_convert(1650,1500,4095,2048,&v,&t));+ assert(!load_adc_convert(1650,1500,564,2048,&v,&v));+ puts("ADC nominal conversion: divider, ratiometric NTC, invalid reference and saturation tests passed.");+}
tools/build_stm32_bringup.py+2−2
@@ -24,9 +24,9 @@ for name,path in paths.items(): cc=root/'usr/bin/arm-none-eabi-gcc'; prefix=root/'usr/bin/arm-none-eabi-' newlib=root/'usr/lib/arm-none-eabi/newlib/thumb/v6-m/nofp' h=paths['stm32g0xx_hal_driver']; c=paths['cmsis_device_g0']-sources=[P/'firmware'/s for s in ['load_core.c','instruments.c','measurement_cache.c','stm32/instrument_input.c','load_protocol.c','fan_tach.c','stm32/fan_input.c','stm32/main.c','stm32/clock.c','stm32/usb_descriptors.c','stm32/syscalls.c']]+sources=[P/'firmware'/s for s in ['load_core.c','adc_conversion.c','stm32/adc_input.c','instruments.c','measurement_cache.c','stm32/instrument_input.c','load_protocol.c','fan_tach.c','stm32/fan_input.c','stm32/main.c','stm32/clock.c','stm32/usb_descriptors.c','stm32/syscalls.c']] sources += [t/'src'/s for s in ['tusb.c','common/tusb_fifo.c','device/usbd.c','class/cdc/cdc_device.c','portable/st/stm32_fsdev/dcd_stm32_fsdev.c','portable/st/stm32_fsdev/fsdev_common.c']]-sources += [h/'Src'/('stm32g0xx_hal'+s+'.c') for s in ['', '_cortex','_pwr','_pwr_ex','_rcc','_rcc_ex','_gpio','_i2c','_i2c_ex','_dma']]+sources += [h/'Src'/('stm32g0xx_hal'+s+'.c') for s in ['', '_cortex','_pwr','_pwr_ex','_rcc','_rcc_ex','_gpio','_i2c','_i2c_ex','_dma','_adc','_adc_ex']] sources += [c/'Source/Templates/system_stm32g0xx.c',c/'Source/Templates/gcc/startup_stm32g0b1xx.s'] includes=[P/'firmware',P/'firmware/stm32',t/'src',paths['CMSIS_5']/'CMSIS/Core/Include',c/'Include',h/'Inc',root/'usr/include/newlib'] flags=['-mcpu=cortex-m0plus','-mthumb','-Os','-g3','-ffunction-sections','-fdata-sections','-fno-common','-fstack-usage','-Wall','-Wextra',
tools/check_firmware.sh+2
@@ -14,6 +14,8 @@ gcc "${cc_args[@]}" firmware/load_core.c firmware/load_protocol.c tests/test_loa "$load_check_dir/protocol" gcc "${cc_args[@]}" demos/fan-safety/trace.c firmware/load_core.c firmware/fan_tach.c -lm -o "$load_check_dir/fan-demo" "$load_check_dir/fan-demo" > "$load_check_dir/fan-trace.json"+gcc "${cc_args[@]}" firmware/adc_conversion.c tests/test_adc_conversion.c -lm -o "$load_check_dir/adc-conversion"+"$load_check_dir/adc-conversion" gcc "${cc_args[@]}" firmware/measurement_cache.c tests/test_measurement_cache.c -lm -o "$load_check_dir/measurement-cache" "$load_check_dir/measurement-cache" gcc "${cc_args[@]}" firmware/instruments.c tests/test_instruments.c -lm -o "$load_check_dir/instruments"