The character LCD is the display in every kit, and it is not a screen in the sense the OLED is. There is no framebuffer and no graphics: a sketch sends it characters, and the controller has the shapes for them in a ROM. That makes it the cheapest way to put a number in front of somebody, and it is why it has outlived several generations of nicer displays.
Mokxi has it twice, because the shops sell it twice.
Two parts, one controller
Character LCD, 16x2 is the bare module: sixteen pins along one edge, and
your sketch drives the HD44780 directly. Six wires from the board is the usual
hookup: RS, E and four data lines, plus power, a contrast pin and the
backlight.
Character LCD with I2C backpack is the same panel with a little board
soldered to the back of it: a PCF8574 eight-bit port expander, a contrast
trimmer and a transistor for the backlight. It brings out four pins (GND,
VCC, SDA, SCL), and it is the one most kits ship now.
Behind them is one model
(crates/parts/src/parts/hd44780.rs): the same command decode, the same
memory, the same busy timing, the same pixels. Only the way a byte arrives
differs.
What the controller does
Everything the datasheet says, decoded from the highest set bit of the byte down: clear, home, entry mode, display and cursor and blink on and off, shift, function set, set the CGRAM address, set the DDRAM address.
Two of those are worth knowing about.
DDRAM is eighty bytes and the window is sixteen. Row 0 is addresses 0x00
to 0x27 and row 1 is 0x40 to 0x67, so each line is really forty
characters and the panel is a window onto sixteen of them. Print past column 16
and the characters go somewhere nobody can see until you shift the display,
which is the first surprise.
CGRAM is eight characters of your own. Write eight bytes of five bits each and print character code 0 to 7 to get them back. They are drawn by exactly the same code that draws the built-in font, so a custom degree sign or a battery icon looks like it belongs.
The busy flag, which is the thing that goes wrong
A write takes the controller 37 microseconds and a clear takes 1.52 milliseconds, and it ignores the bus entirely for that long. Mokxi models that: a byte clocked in while the controller is busy is dropped, exactly as it is on the bench.
That is not us being awkward. It is the bug behind every "my LCD prints
garbage" question on every forum: a driver with no waits loses half a byte, the
four-bit nibble phase slips, and from then on every character is garbage. Our
own driver (firmware/lib/mokxi_hd44780.h) waits 50 microseconds after a byte
and 2 milliseconds after a clear, which is what the Arduino libraries do.
Blank displays, and which one you have
Both modules can show you a perfectly correct, perfectly invisible display, but for different reasons.
On the bare module it is contrast. V0 is an analog input, and at the
supply rail the characters vanish completely. That is what the little blue pot
on a real one is for, and it is why a first LCD so often looks dead. The window
is narrow and at the bottom: on 5 V nothing shows with V0 above about 1.5 V,
so a pot at mid travel is a blank panel, and the text comes in over the last
fifth of the turn. Wire V0 to ground for maximum contrast; leave it unwired
and Mokxi treats it as ground, which is how the cheap modules with a fixed
resistor behave.
On both, a module with power and contrast that has never been initialized
shows a row of solid boxes on its first line. That is the controller's
one-line reset state, and it is the bench's own diagnostic: boxes and no text
means the init never arrived (the wiring, the I2C address, or a missing
begin()), not that the panel is broken.
On the backpack the trimmer is a property rather than a pin, so contrast is
not the problem. The backlight is. It is bit 3 of the port byte and nothing
else, so a driver that never sets that bit leaves you with a working display in
a dark window. init() or backlight() in LiquidCrystal_I2C.h turns it on;
noBacklight() turns it off again, and the panel goes blank on the canvas
when it does.
A row of boxes
Powered and never initialized, both modules show the top row as sixteen solid boxes and the bottom row empty (on a 20x4, rows 1 and 3). That is the controller out of reset, driving its panel as one line, and it is what a real LCD shows when the sketch never reached it: a wrong I2C address (0x27 against 0x3F), SDA and SCL swapped, the wrong pins, or code that is not running. The backpack's expander powers up with every pin high, so its backlight is on over the boxes, as on the bench. The first function set a library sends ends it.
The backpack's port byte
There is no display protocol on the I2C bus at all. The expander has one register (its eight output pins), and every byte after the address lands on them. The backpack wires them to the LCD in an order every Arduino library hard-codes, and so does Mokxi:
| bit | goes to |
|---|---|
P0 |
RS |
P1 |
RW |
P2 |
E |
P3 |
the backlight |
P4 to P7 |
D4 to D7 |
So one character is six writes to the expander: the high nibble with E low,
high, low again, then the same three for the low nibble. The controller latches
on the falling edge of E, exactly as it does on the bare module.
The address is 0x27 on a PCF8574 module and 0x3F on a PCF8574A one, and the
two are physically identical. It is the address property (39, which is 0x27, by
default), so you can set whichever your board would have been.
Writing to one
Both modules compile the standard libraries as they stand. The bare module takes
LiquidCrystal.h on six pins:
#include <LiquidCrystal.h>
LiquidCrystal lcd(12, 11, 5, 4, 3, 2); // RS, E, D4, D5, D6, D7
void setup() {
lcd.begin(16, 2);
lcd.print("Hello, world!");
}
void loop() {
lcd.setCursor(0, 1);
lcd.print(millis() / 1000);
}
The backpack takes LiquidCrystal_I2C.h:
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 16, 2);
void setup() {
lcd.init();
lcd.backlight();
lcd.setCursor(0, 0);
lcd.print("Hello, world!");
}
void loop() {}
Both headers are Mokxi's own, written to the libraries' documented calls. The
backpack is on Wire's pins: A4 and A5 on an Uno, the board's own I2C pins
elsewhere (each board's help article says which), or wherever
Wire.begin(sda, scl) put them. print is the board's own, so a float prints
with two places as it does on Serial. init() turns the backlight on, where
the upstream library leaves that to backlight().
Mokxi also ships two small drivers of its own, mokxi_lcd1602.h
(Lcd1602 lcd(rs, e, d4, d5, d6, d7)) and mokxi_lcd1602_i2c.h
(Lcd1602I2c lcd(scl, sda)), with printFixed for a number scaled by a power
of ten and padLine to space out the rest of a row. Some templates use them.
What is not modeled
Reads of any kind. The busy flag, the address counter and DDRAM read back nothing, so a driver that polls the busy flag instead of waiting will hang. Wait instead; that is what every real library does anyway. Also not modeled: the 5x10 font a one-line module can select, and the viewing angle.
The busy timings are the datasheet's two typical numbers (37 microseconds for a write
and 1.52 milliseconds for clear and home), used as exact values. A real HD44780's
timing scales with whatever its internal oscillator happens to be running at, so those
figures move by tens of percent between parts and with temperature, and the 4-bit
nibble timing (the minimum E pulse width and the hold after it) is not checked here
at all: a driver can clock E as fast as it likes.
A byte arriving while the controller is busy is dropped. That is a choice the model makes so the fault is visible and repeatable; on a real part the outcome is undefined and more often a corrupted character than a lost one.
On the backpack, the PCF8574 never stretches the clock and acknowledges every byte
sent to it. There is no read of the expander's port, no NACK and no bus error of any
kind, and the backlight transistor is bit 3 and nothing else: no current, no
brightness. Contrast on the backpack is the contrast property (0 to 1, 0.75 by
default) rather than the trimmer's actual
divider.
See it: Thermometer on an I2C LCD for the backpack, and Thermometer in the templates for the six-wire hookup.
If an I2C LCD stays blank, shows blocks or prints leftovers, Arduino I2C LCD not working goes through the causes by symptom.
Related
- The I2C 16x2 LCD part page, with its pinout
- The 16x2 LCD part page, with its pinout
- The bare 16x2 LCD
- The 20x4 LCD
- Two wires, several chips: I2C