├── .gitignore ├── LICENSE ├── README.md ├── st7789py.py └── test_st7789.py /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .vscode 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Ivan Belokobylskiy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Slow ST7789 driver for MicroPython 2 | ================================== 3 | 4 | This is a slow MicroPython driver for 240x240 ST7789 display without CS pin 5 | from Ali Express. It also supports 135x240 TTGO Display 6 | 7 | Version: 0.1.4 8 | 9 | The performance is quite low due to python function call overhead. 10 | If you have a chance to build firmware and you are using 11 | ESP8266/ESP32 controllers, you should try the fast driver 12 | https://github.com/devbis/st7789_mpy 13 | 14 | Examples 15 | -------- 16 | 17 | # ESP8266 18 | import machine 19 | import st7789py 20 | spi = machine.SPI(1, baudrate=40000000, polarity=1) 21 | display = st7789py.ST7789(spi, 240, 240, reset=machine.Pin(5, machine.Pin.OUT), dc=machine.Pin(4, machine.Pin.OUT)) 22 | display.init() 23 | display.pixel(120, 120, st7789py.YELLOW) 24 | -------------------------------------------------------------------------------- /st7789py.py: -------------------------------------------------------------------------------- 1 | import time 2 | from micropython import const 3 | import ustruct as struct 4 | 5 | # commands 6 | ST77XX_NOP = const(0x00) 7 | ST77XX_SWRESET = const(0x01) 8 | ST77XX_RDDID = const(0x04) 9 | ST77XX_RDDST = const(0x09) 10 | 11 | ST77XX_SLPIN = const(0x10) 12 | ST77XX_SLPOUT = const(0x11) 13 | ST77XX_PTLON = const(0x12) 14 | ST77XX_NORON = const(0x13) 15 | 16 | ST77XX_INVOFF = const(0x20) 17 | ST77XX_INVON = const(0x21) 18 | ST77XX_DISPOFF = const(0x28) 19 | ST77XX_DISPON = const(0x29) 20 | ST77XX_CASET = const(0x2A) 21 | ST77XX_RASET = const(0x2B) 22 | ST77XX_RAMWR = const(0x2C) 23 | ST77XX_RAMRD = const(0x2E) 24 | 25 | ST77XX_PTLAR = const(0x30) 26 | ST77XX_COLMOD = const(0x3A) 27 | ST7789_MADCTL = const(0x36) 28 | 29 | ST7789_MADCTL_MY = const(0x80) 30 | ST7789_MADCTL_MX = const(0x40) 31 | ST7789_MADCTL_MV = const(0x20) 32 | ST7789_MADCTL_ML = const(0x10) 33 | ST7789_MADCTL_BGR = const(0x08) 34 | ST7789_MADCTL_MH = const(0x04) 35 | ST7789_MADCTL_RGB = const(0x00) 36 | 37 | ST7789_RDID1 = const(0xDA) 38 | ST7789_RDID2 = const(0xDB) 39 | ST7789_RDID3 = const(0xDC) 40 | ST7789_RDID4 = const(0xDD) 41 | 42 | ColorMode_65K = const(0x50) 43 | ColorMode_262K = const(0x60) 44 | ColorMode_12bit = const(0x03) 45 | ColorMode_16bit = const(0x05) 46 | ColorMode_18bit = const(0x06) 47 | ColorMode_16M = const(0x07) 48 | 49 | # Color definitions 50 | BLACK = const(0x0000) 51 | BLUE = const(0x001F) 52 | RED = const(0xF800) 53 | GREEN = const(0x07E0) 54 | CYAN = const(0x07FF) 55 | MAGENTA = const(0xF81F) 56 | YELLOW = const(0xFFE0) 57 | WHITE = const(0xFFFF) 58 | 59 | _ENCODE_PIXEL = ">H" 60 | _ENCODE_POS = ">HH" 61 | _DECODE_PIXEL = ">BBB" 62 | 63 | _BUFFER_SIZE = const(256) 64 | 65 | 66 | def delay_ms(ms): 67 | time.sleep_ms(ms) 68 | 69 | 70 | def color565(r, g=0, b=0): 71 | """Convert red, green and blue values (0-255) into a 16-bit 565 encoding. As 72 | a convenience this is also available in the parent adafruit_rgb_display 73 | package namespace.""" 74 | try: 75 | r, g, b = r # see if the first var is a tuple/list 76 | except TypeError: 77 | pass 78 | return (r & 0xf8) << 8 | (g & 0xfc) << 3 | b >> 3 79 | 80 | 81 | class ST77xx: 82 | def __init__(self, spi, width, height, reset, dc, cs=None, backlight=None, 83 | xstart=-1, ystart=-1): 84 | """ 85 | display = st7789.ST7789( 86 | SPI(1, baudrate=40000000, phase=0, polarity=1), 87 | 240, 240, 88 | reset=machine.Pin(5, machine.Pin.OUT), 89 | dc=machine.Pin(2, machine.Pin.OUT), 90 | ) 91 | 92 | """ 93 | self.width = width 94 | self.height = height 95 | self.spi = spi 96 | if spi is None: 97 | import machine 98 | self.spi = machine.SPI(1, baudrate=40000000, phase=0, polarity=1) 99 | self.reset = reset 100 | self.dc = dc 101 | self.cs = cs 102 | self.backlight = backlight 103 | if xstart >= 0 and ystart >= 0: 104 | self.xstart = xstart 105 | self.ystart = ystart 106 | elif (self.width, self.height) == (240, 240): 107 | self.xstart = 0 108 | self.ystart = 0 109 | elif (self.width, self.height) == (135, 240): 110 | self.xstart = 52 111 | self.ystart = 40 112 | else: 113 | raise ValueError( 114 | "Unsupported display. Only 240x240 and 135x240 are supported " 115 | "without xstart and ystart provided" 116 | ) 117 | 118 | def dc_low(self): 119 | self.dc.off() 120 | 121 | def dc_high(self): 122 | self.dc.on() 123 | 124 | def reset_low(self): 125 | if self.reset: 126 | self.reset.off() 127 | 128 | def reset_high(self): 129 | if self.reset: 130 | self.reset.on() 131 | 132 | def cs_low(self): 133 | if self.cs: 134 | self.cs.off() 135 | 136 | def cs_high(self): 137 | if self.cs: 138 | self.cs.on() 139 | 140 | def write(self, command=None, data=None): 141 | """SPI write to the device: commands and data""" 142 | self.cs_low() 143 | if command is not None: 144 | self.dc_low() 145 | self.spi.write(bytes([command])) 146 | if data is not None: 147 | self.dc_high() 148 | self.spi.write(data) 149 | self.cs_high() 150 | 151 | def hard_reset(self): 152 | self.cs_low() 153 | self.reset_high() 154 | delay_ms(50) 155 | self.reset_low() 156 | delay_ms(50) 157 | self.reset_high() 158 | delay_ms(150) 159 | self.cs_high() 160 | 161 | def soft_reset(self): 162 | self.write(ST77XX_SWRESET) 163 | delay_ms(150) 164 | 165 | def sleep_mode(self, value): 166 | if value: 167 | self.write(ST77XX_SLPIN) 168 | else: 169 | self.write(ST77XX_SLPOUT) 170 | 171 | def inversion_mode(self, value): 172 | if value: 173 | self.write(ST77XX_INVON) 174 | else: 175 | self.write(ST77XX_INVOFF) 176 | 177 | def _set_color_mode(self, mode): 178 | self.write(ST77XX_COLMOD, bytes([mode & 0x77])) 179 | 180 | def init(self, *args, **kwargs): 181 | self.hard_reset() 182 | self.soft_reset() 183 | self.sleep_mode(False) 184 | 185 | def _set_mem_access_mode(self, rotation, vert_mirror, horz_mirror, is_bgr): 186 | rotation &= 7 187 | value = { 188 | 0: 0, 189 | 1: ST7789_MADCTL_MX, 190 | 2: ST7789_MADCTL_MY, 191 | 3: ST7789_MADCTL_MX | ST7789_MADCTL_MY, 192 | 4: ST7789_MADCTL_MV, 193 | 5: ST7789_MADCTL_MV | ST7789_MADCTL_MX, 194 | 6: ST7789_MADCTL_MV | ST7789_MADCTL_MY, 195 | 7: ST7789_MADCTL_MV | ST7789_MADCTL_MX | ST7789_MADCTL_MY, 196 | }[rotation] 197 | 198 | if vert_mirror: 199 | value = ST7789_MADCTL_ML 200 | elif horz_mirror: 201 | value = ST7789_MADCTL_MH 202 | 203 | if is_bgr: 204 | value |= ST7789_MADCTL_BGR 205 | self.write(ST7789_MADCTL, bytes([value])) 206 | 207 | def _encode_pos(self, x, y): 208 | """Encode a postion into bytes.""" 209 | return struct.pack(_ENCODE_POS, x, y) 210 | 211 | def _encode_pixel(self, color): 212 | """Encode a pixel color into bytes.""" 213 | return struct.pack(_ENCODE_PIXEL, color) 214 | 215 | def _set_columns(self, start, end): 216 | if start > end or end >= self.width: 217 | return 218 | start += self.xstart 219 | end += self.xstart 220 | self.write(ST77XX_CASET, self._encode_pos(start, end)) 221 | 222 | def _set_rows(self, start, end): 223 | if start > end or end >= self.height: 224 | return 225 | start += self.ystart 226 | end += self.ystart 227 | self.write(ST77XX_RASET, self._encode_pos(start, end)) 228 | 229 | def set_window(self, x0, y0, x1, y1): 230 | self._set_columns(x0, x1) 231 | self._set_rows(y0, y1) 232 | self.write(ST77XX_RAMWR) 233 | 234 | def vline(self, x, y, length, color): 235 | self.fill_rect(x, y, 1, length, color) 236 | 237 | def hline(self, x, y, length, color): 238 | self.fill_rect(x, y, length, 1, color) 239 | 240 | def pixel(self, x, y, color): 241 | self.set_window(x, y, x, y) 242 | self.write(None, self._encode_pixel(color)) 243 | 244 | def blit_buffer(self, buffer, x, y, width, height): 245 | self.set_window(x, y, x + width - 1, y + height - 1) 246 | self.write(None, buffer) 247 | 248 | def rect(self, x, y, w, h, color): 249 | self.hline(x, y, w, color) 250 | self.vline(x, y, h, color) 251 | self.vline(x + w - 1, y, h, color) 252 | self.hline(x, y + h - 1, w, color) 253 | 254 | def fill_rect(self, x, y, width, height, color): 255 | self.set_window(x, y, x + width - 1, y + height - 1) 256 | chunks, rest = divmod(width * height, _BUFFER_SIZE) 257 | pixel = self._encode_pixel(color) 258 | self.dc_high() 259 | if chunks: 260 | data = pixel * _BUFFER_SIZE 261 | for _ in range(chunks): 262 | self.write(None, data) 263 | if rest: 264 | self.write(None, pixel * rest) 265 | 266 | def fill(self, color): 267 | self.fill_rect(0, 0, self.width, self.height, color) 268 | 269 | def line(self, x0, y0, x1, y1, color): 270 | # Line drawing function. Will draw a single pixel wide line starting at 271 | # x0, y0 and ending at x1, y1. 272 | steep = abs(y1 - y0) > abs(x1 - x0) 273 | if steep: 274 | x0, y0 = y0, x0 275 | x1, y1 = y1, x1 276 | if x0 > x1: 277 | x0, x1 = x1, x0 278 | y0, y1 = y1, y0 279 | dx = x1 - x0 280 | dy = abs(y1 - y0) 281 | err = dx // 2 282 | if y0 < y1: 283 | ystep = 1 284 | else: 285 | ystep = -1 286 | while x0 <= x1: 287 | if steep: 288 | self.pixel(y0, x0, color) 289 | else: 290 | self.pixel(x0, y0, color) 291 | err -= dy 292 | if err < 0: 293 | y0 += ystep 294 | err += dx 295 | x0 += 1 296 | 297 | 298 | class ST7789(ST77xx): 299 | def init(self, *, color_mode=ColorMode_65K | ColorMode_16bit): 300 | super().init() 301 | self._set_color_mode(color_mode) 302 | delay_ms(50) 303 | self._set_mem_access_mode(4, True, True, False) 304 | self.inversion_mode(True) 305 | delay_ms(10) 306 | self.write(ST77XX_NORON) 307 | delay_ms(10) 308 | self.fill(0) 309 | self.write(ST77XX_DISPON) 310 | delay_ms(500) 311 | -------------------------------------------------------------------------------- /test_st7789.py: -------------------------------------------------------------------------------- 1 | # code for micropython 1.10 on esp8266 2 | 3 | import random 4 | 5 | import machine 6 | import st7789py as st7789 7 | import time 8 | 9 | 10 | def main(): 11 | spi = machine.SPI(1, baudrate=40000000, polarity=1) 12 | display = st7789.ST7789( 13 | spi, 240, 240, 14 | reset=machine.Pin(5, machine.Pin.OUT), 15 | dc=machine.Pin(2, machine.Pin.OUT), 16 | ) 17 | display.init() 18 | 19 | while True: 20 | display.fill( 21 | st7789.color565( 22 | random.getrandbits(8), 23 | random.getrandbits(8), 24 | random.getrandbits(8), 25 | ), 26 | ) 27 | # Pause 2 seconds. 28 | time.sleep(2) 29 | --------------------------------------------------------------------------------