Two rows, forty columns
The display has eighty characters of memory and thirty-two lit cells. Knowing where the other forty-eight went explains setCursor, scrolling, and why a long message vanishes instead of wrapping.
Eighty characters, thirty-two of them lit
The controller has eighty bytes of display memory laid out as two rows of forty. A 1602 lights the first sixteen of each. The rest is real memory: you can write to it, it remembers what you wrote, and nothing shows.
setCursor(0, 1) exists rather than you counting characters. The twenty-four columns past the right edge are real memory: you can print into them, and scrolling is the window sliding over them. Print past column 39 and the characters are simply dropped — which is why a message longer than 40 characters has to be scrolled by your own code, a slice at a time, rather than by the display.Row 1 is 0x40
The two rows are forty apart in memory, not sixteen. Row 0 runs from address
0x00 and row 1 from 0x40 — and that is the whole reason setCursor(column, row) exists rather than you counting characters from the top left. The library
holds those two numbers in a lookup table and does the addition for you.
It is also why text does not wrap. A string that overruns column 15 keeps going into columns 16 to 39 of the same row, where nothing is lit. It has not been lost and it has not moved to row 1; it is sitting in memory, off the edge.
Scrolling moves the window
scrollDisplayLeft() does not move your text. It moves which sixteen of the
forty columns are lit, one column at a time, over both rows at once. Print a
message thirty characters long and scroll fourteen times and you have read all
of it.
Past forty characters, the controller drops what it cannot store. A message longer than that has to be scrolled by your own code: keep the string in a normal array, print a sixteen-character slice of it, and print a different slice a moment later. That approach works for any length and is usually the one to reach for.
Two instructions worth knowing apart
home() returns the cursor to the top left and undoes any scrolling. It erases
nothing.
clear() writes a space into all eighty cells and then goes home. The
controller needs about 1.5 ms to do it — forty times longer than any other
instruction — and the library sits and waits. That is the flicker people
describe when they call it once per loop.
When it does not work
It never will. The rows are separate stretches of memory forty characters apart, so a string that runs off the end of row 0 carries on into columns 16 to 39 of row 0 — which are not lit. Split the string yourself and setCursor(0, 1) for the second half.
They are stored, not lost, until you pass column 39. Scroll the window and they come into view. Past column 39 the controller drops them, which is why a message longer than forty characters has to be scrolled by your own code a slice at a time.
Yes — the window is one window over both rows. There is no way to scroll one row and hold the other. If you need that, print a moving slice of your own string into the row you want and leave the other alone.
It is not meant to. home() puts the cursor back at the top left and undoes any scrolling; clear() wipes all eighty cells and takes about 1.5 ms doing it, which is forty times longer than any other instruction.
Edit this page — content/books/lcd1602/two-rows-forty-columns.mdx
Questions about this product
See what other owners have asked, and read their solutions.
3-Pack 1602 LCD Display Module, I2C 16x2 Blue
Loading discussions…
Discuss this article
Ask about this page. The answer stays here, on the page it belongs to, for whoever hits the same wall next.