Skip to main content

4.2 Autoscroll text

In example 4.1 both messages were chosen to fit: twelve characters each, comfortably inside the sixteen the display can show. This one deliberately breaks that rule. The message is twenty-three characters long, which is seven more than there is room for, and instead of shortening it we are going to move it one character at a time, over and over, so the whole sentence goes past the reader like a departures board.

The build does not change at all from 4.1. What changes is what the display does with a message that is too long for it.

In this documentation you will learn:

  • Why a message longer than 16 characters is not lost, even though you cannot see all of it.
  • That each row of the display is really a 40-character buffer with a small window onto it.
  • How scrollDisplayLeft() moves that window instead of moving the text.
  • How to set the scroll speed, and why the whole loop takes about 12 seconds.
  • Why the screen goes briefly blank every lap, and why that is not a fault.
  • Why a scrolling message needs a while True loop, when 4.1 needed none.

Hardware required:

  • 1x Soldered NULA MINI board
  • 1x Breadboard
  • 1x 16x2 LCD display with I2C adapter
  • 1x Qwiic cable
  • 1x USB-C cable
ℹ️
No resistors and no jumper wires, exactly as in 4.1. The Qwiic cable carries power and data together and only fits one way round, and the breadboard is there purely to hold the board steady.

Putting the components together

If your board is still wired up from example 4.1, it is already correct, so skip straight to the code. Otherwise the four steps below are the same ones you did there.

1. Insert the NULA MINI board on the breadboard

Push the board into one end of the breadboard so that its two rows of pins sit on either side of the centre channel, with the chip facing down.

NULA MINI board seated on the breadboard
Step 1: the board seated on the breadboard

2. Plug the Qwiic cable into the board

The NULA MINI has one Qwiic connector, on the edge of the board between the USER and RST buttons. It is the white connector marked qwiic on the silkscreen. Push the cable in until it clicks.

Qwiic cable plugged into the NULA MINI board, with the other end still loose
Step 2: the cable in the Qwiic connector of the board, other end still free
⚠️
Do not confuse it with the smaller two-contact connector on the opposite edge of the board, marked - and +. That one is the battery connector, and a Qwiic cable will not fit it.

3. Plug the other end into the display

Turn the display over and plug the free end of the cable into either of the two Qwiic connectors on the purple I2C LCD ADAPTER board. They are wired together, so it makes no difference which one you use.

The display turned over to show its purple adapter board, with the Qwiic cable running from it to the NULA MINI on the breadboard
Step 3: the display turned over, cable in one of the two Qwiic connectors of the adapter

4. Plug in the USB-C cable

Connect the board to your computer. The purple PWR light comes on and the backlight of the display lights up blue, with nothing on the screen yet.

The finished build with USB-C connected: the display backlight is lit blue but the screen shows no characters
Step 4: powered up, before running the script. Backlight on, screen still blank
ℹ️
As in 4.1, a lit backlight only proves the display has power. It says nothing about whether the board is talking to it.

Getting the driver onto the board

This example uses the same LCD.py driver as 4.1, so if that example worked it is already on the board. If not, install the whole lib folder as described on the Setting up MicroPython page:

mpremote mip install github:SolderedElectronics/Soldered-NULA-Beginner-kit-MicroPython-project-examples/lib

Where the rest of the message lives

Example 4.1 said that a message longer than sixteen characters gets cut off at the edge of the screen. That is what it looks like, but it is not quite what happens, and the difference is the whole point of this example.

Each row of the display is backed by a 40-character buffer inside the controller chip of the display. Printing writes into that buffer. The glass shows a 16-character window onto it, and everything outside the window is still there: stored, remembered, simply not being shown.

Our message is twenty-three characters long, so it fits in the buffer with room to spare:

position: 0123456789012345678901234567890123456789
buffer: Hello from NULA MINI!
shown: ^^^^^^^^^^^^^^^^

The sixteen ^ marks are the window. When the script prints the message, positions 0 to 15 are what you see, Hello from NULA, and MINI! sits in positions 16 to 21, written and waiting.

scrollDisplayLeft() does not move the text. It moves that window one position along the buffer, which makes the text appear to slide the other way. Call it repeatedly and the window walks the length of the buffer, bringing the hidden characters into view a column at a time.

Because the buffer is 40 positions long, the window needs 40 shifts to get back where it started, and each shift is followed by a pause of SCROLL_DELAY_MS. That sets the length of one lap:

40 shifts x 300 ms = 12000 ms = 12 seconds per lap

Change SCROLL_DELAY_MS and the arithmetic follows: at 100 a lap takes about four seconds and the text moves briskly, at 600 it takes twenty-four and reads more comfortably.

