资讯动态

STM32F407ZET驱动1.8寸TFT_LCD:ST7735S初始化与SPI调试避坑指南

发布时间:2026/10/13 16:02:27 来源:尧图企业网站定制
简介面向STM32F407嵌入式开发者的1.8寸TFT液晶屏驱动工程围绕Cortex-M4内核与串行通信接口解决屏幕初始化、显示数据写入及画面刷新等关键问题适合入门至中级嵌入式学习者快速上手显示驱动开发。整套资源共186个文件压缩包仅5.72MB以C语言源文件与头文件为主并包含Keil工程文件、链接脚本、可执行映像及地址映射文件可支持直接编译查看代码或烧录到开发板运行其中头文件声明外设接口C源文件实现驱动逻辑hex文件可独立烧录验证。工程还集成了定时器、模数转换、时钟控制、CAN总线等标准外设驱动既可辅助液晶屏驱动开发也可作为STM32F4外设编程的参考范例。已有815人学习对照源码可深入理解底层通信时序、寄存器配置和显示控制逻辑便于迁移到同类型小尺寸液晶屏项目并自行修改扩展。1. 为什么STM32F407ZET驱动1.8寸TFT_LCD总会在初始化上卡住先分清屏、驱动IC和接线接手一块1.8寸TFT_LCD模组用STM32F407ZET驱动很多人第一反应是查例程、找引脚配置文件结果经常是白屏、花屏、颜色错乱。问题往往不在主控而在你拿到的是哪一种面板、配的是哪一颗驱动IC、上电时序对不对。这块屏绝大多数是SPI接口、128×160分辨率、内置ST7735S或类似驱动芯片的模组少数是并行8080接口。我在这篇文章里会按实际调试流程把从引脚决策、初始化序列、模拟SPI与硬件SPI切换到白屏和花屏的几个典型坑完整走一遍。适合刚把STM32F407ZET跑起来、准备点亮第一块彩屏的开发者也适合想从模拟SPI换硬件SPI提升刷新率的从业者。2. 驱动原理与引脚决策SPI时序、寄存器与你的第一种接线方案2.1 先看屏的驱动ICST7735S/ST7735R 的寄存器链路1.8寸TFT_LCD模组背后一般有一颗驱动IC最常见的是ST7735S或ST7735R少数是ILI9163。屏是RGB565格式分辨率128×160。你不知道驱动IC的情况下先看模组丝印、卖家提供的规格书或者直接查屏ID寄存器。ST7735S的ID通常是0x89ILI9163的ID是0x01。读取方法是在初始化完成或者复位后用串口把RDDID指令发出去再读取MISO上的数据。如果你用的是模拟SPI需要把MISO引脚也配上输入模式否则读不出来。上电时序上LCD模组有明确要求先供VCC等待至少10ms然后拉高背光LEDK/LEDA再对RST引脚产生一个低电平脉冲脉冲宽度不少于10us之后释放RST等待120ms再开始发送初始化命令。很多时候白屏是因为MCU和屏同时上电RST时序靠代码控制但又在供电稳定前就执行了。一个实用的做法是在主程序入口延时200ms之后再碰LCD的任何引脚。2.2 用模拟 SPI 还是硬件 SPISTM32F407ZET 的引脚规划STM32F407ZET有多个SPI外设SPI1、SPI2、SPI3都可用。硬件SPI速度高用DMA更省CPU缺点是引脚复用配置麻烦而且在高速模式下对SCK、MOSI的布线长度更敏感。模拟SPI则简单任意GPIO都能承担SCK、MOSI、CS、DC、RST、BL缺点是刷新一屏数据时CPU占用大大约只有2到4MHz等效速率。我的选择原则是先拿模拟SPI调通显示逻辑确认初始化序列和坐标计算没问题再切换到硬件SPI提速。这样能把变量拆开不会在硬件SPI配置和初始化序列同时出错时无从下手。具体接线参考如下STM32F407ZET的板子上我一般用SPI1所在的PA5-PA7这样方便后续做DMA。当然模拟SPI阶段用任意GPIO都可以只要你能在代码里改掉。屏端引脚功能说明推荐STM32F407ZET引脚备注VCC3.3V电源3.3V不要接5V逻辑电平不匹配GND地GND与MCU共地CS片选低有效PB12模拟SPI可任选硬件SPI建议固定DC数据/命令选择PA2低电平写命令高电平写数据RST复位低有效PB0上电后拉低再拉高BL背光控制PB1简单接3.3V也行想控制亮度就PWMSCKSPI时钟PA5SPI1_SCKMOSI主出从入PA7SPI1_MOSIMISO主入从出PA6读ID时用到不读可省2.3 最小驱动代码时序函数与写命令/写数据分离把时序函数拆成最底层是后续所有显示功能的地基。下面这段是基于模拟SPI的最小实现跑通了再往上加功能// 模拟SPI引脚定义 #define LCD_CS_H GPIO_SetBits(GPIOB, GPIO_Pin_12) #define LCD_CS_L GPIO_ResetBits(GPIOB, GPIO_Pin_12) #define LCD_DC_H GPIO_SetBits(GPIOA, GPIO_Pin_2) #define LCD_DC_L GPIO_ResetBits(GPIOA, GPIO_Pin_2) #define LCD_RST_H GPIO_SetBits(GPIOB, GPIO_Pin_0) #define LCD_RST_L GPIO_ResetBits(GPIOB, GPIO_Pin_0) #define LCD_SCK_H GPIO_SetBits(GPIOA, GPIO_Pin_5) #define LCD_SCK_L GPIO_ResetBits(GPIOA, GPIO_Pin_5) #define LCD_MOSI_H GPIO_SetBits(GPIOA, GPIO_Pin_7) #define LCD_MOSI_L GPIO_ResetBits(GPIOA, GPIO_Pin_7) // 模拟SPI发送一字节MSB先行 static void LCD_SPI_SendByte(uint8_t dat) { for (uint8_t i 0; i 8; i) { if (dat 0x80) { LCD_MOSI_H; } else { LCD_MOSI_L; } LCD_SCK_H; LCD_SCK_L; dat 1; } } // 写命令DC低电平 static void LCD_WriteCmd(uint8_t cmd) { LCD_CS_L; LCD_DC_L; LCD_SPI_SendByte(cmd); LCD_CS_H; } // 写数据DC高电平 static void LCD_WriteData(uint8_t dat) { LCD_CS_L; LCD_DC_H; LCD_SPI_SendByte(dat); LCD_CS_H; }这里的逻辑顺序是先拉低CS选中屏端再按DC引脚区分命令或数据发送完成后拉高CS。模拟SPI的SCK空闲电平是低所以SCK拉高再拉低就是一个时钟周期。速度上每个字节8个循环换算到72MHz主频时大约实际速率在3MHz左右足够点亮屏幕做菜单等简单界面不卡。3. STM32F407ZET 初始化序列与填充函数从白屏到画出第一行像素3.1 ST7735S 初始化寄存器序列ST7735S的初始化依赖一段固定序列命令顺序不能乱。少了SLPOUT后会直接白屏MadCtl设置错误会导致横竖屏坐标错乱。我实际调通一段快速初始化序列是这样的void LCD_Init(void) { // 上电稳定 delay_ms(200); LCD_RST_L; delay_ms(50); LCD_RST_H; delay_ms(120); LCD_WriteCmd(0x01); // SWRESET 软件复位 delay_ms(150); LCD_WriteCmd(0x11); // SLPOUT 退出睡眠 delay_ms(100); LCD_WriteCmd(0xB1); // FRMCTR1 帧率控制 LCD_WriteData(0x01); LCD_WriteData(0x2C); LCD_WriteData(0x2D); LCD_WriteCmd(0xB2); // FRMCTR2 LCD_WriteData(0x01); LCD_WriteData(0x2C); LCD_WriteData(0x2D); LCD_WriteCmd(0xB3); // FRMCTR3 LCD_WriteData(0x01); LCD_WriteData(0x2C); LCD_WriteData(0x2D); LCD_WriteData(0x01); LCD_WriteData(0x2C); LCD_WriteData(0x2D); LCD_WriteCmd(0xB4); // INVCTR 反色控制 LCD_WriteData(0x07); LCD_WriteCmd(0xC0); // PWCTR1 电源控制 LCD_WriteData(0xA2); LCD_WriteData(0x02); LCD_WriteData(0x84); LCD_WriteCmd(0xC1); // PWCTR2 LCD_WriteData(0xC5); LCD_WriteCmd(0xC2); // PWCTR3 LCD_WriteData(0x0A); LCD_WriteData(0x00); LCD_WriteCmd(0xC3); // PWCTR4 LCD_WriteData(0x8A); LCD_WriteData(0x2A); LCD_WriteCmd(0xC4); // PWCTR5 LCD_WriteData(0x8A); LCD_WriteData(0xEE); LCD_WriteCmd(0xC5); // VMCTR1 电压控制 LCD_WriteData(0x0E); LCD_WriteCmd(0x20); // 反色关闭 LCD_WriteCmd(0x36); // MADCTL 访问控制 LCD_WriteData(0xC8); LCD_WriteCmd(0x3A); // COLMOD 颜色模式 LCD_WriteData(0x05); // 16位RGB565 LCD_WriteCmd(0x2A); // CASET 列地址 LCD_WriteData(0x00); LCD_WriteData(0x00); LCD_WriteData(0x00); LCD_WriteData(0x7F); LCD_WriteCmd(0x2B); // RASET 行地址 LCD_WriteData(0x00); LCD_WriteData(0x00); LCD_WriteData(0x00); LCD_WriteData(0x9F); LCD_WriteCmd(0x21); // 内存访问控制相关 delay_ms(50); LCD_WriteCmd(0x29); // DISPON 打开显示 delay_ms(50); }注意MADCTL设置成0xC8这个值决定了RGB与BGR顺序、横竖屏扫描方向以及行列交换。如果你的屏显示效果偏色先检查这里如果你发现画面与刷入数据相比旋转了90度也改这个寄存器。0xC8对应的是RGB颜色顺序加上竖屏模式横屏则改为0xA8或0x68具体看面板走线。3.2 画点、区域填充与滚动显示WRAM写入命令是0x2C写完后连续发送像素数据屏会自动递增地址。为了性能我只把目标区域用CASET和RASET框住再做填充void LCD_SetWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) { LCD_WriteCmd(0x2A); // CASET LCD_WriteData(x0 8); LCD_WriteData(x0 0xFF); LCD_WriteData(x1 8); LCD_WriteData(x1 0xFF); LCD_WriteCmd(0x2B); // RASET LCD_WriteData(y0 8); LCD_WriteData(y0 0xFF); LCD_WriteData(y1 8); LCD_WriteData(y1 0xFF); LCD_WriteCmd(0x2C); // RAMWR } void LCD_Fill(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t color) { LCD_SetWindow(x0, y0, x1, y1); uint32_t pixelCount (x1 - x0 1) * (y1 - y0 1); // 连续写像素颜色高字节在前 for (uint32_t i 0; i pixelCount; i) { LCD_WriteData(color 8); LCD_WriteData(color 0xFF); } }画点和区域填充分开后后续写字符、画图、滑块条都能复用。像素颜色格式是RGB565比如红色是0xF800绿色是0x07E0蓝色是0x001F。如果你拿到的数据是RGB888必须先做转换否则屏幕上会出现明显偏绿或偏蓝的情况。3.3 关键参数字体、坐标与颜色格式1.8寸屏分辨率128×160竖屏情况下左上角坐标是(0,0)右下角是(127,159)。字符显示一般用6×8或8×16点阵菜单栏如果用16号字体一屏可以显示10行每行8个汉字。中文字库通常自己取模放到常量数组里或者外挂Flash字库。和常见桌面端开发不同这里没有系统字体所有字符都要转成点阵数组。关于坐标系注意ST7735S的MADCTL不仅影响显示方向还影响行/列地址的递增方向。如果你把x方向当作短边初始化时要把CASET的范围限制在0-127RASET限制在0-159。反过来如果屏被识别成160×128画面就会有一半区域显示错乱。4. 移植到硬件 SPI复用映射、DMA 与帧率变化4.1 GPIO 复用配置与 SPI 时钟使能从模拟SPI切到硬件SPI第一步是用CubeMX或者直接寄存器配置GPIO复用关键点是打开GPIO时钟和SPI时钟并把SCK、MOSI、CS、DC、RST设为复用功能模式。// 使能SPI1与GPIOA、GPIOB时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE); RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE); GPIO_InitTypeDef GPIO_InitStructure; // 配置SCK与MOSI为复用推挽输出 GPIO_InitStructure.GPIO_Pin GPIO_Pin_5 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_OType GPIO_OType_PP; GPIO_InitStructure.GPIO_PuPd GPIO_PuPd_NOPULL; GPIO_Init(GPIOA, GPIO_InitStructure); GPIO_PinAFConfig(GPIOA, GPIO_PinSource5, GPIO_AF_SPI1); GPIO_PinAFConfig(GPIOA, GPIO_PinSource7, GPIO_AF_SPI1); // CS、DC、RST使用普通推挽输出 GPIO_InitStructure.GPIO_Pin GPIO_Pin_12; GPIO_InitStructure.GPIO_Mode GPIO_Mode_OUT; GPIO_InitStructure.GPIO_OType GPIO_OType_PP; GPIO_InitStructure.GPIO_PuPd GPIO_PuPd_NOPULL; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOB, GPIO_InitStructure); // SPI1参数配置 SPI_InitTypeDef SPI_InitStructure; SPI_InitStructure.SPI_Direction SPI_Direction_1Line_Tx; 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在168MHz主频下SPI1挂在APB2上实际是84MHz/810.5MHz。这个速率对ST7735S来说通常可行但有些模组信号质量不好的时候降到4MHz更保险。我的经验是先以8分频跑通再逐步提高直到出现花屏就退回两级。CPOL和CPHA一定要和模组匹配ST7735S一般是模式0也就是CPOLLow、CPHA1Edge。4.2 用 DMA 把显示缓存搬到屏幕硬件SPI再配合DMA刷屏时CPU几乎不参与帧率比模拟SPI有明显提升。一个常见方案是先建一块128×160×2字节的显存写入时先改显存再一次性把整块显存用DMA推到GR420。#define LCD_WIDTH 128 #define LCD_HEIGHT 160 uint16_t lcdBuffer[LCD_WIDTH * LCD_HEIGHT]; void LCD_Flush(void) { // 设置全屏窗口 LCD_SetWindow(0, 0, LCD_WIDTH - 1, LCD_HEIGHT - 1); // 禁用SPI中断启动DMA DMA_InitTypeDef DMA_InitStructure; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE); DMA_DeInit(DMA2_Stream3); DMA_InitStructure.DMA_Channel DMA_Channel_3; DMA_InitStructure.DMA_PeripheralBaseAddr (uint32_t)SPI1-DR; DMA_InitStructure.DMA_Memory0BaseAddr (uint32_t)lcdBuffer; DMA_InitStructure.DMA_DIR DMA_DIR_MemoryToPeripheral; DMA_InitStructure.DMA_BufferSize LCD_WIDTH * LCD_HEIGHT * 2; DMA_InitStructure.DMA_PeripheralInc DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize DMA_PeripheralDataSize_Byte; DMA_InitStructure.DMA_MemoryDataSize DMA_MemoryDataSize_Byte; DMA_InitStructure.DMA_Mode DMA_Mode_Normal; DMA_InitStructure.DMA_Priority DMA_Priority_High; DMA_InitStructure.DMA_FIFOMode DMA_FIFOMode_Disable; DMA_Init(DMA2_Stream3, DMA_InitStructure); SPI_DMACmd(SPI1, SPI_DMAReq_Tx, ENABLE); DMA_Cmd(DMA2_Stream3, ENABLE); }注意DMA发送的字节序取决于显存内RGB565的高低字节排列。如果lcdBuffer[0]是color变量整体发送时高字节在前那么DMA的MemoryDataSize设为Byte就能按顺序发出去。如果数据错乱成颜色对调检查SPI_FirstBit_MSB以及显存内字节排列。DMA传输完成后要等DMA的TCIF标志置位再关闭否则下次写入会覆盖尚未完成的传输。4.3 移植适配时的引脚边界硬件SPI下不是所有GPIO都能当SPI引脚用。STM32F407ZET的SPI1只能映射到PA5/PA6/PA7或PB3/PB4/PB5SPI2在PB13-PB15SPI3在PB3/PB4/PA15。如果PCB已经布好线板子把SCK接到了PA4之类的引脚那只能加硬件SPI映射或者继续用模拟SPI。另外CS在大多数模组上是低有效如果你用DMA连续刷一整屏CS全程保持低没问题但你用普通IO翻转CS时每次写命令后再拉高会引入额外延时对屏本身不是问题只是速度上限低了些。有一个常见踩坑点是SPI的NSS引脚。配置SPI_NSS_Soft之后NSS引脚由软件控制但GPIO配置为普通输出时不能把它和硬件NSS功能连在一起否则SPI的传输可能被片选信号干扰。我的做法是把CS和NSS完全分开CS由任意GPIO控制NSS功能不用。5. 避坑记录白屏、花屏、反色、字符重影的常见原因5.1 白屏或一半亮一半暗现象初始化完成后屏幕全白背景灯亮但没有内容。偶尔屏幕上半部分正常下半部分白。原因最常见是上电时序不对SLPOUT之后没有等到延时就直接发DISPON其次是初始化序列里缺少0x20反色控制或者电源控制寄存器设置不完整。还有一种是RST引脚在MCU和屏之间接线松动导致芯片没有完成复位。解决先测RST波形复位后至少延时120ms。然后把初始化序列末尾的DISPON命令单独提出来在主循环中延时500ms后重发一次如果屏幕能亮起来说明是初始化时序问题。再把0x20、0x21都发一遍排除反色位的影响。5.2 花屏雪花现象刷字符或图片时屏幕上随机出现噪点、横纹刷新越快越明显。原因SPI时钟太高。STM32F407的SPI1可以跑到42MHz以上但排线过长、杜邦线交叉时信号过冲和串扰会让屏端采样错误。另外一个隐蔽原因是SPI模式下主设备发送一字节时从设备需要时间处理连续发送不间隔会导致数据错位。解决把SPI分频从2改成8实测下来10.5MHz以下基本稳定。还有我建议把SCK到GND之间加一个100pF电容采样边沿会更干净。用杜邦线连接时线长控制在10cm以内。5.3 显示反色与颜色错乱现象红色变成蓝色或者整体像相底效果偏色严重。原因MADCTL的RGB/BGR位设置错了导致RGB565颜色分量被交换又或者初始化序列中把0x20反色命令反了屏幕变成负片效果。有些模组还需要执行0x3A COLMOD05如果面板默认是12位色深颜色位数不一致也会出现色偏。解决按这张表的思路快速试错每次修改后重新初始化再看效果MADCTL0xC8偏色就换0x08反色效果就发0x21如果颜色数不对检查COLMOD参数是0x05还是0x03。5.4 字符重影与坐标错乱现象画一个矩形结果显示在屏幕边缘错位或者字符叠加到前一行。原因CASET和RASET设置范围超过128×160或者写入窗口后没有重新指定起始地址。另一个原因是MADCTL扫描方向不同导致行地址与列地址对调。解决清屏时先固定调用LCD_SetWindow(0,0,127,159)再发0x2C不要依赖上一次的窗口残留。绘制字符时每画完一行就重设坐标不要自动换行计算。6. 让屏幕刷新快一倍DMA SPI 双缓冲的技巧6.1 双缓冲的思路想把帧率从十几帧拉到接近三十帧最直接的做法是在显存上动手。准备两个缓冲区前一个被SPI DMA读取时后一个给CPU写入等DMA完成中断到达后交换指针形成流水线。这样CPU和SPI总线可以并行工作而不会互相等待。uint16_t frontBuffer[128 * 160]; uint16_t backBuffer[128 * 160]; volatile uint8_t bufferIndex 0; void LCD_SwapBuffers(void) { bufferIndex ^ 1; } uint16_t* LCD_GetDrawBuffer(void) { return bufferIndex ? backBuffer : frontBuffer; } uint16_t* LCD_GetTxBuffer(void) { return bufferIndex ? frontBuffer : backBuffer; }实际刷新流程是先向LCD_GetDrawBuffer写入画面修改为需要显示的内容然后调用LCD_SetWindow配置全屏区域把TxBuffer的地址作为SPI DMA的源启动传输。DMA传输完成中断里调用LCD_SwapBuffers将写缓冲区和发送缓冲区对调。注意一点DMA源地址在启动前就锁定了所以不能在传输过程中修改被读缓冲区的内容。传输时间约等于40KB数据除以SPI速率如果你的SPI是10.5MHz传完一屏大约40ms那么你留给CPU绘图的时间只有40ms复杂界面就会看到撕裂感。频闪不明显但帧率上不去时可以考虑降低色彩位数或只刷新变化区域。6.2 验证刷新率验证刷新率有个简单办法主循环里每次DMA完成中断计数用秒计数统计帧率再用逻辑分析仪看CS片选信号的高电平时间。如果你看到CS低电平持续40ms说明瓶颈在SPI带宽如果CS低电平时间只有10ms但总帧率还是低问题在CPU绘图逻辑或缓冲切换能力不足。6.3 一点习惯从那以后我每次在STM32F407ZET上点这种小屏都会强制走一遍四个动作确认驱动IC型号用模拟SPI跑通初始化序列再测MADCTL颜色方向最后才切硬件SPI和DMA。调过的屏多了反而越来越不敢跳过这些步骤。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