news 2026/9/10 2:07:45

STM32F103VE驱动ILI9341显示图片的完整调试记录

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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接口节省IO,STM32F103VE引脚虽然多,但挂了一堆传感器之后也未必够用;三是ILI9341的资料和代码极其成熟,不管是正点原子的代码还是网上各路开源工程,初始化序列都是现成的,出问题也好查。

1.2 为什么是VE而不是C8T6,Flash容量是硬门槛

很多人会问,都是F103系列,为什么偏偏用带“VE”后缀的型号?关键差异在Flash和RAM:STM32F103C8T6只有64KB Flash和20KB RAM,而STM32F103VE有512KB Flash和64KB RAM。

这个差别在显示图片的时候是致命的。一张240x320的16位色图片,裸数据是240×320×2=153600字节,也就是150KB。64KB Flash的C8T6连一张图都塞不下,更别说还要放固件代码。VE的512KB Flash扣掉固件占用,大概能放两三张整幅图片,RAM也能轻松开帧缓冲。如果你只是显示字符和色块,C8T6完全够用;但标题是“显示图片”,那就老老实实选VE这个级别。

另外F103VE有FSMC外设,如果后续想升级成8080并口屏,FSMC可以直接映射LCD为内存区域,写像素就像写普通变量一样快。这次虽然用的是SPI模块,但芯片选型上已经把这个扩展余地留好了。

2. 硬件接线:影响屏稳定性的几个“小”细节

2.1 引脚分配与SPI外设的对应关系

接线这部分最容易翻车,尤其是把软件IIC的思路带到SPI上来,随意接引脚导致后面调不通。STM32F103VE的SPI1外设,默认引脚是PA5(SCK)、PA6(MISO)、PA7(MOSI),NSS片选可以软件控制,我建议直接用一个普通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。很多人会漏接GND,ST-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驱动本身的逻辑不难,真正要花心思的往往是供电、接线和时序这些隐蔽环节。希望这篇文章能帮你少走一点弯路。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 2:07:29

curl 库 CURLINFO_SIZE_UPLOAD_T 完全指南:精确统计上传字节数

curl 库 CURLINFO_SIZE_UPLOAD_T 完全指南:精确统计上传字节数 【免费下载链接】curl A command line tool and library for transferring data with URL syntax, supporting DICT, FILE, FTP, FTPS, GOPHER, GOPHERS, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, MQTT…

作者头像 李华
网站建设 2026/9/10 2:06:49

语音情感识别实战:基于PyTorch的CNN+LSTM模型构建与部署

简介:基于Pytorch实现的语音情感识别项目源码,面向机器学习开发者与语音技术研究人员,提供从语音降噪、特征提取到模型训练与推理的完整流程,适用于智能客服、心理健康辅助、人机交互等场景。压缩包共29个文件,包含25个…

作者头像 李华
网站建设 2026/9/10 2:04:34

CANN/GE算子表达样例指南

Graph中各类算子表达样例 【免费下载链接】ge GE(Graph Engine)是面向昇腾的图编译器和执行器,提供了计算图优化、多流并行、内存复用和模型下沉等技术手段,加速模型执行效率,减少模型内存占用。 GE 提供对 PyTorch、T…

作者头像 李华
网站建设 2026/9/10 2:00:48

树莓派4B部署YOLOv5-Lite目标检测:从模型转换到NCNN推理实战

简介:面向树莓派4B的YOLOv5-Lite目标检测资源包,为边缘计算与IoT场景提供了一套轻量级深度学习落地方案,适合希望在低功耗设备上完成实时目标检测的开发者、学习者,可应用于智能安防、无人零售、农业监测等常见边缘视觉任务。资源…

作者头像 李华
网站建设 2026/9/10 2:00:21

Ruffle拖放SWF完整内幕:丢进窗口的那0.5秒里,后台跑了4次接力

Ruffle拖放SWF完整内幕:丢进窗口的那0.5秒里,后台跑了4次接力 【免费下载链接】ruffle A Flash Player emulator written in Rust 项目地址: https://gitcode.com/GitHub_Trending/ru/ruffle 把 game.swf 拖进窗口、松开鼠标,半秒后画面就动起来了。Ruffle 的拖放加载看…

作者头像 李华
网站建设 2026/9/10 2:00:19

CANN/ge AddNodeByOp API文档

AddNodeByOp 【免费下载链接】ge GE(Graph Engine)是面向昇腾的图编译器和执行器,提供了计算图优化、多流并行、内存复用和模型下沉等技术手段,加速模型执行效率,减少模型内存占用。 GE 提供对 PyTorch、TensorFlow 前…

作者头像 李华