duinoWitchery / hd44780

Extensible hd44780 LCD library
GNU General Public License v3.0
238 stars 57 forks source link

4 bit mode stability? #19

Closed gitterman closed 3 years ago

gitterman commented 3 years ago

I did build a timer for my heater with the hd44780 lib and I occasionally observe weird displays which I only can cure by power cycling my timer box. Line 343 of the hd44780.cpp states that there must be 'three "goto 8 bit mode" followed by one "goto to 4 bit mode" if using 4 bit mode.' This is in line with "state 3" in the "mode selection" paragraph of https://en.wikipedia.org/wiki/Hitachi_HD44780_LCD_controller . If I understand correctly, it is only done at initialization, though. What happens, if there is a glitch somehow at runtime? Will the display ever catch up again, if it has lost its 4-bit mode?

bperrybap commented 3 years ago

Line 343 of the hd44780.cpp states that there must be 'three "goto 8 bit mode" followed by one "goto to 4 bit mode" if using 4 bit mode.' This is in line with "state 3" in the "mode selection" paragraph of https://en.wikipedia.org

The comment about being "in line with state 3" is incorrect or a misunderstanding of how the 3 goto 8 bit mode command sequence works. The 3 command sequence is designed to cause the LCD end up in 8 bit mode when the sequence completes. The state the LCD is in has no impact on the commands the host will be sending to reliably first get the LCD into 8 bit mode so it can then get the LCD into 4 bit mode.

The host must always send 3 "goto 8 bit mode" to ensure that the hd44780 will always be set to 8 bit mode regardless of what state the hd44780 LCD is in when the sequence starts. In the detailed description on the Wikipedia page and in the .cpp file you can see how the lcd will move through the states depending on the state it starts in. Depending on what state the LCD is in when 3 commands are sent, the LCD may: 1) (if starting in 8 bit mode) enter 8 bit mode 3 times 2) (if starting in 4 bit mode waiting for first nibble) enter 8 bit mode twice 3) (if starting in 4 bit mode waiting for 2nd nibble) This state has 2 possibilities a) execute a garbage command, then enter 8 bit mode b) execute a garbage command (if that happened to be a goto 8 bit mode [possible but not likely]), then enter 8 bit mode twice.

If I understand correctly, it is only done at initialization, though.

yes The hd44780 library only does this sequence once during the begin() call.

What happens, if there is a glitch somehow at runtime? Will the display ever catch up again, if it has lost its 4-bit mode?

If there is a glitch and the host and the LCD get out of nibble sync, then they are out of sync with each other until the interface is re-initialized.

I did build a timer for my heater with the hd44780 lib and I occasionally observe weird displays which I only can cure by power cycling my timer box.

When controlling high loads, it is not uncommon to have electrical issues due to either electrical noise from things like motors activating/de-activating, relays switching (typically high currents), or through high current loads causing voltage undershoot / overshoot on the main power which can cause issues on the supply voltage feeding voltage to the Arduino and/or the LCD.

First thing is make sure you have flyback diodes on any relay / contactor you are using. https://en.wikipedia.org/wiki/Flyback_diode

Here are some things that can help:

I'm not sure how you are controlling the heater but is is possible that you will need to provide some isolation between power to the relay / contactor being used and the Arduino.

There are tons of stuff about this if you search around. Here are a few links: https://electronics.stackexchange.com/questions/237688/arduino-16x2-lcd-weird-symbols-after-relay-switch https://forum.arduino.cc/index.php?topic=401632.0 https://forum.arduino.cc/index.php?topic=39140.0 https://forum.arduino.cc/index.php?topic=177827.0

gitterman commented 3 years ago

Thanks a lot for getting back to me so quickly. My LCD is operated in 4bit mode. The power to my heater is only switched on when I start the timer and switched off at the end. The LCD displays the count down time once per second and the problem does appear during that time, when the power line is not switched. I will add a decoupling C to the LCD to improve power stability and possibly add some code to be able to re-initialize the LCD manually at runtime. Thanks again for your hints.

bperrybap commented 3 years ago

I would really focus on trying to fully resolve the issue vs attempting to work around it. You can call begin() again to re-initialize, but If you are getting power glitches, there could be other issues like potential memory corruption.

It is interesting that you see the issue when the power is not being switched which means something else may be going on. What Arduino board are you using? And how is it powered? How long are your i2c wires? Do you know if your backpack has the required pullups on the i2c signals? You can run I2CexpDiag and it will tell you.

bperrybap commented 3 years ago

Actually, I just assumed you were using i2c. Maybe you are not. Which i/o class and what type of LCD are you using?

gitterman commented 3 years ago

