Correct the docs: serial receive is implemented now

The previous commit documented serial receive as a permanent limitation, listing
what would be needed to build it. It has since been built, so that section was
actively misleading -- it told the reader not to try something that already works.

Replaced with what it takes to keep working, since each of the three pieces fails
silently on its own: USART1 needs a chardev, DMA must decrement CNDTR for USART
(serviced on the CNDTR read, which is where the driver looks), and DR writes must
reach the chardev and not just stderr. The last one is the trap -- transmit that
goes only to stderr is indistinguishable from the firmware ignoring the command.

README status table and build-verification steps updated to match.
This commit is contained in:
mckero committed 2026-08-28 16:26:55 +01:00
1 parent b84d229326
commit f3412f3316
2 files changed
+27 -18

No files matched your search

+23 -17
View File
@@ -452,26 +452,32 @@ from a passing emulator test.
Timing is also deliberately wrong — see the SysTick section in README.md. Fine Timing is also deliberately wrong — see the SysTick section in README.md. Fine
for menus and control flow; useless for signal timing. for menus and control flow; useless for signal timing.
**Serial receive does not work.** Transmit does — the firmware banner and its ## Serial, both directions
`printf` output reach stderr and the web UI log — but nothing can be sent *to* the
firmware. Two things are missing, and both would need building:
- USART1 is a `py32-stub` with no chardev backend, so there is no source of Works, and `tools/test_serial_rx.py` proves it by speaking the real protocol:
incoming bytes. `0x0514` hello gets a `0x0515` ack, and `0x051B` returns the requested EEPROM bytes.
- `driver/uart.c` receives over DMA channel 2 in `LL_DMA_MODE_CIRCULAR` and finds Attach with `-serial unix:/path/to.sock` or any other chardev; it defaults to
the write pointer with `sizeof(UART_DMA_Buffer) - LL_DMA_GetDataLength(...)`. The `serial0`.
DMA model only services SPI and never decrements `CNDTR` for USART, so that
expression is always 0 and the firmware sees an empty buffer. A channel sitting
permanently armed with `cndtr=256` and `cpar=0x40013804` is this, not a bug.
What that costs: `UART_IsCommandAvailable` never fires, so the whole UV-K5 Three things had to line up, and each failed silently on its own:
programming protocol in `app/uart.c` is unreachable — `0x0514` handshake, `0x051B`
EEPROM read, `0x051D` EEPROM write, `0x05DD` reset. CPS/CHIRP-style tools cannot
talk to this emulator. Keypad, screen and the web UI are unaffected.
Doing it properly means giving USART1 a real chardev, implementing circular-mode - **USART1 needs a chardev.** It is otherwise a register stub with nowhere for
DMA with a decrementing `CNDTR`, and driving it from receive rather than from incoming bytes to come from.
`TXDMAEN` as the SPI path does. - **DMA has to service USART, decrementing `CNDTR`.** `driver/uart.c` never reads
DR. It receives over a circular channel and locates new data with
`sizeof(UART_DMA_Buffer) - LL_DMA_GetDataLength(...)`, so a count that never moves
means a buffer that always looks empty, no matter how many bytes arrived. The
service runs on a `CNDTR` read, which is exactly where the driver looks — no timer
needed, and nothing can be delivered before the guest asks for it.
- **DR writes must also reach the chardev.** They used to go only to stderr. A host
tool would send a command, the firmware would answer, and the answer went
somewhere the tool could not see. That is indistinguishable from being ignored,
and it cost a debugging round: the first run of the new test reported "no reply at
all" alongside *zero* bytes of boot output, which looked like broken receive when
in fact transmit was fine and simply invisible.
Channels also record the length they were programmed with, because `CNDTR` counts
down and the write offset has to come from the difference.
## If you add a peripheral ## If you add a peripheral
+4 -1
View File
@@ -40,7 +40,7 @@ has no public datasheet, so its driver is the only specification available.
| Frequency entry | works, stored per band and kept | | Frequency entry | works, stored per band and kept |
| Keypad and menu navigation | works, including waking from power save | | Keypad and menu navigation | works, including waking from power save |
| Serial output (firmware log) | works, appears in the web UI log | | Serial output (firmware log) | works, appears in the web UI log |
| Serial input / CPS programming | **not implemented**, see [AGENTS.md](AGENTS.md#what-this-cannot-do) | | Serial input, CPS programming protocol | works, `-serial` any chardev |
| Timing accuracy | deliberately wrong, see [Timing](#timing) | | Timing accuracy | deliberately wrong, see [Timing](#timing) |
| Radio/RF behaviour | not modelled | | Radio/RF behaviour | not modelled |
@@ -77,6 +77,8 @@ keypresses silently stop working. Run the test after touching that code;
keypad_test.py keypad regression test, boots its own instance keypad_test.py keypad regression test, boots its own instance
test_flash_persist.py flash writes survive a power cycle test_flash_persist.py flash writes survive a power cycle
test_freq_entry.py a typed frequency takes effect and persists test_freq_entry.py a typed frequency takes effect and persists
test_serial_rx.py the firmware answers programming commands
lib_kill_emulator.sh cleanup that only ever kills emulators
webui.py web remote control: live LCD plus clickable keypad webui.py web remote control: live LCD plus clickable keypad
dn42_firewall.sh restrict the web UI port to DN42 sources dn42_firewall.sh restrict the web UI port to DN42 sources
restore_flash.sh roll the flash image back to its pristine state restore_flash.sh roll the flash image back to its pristine state
@@ -118,6 +120,7 @@ Then check the build actually works, which takes about a minute:
python3 tools/keypad_test.py python3 tools/keypad_test.py
python3 tools/test_flash_persist.py python3 tools/test_flash_persist.py
python3 tools/test_freq_entry.py python3 tools/test_freq_entry.py
python3 tools/test_serial_rx.py
This matters more than it looks. The keypad can break silently under -O2 without This matters more than it looks. The keypad can break silently under -O2 without
any compiler warning -- see the `volatile` note in [Status](#status) -- so a clean any compiler warning -- see the `volatile` note in [Status](#status) -- so a clean