STM32F103VE驱动ILI9341显示图片的完整调试记录
简介STM32F103VE驱动2.8寸ILI9341 LCD显示图片的完整工程源码面向嵌入式入门开发者及需要快速实现屏幕显示的项目人员。工程基于ARM Cortex-M3内核通过SPI接口与ILI9341通信包含LCD初始化、清屏、画点、图片显示等封装函数并针对正点原子34针2.8寸屏完成适配。压缩包共413个文件以h头文件、c源码、d依赖文件、o目标文件及crf编译中间产物为主其中h和c覆盖全部底层驱动与上层调用逻辑uvprojx为Keil工程配置可直接打开hex为编译后的烧录文件另含jpg测试图片与编译清理脚本整体约11.94MB目录结构清晰便于按模块查阅。已有1531人浏览学习。配合Image2LCD工具可将图片转换为ILI9341可直接显示的数据格式帮助理解像素编码与SPI传输流程代码风格简洁、函数封装完善可直接编译烧录验证也可作为二次开发模板。无论你想掌握LCD初始化时序还是需要快速点亮屏幕这份资源都能提供可靠参考。 前一阵子做一个小项目需要在本地设备上显示一张产品示意图。我翻出手头这块吃灰挺久的2.8寸ILI9341 LCD屏配上STM32F103VE目标很明确把一张240x320的图片老老实实显示出来。原本以为跑个官方例程、换个数组就能收工结果从接线到取模、从初始化到烧录硬是踩了一圈坑才把屏幕点亮。这篇文章就把我这次完整的调试过程写出来包括硬件接线、初始化序列、图片数据制作、烧录报错排查这几个环节给同样想用STM32F103VE驱动ILI9341显示图片的朋友一个可以直接照着做的参考。1. 为什么最终选了STM32F103VE 2.8寸ILI9341这个组合1.1 ILI9341这块屏的参数和接口类型2.8寸ILI9341是市面上最常见、资料最多的小尺寸TFT屏之一分辨率240x320驱动芯片是ILITEK的ILI9341支持16位/18位真彩色。常见的模块会引出SPI接口或者8080并口我手里这块只接了SPI引脚的版本总共8个引脚VCC、GND、CS、SCLK、MOSI、DC、RST、BLK。选它最核心的原因就三个一是分辨率在小屏里够看能放下一张带标题的示意图二是SPI接口节省IOSTM32F103VE引脚虽然多但挂了一堆传感器之后也未必够用三是ILI9341的资料和代码极其成熟不管是正点原子的代码还是网上各路开源工程初始化序列都是现成的出问题也好查。1.2 为什么是VE而不是C8T6Flash容量是硬门槛很多人会问都是F103系列为什么偏偏用带“VE”后缀的型号关键差异在Flash和RAMSTM32F103C8T6只有64KB Flash和20KB RAM而STM32F103VE有512KB Flash和64KB RAM。这个差别在显示图片的时候是致命的。一张240x320的16位色图片裸数据是240×320×2153600字节也就是150KB。64KB Flash的C8T6连一张图都塞不下更别说还要放固件代码。VE的512KB Flash扣掉固件占用大概能放两三张整幅图片RAM也能轻松开帧缓冲。如果你只是显示字符和色块C8T6完全够用但标题是“显示图片”那就老老实实选VE这个级别。另外F103VE有FSMC外设如果后续想升级成8080并口屏FSMC可以直接映射LCD为内存区域写像素就像写普通变量一样快。这次虽然用的是SPI模块但芯片选型上已经把这个扩展余地留好了。2. 硬件接线影响屏稳定性的几个“小”细节2.1 引脚分配与SPI外设的对应关系接线这部分最容易翻车尤其是把软件IIC的思路带到SPI上来随意接引脚导致后面调不通。STM32F103VE的SPI1外设默认引脚是PA5SCK、PA6MISO、PA7MOSINSS片选可以软件控制我建议直接用一个普通GPIO来控制CS方便后续挂多个SPI设备。我用的具体接法如下LCD引脚STM32F103VE引脚说明VCC3.3V模块供电GNDGND共地CSPA4片选软件控制低电平有效SCLKPA5SPI1_SCK硬件SPI时钟MOSIPA7SPI1_MOSI主发从收DCPA2数据/命令选择低电平命令高电平数据RSTPA1复位信号低电平复位BLKPA0背光控制高电平点亮可接PWM调亮度这里有个细节必须提DC引脚必须选一个普通的、没有复用冲突的GPIO因为它和SPI硬件无关纯粹是给ILI9341发送一个电平状态告诉它当前字节是命令还是数据。很多人一开始把DC接到PA4这种引脚上等后面想用硬件NSS才发现冲突。所以规划引脚时先把芯片外设的固定引脚画出来再分配GPIO别一开始就乱点鸳鸯谱。2.2 背光、去耦电容和引脚上拉的隐性作用BLK背光引脚不要直接悬空。有些模块内部没加上拉你不控制它背光默认就不亮很容易误判成屏幕坏了。我用的模块把BLK接一个限流电阻到3.3V就能常亮但更推荐的方式是把BLK接到PA0然后软件里输出高电平后面想用PWM调亮度也方便。电源这一块是很多新手的盲区。LCD模块内部有电荷泵电路给玻璃基板供电瞬间电流波动比想象中大。如果直接用杜邦线从开发板的3.3V引脚取电在刷屏瞬间SPI数据传输导致电流抖动屏幕就可能出现水波纹甚至闪白。正确做法是在LCD模块的VCC和GND之间就近并联一个10uF电解电容和一个100nF陶瓷电容。这就是热词里提到的“lcd极化避免”——电源极性不能接反电解电容正负极也不能颠倒去耦要靠近模块供电端而不是靠近开发板端。至于4*4键盘为什么有人说要上拉道理和LCD控制引脚一样的GPIO在MCU复位的瞬间处于高阻态如果不外部上拉DC和CS信号会乱飘屏可能在上电瞬间收到垃圾命令闪一下白屏。我给CS、DC、RST三个引脚都加了10K上拉到3.3V复位期间保持确定电平实测白屏问题基本消失。3. 初始化序列拆解点亮屏幕前ILI9341做了什么3.1 从复位到Sleep Out屏的启动状态机ILI9341上电之后不是马上就能显示它内部有一个比较严谨的状态机硬件复位 - 等待电源稳定 - Sleep Out退出睡眠- 等待内部DC-DC稳定 - 设置像素格式和扫描方向 - Display On。很多人直接把网上抄的初始化数组往SPI里一丢屏幕上出来东西了就完事。可一旦屏不亮就完全不知道从哪里排查。我建议至少把初始化序列按功能分段理解这样出了问题你能定位到是供电问题、时序问题还是命令参数问题。我用的核心初始化流程简化版如下static void LCD_Init(void) { // 1. 硬件复位 LCD_RST_L(); HAL_Delay(50); LCD_RST_H(); HAL_Delay(120); // 2. 软件复位 LCD_Write_Cmd(0x01); // Software Reset HAL_Delay(120); // 3. 退出睡眠 LCD_Write_Cmd(0x11); // Sleep Out HAL_Delay(120); // 4. 像素格式16bit LCD_Write_Cmd(0x3A); // Pixel Format Set LCD_Write_Data(0x55); // 16位真彩色RGB565 // 5. 内存访问控制设置扫描方向 LCD_Write_Cmd(0x36); // Memory Access Control LCD_Write_Data(0x08); // 正常方向BGR顺序调整后为0x08 // 6. 显示开 LCD_Write_Cmd(0x29); // Display ON HAL_Delay(20); }上面的0x11 Sleep Out比较关键。ILI9341上电默认是处于睡眠模式的你不发这条命令即使所有寄存器都配好了背光亮着屏幕也是一片白或者暗暗的。很多人第一次接屏看到白屏第一反应是SPI时序不对其实很可能就是忘了这步。3.2 关键命令颜色格式、扫描方向、窗口功能0x3A命令设置像素格式参数0x55表示16位色。这是显示图片的基础后面的RGB565颜色数据能否正确解析全靠这一条。如果这里设置成18位色数据格式错位图片颜色就会完全错乱。0x36命令控制扫描方向参数0x08是常用的横屏/竖屏调整开关。这个命令实际上是控制ILI9341从哪个角开始刷新像素。默认从上到下、从左到右如果你的屏幕镜像了或者反了调整这个字节就行不需要改数据数组。真正显示图片要理解的是“窗口功能”它由0x2A列地址和0x2B行地址两条命令配合。窗口功能允许你指定屏幕上一个矩形区域之后所有的数据写入只在这个窗口内生效。显示图片时我们先把窗口设成图片左上角到右下角然后连续发送整张图片数据ILI9341会自动依次填入这个矩形区域。这个机制特别像在Word里选中一块区域再粘贴图片区域选好了数据往里倒就行。4. 让图片上屏从BMP到RGB565数组的完整链路4.1 图片预处理与取模工具的参数选择LCD本身只认识像素点数据不认识JPG更不认识PNG。所以得先在电脑上把图片转换成C语言数组。我用的是Image2Lcd这个工具界面老但很好用。设置参数时要注意几点输出数据类型选“C语言数组”扫描方式选“水平扫描”输出灰度选“16位真彩色”最大宽度和高度填240和320勾选“高位在前”还是“低位在前”取决于你代码里怎么拼RGB565数据这里最坑的是字节序。RGB565一个像素占2字节比如红色是0xF800在工具里可能会生成{0x00, 0xF8}或者{0xF8, 0x00}两种排列。如果显示出来每个颜色通道明显反了红蓝互换就是字节序没对上。我在代码里统一按“高字节在前”处理数据直接按16位读取实测正常。4.2 容量计算一张图占多少Flash空间转换完的数组很吓人我用文本编辑器打开生成的.c文件一个240x320的数组有几百行。一张图153600字节是固定的这和内容无关因为每像素2字节不可能压缩。这个数据量放在STM32F103VE的512KB Flash里问题不大但如果你的工程启用了很多库剩余空间可能不足编译时就会报FLASH溢出。一个比较实用的检查方法是编译后在Map文件里看_ImageSize如果固件加上图片数组超过512KB就要考虑砍掉一些不用的功能或者用外部SPI Flash存储图片。我的工程里放了2张图一张是菜单背景一张是产品示意图编译后剩余Flash还有一百多KB整体稳定。注意大数组要定义成const uint8_t image[] {}放到Flash里不要定义成局部数组或者全局普通数组否则会占掉珍贵的64KB RAM。4.3 显示核心代码设置窗口加连续写显存图片数据显示的核心逻辑其实就三步设置窗口、把显存指针指到窗口起点、连续写入数据。用SPI加DMA发送可以极大提升刷新速度。void LCD_ShowImage(uint16_t x, uint16_t y, uint16_t w, uint16_t h, const uint8_t *img) { uint32_t imgSize (uint32_t)w * h * 2; // 1. 设置窗口 LCD_Write_Cmd(0x2A); // 列地址 LCD_Write_Data(x 8); LCD_Write_Data(x 0xFF); LCD_Write_Data((x w - 1) 8); LCD_Write_Data((x w - 1) 0xFF); LCD_Write_Cmd(0x2B); // 行地址 LCD_Write_Data(y 8); LCD_Write_Data(y 0xFF); LCD_Write_Data((y h - 1) 8); LCD_Write_Data((y h - 1) 0xFF); // 2. 连续写显存 LCD_Write_Cmd(0x2C); // Memory Write // 3. 片选拉低后连续传输 LCD_CS_L(); HAL_SPI_Transmit_DMA(hspi1, (uint8_t *)img, imgSize); // 等待DMA完成或者用回调里拉高CS }这段代码里有一个细节很多人会忽略DMA发送1024字节以上数据时SPI片选必须由软件持续锁住不能每个字节都拉高拉低。我之前写过一份代码在逐字节发送时每次传输完都拉高CS结果一刷整屏就花了因为ILI9341把一次窗口的数据截断成了很多个不完整的传输。正确的流程是发完0x2C命令后保持CS拉低让DMA把整块数据一次推给屏幕全部结束后再拉高CS。实测整屏刷新时间从逐字节传输的1秒多降到了200ms左右视觉上就是流畅了很多。5. 实测排坑device not found、花屏、颜色异常逐个击破5.1 烧录失败device not found排查全过程代码写完信心满满去烧录结果Keil直接弹了个报错核心提示是error: device not found - device:vendor: stm32f103ve stmicroelectronics这个报错的意思是调试器我用的是ST-Link没有在SWD总线上发现STM32F103VE这颗芯片。排查链路我走了一遍按顺序记录一下帮大家省时间。第一步查接线。SWD只需要四根线SWDIO、SWCLK、GND、3.3V。很多人会漏接GNDST-Link和目标板不共地SWD时序就完全乱掉这是最常见的“device not found”原因。检查之后确认接线没问题。第二步查目标板供电。屏幕模块直接从板上3.3V取电如果屏的电荷泵把电压拉低芯片可能处于欠压复位状态自然找不到设备。我尝试把LCD模块供电断开只用杜邦线给芯片供电再烧录就成功了。这让我意识到问题出在电源上而不是芯片坏了。第三步查复位状态。如果RST引脚被外部电路持续拉低芯片一直处在复位状态同样找不到设备。用万用表量RST引脚电压正常应该在3.3V附近如果为0就要顺着复位电路查。最终的处理方案是在ST-Link和开发板之间加了一根粗短的GND线和3.3V线降低线路阻抗。屏幕供电独立从USB转TTL的5V经过一块AMS1117降压模块取电不再和MCU抢电。这样处理之后烧录一次通过。后来看论坛上很多人也遇到过类似问题大部分都是供电不足引起的极少数是芯片读保护锁死需要先用ST-Link Utility解锁。5.2 花屏与残影从供电和SPI速率两个方向查烧录成功后第一次刷图屏幕下半部分全是彩条上半部分正常。这个现象我第一反应是数据窗口设置不对但检查半天没发现逻辑错误。后来用逻辑分析仪抓了SPI信号发现波形很“毛糙”SCLK上升沿处有很多抖动。这就是典型的信号完整性问题因为杜邦线太长加上背光电流在公共地线上叠加了纹波把SPI时序搞乱了。解决方案有两步。第一步在LCD的VCC和GND之间加了一个100uF电解电容效果立竿见影花纹区域缩小了第二步把SPI时钟从18MHz降到9MHz整屏刷新率虽然低了一点但显示彻底干净了。feedthrough这个现象也值得提一下。LCD内部相邻像素之间存在寄生电容当某一行数据切换时电压会串扰到邻近行表现为高对比度图像边缘出现残影或拖影。这在低刷新率和信号不干净时特别明显。我的体会是如果屏幕显示静止图片时边缘有一圈淡色拖影优先查电源纹波其次降低SPI速率实在不行再把窗口刷新改成逐行刷新。5.3 颜色偏色与扫描方向的判断逻辑图片显示出来了但颜色明显发蓝发红或者红蓝互换。这种情况基本可以锁定在RGB565的字节序上。ILI9341命令0x36的BGR位会影响颜色通道的排列。默认情况下红色在LCD上可能被解析成蓝色。我在初始化里把0x36参数设为0x08这个值开启了BGR顺序。如果你发现红蓝完全互换就在0x08和0x28之间切换蓝色问题立刻解决。还有一个容易忽略的问题图片在电脑上看正常但屏幕上上下颠倒或者左右镜像。原理不是取模出错了而是0x36命令的扫描方向位没配好。调整0x36参数参数值效果0x08默认方向从上到下从左到右0xC8上下颠倒0x28左右颠倒0xE8上下左右均颠倒如果你想固定一个方向又不想每次重新取模直接改这个参数就行比重新转换图片省事得多。5.4 复位瞬间闪白屏上拉电阻带来的教训整个项目调试到后期发现一个偶发问题MCU重新上电的瞬间屏幕会闪一下白屏然后正常显示。一开始以为是初始化时序问题但每次打开电源都闪说明问题发生在上电早期。用示波器抓DC引脚的电平变化发现MCU复位期间DC引脚电压呈锯齿状乱跳直到程序启动后被GPIO配置拉稳。IL9341在上电瞬间收到乱码命令就会短暂进入异常状态。解决方法是给DC、CS、RST三个引脚分别加10K上拉电阻到3.3V确保MCU复位时这些信号保持高电平屏不会误触发。这个思路和4*4键盘矩阵接上拉电阻是同一个道理GPIO在MCU复位瞬间不可控外部上拉可以提供确定的默认电平。如果你用的是现成开发板板上可能已经带了这些上拉电阻但如果是自己画的板子或者飞线连接这一课一定要记住。文章写到这我再补充一个实际操作中的小技巧SPI的MISO引脚在只读驱动LCD时完全可以不接因为ILI9341的SPI读功能基本用不上我们只关心写数据。省掉MISO线可以减少杜邦线数量让信号干扰少一个来源。别小看这一个小改动实测下来信号毛刺确实少了一些。还是那句老话LCD驱动本身的逻辑不难真正要花心思的往往是供电、接线和时序这些隐蔽环节。希望这篇文章能帮你少走一点弯路。本文还有配套的精品资源点击获取