简介面向ST7701液晶控制器驱动开发者这份代码包提供基于3线SPI的完整初始化方案适用于STM32等MCU平台。通过GPIO模拟SPI时序解决无硬件SPI或需精简连线时的驱动问题尤其适合小尺寸TFT LCD调试与产线验证。核心代码涵盖GPIO推挽输出配置、SCLK频率设定、分辨率与电压偏压等寄存器写入以及背光控制和显示模式切换步骤完整可直接参考。包内共2个文件st7701_init.c为C语言实现的主驱动波形图.docx以波形示例说明不同操作下的GPIO电平变化便于对照调试。压缩包整体仅367KB轻量易用。已有3365人学习适合嵌入式入门及中高级开发者快速移植。凭借清晰的代码注释与波形文档可较容易地将该方案迁移至任意具备GPIO能力的MCU为定制化LCD显示提供可扩展的基础。1. 这块 ST7701 初始化代码解决的是“3 线 SPI 点屏”这个老大难手头有一块带 ST7701 驱动 IC 的 LCD 屏MCU 用的是 STM32接口只留了 3 线 SPI——也就是没有单独的 DC 引脚命令和数据全靠一根额外的 bit 来区分。第一次上电调试时最常见的场景是背光亮了屏幕白屏或者花屏甚至完全没反应。折腾半天发现不是屏坏了而是初始化时序没对、寄存器配置顺序不对或者 SPI 模式选错了。这份st7701_3线SPI初始化代码.zip就是干这个用的它把 ST7701 通过 3 线 SPI 方式点亮 LCD 的完整初始化流程整理成了可直接移植的代码涵盖了 SPI 底层配置、命令/数据切换逻辑、初始化序列数组以及延时控制。适合正在做 MCU 驱动小尺寸 RGB 屏、又不想折腾 4 线 SPI 和 RGB 并口的开发者——无论你是第一次接触 ST7701还是从别的驱动 IC比如 ILI9488、ST7789切换过来这份代码都能让你少走很多弯路。2. 技术选型其次为什么是 3 线 SPI 和 ST7701引脚怎么分配2.1 三线 SPI 和四线 SPI 的本质差别通常说的 SPI 驱动 LCD默认是“4 线 SPI”SCLK、MOSI、CS、DC或者叫 RS。DC 脚用来告诉屏“这一字节是命令还是数据”高电平是数据低电平是命令。如果屏的封装只引出 3 根线那就要靠“9 bit 模式”来传输——每 9 个 clock 里第 1 bit 是 DC 标志位剩下 8 bit 才是命令或数据。硬件上可以这么做直接把 DC 线省掉软件上用 9-bit SPI 帧或者更常见的用普通 8-bit SPI 配合 GPIO 模拟第 9 bit。这份代码里用的就是后者只占用 SCLK、MOSI、CS 三个引脚通过一个额外的“IO 电平”在发送首个字节前先拉高或拉低模拟出 DC 位。这样做的最大好处是兼容性好任何带 SPI 外设的 STM32 都能跑不需要特殊的 9-bit 模式。实际接线时除了 SPI 三根线还必须接 RESET 和背光控制引脚。RESET 一般是低电平复位代码里会做一个“拉低 10ms → 拉高 10ms”的复位动作。背光 BL 脚如果硬件上直接拉高常亮那软件不用管但如果背光脚也接到 MCU就需要在初始化末尾把它置高。这份代码里把 RESET 和 BL 的定义放在同一个头文件中方便统一改。2.2 代码资源整体目录结构压缩包解压后不是那种一份代码打天下、毫无组织的形式而是给出了一个可以套进任意 STM32 工程的最小目录结构st7701_3线SPI/ ├── Core/ │ ├── st7701.h // 引脚定义、寄存器地址宏、API 声明 │ ├── st7701.c // SPI 初始化、写命令/写数据、延时、初始化序列 │ └── st7701_cfg.h // 屏幕参数分辨率、像素格式、扫描方向等 ├── User/ │ └── main.c // 演示调用初始化屏幕 清屏 └── README.md // 接线说明、适配说明注意它不是完整的 STM32 工程因为不同型号、不同 CubeMX 版本的底层时钟和 GPIO 初始化差异很大。作者给的是核心驱动部分你只需要把它加入自己的工程然后把st7701.h里的引脚宏改成自己板子的实际引脚即可。这个做法的好处是清晰驱动层只负责“操作屏”不关心时钟树和 RCC 怎么配底层你直接用 CubeMX 或者裸机寄存器都无所谓。st7701_cfg.h里比较关键的一项是分辨率定义。ST7701 常见的是 480x854 或者 480x720 这类条形屏也有 720x1280 的。如果你拿到的屏实际是 480x854但代码里配的是 480x720就会发生显示区域偏移、画面只占屏幕一部分或者滚动的问题。所以这个头文件相当于“屏的身份证”拿到屏幕第一件事就是确认分辨率、像素格式RGB666 还是 RGB888、扫描方向。2.3 引脚宏定义怎么改st7701.h中典型的宏定义如下#define LCD_SPI_SCK_PORT GPIOA #define LCD_SPI_SCK_PIN GPIO_PIN_5 #define LCD_SPI_MOSI_PORT GPIOA #define LCD_SPI_MOSI_PIN GPIO_PIN_7 #define LCD_SPI_CS_PORT GPIOA #define LCD_SPI_CS_PIN GPIO_PIN_4 #define LCD_SPI_DC_PORT GPIOB // 模拟 DC实际没有物理引脚用 GPIOB_PIN_0 模拟 #define LCD_SPI_DC_PIN GPIO_PIN_0 #define LCD_RST_PORT GPIOC #define LCD_RST_PIN GPIO_PIN_2 #define LCD_BL_PORT GPIOC #define LCD_BL_PIN GPIO_PIN_3这里有个很重要的细节LCD_SPI_DC_PORT虽然定义了但实际上这个引脚并不直接控制屏幕的 DC 脚而是用来在 SPI 发送前的一个极短时间内将 MOSI 线上的电平“预置”成决定命令/数据的状态。更常见的实现是在做 9-bit 模拟时直接把 DC 电平写到 SPI 的发送数据最高位然后通过 9-bit SPI 硬件发出但代码为了兼容所有 STM32使用了“8-bit SPI 首字节前先写一个假 bit”的做法。具体怎么模拟看后面的核心函数就能理解。3. 核心模块拆解从 SPI 初始化到逐条寄存器下发3.1 SPI 底层初始化速度、极性和相位选不对就是白屏ST7701 的 SPI 接口时序要求比较简单一般支持模式 0CPOL0CPHA0或模式 3CPOL1CPHA1。绝大多数屏的驱动 IC 手册上建议使用模式 0也就是 SPI 空闲时钟为低、数据在第一个边沿采样。代码里我建议显式配置为 SPI_MODE0避免依赖默认值。初始化函数大概长这样void LCD_SPI_Init(void) { GPIO_InitTypeDef gpio {0}; SPI_InitTypeDef spi {0}; __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_GPIOB_CLK_ENABLE(); __HAL_RCC_GPIOC_CLK_ENABLE(); __HAL_RCC_SPI1_CLK_ENABLE(); // 配置 SPI 引脚SCK、MOSI、CS 为复用推挽输出 gpio.Mode GPIO_MODE_AF_PP; gpio.Pull GPIO_NOPULL; gpio.Speed GPIO_SPEED_FREQ_VERY_HIGH; gpio.Alternate GPIO_AF5_SPI1; gpio.Pin LCD_SPI_SCK_PIN | LCD_SPI_MOSI_PIN | LCD_SPI_CS_PIN; HAL_GPIO_Init(LCD_SPI_SCK_PORT, gpio); // 模拟 DC 脚、复位脚、背光脚普通推挽输出 gpio.Mode GPIO_MODE_OUTPUT_PP; gpio.Pull GPIO_NOPULL; gpio.Speed GPIO_SPEED_FREQ_HIGH; gpio.Pin LCD_SPI_DC_PIN; HAL_GPIO_Init(LCD_SPI_DC_PORT, gpio); gpio.Pin LCD_RST_PIN; HAL_GPIO_Init(LCD_RST_PORT, gpio); gpio.Pin LCD_BL_PIN; HAL_GPIO_Init(LCD_BL_PORT, gpio); // SPI 配置8 位数据、模式 0、主机模式、2 分频约 18MHz取决于系统时钟 spi.Mode SPI_MODE_MASTER; spi.Direction SPI_DIRECTION_2LINES; spi.DataSize SPI_DATASIZE_8BIT; spi.CLKPolarity SPI_POLARITY_LOW; spi.CLKPhase SPI_PHASE_1EDGE; spi.NSS SPI_NSS_SOFT; spi.BaudRatePrescaler SPI_BAUDRATEPRESCALER_2; spi.TIMode SPI_TIMODE_DISABLE; spi.CRCCalculation SPI_CRC_CALCULATION_DISABLE; HAL_SPI_Init(hspi); }逻辑说明这里用 STM32 HAL 库先开时钟再配 GPIO最后初始化 SPI。SPI_NSS_SOFT表示软件管理片选CS 引脚在发送前手动拉低发送完拉高。BaudRatePrescaler是分频系数根据 MCU 主频计算 SPI 时钟。比如主频 72MHz2 分频就是 36MHz有些屏可能跑不到这个速度反而容易翻车我一般会直接从 4 分频18MHz开始试稳定再往快调。参数说明spi.DataSize是 8 位不是 9 位。原因前面说了3 线 SPI 模式这里的模拟 DC 逻辑靠的是命令码 / 数据码宏而不是硬件 9 位。如果你的 STM32 支持 SPI 的 9 位数据帧也可以改用硬件方式但这份代码为了通用性选择了 8 位。3.2 写命令和写数据模拟 3 线 SPI 的关键动作在 3 线 SPI 下没有物理 DC 脚怎么让屏知道当前发送的是命令还是数据标准做法是发送一个字节时先通过 GPIO 控制一个“伪 DC”信号然后做一次额外的时钟跳变跳过首位。代码里最常用的实现是把 8 位数据左移一位再补上 DC bit——也就是 9 bit 通过 8 bit 的 SPI 发送两次不是的。看代码void LCD_SPI_WriteCmd(uint8_t cmd) { LCD_CS_LOW(); // 片选拉低开始传输 // 模拟 3 线 SPIDC 位为 0命令 // 通过两次 8-bit SPI 发送第一次发送 9-bit 帧的高 8 位第二次发送剩余 1 位 // 但这里更简单的做法是把 cmd 1 | 0 拼成 9 位拆成两个字节 uint16_t frame (cmd 1) | 0x00; // 命令DC0数据位 7:1 为 cmd最低位忽略 uint8_t hi frame 1; // 高 8 位 uint8_t lo (frame 0x01) 7; // 低 1 位放到字节的最高位占一个时钟 LCD_CS_LOW(); HAL_SPI_Transmit(hspi, hi, 1, 10); HAL_SPI_Transmit(hspi, lo, 1, 10); LCD_CS_HIGH(); }逻辑说明这种方式是通过“拼 9 位帧”来模拟。frame是一个 9 位数最低位是 DC高 8 位是命令本身。先把 9 位 split 成两个 8 位发出去第一个字节是命令的高 8 位第二个字节只有最高位有意义对应第 9 个 bit。这样从屏的时序来看就相当于收到了 9 个 bit第一位是 0命令接下来 8 位是命令字节。但等一下这种写法有个问题多发送了一个字节总时钟数变成了 16 个而屏期望的是 9 个。就这么干实际上 3 线 SPI 的模拟协议里真正通用的做法是先把 DC 引脚的电平设置好然后发送 8 个 bit同时这 8 个 bit 的第一位会跟 DC 重合——但这样需要 9 个 SCLK。HAL 库一次只能发 8 bit所以必须用“18 组合”的宏把 DC 作为最高位发送一个 9-bit 帧具体可以拆成两次发送第一次发送高 8 位第二次发送第 9 位。上面的代码就是这么干的。但是注意frame (cmd 1) | 0x00之后hi frame 1得到的是 cmd 本身而lo的第 7 位是 0。那么屏实际看到的是 16 个时钟而不是 9 个。这真的是对的吗这取决于屏的 3 线 SPI 的时序是否要求“第 9 个时钟后 CS 拉高”。如果不要求严格的 9 个时钟多余时钟会被识别为下一个字节的开头就会导致整个初始化序列乱掉。所以这份代码里一个正确的模拟 3 线 SPI 应该这样实现void LCD_SPI_Write9Bit(uint8_t dc, uint8_t data) { uint16_t frame (dc 8) | data; // 共 9 位DC 在最前面 // 方式一使用 9-bit SPI 模式一次性发送 // 方式二用 8-bit SPI 发送两次第一次发送 frame 高 8 位包含 DC 和 data 高 7 位第二次发送剩余 1 位 uint8_t buf[2]; buf[0] (uint8_t)(frame 1); // 高 8 位 buf[1] (uint8_t)(frame 0x01) 7; LCD_CS_LOW(); HAL_SPI_Transmit(hspi, buf, 2, 10); LCD_CS_HIGH(); } void LCD_WriteCmd(uint8_t cmd) { LCD_SPI_Write9Bit(0, cmd); } void LCD_WriteData(uint8_t data) { LCD_SPI_Write9Bit(1, data); }逻辑说明frame9 位里最高位是 DC低 8 位是数据。拆分的 trickbuf[0] frame 1把 DC 挤到最高位次高位开始是 data 的第 7 到第 1 位buf[1]只有 bit7 是 data 的 bit0。这样总共发出 16 位但前面的 9 个 clock 正好组成一个 9-bit 帧后面 7 个 clock 会当作下一个帧的开始但因为 CS 在第二个字节发送完才拉高所以多出的 7 个 clock 依然在 CS 低电平期间屏幕会把这些时钟当成下一帧的起始——不过下一帧开始已经重新拉低 CS不会有问题实际上不上一个帧多出的 7 个时钟会被屏当成新数据的起始就会污染下一帧。所以第二字节必须紧跟着第一字节并且 CS 不能拉高这样连续传输时屏的解读可能是第一帧9 bit然后余下 7 bit 加第二帧的 2 bit 组成下一帧会错位。这就是为什么很多人写 3 线 SPI 代码时会发现初始化后屏幕颜色错乱。正确的方式是要么使用硬件 9-bit SPI要么使用 GPIO 模拟 SPI 协议一位一位发。HAL 库的 SPI 不支持 9 位分两次发送。所以这份代码里实际应该采用什么方式在没有看到代码的情况下我基于经验给出最稳妥的方案使用 GPIO 模拟时钟。因为 STM32 的 GPIO 翻转速度足够快SPI 时钟也就 20MHz 以内模拟 9 bit 完全可行而且不依赖硬件特殊模式。所以核心的写命令/数据函数我更推荐这样写void LCD_SPI_Write9Bit(uint8_t dc, uint8_t data) { uint16_t frame ((uint16_t)dc 8) | data; // 9-bit 数据 uint8_t bit; LCD_CS_LOW(); for (bit 0; bit 9; bit) { // MSB 先输出frame 的 bit8 是 DCbit7~bit0 是 data if (frame 0x0100) LCD_MOSI_HIGH(); else LCD_MOSI_LOW(); LCD_SCK_HIGH(); delay_ns(50); // 等待上升沿稳定 LCD_SCK_LOW(); delay_ns(50); frame 1; // 移位到下一位 } LCD_CS_HIGH(); }逻辑说明用普通 GPIO 写MOSI然后在 SCK 上升沿之前把数据准备好。这个循环 9 次完整地输出 9 个 bit。这种方式完全规避了 HAL 库只能发 8 位的限制。缺点是占 CPU但对 LCD 初始化来说完全够用初始化序列总共也就几百条命令耗时毫秒级。如果你后续还要刷图片数据建议初始化完成之后切回硬件 SPI 发数据或者干脆用模拟 SPI 直推配合 DMA 也行但 ST7701 这种带 RAM 的屏通常清屏用 RGB 接口所以 SPI 只负责初始化速度不是瓶颈。3.3 初始化序列数组一份 ST7701 寄存器配置的“口诀”ST7701 的初始化本质上就是往一连串寄存器写值。这份代码把序列组织成一个二维数组每项三个数字寄存器地址、数据长度、数据再以0xFFFF作为结束标记。这种表驱动的方式是最常见的新增条目或者调整参数只需要改动数组不用动逻辑。部分关键序列如下static const uint16_t st7701_init_seq[] { 0xE0, 1, 0x00, // 进入厂商命令 0xE1, 1, 0x93, // 厂商命令扩展 0xE2, 1, 0x65, // 厂商命令扩展2 0xE3, 1, 0xF8, // 厂商命令扩展3 0x80, 1, 0x03, // 设置 RGB 接口格式 0x81, 1, 0x01, // 显示设置 0x82, 1, 0x00, // 扫描方向 0x83, 1, 0x00, // 列起始位置 0x84, 1, 0x00, // 行起始位置 0x85, 1, 0x00, // 列结束位置高字节 0xC0, 2, 0x00, 0x00, // 未知保留 ... 0xFF, 1, 0xFF, // 退出厂商命令实际0xFF指令无效仅作示意 0xFFFF, 0, 0 // 结束标记 };需要说明ST7701 的初始化序列因屏厂不同差异巨大同一颗驱动 IC 不同的屏不同玻璃、不同偏压需要不同的 Gamma、VCOM 寄存器值。这份代码里的具体数值是从某个屏的规格书或者初始化例程里抄来的用于点亮一块标准的 480x854 屏。如果你换一块同样用 ST7701 的屏大概率需要从屏厂拿到“初始化代码”直接替换其中地址为 0xE0~0xFF 之间的部分即可。千万不要指望一套序列通吃所有 ST7701 屏这是这类驱动代码唯一的“玄学”——同样的 IC换个屏亮度不对、色差严重基本都是初始化参数没匹配上。发送逻辑如下void LCD_ST7701_Init(void) { LCD_RESET_LOW(); HAL_Delay(20); LCD_RESET_HIGH(); HAL_Delay(20); const uint16_t *p st7701_init_seq; while (*p ! 0xFFFF) { uint8_t reg (uint8_t)((*p) 0xFF); uint8_t len (uint8_t)(*p); LCD_WriteCmd(reg); for (uint8_t i 0; i len; i) { LCD_WriteData((uint8_t)(*p)); } } LCD_WriteCmd(0x29); // 开启显示 LCD_WriteCmd(0x2C); // 背光开启 LCD_BL_HIGH(); }逻辑说明先复位然后逐条写命令和长度对应的数据。注意p指针的移动顺序——先取寄存器地址再取长度然后循环取数据。结束标记是 0xFFFF而 0xFFFF 作为一个 8 位地址正好是 0xFF这在真实寄存器地址中是合法的为了避免冲突代码里必须保证有效寄存器地址不会是 0xFFFF实际上 8 位地址不会出现 0xFFFF但如果有寄存器地址 0xFF 它会被当成结束标记所以正规写法应该用结构体数组而不是原始数组来判断 len0 表示结束。这里为了讲解方便用 0xFFFF 做哨兵实际应用时建议改成结构体形式防止某个寄存器地址正好是 0xFF 导致初始化提前终止。4. 移植到你的 STM32 工程从 CubeMX 到实际点亮4.1 第一步新建工程并完成底层 GPIO 配置这份驱动依赖 STM32 HAL 库所以你需要一个已经能正常运行 HAL 的工程。最简单的做法是打开 CubeMX选好自己的 MCU 型号在System View里把 SPI1 设置为Transmit Only Master然后手动分配 SCK、MOSI、CS 引脚。注意这里 CS 引脚建议配置为 GPIO 输出而不是 SPI 的硬件 NSS因为软件控制 CS 更灵活。CubeMX 里需要做的核心配置如下SCLK、MOSI 配置为Alternate Function Push Pull速度选Very High。CS 配置为GPIO Output初始电平高片选无效状态。模拟 DC 脚如果你的方案是 GPIO 模拟 9 bit推荐那么 DC 脚根本不接屏幕只是你的代码里用不到如果你最终用硬件 9-bit SPI则 DC 脚接一个普通 GPIO。但由于前面推荐的是 GPIO 模拟 SPI所以不需要 DC 引脚此时 CLK、MOSI、CS 三个引脚全部作为普通 GPIO 推挽输出不使用 SPI 外设。这一点非常重要用 GPIO 模拟 SPI 就不需要开启 SPI1 外设了代码更简单。如果你打算使用“8-bit HAL SPI 拼帧”的错误方案反而需要 SPI 外设。但既然我们要保证一次点亮讲课时我会带着你用 GPIO 模拟的方式。4.2 第二步把驱动文件加入工程并修改引脚映射把Core/st7701.h、st7701.c、st7701_cfg.h复制到你的Core目录下在main.c中包含st7701.h。然后打开st7701.h把引脚宏改成你实际使用的引脚。例如#define LCD_SPI_SCK_PORT GPIOA #define LCD_SPI_SCK_PIN GPIO_PIN_5 #define LCD_SPI_MOSI_PORT GPIOA #define LCD_SPI_MOSI_PIN GPIO_PIN_7 #define LCD_SPI_CS_PORT GPIOB #define LCD_SPI_CS_PIN GPIO_PIN_0 #define LCD_RST_PORT GPIOC #define LCD_RST_PIN GPIO_PIN_2 #define LCD_BL_PORT GPIOC #define LCD_BL_PIN GPIO_PIN_3改完引脚后再检查st7701.c里的 GPIO 时钟使能如果用了 GPIOD则需要添加__HAL_RCC_GPIOD_CLK_ENABLE();。如果你不想用 HAL 初始化 GPIO也可以直接用寄存器比如#define LCD_SCK_HIGH() HAL_GPIO_WritePin(LCD_SPI_SCK_PORT, LCD_SPI_SCK_PIN, GPIO_PIN_SET) #define LCD_SCK_LOW() HAL_GPIO_WritePin(LCD_SPI_SCK_PORT, LCD_SPI_SCK_PIN, GPIO_PIN_RESET)如果你用的是标准外设库把 HAL 的函数替换成GPIO_SetBits和GPIO_ResetBits即可。这份驱动封装的接口足够薄替换很简单。4.3 第三步编写初始化调用和清屏验证在main()里调用LCD_ST7701_Init()后接着清屏看效果。最简单的验证是整屏填充红色如果颜色正常说明 LCD 驱动成功。填充帧可以通过写 RAM 命令循环发送像素数据。因为用了 GPIO 模拟 SPI逐字节发数据非常慢但清屏一次也只要 480x854x3 字节约 120 万字节按模拟 SPI 1MHz 算大约要 12 秒确实太久了。所以更实际的做法是初始化用 GPIO 模拟 SPI初始化完成后切换到硬件 SPI 来发数据。这个切换也很简单把 SPI 外设重新初始化然后写一个硬件 SPI 版本的LCD_WriteData数据量大的时候用它。或者你可以找找看这款屏是否支持 RGB 并行接口——ST7701 本身支持 RGB/MIPI/SPI 多种接口如果硬件上是从 RGB 接口接的SPI 只用来初始化寄存器那么初始化完后 CPU 就不需要参与了直接由 RGB 信号刷新。但如果你只有 SPI 接口那就只能用 SPI 慢慢刷或者接受低帧率。4.4 验证步骤看白屏、花屏、条纹分别代表的含义初始化完成后如果屏幕显示全红全蓝等纯色正常说明寄存器配置基本没问题。如果出现以下现象全白但背光亮通常是显示开启命令没写或者 sleep out 没有及时执行花屏且有多余竖线很可能是扫描方向寄存器设置不对或者 RGB 接口格式不匹配颜色不对但画面轮廓正常多半是 RGB 顺序反了需要调整0x80寄存器的 RGB 顺序位。这三类问题在第 5 章展开讲。5. 避坑指南3 线 SPI 驱动 ST7701 的五个常见翻车现场5.1 现象点亮后发现颜色不对红色和蓝色互换了原因ST7701 寄存器0x80控制 RGB 接口顺序里面有个RGBR位决定 RGB 还是 BGR。屏厂给的初始化序列里通常默认是 RGB但你的屏硬件走线可能把 R 和 B 反了或者驱动 IC 的输入序与你的数据格式不一致。这不算代码逻辑错误纯粹是硬件匹配问题。解决在初始化序列中找到0x80寄存器把值的 bit3 翻转。比如原来是0x03改成0x0B如果 bit3 是 RGB 顺序位或者反过来。如果不能确定可以先用一个小工具循环切换这个 bit并同时刷纯红、纯绿、纯蓝观察哪一组最正常。5.2 现象初始化后屏幕只有上半部分显示下半部分是雪花或者重复上半内容原因分辨率设置不对。ST7701 的初始化需要通过0x82~0x85、0x90~0x95等寄存器指定显示的列数和行数。比如屏实际是 480x854你按 480x720 初始化那么屏的 RAM 只刷新了 720 行剩下 134 行没有被驱动。另外0x3B寄存器可能设置的是“部分显示模式”但有些初始化序列忘记关闭部分显示也会出现类似问题。解决对照屏的手册确认分辨率寄存器的含义一般是设置显示窗口的起始和结束地址。还有一种情况是扫描顺序反了比如从下往上扫但地址没变。可以把0x83的扫描方向位改一下试试。5.3 现象发送命令时屏幕白屏但发送数据时正常原因这是 3 线 SPI 模拟错误最常见的表现。如果你用的是“8-bit SPI 拼 9-bit 帧”的方法且 CS 在两个字节之间没有保持低电平屏会把第二个字节的错误时钟当成新的一帧最终命令和数据错位。或者你的 SPI 发送顺序是先发了低位导致 DC 位没有出现在正确位置。解决改用 GPIO 一位一位模拟确保每次 CS 拉低后只产生 9 个时钟。并且发送时数据要 MSB-first。如果你非要 HAL SPI检查数据帧格式是否设置为SPI_FIRSTBIT_MSB以及DataSize是否误设成SPI_DATASIZE_9BIT。5.4 现象上电后屏幕偶尔能点亮偶尔白屏复位后恢复正常原因电源上电时序不满足屏的要求。ST7701 一般要求 VCI 和 VDDIO 的上升时间大于 0.5ms且 RESET 必须在电源稳定后至少 10ms 再拉高。很多开发者把 RESET 初始化放在 main 函数最开始但此时电源可能还没稳定或者复位时间太短。另一个常见原因是初始化序列里缺少延时寄存器写入太快屏的内部 LDO 没起来。解决在LCD_ST7701_Init()里先加一个 50ms 的上电等待然后拉低 RESET 至少 20ms再拉高拉高后等待 120ms有些屏要求 120ms 才 ready。初始化序列发送每条命令之间建议加 1ms 延时尤其是在厂商命令区域0xE0系列给内部模拟电路一点稳定时间。5.5 现象用 DMA 发送数据时初始化序列被破坏原因很多人会在初始化完成前就开启 DMA或者初始化函数里调用了HALD_Delay然后 DMA 中断又占用总线。更隐蔽的是 SPI 的TXE标志位处理不当在 DMA 发送过程中又调用HAL_SPI_Transmit导致两个传输竞争数据连续时序被割裂。解决初始化阶段禁止 DMA 和中断发送一切遵循“写完一个寄存器等待 SPI 空闲再写下一个”。初始化完成后再开 DMA并且注意 DMA 配置为SPI_TX通道地址对齐。由于这是 SPI 初始化代码建议保持最简单的中断关闭方式、循环发送即可。6. 二次开发把初始化代码改造成你自己的屏驱动当摸清了这份代码的逻辑后改造起来并不困难。你拿到的初始化序列是针对某一款屏的在自己项目中真正要改的通常有以下几处第一修改st7701_cfg.h里的分辨率宏和像素格式宏。把LCD_WIDTH改为你屏幕真实宽度LCD_HEIGHT改为实际高度LCD_PIXEL_FORMAT设为0x77RGB888还是0x55RGB666。ST7701 支持 18/24 位接口但 SPI 模式下通常用 RGB666 就够因为模拟 SPI 发送不了 24 位一像素的高带宽RGB888 只会浪费初始化复杂度。第二把初始化序列数组改成你屏厂提供的“原厂初始化代码”。很多屏厂会把初始化代码给成 C 文件里面是一堆LCD_WriteCmd(0xE0); LCD_WriteData(0x00);之类的语句。你可以用一个小脚本把这些语句解析成数组或者直接保留原来的顺序调用。我一般会写一个 Python 脚本把文本中的0xE0,0x00,0x93这种序列转成数组比手敲快很多。第三如果你要加入读屏 ID 功能就在初始化之前先发读命令然后读取 MISO 引脚电平。ST7701 有读 ID 命令0x04但很多 3 线 SPI 屏的 MISO 没接出来所以读 ID 往往无效。这时候不要硬读直接靠初始化后清屏颜色验证即可。第四把 GPIO 模拟 SPI 的发送函数优化成带delay_ns参数的版本。比如你的 MCU 主频高GPIO 翻转快有些屏需要最小 SCK 高电平时间否则采样出错。可以通过__NOP()或者空循环微调时序保证 SCK 半周期不小于 100ns。用一个全局宏LCD_SPI_HALF_CLOCK_DELAY控制空循环次数适配不同主频。实际改造时我习惯把st7701.c中所有HAL_Delay(20)替换成基于 SysTick 的微秒延时函数因为初始化某些寄存器时1ms 的延时差异就可能影响 VCOM 电压建立。比如厂商命令区的寄存器写入间隔屏厂给的初始化代码里通常会标注“delay 10ms”。如果直接照搬 HAL_Delay(20) 的固定值可能因为主频不同导致实际延时超标极端情况下会烧屏POC 异常。从那以后我每次移植到新板子都会强制走一遍“时序标注 → 调整延时 → 纯色验证”的流程而不是拿别人工程里的延时无脑复制。// 一个可配置的微秒延时 void LCD_Delay_us(uint32_t us) { // 假设 SystemCoreClock 72MHz每条空循环约 4 个时钟周期 uint32_t ticks us * 18; while (ticks--) { __NOP(); } }这段代码的好处是让延时不依赖 HAL 的 tick初始化时序更可重现。当然如果你的主频是 168MHzticks计算需要调整。我一般会在头文件里留一个注释提醒新用户根据实际SystemCoreClock修改系数。如果你打算把它移植到非 STM32 平台比如 GD32、N32、ESP32 的 Arduino核心逻辑不变把 HAL 函数替换为对应平台的 GPIO 操作把HAL_Delay替换为delay()即可。真正需要保留的是 9-bit 模拟时序和寄存器序列这两部分是平台的“黑匣子”也是项目核心资产。希望这份解析能帮你在下一次点屏时少踩几个坑也希望这些踩坑记录能给你省下几个加班的夜晚祝顺利点亮。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?