Skip to content

Fixes for several lcd_1602_i2c.c issues #597

@ahmogit

Description

@ahmogit

Re the following comment in lcd_toggle_enable() accompanying the value
of 600 us for DELAY_US:

"We cannot do this too quickly or things don't work"

Various datasheets I've come across for these 16x2 (and 20x4) modules from
3-4 different manufacturers show the Enable minimum active pulse dwell
(e.g. t_w,min, on p. 14 of [1]) as 250 ns or less. So that 600 us value is
excessive by more than three orders of magnitude.

The reason that the example code needs that huge value in order to "work"
properly is because the required delays (e.g. per [1], p. 17) between the
first three initialization commands are missing from lcd_init(). But the
time expended in lcd_toggle_enable(), due that artificially huge 600 us
DELAY_US is fortuitously compensating (partially) for the missing
delays in lcd_init() (since lcd_init() invokes lcd_toggle_enable()
via lcd_send_byte() on every write operation.)

Behavior observed with the present code, due to the above issues:

  • Unreliable initialization: Malformed screen characters occasionally
    appear immediately after init. (Observed on approximately 5 % of
    inits of most 16x2 units that I have, depending on LCD vendor, and
    considerably more often (perhaps 10% - 20%) on the 20x4 modules,
    which have near-identical interface specs).

  • Write times much slower than the LCD module is capable of.

  • Write times scale poorly with I2C clock rate. (For example,
    increasing the I2C clock by a factor of 10 leads to only about
    20% reduction in write times; see examples below.)

To address these issues: If you insert explicit delays between the first
two writes in lcd_init() in order to satisfy the requirements in [1], e.g.

   [...]
   lcd_send_byte(0x03, LCD_COMMAND);
   sleep_ms(5);                        // Conservative
   lcd_send_byte(0x03, LCD_COMMAND);
   sleep_ms(5);                        // Conservative
   lcd_send_byte(0x03, LCD_COMMAND);
   [ ... ]

then you can reduce DELAY_US down to 1 us, or even eliminate it entirely,
since the I2C comm delays alone will easily meet the 250 ns t_w,min
requirement. The 5 ms example value above is even conservative; per [1],
only 4.1 ms is required between the first two init commands and 100 us
between the second and third.

These two mods eliminate the initialization reliability issue entirely,
and provide significantly improved per-character write times as well.
More importantly, the write times scale approximately in proportion to
I2C clock rate, as one would expect. (The original code scales poorly
with I2C clock rate because the huge 600 us delay in lcd_toggle_enable()
is incurred on every I2C write, i.e. a massive overhead on every write
operation.)

Timing examples, on Pico RP2040:

Original vs. fixed: I2C clock 100 kHz:

=================================================
examp_orig:
  I2C rate:                   100 kHz
    lcd_string(1 char):     4.894 ms
    lcd_string(15 chars):  73.103 ms
=================================================

=================================================
examp_fixed:
  I2C rate:                   100 kHz
    lcd_string(1 char):     1.294 ms
    lcd_string(15 chars):  19.194 ms
=================================================

Original vs. fixed: I2C clock 1 MHz:

=================================================
examp_orig:
  I2C rate:                  1000 kHz
    lcd_string(1 char):     3.763 ms
    lcd_string(15 chars):  56.201 ms
=================================================

=================================================
examp_fixed:
  I2C rate:                  1000 kHz
    lcd_string(1 char):     0.169 ms
    lcd_string(15 chars):   2.323 ms
=================================================

REFERENCES

[1] Typical spec sheet for one particular manufacturer (WaveShare)
of these ubiquitius 16x2 LCD modules:

     https://www.waveshare.com/datasheet/LCD_en_PDF/LCD1602.pdf

Datasheets for similar modules from other vendors give generally
similar figures for t_w,min and for the requred delays between
the initialization commands. So this WaveShare example datasheet
is not an outlier.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions