HD44780 LCD displays garbage (protocol timing issue, PR 5318)

Basic Information:

Printer Model: Anet A8
MCU / Printerboard: BTT Manta E3EZ
Host / SBC: Raspberry Pi CM4
klippy.log

klippy.log (6.7 KB)

Describe your issue:

My Anet A8 has an HD44780 LCD display. Since I don’t want it to be just black, I want to make it work with Klipper. I was able to figure out how to correctly connect it to the new MCU. But it only displays garbage. After deep research I found this 4 year old pull request, that fixed my issue. It just never got merged. The problem is with the default protocol timing. Changing HD44780_DELAY in ‎klippy/extras/display/hd44780.py‎ from .00004 to .00005 fixes the display issue.

Should I create a new PR? Or can you reconsider the existing PR?

1 Like

Hello @Dampfwalze !

The klippy.log appears not to have the usual contents.
Do you run something else than genuine Klipper?

I run Klipper using Docker. I disabled the log files in favor of the Docker log system.

The proper klippy.log has it’s justification for existence.

Exactly for such cases.

klippy.log (61.7 KB)

I didn’t know using the stdout would be different from using a log file for logging. But never the less, I already know my problem. I just need to be able to increase the protocol timing for the HD44780 display. In my configuration you can see that I specified hd44780_pin_delay. That parameter does not exist on the master branch. I applied the PR by hand to make it work.

If I open a PR on GitHub, will it be reviewed??

Welcome Dampfwalze,

since you are 4 people (@seho85, @GnuReligion. @TimoMF, and you) now, who had that problem and solved it with PR5318 it’s worth to renew PR5318.

@koconnor wrote on Sep 1, 2022: >I guess I’ll wait and see if any others report this issue.

4 people should be enough.

That sounds good! Should I open a new PR myself, or will you handle it?

Why me? I don’t use a HD44780 with Klipper right now. Let’s wait a bit. Someone mentioned above will probably get back.

I’m not sure how to best handle this. As mentioned in the comments of that PR, the current 40us delay is already pushing the limits of what is feasible with the current implementation. (The mcu task execution becomes busy during these transmits and a display update could easily lead to 5+ms task delays.)

What is the minimum value that does work reliably? I understand 50us works, but does 45us? What about 42us?

For what it is worth, the specs say 37us and we use 40us. So, 50us seems to be quite high.

Some possible solutions I can think of:

  • Make no change to the upstream code and have the few impacted users modify the python source code.
  • Increase the default beyond 40us so more displays are supported.
  • Add a config option to set this delay (with strict limits on the maximum value to reduce the chance of internal crashes due to task delays).

Again, I’m not sure what the best solution is.
-Kevin

2 Likes

Perhaps I can share some observations (but I have no logic analyzer; just try and error)

For my HD44780 display it was not relevant if it has 37, 40, 42 or whatever µs pin_delay,
but the reason for unreadable display was the initialisation sequence timing (that the display switches to 4-bit mode)

Unfortunately I cannot present a clean solution.

1 Like

What is the minimum value that does work reliably? I understand 50us works, but does 45us? What about 42us?

For me it seems to work reliably with 45 us. Anything lower and garbage starts to appear. But this value is probably very dependent on the unit you have. These cheap China electronics have a lot of variation. One unit might work with 37 us, while the other only works with 50 us. This is speculation though.

Some possible solutions I can think of:

Your first solution would not be very ideal. Then I would need to maintain my own version of Klipper, just for 1-6 lines of code. Moreover, this only works for tech savvy people like me, but for the average user, who happens to have a similar display, this is far from ideal. I also don’t think increasing this value for everyone would be the right way.

It would be best if affected people can just add one config setting, while the rest never notices. The docs should clearly indicate possible implications.

But then one could consider the length of the transmitted data and if it has to be send all at once, or if it can be broken down into smaller chunks. So it would only be necessary to block while transmitting one smaller chunk.

A different solution that could be considered is, rewriting the MCU part of the display controller to make use of Klippers real-time event system (sched_add_timer()). I only briefly viewed the MCUs runtime, but for me it looks like this could be used to schedule GPIO state changes efficiently. This way, the whole control logic could be fully asynchronous and non-blocking. Please correct me if I’m wrong.

As for @DaviKlip observations, could you please be a bit more specific? Which exact timing did you change how? Because with the hd44780_init_delay from the PR, I could not get anywhere when leaving the hd44780_pin_delay at 40 us.

1 Like

I meant the initialization sequence of the display after power on in hd44780.py
For example what I tried (see except) and I modified lcd_hd44780.c

But it does not work reliably. Sometimes display works correctly, sometimes it is mangled.

        # Nibble sequence with delays after each        
        # Format: (nibble_value, delay_after_in_seconds)
        init_nibbles = [
            (0x03, 0.0050),   # First 0x03: wait 5ms (datasheet: 4.1ms min)
            (0x03, 0.0002),   # Second 0x03: wait 200µs (datasheet: 100µs min)
            (0x03, 0.0002),   # Third 0x03: wait 200µs
            (0x02, 0.0002),   # Switch to 4-bit mode: wait 200µs
        ]

and

        # Now in 4-bit mode - send configuration commands as full bytes        
        # These are sent as two nibbles each (high nibble first)
        #
        # Format: (command_byte, delay_after_in_seconds)
        init_cmds = [
            # Function Set: 4-bit, 2-line, 5x8 font
            # DL=0 (4-bit), N=1 (2-line), F=0 (5x8)
            (0x28, 0.000050),

            # Display Control: Display ON, Cursor OFF, Blink OFF
            # D=1 (display on), C=0 (cursor off), B=0 (blink off)
            (0x0C, 0.000050),

            # Clear Display - this takes 1.52ms!
            (0x01, 0.002),

            # Entry Mode Set: Increment, No Shift
            # I/D=1 (increment), S=0 (no shift)
            (0x06, 0.000050),
        ]