资讯动态

STM32F103RC驱动ILI9341 SPI屏移植LittleVGL V6.0完整指南

发布时间:2026/9/8 17:55:10 来源:尧图企业网站定制
简介本资源是一套基于STM32F103RC微控制器与LittlevGL V6.0图形库驱动ILI9341 SPI接口TFT显示屏的完整嵌入式GUI开发实践方案面向嵌入式初学者、STM32开发者及GUI界面设计学习者解决SPI屏驱动适配、LVGL移植配置与基础UI组件实现等典型工程问题。压缩包共39个文件涵盖18个头文件含lv_conf.h、lv_drv_conf.h、驱动配置与硬件抽象层定义、12个C源文件含SPI/DMA初始化、ILI9341底层驱动、encoder输入支持及main主逻辑、2张测试板实物图test_board.png、stm32f4stampV1_1.jpg及Keil MDK工程文件.uvprojx/.uvoptx整体大小2.39MB结构清晰便于快速定位驱动层、GUI层与硬件适配模块。目前已有32人学习下载提供可直接编译运行的完整工程包含按钮、滑动条等基础控件示例、DMA加速显示优化、编码器交互支持并附README.md说明与.gitmodules子模块引用是掌握LVGL在Cortex-M3平台落地的关键实操参考。1. 项目概述与核心价值最近在整理一个老项目翻出来一个基于STM32F103RC和LittleVGL V6.0驱动ILI9341 SPI显示屏的完整测试工程。这个项目虽然硬件平台不算新但麻雀虽小五脏俱全从底层SPI驱动、屏幕初始化、到上层GUI框架的移植与应用完整地走通了一条路。对于刚接触嵌入式GUI或者想用低成本MCU跑图形界面的朋友来说这个项目依然有很强的参考价值。它清晰地展示了如何将一块常见的2.4寸240x320 TFT屏通过SPI接口“驯服”并跑起一个功能丰富的图形库。STM32F103RC属于经典的“增强型”系列72MHz主频256KB Flash48KB RAM性能对于驱动SPI屏和运行LittleVGL的轻量级界面绰绰有余。ILI9341更是TFT驱动IC中的“老兵”资料多稳定性好。而LittleVGL现在已更名为LVGL是一个开源、高度可裁剪的嵌入式图形库V6.0版本在资源消耗和功能上达到了一个很好的平衡。这个组合可以说是学习嵌入式GUI开发的“黄金入门套餐”。它能帮你解决最实际的问题如何用最少的硬件资源实现一个流畅、美观的交互界面。无论你是学生做毕业设计还是工程师做产品原型这套方案都能提供一个扎实的起点。2. 硬件选型与电路设计解析2.1 核心控制器STM32F103RC的考量选择STM32F103RC作为主控是基于成本、性能、生态三方面的权衡。首先它的价格在F1系列中具有竞争力且货源相对稳定。性能上72MHz的Cortex-M3内核足以流畅处理LittleVGL的绘图事件和用户输入。关键的RAM有48KB这为图形库的帧缓冲区Frame Buffer和对象存储提供了宝贵空间。我们知道LittleVGL需要一块内存作为画布来渲染界面240320的屏幕如果使用16位色RGB565一帧图像就需要240320*2 153600字节约150KB这远超芯片RAM。因此我们通常采用**局部缓冲Partial Buffer或直接绘制Direct Mode**策略。F103RC的48KB RAM允许我们开辟一个几十KB的局部缓冲区通过LittleVGL的刷新区机制分块刷新屏幕在性能和内存消耗间取得平衡。在引脚资源上F103RC拥有丰富的GPIO和多个SPI外设。我们至少需要一组SPI用于屏幕数据传输、若干GPIO用于控制线如DC、RESET、背光可能还需要额外的SPI或I2C用于触摸屏如果是有触控功能的屏幕。其IO口驱动能力也足够直接驱动屏幕通常无需额外的缓冲芯片。2.2 显示模块ILI9341与SPI接口模式ILI9341是一个262K色TFT-LCD控制器支持8080并行接口和SPI接口。为了节省MCU的IO口我们选择了SPI模式。这里有一个关键点ILI9341的SPI接口协议并非标准SPI。它支持两种SPI模式3线SPI仅SDA线和4线SPI带单独的DCX命令/数据选择线。我们的项目采用的是更常见的4线SPI模式。引脚连接至关重要SCK (SPI时钟)连接MCU的SPI SCK引脚。MOSI (主机输出)连接MCU的SPI MOSI引脚用于发送数据或命令到屏幕。DCX (数据/命令选择)这是一个GPIO。拉高时MOSI上传送的是像素数据拉低时传送的是寄存器命令。这是SPI驱动屏幕与驱动普通Flash芯片最大的不同。RESET (复位)硬件复位引脚低电平有效。通常也由一个GPIO控制。CS (片选)SPI片选低电平有效。虽然很多例程为了简化可能直接接地即一直选中但为了符合标准SPI协议和便于多设备管理建议还是用GPIO控制。背光控制通常连接一个GPIO通过PWM或简单的高低电平控制亮度。注意有些ILI9341模块的SPI接口最高时钟支持到几十MHz但实际使用时受限于布线质量和MCU的SPI驱动能力建议先从较低频率如18MHz或9MHz开始测试稳定后再逐步提高。过高的速率可能导致显示错乱或雪花点。2.3 SPI外设配置与性能压榨STM32F103的SPI外设功能强大。我们的目标是让数据吞吐速度最大化以减少刷屏延迟提升界面流畅度。首先SPI应配置为主机模式、全双工。数据帧格式设置为8位虽然ILI9341支持9位传输用于命令参数但8位模式更通用驱动编写更简单。时钟极性CPOL和相位CPHA需要根据ILI9341的数据手册确定通常模式0CPOL0 CPHA0或模式3CPOL1 CPHA1都可行但必须与屏幕模块的时序要求一致。一个常见的坑是模式选错导致显示全白或全黑没有任何内容。其次为了极致性能必须开启DMA直接存储器访问。当需要刷新一大块区域时CPU只需要配置好DMA的源地址存放像素数据的数组和目标地址SPI数据寄存器然后启动传输就可以去处理其他任务了。DMA传输完成会产生中断我们在中断里进行后续处理如关闭片选、标记刷新完成。这能极大解放CPU避免在SPI等待状态中空转。SPI时钟频率的计算STM32F103的APB2总线时钟最高72MHz作为SPI1的时钟源。SPI的波特率预分频器可以设置为2、4、8、16、32、64、128、256分频。如果我们设置为主频2分频则SPI时钟为36MHz。这是理论最大值实际要留有余量。在我的测试中使用硬件SPI1配置为18MHz4分频配合DMA刷屏速度已经非常快肉眼无拖影。3. 软件架构与LittleVGL V6.0移植详解3.1 底层驱动层打通MCU与屏幕这是整个项目的基石目标是为上层提供一个统一的、高效的屏幕操作接口。主要包含以下文件ili9341.c/.h 屏幕驱动核心。包含初始化序列、设置窗口、读写GRAM、画点、画线、填充矩形等基本函数。其中ILI9341_WriteCommand和ILI9341_WriteData是两个最基础的函数分别用于发送命令字节和数据字节。它们内部会控制DCX引脚的电平并调用SPI发送函数。spi.c/.h 封装STM32的SPI硬件操作。提供SPIx_ReadWriteByte单字节收发、SPIx_WriteBuffer_DMADMA方式写缓冲区等函数。这里的关键是确保SPI的发送和接收函数是阻塞式或中断/DMA式的并且处理好总线忙状态。lv_port_disp.c这是LittleVGL显示接口移植的关键文件。我们需要在其中实现一个名为disp_flush的回调函数。这个函数的作用是当LittleVGL在它的内部缓冲区draw buffer中完成一块区域的绘制后调用此函数将该区域的数据像素数组搬运到实际的屏幕上。我们的任务就是在这个函数里调用底层ili9341.c中的函数将这块数据通过SPI发送到ILI9341的对应GRAM区域。disp_flush函数通常接收一个区域坐标x1, y1, x2, y2和一个像素数据数组指针。实现步骤如下调用ILI9341_SetWindow设置ILI9341的GRAM写入窗口为这个区域。将像素数据数组通过SPI最好是DMA发送出去。数据格式需要匹配LittleVGL默认使用RGB565颜色格式这与ILI9341的16位接口模式匹配直接发送即可。在DMA传输完成中断或发送完成后调用lv_disp_flush_ready通知LittleVGL这块区域刷新完毕它可以开始处理下一块了。3.2 中间层LittleVGL V6.0核心与配置LittleVGL V6.0的源码结构清晰。我们需要重点关注lv_conf.h这个配置文件。它是裁剪和定制图形库的“总开关”。对于STM32F103RC这个资源有限的平台精细配置尤为重要颜色深度LV_COLOR_DEPTH设置为16即RGB565兼顾色彩和内存消耗。缓冲区大小LV_HOR_RES_MAX和LV_VER_RES_MAX设置为240和320。绘制缓冲区LV_DISP_DEF_DRAW_BUF_SIZE这是性能关键。它定义了disp_flush函数一次能处理的像素数量。由于我们RAM有限不能设置成整个屏幕153600字节。可以设置为屏幕宽度的10-20行例如LV_HOR_RES_MAX * 20。这意味着LittleVGL会分块渲染屏幕每次渲染20行然后调用disp_flush刷新这20行循环直到整个区域刷新完。这种“局部渲染”是内存受限系统的标准做法。功能裁剪关闭所有不需要的特性和控件以节省Flash和RAM。例如如果不用动画就#define LV_USE_ANIMATION 0如果只用按钮和标签就把其他控件的宏都关掉。内存管理LittleVGL默认使用动态内存分配malloc但在资源紧张或要求实时性的嵌入式系统中使用静态数组或自定义的内存池更稳定。可以在lv_conf.h中配置使用静态或动态内存并指定内存大小。移植步骤可以概括为1) 复制LVGL源码到工程2) 根据硬件修改lv_conf.h3) 实现lv_port_disp.c中的显示接口4) 在主循环中定期调用lv_timer_handler()和lv_task_handler()V6.0中通常是lv_tick_inc()和lv_task_handler。3.3 应用层构建测试界面驱动和库都移植好后就可以在应用层大展拳脚了。在main.c中初始化硬件后进行LittleVGL的初始化然后创建各种控件对象来构建界面。一个典型的测试界面可能包括基础图形绘制创建画布Canvas对象在上面演示画点、线、矩形、圆、圆弧等验证底层绘图API是否正常。控件测试创建按钮Button、标签Label、滑块Slider、下拉列表Dropdown、图表Chart等。为按钮添加事件回调函数测试用户交互。样式系统LittleVGL强大的样式Style系统可以定义控件的外观。测试修改背景色、边框、阴影、字体等属性。动画效果创建简单的属性动画如让一个按钮移动或改变颜色测试系统的流畅度。通过构建这样一个综合性的测试界面可以全面验证SPI驱动效率、LittleVGL移植的完整性、以及系统在真实负载下的稳定性。4. 关键代码实现与驱动优化技巧4.1 ILI9341初始化序列的稳健实现初始化序列是一系列按照特定时序发送的命令和参数用于配置屏幕的驱动电压、伽马校正、颜色模式、扫描方向等。通常供应商会提供一个初始化代码数组。我们的实现要点在于稳健。// 示例发送初始化命令序列 void ILI9341_Init(void) { // 1. 硬件复位 ILI9341_RST(0); Delay_ms(100); ILI9341_RST(1); Delay_ms(100); // 2. 发送命令序列 const uint8_t init_cmds[] { 0xCF, 3, 0x00, 0xC1, 0x30, // 电源控制B 0xED, 4, 0x64, 0x03, 0x12, 0x81, // 电源序列控制 0xE8, 3, 0x85, 0x00, 0x78, // 驱动器时序控制A // ... 更多命令 0x36, 1, 0x48, // 内存访问控制 (MY MX MV ML RGB MH) 0x3A, 1, 0x55, // 像素格式设置 (16位/pixel) 0x11, 0, // 退出睡眠模式 ILI9341_CMD_DELAY, 120, // 延迟120ms 0x29, 0, // 开启显示 }; send_cmd_list(init_cmds, sizeof(init_cmds)); }技巧将初始化序列组织成{命令 参数个数 参数...}的数组并设计一个send_cmd_list函数来解析发送。对于需要延迟的命令如退出睡眠0x11后可以插入一个特殊的延迟命令码如0xFF并在函数中处理为Delay_ms。这样代码更清晰也易于修改。内存访问控制0x36命令尤其重要它控制了GRAM的扫描方向和颜色格式。参数0x48代表设置MY1, MX0, MV0, ML0, BGR1, MH0。这通常意味着屏幕旋转了180度并且是BGR顺序而非RGB。如果显示颜色不对红蓝反色或方向不对首先检查这个参数。4.2 高效的区域刷新函数这是驱动性能的核心。我们需要实现一个函数能够将内存中的一块颜色数据快速填充到屏幕的指定矩形区域。void ILI9341_FillArea(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t *color_data) { // 1. 设置GRAM窗口 ILI9341_SetWindow(x1, y1, x2, y2); // 2. 发送写GRAM命令 ILI9341_WriteCommand(0x2C); // 3. 准备数据切换DCX为数据模式 ILI9341_DC(1); // 4. 使用DMA发送数据 uint32_t size (x2 - x1 1) * (y2 - y1 1); SPI1_DMA_Send((uint8_t*)color_data, size * 2); // size * 2 因为每个像素是16位 // 5. 等待DMA传输完成在中断中标记 while(!dma_transfer_complete); dma_transfer_complete 0; }优化点窗口设置优化ILI9341_SetWindow函数内部会发送4次坐标数据x起x止y起y止。这是一个固定的开销。如果连续刷新相邻区域可以考虑合并刷新请求减少窗口设置次数。DMA双缓冲当一块数据正在通过DMA发送时CPU可以准备下一块要发送的数据。这需要分配两个缓冲区在disp_flush中交替使用可以实现近乎无缝的刷新进一步提升流畅度尤其对于动画效果。数据打包STM32的SPI支持16位数据帧格式。如果配置为16位模式理论上可以将发送效率提升一倍因为一次传输就能发送一个像素。但需要确认ILI9341在SPI 16位模式下的兼容性并调整驱动代码。4.3 LittleVGL显示端口(porting)的深度适配lv_port_disp.c的disp_flush函数是与LittleVGL引擎对接的桥梁。一个高效的实现直接决定UI的流畅度。static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p) { // area: 需要刷新的区域 // color_p: 该区域渲染好的像素数据指针 uint32_t width lv_area_get_width(area); uint32_t height lv_area_get_height(area); // 调用底层驱动填充区域 ili9341_fill_area_dma(area-x1, area-y1, area-x2, area-y2, (uint16_t*)color_p); // 重要通知LVGL本区域刷新已完成。 // 这个调用必须在数据真正发送到屏幕后执行。 // 对于DMA我们在DMA传输完成中断中调用它。 } // 在DMA传输完成中断服务函数中 void DMA1_Channel3_IRQHandler(void) { if(DMA_GetITStatus(DMA1_IT_TC3)) { DMA_ClearITPendingBit(DMA1_IT_TC3); dma_transfer_complete 1; // 关键步骤通知LVGL刷新完成 lv_disp_flush_ready(disp_drv); } }关键细节异步通知如果使用DMAdisp_flush函数在启动DMA后就直接返回了不能立即调用lv_disp_flush_ready。必须在DMA传输完成的中断服务程序里调用它。否则LittleVGL会认为刷新瞬间完成可能在上一次DMA还没结束时就开始渲染下一块导致数据覆盖或乱码。缓冲区对齐确保传递给ili9341_fill_area_dma的color_p缓冲区地址是字节对齐的最好是4字节对齐这能保证DMA传输的最高效率。LVGL的绘制缓冲区在定义时可以使用LV_ATTRIBUTE_LARGE_RAM_ARRAY等修饰符来指定对齐方式。面积计算与优化lv_area_get_width/height是宏计算很快。但如果刷新区域很小比如只改变了一个按钮的状态频繁设置SPI窗口和启动DMA的开销可能比直接传输数据还大。对于极小的更新可以考虑用非DMA的轮询模式发送或者积累小更新合并为一次刷新这需要修改LVGL的机制较复杂。5. 调试过程与典型问题排查实录5.1 上电无显示或花屏这是最常见的问题排查需要有条理。电源与背光首先用万用表测量屏幕模块的VCC和GND确保供电电压正确通常是3.3V或5V看模块要求。检查背光控制引脚BL是否被正确拉高或给了PWM信号。最简单的测试是断开BL引脚直接接到VCC上看屏幕是否亮起。复位时序确保复位信号RST的时序满足数据手册要求。通常是低电平保持至少10ms然后拉高再等待至少120ms后再发送初始化命令。时序不足可能导致初始化失败。SPI通信验证这是重点。可以编写一个简单的测试程序循环发送一个固定的命令如读显示ID命令0xD3和参数然后用逻辑分析仪或示波器抓取SCK、MOSI、DCX、CS四条线的波形。检查时钟极性/相位对比抓取的波形和ILI9341数据手册的SPI时序图看CPOL和CPHA是否匹配。不匹配会导致数据在错误的时钟边沿被采样。检查DCX引脚确认发送命令字节时DCX为低发送参数或数据字节时DCX为高。这是最容易出错的地方之一。检查数据字节确认发送的字节值是否正确。特别是初始化序列一个字节错误就可能导致屏幕工作异常。初始化序列尝试使用经过验证的、最简单的初始化序列。有时供应商提供的序列过于复杂可能包含某些特定优化可以先从最基础的、能点亮屏幕的序列开始。5.2 显示颜色异常红蓝反色、颜色失真颜色问题通常与像素格式和内存访问控制寄存器有关。像素格式设置检查发送的0x3A像素格式设置命令参数。对于16位模式常用0x55或0x65。0x55对应RGB5650x65可能对应BGR565。如果红蓝色反了尝试切换这两种模式。内存访问控制0x36命令的BGR位控制红蓝交换。如果设置BGR1则是BGR顺序BGR0则是RGB顺序。LittleVGL默认输出RGB565如果屏幕硬件是BGR排列就需要设置这个位或者在软件里交换红蓝分量。数据字节序STM32是小端机。一个uint16_t类型的颜色值0xF800纯红在内存中存储为0x00, 0xF8。当你通过SPI以8位模式发送时是先发低字节0x00还是高字节0xF8这需要与ILI9341的期望匹配。通常我们需要先发送高8位color 8再发送低8位color 0xFF。如果顺序反了颜色就会完全错乱。在DMA发送时需要确保内存中的数据排列顺序符合屏幕要求有时可能需要预先转换或使用SPI的16位模式来规避此问题。5.3 刷新缓慢、界面卡顿性能瓶颈可能出现在多个环节。SPI时钟频率使用示波器测量实际的SCK频率是否达到配置值如果远低于配置检查SPI外设的时钟源APB2是否使能并正确分频。DMA配置确认DMA通道是否配置正确传输数据宽度是否匹配内存和外设都设置为半字或字节。检查DMA中断优先级避免被其他高优先级中断频繁打断。LittleVGL缓冲区大小在lv_conf.h中增大LV_DISP_DEF_DRAW_BUF_SIZE。缓冲区越大disp_flush被调用的次数越少每次传输的数据量越大DMA的效率越高。但受限于RAM需要找到平衡点。可以尝试设置为屏幕宽度的20、30、40行进行测试。CPU负载在main循环中除了调用lv_timer_handler()是否还有其他耗时任务使用GPIO翻转和示波器测量lv_timer_handler()的执行时间。如果一帧的渲染时间超过16ms60Hz就会感到卡顿。可以考虑优化UI减少复杂控件或动画或者提高MCU主频如果可能。绘制操作优化避免在短时间内频繁触发大面积重绘。例如一个进度条更新应该只刷新进度条变化的区域而不是整个屏幕。合理使用LittleVGL的lv_obj_invalidate_area()函数来标记脏区域。5.4 触摸屏如果包含失灵或不准如果项目包含电阻触摸屏通常通过SPI或模拟SPI的XPT2046芯片驱动常见问题如下SPI冲突如果触摸屏和显示屏共用SPI总线必须严格管理片选CS信号确保同一时刻只有一个设备被选中。并且在切换设备时最好有一个小的延时并重新配置SPI模式因为XPT2046和ILI9341的SPI模式可能不同。校准电阻屏必须校准。实现一个四点或五点校准程序获取校准矩阵参数。校准数据需要存储到Flash中每次上电读取。采样噪声对触摸坐标进行软件滤波例如连续采样多次取中值或平均值可以消除抖动。压力阈值读取XPT2046的Z轴压力值只有当压力超过一定阈值时才认为是一次有效触摸可以防止误触发。6. 项目总结与进阶优化方向这个基于STM32F103RC和LittleVGL V6.0的项目成功地将一个高性能的图形库运行在了一个资源有限的经典MCU上。整个过程涉及了硬件接口理解、底层驱动编写、中间件移植和上层应用开发是一个完整的嵌入式系统开发流程的缩影。从个人实践来看最深的体会是调试工具的重要性。一个逻辑分析仪在分析SPI时序问题时无可替代一个简单的GPIO翻转配合示波器是测量函数执行时间、分析性能瓶颈的利器。不要只依赖printf。对于想在此项目基础上进一步优化的朋友可以考虑以下几个方向启用STM32的FSMC总线驱动8080并行接口如果屏幕模块支持8080接口使用FSMC驱动可以带来数量级的速度提升彻底解决刷屏速度瓶颈实现全屏动画无压力。但这需要更多的IO口。升级LittleVGL版本LVGL已经发展到V9.x带来了更多炫酷的控件、效果和更优化的底层架构。可以考虑将显示驱动接口从V6.0迁移到新版本虽然有一定工作量但能获得更好的开发体验和社区支持。引入RTOS如果项目功能变得复杂需要同时处理UI、网络、数据采集等任务引入FreeRTOS等实时操作系统是必然选择。将LVGL的任务放在一个独立的线程中通过消息队列与其它任务通信可以使系统结构更清晰响应更实时。制作UI文件使用LVGL官方的PC模拟器如SquareLine Studio进行可视化UI设计生成C代码再移植到嵌入式平台。这能极大提高复杂界面的开发效率。这个项目压缩包里的代码更像是一个“工作原型”它证明了方案的可行性。当你真正把它用到产品中时还需要考虑代码的健壮性异常处理、功耗管理动态调整背光、睡眠模式以及更精细的内存管理。希望这个详细的拆解能帮你少走弯路更快地让这块屏幕“活”起来。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价