Published on May 30, 2024 · Reading time: 6 minutes
If you just want to download the driver scroll to the very bottom of the page. Thank you.
Table of Contents
I’m working on a new quick project that requires a large display with the best sunlight visibility possible. IPS modules are pretty terrible in this regard, while cheap OLED modules are susceptible to burn-in.
After some research, I’ve found a cheap (€10) ST7567 transflective display
module with a white backlight and dark font. Little had I known that
the CircuitPython driver for this display
is incompatible with the displayio
compositor. It’s time to change it.
What is displayio
? It’s the compositing system built into the CircuitPython,
written in C. It offers excellent performance and a variety of first-party
hardware-agnostic libraries for variable-width text, shapes, charts, basic UI
components or even tilemaps. However, its most useful feature is the serial
monitor, so you can see error messages and REPL prompts.
Setting up
First, let’s set up the hardware connections. The display has an 8-pin, 1.0 mm pitch FPC connector. The datasheet for the ST7567 controller states that the only required components are two capacitors, and they are both already present on the flex cable. I’ve received no documentation for the display, except for some technical parameters and a wiring diagram:
Pin | Meaning |
---|---|
1 | Chip Select |
2 | Data / Command |
3 | Reset |
4 | Data |
5 | Clock |
6 | 3.3V |
7 | GND |
8 | GND |
There are separate backlight pins as well (anode and cathode). The declared power consumption is 70 mA for the 5 built-in LEDs, so it’s necessary to add a basic transistor circuit. Do not drive so many diodes directly from the microcontroller’s digital pin!
I have installed CircuitPython 9.0.5 on Raspberry Pi Pico, copied the
adafruit_framebuf.py
and adafruit_st7565.py
library files from the
CircuitPython library bundle, and run the following script:
import board
from busio import SPI
from digitalio import DigitalInOut
from adafruit_st7565 import ST7565
spi = SPI(board.GP18, MOSI=board.GP19)
dc = DigitalInOut(board.GP20)
cs = DigitalInOut(board.GP17)
reset = DigitalInOut(board.GP21)
display = ST7565(spi, dc, cs, reset)
It worked, but I could barely see what’s on the screen even at the minimum contrast setting, because everything was so dark. I’ve checked the technical parameters once again and found a solution. The recommended bias setting for this very display is 1/9, but it was initialized with the value of 1/7. Bingo!
There were also some artifacts near the right edge of the screen, and the text
was incorrectly positioned. Luckily, I’ve already experienced that when working
with cheap SH1106 OLED modules. For the displayio
driver, you would set the
colstart
parameter. The framebuf
ST7565 driver has an undocumented
start_bytes
variable that does the same.
This is the display configuration that worked for me:
display = ST7565(spi, dc, cs, reset)
display.write_cmd(display.CMD_SET_BIAS_9)
display.contrast = 5
display.start_bytes = 4
Writing a displayio driver
People are frightened when someone starts a talk about drivers. This usually means your Windows computer is broken because newly installed drivers are not compatible with something else, or you need to update the damn drivers because the new AAA game does not work properly.
The displayio
drivers are not scary at all! In fact, they’re pretty easy to
understand. Most of the time, they only store an initialization sequence for
the display and some helper functions. Let’s write a driver for the ST7565
display.
The initialization sequence is a list of commands that are necessary to set up
a display. Such commands may require extra data, and sometimes you need to add a
short delay here and there to let the display do its thing. CircuitPython
developers came up with an interesting idea: encode everything as a bytearray
and let the displayio
do all the processing. Define a command, declare a
number of parameters (and an optional delay by setting the highest bit to 1),
list the parameters, and finally define the delay. Rinse and repeat.
Let’s rewrite the __init__
of the original driver. I had temporarily replaced
CMD_SET_ALLPTS_NORMAL
with CMD_SET_ALLPTS_ON
so I could actually see that
the display was initialized. I had also changed the bias and contrast values.
Original | Init seq. |
---|---|
CMD_SET_BIAS_9 |
A2 00 |
CMD_SET_ADC_REVERSE |
A1 00 |
CMD_SET_COM_NORMAL |
C0 00 |
CMD_SET_DISP_START_LINE |
40 00 |
CMD_SET_POWER_CONTROL | 0x4; time.sleep(0.05) |
2C 80 32 |
CMD_SET_POWER_CONTROL | 0x6; time.sleep(0.05) |
2E 80 32 |
CMD_SET_POWER_CONTROL | 0x7; time.sleep(0.01) |
2F 80 0A |
CMD_SET_RESISTOR_RATIO | 0x7 |
27 00 |
CMD_DISPLAY_ON |
AF 00 |
CMD_SET_ALLPTS_ON |
A5 00 |
CMD_SET_VOLUME_FIRST |
81 00 |
CMD_SET_VOLUME_SECOND | 0x05 |
05 00 |
So this is the initialization sequence that should work:
import board
from busdisplay import BusDisplay
from busio import SPI
from displayio import release_displays, Group
from fourwire import FourWire
init_sequence = (
b"\xA2\x00"
b"\xA1\x00"
b"\xC0\x00"
b"\x40\x00"
b"\x2C\x80\x32"
b"\x2E\x80\x32"
b"\x2F\x80\x0A"
b"\x27\x00"
b"\xAF\x00"
b"\xA5\x00"
b"\x81\x00"
b"\x05\x00"
)
release_displays()
spi = SPI(board.GP18, MOSI=board.GP19)
bus = FourWire(spi, command=board.GP20, chip_select=board.GP17, reset=board.GP21)
display = BusDisplay(bus, init_sequence, width=128, height=64, rotation=0, colstart=4)
Except it didn’t.
I’ve looked at the source code of other displayio
drivers and couldn’t figure
out what’s wrong. The most suspicious code in the original ST7565 driver was the
0.5s reset command, but no other display required it.
Somehow, I saw some signs of life after I added more keyword arguments:
display = BusDisplay(bus, init_sequence, width=128, height=64, rotation=0, colstart=4,
data_as_commands=True, SH1107_addressing=True, color_depth=1)
I understand why data_as_commands
may be necessary: no commands have any extra
values, and the contrast command was split in two. But what about those other
two kwargs? I’ve downloaded the CircuitPython source code, searched for their
usages, and found no clues.
Okay, so what happens if I disable CMD_SET_ALLPTS_ON
? Can I see the REPL? It
looks like, I can’t. But since I use the SH1107_addressing
kwarg, maybe the
hint is in the SH1107 displayio
driver?
# for sh1107 use column and page addressing.
# lower column command = 0x00 - 0x0F
# upper column command = 0x10 - 0x17
# set page address = 0xB0 - 0xBF (16 pages)
These are the same values as in the ST7565 framebuf
driver. But I could be
missing three more kwargs: grayscale
, single_byte_bounds
and
pixels_in_byte_share_row
. I’ve added the first one, and some junk has been
displayed, with the screen content different on each keystroke while in REPL
(look at the noise in the top-left corner of the display).
I’ve enabled the second one, and it did nothing.
I’ve enabled the third one, AND IT WORKS! Just as if I were using the framebuf
driver, but with all the benefits of displayio
.
Observations
It looks like many displays are similar to each other software-wise. The ST7565 (and ST7567) have the same buffer layout as the SH1106, SSD1306, KS0108 and so on, with one byte describing eight adjacent vertical pixels and eight 132px-wide pages. They all use a specific addressing scheme, where you need to run one command to specify the vertical position for the upcoming raw data but two commands for the horizontal position.
I’ve cleaned up the code and published a CircuitPython ST7565 displayio library on GitHub. It’s already available in the CircuitPython Community bundle, which means it can be installed by running the following command:
circup install displayio_st7565
Check out other blog posts:
-
Making framebuf text 10x faster in CircuitPython
2024-12-23
Finding a cause of slow text rendering and optimizing it for monochrome LCD and OLED displays.
-
Tracking libadwaita adoption in Fedora (updated)
2024-10-29
The complete list of software preinstalled in Fedora, including apps using the libadwaita library.
-
Reinstalling Debian, fast
2024-10-12
Installing Debian with core GNOME, fixing UI inconsistencies, restoring software needed on a home PC.