ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

STM32F407驱动ST7735S TFT-LCD完整指南:从初始化到DMA刷屏

STM32F407驱动ST7735S TFT-LCD完整指南:从初始化到DMA刷屏 简介一份面向嵌入式开发者的STM32F407ZET驱动1.8寸TFT-LCD完整工程资料覆盖微控制器配置、LCD底层驱动、SPI/I2C通信协议及显示初始化流程适合学习STM32F4显示控制或进行小型彩屏项目开发的用户参考。压缩包共186个文件以.h头文件、.c源码为主同时包含.o/.d/.crf等编译中间文件、.uvprojx工程文件及.hex烧录文件可帮助理解从源码到固件的完整构建过程整体大小仅5.72MB便于快速下载使用。已有814人学习浏览内容聚焦F407驱动1.8寸屏的实际实现既展示了核心板与扩展板的硬件接线思路也提供了初始化序列、图像数据传输和命令控制等关键代码能够为开发者节省底层适配时间直接基于工程进行二次开发。 如果你手头正好有一块1.8寸TFT_LCD打算接到STM32F407ZET上跑个显示我猜你已经体会过那种“线接完了就是不亮”的感觉。这块屏说简单也简单——一个SPI接口、五根信号线、一套初始化序列点亮之后显示中文、图片、曲线都很自由但说它坑也多网上的代码往往型号对不上引脚定义和初始化参数稍微偏差屏幕要么白茫茫一片要么颜色完全错乱。这篇文章我就围绕ST7735S1.8寸TFT最常用的驱动方案在STM32F407ZET上的完整驱动流程来展开把硬件接线、SPI配置、初始化序列、画点画线这些底层函数以及实际调试中踩过的坑一并整理出来。无论你是刚入手STM32的新手还是想快速把这屏幕集成进自己项目的工程师都可以照着走一遍。1. 屏幕选型与接口认知ST7735S和SPI的搭配为什么是主流1.1 先确认你手里的屏幕是哪颗芯片1.8寸TFT-LCD模块在各大平台上一搜一大把分辨率几乎全是128x160封装和引脚排列也非常相似。但“长得像”不代表驱动代码能通用。这个尺寸最主流的驱动芯片是ST7735S它由ST原厂的ST7735R演进而来兼容性很好但也有相当一部分模块用的是ILI9163C、GC9106这类替代方案。第一次用先不要急着写代码先确认芯片型号。确认方法有三个一是看模块背面或者排针附近的丝印通常会印ST7735S、ILI9163C之类的字样二是直接问卖家要规格书和参考代码很多淘宝页面提供“资料下载”链接三是拿ST7735S的初始化代码直接试如果屏幕能点亮但颜色偏色大概率是被替换成了其他芯片。这个“替代芯片”的问题在1.8寸屏上特别常见因为很多模块厂会根据供货情况切换驱动IC但外观和引脚定义完全不变。我个人的习惯是模块到手先拍张引脚图再查一遍丝印最后再打开CubeMX。因为ST7735S和ILI9163C虽然初始化流程类似但寄存器细节有差别用错序列会出现显示反色、颜色错乱等诡异的症状这个后面第五节会专门讲排查链路。1.2 4线SPI接口为什么小屏都用它1.8寸TFT模块大多支持SPI和并口两种模式但出厂默认接成4线SPI。SPI模式下实际用到的信号线只有五根SCK时钟、SDA数据、CS片选、DC数据/命令选择、RESET复位加上电源和地总共七八个引脚。选择SPI而不是并口的理由很现实省引脚。STM32F407ZET引脚虽多但如果你还要接传感器、按键、通信模块GPIO资源会越来越紧张。SPI方案把数据线压缩到一根一片屏幕只占五六个IO后面再加什么外设都不心疼。另一个原因在于STM32F407的SPI外设非常成熟硬件SPI的速率可以达到42MHz跑一块128x160的屏幕绰绰有余。很多老项目习惯用GPIO模拟SPI其实没必要F407ZET自带3个SPI外设配置好DMA之后刷屏效率比GPIO模拟高出一个量级。我在项目里默认用硬件SPI1DMA这块的优化思路放在第五节讲。1.3 RGB565颜色格式128x160分辨率、65536色的屏幕颜色编码用的是RGB565——高5位是红色中间6位是绿色低5位是蓝色。之所以用565而不是888是因为16位刚好是两个字节一次SPI传输就能带走一个像素速度和内存都划算。代码里常用的颜色宏是// r,g,b取值范围0-255 #define RGB565(r, g, b) \ (((r 3) 11) | ((g 2) 5) | (b 3))这个宏看起来简单却是后面颜色调试的关键。很多人屏幕能亮但显示出来的红色变成了蓝色原因之一就是RGB分量在发送时字节顺序不对或者MADCTL寄存器里的BGR位设置反了。这个问题我在项目里至少遇到过两次每次都排查了半小时以上后面第五节会展开。2. 硬件接线与SPI通信配置先让通信链路跑通2.1 引脚分配和接线表这里给出我在STM32F407ZET开发板上最常用的一组接线用的是SPI1引脚好记、排针也好插LCD模块引脚功能STM32F407引脚VCC电源3.3V3.3VGND地GNDCS片选PA4RESET复位PA3DC / A0数据/命令选择PA2SDASPI数据输入PA7SPI1_MOSISCKSPI时钟PA5SPI1_SCKBL背光3.3V 或 PWM引脚先说一个容易忽略的细节VCC一定接3.3V不要接5V。虽然很多模块板载了稳压芯片但从ST7735S的数据手册看其逻辑工作电压范围是1.8V到3.3V直接怼5V有烧毁风险。如果用的是面包板供电还要确认电源纹波不要太大否则屏幕会出现随机花屏。背光引脚BL的处理也很有讲究。最简单的做法是直接接3.3V让背光常亮但如果感觉屏幕亮得刺眼可以接到定时器的PWM输出脚通过调节占空比控制亮度。我自己习惯把BL接到PA1上配置成TIM2的PWM输出这样亮度可以随时调。2.2 CubeMX里的SPI1参数配置STM32开发现在基本都用STM32CubeMX生成初始化代码SPI1的参数配置如下ModeFull-Duplex Master全双工主机Hardware NSS SignalDisablePrescaler先选大分频比如SPI时钟4分频等调通后再提速Data Size8 BitsFirst BitMSB FirstClock PolarityLowClock Phase1 Edge这几项里有几个关键点。第一片选CS不用硬件NSS而是用普通GPIO软件控制原因很简单后续在切换命令/数据状态时CS的拉低拉高时序由自己控制更灵活不容易出现硬件NSS自动翻转带来的干扰。第二时钟极性和相位在ST7735S上一般是CPOL0、CPHA1也就是Mode 0但如果你的模块比较特殊显示花屏时可以把这两个参数翻过来试一下。另一个我踩过的坑是GPIO速度。在CubeMX中把SCK、MOSI、CS、DC这些引脚的速度设置为High或者Very High。如果保持默认的Low速度SPI时钟稍微提高后信号上升沿会变缓屏幕会随机出现花点。这个现象很隐蔽因为SPI逻辑上仍是通的只是时序边缘不够陡峭。2.3 SPI发送的基础封装配置完外设后最底层的封装就是两个函数写命令和写数据。void LCD_WriteCmd(uint8_t cmd) { LCD_CS_LOW(); LCD_DC_LOW(); // 命令模式DC拉低 HAL_SPI_Transmit(hspi1, cmd, 1, 10); LCD_CS_HIGH(); } void LCD_WriteData(uint8_t data) { LCD_CS_LOW(); LCD_DC_HIGH(); // 数据模式DC拉高 HAL_SPI_Transmit(hspi1, data, 1, 10); LCD_CS_HIGH(); }DC引脚的作用就是告诉屏幕“接下来发的是命令还是数据”。低电平代表命令高电平代表数据。只要这个逻辑不乱屏幕就能正确解析收到的字节。如果后面发现屏幕显示的“内容”像是命令和数据错位第一件事就查DC切换是否及时。3. 初始化序列屏幕从白屏到点亮的关键一步3.1 初始化到底在做什么不少新手拿到屏幕写完SPI发送函数发现屏幕依然白屏然后就懵了。这其实正常——ST7735S上电后不会自动进入可显示状态必须由主机发送一串初始化命令把像素格式、扫描方向、电源参数、伽马曲线全部配置好才能真正开始显示。可以这么理解初始化序列就是给屏幕“上户口”。不完成这步屏幕虽然通电了但不知道自己该用什么方式工作只能保持白屏。这个序列通常包括几个环节软件复位、退出睡眠模式、设置像素格式、设置扫描方向、配置帧率、配置电源、写伽马曲线、最后开启显示。3.2 一套可以直接用的ST7735S初始化序列下面是我在STM32F407ZET上验证过多次的初始化代码适用于大多数ST7735S模块。注意延时一定要给足特别是复位和退出睡眠后的等待时间void LCD_Init(void) { // 硬件复位 LCD_RESET_HIGH(); HAL_Delay(10); LCD_RESET_LOW(); HAL_Delay(10); LCD_RESET_HIGH(); HAL_Delay(120); LCD_WriteCmd(0x01); // SWRESET 软件复位 HAL_Delay(120); LCD_WriteCmd(0x11); // SLPOUT 退出睡眠 HAL_Delay(120); LCD_WriteCmd(0x36); // MADCTL 访问控制 LCD_WriteData(0xC8); // 扫描方向 BGR顺序 LCD_WriteCmd(0x3A); // COLMOD 像素格式 LCD_WriteData(0x05); // 16位颜色 // 帧率设置 LCD_WriteCmd(0xB1); LCD_WriteData(0x05); LCD_WriteData(0x3A); LCD_WriteData(0x3A); // 电源控制 LCD_WriteCmd(0xC0); LCD_WriteData(0x2C); LCD_WriteData(0x2D); LCD_WriteCmd(0xC1); LCD_WriteData(0x01); LCD_WriteCmd(0xC2); LCD_WriteData(0x01); // 伽马校正 LCD_WriteCmd(0xE0); LCD_WriteData(0x0F); LCD_WriteData(0x24); LCD_WriteData(0x1E); LCD_WriteData(0x1A); LCD_WriteData(0x0C); LCD_WriteData(0x0F); LCD_WriteData(0x09); LCD_WriteData(0x0B); LCD_WriteData(0x3D); LCD_WriteData(0x3D); LCD_WriteData(0x2B); LCD_WriteData(0x2B); LCD_WriteData(0x2A); LCD_WriteData(0x1C); LCD_WriteCmd(0xE1); LCD_WriteData(0x00); LCD_WriteData(0x1B); LCD_WriteData(0x21); LCD_WriteData(0x25); LCD_WriteData(0x33); LCD_WriteData(0x30); LCD_WriteData(0x36); LCD_WriteData(0x44); LCD_WriteData(0x22); LCD_WriteData(0x22); LCD_WriteData(0x34); LCD_WriteData(0x34); LCD_WriteData(0x35); LCD_WriteData(0x03); LCD_WriteCmd(0x21); // INVON 开反色 LCD_WriteCmd(0x13); // NORMALON 正常显示 LCD_WriteCmd(0x29); // DISPON 开显示 HAL_Delay(100); }这套序列里最值得关注的是MADCTL0x36这个寄存器它控制屏幕的扫描方向和RGB/BGR顺序。这里写的0xC8适用于屏幕排针朝下、正常竖屏使用的情况。如果你的屏幕显示方向不对或者红蓝颜色互换改的就是这个值。3.3 为什么网上初始化序列五花八门ST7735S被很多液晶模组厂采购不同批次、不同产线的模块在电源电压和伽马曲线上会有细微差异。所以你会看到网上流传的初始化序列五花八门有的甚至冲突。这很正常不代表谁的代码错了而是他们的模块批次不一样。判断标准很简单屏幕能正常显示、颜色正常、没有明显偏色就说明这套初始化序列适合你的模块。我在项目里一般先跑一套标准序列点亮后如果显示效果偏暗或者对比度差再微调伽马寄存器部分。注意不要为了追求“完美颜色”无限调参小屏幕的色准上限本来就不高能清楚显示才是重点。提示初始化代码里延时很重要。硬件复位后至少等待10ms软件复位和SLPOUT之后等待120ms。这几个延时少了屏幕就会卡在某个中间状态表现是背光亮了但屏幕无任何显示。4. 画点与填充的驱动实现理解LCD的“窗口”机制4.1 地址窗口CASET和RASET初始化完成后屏幕已经可以接收显示数据了。但这里有个关键概念必须先搞清楚ST7735S不是“直接往坐标写像素”的而是先告诉它要操作哪块区域再往这块区域连续灌像素数据。这个区域就叫做“窗口”由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 准备写显存 }窗口机制理解之后画点、画矩形、清屏就都是同一件事的不同参数而已设定一个窗口然后连续发送像素颜色。这也是LCD驱动里最核心的“提交”步骤。忘记调用RAMWR0x2C是新手常犯的错误——设置了地址但没进入写显存模式屏幕自然纹丝不动。4.2 画点、填充矩形与清屏有了窗口函数画点就是设置一个1x1的窗口然后发送两个字节的颜色数据void LCD_DrawPixel(uint16_t x, uint16_t y, uint16_t color) { if (x LCD_WIDTH || y LCD_HEIGHT) return; LCD_SetWindow(x, y, x, y); LCD_WriteData(color 8); LCD_WriteData(color 0xFF); }注意RGB565颜色是两个字节先发高字节还是低字节必须和你的屏幕驱动IC匹配。ST7735S默认是高位在前也就是先发color的高8位再发低8位。如果你发现颜色点在屏幕上像“噪点”一样混乱大概率就是把字节顺序发反了。填充矩形就是把窗口设成整个目标区域然后循环发送颜色数据。这里有一个性能细节不要一次性把窗口内每个像素都调用一次LCD_SetWindow那样会反复发送CASET/RASET命令白白消耗大量时间。正确的做法是设置一次窗口然后在循环里连续写数据void LCD_Fill(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t color) { uint32_t i, total (x1 - x0 1) * (y1 - y0 1); LCD_SetWindow(x0, y0, x1, y1); for (i 0; i total; i) { LCD_WriteData(color 8); LCD_WriteData(color 0xFF); } }清屏就是填充整个分辨率区域。我在调试时经常用不同颜色的清屏来快速判断屏幕是否正常先清成红色再清成绿色如果红绿蓝都能正确呈现说明颜色数据链路是通的问题多半出在上层应用逻辑。4.3 连续写数据的缓冲区优化上面的填充函数简单可靠但效率并不高。因为每写两个字节都调用一次HAL_SPI_Transmit而每次HAL调用都有固定开销。当需要填充整个屏幕时128x160共20480个像素每个像素两次HAL调用总耗时很可观。更高效的做法是准备一个发送缓冲区把一行的像素数据先拼装好再一次性用HAL_SPI_Transmit发出去uint8_t line_buf[128 * 2]; // 一行128像素每像素2字节 void LCD_FillLine(uint16_t y, uint16_t color) { uint32_t i; LCD_SetWindow(0, y, 127, y); for (i 0; i 128; i) { line_buf[i * 2] color 8; line_buf[i * 2 1] color 0xFF; } LCD_CS_LOW(); LCD_DC_HIGH(); HAL_SPI_Transmit(hspi1, line_buf, 128 * 2, 10); LCD_CS_HIGH(); }有了行缓冲后面接DMA刷屏就顺理成章了。先把数据拼进一个数组再触发DMA传输CPU可以去做别的事情这是让屏幕真正“流畅”起来的关键。5. 实测踩坑与性能优化白屏、花屏、颜色失真与刷新提速5.1 白屏先怀疑复位和供电再检查初始化时序白屏是驱动1.8寸TFT时遇到最多的故障我自己第一次调试时也卡了很久。排查顺序我建议是先看供电再看复位最后查初始化延时。供电方面用万用表量一下LCD模块的VCC和GND之间是不是稳定的3.3V。有些面包板供电线太长压降明显屏幕会进入一种“半死不活”的状态背光亮但屏幕无任何显示。这时候换一根粗短线或者直接从开发板的3.3V排针取电问题往往立刻消失。复位方面很多初始化代码里只有软件复位没有硬件复位。我建议无论模块是否要求都先做一次硬件复位RESET拉低至少10ms再拉高等待100ms以上再发命令。如果复位信号太短芯片内部状态机没有彻底复位后续初始化序列就会乱套。初始化延时方面前面那张序列里的120ms不是随便写的。SLPOUT退出睡眠之后芯片需要一定时间稳定内部电路延时不足会导致后续命令被忽略。遇到白屏时可以把这些延时都加长到200ms再试一次成本很低但排除了一类常见问题。5.2 花屏SPI速率、GPIO速度和窗口地址是重点花屏的现象比白屏更难排查因为它说明通信链路是通的但数据在传输过程中出了错。最常见的两个原因SPI速率过快和DC信号时序不对。SPI速率过快时屏幕会表现出“画面间歇性撕裂”或者“局部雪花点”。尤其是用杜邦线连接时线间电容和信号反射会把波形搞坏。处理方案是先把SPI预分频调大比如从4分频改成32分频如果花屏消失说明问题就在速率。稳定以后再根据实际布线逐步提速。GPIO速度这个坑前面提过这里再强调一次CubeMX生成代码时SPI的SCK、MOSI以及CS、DC引脚默认GPIO速度是Low这在低速SPI下没问题但一旦提高SPI时钟信号边沿会明显变慢。把这些引脚改成GPIO_SPEED_FREQ_VERY_HIGH花屏问题经常会不治而愈。窗口地址出错也会导致花屏但现象比较有规律画面整体错位或者某一区域出现“重复图案”。这种问题通常出现在自己移植画图函数时CASET和RASET传入了错误的坐标范围。排查时可以先用全屏填充如果全屏颜色正常只是局部图案错位那就是窗口参数的问题。5.3 颜色失真BGR顺序和字节序是两大嫌疑人颜色不对是最容易误导人的故障因为它看起来“有显示但不对”。我遇到过红色变成蓝色、绿色变成紫色等各种奇怪组合最后归属到两类原因。第一类是MADCTL寄存器里的BGR位设反了。ST7735S默认的RGB顺序可能和你的模块不一致0x36寄存器第3位控制RGB/BGR顺序。初始化里向0x36写入的数据如果第3位是0就按RGB解释是1就按BGR解释。模块不同正确值也不同。我在代码里默认用0xC8如果你的屏幕红蓝互换试着改成0x08再对比。第二类是像素字节发送顺序。RGB565颜色data是16位发送时先发高字节还是先发低字节取决于驱动IC实现。ST7735S通常要求高字节在前但个别模块可能相反。测试方法很简单画一个纯红色块如果显示成了红色但边缘有绿色噪点可能是字节序问题如果直接显示成蓝色那就是BGR位问题。颜色调试时我习惯写一个小的自测函数依次在屏幕四角画上红、绿、蓝、白四个色块然后观察颜色是否符合预期。这个测试一跑颜色链路的所有问题都能快速定位。5.4 性能优化DMA、行缓冲和局部刷新屏幕显示稳定之后下一步就是提速。裸机循环发送HAL_SPI_Transmit时CPU被阻塞在等待发送完成的循环里刷一屏128x160全彩数据要发20480个像素、40960个字节即使SPI速率不算低CPU占用率依然很高。我的做法是引入DMA。流程是先算好一整块要显示的数据放到内存缓冲区然后启动SPI DMA传输传输完成触发中断。CPU在DMA搬运期间可以去处理其他任务比如按键扫描、通信处理刷屏体验会明显改善。// 假设buf里已经拼好这一帧的数据 LCD_CS_LOW(); LCD_DC_HIGH(); HAL_SPI_Transmit_DMA(hspi1, buf, size); // 在SPI_TX_COMPLETE回调里拉高CS并置标志位需要注意DMA传输期间不能修改buf里的数据否则会出现画面撕裂。解决方法是双缓冲一块在传输另一块在拼装传输完成后交换。这个思路在刷新复杂界面时几乎是必须的。局部刷新则是另一个思路。很多时候界面只是改了几个数字、一条曲线没必要把整个屏幕都重刷一遍。利用前面提到的窗口机制只更新变化区域刷新时间能从几十毫秒降到几毫秒。STM32F407ZET性能足够但省下来的时间用来跑业务逻辑总归是划算的。如果后续想上更复杂的界面可以直接在这套底层之上移植LVGL把画点函数和刷屏函数对接好DMA刷新模式保持开启一块1.8寸小屏也能跑出很流畅的菜单动画。我自己在几个小项目里就是这套组合稳定性和开发效率都相当理想。本文还有配套的精品资源点击获取
返回列表