嵌入式GUI开发实战:emWin 2D绘图与图像显示优化指南
1. 嵌入式GUI开发中的2D绘图与图像显示从基础到实战在嵌入式设备上实现一个流畅、美观的图形用户界面从来都不是一件容易的事。屏幕尺寸有限、处理器性能不高、内存捉襟见肘这些硬件限制就像一道道紧箍咒让GUI开发变得极具挑战性。但用户对体验的要求却越来越高一个反应迟钝、画面粗糙的界面足以让一款优秀的产品黯然失色。我接触过不少项目从简单的工业仪表盘到复杂的医疗设备人机界面核心诉求都离不开两点画得快和画得好。“画得快”考验的是图形库的底层绘制效率和对硬件资源的极致利用“画得好”则要求图形库提供丰富、灵活的API让开发者能轻松绘制出各种基本图形、曲线图表并能流畅地显示复杂的图片资源。这正是SEGGER emWin图形库的强项所在。它不仅仅是一个图形渲染引擎更是一套为嵌入式环境深度优化的完整解决方案。其2D图形库和图像显示模块是构建任何嵌入式GUI的基石。今天我就结合自己多年的踩坑经验带你深入emWin的2D世界不仅告诉你API怎么用更会分享在真实项目中如何权衡选择、规避陷阱以及榨干每一KB内存的实用技巧。2. 核心思路与方案选型为什么是emWin在开始敲代码之前我们得先想清楚面对琳琅满目的嵌入式GUI方案为什么emWin常常成为首选尤其是在涉及复杂2D绘图和图像显示的场合。这背后是一系列工程化的权衡。2.1 硬件抽象与驱动适配emWin的核心优势在于其出色的硬件抽象层HAL。它通过一个精心设计的LCD驱动接口将上层应用与底层硬件完全解耦。这意味着你为一块STM32的FSMC接口TFT屏编写的绘图代码几乎可以无缝移植到使用SPI接口的OLED屏上只需更换底层驱动即可。这种可移植性在项目迭代或产品线扩展时价值连城。在2D绘图方面emWin提供了软件实现和硬件加速如果MCU有GPU或2D加速器两种路径。对于大多数没有专用图形硬件的MCUemWin的软件算法经过了高度优化例如它的多边形填充算法、抗锯齿线段绘制在ARM Cortex-M系列内核上能发挥出接近极限的性能。我曾经在一个72MHz的Cortex-M3平台上测试绘制一个带抗锯齿的圆角矩形窗口emWin比某些开源库快出近一倍这直接决定了界面响应的“跟手”程度。2.2 内存管理策略嵌入式开发永恒的主题是内存。emWin在内存管理上非常灵活。它自带内存池管理但你也可以完全接管使用自己的内存分配函数通过GUI_ALLOC_AssignMemory。对于2D绘图尤其是使用内存设备Memory Device进行局部重绘或双缓冲时这种控制权至关重要。图像显示更是内存消耗大户。一张320x240的24位色BMP图未压缩就需要225KB的存储空间直接加载到RAM中对于许多MCU来说是难以承受的。emWin的“Ex”系列函数如GUI_BMP_DrawEx,GUI_JPEG_DrawEx采用流式解码Streaming Decode方案只需提供一个小缓冲区通常是一行或几行像素的数据由回调函数按需从存储介质如SPI Flash、SD卡读取数据从而实现了“小内存显示大图”。这种设计哲学是嵌入式资源受限思维的典型体现。2.3 功能完备性与代码尺寸emWin的2D图形库提供了从点、线、矩形、圆、椭圆、多边形、圆弧到饼图、曲线图的全套绘制函数。并且每个基本图形都配套了填充Fill和轮廓Draw两种版本。这种完整性意味着你不需要自己再去实现任何基础几何图形减少了重复造轮子的风险和潜在的bug。更难得的是在保持功能完备的同时emWin支持链接时优化Link-Time Optimization。你可以通过配置只链接你实际用到的模块。例如如果你的项目只用BMP和PNG那么JPEG和GIF的解码库就不会被包含进最终固件从而有效控制代码体积。我曾经将一个包含基本控件、2D绘图和BMP显示的GUI系统在开启Thumb-2指令集和Os优化后压缩到了150KB以下这对于片内Flash只有256KB的芯片来说非常友好。3. 2D绘图函数深度解析与实战要点了解了为什么选emWin接下来我们进入实战环节。emWin的2D API看似简单但用得好与不好效果和性能天差地别。3.1 坐标系与绘图上下文emWin使用标准的笛卡尔坐标系原点(0,0)默认在屏幕左上角。所有绘图函数的坐标参数都是基于当前窗口Window或活动层Layer的客户区Client Area。理解“当前窗口”的概念是关键。通过GUI_SetClipRect()可以设置裁剪区域所有绘图操作只会影响该区域内的像素这在制作局部动画或防止绘制溢出时非常有用。绘图上下文GUI Context是一个容易被忽略但极其重要的概念。它不是一个显式的对象而是一组当前生效的绘图属性集合包括当前颜色由GUI_SetColor()和GUI_SetBkColor()设置影响线条、文本和填充的颜色。当前字体由GUI_SetFont()设置。画笔尺寸由GUI_SetPenSize()设置影响线条宽度。绘制模式如GUI_TM_NORMAL覆盖、GUI_TM_TRANS透明忽略背景色、GUI_TM_REV反色等通过GUI_SetTextMode()设置但它也影响某些图形绘制。重要提示GUI_SetTextMode(GUI_TM_TRANS)在显示带透明背景的位图或叠加文本时常用但请注意它可能会轻微影响绘制性能因为需要读取目标像素进行混合计算。3.2 基本图形绘制从简单到复杂1. 点与线GUI_DrawPoint()是最基本的操作。GUI_DrawLine()和GUI_DrawPolyLine()则用于绘制线段和多段线。这里有一个性能技巧如果需要连续绘制多条相连的线段使用GUI_DrawPolyLine()一次性传入所有顶点数组比多次调用GUI_DrawLine()效率高得多因为它减少了函数调用开销和上下文设置次数。2. 矩形与多边形矩形绘制函数GUI_DrawRect()和GUI_FillRect()最常用。但多边形绘制GUI_FillPolygon()更有讲究。它的参数是一个顶点坐标数组。这里有个大坑emWin要求多边形必须是“简单多边形”不能自相交且顶点必须按顺时针或逆时针顺序依次给出。如果顺序错乱填充结果会不可预测。在项目中我通常先用数学库或自己写对多边形顶点进行排序确保万无一失。/* 绘制一个填充的箭头多边形 */ static const GUI_POINT aPoints[] { {0, 10}, {20, 10}, {20, 0}, {30, 15}, {20, 30}, {20, 20}, {0, 20} }; GUI_FillPolygon(aPoints, GUI_COUNTOF(aPoints), 100, 100);3. 圆、椭圆与圆弧GUI_DrawCircle(),GUI_FillCircle(),GUI_DrawEllipse(),GUI_FillEllipse()用法直观。但绘制圆弧GUI_DrawArc()时角度参数a0和a1是以度为单位0度指向3点钟方向角度值逆时针增加。这个约定和某些数学库不同务必注意。椭圆绘制目前有一个限制GUI_DrawArc()的ryY轴半径参数实际上未被使用函数内部使用rx作为统一半径来绘制圆弧。这意味着在emWin V5.28中GUI_DrawArc()只能画正圆的弧不能画椭圆的弧。如果需要椭圆弧通常需要自己用短线段来逼近。4. 曲线图与饼图GUI_DrawGraph()用于快速绘制折线图它接受一个I16有符号16位整数数组作为Y值序列。这个函数非常高效因为它内部是连续的GUI_DrawLine()调用。但请注意Y值是基于你提供的y0坐标的偏移量。如果你的数据是绝对坐标需要先进行转换。GUI_DrawPie()用于绘制扇形饼图。它的一个经典应用场景是绘制仪表盘的指针或扇形进度条。结合不同的起始和结束角度可以轻松实现环形菜单或百分比显示。3.3 高级技巧脏矩形与撕裂效应避免当界面只有小部分区域需要更新时如一个跳动数字、一个移动的图标重绘整个屏幕是巨大的性能浪费。emWin的脏矩形Dirty Rectangle机制可以完美解决这个问题。通过GUI_DIRTYDEVICE_Create()创建一个脏矩形设备后所有后续的绘图操作都会被记录其影响的矩形区域。在需要更新物理显示屏时比如在GUI_Exec()循环中调用GUI_DIRTYDEVICE_Fetch()获取这个脏矩形区域的信息位置、大小然后只将这一小块区域的数据通过LCD驱动刷到屏幕上。这能极大减少总线带宽占用和刷新时间对于电池供电的设备尤为重要。GUI_DIRTYDEVICE_INFO DirtyInfo; GUI_DIRTYDEVICE_Create(); // 通常在初始化时创建一次 // ... 执行一系列绘图操作 ... if (GUI_DIRTYDEVICE_Fetch(DirtyInfo)) { // 只将DirtyInfo描述的矩形区域更新到显示屏 LCD_UpdateRect(DirtyInfo.x0, DirtyInfo.y0, DirtyInfo.xSize, DirtyInfo.ySize); }撕裂效应Tearing是另一个在显示动态内容时常见的问题。它发生在显示屏的刷新过程逐行扫描中帧缓冲区的数据被更改导致屏幕上半部分显示旧帧下半部分显示新帧出现一条明显的撕裂线。emWin提供了GUI_SetRefreshHook()函数来应对。你可以在此钩子函数中等待显示屏的垂直消隐期V-Blank或撕裂效应信号TE Signal确保只在屏幕不扫描的时候更新帧缓冲区。这通常需要硬件显示屏的TE引脚和驱动层的配合。4. 图像文件显示BMP与JPEG的嵌入式生存之道在资源受限的嵌入式系统上显示图片是一场存储空间、内存容量和处理器性能的三角博弈。emWin提供了BMP和JPEG两种最常用格式的支持并给出了不同的解决方案。4.1 BMP图像简单直接但体积庞大BMP是未经压缩的位图格式显示它几乎就是内存拷贝速度最快但代价是巨大的存储空间占用。1. 编译时集成推荐用于小图标、LOGO对于界面中固定不变的、尺寸较小的图标如按钮图标、状态指示灯最佳实践是使用SEGGER提供的Bitmap Converter工具将其转换为C数组直接编译链接到代码中。这样做的好处是零运行时解码开销图像数据已经是显存友好的格式取决于配置GUI_DrawBitmap()可以直接绘制。存储位置灵活数组可以放在内部Flash节省RAM或外部QSPI Flash中。支持多种色彩格式工具可以转换成目标显示屏所需的色彩格式如RGB565, ARGB8888。// 使用Bitmap Converter转换后生成的文件 #include company_logo.c GUI_DrawBitmap(bmcompany_logo, x, y);2. 运行时解码用于可变或大图对于需要从文件系统如SD卡、U盘动态加载的BMP图使用GUI_BMP_Draw()或GUI_BMP_DrawEx()。GUI_BMP_Draw(): 要求将整个BMP文件先读入RAM。这对于大图来说通常是不可行的。GUI_BMP_DrawEx():流式解码的核心。你只需要提供一个回调函数pfGetDataemWin会在需要解码下一行像素时调用它你从存储介质中读取一小块数据比如一行即可。这几乎不占用额外RAM。static int _GetData(void *p, const U8 **ppData, unsigned NumBytesReq) { FIL *pFile (FIL *)p; UINT br; static U8 aBuffer[1024]; // 一个小缓冲区 if (NumBytesReq sizeof(aBuffer)) { NumBytesReq sizeof(aBuffer); } f_read(pFile, aBuffer, NumBytesReq, br); *ppData aBuffer; return br; } void ShowBMPFromSD(const char *sFilename, int x, int y) { FIL file; if (f_open(file, sFilename, FA_READ) FR_OK) { GUI_BMP_DrawEx(_GetData, file, x, y); f_close(file); } }4.2 JPEG图像高压缩比解码消耗CPUJPEG通过有损压缩可以极大减少图片的存储空间非常适合存储照片等复杂图像。但解码过程需要大量的CPU运算和临时内存。1. 内存消耗估算emWin的JPEG解码器需要大约33KB的固定RAM作为工作缓冲区外加与图片宽度相关的动态内存。计算公式近似为所需RAM ≈ 图片X方向像素数 * 80字节 33KB。 例如解码一张800x600的JPEG图片大约需要 800 * 80 / 1024 33 ≈ 62 33 95KB 的可用堆内存。你必须确保你的系统有足够的内存池否则解码会失败甚至导致系统崩溃。2. 渐进式JPEGProgressive JPEG的陷阱普通的JPEGBaseline是按从上到下的顺序编码的。渐进式JPEG则先存储一个低质量的全图再多次叠加细节。emWin支持渐进式JPEG但有一个严重性能问题为了解码任意一行它都可能需要从头开始扫描整个文件数据流。如果内存不足以一次性解码整张图采用分带Banding解码解码速度会变得极慢。在嵌入式项目中如果图片源不可控建议在PC端用工具将渐进式JPEG转换为标准的基线JPEG。3. 性能优化内存设备Memory Device由于JPEG解码开销大绝对避免在频繁调用的回调如WM_PAINT消息处理中直接调用GUI_JPEG_Draw()。正确的做法是使用内存设备。创建一个与图片等大或稍大的内存设备。将内存设备设置为当前绘制目标。在该内存设备上解码并绘制JPEG图片这步只执行一次。后续需要显示该图片时只需使用GUI_MEMDEV_CopyToLCD()或GUI_MEMDEV_Draw()将内存设备中的内容快速拷贝到屏幕指定位置。这相当于把解码后的图片“缓存”了起来用空间换时间。GUI_HMEM hMemJPEG; void CreateJPEGCache(const void *pData, int Size) { GUI_JPEG_INFO Info; GUI_JPEG_GetInfo(pData, Size, Info); // 获取图片尺寸 hMemJPEG GUI_MEMDEV_CreateFixed(0, 0, Info.XSize, Info.YSize, GUI_MEMDEV_HASTRANS, GUI_MEMDEV_APILIST_32, NULL); GUI_MEMDEV_Select(hMemJPEG); GUI_JPEG_Draw(pData, Size, 0, 0); // 解码并绘制到内存设备 GUI_MEMDEV_Select(0); // 切回默认设备LCD } void ShowCachedJPEG(int x, int y) { // 快速显示无需再次解码 GUI_MEMDEV_Draw(hMemJPEG, x, y); }4.3 图像缩放显示无论是BMP还是JPEGemWin都提供了DrawScaled版本函数如GUI_BMP_DrawScaledEx,GUI_JPEG_DrawScaled。它们通过分子(Num)和分母(Denom)参数来指定缩放比例。例如Num1, Denom2表示缩小到原图的1/2Num3, Denom2表示放大到原图的1.5倍。注意事项缩放特别是放大操作是计算密集型任务并且会占用额外的临时内存。在低性能MCU上缩放大图可能导致明显的卡顿。如果可能尽量在PC端预处理图片生成所需尺寸的多个版本在嵌入式端根据情况选择加载而不是实时缩放。5. 实战流程与核心环节实现理论说再多不如一个完整的例子。假设我们要为一个智能家居控制面板实现一个主界面包含一个圆形背景、一个动态更新的温湿度曲线图和一个天气图标JPEG格式。5.1 环境搭建与初始化首先确保emWin已正确移植到你的目标平台。这通常包括实现LCDConf.c和GUIDRV_Template.c中的底层函数。初始化序列如下#include GUI.h void MainTask(void) { // 1. 硬件初始化时钟、SDRAM、LCD等应在调用GUI_Init前完成 // 2. 初始化emWin GUI_Init(); // 3. 设置默认字体、颜色等 GUI_SetFont(GUI_Font16_ASCII); GUI_SetColor(GUI_WHITE); GUI_SetBkColor(GUI_BLUE); GUI_Clear(); // 用背景色清屏 // 进入主循环 while(1) { DrawMainScreen(); GUI_Delay(100); // 延时并处理内部消息 } }5.2 绘制静态界面元素我们首先绘制静态部分一个渐变的圆形背景用同心圆模拟和静态文本。static void DrawStaticElements(void) { int i; // 绘制渐变圆形背景 for(i 50; i 0; i - 2) { GUI_SetColor(GUI_DARKBLUE i * 0x010101); // 简单模拟颜色变化 GUI_FillCircle(120, 120, i); } // 显示标题 GUI_SetColor(GUI_WHITE); GUI_SetFont(GUI_Font24B_ASCII); GUI_DispStringHCenterAt(Smart Home, 120, 20); // 显示标签 GUI_SetFont(GUI_Font16_ASCII); GUI_DispStringAt(Temperature:, 30, 180); GUI_DispStringAt(Humidity:, 30, 210); }5.3 实现动态曲线图曲线图需要定期更新。我们将最近50个温度数据存储在一个数组中并实现一个滚动刷新的效果。static I16 aTemperatureHistory[50] {0}; static int historyIndex 0; static void UpdateAndDrawGraph(void) { int i; int newValue GetSensorTemperature(); // 假设这个函数读取传感器值 // 更新数据数组先进先出 aTemperatureHistory[historyIndex] newValue; historyIndex (historyIndex 1) % GUI_COUNTOF(aTemperatureHistory); // 清除曲线图区域一个矩形区域 GUI_SetColor(GUI_BLACK); GUI_FillRect(150, 160, 300, 230); // 设置曲线颜色和画笔 GUI_SetColor(GUI_GREEN); GUI_SetPenSize(2); // 计算并绘制曲线 // 注意GUI_DrawGraph要求Y值是相对于起点的偏移量我们需要转换 // 我们假设温度范围是0-40度对应在160-230像素高度显示 I16 aGraphPoints[50]; for(i 0; i GUI_COUNTOF(aTemperatureHistory); i) { int idx (historyIndex i) % GUI_COUNTOF(aTemperatureHistory); // 将温度值映射到Y坐标偏移量 (0度-230, 40度-160) aGraphPoints[i] (I16)(230 - (aTemperatureHistory[idx] * 70 / 40)); } // 绘制曲线X方向从150开始每点间隔3像素 // 由于GUI_DrawGraph是画连续线我们这里用一个简化方法先移动画笔到起点 GUI_MoveTo(150, aGraphPoints[0]); for(i 1; i GUI_COUNTOF(aGraphPoints); i) { GUI_DrawLineTo(150 i*3, aGraphPoints[i]); } // 显示当前值 GUI_SetColor(GUI_YELLOW); GUI_DispDecAt(newValue, 100, 180, 2); }5.4 加载与显示JPEG天气图标天气图标从SD卡加载并使用内存设备进行缓存避免每次重绘都解码。static GUI_HMEM hMemWeatherIcon NULL; static void LoadWeatherIcon(void) { if (hMemWeatherIcon) return; // 已加载 FIL file; U8 *pBuffer; U32 fileSize; GUI_JPEG_INFO Info; if(f_open(file, 0:/weather/sunny.jpg, FA_READ) ! FR_OK) { return; } fileSize f_size(file); pBuffer GUI_ALLOC_Alloc(fileSize); // 分配内存加载文件 if(pBuffer) { UINT br; f_read(file, pBuffer, fileSize, br); f_close(file); // 获取图片信息 if(GUI_JPEG_GetInfo(pBuffer, fileSize, Info) 0) { // 创建内存设备并解码图片到其中 hMemWeatherIcon GUI_MEMDEV_CreateFixed(0, 0, Info.XSize, Info.YSize, GUI_MEMDEV_HASTRANS, GUI_MEMDEV_APILIST_32, NULL); if(hMemWeatherIcon) { GUI_MEMDEV_Select(hMemWeatherIcon); GUI_JPEG_Draw(pBuffer, fileSize, 0, 0); GUI_MEMDEV_Select(0); } } GUI_ALLOC_Free(pBuffer); // 释放文件数据内存 } } static void DrawWeatherIcon(void) { if(hMemWeatherIcon) { // 将缓存好的图标绘制到屏幕(250, 50)位置 GUI_MEMDEV_Draw(hMemWeatherIcon, 250, 50); } else { // 备用显示一个矩形框 GUI_SetColor(GUI_GRAY); GUI_FillRect(250, 50, 300, 100); GUI_SetColor(GUI_WHITE); GUI_DispStringHCenterAt(Icon, 275, 75); } }5.5 整合与主绘制函数最后将所有绘制步骤整合到主绘制函数中并在主循环中调用。static void DrawMainScreen(void) { // 绘制静态背景和文字通常只需一次 static int firstCall 1; if(firstCall) { DrawStaticElements(); LoadWeatherIcon(); // 加载并缓存图标 firstCall 0; } // 绘制动态部分 DrawWeatherIcon(); // 绘制图标从内存设备快速复制 UpdateAndDrawGraph(); // 更新并绘制曲线图 // ... 可以添加其他动态元素如时间更新等 }6. 常见问题、调试技巧与性能优化实录在实际项目中使用emWin的2D和图像功能时会遇到各种各样的问题。下面是我总结的一些典型问题及其解决方案。6.1 绘图闪烁问题问题描述在动态更新图形如进度条、动画时屏幕出现明显的闪烁。根本原因直接向显存或映射到LCD的帧缓冲区绘制当绘制过程较慢时用户会看到中间状态。解决方案使用内存设备Memory Device进行双缓冲这是最彻底的解决方案。将所有绘图操作在离屏的内存设备上完成然后一次性GUI_MEMDEV_CopyToLCD()到屏幕。这完全消除了闪烁但会消耗一块与绘制区域等大的内存。使用脏矩形更新如果无法承担双缓冲的内存开销务必启用脏矩形跟踪。只更新屏幕上真正发生变化的那一小块区域可以极大缩短“可见”的绘制时间减少闪烁感。优化绘制顺序先画背景再画前景。避免在同一区域用不同颜色反复绘制。6.2 图像显示花屏、错位或颜色异常问题描述显示的BMP或JPEG图片颜色不对或出现错位的条纹。排查步骤检查文件数据源对于DrawEx系列函数确保你的GetData回调函数返回了正确的数据。在回调中加入调试输出确认请求的字节数和实际读取的字节数是否匹配。文件读取指针定位是否正确检查色彩格式emWin内部和你的LCD驱动可能使用不同的色彩格式如RGB565, ARGB8888。确保你通过LCD_X_Config()配置的显示驱动色彩格式与GUI_BMP_Draw等函数解码输出的格式一致。BMP文件本身也有多种色彩深度1,4,8,16,24,32位确认emWin支持你使用的格式。检查内存对齐某些MCU的DMA或LCD控制器对内存地址有对齐要求如4字节对齐。确保你传递给绘图函数的缓冲区地址是安全的。使用GUI_ALLOC_Alloc分配的内存通常是对齐的。JPEG内存不足这是最常见的原因。如果JPEG解码时内存不足解码会静默失败或产生乱码。务必使用GUI_JPEG_GetInfo检查解码是否成功并确保系统堆内存大于前面提到的估算值图片宽*80 33KB。6.3 性能瓶颈分析与优化当界面反应迟钝时需要系统性地定位瓶颈。测量绘制时间使用一个高精度定时器如SysTick在绘图函数调用前后打点计算耗时。GUI_Delay()本身也会消耗时间。定位耗时操作复杂几何图形GUI_FillPolygon填充一个顶点很多的多边形会很慢。考虑是否能用多个简单图形拼接或预先渲染到位图。大量透明绘制GUI_TM_TRANS模式下的绘制比GUI_TM_NORMAL慢很多。尽量减少透明区域的使用。JPEG解码这是CPU杀手。务必使用内存设备缓存解码结果绝对禁止在每帧中重复解码。频繁的全局重绘这是最大的性能杀手。必须采用脏矩形或局部更新策略。利用硬件特性如果MCU有FPU确保编译器启用了硬件浮点一些图形计算如旋转、缩放会受益。如果MCU有DMA在LCD_X_Config中配置使用DMA传输数据到LCD可以解放CPU。如果MCU有硬件JPEG解码器如某些STM32H7系列研究emWin是否支持或如何接入该硬件加速器这将是性能的飞跃。6.4 内存优化技巧使用存储设备Storage Device替代内存设备对于非常大的、不常变化的背景图可以将其以原始像素格式已转换好存放在外部QSPI Flash或SD卡中。显示时使用GUI_DrawStreamedBitmap()配合GetData回调直接从存储介质流式读取像素数据并发送到LCD完全避免占用大块RAM。选择性的抗锯齿GUI_AA_EnableHiRes()等抗锯齿功能会显著增加计算量。只为最需要平滑效果的元素如大号字体、关键曲线开启抗锯齿其他地方关闭。字体管理只链接项目实际用到的字体。使用GUI_Font_开头的字体它们通常是等宽或小字库字体比GUI_Font开头的标准字体更节省空间。对于中文等大字符集务必使用字体生成工具提取子集。配置GUI_NUMBYTES在GUIConf.h中合理设置emWin动态内存池的大小。太小会导致分配失败太大会浪费内存。通过GUI_ALLOC_GetNumUsedBytes()和GUI_ALLOC_GetNumFreeBytes()在运行时监控内存使用情况找到最佳值。最后记住嵌入式GUI开发是一个在功能、性能和资源之间不断权衡的艺术。没有放之四海而皆准的最优解只有最适合你当前项目约束的解决方案。emWin提供了强大的工具集但如何用好它们取决于你对这些工具的理解和对项目需求的深刻把握。多测试多测量用数据而不是直觉来指导你的优化方向。

相关新闻