ℹ️
This is why scrolling is nearly free. The board is not re-sending the message on every step. It sends a single one-byte "shift" command and the display does the rest. The text itself is written once, before the loop starts. Timed on the board, one scrollDisplayLeft() call takes 0.84 ms, so a real lap comes out at 12034 ms against the 12000 ms the arithmetic predicts. The 34 ms of difference is the forty shift commands themselves.
⚠️
The matching call for the other direction is named scroll_display_right(), in snake case, not scrollDisplayRight(). The driver is inconsistent about this, so if you try to reverse the direction and get AttributeError: 'LCD_I2C' object has no attribute 'scrollDisplayRight', that is why.

The message has a space at each end

Look closely at the message in the code and you will see it starts and ends with a space:

message = " Hello from NULA MINI! "

Neither is a typo. The window eventually wraps around from the end of the 40-character buffer back to the beginning, so the end of the message meets its own start. Without those two spaces you would get MINI!Hello jammed together for one lap in every rotation.

Once a lap the screen goes blank

There is one more consequence of those numbers, and it is worth knowing about before you run the script. The message occupies 23 of the 40 buffer positions, but the first and last of those are the two spaces, so the printed letters only run from position 1 to position 21. So the stretch with nothing visible in it is 19 positions long, not 17, and a 16-character window fits inside a 19-position gap in four different places. For four steps of every lap, a little over a second, the window is looking at nothing at all and the display is genuinely, correctly empty.

⚠️
This looks exactly like the fault described in 4.1, so it is worth learning the difference. A screen that goes blank sometimes, briefly, once every twelve seconds, is scrolling correctly. A screen that is always blank is the CONTRAST trimmer. See If the backlight is on but the screen is empty in the previous example.

Everything repeats, this time

In 4.1 the script ran from top to bottom and ended, because a display holds its last message on its own and there was nothing to repeat.

Here that flips around. The message is still printed once, before the loop, because printing it repeatedly would achieve nothing. But the movement has to be driven, so a while True loop does two things forever: shift the window one position, then wait. That pairing of an action and a pause is what turns a static screen into an animated one.

ℹ️
There is no clear() in this script, and none is needed: begin() already clears the display and returns the cursor to the start as part of setting it up. That is also why print() can be called without a setCursor() first: the cursor is already at position 0.

Code

# I2C is what the Qwiic connector carries, and the display is a Qwiic module.
from machine import I2C, Pin

# The Soldered driver for the LCD display, found in the lib folder of the examples repository.
from LCD import LCD_I2C
import time

# Here we set up the I2C connection. On the NULA MINI, I2C uses IO6 for the data line (SDA) and IO7 for the clock
# line (SCL), which are the pins the Qwiic connector is wired to.
i2c = I2C(0, scl=Pin(7), sda=Pin(6))

# Here we create our display object, which we named "lcd", and hand it the I2C connection.
# The display is 16 characters wide and 2 rows tall.
lcd = LCD_I2C(i2c)

# Create a message that will scroll across the display. The spaces at the beginning and the end leave a small gap,
# so the text does not run straight into itself as it travels.
message = " Hello from NULA MINI! "

# The speed of scrolling in milliseconds. Higher number = slower scroll.
# Feel free to experiment with this value.
SCROLL_DELAY_MS = 300

# begin() starts the communication and prepares the display. It has to come before anything else.
lcd.begin()

# backlight() turns on the light behind the screen. It comes after begin(), which would otherwise switch it back off.
lcd.backlight()

# Print the message once. Only the visible part will show initially.
lcd.print(message)

while True:

# scrollDisplayLeft() shifts everything already on the screen one position to the left. We never print the
# message again, we only keep moving it, and doing that over and over is what creates the moving effect.
lcd.scrollDisplayLeft()

# time.sleep_ms() pauses the program for the given number of milliseconds. This is what controls the scrolling
# speed: a longer wait between movements means slower scrolling.
time.sleep_ms(SCROLL_DELAY_MS)

What you should see

Press Run. The message appears and immediately begins sliding across the top row, one character every 300 milliseconds, and it never stops:

ℹ️
The Shell stays empty the whole time. This script never calls print(), so there is nothing for the console to show. The display is the only output it has. A blank Shell here means the script is running normally, not that something has gone wrong.
ℹ️
Because the loop never ends, the script keeps running until you stop it. Pressing Stop in Thonny leaves the last frame of the message frozen on the screen. The display holds whatever it was showing when the code stopped.

Full example

Check out the full example code on the link below:

4.2_Auto_Scroll_Text.py

Example that scrolls a message longer than the screen across the Qwiic 16x2 LCD using scrollDisplayLeft().