I use 4 bit parallel mode on a blue-white LCD1602 with an arduino mini, not i2c. The device is powered by a 5V / 1A power supply. I am using PWM for contrast and backlight adjustment though, which may possibly create issues. The backlight PWM drives the backlight directly and the contrast PWM is smoothed by an 1k-10uF RC filter.

bperrybap commented 3 years ago

So you are using the hd44780_pinIO i/o class correct? with the backlight control using setBacklight() ? Are you using your own wiring or an LCD keypad shield? (some LCD keypads shields have an issue on the D10 backlight circuit so you can't use PWM on those shields without risking damaging the Arduino)

Which Arduino board? Is it 16x2 or 20x4 ? The reason I ask is that the 20x4 backlights draw a lot more power then the 16x2 backlights.

The pin i/o control is usually very stable, as long you don't have any ground loops or long wires. Are you activating the backlight on/off dimming during the period when there is an issue?

gitterman commented 3 years ago

Yes, I am using the hd44780_pinIO class on a 1602 LCD I don't use the setBacklight() for backlight control but do it on my own with a simple analogWrite. The contrast and backlight is not changed during operation. It is set in setup() once but can be altered in a config menu to allow adaptation to different ambient light conditions. I am going to add a trigger to call begin() when I go in my config menu, which will allow me to verify if the problem is the LCD or possibly something else. As I said the problem only arises occasionally, so I cannot re-produce it.

gitterman commented 3 years ago

Meanwhile, I found that hd44780 driven LCDs quite often have problems with relay driven circuitry. https://forum.arduino.cc/index.php?topic=262820.0 https://forum.arduino.cc/index.php?topic=170473.0 Invoking lcd.begin periodically is too slow so I added a resync() function to the library which tries to keep the LCD in sync. Calling this function every second during my LCD timer ticks does repair out of sync problems. A problem I observed though, is that my custom characters get corrupted after a few minutes with my sketch just running idle i.e. without any relay switching and without any load at the output. Thus, for a full repair I not only need to call lcd.resync but also lcd.create periodically. `int hd44780::resync()
{ command4bit(HD44780_FUNCTIONSET|HD44780_8BITMODE) ; delay(5) ; // wait 5ms vs 4.1ms, some are slower than spec

command4bit(HD44780_FUNCTIONSET|HD44780_8BITMODE) ;
delay(1) ;                 // wait 1ms vs 100us

command4bit(HD44780_FUNCTIONSET|HD44780_8BITMODE) ;
delay(1) ;                 // wait 1ms vs 100us

if(!(_displayfunction & HD44780_8BITMODE))
    command4bit(HD44780_FUNCTIONSET|HD44780_4BITMODE);
delay(1) ;                 // wait 1ms vs 100us
return( 0 ) ;

}`

bperrybap commented 3 years ago

Often a relay is controlling a very large restive load which can droop the mains voltage or an inductive load which can insert noise or voltage spikes. Either of these can create havoc with other circuitry. There are things that can be done to the circuity to help eliminate this. First make sure that the flyback diode with a resistor is in place, then consider doing things like using shorter and/or thicker wires, adding small capacitors to remove noise, or a larger capacitor to help bridge voltage droops or adding an inline inductor to the dc input voltage for the low voltage components to remove noise spikes.

Keep in mind that once there is signal integrity issues, then anything can happen to the LCD since when the host and the LCD lose nibble sync, the LCD will be processing garbage commands. Those garbage commands could do anything, like:

etc...

The point is there is no way to know what has been affected in the LCD once garbage commands are being processed. The effects tend to be worse on LCD backpacks than with pin control, but the potential issues for the LCD are the same. While doing a resync() like this will get the host and the LCD talking again, it won't be enough to get the LCD back to where it should be 100% of the time.

Note that to add a resync() type of command to the library, it should check the return status of each command and return int accordingly to ensure that on more complex interfaces like i2c that it can detect i/o issues. i.e.: (begin() currently isn't doing this beyond the ioinit() call and i'll probably add the extra checks just for additional robustness)

int hd44780::resync()
{
int status;
        if((status = command4bit(HD44780_FUNCTIONSET|HD44780_8BITMODE)))
                return(status);
        delay(5); // wait 5ms vs 4.1ms, some are slower than spec

        if((status = command4bit(HD44780_FUNCTIONSET|HD44780_8BITMODE)))
                return(status);
        delay(1);               // wait 1ms vs 100us

        if((status = command4bit(HD44780_FUNCTIONSET|HD44780_8BITMODE)))
                return(status);
        delay(1);               // wait 1ms vs 100us

        if(!(_displayfunction & HD44780_8BITMODE))
        {
                if((status = command4bit(HD44780_FUNCTIONSET|HD44780_4BITMODE)))
                        return(status);
        }
        delay(1);               // wait 1ms vs 100us
        return(RV_ENOERR);      // it all worked
}