简介:这是一份面向嵌入式开发者的STM32+HAL库LCD显示初始化配置工程资料,适合已掌握STM32基础、需要快速上手液晶屏与触摸屏驱动的读者。资源围绕LCD初始化流程展开,涵盖接口选择、GPIO与时序配置、分辨率/颜色格式设定、初始化命令序列写入,以及触摸屏I2C/SPI配置和中断处理等关键环节,并配有HAL库驱动和main函数调用示例。资源共179个文件,以c/h源码、o/d编译目标文件、uvprojx工程文件及ioc/uvoptx等配置为主,体积仅1.14MB,结构精炼便于查阅;同时附带了clean脚本,方便用户清理中间文件后直接打开Keil工程进行学习或二次开发。现有754人浏览学习,说明该工程对LCD移植具备一定参考价值,能帮助开发者少走弯路,快速完成显示模块的初始化和验证。
1. LCD初始化为什么总是第一道坎
先说个我自己的经历。前几年接手一个项目,用的STM32F103C8T6,屏幕是淘宝最常见的1.8寸SPI屏,主控ST7735S。当时我以为屏已经点亮过了,驱动代码应该好移植,结果从硬件焊好到屏幕上出现第一行字,整整折腾了一天半。中间经历了好几次“代码看着没问题、接线也没问题、但屏就是白屏”的状态,最后才定位到是初始化时序和命令顺序的问题。
从那以后我养成了一个习惯:凡是接触一款新LCD,先花时间把初始化配置吃透,再去管画点、画线、刷图片这些上层功能。原因很简单——LCD显示初始化配置决定了屏幕能不能正常进入工作状态,这一步错一个细节,后面所有显示代码全都是在给一块“没有醒过来的屏”干活。
很多人觉得初始化无非就是搬一份驱动代码过来,改改引脚、改改宏定义,跑通就完事。真正做产品、做项目的时候你会发现,初始化这段代码是整个LCD驱动里最容易出问题的环节。为什么?因为它不是简单的寄存器赋值,它涉及硬件复位时序、控制器工作模式选择、像素格式匹配、显存扫描方向设置,还要和你的单片机时钟配置、SPI通讯速率、GPIO初始电平严格配合。任何一个环节有偏差,现象可能都是“屏没反应”,但根因完全不同。
这篇文章我会从STM32 HAL库的角度,把LCD初始化配置这件事完整拆开讲。内容覆盖硬件选型、CubeMX配置、初始化命令序列、常见故障排查,以及我在实际项目中踩过的具体坑。无论你用的是ST7735、ILI9341还是其他常见SPI接口LCD,思路都是通用的。
2. 选屏与接线:硬件阶段决定后续调试难度
2.1 SPI屏的种类与接口差异
市面上基于STM32项目最常见的LCD屏,主控芯片就那么几个:ST7735S(1.8寸、1.44寸小屏)、ILI9341(2.4寸、2.8寸)、ILI9488(3.5寸)。全都是SPI接口的TFT屏,区别主要在分辨率和颜色深度上。
- ST7735S:128x160,RGB565,适合小尺寸、低成本的显示需求,比如温湿度计、小仪表盘。
- ILI9341:240x320,RGB565或者RGB666,最常见,资料最多,适合做菜单界面、简单的图形交互。
- ILI9488:320x480,通常是RGB666,8位/16位/SPI接口都有,适合大屏显示,但SPI模式下刷新率会受影响。
- ST7789:240x320或者240x240,很多圆屏和全贴屏幕使用,初始化命令和ST7735略有差异,但是现在的驱动库普遍都支持。
接口方面,四线SPI(SCL、SDA、CS、DC)是最省引脚的方案,性价比高,也是我优先推荐的接法。RS(也叫DC)引脚用来区分命令和数据,这个引脚在初始化中的作用非常关键,后面会讲到。
特别注意:还有一种三线SPI的屏,没有DC引脚,命令和数据通过9位数据帧区分。这种屏写驱动会增加复杂度,不大建议新手选。
2.2 引脚分配中最容易忽略的三个细节
第一,电平匹配。很多LCD模块是3.3V逻辑,但STM32F103系列有不少引脚是5V容忍的,不是所有引脚都是。而且现在很多便宜的LCD模块板上自带了电平转换或者寄存器,接5V的单片机也不会烧,但数据时序的电平标准不同,可能导致初始化失败。我的建议始终是:凡是接到LCD模块的GPIO,一律按3.3V逻辑来设计,别贪5V的兼容性。
第二,片上外设引脚冲突。STM32F103C8T6有PB3、PB4、PA15这三个引脚默认是JTAG功能,如果你把这几个引脚用作LCD的DC、CS或者RES,程序里必须先把复用功能关掉。很多人初始化死活不成功,查到最后发现是JTAG占用了引脚。
// 把PB3、PB4、PA15释放为普通GPIO __HAL_AFIO_REMAP_SWJ_NOJTAG();第三,背光引脚的处理。很多屏模块的BLK(背光)引脚,有的模块内部已经拉了上拉,有的没有。在CubeMX里配置成输出模式,初始电平先设置为低,在初始化完成后再拉高,防止上电瞬间背光先亮、屏幕却还在复位中造成的“灰屏闪白”现象。
2.3 我推荐的接线方案
以STM32F103C8T6为例,如果用硬件SPI1,优先这样分配:
| LCD引脚 | STM32引脚 | 说明 |
|---|---|---|
| VCC | 3.3V | 电源 |
| GND | GND | 共地 |
| SCL | PA5(SPI1_SCK) | 时钟 |
| SDA | PA7(SPI1_MOSI) | 数据 |
| CS | PA4 | 片选,软件控制 |
| DC | PA1 | 数据/命令选择 |
| RES | PA2 | 复位 |
| BLK | PA3 | 背光控制 |
为什么不把CS也挂在硬件SPI上?因为HAL库下,硬件NSS使用起来要多考虑片选自动管理的逻辑,而用普通GPIO手动控制CS更加直观可靠。实测下来,手动拉低CS、发数据、拉高CS,延时可以忽略不计。
3. CubeMX工程配置:时钟、SPI、GPIO,一个都不能错
3.1 时钟树设置
初始化配置的程序写得好不好,首先取决于单片机本身的时钟对不对。很多人在CubeMX里直接把HCLK拉到最高(比如72MHz),但是没检查APB1和APB2的外设时钟树。SPI1挂在APB2上,SPI2挂在APB1上,外设时钟不对,SPI的波特率就是算不准的。
对于F103,我习惯把HCLK设为72MHz,APB1分频2(36MHz),APB2不分频(72MHz)。这样SPI2最高支持18MHz,SPI1理论上可以到36Mbit/s,但实际LCD SPI接口速率上限往往是屏本身决定的。
LCD的SCLK频率不要一上来就拉满。STM32F103的SPI外设时钟72MHz,分频系数可以选2、4、8、16、32、64、128、256。初始化阶段用8分频甚至16分频(即9MHz或4.5MHz)是稳妥的。等初始化完成、确认显示正常后,再改分频系数来提高刷新速度。我遇到过一个情况:SPI速率设得太高,ILI9341的初始化命令序列全部按最高速度发出去,结果寄存器写入错乱,屏幕出现随机色块。降到8分频后恢复正常。这不是单片机不行,而是模块上的走线质量、杜邦线的长度干扰,都会让高速SPI产生误码。
3.2 SPI参数配置:极性和相位是初始化失败的隐形因素
在CubeMX的SPI配置页面里,有CPOL(时钟极性)和CPHA(时钟相位)两个参数。不同LCD控制器要求的模式不一样:
- ST7735S和ILI9341在四线SPI模式下,大部分模块使用SPI模式0(CPOL=0,CPHA=0),即空闲时钟为低电平,第一个边沿采样数据。
- 也有少量模块设计成SPI模式3(CPOL=1,CPHA=1),空闲时钟为高电平,上升沿采样。
我在实际项目中遇到过一次:代码是网上找的,作者用的是ST7735S,我换了个牌子的同型号屏,白屏。后来用逻辑分析仪对比正常屏和异常屏的SPI时序,发现异常屏模块在CS拉低之前需要SCK保持一个确定的电平。我把CPOL改成1之后,一切正常。
所以SPI参数不能盲目抄。最有效的办法是:看屏模块背面的原理图或者卖家给的数据手册,找到SCL引脚在不传输时的默认状态。如果默认低电平,通常对应模式0;默认高电平,对应模式3。没有资料的话,直接在屏幕上跑一个“初始化+清屏”测试,两种模式都试一下,能正常显示的就是正确配置。
3.3 GPIO和背光引脚的CubeMX配置
GPIO口的配置同样重要:
- CS、DC、RES这三个引脚配置为推挽输出GPIO_MODE_OUTPUT_PP,速度GPIO_SPEED_FREQ_HIGH,初始电平都设置为高。CS和RES高电平是默认状态,DC高电平是数据模式,以免上电瞬间产生误操作。
- BLK背光引脚同样配置为推挽输出,初始电平设置为低。初始化完成后在代码里拉高点亮。
- 如果SPI用的是硬件模式,SCK和MOSI由CubeMX自动配置为AF复用功能,不用手动干预。
提示:RES引脚千万不要悬空。有些模块内部有上拉,悬空也能复位成功,但“能工作”和“可靠工作”是两回事。RES拉低再拉高的复位时序,对LCD控制器来说优先级别非常高,建议所有项目都把RES接到GPIO上用代码控制。
4. LCD初始化程序拆解:命令顺序背后的逻辑
4.1 硬件复位时序
很多人的初始化代码是从LCD_Init()函数开始的,但在调用任何LCD寄存器命令之前,必须先做硬件复位。这个顺序不能乱。标准做法:
LCD_RES_GPIO_Port->BSRR = LCD_RES_Pin; // RES拉高 HAL_Delay(50); // 等待稳定 LCD_RES_GPIO_Port->BRR = LCD_RES_Pin; // RES拉低 HAL_Delay(50); // 保持低电平至少10us,这里放余量 LCD_RES_GPIO_Port->BSRR = LCD_RES_Pin; // RES拉高 HAL_Delay(120); // 等待内部模拟电源稳定,ST7735S建议至少120ms这个时序为什么重要?LCD控制器上电后内部有一个电源管理模块在稳定电压,如果在它还没准备好的时候就写入命令,寄存器可能写入失败。像ST7735S的数据手册里明确写了“Wait 120ms after RESET before sending display commands”,这个延时不能省。
我见过有人贪快,把120ms的延时压缩成10ms,结果屏幕偶尔能亮偶尔不能亮,而且每次初始化的表现还不一样。后来把延时恢复,问题彻底消失。
4.2 初始化命令序列:每条命令在干什么
以ST7735S为例,网上流传的初始化代码通常是几十条命令的数组,看不懂的人直接复制。其实这些命令可以分成几组,每一组都有明确目的:
第一组,软件复位和退出休眠。比如SWRESET(0x01)和SLPOUT(0x11)。SLPOUT命令让控制器从睡眠模式进入正常工作模式,执行之后要等120ms以上,让内部的DC-DC转换电路稳定输出。很多人漏了这个延时,直接发后续命令,就会出现初始化“看起来成功”但屏幕一直黑屏的情况。
第二组,帧率和电源控制。比如FRMCTR1(0xB1)、PWCTR1(0xC0)、PWCTR2(0xC1)这些命令,主要设置行扫描周期、VCOM电压、升压电路等参数。不同面板对这些值有特定要求,直接改数值可能导致偏色或者对比度异常。我处理过一块屏幕整体发紫,排查到最后是VCOM电压设置值偏大。
第三组,像素格式设置。COLMOD(0x3A)命令决定像素格式,ST7735S支持12位、16位和18位RGB。STM32环境几乎都是RGB565(16位),所以COLMOD设为0x05。如果你设置成0x03(12位),屏幕显示会明显偏色,因为后面的画点程序按2字节一个像素填充,模式不匹配。
第四组,显示方向和显存扫描。MADCTL(0x36)是初始化里最容易引起“显示方向不对”或者“颜色通道反了”的命令。MADCTL的bit7是页面地址顺序,bit6是列地址顺序,bit3是RGB/BGR顺序。比如我的项目里屏幕需要上下翻转,就设置MADCTL = 0xC0,然后强制把RGB顺序改成BGR(bit3置1),否则红色和蓝色会互换,整张图片颜色完全不对。
// ST7735S 初始化核心片段,这里只列出关键命令 static const uint8_t init_cmd_list[] = { 0x01, 0x00, // SWRESET,软件复位 0x11, 0x00, // SLPOUT,退出休眠 0x3A, 0x01, 0x05, // COLMOD,RGB565格式 0x36, 0x01, 0xC0, // MADCTL,扫描方向+BGR顺序 0x29, 0x00, // DISPON,开启显示 };4.3 发送命令和发送数据的底层函数
有了命令序列,底层发送函数必须严格区分“命令”和“数据”。四线SPI模式下,DC引脚电平决定当前字节是命令还是数据:
void LCD_WriteCmd(uint8_t cmd) { LCD_CS_LOW(); LCD_DC_LOW(); // DC=0:发送命令 HAL_SPI_Transmit(&hspi1, &cmd, 1, 100); LCD_CS_HIGH(); } void LCD_WriteData(uint8_t data) { LCD_CS_LOW(); LCD_DC_HIGH(); // DC=1:发送数据 HAL_SPI_Transmit(&hspi1, &data, 1, 100); LCD_CS_HIGH(); }很多人会把DC的拉高拉低操作放在HAL_SPI_Transmit之后,这个顺序是错的。SPI是边沿采样,DC电平必须在SCK产生时钟边沿之前稳定下来。实际操作中,先配置DC电平再启动SPI传输,才能保证控制器正确解析当前是命令还是数据。
另一个细节是CS片选。每次传输前拉低CS、传输完拉高CS,这个做法虽然慢一点,但可靠。有些屏的控制器支持“保持CS低电平,连续发送多个字节”的模式,可以减少GPIO翻转的耗时,但前提是DC电平切换要准确。我的建议是初始化阶段老老实实每字节一次CS脉冲,效率低点没关系,关键是稳定。
4.4 初始化完别急着画图,先做显示自检
初始化函数的最后,我强烈建议加一个显示自检。最省事的做法是连续填充几个纯色块:全白、全红、全蓝。这样做有三个好处:
一,能确认屏幕确实退出了休眠并开始显示。SLPOUT之后如果立刻清屏为白色,理论上看到的是一块白屏,如果还是黑的,就是初始化没走完或者DISPON没发。
二,能通过纯色判断颜色通道是否错了。全红显示成绿色或者蓝色,那不用等画图,直接在MADCTL里反转RGB顺序即可。
三,能为后续画点函数提供验证基准。自检通过后再把底层画点函数跑一遍,如果显示异常,问题就缩小到坐标计算或显存读写部分,而不是初始化的问题。
void LCD_DisplaySelfTest(void) { LCD_Fill(0, 0, 127, 159, 0xFFFF); // 白色 HAL_Delay(200); LCD_Fill(0, 0, 127, 159, 0xF800); // 纯红 HAL_Delay(200); LCD_Fill(0, 0, 127, 159, 0x001F); // 纯蓝 HAL_Delay(200); }5. 烧录连接失败与初始化失效的排查链路
5.1 “no stm32 target found”问题定位
在配置完工程、写好初始化代码之后,很多人卡在了第一步:程序根本烧不进去。Keil或者STM32CubeProgrammer报错no stm32 target found,瞬间所有LCD相关代码都变成纸上谈兵。
这个错误最常见的原因有几个。SWDIO和SWCLK两根线接触不良,是最多的。杜邦线插得不紧、线序反了、转接板接触氧化,都会导致调试器找不到目标芯片。
其次是目标板供电问题。STM32F103的核心电压是3.3V,如果你的板子没有外部的LDO给MCU供电,只靠ST-Link的3.3V输出,大电流设备一接上去就会把电压拉低,调试器自然无法识别。
还有一个容易被忽略的原因:软件复位后芯片进入了低功耗模式。如果你之前烧过一个进入STOP模式或者待机模式的程序,调试器可能无法通过复位唤醒芯片。这种情况的解决办法是先把BOOT0拉高,上电进入系统存储器模式(内置bootloader),然后通过ST-Link重新烧录。
5.2 初始化失败但能烧录:如何缩小问题范围
如果程序能烧录但屏幕没反应,我建议按下面这个顺序检查,不要凭感觉乱改。
第一步,用万用表量LCD模块的VCC电压是不是3.3V,背光引脚是不是被拉高了。如果背光电压正常但屏幕不亮,大概率不是初始化问题,而是偏光片或者屏幕排线的问题。
第二步,用示波器或者逻辑分析仪抓CS、SCL、SDA三根线的波形。初始化代码跑一次,CS应该有至少几十次拉低动作。如果CS波形都没有,说明程序压根没执行到LCD初始化,问题在系统时钟配置或者死循环里。
第三步,核对RES引脚波形。正常复位时序应该是一个清晰的“高-低-高”脉冲,脉冲宽度要满足至少10us。如果RES引脚在初始化过程中一直是高电平没有变化,检查CubeMX里引脚编号是否配置对了。
第四步,确认SPI发送是否有数据。抓SDA波形,如果CS拉低期间SDA没有变化,说明HAL_SPI_Transmit返回错误或者SPI时钟没开。HAL_SPI_Transmit的返回值一定要检查,很多人只是调用了这个函数,不确认返回值,SPI外设配置错误时函数调用直接失败。
5.3 用初始化失败的时间点反推原因
还有一种情况是初始化执行到一半屏幕有轻微反应,但最终显示还是乱码或者脏屏。这时候可以通过在初始化命令序列的不同位置插入延时和GPIO翻转,观察屏幕的变化来判断失败点。
比如在SLPOUT命令之后加一个很长的延时(500ms),如果屏幕从黑屏变成模糊的灰白色,说明控制器已经退出休眠,问题出在后半段的像素格式和扫描方向设置上。如果加了延时屏幕还是完全黑屏,说明在SLPOUT之前就已经出问题了,重点查硬件复位和SPI通讯。
这个方法听起来土,但在没有逻辑分析仪的场合非常有效。我调试一块不认识的国产屏时,就是用这种“分段染色法”确认到底是命令发送失败还是面板参数不匹配的。
6. 白屏、黑屏、花屏、偏色:根据现象直接锁定问题
6.1 故障现象和根因对照表
| 现象 | 最可能原因 | 检查方向 |
|---|---|---|
| 白屏 | 背光亮了但没有任何显存内容输出 | CS/DC时序、DISPON命令是否发出 |
| 黑屏 | 背光没亮或控制器还在休眠 | BLK引脚电平、SLPOUT和延时 |
| 花屏/彩色噪点 | 像素格式不对或SPI通讯有误码 | COLMOD设置、SPI速率、接线质量 |
| 颜色反了(红蓝互换) | MADCTL的RGB/BGR顺序设置反了 | 修改MADCTL的bit3 |
| 显示方向不对 | MADCTL的扫描方向位设置不符合安装方向 | 重新计算MADCTL值 |
| 对比度异常/发紫 | 面板的VCOM电压参数不匹配 | 初始化参数中PWCTR相关寄存器 |
这里重点说白屏。白屏的本质是控制器的显存被清零了或者没有被写入图像数据,导致整个面板所有像素都显示为白色。初始化完成后如果立刻清屏填充,看到的不应该是纯白,而是你填充的颜色。如果背光亮了但整个屏幕就是纯白,十有八九是DC引脚和CS引脚的时序配合问题,命令被当成数据写进了显存。
6.2 关于亮度、偏光和肉眼判断的补充经验
热搜词里有“lcd亮度”和“lcd极化避免”,这两个恰好是很多人忽略的“假故障”来源。
LCD的背光通过BLK引脚控制,有人直接用一个GPIO拉高,有人用PWM调节亮度。用PWM的时候要注意频率,低于1kHz的PWM会在屏幕上看得出闪烁。我一般用10kHz左右的PWM频率,肉眼完全无感知。初始化阶段直接把PWM占空比设置为100%,等显示自检通过后再调暗,可以排除“由于亮度过低导致看不清初始化效果”的情况。
“极化避免”这个词,放在LCD上通常指的是偏光片。TFT屏本身就有一层偏光膜,不同的视角方向看,亮度和对比度都不一样。我遇到过一次:屏幕明明正常显示,但从某个角度看是“黑屏”,换个角度就能看到画面,其实是偏光片方向问题,不是初始化的问题。所以屏幕点不亮的时候,先换个角度和光线条件看一下,排除这种物理假故障。
6.3 最后再分享一个判断屏幕好坏的土办法
如果初始化代码实在调不通,又怀疑屏本身是坏的,可以在上电后触摸屏幕表面,或者用手电筒倾斜照射屏幕侧面观察是否有液晶分子反应的痕迹。然后人为把RES拉高拉低几次,如果屏幕边缘出现微弱的灰白色变化,说明面板基本是好的,问题还在驱动和初始化代码上。
另外一个小技巧:初始化命令不要全部照搬网上的“万能初始化数组”。不同批次的面板即使主控芯片一样,厂家的初始化参数也可能不同。遇到初始化结果异常,先试试清屏纯色,如果纯色正常但是花屏,多半不是初始化问题,而是后面填充显示区域函数里坐标范围设置错了。如果纯色也不正常,那再看初始化参数和时序。
我在实际项目中,初始化这块的调试原则就八个字:硬件优先、时序其次、寄存器别乱动。先把接线和供电确认了,再把SPI时序量准,最后才去分析初始化命令参数。按这个顺序排查,绝大多数问题都能在半小时内定位到根因。
本文还有配套的精品资源,点击获取