Files
firmware/docs/notes
Oleksandr ShyshatskyiandClaude Opus 5 a7e0b3fd3e Bring the MCU handshake in line with the decompiled stock routine
The level board never left its bootloader, so klippy died on
"mcu 'levelboard': Wait for identify_response". Four separate defects,
three of them in ff-mcu-bringup.py and all of them visible in the
printer's own output once you line the runs up.

The ack. ttyS7 answers 0x01 to 'A', every time, where ttyS4 answers 0x06.
The script sent 'A' once, saw a byte it did not recognise, printed "sent
the banner but acked b'\x01'" and gave up -- leaving the board in its
bootloader. So the three bootSerial* routines were disassembled out of
1.9.7's firmwareExe rather than guessed at any further, and stock does
this:

    send = 'A'
    for (j = 0; j < 0x32; j++) {
        write(fd, &send, 1);
        read(fd, &recv, 1);
        if (recv == 6) { isAck = 1; break; }
    }

Fifty attempts, and the reply is ignored except as a reason to stop
early: isAck is initialised to 1 and never assigned 0 anywhere in the
function, so the ack byte cannot fail the handshake at all. We now do
the same fifty writes and treat a non-0x06 byte as information, not a
verdict.

One port at a time. poke() opened, polled and closed each device in
turn, and a serial port nobody holds open is shut down in the driver --
so every banner ttyS7 sent while ttyS4 was being polled went in the bin.
That is the whole explanation for the two ports taking turns reporting a
banner across consecutive runs. Both are held open for the pass now.

A read count instead of a clock. MAX_READS=50 was documented as a ~5s
ceiling, which holds only when the board says nothing: VTIME=1 bounds an
*idle* read at 0.1s, and a board that is talking returns reads instantly.
Fifty of those can elapse in milliseconds. The pass is on a wall clock
now.

"No banner" reported as "already running". It is not the same thing, and
the printer proved it: ttyS4 was declared already-running on one pass and
sent a bootloader banner on the next. Stock's ready phase is only ~5s and
never waits for a second banner, so it only ever worked when it ran right
after the board powered up; the banner period is longer than that window.
An unknown is reported as an unknown now, and a board that keeps
bannering after fifty 'A' is reported as still in its bootloader.

Separately, start.sh tested -x on the script and printed "missing" while
it sat right there in /usr/data/anvil/bin. It is handed to the
interpreter by path, so the execute bit is irrelevant -- the payload
carries mode 644 in git and only patch.sh and run-append.sh ever chmod
it. Tests -f now.

Verified against ptys standing in for the boards: heat acking 0x06 and
level acking 0x01 both come up; a board that yields 0x06 only on the
eleventh 'A' is caught in 0.2s; a board that keeps bannering is reported
as stuck with a non-zero exit; a silent port reports the real deadline;
missing devices still name both ports and do not raise.

docs/notes/10-hardware.md carries the decompiled handshake, including the
two things the three routines do not share and stock's odd
tcsetattr(fd, TCSETS, ...).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-24 13:07:10 +03:00
..