← Commit history
design/firmware-protocol-check.log+2−4
@@ -1,10 +1,8 @@ load_core: safety policy tests passed (host C build; no MCU/HAL validation)-fan_tach: qualification, RPM, stale input, noise, slow fan and timer wrap passed+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 ................. -----------------------------------------------------------------------Ran 17 tests in 0.042s+Ran 17 tests in 0.027s  OK-OK: external command completed; its running time is in this session-Hint: Use exec for build, browser automation and other work outside AI Flow. Do not start an idle heartbeat; only actual work counts.
design/mcu-pinmap.json+1−1
@@ -1,7 +1,7 @@ {   "inputSha256": {     "design/netlist.json": "de7b88e327356a7d269f0a043e2bfc56a6760d00868564b0740bea3f79dc3bfd",-    "kicad/adom-custom-electronic-load.kicad_pcb": "b2a4d8321f24af5d3f95f9df6fab570b96abb298b4cc70b3f932037d2fec9f8d"+    "kicad/adom-custom-electronic-load.kicad_pcb": "347fc37404eff3e81dfb3211f06e3da4fe036657e130fec4f0f64a7e6f494f09"   },   "part": "STM32G0B1KCT6",   "package": "LQFP32 GP, not the N-suffix variant",
design/stm32-bringup-build.json+11−8
@@ -27,31 +27,34 @@     }   },   "sources": {-    "firmware/fan_tach.h": "028b3525480ba81a935167297f33b190eee7836a82ae41cca520043bd430543d",+    "firmware/fan_tach.c": "844f00c1f77e39529bd8f667d543ecee187ff4fe91108aedb7a2c36eb22846fa",+    "firmware/fan_tach.h": "20fc7b0bbb1a040dfd0453e3af22cf62e7a564241c28cf9cd08dd9b6d87ddf80",     "firmware/instruments.h": "1bcd895abf8fe74c5a96028b8b559eafdff0919e970244d4445ccbd72b7a6ee6",     "firmware/load_core.c": "f7a443d01013cc519e6b1110f2fc38abdf41222399db597cc70cca1832ae6ab3",     "firmware/load_core.h": "408263af21e6c15e82f4118343a2b7d0dd2db61808754c0b91021acd1146fadf",     "firmware/load_protocol.c": "208d7e7fb9a05da31c3c200b018f5b188e53ac3d95ac2288a61f683b4f3df1af",     "firmware/load_protocol.h": "f64f6b689d57501031515e8ee9f8c8349a2cea63738e73db16f95e747caf91fb",-    "firmware/stm32/README.md": "0d3c5c029a73e98f22d1f9520c367b3740a26d00c2fbaab70ed06a2307758972",+    "firmware/stm32/README.md": "da1875c99b20608e8bdb6bba59d3a3dc045261cfa5bbcf2df1792089f31f4dc4",     "firmware/stm32/clock.c": "f42f5c1ff229b065a048218ecc575624276aaa229e0bc340d80c80eec35416d2",     "firmware/stm32/dependencies.json": "7e8f3c17a59c856371a9f411dce6a9dadafbb8c8edd5ede5e4e5cf46b1567e92",+    "firmware/stm32/fan_input.c": "d4ebe5572dbbcd1a3ea84c4e31442732a7237e94d554ec8acdc07142486e92f8",+    "firmware/stm32/fan_input.h": "6dc99727c0e3aa5f711cdfd00b4c53d7bf123ad6698bbe842a64ec6b7d87e77b",     "firmware/stm32/licenses/CMSIS_5-LICENSE.txt": "b40930bbcf80744c86c46a12bc9da056641d722716c378f5659b9e555ef833e1",     "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": "c089315fab9ffc3315508f44132039dfbf5b51e3c089260adf7d4e009963881a",+    "firmware/stm32/main.c": "522b0960564c91dca05b876f0ab05aad4707ac57fb5190b623f7e8e92f3ef84d",     "firmware/stm32/stm32g0b1kc.ld": "1c938076e760300379115ee955589434dba7b84d1bb945dd41cf39abd342fd13",     "firmware/stm32/stm32g0xx_hal_conf.h": "2e2a3aed4bf01ce3f890df69061e740369ed8839bb53d5b1be745be4de185b6f",     "firmware/stm32/syscalls.c": "bbfcedecfe5f33380424ab90cdb89c842c6a5717e01298a29ec43aef24c1524d",     "firmware/stm32/tusb_config.h": "dfbb343cc609aa2693058b7d7e6e6c487524222cbe3eab5296568eca0b7d3970",     "firmware/stm32/usb_descriptors.c": "ed2666d7115f05a07088dd92ebf35a600aadd8d9878fae2eb7635201b8f0b2a9",-    "tools/build_stm32_bringup.py": "2a199449eb8c9f5d51a9ab9250f7ca988b19b736b5fb843a347c6cdcceca9077"+    "tools/build_stm32_bringup.py": "20f8d74fa7bfc8d3ea9a4ba5fce1b912b98a88389285111ff70d58fde4aa93f2"   },   "artifacts": {-    "adom-load-bringup.hex": "789ab63e515857a23c11f17d343ca44ddd4f80f948631453c282b378b445db6f",-    "adom-load-bringup.bin": "a4da063939102f26ba5bcb18514f17c2c21526f9edc84de3740a7f8e67f8f0c4",-    "adom-load-bringup.map": "7d40c48d7a37ccac6191961c26e141627070a1de1fce38a45705a9066eb6028c",-    "adom-load-bringup.elf": "37f5f17123ee96cc27cc746860e2cc7f3d4350181fc75d3046c88f8025e38c40"+    "adom-load-bringup.hex": "42f04e001c0fcffa13830f8093853a73a79921f73f598670ffd9537236cb5bf8",+    "adom-load-bringup.bin": "1e7c37698fe2f07f5160709788b912531ca02c8e64496c145c3919f1d4802d6f",+    "adom-load-bringup.map": "85e535ea52666b9f87b7795fd2ef9b0cca838e69ee95917aa8bae6193aa43d81",+    "adom-load-bringup.elf": "85d9669f5fd738ffc1153c34b7c1666dd6dd5d937eeee1e33241bcbf6e4377d6"   } }
design/stm32-bringup-build.log+3−5
@@ -1,8 +1,6 @@ Memory region         Used Size  Region Size  %age Used-             RAM:       23912 B       144 KB     16.22%-           FLASH:       63108 B       256 KB     24.07%+             RAM:       23968 B       144 KB     16.25%+           FLASH:       64592 B       256 KB     24.64%    text	   data	    bss	    dec	    hex	filename-  61352	   1752	  22160	  85264	  14d10	/home/adom/project/adom-custom-electronic-load/build/stm32-bringup/adom-load-bringup.elf+  62836	   1752	  22216	  86804	  15314	/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.-OK: external command completed; its running time is in this session-Hint: Use exec for build, browser automation and other work outside AI Flow. Do not start an idle heartbeat; only actual work counts.
docs/completion-plan.md+2−2
@@ -4,11 +4,11 @@ The active objective is to finish this electronic-load design through AIFlow, th  ## Latest accepted board -AIFlow aiflow/run/board-14.kicad_pcb, native-saved SHA-256 `b2a4d8321f24af5d3f95f9df6fab570b96abb298b4cc70b3f932037d2fec9f8d`. Cooling-clearance changes and five verified vias are landed. Native candidate DRC: zero errors/opens, fifteen library lookup warnings; saved physical copper matches. Current step: thermal-vias, with unacknowledged shortfalls. Three 3 A current-neck flags and the external case-cooling model remain unresolved. Portable device protocol and host interoperability checks pass. A pinned, output-locked STM32 USB bring-up image compiles; it has not been flashed and the functional HAL is incomplete.+Native-saved current-path rework SHA-256 `347fc37404eff3e81dfb3211f06e3da4fe036657e130fec4f0f64a7e6f494f09`, adopted and checked as AIFlow board-16. D1 rotation and parallel power paths are landed. Native DRC with project rules: zero errors/opens; saved physical copper exactly matches the candidate. The paired 0.1 mm explicit-terminal diagnostic reduces four power-path peaks but leaves SHUNT_P at 61.7 A/mm² against a 60 threshold. Ground rises to 43.3 A/mm². Current step: routing revisit; thermal-via shortfalls and case cooling remain unresolved. Portable protocol checks pass. The output-locked STM32 USB bring-up image compiles but is unflashed and its functional HAL is incomplete. See current-path-rework.md.  ## Resume here -1. Inspect the live AdomLapper board and AIFlow run; current required step is thermal-vias (visit 3). Preserve other threads and project rules.+1. Inspect the live AdomLapper board and AIFlow run; current required step is routing revisit. Preserve other threads and project rules. Continue the isolated shunt copper experiment only after checking its current/DRC results; it is not yet adopted. 2. Continue the compact 5 W prototype basis announced on 9 October. It follows the original size/cost priority and 5 V/1 A DUT. The prior 25 W assistant assumption is documented as a future option, not a blocker. Keep the 55 C trip and all validation requirements. 3. Solve the actual MOSFET case/interface/sink/fan assembly and probe clearance. The shunt-via candidate now passes only with the verified GND stitch at (133.018,77.818). Earlier candidates without that stitch disconnected ground; do not replay them. Via-count acknowledgements require actual engineering justification, not a desire to advance the timeline. 4. Complete and revalidate pours, outline, same-board current/thermal Fields screens, net tour, full fitted-model/silkscreen review, native 3D, finish and fabrication exports in the enforced sequence. Record significant real actions. Never mark these passed to bypass an unresolved gate.
firmware/fan_tach.c+3
@@ -8,6 +8,9 @@ void fan_tach_edge(fan_tach *f,uint32_t now){  if(period<3000 || period>6000){f->count=0;f->next=0;return;}  f->periods_us[f->next]=period;f->next=(f->next+1)%8;if(f->count<8)f->count++; }+void fan_tach_expire(fan_tach *f,uint32_t now){+ if(f->seen && now-f->last_edge_us>15000)fan_tach_init(f);+} bool fan_tach_read(const fan_tach *f,uint32_t now,float *rpm){  *rpm=0;  if(!f->seen || f->count<8 || now-f->last_edge_us>15000)return false;
firmware/fan_tach.h+3
@@ -6,5 +6,8 @@ typedef struct { uint32_t last_edge_us, periods_us[8]; unsigned count, next; boo void fan_tach_init(fan_tach *f); /* Call for rising edges only from a microsecond timer. Snapshot atomically outside ISR. */ void fan_tach_edge(fan_tach *f, uint32_t now_us);+/* Poll regularly (well before the 32-bit timer wraps) to discard stale history.+ * Serialize with fan_tach_edge when used by an interrupt. */+void fan_tach_expire(fan_tach *f, uint32_t now_us); bool fan_tach_read(const fan_tach *f, uint32_t now_us, float *rpm); #endif
firmware/stm32/README.md+16−2
@@ -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. -Telemetry is unavailable (`null` measurements, invalid sensors), and assembly qualification is false. 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.+Voltage, current and temperature telemetry remain unavailable (`null`, invalid sensors), and assembly qualification is false. 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,4 +27,18 @@ The explicitly supplied lab boot ID is **not fresh across resets**. Production f  ## Remaining adapter work -ADC calibration and temperature conversion; INA226/DAC60501 I2C integration and readback; 32-bit tach timestamp capture and ISR latency; 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.+ADC calibration and temperature conversion; INA226/DAC60501 I2C integration and readback; 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++PA7 rising edges timestamp the free-running 32-bit TIM2 counter at 1 MHz.+The existing decoder requires eight valid periods and assumes two pulses per+revolution. The main loop snapshots it with interrupts masked, expires stale+history after 15 ms, and requalifies after a main-loop polling gap over 15 ms.+This prevents old RPM history from becoming valid again when the microsecond+counter wraps. USB replies expose the measured RPM only while qualified.++This is compiled integration, not a measured fan test. Confirm the fan's pulse+count, external pull-up, oscillator accuracy, ISR latency under USB traffic,+disconnect/stuck-wire behavior and physical stall response on the actual board.+No fan drive, load-enable, or assembly qualification is introduced by this input.
firmware/stm32/fan_input.cadded+43
@@ -0,0 +1,43 @@+/* Read-only fan tach adapter. No fan drive or load-enable output. */+#include "stm32g0xx_hal.h"+#include "fan_tach.h"+#include "fan_input.h"+static fan_tach tach;+static uint32_t previous_poll_ms;+bool load_fan_input_init(void) {+  /* This clock tree uses APB divide-by-one; refuse any other configuration. */+  uint32_t hz=HAL_RCC_GetPCLK1Freq();+  if(hz!=HAL_RCC_GetHCLKFreq() || hz<1000000 || hz%1000000) return false;+  __HAL_RCC_TIM2_CLK_ENABLE();+  __HAL_RCC_TIM2_FORCE_RESET(); __HAL_RCC_TIM2_RELEASE_RESET();+  TIM2->PSC=hz/1000000-1; TIM2->ARR=UINT32_MAX;+  TIM2->EGR=TIM_EGR_UG; TIM2->SR=0; TIM2->CNT=0;+  TIM2->CR1=TIM_CR1_CEN;+  fan_tach_init(&tach); previous_poll_ms=HAL_GetTick();+  GPIO_InitTypeDef gpio={0};+  gpio.Pin=GPIO_PIN_7; gpio.Mode=GPIO_MODE_IT_RISING; gpio.Pull=GPIO_NOPULL;+  HAL_GPIO_Init(GPIOA,&gpio); /* external tach pull-up is on the PCB */+  __HAL_GPIO_EXTI_CLEAR_RISING_IT(GPIO_PIN_7);+  HAL_NVIC_SetPriority(EXTI4_15_IRQn,1,0);+  HAL_NVIC_EnableIRQ(EXTI4_15_IRQn);+  return true;+}+void EXTI4_15_IRQHandler(void) {+  if(__HAL_GPIO_EXTI_GET_RISING_IT(GPIO_PIN_7)) {+    uint32_t now=TIM2->CNT;+    __HAL_GPIO_EXTI_CLEAR_RISING_IT(GPIO_PIN_7);+    fan_tach_edge(&tach,now);+  }+}+bool load_fan_input_read(float *rpm,uint32_t *last_edge_ms) {+  uint32_t mask=__get_PRIMASK(); __disable_irq();+  uint32_t ms=HAL_GetTick(),us=TIM2->CNT;+  /* A delayed main loop must requalify, even across a microsecond-timer wrap. */+  if(ms-previous_poll_ms>15)fan_tach_init(&tach);+  previous_poll_ms=ms;+  fan_tach_expire(&tach,us);+  bool valid=fan_tach_read(&tach,us,rpm);+  *last_edge_ms=valid ? ms-(us-tach.last_edge_us)/1000 : ms;+  __set_PRIMASK(mask);+  return valid;+}
firmware/stm32/fan_input.hadded+7
@@ -0,0 +1,7 @@+#ifndef ADOM_FAN_INPUT_H+#define ADOM_FAN_INPUT_H+#include <stdbool.h>+#include <stdint.h>+bool load_fan_input_init(void);+bool load_fan_input_read(float *rpm, uint32_t *last_edge_ms);+#endif
firmware/stm32/main.c+4
@@ -3,6 +3,7 @@ #include "stm32g0xx_hal.h" #include "tusb.h" #include "load_protocol.h"+#include "fan_input.h" #include <stdio.h> #include <math.h> #if !defined(ADOM_BRINGUP_OUTPUT_LOCK) || ADOM_BRINGUP_OUTPUT_LOCK != 1@@ -60,6 +61,7 @@ void tud_cdc_line_state_cb(uint8_t itf, bool dtr, bool rts) { 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();   __HAL_RCC_SYSCFG_CLK_ENABLE(); __HAL_RCC_PWR_CLK_ENABLE();   GPIO_InitTypeDef gpio = {0};   gpio.Pin = GPIO_PIN_11 | GPIO_PIN_12;@@ -89,6 +91,8 @@ int main(void) {     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};+    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);     /* Bounded ingress; never consume a request without room for its full reply. */     for (unsigned n = 0; online && n < 64 && tud_cdc_available() &&
mechanical/cooling-body-audit.json+1−1
@@ -1,5 +1,5 @@ {-  "boardSha256": "74350d625de85228c9ee876f83748d8ed08d021b026cd5ebbec9d69e059fccff",+  "boardSha256": "347fc37404eff3e81dfb3211f06e3da4fe036657e130fec4f0f64a7e6f494f09",   "method": "Conservative transformed fitted STEP bounding boxes, board XY with Z above top copper; zero local model transforms only",   "sinkEnvelopeMm": [     112,
tests/test_fan_tach.c+6−1
@@ -13,5 +13,10 @@ int main(void){  t+=7000;fan_tach_edge(&f,t);assert(!fan_tach_read(&f,t,&rpm)); /* below window */  fan_tach_init(&f);t=UINT32_MAX-1000;fan_tach_edge(&f,t);  for(int i=0;i<8;i++){t+=4286;fan_tach_edge(&f,t);}assert(fan_tach_read(&f,t,&rpm)); /* wrap */- puts("fan_tach: qualification, RPM, stale input, noise, slow fan and timer wrap passed");+ uint32_t old=t;fan_tach_expire(&f,t+15001);assert(!f.seen);+ /* Same timer value after a complete wrap must not resurrect old RPM. */+ assert(!fan_tach_read(&f,old,&rpm));+ t+=20000;fan_tach_edge(&f,t);assert(!fan_tach_read(&f,t,&rpm));+ for(int i=0;i<8;i++){t+=4286;fan_tach_edge(&f,t);}assert(fan_tach_read(&f,t,&rpm));+ puts("fan_tach: qualification, RPM, stale input, noise, slow fan, timer wrap and stale-history expiry passed"); }
tools/build_stm32_bringup.py+1−1
@@ -24,7 +24,7 @@ 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','load_protocol.c','stm32/main.c','stm32/clock.c','stm32/usb_descriptors.c','stm32/syscalls.c']]+sources=[P/'firmware'/s for s in ['load_core.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']] sources += [c/'Source/Templates/system_stm32g0xx.c',c/'Source/Templates/gcc/startup_stm32g0b1xx.s']
videos/inventory.json+7−1
@@ -1,5 +1,5 @@ {-  "rawTakeCount": 52,+  "rawTakeCount": 53,   "reviewedExcerptCount": 16,   "countingRule": "Unique raw window recording filenames in this project; excludes derivative action/speed exports and reference projects. Raw count includes rejected takes.",   "takes": [@@ -314,6 +314,12 @@       "durationSeconds": 68.2,       "topic": "Actual native D1 rotation and parallel power-path rerouting",       "review": "Full decode passed; contact sheet inspected. 11.5 second action cut available; labels clutter the 2D view and fill display is off. Final editorial selection pending. Current and thermal gates unresolved."+    },+    {+      "file": "aiflow/run/window-1514974-20261009-154631.mp4",+      "durationSeconds": 67.8,+      "topic": "Routing checkpoint after adopted current-path rework",+      "review": "Contact sheet inspected; nearly static. Exclude from action edit. Not a routing demonstration."     }   ] }