- boot_usart: bare-metal USART1 460800, polled TX, 3KB DMA ring RX, SysTick millis
- boot_cli: polled line editor + mini printf + command subset; wire-compatible
with the app UART CLI (prompt/devinfo cur bank/ota marker) so
ble_ota_update.py --uart works unchanged against the bootloader
- share app_ota.c/app_bootset.c into the Boot build via BOOT_FIRMWARE ifdef
(single OTA engine source); frame constants moved to shared Boot/src/ota_wire.h
- entry: 2s Enter window after the boot banner, or forced when no jumpable
image (invalid bootsetting + APP1 vector sanity fail) - brick recovery path
- OTA target in boot = inactive bank per bootsetting (APP1 when invalid)
- merge_image.py: hard-fail when boot bin exceeds the 16KB region
- APP V1.00.28 (shared-source touch only, behavior unchanged)
- tested: normal boot / download CLI / OTA APP1<->APP2 round trip / bricked
recovery (bootsetting+APP1 sector erased -> forced download -> OTA restore)
- docs: dev log sec.52, ble_protocol sec.6.8, AGENTS.md
Per request the full portable environment is now in git: a fresh clone's
tools/ runs everything with zero installation. Only regenerable release
zips and the dev venv stay ignored.
- tools/python-embed/: embedded Python 3.12.10 runtime with bleak/pyserial
preinstalled (git-ignored, fetched from the huaweicloud mirror); bats
resolve Python in order: python-embed -> .venv-ble -> system
- NSpyocd.exe + DFP pack copied into tools/ (git-ignored);
flash_package.py prefers the in-tools copies
- make_release.bat/ps1: one-click zip to out/mothercup_tools_<ts>.zip
(staged in TEMP to avoid self-inclusion; zip excluded from git)
- all bats converted to CRLF (LF-only breaks cmd block parsing and %~dp0)
- verified: zip extracted outside the repo runs ble_ota --uart end-to-end
(PASS, 27 kB/s) and flash_package via the bundled NSpyocd
- docs: dev log section 49, AGENTS.md tools section + heap 18KB fix
The venv is a per-machine artifact and stays out of git; the bats
auto-create it on first run but only installed bleak - pyserial
(required by UART OTA) was missing on a fresh machine. Both bats now
install from requirements-ble.txt (bleak==3.0.2, pyserial==3.5).
- firmware BSP_USART_BAUDRATE 460800 (divider error +0.03%); uartrst/
uartinfo follow the macro
- RX DMA ring 2KB -> 3KB: at 460800 2KB only covers 44ms, less than the
~45ms flash-erase interrupt-off window; stack top now 0x2000BDE8
(guard warns <1KB from the 0x2000C000 cliff, by design)
- tools: ble_ota_update.py UartTransport and uart_cap.py default baud
- Verified: stream OTA 55832B in 2.6s @23kB/s (one lost frame recovered
by go-back-N), PASS after reboot; text CLI fine at 460800
- docs: ble_protocol.md, design spec, AGENTS.md, dev log section 48
Firmware:
- bsp_usart: RX moved from RXDNE byte-queue IRQ to DMA CH2 circular ring
(2KB) + IDLE-irq wakeup; bytes keep landing during flash-erase
interrupt-off windows (the reason lockstep was needed before)
- app_ota: stream mode (ble_protocol.md section 6.7) - 16B BEGIN with
flags bit0=STREAM (UART channel only), DATA acked on 4KB sector
crossings (original chunk length; two ack-gating bugs fixed during
field test), throttled BAD_STATE for go-back-N
- FreeRTOS heap 20KB->18KB: the 2KB ring pushed the stack top past the
0x2000C000 SRAM cliff (probed via SWD: accesses above fault on this
silicon, usable app SRAM is 32KB) which locked the board at boot;
merge_image.py now hard-fails the package when the image initial SP
leaves (0x20004000, 0x2000C000]
Tools:
- ble_ota_update.py: UART stream sender (8KB window, stall watchdog
rewind, auto-fallback to lockstep on pre-V1.00.24 firmware,
--lockstep to force); case-insensitive option parsing
- flash_package.py/bat: stream NSpyocd output live (chunked reads keep
the \r progress bar), vendor banner rebranded to CAIIC NSLINK UMP
- merge_image.py: initial-SP cliff guard
Verified: UART stream OTA both directions, 55.8KB in ~5.9s @9.5kB/s
0 rewinds (lockstep was 39s), PASS after reboot; board boot fixed and
verified via SWD.
Docs: ble_protocol.md section 6.7, dev log section 47 (+ SRAM cliff
post-mortem), AGENTS.md RAM rule rewritten (both cliffs) + V1.00.25
- firmware version bump V1.00.22 -> V1.00.23 (0x00010017), no code change;
user-verified BLE OTA (APP1->APP2) and UART OTA both PASS
- ble_ota_update.py: frame-level live progress bar on tty (bar/pct/bytes/
rate/per-frame ack RTT, ~10fps throttled), 5%-step lines when redirected;
resend/resync notices no longer mix into the bar line; END summary now
reports resent/resynced counters; BEGIN/END stage banners
- docs: dev log 46, AGENTS.md sync
- firmware: version bump to V1.00.22 (0x00010016), no code change
- ble_ota_update.py: cur-bank detection now reads the full devinfo output
and parses strictly (early break at the 'cur bank:' prefix could miss
'APP2' and silently default to APP1 -> wrong payload selection, caught
by the device bank_mismatch guard on hardware)
- new pre-flight check_blob_bank(): payload vector reset PC must land in
the target bank before transferring (BLE and UART paths)
- UART path: post-reboot verification over the text CLI (bank+version),
deterministic PASS/FAIL like the BLE path
- verified: UART OTA APP2->APP1 V1.00.22 full pass with auto-verify
- docs: dev log 45, AGENTS.md sync
- UART binary mode switch mechanism: enter handshake marker '[ota] binary
mode ON', explicit exit via OTA_ABORT (immediate), 10s idle fallback
(was 3s, must exceed the host retry window); host resync by CR probe
- fix: otaIdleMs not re-armed on mode entry - every second 'ota' entry was
kicked out by an instant idle timeout (leftover count from prior session)
- lossy-link tolerance: device drops partial frames after a 100ms byte gap
so host resends re-align; OTA session is now single-owner (second BEGIN
on another channel gets BAD_STATE)
- ble_ota_update.py: wait for the enter marker instead of blind sleeps,
per-frame resend (2s x3), BAD_STATE+expected-offset treated as implicit
ack for duplicate DATA/BEGIN (resync after ack loss), stage-labeled
timeouts with raw-rx dump
- verified on hardware: UART OTA full pass (55.7KB/41s, 4-5 lost frames
auto-recovered) + BLE OTA regression pass; docs: dev log 44,
ble_protocol.md 6.6 rework, AGENTS.md sync
- app_ota.c: after END ok, wait until the RSP is consumed by the host
(BLE e0005 read-out / UART TX done) plus 300ms grace before resetting,
2s timeout fallback; polled from the BLE schedule task (rwip_schedule
must keep running for the ATT read to be processed)
- main.c: app_ota_reset_poll() in the BLE schedule task loop
- docs: ble_protocol.md 6.6 note, dev log section 43, AGENTS.md sync