|
从网上捡垃圾淘到几块液晶屏,分辨率256*64,驱动芯片ST7529,手册很容易搞到。本来芯片支持并口,但该屏强制设置成8为并口输出,经仔细比对分析,不能通过在电路板和排线上动手术来实现spi驱动。8位并口接线太多,而且我习惯使用ESP32和micropython来编程,其电平翻转速度堪忧,但是spi速度很快,这也是我采用spi方案(mcp23S17)的原因,此前用I2C方案(mcp23017)特实验成功过,但是屏幕刷新速度较慢。其实采用spi方案的刷新速度也不是很快,10秒刷新一屏,感人!原因是,即使我翻转屏幕一个控制脚的电平,也要写入1个字节,很亏不是么!传输8位并口上的数据也是已写入1个字节,这个倒是不亏。总的来说,实用性不是很强,权当作实验性学习了。 程序代码如下: from machine import SPI, Pin import framebuf, my_ufont import time
class MCP23S17: def __init__(self, spi, cs_pin=9): self.spi = spi self.cs = Pin(cs_pin, Pin.OUT, value=1) self.init() # 初始化设备 def write_reg(self, addr, val): """向指定寄存器写入单个值""" self.cs.value(0) self.spi.write(bytes([0x40, addr, val])) # 0x40是写操作控制字节 self.cs.value(1) def read_reg(self, addr): """读取指定寄存器的值""" self.cs.value(0) self.spi.write(bytes([0x41, addr])) # 0x41是读操作控制字节 val = self.spi.read(1)[0] self.cs.value(1) return val def get_current_bank(self): """检测当前BANK位状态""" iocon_val = self.read_reg(0x0A) current_bank = (iocon_val >> 7) & 1 if current_bank == 1: iocon_val_bank1 = self.read_reg(0x05) if (iocon_val_bank1 >> 7) & 1 == 1: return 1 return 0 def force_bank0(self): """强制将BANK位切换为0""" current_bank = self.get_current_bank() if current_bank == 1: print("检测到BANK=1,正在切换为BANK=0...") self.write_reg(0x05, 0x00) new_bank = self.get_current_bank() if new_bank == 0: print("BANK切换为0成功") else: print("警告:BANK切换失败") else: print("当前BANK=0,无需切换") def init(self): """初始化MCP23S17,配置端口A和B为输出""" self.force_bank0() self.write_reg(0x00, 0x00) # IODIRA = 0x00(端口A输出) self.write_reg(0x01, 0x00) # IODIRB = 0x00(端口B输出) class ST7529: def __init__(self, mcp23s17): self.mcp = mcp23s17 # 控制引脚定义 self.SIGNAL_MAP = { 'A0': 0, # 命令/数据选择 'WR': 1, # 写使能 'RST': 2, #复位 #'CS': 3 #片选,已物理接地 } self.width = 256 self.height = 64 # 4位灰度 → 5位灰度映射表 #self.gray_map = [0, 2, 4, 6, 8, 10, 13, 14, 17, 19, 21, 23, 25, 27, 29, 31] # 初始化控制引脚状态(RST=1, A0=0, WR=1) self.current_gpiob = 1 << self.SIGNAL_MAP['RST'] self.mcp.write_reg(0x13, self.current_gpiob) self.reset_st7529() self.init_display()
def set_A0(self, is_data): """设置A0引脚(命令/数据模式切换)""" if is_data: self.current_gpiob |= (1 << self.SIGNAL_MAP['A0']) else: self.current_gpiob &= ~(1 << self.SIGNAL_MAP['A0']) self.mcp.write_reg(0x13, self.current_gpiob)
def pulse_WR(self): """生成WR写脉冲(每个数据必须一次)""" # 拉低WR self.current_gpiob &= ~(1 << self.SIGNAL_MAP['WR']) self.mcp.write_reg(0x13, self.current_gpiob) # 拉高WR(无延时,硬件足够响应) self.current_gpiob |= (1 << self.SIGNAL_MAP['WR']) self.mcp.write_reg(0x13, self.current_gpiob)
def reset_st7529(self): """复位液晶屏""" # 拉低RST self.current_gpiob &= ~(1 << self.SIGNAL_MAP['RST']) self.mcp.write_reg(0x13, self.current_gpiob) time.sleep_ms(200) # 拉高RST self.current_gpiob |= (1 << self.SIGNAL_MAP['RST']) self.mcp.write_reg(0x13, self.current_gpiob) time.sleep_ms(200)
def init_display(self): """初始化液晶屏""" init_cmds = [ (0x30, []), # Ext = 0 (0x94, []), # Sleep Out (0xd1, []), # OSC On (0x20, [0x08]), # Power Control Set ('delay', 20), (0x20, [0x0B]), # Power Control Set (Booster ON) ('delay', 5), (0x81, [0x3f, 0x03]), # Electronic Control (0xca, [0x04, 0x0f, 0x00]), # Display Control (0xa7, []), # Normal Display (0xbb, [0x00]), # COM Scan Direction (0xbc, [0x02, 0x01, 0x02]), # Data Scan Direction (0x75, [0x00, 0x3f]), # Line Address Set (0-63) (0x15, [0x00, 0x54]), # Column Address Set (0x31, []), # Ext = 1 (0x32, [0x00, 0x01, 0x05]), # Analog Circuit Set (0x20, [0x02, 0x09, 0x11, 0x13, 0x15, 0x16, 0x17, 0x18, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1d, 0x1f]), (0x21, [0x02, 0x10, 0x13, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1b, 0x1c, 0x1c, 0x1d, 0x1e, 0x1f]), (0x34, []), # Software Initial (0x30, []), # Ext = 0 (0xaf, []), # Display On ('delay', 100), # 发送初始化命令 for item in init_cmds: if item[0] == 'delay': time.sleep_ms(item[1]) else: self._write_cmd_fast(item[0]) for data in item[1]: self._write_data_fast(data)
def _write_cmd_fast(self, cmd): """快速写入命令""" self.set_A0(is_data=False) # 命令模式 self.mcp.write_reg(0x12, cmd) # 数据总线送命令 self.pulse_WR() # 写脉冲锁存
def _write_data_fast(self, data): """快速写入数据(每个数据独立锁存)""" self.set_A0(is_data=True) # 数据模式 self.mcp.write_reg(0x12, data) # 数据总线送数据 self.pulse_WR() # 写脉冲锁存(关键:确保每个数据被正确接收)
def convert_buf_to_display32grey(self, buf): """将RGB565格式的图像数据转换为32级灰度格式,只利用一个通道""" if len(buf) != 255*64*2: raise ValueError(f"输入buf长度错误!需32640字节,实际{len(buf)}字节") display_data = [] for row in range(64): row_start = row * 510 row_buf = buf[row_start : row_start + 510] if len(row_buf) < 510: raise ValueError(f"第{row}行数据不足510字节") row_display = bytearray(255) disp_pos = 0 for group in range(0, 510, 2): b0, b1 = row_buf[group], row_buf[group+1] # 提取B通道值(5位) # 前字节的低5位就是B值(因为是小端模式) r_value = b0<<3 #B值要作为高5位写入 row_display[disp_pos] = r_value disp_pos += 1 display_data.append(row_display) return display_data
def display_framebuf(self, buf): """显示framebuf图像""" display_data = self.convert_buf_to_display32grey(buf) self._write_cmd_fast(0x5c) # 进入RAM写入模式 # 逐行逐点显示(每个点独立锁存,确保显示正确) for row_data in display_data: for data in row_data: self._write_data_fast(data) def clear_display(self, gray=0): """清屏(默认黑色,可指定灰度0~31)""" self._write_cmd_fast(0x5c) for _ in range(255 * 64): self._write_data_fast(gray<<3) #要作为高5位写入
def set_contrast(self, contrast): """设置对比度""" self._write_cmd_fast(0x81) self._write_data_fast(contrast & 0x3f) self._write_data_fast((contrast >> 6) & 0x7)
# 使用示例 if __name__ == "__main__": # 初始化SPI(波特率8MHz,根据硬件调整引脚) spi = SPI(1, baudrate=40_000_000, sck=3, mosi=10, miso=6) mcp23s17 = MCP23S17(spi=spi, cs_pin=2) # 初始化显示屏 lcd = ST7529(mcp23s17) # 创建framebuf(256×64分辨率,RGB565彩色,实际是利用蓝色通道作为5位灰度来传递数据和显示) buf_size = 255*64*2 # 64行×510字节/行 buf = bytearray(buf_size) fb = framebuf.FrameBuffer(buf, 255, 64, framebuf.RGB565) #font = my_ufont.BMFont("simsun12.bmf", fb, 255, 64) # 绘制测试内容 fb.fill(0) # 清屏(白色) #32位灰度下,以下对颜色的设置值在0~31!实质是利用蓝色通道 #font.text2buf('我爱大家 清屏Hello World!', 0, 30, 13, 0) #fb.text("Hello World!", 10, 10, 23) # 黑色文字 fb.fill_rect(210, 30, 40, 20, 5) # 灰色矩形 #fb.line(100, 30, 200, 50, 31) # 黑色线段 # 显示图像 lcd.display_framebuf(buf) print("显示完毕")
其中的fb.text()直接利用framebuf里的函数,只能输出固定大小的应为字符。font = my_ufont.BMFont("simsun12.bmf", fb, 255, 64)和font.text2buf('我爱大家 清屏Hello World!', 0, 30, 13, 0)则可以输出汉字和字母,其大小和字体由我自己生成的字库来决定,而生成字库也是用自编的mpy代码读取PC系统的任意字库。为了使本帖简化,就暂不详细说明如何生成字库了。
|