STM32驱动1.44寸TFT屏:SPI配置与ST7735初始化全流程

📅 发布时间:2026/10/17 4:32:07
STM32驱动1.44寸TFT屏:SPI配置与ST7735初始化全流程
简介STM32F103RCT6微控制器搭配1.44英寸TFT彩色液晶屏的嵌入式显示工程面向需要学习单片机串行外设接口驱动、彩色屏幕初始化以及图形界面显示的开发者。资源压缩包内共有八十六个文件以C语言源码和头文件为主体同时包含启动文件、工程配置文件、可烧写固件、说明文档以及烧录辅助脚本整个压缩包仅二百八十九KB目录结构清晰可以直接导入Keil工程并运行。资源内提供了底层液晶驱动、图形绘制库、汉字字库、延时控制以及指示灯控制等模块并通过示例程序完整展示了从引脚功能定义、串行外设接口初始化、命令与数据发送到显存缓冲区更新和屏幕刷新显示的全流程。同时附带标准外设库与处理器启动文件便于深入理解寄存器操作与工程启动机制。该项目已吸引四千零五十一人学习下载对电子竞赛、课程设计或入门嵌入式显示开发均有参考价值能够帮助开发者快速跑通屏幕显示并迁移到其他基于该微控制器的应用场景。1. STM32F103RCT6 1.44TFT一块 128×128 小屏点亮背后的完整链路某开发者第一次拿到 1.44 寸 TFT 屏幕时以为接上五根线就能显示结果上电白屏折腾两天才发现是复位时序不对。STM32F103RCT61.44TFT屏幕显示这个组合看起来只是“点一块屏”实际链路从 SPI 总线、ST7735 驱动芯片初始化、坐标窗口管理一直到字模和图片数据的组织每一环都藏着坑。这块屏分辨率 128×128、色彩 16 位 RGB565适合做小体积人机交互终端、实验仪表显示面板或者作为入门 STM32 的第一个外设驱动项目。下面按顺序拆开这份驱动工程引脚连接、SPI 配置、ST7735 核心初始化、画点画线显示文字、常见坑点排障最后落到怎么改成菜单框架。初级开发者可以照着把屏幕点亮有经验的人可以直接拿底层接口去改业务。2. 硬件连接与 SPI 总线配置先让数据真正跑到屏幕上2.1 分清 SPI 屏与串口屏八根线怎么接1.44 寸 TFT 模块上的丝印一般写着 VCC、GND、CS、RESET、DC、SDA、SCL、BL。这块屏内部是 ST7735 驱动四线 SPI 协议DC 脚决定当前字节是命令还是数据跟常见的串口屏完全是两码事。串口屏通常只有 TX、RX 两根通信线内部自带协议解析而 SPI 屏的所有时序都要 MCU 自己控制这也是很多人把“串口屏”的接法套到 SPI 屏上翻车的原因。我常用的接法是走 STM32F103RCT6 的 SPI1 默认引脚PA5 做 SCK、PA7 做 MOSI不接 MISO只写不读。CS 接到 PA4DC 接到 PA1RESET 接到 PA0BL 背光控制接到 PA2 或者直接 3.3V。完整的引脚分配见下表实际焊接前先对一下自己板子的原理图别照抄网上的图。屏幕引脚功能说明STM32F103RCT6 引脚VCC模块电源 3.3V3.3VGND电源地GNDCS片选低电平有效PA4RESET硬件复位低有效PA0DC数据/命令选择PA1SDASPI 数据输入PA7 (MOSI)SCLSPI 时钟PA5 (SCK)BL背光高电平点亮PA2 或直接 3.3V接之前一定要确认模块上有没有电平转换和稳压芯片。老式模块 VCC 标 5V 也能工作但 1.44 寸这些模块多数是纯 3.3V直接怼 5V 会烧屏这是血泪经验。烧了之后屏幕的表现是通电后全屏一个颜色不变背光正常但不显示任何内容这时候只能换屏。还有一点有些模块丝印写的 SDA、SCL 看起来像 I2C其实在 SPI 屏上就是 MOSI 和 SCK别被丝印误导。2.2 使能时钟与 GPIO 初始化用标准库把引脚管起来工程我用 STM32 标准外设库工程模板建好之后第一步是开 GPIOA 和 SPI1 的时钟。GPIO 里要区分两种引脚CS、DC、RESET、BL 是普通推挽输出SCK 和 MOSI 是复用推挽输出。很多新人把所有引脚全配置成 GPIO_Mode_Out_PPSPI 硬件模式下发数据会失败因为 MOSI 和 SCK 必须交给 SPI 外设控制不能再当普通 IO 口用。void LCD_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1, ENABLE); /* CS / DC / RESET / BL 这些控制脚普通推挽即可 */ GPIO_InitStructure.GPIO_Pin GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_4; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); /* SCK / MOSI 复用为 SPI1 */ GPIO_InitStructure.GPIO_Pin GPIO_Pin_5 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); /* 初始状态 */ GPIO_SetBits(GPIOA, GPIO_Pin_0); /* RESET 先拉高 */ GPIO_SetBits(GPIOA, GPIO_Pin_2); /* BL 默认点亮或关闭 */ GPIO_SetBits(GPIOA, GPIO_Pin_4); /* CS 默认拉高不选中 */ }这里把控制脚和 SPI 数据脚分开配置是因为外设模式下 GPIO 必须是复用推挽普通推挽只是“能拉高拉低”但引脚已经不属于 GPIO 外设管理了。RESET 引脚初始化时先拉高后面初始化序列里再按“拉低-延时-拉高”走一遍。CS 默认拉高避免在 SPI 还没配置好时误选中屏幕。2.3 SPI 模式与波特率CPOL 和 CPHA 决定了能不能点亮ST7735 的数据手册里 SPI 时序支持 Mode 0 和 Mode 3绝大多数模块的驱动代码是 Mode 0CPOLLowCPHA1Edge。有个很隐蔽的问题网上有些教程把 CPHA 写成 2Edge屏幕也能亮但采样点落在数据变化沿附近长时间跑会出现偶发闪屏这种问题最难定位因为不是每次上电都复现。void LCD_SPI_Init(void) { SPI_InitTypeDef SPI_InitStructure; SPI_InitStructure.SPI_Direction SPI_Direction_2Lines_FullDuplex; SPI_InitStructure.SPI_Mode SPI_Mode_Master; SPI_InitStructure.SPI_DataSize SPI_DataSize_8b; SPI_InitStructure.SPI_CPOL SPI_CPOL_Low; SPI_InitStructure.SPI_CPHA SPI_CPHA_1Edge; SPI_InitStructure.SPI_NSS SPI_NSS_Soft; SPI_InitStructure.SPI_BaudRatePrescaler SPI_BaudRatePrescaler_8; SPI_InitStructure.SPI_FirstBit SPI_FirstBit_MSB; SPI_Init(SPI1, SPI_InitStructure); SPI_Cmd(SPI1, ENABLE); }SPI_BaudRatePrescaler_8 在 72MHz 主频下得到 9MHz 的 SPI 时钟短杜邦线环境下够用。如果你的板子主频不是 72MHz记得按实际时钟树去算分频后的总线速率。写 SPI 字节的底层函数用轮询方式发送前查 TXE 标志位发送后等 BSY 拉低这是保证一字节完整送出去的常规写法。static void LCD_SPI_WriteByte(uint8_t data) { while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) RESET); SPI_I2S_SendData(SPI1, data); while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY) SET); }写完一个字节等 BSY 清掉再发下一个。这个函数是底层所有显示操作的最终出口后续写命令、写像素数据都会调它。换成 HAL 库时GPIO 用 GPIO_MODE_AF_PP 配置SPI 通过句柄调用 HAL_SPI_TransmitCPOL/CPHA 参数一一对应初始化序列可以直接照搬。3. 核心模块拆解从 ST7735 初始化到第一帧画面3.1 初始化序列代码里那串 0x11、0x36 到底在做什么ST7735 是这块屏幕的“大脑”内部有一组控制寄存器。上电后寄存器处于默认状态显示是关闭的必须通过 SPI 写入一段初始化序列告诉屏幕“像素格式用 RGB565、扫描方向怎么走、开显示”。我摘出最常用的初始化函数void LCD_Init(void) { LCD_GPIO_Init(); LCD_SPI_Init(); /* 硬件复位时序 */ LCD_RESET_Clr(); delay_ms(10); LCD_RESET_Set(); delay_ms(20); LCD_WR_REG(0x11); /* Sleep Out退出睡眠模式 */ delay_ms(120); LCD_WR_REG(0x36); /* MADCTL设置扫描方向和 RGB/BGR */ LCD_WR_DATA(0x60); LCD_WR_REG(0x3A); /* COLMOD像素格式 */ LCD_WR_DATA(0x05); /* 16bit / pixel */ LCD_WR_REG(0x20); /* INVON反色关闭 */ LCD_WR_REG(0x13); /* NORON正常显示模式 */ delay_ms(10); LCD_WR_REG(0x29); /* DISPON打开显示 */ delay_ms(100); }这里省略了伽马校正相关的命令段0xE0、0xE1 那一串因为驱动 IC 出厂时 ROM 里已经烧了默认值直接写核心命令也能正常显示。0x11 退出睡眠后必须延时 120ms 等内部时钟稳定0x29 开显示后再延时 100ms这两个延时是屏幕不发白的两个关键点。0x36 里的数据 0x60 含义是 MX1、MY1按左上角为原点布局如果屏幕上下颠倒就把 0x60 改成 0xA0 或 0xC0 试试这是调屏幕方向最快的入口。3.2 命令与数据通道DC 引脚把路分开ST7735 的 DC 引脚决定当前字节是命令还是数据驱动里封装两个函数来区分void LCD_WR_REG(uint8_t reg) { LCD_DC_Clr(); /* DC0 表示命令 */ LCD_CS_Clr(); LCD_SPI_WriteByte(reg); LCD_CS_Set(); } void LCD_WR_DATA(uint8_t data) { LCD_DC_Set(); /* DC1 表示数据 */ LCD_CS_Clr(); LCD_SPI_WriteByte(data); LCD_CS_Set(); }每次传输前把 CS 拉低、传输完拉高DC 决定走命令通道还是数据通道。注意连续往显存搬数据时CS 可以一直拉低不用来回切换但 DC 必须保持高电平否则屏幕会把像素数据当成命令解析。3.3 坐标窗口与打点把每个像素翻译成 RGB565屏幕显示的第一步是“选点”。ST7735 内部有一块 132×162 的显存但 1.44 寸屏只显示 128×128 的区域所以要先用 CASET列地址和 RASET行地址定义一块窗口再往窗口里填像素数据。窗口设置函数如下void LCD_SetWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) { LCD_WR_REG(0x2A); /* CASET */ LCD_WR_DATA(x0 8); LCD_WR_DATA(x0 0xFF); LCD_WR_DATA(x1 8); LCD_WR_DATA(x1 0xFF); LCD_WR_REG(0x2B); /* RASET */ LCD_WR_DATA(y0 8); LCD_WR_DATA(y0 0xFF); LCD_WR_DATA(y1 8); LCD_WR_DATA(y1 0xFF); LCD_WR_REG(0x2C); /* RAMWR准备写显存 */ }窗口设完之后写入的数据会按行列顺序自动填充。打点函数就是先设一个 1×1 的窗口再写一个 16 位颜色值void LCD_DrawPoint(uint16_t x, uint16_t y, uint16_t color) { if (x LCD_WIDTH || y LCD_HEIGHT) return; LCD_SetWindow(x, y, x, y); LCD_DC_Set(); LCD_CS_Clr(); LCD_SPI_WriteByte(color 8); LCD_SPI_WriteByte(color 0xFF); LCD_CS_Set(); }颜色值是 RGB565 格式高字节在前所以分两次发送。如果整个工程都按低字节在前发屏幕颜色就会整个错位红色显示成蓝色之类这是很典型的翻车点。LCD_WIDTH 和 LCD_HEIGHT 是宏定义固定 128二次开发换屏时只改宏。3.4 画线、画矩形、画圆在打点基础上做加法有了打点函数画实心矩形就是双重循环填充写起来最直接void LCD_FillRect(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t color) { uint16_t i, j; for (i y0; i y1; i) { for (j x0; j x1; j) { LCD_DrawPoint(j, i, color); } } }画圆用中点画圆算法利用八分圆的对称性每算出一个坐标就画 8 个对称点void LCD_DrawCircle(uint16_t xc, uint16_t yc, uint16_t r, uint16_t color) { int x 0, y r, d 1 - r; while (x y) { LCD_DrawPoint(xc x, yc y, color); LCD_DrawPoint(xc - x, yc y, color); LCD_DrawPoint(xc x, yc - y, color); LCD_DrawPoint(xc - x, yc - y, color); LCD_DrawPoint(xc y, yc x, color); LCD_DrawPoint(xc - y, yc x, color); LCD_DrawPoint(xc y, yc - x, color); LCD_DrawPoint(xc - y, yc - x, color); if (d 0) { d 2 * x 3; } else { d 2 * (x - y) 5; y--; } x; } }这几个函数组成了 GUI 层的最小集合。真正的项目里不会在应用层直接调 LCD_WR_REG而是封装成 LCD 驱动库应用层只认 DrawPoint、DrawLine、DrawCircle、ShowString 这几个接口。4. 显示效率与数据组织刷新一屏究竟要多久4.1 算一笔数据账128×128×2 字节的代价1.44 寸屏全屏 128×128RGB565 每个像素 2 字节全屏刷新需要连续写入 32768 字节。SPI 时钟 9MHz一个字节 8 位纯数据搬运时间约 29ms再加上命令和窗口切换开销一屏刷完大概 3540ms实际刷新率 25 帧左右。这也是很多初学者写出全屏循环画点后觉得“屏幕闪得不行”的原因因为 SPI 的带宽就摆在那里。想要提速最直接的办法是连续写。画实心矩形不要一个点一个点调 LCD_DrawPoint而是把窗口设为整个矩形区域然后用循环连续送数据void LCD_FillRectFast(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t color) { uint32_t total (x1 - x0 1) * (y1 - y0 1); uint32_t i; LCD_SetWindow(x0, y0, x1, y1); LCD_DC_Set(); LCD_CS_Clr(); for (i 0; i total; i) { LCD_SPI_WriteByte(color 8); LCD_SPI_WriteByte(color 0xFF); } LCD_CS_Set(); }这样数据流只做一次窗口设置剩下的全是 SPI 连续搬运刷新同样一个矩形区域的时间能缩短一个数量级。驱动层写好连续写接口上层才能做高效渲染。4.2 局部刷新与 DMA小内存下的显示策略很多界面只是更新一个数字或一段进度条没必要整屏重绘。ST7735 的窗口机制天然支持局部刷新比如显示一个温度值只需要把数值所在的矩形窗口 set 出来写入新内容。这样单次刷新的数据量从 32KB 降到几百字节整机响应速度快很多。刷新方式数据量典型耗时适用场景全屏填充颜色32KB约 35ms页面切换、清屏局部窗口刷新8×16 字符256 字节小于 1ms温度、速度等数值实时更新DMA 后台刷新32KBCPU 不阻塞动画演示、波形绘制如果要做动画效果可以用 DMA 搬运。做法是把显示数据放到一个数组里SPI 初始化之后用 SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Tx, ENABLE) 启动 DMA 发送CPU 可以继续跑业务逻辑。但这个方案会吃掉大量 SRAMF103RCT6 的 48KB SRAM 放一帧全屏数据就占了 32KB需要非常克制我一般只在局部窗口小动画时才用。4.3 显示字符串与汉字字模数据怎么组织英文字符可以用取模软件直接取 6×8 或 8×16 的点阵存成 const 数组放 Flash。显示一个字符的代码大致是void LCD_ShowChar(uint16_t x, uint16_t y, char ch, uint16_t color) { uint8_t i, j; const uint8_t *font ASCII8x16[(uint8_t)ch - 32]; for (i 0; i 16; i) { for (j 0; j 8; j) { if (font[i] (0x80 j)) { LCD_DrawPoint(x j, y i, color); } else { LCD_DrawPoint(x j, y i, LCD_BLACK); } } } }这个函数每个点都走打点显示一个字符需要 8×16128 次打点调用速度不算快但足够用。汉字建议用 16×16 点阵F103RCT6 内部 256KB Flash 放常用汉字没问题但如果是几千字的完整字库就要外挂 SPI Flash 或者从 SD 卡读取。取模参数要固定在文件头注释里包括字体、宽高、扫描方式、是否需要反色方便后来者快速对上。5. 避坑指南白屏、花屏、偏色的排查顺序5.1 白屏但背光亮先查复位时序现象上电后屏幕纯白背光亮代码编译下载都没报错逻辑分析仪看到 MOSI 有波形。原因白屏且背光亮说明电源和背光正常问题大概率出在 ST7735 的复位时序或 SPI 通信没建立起来。很多模块的 RESET 引脚如果直接接 VCC内部 RC 复位电路触发不了完整的复位流程寄存器状态混乱。解决让 RESET 先拉低 10ms 再拉高 20ms再进初始化序列LCD_RESET_Clr(); delay_ms(10); LCD_RESET_Set(); delay_ms(20);如果这条解决不了用示波器看 MOSI 上有没有波形。有波形但白屏大概率是初始化序列少了关键命令没波形则是 SPI 没跑起来回头查 GPIO 复用配置和 SPI 时钟使能。5.2 花屏或斜纹SPI 速率和飞线现象屏幕能显示内容但画面带斜向条纹或者刷新时边缘闪烁显示不稳定。原因SPI 时钟太高或者杜邦线飞得太长、太乱导致信号串扰。9MHz 对 ST7735 不是极限但飞线环境下波形会变形数据采样出错就是花屏。解决把 SPI_BaudRatePrescaler 从 8 改成 16时钟降到 4.5MHz 试一下。画面干净就是速率问题再把线剪短、GND 线接好、SCK 和 MOSI 分开走。做原型验证时这种玄学问题最耗时间我一般先降速再查线。5.3 颜色整个不对RGB/BGR 位序和字节序现象红色显示成蓝色或者整个画面颜色像调色盘错位偏冷偏暖明显。原因ST7735 的 0x36 寄存器里 BGR 位控制颜色通道顺序模块不同厂商对这个位的默认值处理不一样。另外 RGB565 发送时高字节在前如果工程里统一发了低字节在前颜色也会错乱。解决初始化函数里把 0x36 的数值在 0x60 和 0x68 之间切换。0x60 是 RGB 顺序0x68 是 BGR 顺序逐个试到颜色正常为准。写像素时强制 high byte first驱动层统一封装不要在应用层临时拼字节。5.4 上电偶尔白屏按一下复位就好电源在捣乱现象上电后随机白屏按复位键可能恢复运行几分钟后偶尔花屏、闪屏。原因电源问题。屏幕模块启动瞬间电流较大如果 MCU 和屏幕共用一个 LDO 且电容不足电压跌落导致 ST7735 初始化失败或运行中掉数据。解决屏幕 VCC 引脚旁边就近并一个 10uF 电解电容加一个 0.1uF 陶瓷电容。背光供电如果条件允许就单独走一路。这类问题排查优先级应该放在 SPI 时序之前不然改代码调几天都没用。5.5 字符缺上半截或偏移取模方式对不上现象显示英文字符时上半截缺失、左右偏移或者显示汉字时出现乱点。原因取模软件里“行扫描”和“列扫描”选错或者高位在前、低位在前配置和解析代码不一致。代码里用 font[i] (0x80 j) 逐位判断就要求取模时也是高位在左、逐行排列。解决取模时固定选择“行扫描高位在前”。为了不再踩同一个坑我把取模参数写进字模文件的注释头字体、宽高、扫描方式、反色标志半年后翻代码也能一眼对上。6. 二次开发实战把固定画面改成按键菜单框架6.1 驱动层与应用层分离拿到这份工程后第一件事是把 LCD_WR_REG、LCD_SetWindow 这些底层函数锁在 lcd_drv.c 里对外只暴露 DrawPoint、DrawLine、FillRect、ShowString、ShowNum 这类接口。应用层代码不直接操作 SPI。这样后续换库、换屏幕比如从 1.44 寸换成 2.0 寸只改驱动层页面代码不用动。我一般把上层显示封装成页面函数比如 Page_Home、Page_Config再用按键触发切换。切换骨架用状态机实现typedef enum { PAGE_HOME, PAGE_CONFIG, PAGE_ABOUT } PageId; void UI_Process(uint8_t key) { static PageId current PAGE_HOME; switch (key) { case KEY_NEXT: if (current PAGE_ABOUT) { current; } else { current PAGE_HOME; } break; case KEY_OK: /* 页面内的确认动作交给当前页面处理 */ break; default: break; } switch (current) { case PAGE_HOME: Page_Home(); break; case PAGE_CONFIG: Page_Config(); break; case PAGE_ABOUT: Page_About(); break; } }按键用 GPIO 外部中断扫描去抖延时 20ms。每次切换重新绘制整个页面页面内部尽量使用局部刷新比如配置页面调整数值时只重绘数字所在窗口不重绘整屏。6.2 换屏和换驱动的预留方案如果想把这块 1.44 寸屏换成 1.8 寸或 2.0 寸屏驱动芯片往往变成 ST7735R 或 ILI9341。底层接口保持 LCD_SetWindow、LCD_DrawPoint 不变只需要改初始化序列、屏幕宽高宏定义。把屏宽高定义在 lcd_conf.h 里上层 GUI 全部引用宏而不直接写 128是每次点屏都必须养成的习惯。我从第一次做 1.44 寸屏翻车之后现在写显示工程会强制走一遍流程确认驱动芯片型号、确认引脚和电压、写初始化序列、跑一个纯色全屏测试、再写字符和图形。这套流程每换一块屏都能省掉不少调试时间希望帮到你。如果你正需要一份能直接编译的 1.44TFT 驱动底子工程里除了上述源码还有完整的可编译工程结构和取模说明。本文还有配套的精品资源点击获取