数码之家

 找回密码
 立即注册

QQ登录

只需一步,快速开始

微信登录

微信扫一扫,快速登录

搜索
查看: 593|回复: 4

[other] 并口液晶屏的SPI驱动实验(micropython)

[复制链接]
发表于 2025-10-22 17:08:34 | 显示全部楼层 |阅读模式
从网上捡垃圾淘到几块液晶屏,分辨率256*64,驱动芯片ST7529,手册很容易搞到。本来芯片支持并口,但该屏强制设置成8为并口输出,经仔细比对分析,不能通过在电路板和排线上动手术来实现spi驱动。8位并口接线太多,而且我习惯使用ESP32micropython来编程,其电平翻转速度堪忧,但是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系统的任意字库。为了使本帖简化,就暂不详细说明如何生成字库了。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册 微信登录

x
 楼主| 发表于 2025-10-22 17:10:24 | 显示全部楼层
图片忘了上传,补充一下。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册 微信登录

x
回复 支持 反对

使用道具 举报

发表于 2025-10-22 20:04:20 | 显示全部楼层
micropython太费内存了
回复 支持 反对

使用道具 举报

 楼主| 发表于 2025-10-24 09:51:25 | 显示全部楼层
soma 发表于 2025-10-22 20:04
micropython太费内存了

用ESP32C3或C6驱动,内存足够。话说ESP32C3只卖6快多了,还是开发板。这就很好了。比起STM32,根本不需要配置寄存器那些天书一般的低级存在,ESP32C3在mpy,特别是Tonnyl的加持下,那是相当的友好!
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册 微信登录

本版积分规则

APP|手机版|小黑屋|关于我们|联系我们|法律条款|技术知识分享平台

闽公网安备35020502000485号

闽ICP备2021002735号-2

GMT+8, 2025-11-9 04:56 , Processed in 0.561601 second(s), 8 queries , Gzip On, Redis On.

Powered by Discuz!

© 2006-2025 MyDigit.Net

快速回复 返回顶部 返回列表