Post

ENG | Raspberry Pi Pico, MicroPython, and Sitronix ST7567 display

Brief intro to using the Sitronix ST7567 LCD display with a Raspberry Pi Pico and MicroPython. Tutorial covers the wiring, code implementation, and troubleshooting tips.

I was not able to buy good old PCD8544 display so I tried to look elsewhere. In one Czech eshop, I found this display which has similar properties, it is perhaps even cheaper, has higher resolution, backlight is brighter. There was slight issue with documentation which does not provide good defaults and MicroPython drivers I found on GitHub had GPL license. I just don’t want to infect my code with it. What I found is that contrast/electronic volume set to zero gives higher contrast, whereas value 0x3f makes whole display black and text is barely readable only at angle.

I guess for next projects TFT/IPS displays might be better option, they provide higher resolution, they are just not good for low power scenarios, but Raspberry Pico is not suitable microcontroler anyways and for the most projects, it does not matter.

This article is basically a repository for self-documenting code and I’ll just link few articles here:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# MicroPython Sitronix ST7567 128*64 LCD driver
# Pavel Perina, October 2025
# Based on https://www.alldatasheet.com/view.jsp?Searchword=St7567 on some source
# code found on github. Written mostly to avoid GPL licensed code.
# You may need to set elecvolt and regratio to drive your screen properly
# NOTE: With flipX=True, flipY=False framebuffer start is off by a few pixels maybe 
#       because display is 132 pixels wide according do driver datasheet, but it's
#       not in reality. One flip is awlays needed for valid orientation.
# NOTE: Some drivers have staged init issuing several 0x28 commands with 50ms delay.

import time

class ST7567():
    def __init__(self, spi, dc, cs=None, rst=None, contrast=0x00, flipX=False, flipY=True):
        
        dc.init(dc.OUT, value=0)
        self.dc=dc

        if(cs is not None):
            cs.init(cs.OUT, value = 1)      #disable device port
            self.cs=cs

        if(rst is not None):
            rst.init(rst.OUT, value = 0)    #reset device
            self.rst = rst
            time.sleep_ms(1)
            self.rst.value(1)
            time.sleep_ms(1)

        self.spi=spi

        initCommands=[
            0xE2,                           # Software reset
            0xA2|0x01,                      # LCD bias 0:1/9 1:1/7
            0x81,                           # Electronic volume (contrast) mode
            (contrast&0x3f),                # Contrast value: 0x00-0x3F (default 0x1F - in my experience 0 works best)
            0x20|0x03,                      # Voltage regulator ratio: 0x00-0x07 (3.0-6.5V)
            0x28|0x07,                      # Power control: booster+regulator+follower all on
            0xA0|(0x01 if flipX else 0x00), # Segment direction: 0=normal, 1=reversed (flip X)
            0xC0|(0x08 if flipY else 0x00), # Common output direction: 0=normal, 8=reversed (flip Y)
            0x40|0x00,                      # Display start line: 0x40-0x7F (line 0-63)
            0xAE|0x01,                      # Display on/off, 0=off,1=on
        ]

        self.writeCommands(initCommands)

    # Write command(s)
    def writeCommands(self, cmd):
        self.cs.value(0)    # Enable device
        self.dc.value(0)    # Command mode
        self.spi.write(bytearray(cmd))
        self.dc.value(1)    # Disable device 
    
    # Write framebuffer data
    def writeData(self, data):
        self.cs.value(0)
        self.dc.value(1)    # Display data mode
        self.spi.write(data)
        self.dc.value(1)

    # Show framebuffer
    def show(self, buffer):
        self.writeCommands([0x40|0x00])  # Set start line to 0
        for page in range(8):
            self.writeCommands([
                0xB0 | page,    # Set page address (0-7)
                0x10 | 0x00,    # Set column address high nibble
                0x00 | 0x00,    # Set column address low  nibble
            ])
            # Write pages data (8 rows, 128 columns)
            self.writeData(buffer[(128*page):(128*page+128)])


if __name__ == "__main__":
    """
    Wiring expected in this code
    =================================
    GP11            -> RST (Reset)
    GP12            -> DC  (Data/Command)
    GP13 SPI1 CS    -> CS  (Chip select)
    GP14 SPI1 Clock -> SCK (SPI Clock)
    GP15 SPI1 Master Out, Slave In -> SDI (SPI Data In)

    GND             -> GND, LED
    3V3             -> VCC
    """

    print("Running as a standalone test script")
    from machine import Pin, SPI
    from framebuf import FrameBuffer, MONO_VLSB

    print("SPI init")
    spi = SPI(1,
            baudrate=200_000,
            polarity=1,
            phase=1,
            sck=Pin(14),
            mosi=Pin(15)) # under 20Mhz is OK

    print("LCD init")
    lcd=ST7567(spi,
            dc=Pin(12),  # data/command
            cs=Pin(13),  # chipselect, likely not needed for a single SPI device
            rst=Pin(11)) # reset

    print("Loop")
    buffer=bytearray(128*64//8)
    fb = FrameBuffer(buffer, 128, 64, MONO_VLSB)
    counter = 0
    while True:
        fb.fill(0)
        fb.text("*Sitronix ST7567*",0,0)
        fb.text(f"Temp: 23.{counter%99:02d}C", 0, 8)
        fb.text("RH:   67.89 %", 0, 16)
        fb.text("====----====----",0,24)
        fb.text("Temp: 21.09 C",0,32)
        fb.text("987.65 hPa",0,40)
        # Running line at screen bottom to test refresh rate
        fb.pixel( counter   %128,63,1)
        fb.pixel((counter+1)%128,63,1)
        fb.pixel((counter+2)%128,63,1)
        fb.pixel((counter+3)%128,63,1)
        fb.pixel((counter+4)%128,63,1)
        fb.pixel((counter+5)%128,63,1)
        fb.pixel((counter+6)%128,63,1)
        fb.pixel((counter+7)%128,63,1)

        lcd.show(buffer)
        counter += 1
This post is licensed under CC BY 4.0 by the author.