简介这份资源面向使用STM32F10X系列单片机的嵌入式开发者聚焦在STM32F103平台上基于EMWIN库实现PNG图片显示与GUI界面构建。内容涵盖LCD驱动配置、EMWIN库集成与裁剪、PNG解码及RGB565格式转换、窗口控件与事件处理机制以及针对具体硬件平台的移植调试思路适合具备一定嵌入式基础、希望提升图形界面开发能力的工程师参考。压缩包共397个文件约33.51MB以206个h头文件与149个c源文件为主体另含16个png示例图片、汇编启动文件、lib库文件、uvprojx工程文件及hex固件等工程结构完整便于直接编译与二次开发。目前已有181人学习关注。通过该资源可获取一套可运行的PNG显示工程实例理解EMWIN在STM32F103上的移植要点、内存优化与调试方法为物联网设备、工业控制等场景下的GUI开发提供实践参考。1. STM32F103 上跑 EMWIN 显示 PNG为什么值得折腾手上有一块 STM32F103 最小系统板屏幕也点起来了但界面里想放个带透明通道的图标用 BMP 转数组那套流程做出来边缘全是锯齿和黑边。这时候很多人会想到 PNG——压缩率高、支持 Alpha 通道、设计稿直接导出就能用。问题在于STM32F103 没有硬件 PNG 解码器EMWIN 默认也不带 PNG 解码能力直接丢一张 PNG 进去屏幕要么花屏要么什么都不显示。这个方向要解决的就是这件事在 STM32F10X 系列单片机上让 EMWIN 把 PNG 图片正确渲染到 LCD 上。适合已经跑通 EMWIN 基础移植、能显示文字和 BMP 的开发者也适合正在做单片机毕业设计、想给界面加图标和启动图的同学。核心矛盾只有一个——RAM 和 Flash 都紧张PNG 解码又要吃内存怎么在资源受限的前提下把这条路走通是后面几章要拆开讲的东西。2. PNG 解码在 STM32F103 上的三条路选哪条不翻车2.1 先搞清楚 EMWIN 为什么默认不认 PNGEMWIN 的绘图接口是分层的最底层是 LCD 驱动往上是 GUI 绘制再往上是带 Alpha 混合的绘制。PNG 文件本身是压缩格式必须先解压成位图数据再交给 EMWIN 的绘制函数。EMWIN 官方版本里确实有 PNG 支持模块但它依赖一个解码库而且默认配置下不一定被编译进来。在 STM32F103 这种 72MHz、20KB RAM 起步的芯片上EMWIN 的 PNG 模块能不能跑取决于两个硬条件一是解码时的临时缓冲区够不够二是解码后的位图数据放哪里。很多人移植完 EMWIN 发现GUI_PNG_Draw这个函数根本链接不到就是因为 PNG 模块没被使能或者解码库没加进工程。常见做法是去 EMWIN 的配置文件里找GUI_SUPPORT_PNG这类宏把它打开然后把对应的解码源文件加进 Keil MDK 工程。但这里有个坑不同版本的 EMWIN 对这个宏的命名和依赖不一样有的版本 PNG 解码是独立的一个.c文件有的版本把它合进了主库。所以第一步不是急着写代码而是先确认你手上的 EMWIN 包到底带不带 PNG 模块。2.2 方案一用 EMWIN 自带 PNG 模块最省事但有前提如果你的 EMWIN 版本自带 PNG 解码那这是最直接的路。操作步骤不复杂但每一步都要确认到位。第一步在 EMWIN 的配置头文件里使能 PNG 支持。通常是在GUIConf.h或者LCDConf.h附近找到类似下面这样的宏/* GUIConf.h 中使能 PNG 支持 */ #define GUI_SUPPORT_PNG 1 /* 打开 PNG 解码模块 */这个宏打开之后EMWIN 在编译时会去链接 PNG 解码相关的目标文件。如果编译报错说找不到符号说明你的库里没有对应的解码实现那就得换方案。第二步把 PNG 文件转成 C 数组。EMWIN 不能直接读文件系统里的 PNG除非你挂了 FatFS最稳的方式是用工具把 PNG 转成 C 数组编译进 Flash。EMWIN 官方有个BmpCvt工具可以导出 C 文件。导出时要注意几个参数参数项建议值说明输出格式C 数组直接编译进工程颜色格式带 Alpha 的格式保留透明通道压缩方式不压缩或 RLE解码时省 RAM数组类型const 数组放 Flash 不占 RAM第三步在代码里调用绘制函数。假设数组名是_acMyIcon绘制代码如下/* 在指定坐标绘制 PNG 数组 */ GUI_PNG_Draw(_acMyIcon, sizeof(_acMyIcon), 100, 50);GUI_PNG_Draw的第一个参数是数组首地址第二个是数组字节长度后两个是屏幕坐标。这里要注意数组长度必须传对传错了解码会越界表现就是花屏或者死机。这条路的优点是代码量少EMWIN 帮你把解码和 Alpha 混合都做了。缺点是自带解码库对 RAM 的占用不透明在 F103 上如果 PNG 尺寸稍大解码缓冲区就可能撑爆。我一般会先用一张 32x32 的小图标试跑通了再换大图。2.3 方案二预解码成带 Alpha 的位图数组最稳但费 Flash如果自带 PNG 模块跑不起来或者 RAM 实在不够那就走预解码路线。思路很简单在 PC 上把 PNG 解码成位图数据包括 Alpha 通道然后转成 C 数组单片机端只负责把位图数据搬到 LCD 上不做任何解码。这样做的好处是单片机端零解码开销RAM 占用可控坏处是 Flash 占用大。一张 64x64 的 32 位带 Alpha 位图数据量是 64×64×4 16384 字节也就是 16KB Flash。如果图标多Flash 很快就满了。STM32F103C8T6 只有 64KB Flash放不了几张。转换工具可以用BmpCvt导出时选择带 Alpha 的位图格式比如Bitmap with alpha。导出的 C 数组里每个像素占 4 字节分别是 B、G、R、A。绘制的时候用 EMWIN 的 Alpha 混合绘制函数/* 绘制带 Alpha 通道的位图 */ GUI_DrawBitmapAlpha(bmMyIcon, 100, 50);GUI_DrawBitmapAlpha需要传入一个GUI_BITMAP结构体指针这个结构体里包含了位图宽高、每像素字节数、调色板指针和数据指针。用 BmpCvt 导出的时候它会自动生成这个结构体你只需要把生成的.c文件加进工程就行。这里有个细节EMWIN 的 Alpha 混合绘制对源位图的 Alpha 通道顺序有要求有的工具导出的是 ARGB有的是 BGRA。如果画出来颜色不对或者透明区域变黑大概率是通道顺序反了。解决办法是在 BmpCvt 里调整导出格式或者在代码里手动交换通道。2.4 方案三外挂 PNG 解码库灵活但移植成本高还有一条路是外挂一个轻量级 PNG 解码库比如lodepng或者miniz配合 PNG 解析。这条路适合需要动态加载 PNG 的场景比如从 SD 卡读 PNG 文件显示。但在 STM32F103 上这条路我不太推荐原因有两个一是解码库本身要占 Flash 和 RAM二是解码一张稍大的 PNG 需要几十 KB 的临时缓冲区F103 的 RAM 根本不够。如果非要用建议只用来解码小尺寸 PNG并且把解码缓冲区开到最小。lodepng有一个LODEPNG_NO_COMPILE_ENCODER宏可以只保留解码功能省掉编码部分的代码。但即便如此在 F103 上跑起来也很勉强属于能跑但不好用的状态。三条路对比下来我的建议是先试方案一跑不通再退到方案二。方案三除非有特殊需求否则不要在 F103 上碰。3. 从 PNG 文件到屏幕像素手把手跑通最小显示流程3.1 工程准备EMWIN 移植确认清单在开始 PNG 显示之前先确认你的 EMWIN 基础移植是通的。所谓通至少要满足三个条件LCD 能刷纯色、能显示 ASCII 字符、能显示一张不带 Alpha 的 BMP。这三个条件缺一个后面 PNG 调试就会变成玄学。确认清单如下LCD 驱动层LCD_Init能正常初始化LCD_DrawPixel能画点EMWIN 配置层GUI_Init返回 0GUI_Clear能清屏颜色转换LCD_COLOR_INDEX和GUI_COLOR的格式匹配通常是 RGB565定时器给 EMWIN 提供GUI_X_GetTime和GUI_X_Delay的时基如果 BMP 显示正常但 PNG 显示异常问题基本锁定在 PNG 解码模块或者 Alpha 混合环节不用再回头查 LCD 驱动。3.2 用 BmpCvt 把 PNG 转成 EMWIN 能吃的 C 数组BmpCvt 是 EMWIN 配套的工具在 Segger 官网的 EMWIN 包里能找到。打开之后加载 PNG 文件然后按下面的步骤操作第一步在Image菜单里选择Convert into然后选C bitmap file。第二步在弹出的对话框里设置颜色格式。如果要保留透明通道选ARGB或者BGRA具体选哪个取决于你的 LCD 颜色格式和 EMWIN 配置。第三步设置输出选项勾选Use const让数组放 Flash勾选Use alpha保留透明通道。第四步点OK生成.c文件。生成的.c文件里会有类似这样的内容/* 由 BmpCvt 生成的位图数组 */ const U8 _acMyIcon[16384] { 0x00, 0x00, 0x00, 0x00, /* 像素数据 */ /* ... 省略 ... */ }; /* 位图结构体 */ const GUI_BITMAP bmMyIcon { 64, /* XSize */ 64, /* YSize */ 256, /* BytesPerLine */ 32, /* BitsPerPixel */ _acMyIcon, /* Pointer to data */ 0, /* Pointer to palette */ GUI_DRAW_BMP8888 /* Drawing method */ };这里GUI_DRAW_BMP8888表示每像素 32 位带 Alpha 通道。如果你的 EMWIN 版本不支持这个绘制方法可能需要改成GUI_DRAW_BMP565或者其他格式但那样就没有 Alpha 通道了。3.3 在 EMWIN 任务里调用 PNG 绘制并验证把生成的.c文件加进 Keil 工程然后在主循环或者 GUI 任务里调用绘制函数。下面是一个最小的验证代码/* 主函数中的 GUI 初始化与绘制 */ int main(void) { /* 硬件初始化 */ LCD_Init(); GUI_Init(); /* 清屏 */ GUI_SetBkColor(GUI_BLACK); GUI_Clear(); /* 绘制 PNG 位图坐标 (100, 50) */ GUI_DrawBitmap(bmMyIcon, 100, 50); /* 进入主循环 */ while (1) { GUI_Delay(100); } }这段代码的逻辑是先初始化 LCD 和 EMWIN清屏之后在指定坐标绘制位图。GUI_DrawBitmap是 EMWIN 的通用位图绘制函数它会根据GUI_BITMAP结构体里的DrawMethod字段自动选择绘制方式。如果DrawMethod是GUI_DRAW_BMP8888EMWIN 会走 Alpha 混合路径。验证的时候先看屏幕有没有图像出来。如果图像出来了但透明区域是黑色说明 Alpha 通道没生效检查DrawMethod和颜色格式。如果图像位置不对检查坐标参数。如果图像颜色整体偏了检查 RGB 通道顺序。3.4 参数怎么调Alpha 混合与颜色格式的对应关系Alpha 混合的效果取决于两个东西源位图的 Alpha 通道值和 EMWIN 的混合配置。EMWIN 默认的 Alpha 混合是开启的但混合质量可以调。在GUIConf.h里有几个相关宏/* Alpha 混合相关配置 */ #define GUI_USE_ARGB 1 /* 使用 ARGB 格式 */ #define GUI_ALPHA_BLEND 1 /* 使能 Alpha 混合 */GUI_USE_ARGB决定了 EMWIN 内部把颜色值当成 ARGB 还是 BGRA 处理。这个宏必须和 BmpCvt 导出的格式一致否则透明区域会变成黑色或者颜色错乱。我一般会在 BmpCvt 里导出 ARGB 格式然后把GUI_USE_ARGB设为 1。还有一个参数是GUI_ALPHA_BLEND它控制是否在绘制时做混合。如果关掉这个宏Alpha 通道会被忽略透明区域直接显示源颜色。调试的时候可以先关掉它确认图像能正常显示再打开看混合效果。4. 避坑与排查PNG 显示翻车的五个典型现场4.1 现象编译报错找不到 GUI_PNG_Draw原因EMWIN 库里没有 PNG 解码模块或者GUI_SUPPORT_PNG宏没打开。有些 EMWIN 版本把 PNG 解码放在独立的.c文件里需要手动加进工程。解决先确认 EMWIN 包里的文件列表找有没有GUI_PNG.c或者类似名字的文件。如果有加进 Keil 工程并在GUIConf.h里打开GUI_SUPPORT_PNG。如果没有说明这个版本不带 PNG 解码只能走预解码路线。4.2 现象图像显示出来但透明区域是黑色原因Alpha 通道没有被正确识别EMWIN 把透明像素当成了黑色不透明像素。通常是GUI_USE_ARGB宏和 BmpCvt 导出的格式不一致。解决检查 BmpCvt 导出时选的格式如果是 ARGB就把GUI_USE_ARGB设为 1如果是 BGRA就设为 0。改完之后重新编译清屏再画一次。如果还是黑色检查GUI_BITMAP结构体里的DrawMethod是不是GUI_DRAW_BMP8888。4.3 现象图像花屏或者只显示一部分原因数组长度传错或者解码缓冲区不够。花屏通常是因为解码时越界访问了数组后面的内存。解决先确认GUI_DrawBitmap或GUI_PNG_Draw传入的数组长度和实际数组大小一致。如果是用GUI_PNG_Draw第二个参数必须是数组的字节长度不是像素个数。如果是预解码位图检查GUI_BITMAP结构体里的BytesPerLine和XSize是否匹配。4.4 现象图像颜色整体偏红或偏蓝原因RGB 通道顺序反了。BmpCvt 导出的格式可能是 BGR但 EMWIN 按 RGB 解析。解决在 BmpCvt 里换一种导出格式或者在代码里手动交换 R 和 B 通道。手动交换的代码可以写一个循环遍历像素数据把每 4 字节里的第 0 字节和第 2 字节交换。但这样会增加 CPU 开销最好还是在导出时就把格式设对。4.5 现象小图正常大图死机原因RAM 不够。PNG 解码或者 Alpha 混合需要临时缓冲区图越大缓冲区需求越大。STM32F103 的 RAM 通常只有 20KB 到 64KB解码一张 128x128 的 PNG 就可能需要几十 KB 的缓冲区。解决换用预解码方案把解码工作放到 PC 上做单片机端只负责搬运数据。如果非要在单片机端解码把 PNG 尺寸限制在 64x64 以内并且把 EMWIN 的动态内存池调大。动态内存池的大小在GUIConf.h里的GUI_NUMBYTES宏定义可以试着从 4096 加到 8192 或者 16384但要注意别超过芯片 RAM 上限。5. 进阶技巧用 PNG 做启动图和图标集的几个实战习惯5.1 启动图不要用全屏 PNG用分块拼接全屏 PNG 在 F103 上基本跑不动320x240 的 32 位位图数据量是 300KBFlash 根本放不下。我的做法是把启动图切成若干块每块不超过 64x64然后逐块绘制。这样每块的数据量可控绘制的时候也不容易因为 RAM 不够而翻车。分块拼接的代码逻辑很简单用一个循环遍历所有块计算每块的目标坐标然后调用GUI_DrawBitmap。块的顺序和坐标可以写在一个常量数组里方便调整。5.2 图标集用预解码位图统一格式省心如果界面里有一组图标建议全部用同一种格式导出比如统一用 ARGB8888。这样 EMWIN 的绘制路径一致不会出现有的图标透明正常、有的图标透明失效的情况。导出的时候用 BmpCvt 的批量转换功能一次把所有 PNG 转成 C 数组省得一个个手动操作。图标尺寸建议控制在 32x32 到 48x48 之间。再大 Flash 吃不消再小在高分屏上看起来模糊。如果屏幕分辨率是 320x24032x32 的图标已经够用了。5.3 验证 PNG 显示是否正确的三个检查点第一个检查点纯色背景上画一个带透明区域的 PNG看透明区域是否透出背景色。如果透出的是黑色说明 Alpha 没生效。第二个检查点画一个半透明的 PNG看混合后的颜色是否和 PC 上预览的一致。如果偏色检查通道顺序。第三个检查点连续画多张 PNG看是否有内存泄漏或者花屏。如果有检查 EMWIN 的动态内存池是否够用。这三个检查点跑完基本能覆盖 90% 的 PNG 显示问题。剩下的 10% 通常是硬件或者 LCD 驱动的问题不在 PNG 本身的范畴里。5.4 一个我踩过的坑BmpCvt 导出的数组默认不是 constBmpCvt 导出 C 数组的时候默认可能不加const修饰。这意味着数组会被放到 RAM 里而不是 Flash 里。在 F103 上一个 16KB 的数组放到 RAM 里直接就把内存吃光了程序跑起来就死机。解决办法是在 BmpCvt 的导出选项里勾上Use const或者在生成的.c文件里手动给数组加上const关键字。加完之后数组会被链接器放到 Flash 的只读段不占 RAM。这个坑我踩过不止一次后来养成了习惯每次导出 C 数组先检查有没有const。希望帮到你。本文还有配套的精品资源点击获取