尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

STM32 UCGUI移植实战:LCD驱动修改与排查指南

STM32 UCGUI移植实战:LCD驱动修改与排查指南 简介STM32平台上的UCGUI图形界面示例工程基于KEIL开发环境已预先完成底层移植开发者拿到后只需替换LCD驱动的画点函数即可快速运行。面向需要为STM32项目引入图形界面、又不想从零搭建GUI移植流程的嵌入式工程师可作为快速上手的参考。压缩包共851个文件以736个C源码文件和97个头文件为主体覆盖GUI内核、控件、字体、LCD驱动、内存设备等模块另有批处理脚本用于分类编译辅助整体大小仅1.58MB。已有1010人学习下载。工程目录结构清晰按GUI内核、控件、窗口管理、字体、LCD驱动等模块分类组织配有便于分类编译的批处理脚本结合配套说明文档可快速理解移植要点和驱动替换思路减少重复工作。 拿到一个标注“STM32 UCGUI示例KEIL工程已经移植好修改LCD驱动就能使用”的工程大多数人的第一反应都是赶紧把示例跑起来。屏亮了成就感直接拉满屏黑着就开始怀疑是哪个环节出了问题。我在这个坑里进进出出过很多次今天就把这类工程的完整玩法拆开讲清楚移植到底移了什么、改LCD驱动的时候要改哪些文件、为什么有时候改成自己屏幕还是花屏以及一套我自己整理的排查思路。这种示例工程最典型的结构是UCGUI图形库的源码、KEIL下的MDK工程文件、一套已经写好的LCD底层驱动模板以及一个或者几个演示界面。你需要做的只是把底层那块和屏幕相关的驱动换成自己的。所以核心问题只有一个——怎么把“驱动模板”变成“你的屏幕的驱动”。1. 先搞清楚UCGUI工程里“移植好”到底意味着什么很多刚接触UCGUI的人容易误解“移植”这两个字。以为移植是把UCGUI源码下载下来在KEIL里添加文件然后点击编译屏幕就能显示按钮和窗口。实际上UCGUI移植的本质工作是在图形库和真实硬件之间搭一座桥。图形库负责画点、画线、画圆、渲染字体但它不知道你的屏幕是用SPI还是并口更不知道你的屏幕是240x320还是800x480。这些硬件差异全都被隔离在最底层的LCD驱动里。1.1 三层结构LCD驱动只是最底层的那一层UCGUI的代码结构可以简单分成三层应用层你写的GUI_Init、GUI_DrawLine、GUI_DispString这些代码不关心底层用的是什么屏。中间层UCGUI内核包括LCD_L0_、LCD、GUI_Core等源码文件负责图形算法、裁剪、分层。硬件适配层LCDConf.c、GUIDRV_Template.c、LCD_X调试接口以及你写的屏幕初始化函数、写命令函数、写数据函数。从硬件的角度看屏幕驱动能做的事情其实很少就四件事初始化屏幕设置显示窗口、扫描方向、颜色格式、写命令、写数据、设置背光。这四件事做对了UCGUI就可以在屏幕上画任何东西。所以示例工程说“修改LCD驱动就能使用”毫无夸张成分因为中间的图形算法和界面逻辑已经完全和硬件解耦。1.2 示例工程里真正需要动的几张“牌”打开工程后不要急着看GUI_Init后面那个while循环先找到下面这几个文件LCDConf.c / LCDConf.h配置分辨率、颜色格式、缓存大小对应LCD_X_Config函数。GUIDRV_Template.cUCGUI内部使用的模板驱动里面有一堆函数指针负责把GUI请求转成像素操作。你自己写的LCD驱动文件工程里通常叫LCD.c或者ILI93xx.c这个才是真正要改的地方需要实现LCD_Init、LCD_WriteReg、LCD_WriteData这类函数。有些工程把显示驱动直接写在GUIDRV_Template.c里有的单独拆出来。我的建议是单独拆出来因为后期换屏幕、调试初始化序列会很方便不用在UCGUI的代码里翻来翻去。示例工程之所以说“改LCD驱动就能用”就是因为它已经把GUIDRV_Template.c和LCDConf.c之间的接口都打通了你只需要按下这个接口适配自己的屏。在我维护的多个项目中UCDUI层几乎没怎么动过真正反复修改的其实是驱动芯片的初始化序列。不同品牌的显示屏驱动IC虽然都叫“SPI接口”但寄存器千奇百怪你换一个屏就必须重新对一遍初始化流程。2. 动手前必做确认屏的参数和接线拿到示例工程先别急着改代码先花十分钟把屏幕的底细摸清楚。很多移植失败问题不在UCGUI而在屏幕驱动的硬件参数和工程默认配置对不上。2.1 先把屏幕的驱动芯片查清楚最常见的做法是看排线丝印或者问卖家“驱动IC型号”。我遇到比较多的几种方案是GC9307、ST7735、ILI9341、ILI9488。这些驱动IC虽然都是SPI接口居多但初始化代码完全不同。这里特别说下GC9307。这个芯片在市面上很多1.8寸、2.0寸、2.4寸小屏上用得非常多价格便宜颜色效果也还行。它的初始化序列一般由屏幕模组厂提供因为不同厂家同一个GC9307芯片走线不同初始化参数也不同尤其是窗口设置、偏置电压、Gamma校正这些参数。我习惯把初始化序列单独放一个数组方便调整。注意如果你用的是GC9307的屏网上流传的初始化序列有很多版本有的能用有的点不亮或者偏色。建议直接找卖家要原厂提供的初始化代码这个优先级最高比自己对着寄存器手册猜要快得多。2.2 三线SPI和四线SPI的接线差异很多小屏模块支持SPI接口通常有四线SPI和“所谓的”三线SPI两种接法区别在于有没有单独的DC数据/命令选择引脚。标准四线SPI是这样的SCLK、MOSI、DC、CS、RST。每次传输前先拉低DC发命令字节再拉高DC发数据字节。三线SPI在某些模组上把DC引脚省掉了用9位SPI模式第一个bit表示命令还是数据。但更常见的是模组上根本没有DC引脚你必须用软件把DC接到某个GPIO上去模拟本质上还是四线控制只是物理上少了引脚所以我更愿意叫“软件DC”。在STM32上驱动这类屏我一般直接用GPIO模拟DC信号而不是非要去折腾硬件9bit SPI模式。虽然STM32的SPI硬件支持一些变种模式但不同厂商单片机的实现差异较大用通用性强的GPIO模拟最省事逻辑也清晰#define LCD_DC_LOW() HAL_GPIO_WritePin(LCD_DC_GPIO_Port, LCD_DC_Pin, GPIO_PIN_RESET) #define LCD_DC_HIGH() HAL_GPIO_WritePin(LCD_DC_GPIO_Port, LCD_DC_Pin, GPIO_PIN_SET)接线时务必注意很多模组上标的SDA、SCK要跟单片机SPI的MOSI、SCK交叉连接别接反了否则一屏幕雪花。2.3 分辨率、颜色格式、扫描方向也需要确认分辨率相对好办240x320、320x480这些在LCDConf.h里改掉就行。颜色格式是隐藏坑同一个屏幕的驱动IC可以刷成RGB565、RGB666、RGB888等不同格式UCGUI这边必须和屏幕保持一致。如果你看到界面能显示但颜色明显不对——比如红色变蓝色、蓝天变黄——九成是颜色格式不匹配或者RGB通道顺序反了。UCGUI里通常通过LCD_X_Config下的GUICC_565、GUICC_666_888这类枚举来指定颜色格式。比如GC9307默认我一般用RGB565把ST7735的老代码直接套上去很容易偏色因为两者内部取模的字节序不一样。扫描方向决定了横屏和竖屏显示。在初始化序列里设置0x36寄存器的MY、MX、MV位即可。UCGUI那边用LCD_SetOrientation也能做旋转但最好先在驱动层确定好物理扫描方向再进行全局坐标设置免得两个方向设置打架显示内容镜像错乱。3. 修改LCD驱动的完整实操以GC9307为例这一步开始动刀。我尽量用最接近真实工程的代码来演示。假设工程已有的驱动文件名是LCD_driver.c里面已经有SPI通信函数GC9307的初始化可以按如下思路改。3.1 替换SPI初始化和底层读写函数首先确保SPI已经工作正常。大多数STM32示例工程用HAL库SPI初始化部分通常是SPI1、2Mbps左右这个速度在8MHz主频下都问题不大。GC9307 SPI写命令和写数据的关键就一句话DC引脚区分命令和数据。static void LCD_WR_REG(uint8_t reg) { LCD_DC_LOW(); HAL_SPI_Transmit(hspi1, reg, 1, 10); } static void LCD_WR_DATA(uint8_t data) { LCD_DC_HIGH(); HAL_SPI_Transmit(hspi1, data, 1, 10); }如果驱动里需要写16位数据比如设置窗口坐标时记得高字节在前还是低字节在前要看屏幕驱动IC的手册GC9307很多指令是大端方式即高位在前。写错了窗口坐标会错位。我习惯封装一个统一写法static void LCD_WR_REG16(uint16_t reg) { uint8_t buf[2]; buf[0] reg 8; buf[1] reg 0xFF; LCD_DC_LOW(); HAL_SPI_Transmit(hspi1, buf, 2, 10); }对于小屏如果你的MCU不支持DMA直接阻塞发送就行。帧率要求高的时候再上DMADMA还有一个好处是发送像素数据不占CPUUCGUI的绘制过程不会被拖垮。3.2 填充初始化序列重点是内存访问控制GC9307出厂默认的显示方向和坐标系不一定是你想要的。在初始化序列里一般会有0x36MADCTL这个寄存器控制行列扫描方向和RGB/BGR顺序。我的初始化序列中这一段是必须人工确认的// MADCTL LCD_WR_REG(0x36); LCD_WR_DATA(0x60); // 根据实际安装方向调整把0x60换成0x00、0x40、0x80这些值屏幕的显示方向会转而且内容可能变成镜像。这是调整横竖屏最快的入口。完整的初始化序列建议按屏幕模组厂给的来。GC9307常见的序列长这样具体的参数值你要以模组厂为准static void GC9307_Init(void) { LCD_RST_LOW(); HAL_Delay(20); LCD_RST_HIGH(); HAL_Delay(100); LCD_WR_REG(0xFE); LCD_WR_DATA(0x00); LCD_WR_REG(0xEF); LCD_WR_DATA(0x00); // 省略大量寄存器设置…… // 最后开启显示 LCD_WR_REG(0x29); }写好后可以先用纯色测试清屏为红色、绿色、蓝色看看是不是你预期的颜色顺便判断RGB顺序是否需要交换。这个步骤千万不要跳过否则后面UCGUI里跑Demo颜色不对会怀疑是自己的代码问题其实从一开始就是错的。3.3 把驱动接入UCGUI的模板UCGUI的GUIDRV_Template.c里通常有类似这样的接口static void _SetPixelIndex(GUI_DEVICE * pDevice, int x, int y, int PixelIndex) { // 设置显示窗口 LCD_SetWindow(x, y, x, y); // 写像素 LCD_WR_Data(PixelIndex 8); LCD_WR_Data(PixelIndex 0xFF); }如果你的工程里已经实现了这个函数只需要保证内部的LCD_SetWindow和LCD_WR_Data正确即可。屏幕的窗口设置是另一个高频坑很多驱动芯片需要先写列地址0x2A、行地址0x2B然后写0x2C进入GRANT写数据模式。如果你的_SetPixelIndex里没有设置窗口那画一个点会变成画一整条线甚至整屏乱花。在GUI_Init之前建议先直接调用GC9307_Init()再用几个裸的写像素函数画一个对角的测试线。这样做的好处是把UCGUI隔离在外如果线画不出来说明是驱动问题而不是UCGUI配置问题。随后在LCDConf.c的LCD_X_Config里配置分辨率、颜色格式和缓存void LCD_X_Config(void) { GUI_DEVICE_CreateAndLink(GUIDRV_Template_API, GUICC_565, 0, 0); LCD_SetSizeEx(0, 240, 320); LCD_SetVSizeEx(0, 240, 320); }GUICC_565对应RGB565颜色格式如果你之前测试是RGB格式且颜色正确这个保持不变就对了。GUIConf.h里的GUI_NUMBYTES也要给够默认可能会给8KB、16KB画复杂控件的时候不够的话会出现黑块、绘制异常。3.4 内存和时基也要顺便分配好UCGUI在STM32上运行时需要两样隐藏资源一块内存池一个时间基准。内存池在GUIConf.h里配置示例工程里一般是静态数组的形式#define GUI_NUMBYTES (16 * 1024)在GUI_X_Config函数里确认有初始化内存池的操作。有时候你发现GUI_Init正常但跑一段界面就卡住可能就是这里没设置或者设置太小。我用8KB跑简单界面没问题但要跑控件多一点的Demo16KB左右比较稳。时间基准方面UCGUI的GUI_X_Delay、GUI_X_GetTime等函数需要有一个ms级时基。很多示例工程用SysTick或者用TIM6做1ms中断。如果时间基准没接好UCGUI的动画效果会卡住按钮点击反馈也不灵敏。建议在工程里单独建一个定时器不要和操作系统的时基混在一起省心很多。4. 常见问题与排查实录这部分内容是踩了几次很深的坑之后总结出来的。尤其是同时用过ST7735和GC9307之后你会明显感受到“同一个UCGUI内核、不同底屏”之间的差异问题几乎都集中在底层驱动和配置上。4.1 白屏、花屏、卡死三种典型表现白屏最常见的原因是屏没有初始化成功或者复位脚没有拉高、电源没给够、SPI没通。我建议先在KEIL的调试模式下设置几个断点看看程序有没有走到GC9307_Init里面。还有一种情况是初始化序列里最后缺少0x29Display ON指令屏内部状态不正确显示是白的。花屏颜色和图像明显错乱。先确认RGB顺序再看扫描方向最后查窗口设置。如果图像是斜着撕裂的多半是窗口坐标或者SPI数据字节序有问题调整0x36寄存器或者把16位写数据的字节换一下顺序。卡死GUI_Init卡死或者GUI_Delay时死等。检查GUI_NUMBYTES是否足够检查GUI_X_GetTime是否启动了时基检查内存池是否初始化。我看过不少新人把GUI_X_Config里内存池的初始化注释掉了结果GUI_Init一进去就HardFault。我把这几种情况整理成了一张速查表方便定位异常表现优先排查方向备注完全白屏复位、SPI接线、初始化序列先跑裸机清屏测试隔离UCGUI花屏偏色颜色格式、RGB顺序用纯色测试验证镜像、横竖屏错乱0x36寄存器方向位调整MADCTL不要动UCGUI方向画面撕裂SPI速度、DMA缓存降低SPI时钟或修正数据负载GUI_Init卡死内存池、时基检查GUI_NUMBYTES和SysTick4.2 编译环境的一些冷门问题用KEIL跑UCGUI工程偶尔会遇到编译报错最常见的是MDK版本差异。比如工程编译时提示“cannot open source file”或者缺某个启动文件可以确认一下工程里是不是包含了正确的CMSIS和启动文件最好使用和你当前MDK匹配的Device Family Pack。还有一个哭笑不得的状况J-Link插上电脑KEIL提示“J-Link is not a genuine product”之类的问题然后拒绝下载调试。这个和UCGUI本身没有关系但会卡住后续调试。稳妥的解决办法是换成ST-Link或者使用官方正版调试器反正STM32支持ST-Link很方便不用在盗版破解上面花时间不稳定还浪费时间。另外编译速度慢是KEILUCGUI工程的通病。UCGUI源码文件多而且GUI_X、LCD_Driver等文件之间依赖关系复杂改一个头文件经常导致整个工程重新编译。我的做法是把UCGUI的源码编译成静态库平时只重新编译应用层代码效率提升明显。4.3 一个让我印象深刻的GC9307案例有一次我把一个ST7735屏的工程改成GC9307屏UI界面显示出来整体偏红颜色始终不对劲。排查了大概两小时最后发现是GC9307的初始化序列里有一项RGB接口配置和ST7735不同导致内部颜色空间映射错了。当时问题就出在我直接偷懒只把初始化序列整体替换了没有细看每个寄存器的含义。那之后我养成了一个习惯任何屏幕驱动芯片更换时初始化序列必须逐条对一遍寄存器意义不能整段照搬。尤其是那些和颜色格式、扫描方向、电源控制相关的指令不同芯片之间的定义差异很大。UCGUI本身能画图但颜色的正确性最终取决于底层驱动对颜色的解读这个环节没有任何捷径可走。再补充一个判断扫描方向的小技巧初始化完成后在纯色刷屏的同时用UCGUI在四个角落各画一个不同颜色的点然后和屏的物理方向对照。如果点和预期位置不对应直接调整初始化序列里的方向寄存器这是最快的校正手段。比我以前一边改代码一边编译刷屏要高效得多。5. 最后讲点实战体会这套“移植好的KEIL工程改LCD驱动”的方案真正价值在于UCGUI的移植难点从来不在GUI本身而在屏幕驱动适配。图形库只要内存给足、时基给够基本不会出幺蛾子反倒是看起来最不起眼的写寄存器函数、初始化序列、颜色格式会把你折腾到怀疑人生。我个人的建议是拿到这种示例工程先按照“裸机清屏测试 → 画点画线测试 → UCGUI Demo测试”的顺序走一遍。前两步通过了UCGUI那边基本不会再有大问题前两步失败就直接在底层驱动里查别急着去GUI代码里找原因。另外如果工程里已经有触摸屏驱动注意触摸坐标和显示坐标的对应关系横屏竖屏切换时这两个坐标系很容易反转。我的做法是先在应用层打印一下触摸坐标再用GUI_TOUCH_Calibrate校准每次都稳定通过。这个工程后续还可以继续扩展加一个带缓存的FrameBuffer切界面会更顺滑或者把DMA用起来刷屏速度能提升一大截。先把显示驱动这关过好后面这些升级都是水到渠成的事。本文还有配套的精品资源点击获取
返回列表