简介:这份资源面向使用MCU平台驱动小尺寸TFT LCD的嵌入式开发者,聚焦ST7701显示控制器的3线SPI初始化实现。ST7701本身不具备SPI主设备能力,需借助MCU的普通GPIO模拟SPI时序完成命令与数据传输,资源正是围绕这一核心难点展开。压缩包共2个文件,包含1个c源码与1个docx文档,整体约367KB,其中源码给出GPIO配置、时钟设定、初始化序列、写命令与数据、电源与显示配置等关键环节的实现,文档则提供GPIO引脚波形示例,便于对照调试SPI通信时序。目前已有3366人学习下载,适合正在调试ST7701或希望将同类LCD驱动移植到STM32等平台的开发者参考,可帮助快速理解寄存器初始化流程与波形验证思路。1. 拿到一份 ST7701 三线 SPI 初始化代码,先别急着往屏上怼如果你手上有一块 1.47 寸到 2.8 寸之间的小尺寸 TFT 圆屏或方屏,驱动 IC 是 ST7701,排线只有 CS、SCLK、SDA 三根控制线,那这份st7701_3线SPI初始化代码.zip基本就是为你准备的。它解决的不是“显示一张图”这种上层问题,而是最底层、也最容易翻车的一步:MCU 怎么通过三线 SPI 把 ST7701 的寄存器序列喂进去,让屏幕从黑屏变成能正常出图的状态。适合正在用 STM32、GD32 或国产 MCU 点屏的嵌入式开发者,尤其是第一次接触 ST7701 这类需要初始化序列的 RGB/MIPI 屏的人。三线 SPI 不是标准四线 SPI,少了一根 MISO,也没有 DC 引脚,命令和数据靠一个标志位区分,这个细节后面会重点拆。2. 三线 SPI 驱动 ST7701 的架构:命令、数据、时序三件事2.1 三线 SPI 和四线 SPI 的本质区别标准四线 SPI 有 CS、SCLK、MOSI、MISO,屏幕类外设通常还会多一根 DC(数据/命令选择)。三线 SPI 把 MOSI 和 MISO 合并成一根双向 SDA,同时把 DC 的功能塞进数据流的第一个 bit。ST7701 的三线模式里,每个传输单元是 9 bit:第 1 bit 是标志位,0 表示后面 8 bit 是命令,1 表示后面 8 bit 是数据。很多新手直接拿硬件 SPI 的 8 bit 模式去发,结果屏幕毫无反应,就是因为少了这个标志位。常见做法是用 GPIO 模拟三线 SPI,而不是硬 SPI。原因有两个:一是 ST7701 初始化序列对时序要求不算苛刻,GPIO 翻转完全够用;二是硬 SPI 的 8 bit 帧格式不好插入第 9 个标志位,改起来反而麻烦。我一般会在lcd_init.c里写三个底层函数:SPI_WriteByte、LCD_WriteCommand、LCD_WriteData,上层初始化序列只调这两个写函数。// 三线 SPI 底层:GPIO 模拟,CS/SCLK/SDA 由宏定义映射 #define LCD_CS_L() HAL_GPIO_WritePin(LCD_CS_GPIO_Port, LCD_CS_Pin, GPIO_PIN_RESET) #define LCD_CS_H() HAL_GPIO_WritePin(LCD_CS_GPIO_Port, LCD_CS_Pin, GPIO_PIN_SET) #define LCD_SCL_L() HAL_GPIO_WritePin(LCD_SCL_GPIO_Port, LCD_SCL_Pin, GPIO_PIN_RESET) #define LCD_SCL_H() HAL_GPIO_WritePin(LCD_SCL_GPIO_Port, LCD_SCL_Pin, GPIO_PIN_SET) #define LCD_SDA_L() HAL_GPIO_WritePin(LCD_SDA_GPIO_Port, LCD_SDA_Pin, GPIO_PIN_RESET) #define LCD_SDA_H() HAL_GPIO_WritePin(LCD_SDA_GPIO_Port, LCD_SDA_Pin, GPIO_PIN_SET) // 发送一个字节,高位先出,SCLK 上升沿采样 static void SPI_WriteByte(uint8_t dat) { for (uint8_t i = 0; i < 8; i++) { LCD_SCL_L(); if (dat & 0x80) LCD_SDA_H(); else LCD_SDA_L(); dat <<= 1; LCD_SCL_H(); // 上升沿,ST7701 采样 SDA } } // 写命令:标志位 0 + 8bit 命令 void LCD_WriteCommand(uint8_t cmd) { LCD_CS_L(); LCD_SCL_L(); LCD_SDA_L(); // 第 1 bit = 0,表示命令 LCD_SCL_H(); SPI_WriteByte(cmd); LCD_CS_H(); } // 写数据:标志位 1 + 8bit 数据 void LCD_WriteData(uint8_t dat) { LCD_CS_L(); LCD_SCL_L(); LCD_SDA_H(); // 第 1 bit = 1,表示数据 LCD_SCL_H(); SPI_WriteByte(dat); LCD_CS_H(); }这段代码里,SPI_WriteByte只负责 8 bit 数据,标志位在LCD_WriteCommand和LCD_WriteData里单独发。LCD_CS_L()拉低片选后,先发标志位,再发 8 bit 内容,最后拉高 CS。参数dat是你要发送的命令或数据字节,高位先出。注意 SCLK 空闲电平、采样边沿要和 ST7701 数据手册一致,常见是上升沿采样,所以先拉低 SCLK、放好 SDA、再拉高 SCLK。2.2 初始化序列的组织方式ST7701 的初始化序列通常是一长串“命令 + 参数”的组合,比如0xFF切页、0x3A设像素格式、0xB2设 porch 等。原始代码里一般会用一个结构体数组或者两个平行数组来存:命令和对应的参数列表。我见过比较清晰的写法是定义一个LCD_InitCmd结构体,每个条目包含命令、参数个数、参数指针,然后循环发送。typedef struct { uint8_t cmd; uint8_t len; uint8_t para[16]; } LCD_InitCmd; static const LCD_InitCmd st7701_init_seq[] = { {0xFF, 3, {0x77, 0x01, 0x00}}, // 切到 Command2 BK0 {0xEF, 1, {0x08}}, {0xFF, 3, {0x77, 0x01, 0x01}}, // 切到 Command2 BK1 {0xB0, 2, {0x00, 0x11}}, // 正扫/反扫、RGB 顺序 {0xB1, 2, {0x0C, 0x02}}, // 帧率相关 // ... 中间省略几十条 {0x3A, 1, {0x55}}, // 像素格式:RGB565 {0x11, 0, {0}}, // Sleep Out {0x29, 0, {}}, // Display On }; void ST7701_Init(void) { for (uint32_t i = 0; i < sizeof(st7701_init_seq)/sizeof(st7701_init_seq[0]); i++) { LCD_WriteCommand(st7701_init_seq[i].cmd); for (uint8_t j = 0; j < st7701_init_seq[i].len; j++) { LCD_WriteData(st7701_init_seq[i].para[j]); } } }st7701_init_seq里每条记录包含命令字节、参数个数、参数数组。ST7701_Init遍历整个数组,先发命令,再依次发参数。0x11和0x29这种没有参数的命令,len写 0,循环不会发数据。这种组织方式的好处是改初始化参数时只动数组,不动逻辑。参数0x3A的0x55表示 RGB565,如果你用的是 RGB666 或 RGB888,这里要改成0x66或0x77,同时检查 MCU 端 LTDC 或 SPI 屏的像素格式配置是否匹配。2.3 复位和延时:别省那几毫秒ST7701 上电后需要硬件复位或软件复位,然后等至少 120ms 再发初始化命令。很多代码里只写了HAL_Delay(120),但复位引脚如果没接或者没拉,屏幕可能一直处于未知状态。常见做法是:上电后先拉低 RESX 至少 10ms,再拉高,再延时 120ms,然后开始发初始化序列。如果 RESX 直接接在 MCU 的 GPIO 上,记得在main里先初始化这个引脚并输出高电平。void ST7701_Reset(void) { HAL_GPIO_WritePin(LCD_RST_GPIO_Port, LCD_RST_Pin, GPIO_PIN_RESET); HAL_Delay(20); // 低电平保持 20ms,手册要求 >10ms HAL_GPIO_WritePin(LCD_RST_GPIO_Port, LCD_RST_Pin, GPIO_PIN_SET); HAL_Delay(120); // 复位后等待稳定,手册要求 >120ms }HAL_Delay(20)和HAL_Delay(120)这两个值不是随便写的,ST7701 数据手册里复位低电平最小 10ms,复位后到发命令之间最小 120ms。如果你把延时砍到 1ms,可能十次里有三次能亮,剩下七次黑屏,这种玄学问题排查起来非常费时间。我一般会在复位后多等 20ms 余量,宁可慢一点,也不要赌屏幕的个体差异。3. 把代码跑起来:从工程目录到第一个点亮画面3.1 目录结构和文件职责这份压缩包解压后,通常包含以下几个部分:lcd_init.c/lcd_init.h负责 ST7701 初始化序列和底层三线 SPI 读写;lcd.c/lcd.h负责画点、填充、字符显示等上层接口;main.c里调用初始化并做简单测试;可能还有一个delay.c或直接用了 HAL 的HAL_Delay。如果你拿到的工程是 STM32CubeMX 生成的,那 GPIO 和时钟配置在main.c的SystemClock_Config和MX_GPIO_Init里。文件职责你需要改的地方lcd_init.c三线 SPI 底层、ST7701 初始化序列引脚宏定义、初始化参数lcd_init.h引脚宏、函数声明引脚宏定义lcd.c画点、填充、字符分辨率、偏移main.c调用初始化、测试包含头文件、调用顺序常见做法是把引脚宏定义集中放在lcd_init.h顶部,用#define映射到具体的 GPIO 端口和引脚。这样换板子时只改这一个文件,不用满工程搜GPIO_PIN_5。3.2 移植到自己的 MCU 工程如果你用的不是原工程里的 MCU 型号,移植步骤大概是:第一,把lcd_init.c、lcd_init.h、lcd.c、lcd.h四个文件复制到你的工程;第二,在lcd_init.h里把 CS、SCLK、SDA、RST 四个引脚宏改成你实际连接的 GPIO;第三,确保你的工程里有毫秒级延时函数,HAL_Delay或者自己写的delay_ms都行;第四,在main里先初始化 GPIO 时钟和引脚,再调用ST7701_Reset和ST7701_Init。// main.c 里的调用顺序 int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); // 初始化 CS/SCLK/SDA/RST 引脚 ST7701_Reset(); // 硬件复位 ST7701_Init(); // 发初始化序列 LCD_Clear(0x0000); // 清屏为黑色 LCD_ShowString(10, 10, "ST7701 OK", 0xFFFF, 0x0000); while (1) {} }MX_GPIO_Init里要把四个引脚都配成推挽输出,速度至少 Medium。ST7701_Reset和ST7701_Init的调用顺序不能反,先复位再初始化。LCD_Clear和LCD_ShowString是上层接口,用来验证初始化是否成功。如果屏幕亮了但颜色不对,先查0x3A参数和LCD_Clear的颜色格式是否一致。3.3 验证初始化是否成功的三个信号第一个信号:屏幕背光亮但全白或全黑。背光只说明电源和背光电路正常,不代表 ST7701 初始化成功。第二个信号:清屏后屏幕变成你指定的颜色,比如黑色或红色。这说明初始化序列基本走通了,像素格式也匹配。第三个信号:显示字符或色块没有花屏、偏移、颜色反转。如果有偏移,要调 ST7701 的 porch 参数和 MCU 端 LTDC 的时序;如果颜色反转,检查0xB0里的 RGB 顺序位。我一般会先写一个最简单的测试:清屏红色、延时 500ms、清屏绿色、延时 500ms、清屏蓝色。如果三色循环正常,说明初始化没问题,再往上做 UI。如果只有背光没有颜色,优先查三线 SPI 的标志位是不是发反了,以及 CS 是不是一直拉低导致命令错位。4. 避坑与排查:三线 SPI 点 ST7701 最常见的五个翻车点4.1 现象:屏幕完全没反应,背光亮但无图像原因:三线 SPI 的标志位发错,或者 CS 时序不对。很多人把LCD_WriteCommand里的标志位写成 1,或者忘了在发标志位之前拉低 CS。解决:用逻辑分析仪抓 SDA 和 SCLK,确认每个 9 bit 单元的第一个 bit 是 0 还是 1。没有逻辑分析仪的话,把LCD_WriteCommand和LCD_WriteData里的标志位对调试一次,如果屏幕有反应,说明原来发反了。4.2 现象:初始化序列发到一半卡死或屏幕闪烁原因:延时不够。ST7701 某些命令之间需要 1ms 到 10ms 的等待,比如 Sleep Out 之后要等 120ms 才能发 Display On。原始代码里如果只在开头有一个大延时,中间命令之间没有小延时,屏幕可能闪烁或者直接不亮。解决:在0x11之后加HAL_Delay(120),在0x29之前加HAL_Delay(20)。其他命令之间如果手册没要求,可以不加,但加了不会错。4.3 现象:颜色不对,红色显示成蓝色原因:RGB 顺序配置错误。ST7701 的0xB0命令里有一位控制 RGB 还是 BGR,MCU 端 LTDC 或 SPI 屏的像素格式也要对应。解决:先确认0x3A是 RGB565 还是 RGB666,再查0xB0的参数。如果用的是 RGB565,0x3A写0x55;如果颜色通道反了,把0xB0里的 RGB/BGR 位翻转。4.4 现象:屏幕有图像但偏移,右边或下边有黑边原因:porch 参数和分辨率不匹配。ST7701 的0xB2到0xB5控制前后 porch 和行列数,如果这些值和屏幕实际分辨率对不上,图像就会偏移。解决:找屏幕厂家的初始化代码,对照0xB2到0xB5的参数逐条改。不要自己猜,porch 参数猜错会导致图像撕裂或偏移。4.5 现象:换了一块同型号屏,原来的代码不亮了原因:ST7701 的初始化序列和屏幕玻璃、排线、FPC 走线都有关系,不同批次的屏可能用不同的初始化参数。解决:不要假设同型号屏的初始化序列完全一样。换屏时先拿新屏厂家给的初始化代码,和旧代码逐条对比,重点看0xFF切页命令、0xB0、0xB1、0xB2这几条。如果厂家没给,用旧代码试,不亮再调延时和 porch。5. 二次开发:把初始化代码改成你自己的点屏工具5.1 把初始化序列参数化原始代码里的st7701_init_seq是写死的,换屏就要改数组。我一般会把它改成从外部传入的指针,或者用宏开关切换不同屏幕的配置。比如定义LCD_PANEL_A和LCD_PANEL_B两套数组,编译时选一套。这样同一份底层驱动可以支持多块屏,不用复制整个工程。#ifdef LCD_PANEL_A static const LCD_InitCmd *panel_seq = st7701_panel_a_seq; static const uint32_t panel_seq_len = sizeof(st7701_panel_a_seq)/sizeof(LCD_InitCmd); #elif defined(LCD_PANEL_B) static const LCD_InitCmd *panel_seq = st7701_panel_b_seq; static const uint32_t panel_seq_len = sizeof(st7701_panel_b_seq)/sizeof(LCD_InitCmd); #endif void ST7701_Init(void) { for (uint32_t i = 0; i < panel_seq_len; i++) { LCD_WriteCommand(panel_seq[i].cmd); for (uint8_t j = 0; j < panel_seq[i].len; j++) { LCD_WriteData(panel_seq[i].para[j]); } } }panel_seq和panel_seq_len由编译宏决定,ST7701_Init只负责遍历。这样加一块新屏只需要新增一个数组和一组宏,不用动初始化逻辑。参数panel_seq_len用sizeof计算,避免手写长度出错。5.2 加一个初始化自检初始化完成后,可以读回 ST7701 的某个寄存器来确认通信正常。但三线 SPI 没有 MISO,读寄存器需要把 SDA 切成输入模式,时序比较复杂。更简单的自检是:初始化后清屏为已知颜色,然后用 MCU 的 GPIO 读屏幕的 TE 信号(如果有引出),或者直接看屏幕是否出图。我一般会在ST7701_Init最后加一个LCD_Clear(0xF800)红色清屏,如果屏幕变红,说明初始化成功;如果不变,在LCD_Clear后面加断点,看代码有没有跑到这里。5.3 从初始化代码延伸到完整显示驱动这份代码只解决了“点亮”问题,二次开发通常还要加:画点、画线、填充矩形、显示字符、显示图片。这些上层接口依赖一个LCD_SetWindow函数,用来设置 ST7701 的显示区域。LCD_SetWindow本身也是通过三线 SPI 发命令和参数实现的,命令是0x2A(列地址)和0x2B(行地址),然后是0x2C写显存。如果你要移植 LVGL 或自己写 UI,先把LCD_SetWindow和LCD_WriteData的批量写做好,否则刷屏速度会很慢。void LCD_SetWindow(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) { LCD_WriteCommand(0x2A); // 列地址设置 LCD_WriteData(x1 >> 8); LCD_WriteData(x1 & 0xFF); LCD_WriteData(x2 >> 8); LCD_WriteData(x2 & 0xFF); LCD_WriteCommand(0x2B); // 行地址设置 LCD_WriteData(y1 >> 8); LCD_WriteData(y1 & 0xFF); LCD_WriteData(y2 >> 8); LCD_WriteData(y2 & 0xFF); LCD_WriteCommand(0x2C); // 写显存 }LCD_SetWindow的参数是矩形区域的左上角和右下角坐标。0x2A和0x2B各发四个字节,分别是起始和结束地址的高八位、低八位。0x2C之后就可以连续发像素数据。注意 ST7701 的显存地址可能和屏幕物理方向有偏移,如果画出来的图像整体偏移,在x1、y1上加一个偏移量即可。5.4 一个我踩过的坑:批量写数据时 CS 不能频繁拉高三线 SPI 的LCD_WriteData每次都会拉低 CS、发 9 bit、拉高 CS。如果刷一张 240x320 的图,要调用 76800 次LCD_WriteData,每次都有 CS 翻转,速度会非常慢。优化方法是在LCD_SetWindow之后、批量写像素之前,把 CS 一直拉低,然后连续发数据,最后再拉高。这样 CS 只翻转一次,刷屏速度能提升好几倍。我当初没注意这一点,刷一张全屏图要几百毫秒,后来把 CS 控制从LCD_WriteData里拆出来,速度直接降到几十毫秒。从那以后我每次写三线 SPI 的批量传输,都会先确认 CS 是不是可以常拉低,以及 ST7701 是否支持连续写。希望这份初始化代码能帮你少走点弯路,先把屏点亮,再慢慢调上层。本文还有配套的精品资源,点击获取
简介:这份资源面向使用MCU平台驱动小尺寸TFT LCD的嵌入式开发者,聚焦ST7701显示控制器的3线SPI初始化实现。ST7701本身不具备SPI主设备能力,需借助MCU的普通GPIO模拟SPI时序完成命令与数据传输,资源正是围绕这一核心难点展开。压缩包共2个文件,包含1个c源码与1个docx文档,整体约367KB,其中源码给出GPIO配置、时钟设定、初始化序列、写命令与数据、电源与显示配置等关键环节的实现,文档则提供GPIO引脚波形示例,便于对照调试SPI通信时序。目前已有3366人学习下载,适合正在调试ST7701或希望将同类LCD驱动移植到STM32等平台的开发者参考,可帮助快速理解寄存器初始化流程与波形验证思路。1. 拿到一份 ST7701 三线 SPI 初始化代码,先别急着往屏上怼如果你手上有一块 1.47 寸到 2.8 寸之间的小尺寸 TFT 圆屏或方屏,驱动 IC 是 ST7701,排线只有 CS、SCLK、SDA 三根控制线,那这份st7701_3线SPI初始化代码.zip基本就是为你准备的。它解决的不是“显示一张图”这种上层问题,而是最底层、也最容易翻车的一步:MCU 怎么通过三线 SPI 把 ST7701 的寄存器序列喂进去,让屏幕从黑屏变成能正常出图的状态。适合正在用 STM32、GD32 或国产 MCU 点屏的嵌入式开发者,尤其是第一次接触 ST7701 这类需要初始化序列的 RGB/MIPI 屏的人。三线 SPI 不是标准四线 SPI,少了一根 MISO,也没有 DC 引脚,命令和数据靠一个标志位区分,这个细节后面会重点拆。2. 三线 SPI 驱动 ST7701 的架构:命令、数据、时序三件事2.1 三线 SPI 和四线 SPI 的本质区别标准四线 SPI 有 CS、SCLK、MOSI、MISO,屏幕类外设通常还会多一根 DC(数据/命令选择)。三线 SPI 把 MOSI 和 MISO 合并成一根双向 SDA,同时把 DC 的功能塞进数据流的第一个 bit。ST7701 的三线模式里,每个传输单元是 9 bit:第 1 bit 是标志位,0 表示后面 8 bit 是命令,1 表示后面 8 bit 是数据。很多新手直接拿硬件 SPI 的 8 bit 模式去发,结果屏幕毫无反应,就是因为少了这个标志位。常见做法是用 GPIO 模拟三线 SPI,而不是硬 SPI。原因有两个:一是 ST7701 初始化序列对时序要求不算苛刻,GPIO 翻转完全够用;二是硬 SPI 的 8 bit 帧格式不好插入第 9 个标志位,改起来反而麻烦。我一般会在lcd_init.c里写三个底层函数:SPI_WriteByte、LCD_WriteCommand、LCD_WriteData,上层初始化序列只调这两个写函数。// 三线 SPI 底层:GPIO 模拟,CS/SCLK/SDA 由宏定义映射 #define LCD_CS_L() HAL_GPIO_WritePin(LCD_CS_GPIO_Port, LCD_CS_Pin, GPIO_PIN_RESET) #define LCD_CS_H() HAL_GPIO_WritePin(LCD_CS_GPIO_Port, LCD_CS_Pin, GPIO_PIN_SET) #define LCD_SCL_L() HAL_GPIO_WritePin(LCD_SCL_GPIO_Port, LCD_SCL_Pin, GPIO_PIN_RESET) #define LCD_SCL_H() HAL_GPIO_WritePin(LCD_SCL_GPIO_Port, LCD_SCL_Pin, GPIO_PIN_SET) #define LCD_SDA_L() HAL_GPIO_WritePin(LCD_SDA_GPIO_Port, LCD_SDA_Pin, GPIO_PIN_RESET) #define LCD_SDA_H() HAL_GPIO_WritePin(LCD_SDA_GPIO_Port, LCD_SDA_Pin, GPIO_PIN_SET) // 发送一个字节,高位先出,SCLK 上升沿采样 static void SPI_WriteByte(uint8_t dat) { for (uint8_t i = 0; i < 8; i++) { LCD_SCL_L(); if (dat & 0x80) LCD_SDA_H(); else LCD_SDA_L(); dat <<= 1; LCD_SCL_H(); // 上升沿,ST7701 采样 SDA } } // 写命令:标志位 0 + 8bit 命令 void LCD_WriteCommand(uint8_t cmd) { LCD_CS_L(); LCD_SCL_L(); LCD_SDA_L(); // 第 1 bit = 0,表示命令 LCD_SCL_H(); SPI_WriteByte(cmd); LCD_CS_H(); } // 写数据:标志位 1 + 8bit 数据 void LCD_WriteData(uint8_t dat) { LCD_CS_L(); LCD_SCL_L(); LCD_SDA_H(); // 第 1 bit = 1,表示数据 LCD_SCL_H(); SPI_WriteByte(dat); LCD_CS_H(); }这段代码里,SPI_WriteByte只负责 8 bit 数据,标志位在LCD_WriteCommand和LCD_WriteData里单独发。LCD_CS_L()拉低片选后,先发标志位,再发 8 bit 内容,最后拉高 CS。参数dat是你要发送的命令或数据字节,高位先出。注意 SCLK 空闲电平、采样边沿要和 ST7701 数据手册一致,常见是上升沿采样,所以先拉低 SCLK、放好 SDA、再拉高 SCLK。2.2 初始化序列的组织方式ST7701 的初始化序列通常是一长串“命令 + 参数”的组合,比如0xFF切页、0x3A设像素格式、0xB2设 porch 等。原始代码里一般会用一个结构体数组或者两个平行数组来存:命令和对应的参数列表。我见过比较清晰的写法是定义一个LCD_InitCmd结构体,每个条目包含命令、参数个数、参数指针,然后循环发送。typedef struct { uint8_t cmd; uint8_t len; uint8_t para[16]; } LCD_InitCmd; static const LCD_InitCmd st7701_init_seq[] = { {0xFF, 3, {0x77, 0x01, 0x00}}, // 切到 Command2 BK0 {0xEF, 1, {0x08}}, {0xFF, 3, {0x77, 0x01, 0x01}}, // 切到 Command2 BK1 {0xB0, 2, {0x00, 0x11}}, // 正扫/反扫、RGB 顺序 {0xB1, 2, {0x0C, 0x02}}, // 帧率相关 // ... 中间省略几十条 {0x3A, 1, {0x55}}, // 像素格式:RGB565 {0x11, 0, {0}}, // Sleep Out {0x29, 0, {}}, // Display On }; void ST7701_Init(void) { for (uint32_t i = 0; i < sizeof(st7701_init_seq)/sizeof(st7701_init_seq[0]); i++) { LCD_WriteCommand(st7701_init_seq[i].cmd); for (uint8_t j = 0; j < st7701_init_seq[i].len; j++) { LCD_WriteData(st7701_init_seq[i].para[j]); } } }st7701_init_seq里每条记录包含命令字节、参数个数、参数数组。ST7701_Init遍历整个数组,先发命令,再依次发参数。0x11和0x29这种没有参数的命令,len写 0,循环不会发数据。这种组织方式的好处是改初始化参数时只动数组,不动逻辑。参数0x3A的0x55表示 RGB565,如果你用的是 RGB666 或 RGB888,这里要改成0x66或0x77,同时检查 MCU 端 LTDC 或 SPI 屏的像素格式配置是否匹配。2.3 复位和延时:别省那几毫秒ST7701 上电后需要硬件复位或软件复位,然后等至少 120ms 再发初始化命令。很多代码里只写了HAL_Delay(120),但复位引脚如果没接或者没拉,屏幕可能一直处于未知状态。常见做法是:上电后先拉低 RESX 至少 10ms,再拉高,再延时 120ms,然后开始发初始化序列。如果 RESX 直接接在 MCU 的 GPIO 上,记得在main里先初始化这个引脚并输出高电平。void ST7701_Reset(void) { HAL_GPIO_WritePin(LCD_RST_GPIO_Port, LCD_RST_Pin, GPIO_PIN_RESET); HAL_Delay(20); // 低电平保持 20ms,手册要求 >10ms HAL_GPIO_WritePin(LCD_RST_GPIO_Port, LCD_RST_Pin, GPIO_PIN_SET); HAL_Delay(120); // 复位后等待稳定,手册要求 >120ms }HAL_Delay(20)和HAL_Delay(120)这两个值不是随便写的,ST7701 数据手册里复位低电平最小 10ms,复位后到发命令之间最小 120ms。如果你把延时砍到 1ms,可能十次里有三次能亮,剩下七次黑屏,这种玄学问题排查起来非常费时间。我一般会在复位后多等 20ms 余量,宁可慢一点,也不要赌屏幕的个体差异。3. 把代码跑起来:从工程目录到第一个点亮画面3.1 目录结构和文件职责这份压缩包解压后,通常包含以下几个部分:lcd_init.c/lcd_init.h负责 ST7701 初始化序列和底层三线 SPI 读写;lcd.c/lcd.h负责画点、填充、字符显示等上层接口;main.c里调用初始化并做简单测试;可能还有一个delay.c或直接用了 HAL 的HAL_Delay。如果你拿到的工程是 STM32CubeMX 生成的,那 GPIO 和时钟配置在main.c的SystemClock_Config和MX_GPIO_Init里。文件职责你需要改的地方lcd_init.c三线 SPI 底层、ST7701 初始化序列引脚宏定义、初始化参数lcd_init.h引脚宏、函数声明引脚宏定义lcd.c画点、填充、字符分辨率、偏移main.c调用初始化、测试包含头文件、调用顺序常见做法是把引脚宏定义集中放在lcd_init.h顶部,用#define映射到具体的 GPIO 端口和引脚。这样换板子时只改这一个文件,不用满工程搜GPIO_PIN_5。3.2 移植到自己的 MCU 工程如果你用的不是原工程里的 MCU 型号,移植步骤大概是:第一,把lcd_init.c、lcd_init.h、lcd.c、lcd.h四个文件复制到你的工程;第二,在lcd_init.h里把 CS、SCLK、SDA、RST 四个引脚宏改成你实际连接的 GPIO;第三,确保你的工程里有毫秒级延时函数,HAL_Delay或者自己写的delay_ms都行;第四,在main里先初始化 GPIO 时钟和引脚,再调用ST7701_Reset和ST7701_Init。// main.c 里的调用顺序 int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); // 初始化 CS/SCLK/SDA/RST 引脚 ST7701_Reset(); // 硬件复位 ST7701_Init(); // 发初始化序列 LCD_Clear(0x0000); // 清屏为黑色 LCD_ShowString(10, 10, "ST7701 OK", 0xFFFF, 0x0000); while (1) {} }MX_GPIO_Init里要把四个引脚都配成推挽输出,速度至少 Medium。ST7701_Reset和ST7701_Init的调用顺序不能反,先复位再初始化。LCD_Clear和LCD_ShowString是上层接口,用来验证初始化是否成功。如果屏幕亮了但颜色不对,先查0x3A参数和LCD_Clear的颜色格式是否一致。3.3 验证初始化是否成功的三个信号第一个信号:屏幕背光亮但全白或全黑。背光只说明电源和背光电路正常,不代表 ST7701 初始化成功。第二个信号:清屏后屏幕变成你指定的颜色,比如黑色或红色。这说明初始化序列基本走通了,像素格式也匹配。第三个信号:显示字符或色块没有花屏、偏移、颜色反转。如果有偏移,要调 ST7701 的 porch 参数和 MCU 端 LTDC 的时序;如果颜色反转,检查0xB0里的 RGB 顺序位。我一般会先写一个最简单的测试:清屏红色、延时 500ms、清屏绿色、延时 500ms、清屏蓝色。如果三色循环正常,说明初始化没问题,再往上做 UI。如果只有背光没有颜色,优先查三线 SPI 的标志位是不是发反了,以及 CS 是不是一直拉低导致命令错位。4. 避坑与排查:三线 SPI 点 ST7701 最常见的五个翻车点4.1 现象:屏幕完全没反应,背光亮但无图像原因:三线 SPI 的标志位发错,或者 CS 时序不对。很多人把LCD_WriteCommand里的标志位写成 1,或者忘了在发标志位之前拉低 CS。解决:用逻辑分析仪抓 SDA 和 SCLK,确认每个 9 bit 单元的第一个 bit 是 0 还是 1。没有逻辑分析仪的话,把LCD_WriteCommand和LCD_WriteData里的标志位对调试一次,如果屏幕有反应,说明原来发反了。4.2 现象:初始化序列发到一半卡死或屏幕闪烁原因:延时不够。ST7701 某些命令之间需要 1ms 到 10ms 的等待,比如 Sleep Out 之后要等 120ms 才能发 Display On。原始代码里如果只在开头有一个大延时,中间命令之间没有小延时,屏幕可能闪烁或者直接不亮。解决:在0x11之后加HAL_Delay(120),在0x29之前加HAL_Delay(20)。其他命令之间如果手册没要求,可以不加,但加了不会错。4.3 现象:颜色不对,红色显示成蓝色原因:RGB 顺序配置错误。ST7701 的0xB0命令里有一位控制 RGB 还是 BGR,MCU 端 LTDC 或 SPI 屏的像素格式也要对应。解决:先确认0x3A是 RGB565 还是 RGB666,再查0xB0的参数。如果用的是 RGB565,0x3A写0x55;如果颜色通道反了,把0xB0里的 RGB/BGR 位翻转。4.4 现象:屏幕有图像但偏移,右边或下边有黑边原因:porch 参数和分辨率不匹配。ST7701 的0xB2到0xB5控制前后 porch 和行列数,如果这些值和屏幕实际分辨率对不上,图像就会偏移。解决:找屏幕厂家的初始化代码,对照0xB2到0xB5的参数逐条改。不要自己猜,porch 参数猜错会导致图像撕裂或偏移。4.5 现象:换了一块同型号屏,原来的代码不亮了原因:ST7701 的初始化序列和屏幕玻璃、排线、FPC 走线都有关系,不同批次的屏可能用不同的初始化参数。解决:不要假设同型号屏的初始化序列完全一样。换屏时先拿新屏厂家给的初始化代码,和旧代码逐条对比,重点看0xFF切页命令、0xB0、0xB1、0xB2这几条。如果厂家没给,用旧代码试,不亮再调延时和 porch。5. 二次开发:把初始化代码改成你自己的点屏工具5.1 把初始化序列参数化原始代码里的st7701_init_seq是写死的,换屏就要改数组。我一般会把它改成从外部传入的指针,或者用宏开关切换不同屏幕的配置。比如定义LCD_PANEL_A和LCD_PANEL_B两套数组,编译时选一套。这样同一份底层驱动可以支持多块屏,不用复制整个工程。#ifdef LCD_PANEL_A static const LCD_InitCmd *panel_seq = st7701_panel_a_seq; static const uint32_t panel_seq_len = sizeof(st7701_panel_a_seq)/sizeof(LCD_InitCmd); #elif defined(LCD_PANEL_B) static const LCD_InitCmd *panel_seq = st7701_panel_b_seq; static const uint32_t panel_seq_len = sizeof(st7701_panel_b_seq)/sizeof(LCD_InitCmd); #endif void ST7701_Init(void) { for (uint32_t i = 0; i < panel_seq_len; i++) { LCD_WriteCommand(panel_seq[i].cmd); for (uint8_t j = 0; j < panel_seq[i].len; j++) { LCD_WriteData(panel_seq[i].para[j]); } } }panel_seq和panel_seq_len由编译宏决定,ST7701_Init只负责遍历。这样加一块新屏只需要新增一个数组和一组宏,不用动初始化逻辑。参数panel_seq_len用sizeof计算,避免手写长度出错。5.2 加一个初始化自检初始化完成后,可以读回 ST7701 的某个寄存器来确认通信正常。但三线 SPI 没有 MISO,读寄存器需要把 SDA 切成输入模式,时序比较复杂。更简单的自检是:初始化后清屏为已知颜色,然后用 MCU 的 GPIO 读屏幕的 TE 信号(如果有引出),或者直接看屏幕是否出图。我一般会在ST7701_Init最后加一个LCD_Clear(0xF800)红色清屏,如果屏幕变红,说明初始化成功;如果不变,在LCD_Clear后面加断点,看代码有没有跑到这里。5.3 从初始化代码延伸到完整显示驱动这份代码只解决了“点亮”问题,二次开发通常还要加:画点、画线、填充矩形、显示字符、显示图片。这些上层接口依赖一个LCD_SetWindow函数,用来设置 ST7701 的显示区域。LCD_SetWindow本身也是通过三线 SPI 发命令和参数实现的,命令是0x2A(列地址)和0x2B(行地址),然后是0x2C写显存。如果你要移植 LVGL 或自己写 UI,先把LCD_SetWindow和LCD_WriteData的批量写做好,否则刷屏速度会很慢。void LCD_SetWindow(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) { LCD_WriteCommand(0x2A); // 列地址设置 LCD_WriteData(x1 >> 8); LCD_WriteData(x1 & 0xFF); LCD_WriteData(x2 >> 8); LCD_WriteData(x2 & 0xFF); LCD_WriteCommand(0x2B); // 行地址设置 LCD_WriteData(y1 >> 8); LCD_WriteData(y1 & 0xFF); LCD_WriteData(y2 >> 8); LCD_WriteData(y2 & 0xFF); LCD_WriteCommand(0x2C); // 写显存 }LCD_SetWindow的参数是矩形区域的左上角和右下角坐标。0x2A和0x2B各发四个字节,分别是起始和结束地址的高八位、低八位。0x2C之后就可以连续发像素数据。注意 ST7701 的显存地址可能和屏幕物理方向有偏移,如果画出来的图像整体偏移,在x1、y1上加一个偏移量即可。5.4 一个我踩过的坑:批量写数据时 CS 不能频繁拉高三线 SPI 的LCD_WriteData每次都会拉低 CS、发 9 bit、拉高 CS。如果刷一张 240x320 的图,要调用 76800 次LCD_WriteData,每次都有 CS 翻转,速度会非常慢。优化方法是在LCD_SetWindow之后、批量写像素之前,把 CS 一直拉低,然后连续发数据,最后再拉高。这样 CS 只翻转一次,刷屏速度能提升好几倍。我当初没注意这一点,刷一张全屏图要几百毫秒,后来把 CS 控制从LCD_WriteData里拆出来,速度直接降到几十毫秒。从那以后我每次写三线 SPI 的批量传输,都会先确认 CS 是不是可以常拉低,以及 ST7701 是否支持连续写。希望这份初始化代码能帮你少走点弯路,先把屏点亮,再慢慢调上层。本文还有配套的精品资源,点击获取