Skip to content

hd44780: fix state corruption during 4-bit read-back (busy-flag polling) - #583

Open
felixmertins wants to merge 1 commit into
buserror:masterfrom
felixmertins:hd44780-4bit-busy-readback
Open

hd44780: fix state corruption during 4-bit read-back (busy-flag polling)#583
felixmertins wants to merge 1 commit into
buserror:masterfrom
felixmertins:hd44780-4bit-busy-readback

Conversation

@felixmertins

Copy link
Copy Markdown

Fixes #582

Two independent defects made the HD44780 part unusable with firmware
that polls the busy flag in 4-bit mode (standard bidirectional wiring as
in examples/board_hd44780). Details, cycle traces and analysis in the
linked issue; summary:

  1. hd44780: don't reinterpret our own readback as pin input
    hd44780_process_read() raises IRQ_HD44780_ALL with the nibble it
    drives back to the AVR; the part's own pin hook decoded that value as
    a full pin set and clobbered RS/RW/E in pinstate, so the discard
    strobe of a busy poll was processed as a garbage write. The ALL
    case now honours HD44780_PRIV_FLAG_REENTRANT, exactly like the
    D0 ... D7 case already does.

  2. hd44780: track the data pins while driving a read back — the
    D0 ... D7 early-return under REENTRANT left pinstate stale
    while the FILTERED ioport pin IRQs cached the read-back level; a
    subsequent firmware write of the same logic level produced no edge
    notification and was latched with old data bits (single-bit
    corruption in characters written after busy polls). pinstate now
    tracks the value the part drives itself — it is the real bus level,
    nothing in the read path samples it, and the FILTERED D IRQs keep
    the bidirectional connection loop-free.

Validation: my AVR project uses an HD44780 driver that busy-polls before
every byte. Unpatched, the display stays empty / fills with garbage
(traces in the issue). With these two commits, the full LCD regression
suite (init sequence, both DDRAM lines, writes right after busy polls)
passes with no board-side workarounds. No change for firmwares that use
timed writes only, and external users of IRQ_HD44780_ALL as an input
are unaffected.

@gatk555

gatk555 commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

It looks like LLM outout. What shows it is correct?

@felixmertins

Copy link
Copy Markdown
Author

Yes, I use an LLM when writing things up. The findings themselves come from debugging my own firmware against this part, but fair point, prose doesn't prove anything. So I've added a regression test to the branch instead.

The firmware (tests/atmega644_hd44780.c) brings the LCD up in 4-bit mode and polls the busy flag before every byte, wired bidirectionally like examples/board_hd44780/charlcd.c. The test driver then compares DDRAM against what was written. Results:

  • master without these two commits: the init sequence derails, DDRAM stays empty, test fails
  • with only the first commit: single bits are dropped from characters written after busy polls, DDRAM line 2 reads "SO......ET" instead of "SOFT RESET", test fails. That is the corruption pattern from the issue.
  • with both commits: passes, and the other 21 tests in the suite still pass

The mechanism is also easy to check by hand on master: hd44780_process_read() raises IRQ_HD44780_ALL with the read-back nibble (hd44780.c line 313), and the ALL case of the part's own pin hook (line 370) decodes RS, RW and E out of that value, without the REENTRANT guard that the D0..D7 case right below it has. So every status read rewrites the part's idea of RW.

The test hooks into the existing tests/ machinery and runs as part of make run_tests. It needs one extra make rule because it links examples/parts/hd44780.c, which is not part of libsimavr.

@gatk555

gatk555 commented Jul 19, 2026

Copy link
Copy Markdown
Collaborator

I am hampered as a reviewer by being unfamiliar with both the device and the emulation code. This looks like a fairly straightforward case of a bug in peripheral code's handling of input IRQs generated by its own actions, something that has been a problem for the ioport code in the core, as well. Is that correct?

Very nice to see new test code and I am happy to merge this, except that is too verbose, both in the number of commits, their descriptions and in-code comments. Can you squash it to a single commit with a one or two-line description? I am struck by the difference in style between comments in the fix code and the test, and guessing that you wrote all the test code yourself. A personal re-write of the fix comments would be nice, but not essential.

Alternatively, I can do a squash merge, but that will put my name on your submission (I think it will show both.) Please let me know.

Thanks, G.

…test

The part decoded its own IRQ_HD44780_ALL raise during reads as pin input,
clobbering RS/RW/E; and pinstate went stale while driving reads, so the
filtered ioport IRQs swallowed the next same-level write. Both broke
busy-flag polling.
@felixmertins

felixmertins commented Jul 27, 2026

Copy link
Copy Markdown
Author

Yes, that's it. The read path raises IRQ_HD44780_ALL with the nibble it drives back, and the part's own pin hook decodes that as if the firmware had set the pins, RW included. Same kind of thing as the ioport re-entry problems. The pinstate half is fallout from the same raise, the filtered ioport IRQs remember the driven level, so if pinstate stops tracking during reads, a later write of the same level never reaches the part.

@felixmertins
felixmertins force-pushed the hd44780-4bit-busy-readback branch from 8bacfbd to 931b10e Compare July 27, 2026 13:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

hd44780: read-back corrupts part state — 4-bit busy-flag polling unusable

2 participants