ST7789是一種常用的液晶屏控制芯片(最大支持的分辨率為240×320),可與單片機之間通過SPI通信傳送控制指令或者數據。在MicroPython環境下使用ESP32或者樹莓派Pico可以直接下載st7789_mpy庫預先編譯好的固件firmware來嘗試控制液晶屏,支持的各種單片機開發板如下表所示。這里使用樹莓派Pico,將RP2文件夾中的firmware.uf2固件下載下來,然后按相關教程將其拖放到Pico上進行編程。
Directory | File | Device |
---|---|---|
GENERIC-7789 | firmware.bin | Generic ESP32 devices |
GENERIC_SPIRAM-7789 | firmware.bin | Generic ESP32 devices with SPI Ram |
GENERIC_C3 | firmware.bin | Generic ESP32-C3 devices (JPG support not working) |
PYBV11 | firmware.dfu | Pyboard v1.1 |
RP2 | firmware.uf2 | Raspberry Pi Pico RP2040 |
T-DISPLAY | firmware.bin | LILYGO® TTGO T-Display |
T-Watch-2020 | firmware.bin | LILYGO® T-Watch 2020 |
可以根據文檔進行一些簡單的測試:

from machine import SPI, Pin import st7789 import time import math import vga1_8x8 as font spi = SPI(0, baudrate=40_000_000, polarity=0, phase=0, sck=Pin(18,Pin.OUT), mosi=Pin(19,Pin.OUT)) display = st7789.ST7789(spi, 240, 320, reset=Pin(20, Pin.OUT), dc=Pin(21, Pin.OUT), cs=Pin(17, Pin.OUT), backlight=Pin(16, Pin.OUT), color_order = st7789.RGB, inversion = False, rotation = 2) display.init() COLORS = [ 0xFFE0, # yellow 0x0000, # black st7789.BLUE, st7789.RED, st7789.GREEN, st7789.CYAN, st7789.MAGENTA, st7789.YELLOW, st7789.WHITE, st7789.BLACK] for color in COLORS: display.fill(color) time.sleep_ms(400) display.text(font, "Hello world", 0, 0, st7789.WHITE) display.circle(120, 160, 50, st7789.WHITE) display.vline(120, 160, 50, st7789.BLUE) display.hline(120, 160, 50, st7789.RED) display.rect(10, 20, 10, 20, st7789.YELLOW) display.fill_rect(10, 50, 10, 20, st7789.GREEN) display.fill_circle(120, 160, 5, st7789.WHITE) pointlist=((0,0),(20,0),(30,30),(0,20),(0,0)) display.polygon(pointlist, 120, 50, st7789.WHITE, 0, 120, 50) display.polygon(pointlist, 120, 50, st7789.GREEN, math.pi/2, 0, 0) display.fill_polygon(((0,0),(10,0),(5,7),(0,0)), 115, 40, st7789.RED) time.sleep_ms(200) for x in range(240): y = int(80*math.sin(0.125664*x)) + 160 display.pixel(x, y, st7789.MAGENTA) for d in range(-90,-185,-5): a = d * math.pi / 180 x1 = int(50*math.cos(a)+120) y1 = int(50*math.sin(a)+160) display.line(120, 160, x1, y1, st7789.CYAN) time.sleep_ms(50) time.sleep(5) display.off() # Turn off the backlight pin if one was defined during init. time.sleep(2) display.on()
接下來嘗試根據ST7789的數據手冊自己編寫MicroPython代碼驅動液晶屏,以熟悉底層控制邏輯。與傳統的SPI協議不同的地方是由於是只需要顯示,故而不需要從機發往主機的數據線MISO。根據手冊:RESX為復位信號,低電平有效,通常情況下置1;CSX為從機片選, 僅當CS為低電平時,芯片才會被使能。如果只有一個顯示屏可以直接將CSX信號拉低,程序中不需對其進行控制;D/CX為芯片的數據/命令控制引腳,當DC = 0時寫命令,當DC = 1時寫數據;SDA為通過SPI的MOSI引腳傳輸的數據,即RGB數據;SCL為SPI通信時鍾,空閑時低電平,第一個上升沿采樣開始傳輸數據,一個時鍾周期傳輸8bit數據,按位傳輸,高位在前,低位在后(編程時要注意字節順序問題)。因此,SPI的時鍾極性CPOL=0(空閑狀態為低電平),時鍾相位CPHA=0(第一時鍾跳變沿數據被采集)。
ST7789支持12位,16位以及18位每像素的輸入顏色格式,即RGB444,RGB565,RGB666三種顏色格式。這里使用RGB565這種最常用的RGB顏色格式,用兩個字節16位來存儲像素點的信息(可以表示2^16 = 65536種顏色 , 即65K),其中R通道占據5個字節,G通道占據6個字節,B通道占據5個字節。
ST7789芯片內部有數量眾多的寄存器用於控制各種參數,在使用屏幕前需要對這些寄存器進行初始化配置。比較重要的配置有:睡眠設置、顏色格式、屏幕方向、RGB順序、顏色反轉、顯示模式、伽馬校正、顯示開關等,可以先在初始化函數中添加盡量少的關鍵配置項,順利點亮屏幕后再根據具體需求逐漸完善(如伽馬校正等)。可以參考st7789.c中的初始化步驟:

STATIC mp_obj_t st7789_ST7789_init(mp_obj_t self_in) { st7789_ST7789_obj_t *self = MP_OBJ_TO_PTR(self_in); st7789_ST7789_hard_reset(self_in); st7789_ST7789_soft_reset(self_in); write_cmd(self, ST7789_SLPOUT, NULL, 0); const uint8_t color_mode[] = {COLOR_MODE_65K | COLOR_MODE_16BIT}; write_cmd(self, ST7789_COLMOD, color_mode, 1); mp_hal_delay_ms(10); set_rotation(self); if (self->inversion) { write_cmd(self, ST7789_INVON, NULL, 0); } else { write_cmd(self, ST7789_INVOFF, NULL, 0); } mp_hal_delay_ms(10); write_cmd(self, ST7789_NORON, NULL, 0); mp_hal_delay_ms(10); const mp_obj_t args[] = { self_in, mp_obj_new_int(0), mp_obj_new_int(0), mp_obj_new_int(self->width), mp_obj_new_int(self->height), mp_obj_new_int(BLACK)}; st7789_ST7789_fill_rect(6, args); if (self->backlight) mp_hal_pin_write(self->backlight, 1); write_cmd(self, ST7789_DISPON, NULL, 0); mp_hal_delay_ms(150); return mp_const_none; }
下面介紹幾個比較關鍵的寄存器:
1、MADCTL (36h) 控制屏幕顯示/刷新方向和RGB順序
MX=0 表示列地址的方向是從左往右,MX=1 表示列地址的方向是從右往左;MY=0 表示行地址的方向是從上往下,MY=1 表示行地址的方向是從下往上;MV=0表示正常模式,MV=1表示行列方向互換;通過控制MV、MX、MY這三個位的值就可以控制屏幕方向(參考ST7789芯片手冊第125頁)。MADCTL = 0x00是正常方向,MADCTL = 0x60是順時針旋轉90°的方向,MADCTL = 0xC0是屏幕上下顛倒的方向,MADCTL = 0xA0是逆時針旋轉90°的方向。此外MADCTL寄存器的D3位還可以控制RGB顏色順序,該位為0時代表顏色的兩字節按R-G-B順序解釋,設置為1時按G-B-R順序(默認為RGB)。
2、CASET (2Ah): Column Address Set、 RASET (2Bh): Row Address Set 、RAMWR (2Ch): Memory Write 。CASET和RASET寄存器設置列與行像素坐標的范圍,即設定一個繪圖區域,超出該區域無效。對於寬高為240×320像素的屏幕來說,如果在全屏幕上繪圖,則列方向像素的坐標范圍是:XS=0(0h),XE=239(EFh);行方向像素的坐標范圍是:YS=0(0h),YE=319(13Fh)。設置好繪圖區域后即可發送RAMWR指令,開始將像素數據從MCU傳送至ST7789的frame memory。
下面是基於MicroPython的ST7789液晶屏驅動代碼,實現了一些最基本的LCD控制功能以及基礎圖象繪制:

from machine import SPI, Pin import struct import time def color565(red: int, green: int = 0, blue: int = 0) -> int: """ Convert red, green and blue values (0-255) into a 16-bit 565 encoding. """ return (red & 0xF8) << 8 | (green & 0xFC) << 3 | blue >> 3 def swap_bytes(val): ((val >> 8) & 0x00FF) | ((val << 8) & 0xFF00) class LCD_ST7789: # Color definition BLACK = 0x0000 BLUE = 0x001F RED = 0xF800 GREEN = 0x07E0 CYAN = 0x07FF MAGENTA = 0xF81F YELLOW = 0xFFE0 WHITE = 0xFFFF # Color order RGB = 0x00 BGR = 0x08 # Default st7789 display orientation tables # [madctl, width, height] ORIENTATIONS_240x320 = [ [ 0x00, 240, 320 ], # Normal direction [ 0x60, 320, 240 ], # X-Y Exchange, X-Mirror [ 0xC0, 240, 320 ], # X-Mirror, Y-Mirror [ 0xA0, 320, 240 ]] # X-Y Exchange, Y-Mirror def __init__(self, spi, cs=17, dc=21, rst=20, backlight=16, width=240, height=320, rotation=0, color_order=RGB): self.spi = spi self.cs = Pin(cs, Pin.OUT, value=1) # Chip selection pin. Low enable, High disable self.dc = Pin(dc, Pin.OUT, value=0) # dc=0: command, dc=1: data self.rst = Pin(rst, Pin.OUT, value=1) # Reset signal is active low self.backlight = Pin(backlight, Pin.OUT, value=1) self.width = width self.height = height self.rotation = rotation self.color_order = color_order self.init() def send_command(self, command): # Pin objects are callable. The call method provides a shortcut to set and get the value of the pin. self.cs(0) self.dc(0) self.spi.write(bytearray([command])) self.cs(1) def send_data(self, data): self.cs(0) self.dc(1) self.spi.write(bytearray([data])) self.cs(1) def hard_reset(self): """Hard reset display""" self.rst(0) time.sleep_ms(50) self.rst(1) time.sleep_ms(50) def soft_reset(self): """Soft reset display""" self.send_command(0x01) # SWRESET (0x01): Software Reset time.sleep_ms(120) def init(self): """Initialize the lcd register""" # Performs a reset self.hard_reset() # Turn off sleep mode self.send_command(0x11) # SLPOUT (0x11): Sleep Out time.sleep_ms(120) # wait a little time before sending any new commands # Set pixel format self.send_command(0x3A) # COLMOD(0x3A): Interface Pixel Format self.send_data(0x55) # 16-bit 65K RGB mode(RGB565) self.send_command(0xC6) # FRCTRL2 (0xC6): Frame Rate Control in Normal Mode self.send_data(0x01) # 111Hz # self.send_command(0x36) # MADCTL (0x36): Memory Data Access Control # self.madctl_value = self.color_order | 0x00 # self.send_data(self.madctl_value) self.set_rotation(self.rotation) self.send_command(0x20) # INVOFF (0x20): Display Inversion Off # Gamma setting # https://baike.baidu.com/item/%E4%BC%BD%E7%8E%9B%E6%A0%A1%E6%AD%A3/7257507?fr=aladdin # https://www.bilibili.com/video/av2586864/ # https://www.zhihu.com/question/27467127 # self.send_command(0xE0) # PVGAMCTRL (0xE0): Positive Voltage Gamma Control # self.send_data(0xD0) # self.send_data(0x05) # self.send_data(0x09) # self.send_data(0x09) # self.send_data(0x08) # self.send_data(0x14) # self.send_data(0x28) # self.send_data(0x33) # self.send_data(0x3F) # self.send_data(0x07) # self.send_data(0x13) # self.send_data(0x14) # self.send_data(0x28) # self.send_data(0x30) # self.send_command(0XE1) # NVGAMCTRL (0xE1): Negative Voltage Gamma Control # self.send_data(0xD0) # self.send_data(0x05) # self.send_data(0x09) # self.send_data(0x09) # self.send_data(0x08) # self.send_data(0x03) # self.send_data(0x24) # self.send_data(0x32) # self.send_data(0x32) # self.send_data(0x3B) # self.send_data(0x14) # self.send_data(0x13) # self.send_data(0x28) # self.send_data(0x2F) # Turns the display to normal mode (means partial mode off) self.send_command(0x13) # NORON (0x13): Normal Display Mode On self.send_command(0x29) # DISPON (0x29): Display On def set_window(self, Xstart, Ystart, Xend, Yend): """ The address ranges are X=0 to X=239 (Efh) and Y=0 to Y=319 (13Fh). Addresses outside these ranges are not allowed. Before writing to the RAM, a window must be defined that will be written. The window is programmable via the command registers XS, YS designating the start address and XE, YE designating the end address. For example the whole display contents will be written, the window is defined by the following values: XS=0 (0h) YS=0 (0h) and XE=239 (Efh), YE=319 (13Fh). """ if (Xstart > Xend or Xend >= self.width): return if (Ystart > Yend or Yend >= self.height): return # Set the X coordinates self.send_command(0x2A) # CASET (0x2A): Column Address Set self.send_data(Xstart >> 8) # Set the horizontal starting point to the high octet self.send_data(Xstart & 0xFF) # Set the horizontal starting point to the low octet self.send_data(Xend >> 8) # Set the horizontal end to the high octet self.send_data(Xend & 0xFF) # Set the horizontal end to the low octet # Set the Y coordinates self.send_command(0x2B) # RASET (0x2B): Row Address Set self.send_data(Ystart >> 8) self.send_data(Ystart & 0xFF) self.send_data(Yend >> 8) self.send_data(Yend & 0xFF) # This command is used to transfer data from MCU to frame memory # When this command is accepted, the column register and the page # register are reset to the start column/start page positions. self.send_command(0x2C) # RAMWR (0x2C): Memory Write def on(self): """ Turn on the backlight pin if one was defined during init. """ self.backlight(1) time.sleep_ms(10) def off(self): """ Turn off the backlight pin if one was defined during init. """ self.backlight(0) time.sleep_ms(10) def set_color_order(self, color_order: int): """Change color order""" if not color_order in [self.RGB, self.BGR]: return self.send_command(0x36) # MADCTL (0x36): Memory Data Access Control self.color_order = color_order self.madctl_value = color_order | (self.madctl_value & 0xF7) self.send_data(self.madctl_value) def inversion_mode(self, value: bool): """ Enable or disable display inversion mode. Args: value (bool): if True enable inversion mode. if False disable inversion mode """ if value: self.send_command(0x21) # INVON (0x21): Display Inversion On else: self.send_command(0x20) # INVOFF (0x20): Display Inversion Off def sleep_mode(self, value: bool): """ Enable or disable display sleep mode. Args: value (bool): if True enable sleep mode. if False disable sleep mode """ if value: # This command causes the LCD module to enter the minimum power consumption mode. # MCU interface and memory are still working and the memory keeps its contents. self.send_command(0x10) # SLPIN (0x10): Sleep in else: self.send_command(0x11) # SLPOUT (0x11): Sleep Out time.sleep_ms(120) # wait a little time before sending any new commands def set_rotation(self, rotation: int): """ Set display rotation. Args: rotation (int): - 0-Portrait - 1-Landscape - 2-Inverted Portrait - 3-Inverted Landscape """ if rotation < 0 or rotation > 3: return self.rotation = rotation row = self.ORIENTATIONS_240x320[self.rotation] self.madctl_value = self.color_order | row[0] self.width = row[1] self.height = row[2] self.send_command(0x36) # MADCTL (0x36): Memory Data Access Control self.send_data(self.madctl_value) def pixel(self, x: int, y: int, color: int): """ Draw a pixel at the given location and color. Args: x (int): x coordinate y (int): y coordinate color (int): 565 encoded color """ self.set_window(x, y, x, y) pixel = struct.pack('>H', color) self.cs(0) self.dc(1) self.spi.write(pixel) self.cs(1) def blit_buffer(self, buffer: bytes, x: int, y: int, width: int, height: int): """ Copy buffer to display at the given location. Args: buffer (bytes): Data to copy to display x (int): Top left corner x coordinate y (int): Top left corner y coordinate width (int): Width height (int): Height """ if (not 0 <= x < self.width or not 0 <= y < self.height or not 0 < x + width <= self.width or not 0 < y + height <= self.height): raise ValueError("out of bounds") self.set_window(x, y, x + width - 1, y + height - 1) self.cs(0) self.dc(1) buffer_size = 256 limit = min(len(buffer), width * height * 2) chunks, rest = divmod(limit, buffer_size) if chunks: for i in range(chunks): self.spi.write(buffer[i*buffer_size: (i+1)*buffer_size]) if rest: self.spi.write(buffer[i*buffer_size: i*buffer_size+rest]) self.cs(1) def rect(self, x: int, y: int, w: int, h: int, color: int): """ Draw a rectangle at the given location, size and color. Args: x (int): Top left corner x coordinate y (int): Top left corner y coordinate w (int): Width in pixels h (int): Height in pixels color (int): 565 encoded color """ self.hline(x, y, w, color) self.vline(x, y, h, color) self.hline(x, y + h - 1, w, color) self.vline(x + w - 1, y, h, color) def fill_rect(self, x: int, y: int, width: int, height: int, color: int): """ Draw a rectangle at the given location, size and filled with color. Args: x (int): Top left corner x coordinate y (int): Top left corner y coordinate width (int): Width in pixels height (int): Height in pixels color (int): 565 encoded color """ if (x < self.width-1 and y < self.height-1): right = x + width - 1 bottom = y + height - 1 if (right > self.width - 1): right = self.width - 1 if (bottom > self.height - 1): bottom = self.height - 1 self.set_window(x, y, right, bottom) self.cs(0) self.dc(1) self.fill_color_buffer(color, 2*width*height) self.cs(1) def fill_color_buffer(self, color: int, length: int): buffer_pixel_size = 256 # 256 pixels, 512 byte chunks, rest = divmod(length, buffer_pixel_size*2) pixel = struct.pack('>H', color) buffer = pixel * buffer_pixel_size # a bytearray if chunks: for count in range(chunks): self.spi.write(buffer) if rest: self.spi.write(pixel * rest) def fill(self, color: int): """ Fill the entire FrameBuffer with the specified color. Args: color (int): RGB565 encoded color """ self.set_window(0, 0, self.width-1, self.height-1) self.cs(0) self.dc(1) self.fill_color_buffer(color, 2*self.width*self.height) self.cs(1) def line(self, x0: int, y0: int, x1: int, y1: int, color: int): """ Draw a single pixel wide line starting at x0, y0 and ending at x1, y1. Args: x0 (int): Start point x coordinate y0 (int): Start point y coordinate x1 (int): End point x coordinate y1 (int): End point y coordinate color (int): 565 encoded color """ # http://members.chello.at/easyfilter/bresenham.html # https://oldj.net/article/2010/08/27/bresenham-algorithm/ # https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm x, y = x0, y0 dx = abs(x1 - x0) dy = abs(y1 - y0) sx = 1 if x1 >= x0 else -1 sy = 1 if y1 >= y0 else -1 if dy > dx: dx, dy = dy, dx swap = 1 else: swap = 0 f = 2 * dy - dx self.pixel(x0, y0, color) for i in range(dx): if f >= 0: if swap: x += sx y += sy else: x += sx y += sy self.pixel(x, y, color) f = f + 2 * (dy - dx) else: if swap: y += sy else: x += sx f = f + 2 * dy def vline(self, x: int, y: int, length: int, color: int): """ Draw vertical line at the given location and color. Args: x (int): x coordinate y (int): y coordinate length (int): length of line color (int): 565 encoded color """ self.fill_rect(x, y, 1, length, color) def hline(self, x: int, y: int, length: int, color: int): """ Draw horizontal line at the given location and color. Args: x (int): x coordinate y (int): y coordinate length (int): length of line color (int): 565 encoded color """ self.fill_rect(x, y, length, 1, color) def map_bitarray_to_rgb565(self, bitarray, buffer, width, color, bg_color): """ Convert a bitarray to the rgb565 color buffer that is suitable for blitting. Bit 1 in bitarray is a pixel with color and 0 - with bg_color. """ row_pos = 0 length = len(bitarray) id = 0 for i in range(length): byte = bitarray[i] for bi in range(7,-1,-1): b = byte & (1 << bi) cur_color = color if b else bg_color buffer[id] = (cur_color & 0xFF00) >> 8 id += 1 buffer[id] = cur_color & 0xFF id += 1 row_pos += 1 if (row_pos >= width): row_pos = 0 break if __name__ == "__main__": # The RP2040 has 2 hardware SPI buses which is accessed via the machine.SPI class spi = SPI(id=0, baudrate=40_000_000, polarity=0, phase=0, sck=Pin(18,Pin.OUT), mosi=Pin(19,Pin.OUT), bits=8, firstbit=SPI.MSB) lcd = LCD_ST7789(spi, rotation = 0) lcd.fill(LCD_ST7789.BLACK) lcd.fill_rect(0, 0, 20, 40, LCD_ST7789.GREEN) lcd.rect(40, 0, 20, 40, LCD_ST7789.WHITE) lcd.hline(120, 160, 120, LCD_ST7789.RED) lcd.vline(120, 160, 160, LCD_ST7789.BLUE) # lcd.line(120, 160, 239, 0, LCD_ST7789.BLUE) # lcd.line(120, 160, 0, 40, color565(231,107,138)) # lcd.line(0, 40, 239, 319, color565(207,60,244)) # lcd.line(120, 160, 0, 319, LCD_ST7789.GREEN) # lcd.line(120, 160, 239, 319, LCD_ST7789.CYAN) # lcd.line(120, 160, 239, 100, LCD_ST7789.YELLOW) # time.sleep(2) # lcd.sleep_mode(True) # lcd.set_color_order(LCD_ST7789.BGR) # lcd.fill_rect(0, 50, 20, 40, LCD_ST7789.RED) # lcd.fill_rect(0, 100, 20, 40, LCD_ST7789.BLUE) # time.sleep(2) # lcd.sleep_mode(False) # import math # for x in range(240): # y = int(80*math.sin(0.125664*x)) + 160 # lcd.pixel(x, y, LCD_ST7789.MAGENTA) # # lcd.set_rotation(1) # # for x in range(360): # y = int(40*math.sin(0.125664*x)) + 120 # lcd.pixel(x, y, LCD_ST7789.CYAN) # time.sleep(2) # lcd.soft_reset() # time.sleep(2) # lcd.inversion_mode(True) # time.sleep(1) # lcd.off() # time.sleep(1) # lcd.on() SPRITE_WIDTH = 16 SPRITE_HEIGHT = 16 SPRITE_BITMAPS = [ bytearray([ 0b00000000, 0b00000000, 0b00000001, 0b11110000, 0b00000111, 0b11110000, 0b00001111, 0b11100000, 0b00001111, 0b11000000, 0b00011111, 0b10000000, 0b00011111, 0b00000000, 0b00011110, 0b00000000, 0b00011111, 0b00000000, 0b00011111, 0b10000000, 0b00001111, 0b11000000, 0b00001111, 0b11100000, 0b00000111, 0b11110000, 0b00000001, 0b11110000, 0b00000000, 0b00000000, 0b00000000, 0b00000000]), bytearray([ 0b00000000, 0b00000000, 0b00000011, 0b11100000, 0b00001111, 0b11111000, 0b00011111, 0b11111100, 0b00011111, 0b11111100, 0b00111111, 0b11110000, 0b00111111, 0b10000000, 0b00111100, 0b00000000, 0b00111111, 0b10000000, 0b00111111, 0b11110000, 0b00011111, 0b11111100, 0b00011111, 0b11111100, 0b00001111, 0b11111000, 0b00000011, 0b11100000, 0b00000000, 0b00000000, 0b00000000, 0b00000000]), bytearray([ 0b00000000, 0b00000000, 0b00000111, 0b11000000, 0b00011111, 0b11110000, 0b00111111, 0b11111000, 0b00111111, 0b11111000, 0b01111111, 0b11111100, 0b01111111, 0b11111100, 0b01111111, 0b11111100, 0b01111111, 0b11111100, 0b01111111, 0b11111100, 0b00111111, 0b11111000, 0b00111111, 0b11111000, 0b00011111, 0b11110000, 0b00000111, 0b11000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000])] # convert bitmaps into rgb565 blitable buffers blitable = [] for sprite_bitmap in SPRITE_BITMAPS: sprite = bytearray(512) lcd.map_bitarray_to_rgb565( sprite_bitmap, sprite, SPRITE_WIDTH, LCD_ST7789.YELLOW, LCD_ST7789.BLACK) blitable.append(sprite) import random x = random.randint(0, lcd.width - SPRITE_WIDTH) y = random.randint(0, lcd.height - SPRITE_HEIGHT) # draw sprites while True: for i in range(0, len(blitable)): lcd.blit_buffer(blitable[i], x, y, SPRITE_WIDTH, SPRITE_HEIGHT) time.sleep_ms(200) for i in range(len(blitable)-1,-1,-1): lcd.blit_buffer(blitable[i], x, y, SPRITE_WIDTH, SPRITE_HEIGHT) time.sleep_ms(200)
需要注意的是屏幕尺寸為240×320,對RGB565格式來說每個像素占2字節,那么整個屏幕需要240*320*2=153600字節(150Kb),而一般單片機的RAM都很有限,比如STM32F407只有192Kb的RAM,在程序中直接使用數組等緩沖區刷新屏幕時要注意數組大小,數組過大會產生棧溢出,可以設置一個合適的數組長度,分段進行數據刷新。
在當前文件中測試了基本的繪圖函數及一些LCD控制命令(如睡眠、背光開關、顏色反轉、軟復位等),除此之外還使用blit_buffer函數根據給定的字節數組繪制了“吃豆人”,在屏幕上隨機選擇一個初始位置,連續繪制產生動畫效果。由於例子是用MicroPython腳本語言編寫的,比st7789_mpy這個由C語言編譯的庫要慢不少,連續畫斜直線時可以看到肉眼可見的延遲。
LCD_ST7789類的繪圖函數中畫像素點、填充矩形、畫豎線或直線(特殊的填充矩形)、畫矩形等方法比較簡單直接對FrameBuffer進行簡單操作就行,而像畫斜直線、畫圓、畫橢圓、畫貝塞爾曲線、畫抗鋸齒的直線等方法就更復雜一些,涉及計算機圖形學相關知識。在屏幕上畫直線有DDA(數值微分)算法、中點畫線法、Bresenham算法等多種方法,Bresenham算法的優點如下:① 不必計算直線的斜率,因此不做除法。② 不用浮點數,只用整數。③ 只做整數加減運算和乘2運算,而乘2運算可以用移位操作實現。④ Bresenham算法的運算速度很快。程序中使用Bresenham算法來畫直線,下面是該算法的簡單介紹:
參考:
st7789_mpy/examples/st7789.pyi
The Beauty of Bresenham's Algorithm
MicroPython class SPI – a Serial Peripheral Interface bus protocol