ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

STM32驱动TFTLCD彩色屏幕:硬件SPI与ST7735初始化及Proteus仿真实战

STM32驱动TFTLCD彩色屏幕:硬件SPI与ST7735初始化及Proteus仿真实战 简介本资源是一套面向STM32初学者与嵌入式开发者的TFTLCD显示驱动实战方案聚焦硬件SPI接口驱动ST7735控制器的完整实现解决常见LCD初始化失败、色彩异常、刷屏卡顿等典型问题。压缩包含191个文件涵盖34个C源文件含stm32f10x系列外设驱动如usart、rcc、spi等、34个头文件h、34个编译目标文件o及配套调试配置、链接脚本、Hex固件与Proteus仿真工程整体大小6.38MB结构清晰便于理解底层寄存器操作与SPI时序控制逻辑。已有2204人学习下载资源提供可直接运行的Keil工程含uvprojx/uvoptx、ST7735专用驱动函数WriteCommand_7735/WriteDate_7735等、背景色与画笔色全局变量定义以及基于Proteus的软硬协同仿真验证环境显著降低硬件试错成本适合课程设计、毕设开发与模块化驱动移植参考。 拿到这个工程包的时候我第一反应是这套东西对新手来说简直是一条捷径。STM32驱动TFTLCD很多人上来就卡在SPI时序、初始化序列还有屏幕始终白屏这三个坎上。而这套例程把硬件SPI、ST7735驱动、Proteus仿真三件事一次性打包好了你不用先买屏、不用焊线、不用一上来就面对一块黑屏的实物理屏直接在仿真里把逻辑跑通再移植到实物上成功率会高很多。做嵌入式这几年我一直在强调一个观点调屏幕驱动最大的浪费不是在写代码而是在排错。如果你能把时序问题放在设计阶段就解决掉后面省下的时间相当可观。这套源码加仿真工程的价值就在这——它先给你一个能跑起来的参照系你再根据自己的板子改引脚、改速率、改初始化参数而不是从零开始猜。适合谁用刚学完GPIO和串口、想碰屏幕但怕踩坑的新手或者做课程设计、项目Demo想快速看到画面的同学都很合适。1. 项目逻辑与方案选型为什么要用硬件SPI1.1 硬件SPI和软件模拟SPI的取舍先把这个最基础的问题讲透。驱动TFTLCDSPI通信有两种实现方式硬件SPI和软件模拟SPI。软件模拟SPI就是用GPIO口手动拉高拉低时钟线一位一位地把数据“弹”出去。好处是引脚任意、代码直观坏处是太占CPU而且位时序受中断影响很大稍微来一个中断时钟波形就变形了屏幕就可能出现花屏。硬件SPI则是芯片内部的外设模块自动完成时序你只要往数据寄存器里丢一个字节硬件会自动把8个bit按设定的极性和相位发送出去。CPU介入少、速度稳定、波形干净这是驱动屏幕这类对时序敏感的外设最稳妥的做法。这套例程选择的是硬件SPI方案我认为这个判断很清醒。ST7735这颗屏的像素时钟推荐在15MHz以内硬件SPI跑8分频、4分频都能轻松达到而软件模拟SPI往往只能跑到1MHz左右性能差距明显。尤其是你在做动态波形、动画、多级菜单这类帧率敏感的项目时软件模拟SPI的刷新率会让人着急。1.2 ST7735这颗驱动芯片到底是什么ST7735是一颗专门驱动TFT LCD显示的控制器芯片常见于1.44寸、1.8寸的SPI接口彩屏模组分辨率一般是128x128或128x160。它内部集成了一块GRAM显存大小为128x160x2字节约等于40KB也就是说在16位色模式下每一个像素点占用两个字节。这里有个关键点需要理解STM32F103C8T6的RAM只有20KB装不下ST7735整个40KB的GRAM。所以你在代码里不可能把整帧画面缓存到单片机里再一次性刷过去只能通过SPI逐点写入或者分块写入。这也是为什么屏幕刷新速率上不去的主要原因之一。明白这一点你就知道为什么代码里画矩形、画图片都是用区域填充的方式逐块写GRAM而不是用帧缓冲。ST7735支持多种颜色模式最常用的是RGB565也就是16位色红色5位、绿色6位、蓝色5位。这种模式下颜色数据需要两个字节发送顺序通常是高字节在前但注意它和MADCTL寄存器里的RGB/BGR位有关这个细节后面说。1.3 Proteus仿真的定位先跑通再上板很多人对Proteus仿真有误解觉得“仿真能跑又不代表实物能跑”干脆不用。但我的看法不一样仿真最大的意义是帮你把逻辑层面的错误提前暴露掉。比如初始化顺序对不对、SPI写命令和写数据的时序有没有搞反、窗口设置是否合理这些在仿真里跑一眼就能看出来。如果屏没反应至少你能确定是代码问题而不是硬件焊接问题。这套工程里带了Proteus仿真我建议你的学习路径是先打开仿真工程看到画面正常输出再打开源码逐行读最后才去折腾实物。不过要提醒一句Proteus里并没有ST7735这个具体型号的模型通常用的是同类的SPI接口TFT彩屏模型或者用相近的驱动芯片模型做替代。因为SPI接口和驱动命令高度相似仿真逻辑和实物基本一致但引脚电平速度、时序精度不可能完全等效。所以仿真的目标是验证协议逻辑不是验证电气时序。2. 驱动ST7735前必须搞懂的时序与寄存器2.1 SPI写操作的关键序列ST7735的SPI接口有两条写路径写命令和写数据。它靠一个DC引脚有的叫RS、A0来区分DC拉低时SPI发送的内容被解释为命令DC拉高时SPI发送的内容被解释为数据。这个区分是所有SPI接口屏幕驱动的核心逻辑你必须刻在脑子里。具体到一个字节的传输过程以模式0为例SPI时钟空闲时为低电平数据在时钟上升沿被采样。主机先把DC引脚设定好然后拉低片选CS之后SPI外设开始输出时钟和数据每发送完一个字节可以等待TXE标志位置位确认数据移位寄存器已经空了才能继续写下一个字节。全部发完再拉高CS。这里面最容易出问题的地方就是“命令参数”的配合。比如设置显示窗口CASET命令前面要发命令字节0x2A后面跟着4个数据字节表示列起始和结束地址这些数据字节的DC必须是高电平。如果DC切换时机搞错了屏幕就会把一个命令的后半截当成数据发出去表现就是白屏或者显示错乱。2.2 初始化流程几个绕不开的命令ST7735的初始化序列各家屏幕模组大同小异核心步骤如下软件复位、退出睡眠、设置像素格式、设置内存访问控制、伽马校正、开启显示。代码里一般会依次执行0x01 SWRESET软件复位延时150ms左右。0x11 SLPOUT退出睡眠模式这个延时要给足很多白屏就是这里延时不够。0x3A COLMOD设置像素格式写入0x05表示16位色。0x36 MADCTL设置扫描方向和颜色顺序具体要看你的屏是横屏还是竖屏看模块出厂接线。0xB1、0xB4、0xC0、0xC5这些是电源时序和显示时序的参数不同模组会有微调例程里的默认值通常是可以用的。0x29 DISPON开启显示。这里我要说一个很实用的经验如果初始化之后屏幕白屏不要急着改代码先用万用表确认RST引脚有没有正常拉高。很多模组的RST是独立引脚如果不接或者接了不拉高芯片永远停在复位态你发多少命令都没用。2.3 内存访问控制与显示窗口MADCTL寄存器0x36是屏幕显示方向的总开关它的bit7到bit5分别控制行扫描方向MY、列扫描方向MX、行列交换MVbit3是RGB/BGR颜色顺序。你写0x00是竖屏正向写0xC0或0xA0就变成横屏但这个值跟具体模组的出厂排线方向强相关。举个例子我手上有两块不同厂家出的1.44寸屏同样用MADCTL0x00一块正常显示另一块镜像翻转了。这种情况只能自己试把0x00、0xA0、0xC0、0x60分别试一遍找到视觉上正确的那个。窗口设置也不难理解。ST7735内部有一块大的GRAM显示窗口就是你要写入的一块矩形区域。CASET0x2A设置列起始和结束位置RASET0x2B设置行起始和结束位置然后用RAMWR0x2C往窗口里连续写像素数据。窗口的最大意义在于当你只想更新屏幕上某一小块区域时不需要重刷整屏。比如做仪表盘只需要刷新那个指针区域其他区域不动刷新速度能快好几倍。但要注意写入的像素数量必须和窗口大小严格匹配多写或漏写都会导致后面内容错位。3. 源码实现从底层函数到屏幕渲染3.1 工程结构与引脚分配这套例程的源码目录结构很清晰解压后大概是这样的Hardware/存放st7735.c、st7735.h、spi.c、spi.h、delay.cUser/main.c、stm32f10x_it.cProteus/仿真工程文件Output/编译好的hex文件引脚分配沿用了ST官方的常见接法SPI1的SCK在PA5MOSI在PA7CS用软件控制接PA4DC接PA2RST接PA3。其中CS并没有使用STM32硬件NSS引脚原因很简单方便我们在软件里自由控制片选的拉高拉低这样移植到其他型号单片机时只要改几个宏就行。这里我要多说一句硬件SPI的NSS在某些场合可以交给外设自动管理但在多设备总线时可能会出现误触发片选。屏幕这种一主一从的通信软件片选反而更可靠。你只要在每次传输前CS拉低传输完拉高比配置NSS极性、延时、自动模式那堆寄存器省心得多。3.2 SPI初始化和命令写入SPI初始化建议直接把代码贴出来看因为很多新手就是死在这段配置上。void SPI1_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; SPI_InitTypeDef SPI_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1, ENABLE); 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_InitStructure.GPIO_Pin GPIO_Pin_4; // CS GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_Init(GPIOA, GPIO_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); }说明几个设置的意图。SCK和MOSI必须配成复用推挽输出因为引脚要交给SPI外设控制不能再当普通GPIO用。CPOL为Low、CPHA为1Edge对应模式0这是ST7735数据手册里推荐的模式。SPI_Direction配成单线只发送是因为屏幕只需要写、不需要回读这样省掉配置MISO的步骤。分频这里用的8分频在72MHz主频下就是9MHz的SCK对ST7735来说是安全范围。你要是用F103超频到128MHz或者用其他主频更高的MCU记得重新算一下分频系数。命令和数据的发送函数核心就是那两个极简函数void ST7735_WriteCmd(uint8_t cmd) { DC_LOW(); CS_LOW(); while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) RESET); SPI_I2S_SendData(SPI1, cmd); while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY) RESET); CS_HIGH(); } void ST7735_WriteData(uint8_t dat) { DC_HIGH(); CS_LOW(); while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) RESET); SPI_I2S_SendData(SPI1, dat); while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY) RESET); CS_HIGH(); }命令和数据函数之间只差一个DC引脚电平。但就是这么简单的差别很多人写驱动时会把方向搞反导致发送的内容全被当成命令解析。建议你写完之后用逻辑分析仪或者示波器看一眼DC和MOSI的对应关系确认命令阶段DC是低、数据阶段DC是高。3.3 画点、填充与字符显示底层通信搞定之后后续的显示API都是画点函数的延展。画点的逻辑是这样的先用CASET和RASET把窗口定位到指定像素然后发送一个像素的颜色数据两个字节。void ST7735_DrawPixel(uint16_t x, uint16_t y, uint16_t color) { if ((x SCREEN_WIDTH) || (y SCREEN_HEIGHT)) return; ST7735_SetWindow(x, y, x 1, y 1); DC_HIGH(); CS_LOW(); uint8_t hi color 8; uint8_t lo color 0xFF; while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) RESET); SPI_I2S_SendData(SPI1, hi); while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) RESET); SPI_I2S_SendData(SPI1, lo); while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY) RESET); CS_HIGH(); }有了画点函数填充矩形就是循环画点画线就是逐点插值显示字符就是查字库点阵数据然后把每个bit映射成前景色或背景色。这里我建议你重点关注SetWindow的实现因为它直接决定刷新效率。窗口设置时列地址和行地址要包含起始和结束两个边界结束地址是包含在内的。void ST7735_SetWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) { ST7735_WriteCmd(0x2A); // CASET ST7735_WriteData(0x00); ST7735_WriteData(x0 X_OFFSET); ST7735_WriteData(0x00); ST7735_WriteData(x1 X_OFFSET); ST7735_WriteCmd(0x2B); // RASET ST7735_WriteData(0x00); ST7735_WriteData(y0 Y_OFFSET); ST7735_WriteData(0x00); ST7735_WriteData(y1 Y_OFFSET); ST7735_WriteCmd(0x2C); // RAMWR }代码里出现X_OFFSET和Y_OFFSET是因为很多1.44寸屏幕模组的GRAM起始地址并不是0而是偏移了1到2个像素。如果你的屏幕出现内容整体偏上或偏左调整这两个偏移量就能解决。3.4 主循环里的实际调用正常的显示流程在main函数里大概是这样int main(void) { Delay_Init(); SPI1_Init(); ST7735_Init(); ST7735_FillScreen(ST7735_BLACK); ST7735_DrawString(10, 20, Hello ST7735, ST7735_WHITE, ST7735_BLACK); ST7735_DrawLine(0, 60, 127, 60, ST7735_RED); while (1) { ST7735_FillRect(10, 80, 40, 20, ST7735_GREEN); Delay_Ms(500); ST7735_FillRect(10, 80, 40, 20, ST7735_BLUE); Delay_Ms(500); } }注意一点初始化屏幕前Delay_Init必须执行因为ST7735的复位流程里大量使用延时函数延时不准会导致后续命令执行顺序错乱。另外如果用了HAL库版本Delay一般用HAL_Delay但标准库工程通常自己实现一个简单的SysTick延时所以看工程时先找到延时函数在哪别急着改屏幕代码。4. Proteus仿真搭环境、跑工程、对问题4.1 工程文件和元件连接打开Proteus工程后你会看到主控是STM32F103C8屏幕部分用的是仿真的SPI接口TFT彩屏模型。连接关系很简单PA5接SCK、PA7接SDA、PA4接CS、PA2接DC、PA3接RST背光引脚一般直接通过限流电阻接高电平。有些Proteus版本里屏幕模型的引脚命名和实物不太一样可能是SCL/SDA而不是SCK/MOSI这时候要按引脚功能去对应而不是按名字硬抄。我在论坛里看到不少人拿着实物屏幕的引脚顺序去套仿真模型结果发现CS和DC接反了屏幕半天不出来画面。仿真工程还应该关注MCU的晶振配置。Proteus里双击STM32F103C8可以看到CKSEL等选项很多模板默认用HSE外部时钟你需要在属性里把晶振频率设为8MHz。主频能正确倍频到72MHzSPI分频之后才能得到9MHz的SCK。时间基准则直接给1MHz即可。4.2 把源码编译成hex并烧进仿真MCU仿真里运行的不是源码而是编译生成的hex文件。Keil工程里需要做两件事第一在Options for Target的Output页勾选Create HEX File第二Debug页选择Simulator或者ULINK2调试方式但生成hex这一步跟调试方式没关系只要编译成功就行。编译通过后到汇编输出目录找到Project.hex然后回到Proteus双击MCU芯片在Program File一栏把hex文件路径加进去。这个操作很关键很多人改了代码发现仿真画面没变化就是因为忘了重新生成hex或者生成目录和Proteus里填的目录不一致。仿真跑起来之后正常现象是屏幕先完成初始化然后显示填充色和字符串。如果Proteus屏幕仍然全白或全黑不要急着怀疑代码先检查一下有没有给MCU供电。Proteus的STM32模型不需要额外供电但有时画原理图时会不小心漏掉VDD和GND的连接导致模型状态异常。4.3 仿真和实物的差异仿真跑通不等于实物OK这点必须认清。Proteus里的SPI时序是软件模拟的速度比实物慢很多但它不会出现实物那种因为线长、接触不良、信号反射导致的花屏问题。反过来仿真里屏能正常显示至少说明你的初始化序列、命令参数、发送顺序没有逻辑错误。真正上实物时你只需要重点排查硬件电气问题比如杜邦线是否太长、供电是否足够、背光是否点亮。我见过太多人仿真通过后直接连实物结果白屏。排查下来居然是屏幕模块的供电引脚没有接3.3V或者VCC接了5V把模块电压搞乱了。TFT LCD模组对供电纹波还比较敏感建议在VCC和GND之间加一个10uF电解电容加一个100nF陶瓷电容离供电引脚越近越好。5. 常见问题排查与避坑清单5.1 白屏、花屏、闪屏的排查顺序屏幕白屏是最常见的故障我总结的排查顺序是供电、复位、背光、DC极性、初始化命令。先看白屏是不是背光没亮。液晶面板在背光亮起来时即使没有显示内容也会看到灰白色。如果完全没光检查BLK引脚。如果背光亮但屏幕全白说明ST7735没有收到正确的初始化命令或者处于复位状态。这时候用示波器量RST引脚确认它从上电后先低再高如果一直低屏幕永远不会初始化。花屏的情况一般是数据错位或窗口设置错误。比如你在画一幅图前一部分显示正常后面的内容整体错位了那多半是窗口大小和写入像素数量不匹配。解决方法是统计一下你写了多少个像素再对比窗口覆盖了多少像素两者必须相等。闪屏最常见的原因是SPI速率过高或者电源供电不足。屏幕在刷新过程中电流变化很大如果供电线太细或者电容不足电压跌落会导致逻辑错误。降低SPI分频系数到16或者32试试如果闪屏明显改善那就是速率问题。5.2 通信层面的问题如果你有逻辑分析仪排查SPI通信问题会直观很多。重点看两个地方CS有效期间SCK波形是否稳定以及DC线在数据阶段是否确实拉高。有一个隐蔽的问题是SPI的MSB/LSB顺序。ST7735要求高位先发如果误设成LSB First所有字节都会被镜像翻转命令全乱。这种问题在实物上一旦出现你会看到屏幕上出现完全没有规律的颜色块而不是白屏。这时先检查SPI_InitStructure.SPI_FirstBit是不是SPI_FirstBit_MSB。还有个常见坑是硬件NSS没有被禁用。如果你用的是软件片选但代码里忘了把NSS配置成软件管理SPI_NSS_Soft默认的硬件NSS可能会在SPI初始化时自动拉低片选导致总线状态混乱。检查一下SPI初始化结构体这行一定要写。5.3 几个容易忽略的细节很多驱动ST7735的代码能正常初始化屏幕但显示出来的颜色是反的比如红色显示成蓝色。这通常是MADCTL寄存器里的BGR位不对。这个位会影响RGB565数据在GRAM里的字节顺序你需要在MADCTL值里加上0x08即BGR模式具体是加还是不加看屏的实际表现加一次试一次就行。显示内容偏移也是高频问题。不同尺寸的ST7735模组GRAM起点不同。1.44寸的128x128屏有的模块列偏移为2行偏移为11.8寸的128x160屏偏移多为0。如果画面内容整体偏出边界你的X_OFFSET和Y_OFFSET就要改。这里多嘴一句论坛里很多人直接抄别人的代码没有注意到别人用的是不同尺寸的屏结果画面错位还以为是驱动没写对。最后提一个仿真特有的坑Proteus运行速度可能很慢特别是屏幕刷新大量像素时仿真帧率会下降到每秒几帧。这时候不要误认为是代码卡死可以尝试降低屏幕刷新频率或者在仿真设置里降低计时分辨率但这会影响实时性。如果想验证逻辑可以把刷屏区域改小一点先确认通信正确再全屏填充。我个人在实际操作中还有一个习惯每次新拿到一块ST7735屏我都会先写一个最简单的点亮测试函数只做一件事——把整屏填充成纯红色再纯绿色再纯蓝色。如果这一步正常说明基础SPI通信和初始化是好的如果这一步都不对就不要继续画字符了回头查时序和硬件。这个测试函数帮我区分过很多次“是驱动问题还是硬件问题”省下的时间不是一点半点。玩转这块屏幕说实话没有太高深的理论就是把每一层时序想清楚每一类错误归类好你就能很稳地实现想要的效果。本文还有配套的精品资源点击获取
返回列表