嵌入式GUI开发实战:emWin 2D图形库核心API与优化技巧解析
1. 项目概述为什么嵌入式开发需要关注2D图形库在嵌入式系统开发中尤其是涉及人机交互界面HMI的项目图形用户界面GUI的流畅度和美观度直接决定了产品的用户体验和竞争力。然而嵌入式设备的资源通常非常有限主频几十到几百兆赫兹的MCU、几十到几百KB的RAM、以及可能不带硬件图形加速器。在这种“螺蛳壳里做道场”的环境下一个高效、可靠的2D图形库就成了开发者的“救命稻草”。我接触过不少项目从简单的仪表盘到复杂的工业触摸屏核心的图形渲染工作都离不开2D图形库。它本质上是一套软件算法集负责将开发者定义的“画图指令”比如画线、填色、显示图片转化为显示屏帧缓冲区Frame Buffer里一个个像素的颜色值。这个过程听起来简单但要做好却需要极致的优化。一个优秀的2D图形库比如SEGGER的emWin其价值远不止提供几个画图函数。它通过精心设计的算法减少CPU计算量通过智能的内存管理策略如缓存、脏矩形更新降低对RAM的消耗并且提供从基础几何图形到高级功能如抗锯齿、透明度混合、二维码生成的完整API生态。这意味着开发者可以将精力集中在业务逻辑和交互设计上而不是纠结于如何用代码高效地画一个圆或者显示一张被压缩过的图片。本次我们就以emWin的2D图形库为蓝本深入剖析其从基础到高级的API设计与实现逻辑。你会发现这些API背后隐藏着大量针对嵌入式场景的优化技巧和设计哲学。无论是刚接触嵌入式GUI的新手还是希望优化现有图形性能的老手理解这些底层原理和API的“所以然”都能让你在开发中更加游刃有余。2. 核心设计思路emWin 2D图形库的架构与优化哲学emWin的2D图形库并非简单地将标准计算机图形学算法移植到MCU上它的设计从头到尾都贯穿着对嵌入式环境苛刻约束的深刻理解。其核心思路可以概括为三点分层抽象以适配硬件、计算换空间以优化内存、以及按需更新以提升效率。2.1 硬件抽象层HAL与驱动模型这是emWin能适配成千上万种不同显示控制器和MCU的基石。2D图形库的所有绘制命令最终都要落实到对具体显示设备LCD、OLED等的像素操作上。emWin通过一个硬件抽象层将通用的图形API与底层的硬件驱动解耦。当你调用GUI_DrawLine()时库函数内部可能会经过多层处理首先进行坐标裁剪确保线在可视区域内然后可能应用当前设置的画笔样式虚线、点线最后调用一个名为LCD_DrawPixel的底层函数。而这个LCD_DrawPixel的具体实现是由开发者根据自己使用的硬件在驱动层提供的。它可能直接写一个内存地址对于内存映射的LCD也可能通过SPI、8080并行总线发送一组命令和数据。这种设计带来的最大好处是可移植性。你的应用图形代码完全不用关心屏幕是320x240的TFT还是800x480的RGB接口屏。当需要更换硬件时你只需要重新实现或调整底层的驱动函数上层的业务逻辑代码几乎无需改动。在项目初期进行硬件选型评估或者产品后期进行硬件升级时这个优势会体现得淋漓尽致。2.2 内存与性能的权衡策略嵌入式系统的内存尤其是RAM永远是最紧张的资源之一。emWin在这方面做了大量工作显示缓存Display Cache对于不支持直接内存读取或写入速度较慢的显示控制器emWin可以在RAM中开辟一块与屏幕区域对应的缓存区。所有的绘制操作先在这个缓存中进行然后通过一个后台任务或定时器将缓存中变更过的区域脏区更新到实际显示屏。这就是GUI_GCACHE_4_Create()等函数存在的意义。它允许你使用一个低色彩深度如4bpp16色的缓存来模拟高色彩深度的绘制虽然损失了一些颜色但极大地节省了RAM。例如一个320x240的16位色2字节/像素全屏缓存需要150KB而4bpp的缓存仅需37.5KB。流式位图解码Streamed Bitmap对于大尺寸的图片如JPEG、PNG将其完全解码到内存中再显示是不现实的。emWin支持流式解码即通过GUI_DrawStreamedBitmapEx()这类函数配合一个用户提供的GetData回调函数可以边从存储介质如Flash、SD卡读取数据边进行解码和显示。内存中只需要维护几行扫描线的缓冲区即可实现了“小内存显示大图”。脏矩形更新Dirty Rectangle Update这是提升刷新效率的关键技术也是GUI_DIRTYDEVICE_*系列函数的核心价值。传统的全屏刷新每次更新都重绘整个屏幕会带来大量不必要的像素操作造成闪烁和CPU浪费。脏矩形更新机制会跟踪自上次刷新以来屏幕上哪些矩形区域的内容发生了改变。在刷新时只更新这些“脏”的区域。这对于更新频率高但每次变化区域小的界面如动态图表、局部文本更新性能提升巨大。2.3 坐标系统与绘制上下文emWin使用一个统一的笛卡尔坐标系原点(0,0)默认在屏幕左上角X轴向右Y轴向下。所有绘制API的坐标参数都基于此。更重要的是它维护着一个“GUI上下文”GUI Context这是一个包含当前绘制状态的结构体由GUI_CONTEXT定义。这个上下文里保存了诸如当前画笔颜色GUI_SetColor设置、背景色、字体、线型、绘制模式正常、异或、透明、裁剪区域等信息。GUI_SaveContext()和GUI_RestoreContext()这一对函数就是用来保存和恢复这个上下文状态的。这有什么用呢想象一个场景你需要在某个子窗口或控件内进行一系列自定义绘制但绘制完成后必须恢复之前的全局设置以免影响其他部分的UI。这时在绘制前保存上下文绘制后再恢复是一种非常干净和安全的做法。它避免了手动记录和重置一大堆状态变量的繁琐与易错。3. 基础图形绘制API详解与实战掌握了核心思路我们进入实战环节。emWin的2D图形API非常丰富我们挑出最常用和最具代表性的几类进行拆解并附上我踩过坑后才总结出的注意事项。3.1 基本几何图形点、线、矩形、圆这些是构建任何图形界面的砖瓦。它们的API通常很直观但细节决定成败。画线GUI_DrawLine(x0, y0, x1, y1)是最基本的。但要注意GUI_SetPenSize()函数它可以设置线的宽度。当线宽大于1时线的端点处理方式平头、圆头会影响视觉效果这在绘制粗线条的表格或边框时需要留意。画矩形GUI_DrawRect()画空心矩形GUI_FillRect()画实心矩形。这里有一个高频坑点GUI_RECT结构体中的x1和y1坐标emWin的许多API将其定义为矩形右下角像素的坐标而不是矩形宽度。这意味着一个从 (10,10) 到 (20,20) 的矩形宽度和高度都是11个像素包含起点和终点。这与某些图形库如Windows GDI将x1, y1视为独占的右下方外部坐标的习惯不同在计算矩形尺寸时务必小心。画圆与椭圆GUI_DrawCircle(),GUI_FillCircle(),GUI_DrawEllipse(),GUI_FillEllipse()。绘制圆和椭圆通常使用中点圆算法或其变种这是一个纯软件算法。在低端MCU上频繁绘制大量或大半径的圆可能会成为性能瓶颈。如果界面中有很多圆形元素考虑使用预渲染的位图Bitmap来代替运行时绘制。实操示例与心得 假设我们要在屏幕中心画一个红色的实心圆外面套一个蓝色的空心方框。// 假设屏幕尺寸为 320x240 int center_x 160; int center_y 120; int radius 50; int rect_margin 5; // 方框与圆的间距 // 1. 画实心红圆 GUI_SetColor(GUI_RED); GUI_FillCircle(center_x, center_y, radius); // 2. 画空心蓝框 GUI_SetColor(GUI_BLUE); GUI_SetPenSize(2); // 设置边框线宽为2像素 GUI_DrawRect(center_x - radius - rect_margin, center_y - radius - rect_margin, center_x radius rect_margin, center_y radius rect_margin); GUI_SetPenSize(1); // 恢复默认线宽避免影响后续绘制注意GUI_SetColor和GUI_SetPenSize设置的是全局状态。在完成一个局部的、有特殊要求的绘制后一个好的习惯是立即将颜色、线宽等状态恢复到一个已知的默认值比如黑色、1像素除非你确定后续绘制需要延续这个状态。这能避免许多难以调试的显示错误。3.2 高级图形饼图与样条曲线这些图形在数据可视化中非常有用emWin也提供了直接的支持。绘制饼图Pie ChartGUI_DrawPie(x0, y0, r, a0, a1, Type)。这个函数用于绘制一个圆扇形饼图的一块。参数a0和a1是起始角和终止角单位是度degree0度指向屏幕右侧3点钟方向角度增加方向为顺时针。Type参数目前保留应设置为0。输入资料中的示例代码非常经典它演示了如何用循环绘制一个完整的、分色的饼图。其核心逻辑是用一个数组aValues存储每个扇区结束的累积角度在循环中当前扇区的起始角a0是上一个扇区的结束角第一个为0结束角a1就是当前数组值。每次循环改变颜色并调用GUI_DrawPie。绘制样条曲线Spline这是一组更高级的函数用于绘制平滑曲线。它采用“创建-绘制-删除”的对象模式这在需要重复绘制同一曲线时效率更高。创建GUI_HMEM hSpline GUI_SPLINE_Create(px, py, NumPoints);你需要提供一系列控制点的X、Y坐标数组。关键限制X坐标数组必须是严格递增的。这意味着你不能画一个“往回走”的曲线这在定义路径时需要注意。绘制GUI_SPLINE_Draw(hSpline, x, y);或GUI_SPLINE_DrawAA(hSpline, x, y, Width);。后者带抗锯齿AA边缘更平滑但计算量更大。Width参数指定曲线宽度。查询与删除GUI_SPLINE_GetY()可以获取曲线上某X坐标对应的Y值用于数据插值。GUI_SPLINE_GetXSize()获取曲线在X方向上的跨度。最后必须用GUI_SPLINE_Delete(hSpline)释放内存。实战避坑 样条曲线函数计算量相对较大特别是抗锯齿版本。避免在单片机的实时循环如1ms定时器中断中频繁创建和绘制复杂的样条。对于静态或变化不频繁的曲线应在初始化阶段创建好句柄并保存在需要重绘时直接使用GUI_SPLINE_Draw。动态变化的曲线则需要评估每次重新创建的成本是否可接受。3.3 二维码QR Code生成在工业、物流设备中集成二维码生成功能越来越普遍。emWin内置的QR Code支持让这一切变得简单。创建二维码核心函数是GUI_QR_Create()。你需要提供二维码版本与容量和尺寸相关、纠错等级GUI_QR_ECLEVEL_L到GUI_QR_ECLEVEL_H纠错能力递增但数据容量递减、以及编码的字符串。选择合适的纠错等级很重要在可能被污损的工业环境建议使用M15%或Q25%等级对于清洁环境且需要最大化数据容量可用L7%。绘制二维码创建成功后你会得到一个GUI_HMEM类型的句柄。使用GUI_QR_Draw(hQR, x, y)即可在指定位置绘制。获取信息GUI_QR_GetInfo(hQR, info)可以填充一个GUI_QR_INFO结构体其中包含版本、模块数Width和最终位图的像素尺寸Size。这在需要动态计算二维码绘制区域或进行布局对齐时非常有用。销毁使用完毕后务必调用GUI_QR_Delete(hQR)释放资源。经验之谈 二维码的版本决定了其尺寸模块数。如果你不确定该用哪个版本可以传递GUI_QR_VERSION_AUTO让库自动选择能容纳你数据的最小版本。但要注意自动版本计算需要额外的处理时间。对于已知的、固定长度的数据如序列号在开发阶段手动确定一个合适的固定版本是更高效的做法。4. 高级功能与系统集成除了绘制图形2D图形库还提供了一些提升系统性能和集成度的关键功能。4.1 屏幕脏区检测Dirty Device这是实现高效局部刷新的核心技术。其工作原理是创建一个脏区检测设备Dirty Device它会像监控摄像头一样“监视”所有通过emWin API进行的绘制操作并自动记录下发生变化的屏幕区域的外接矩形。API工作流程创建GUI_DIRTYDEVICE_Create()或GUI_DIRTYDEVICE_CreateEx(LayerIndex)。通常建议在GUI初始化完成后、主应用开始绘制前创建。对于多图层应用需要在每个需要监控的图层上分别创建。获取脏区信息在需要刷新屏幕时例如在垂直同步中断后或一个动画帧结束时调用GUI_DIRTYDEVICE_Fetch(info)。如果自上次Fetch后有绘制发生函数返回1并将脏区的位置和大小信息填入GUI_DIRTYDEVICE_INFO结构体。如果无变化则返回0。驱动更新拿到脏区信息后你不再需要刷新整个屏幕。你可以只将info.x0, info.y0, info.xSize, info.ySize这个矩形区域的数据通过你的显示驱动更新到硬件屏幕。这可以节省大量的数据传输时间尤其对于通过低速总线如SPI连接的显示屏效果立竿见影。删除当不再需要时使用GUI_DIRTYDEVICE_Delete()删除监控对象。高级信息GUI_DIRTYDEVICE_INFO中的pData和LineOff字段非常强大。如果脏区设备是在显示驱动初始化时LCD_X_Config中创建的并且驱动是线性可寻址的如GUIDRV_Lin那么pData会直接指向帧缓冲区中脏区第一个像素的地址LineOff则是行跨度stride。这允许你进行极高效的、基于内存的直接操作或DMA传输。4.2 YUV设备支持GUI_YUV_*系列函数用于处理YUV色彩空间的图像。YUV是一种将亮度Y和色度UV分离的色彩编码方式广泛应用于视频流中如JPEG、H.264/265解码输出。有些显示控制器或视频处理单元直接支持YUV数据输入。emWin的YUV设备功能是在标准的RGB图形渲染管线之外开辟一个并行的YUV平面Plane。当你创建YUV设备后如GUI_YUV_CreateEx(LayerIndex, Period)emWin会分配额外的缓冲区并按照你设定的周期Period自动将RGB帧缓冲区中的“脏”区域转换到YUV格式。使用场景与代价场景你的应用需要同时显示GUIRGB和来自摄像头的视频流YUV。你可以将视频流数据直接写入YUV平面通过GUI_YUV_GetpDataEx获取指针而GUI渲染仍在RGB空间进行。硬件混合器或显示控制器会自动将两者叠加显示。代价RGB到YUV的转换是计算密集型的。参数Period设置了转换任务的执行周期毫秒你需要根据MCU性能和屏幕更新率来权衡。周期太短CPU负载会很高周期太长YUV平面的更新会滞后于GUI变化。通常这需要与脏区检测结合使用只转换发生变化的区域以减轻CPU负担。4.3 钩子函数Hook Functions的应用钩子函数是emWin提供给开发者的“注入点”允许你在库执行的特定时刻插入自己的代码。GUI_SetAfterInitHook(pFunc): 设置在GUI_Init()完成后、返回前立刻执行的函数。我常用它来启动一些依赖GUI系统就绪的后台任务比如触摸屏校准进程、或者加载初始界面动画。GUI_SetRefreshHook(pFunc): 这是解决“撕裂”Tearing效应的利器。撕裂发生在屏幕正在从帧缓冲区读取数据进行扫描显示时应用程序却更新了同一块缓冲区的内容导致屏幕上半部分和下半部分显示的是不同帧的数据。这个钩子函数会在驱动准备向显示控制器发送数据之前被调用。你可以在钩子函数里实现等待“垂直消隐期”Vertical Blanking Period或“撕裂效应信号”TE Signal的逻辑。确保只在屏幕不扫描的间隙更新显存从而彻底避免撕裂。前提是你的显示屏支持并提供了TE信号引脚且你的驱动能读取它。GUI_SetControlHook(pFunc): 在显示驱动缓存操作执行前被调用。这给了你一个机会用自定义的方式将图层数据搬运到最终显示设备。例如如果你使用双缓冲Double Buffering可以在这里执行缓冲区交换Swap操作。使用心得 钩子函数是强大的扩展机制但要用好。首先确保你的钩子函数执行时间尽可能短不要阻塞GUI主任务。其次GUI_SetRefreshHook必须与缓存锁定机制配合使用参考手册中Cache Locking部分否则每次画一个点都等待TE信号性能会灾难性下降。正确的做法是在开始一系列绘制操作前锁定缓存绘制完成后解锁此时触发一次刷新钩子等待并更新这样一次等待对应一整帧的更新效率最高。5. 位图文件显示内存与性能的博弈在嵌入式设备上显示图片BMP, JPEG, GIF, PNG是一个经典挑战。emWin提供了多种策略来应对不同的资源约束。5.1 BMP文件的直接绘制与流式绘制BMP格式相对简单emWin支持从1位到32位的多种BMP。GUI_BMP_Draw(): 最简单要求整个BMP文件已完全加载到RAM中。适用于小图标、Logo等资源通常这些资源在编译时就被转换成C数组并链接到代码中。GUI_BMP_DrawEx(): 这是为大图或动态加载图片设计的。它接受一个GUI_GET_DATA_FUNC类型的回调函数指针。当emWin需要解码下一行像素数据时会调用这个回调函数你的回调函数需要从存储介质如SD卡、SPI Flash中读取相应数据块并返回。这样内存中只需要维持很小的行缓冲区实现了“流式”解码显示。这对于显示存储在外部Flash中的全屏背景图至关重要。关键参数解析GUI_BMP_DrawScaled()和GUI_BMP_DrawScaledEx()支持缩放。其缩放因子通过分子Num和分母Denom表示。例如要缩小到原图的75%则Num 3,Denom 4(因为 3/4 0.75)。注意缩放是软件缩放会消耗额外的CPU时间进行插值计算对于大图缩放要谨慎评估性能。5.2 其他格式与通用流式接口对于JPEG和GIFemWin也提供了类似的GUI_JPEG_DrawEx()和GUI_GIF_DrawEx()函数它们同样基于流式解码。PNG支持则需要额外链接SEGGER提供的PNG库。所有这些*Ex()函数都依赖于同一个GUI_GET_DATA_FUNC回调机制。这意味着只要你实现了这个通用的数据获取回调你就能以极低的内存开销显示任意支持格式的大图。这个回调函数的典型实现如下int GetData(void *p, const U8 **ppData, unsigned NumBytesReq, U32 Off) { // p: 用户自定义参数通常是一个文件句柄或结构体指针 // ppData: 输出参数用于返回数据块的指针 // NumBytesReq: 本次请求的字节数 // Off: 请求数据在文件中的偏移量 MY_FILE_HANDLE *pFile (MY_FILE_HANDLE *)p; // 1. 将文件读写指针移动到 Off 处 my_fseek(pFile, Off, MY_SEEK_SET); // 2. 从存储设备读取 NumBytesReq 字节到临时缓冲区 U32 bytesRead my_fread(pFile, s_aBuffer, NumBytesReq); // 3. 将临时缓冲区的地址赋给 *ppData *ppData (const U8 *)s_aBuffer; // 4. 返回实际读取的字节数 return bytesRead; }重要提示ppData指向的数据缓冲区必须在回调函数返回后保持有效直到emWin下一次调用该回调函数。因此通常使用一个全局或静态的固定大小缓冲区s_aBuffer。缓冲区的大小至少需要能容纳一行图像解码所需的最大数据量对于BMP和未压缩格式就是一行像素的字节数对于JPEG这类压缩格式会更大需要参考库的具体要求进行配置。6. 数据结构、绘制模式与常见问题排查6.1 关键数据结构解析理解核心数据结构能让你更灵活地使用API。GUI_RECT: 如前所述x1, y1是包含在内的右下角坐标。许多其他API如GUI_DrawRect,GUI_FillRect, 裁剪区域设置都使用这个结构。牢记其定义可以避免差一错误Off-by-one error。GUI_GRADIENT_INFO: 用于定义多色渐变。Pos成员定义了该颜色在渐变轴上的起始位置对于水平渐变是X坐标垂直渐变是Y坐标。数组中的最后一个Pos定义了渐变的终点。例如定义一个从红色位置0到绿色位置50再到蓝色位置100的水平渐变需要三个GUI_GRADIENT_INFO元素。GUI_BITMAPSTREAM_PARAM: 流式位图解码钩子函数GUI_SetStreamedBitmapHook的参数结构。它允许你在解码过程中介入例如为索引色位图提供自定义调色板缓冲区GUI_BITMAPSTREAM_GET_BUFFER或者在绘制前修改调色板GUI_BITMAPSTREAM_MODIFY_PALETTE实现动态换肤等高级效果。6.2 绘制模式Drawing ModesGUI_SetDrawMode()可以切换绘制模式这是实现特殊视觉效果的基础。GUI_DM_NORMAL: 默认模式直接覆盖目标像素。GUI_DM_XOR异或模式新像素颜色与原有屏幕颜色进行按位异或。一个经典应用是绘制“橡皮筋”选择框第一次画框是可见的在同一个位置再画一次由于异或两次等于恢复原状框就消失了实现了无痕擦除。限制在色彩深度大于1bpp即非单色时可能无法产生预期效果且与画笔大小大于1的功能配合可能有问题。GUI_DM_TRANS透明模式在绘制带有背景色和前景色的物体如字符、某些位图时背景色部分不会被绘制保持屏幕原样。这是显示非矩形图标、实现图像叠加的关键。GUI_DM_REV反色模式交换前景色和背景色。在某些需要高亮或反显的场景下有用。6.3 常见问题与排查技巧实录在多年使用emWin进行开发的过程中我积累了一些典型问题的排查思路问题1绘制速度极慢界面卡顿。排查检查绘制操作是否在循环中过于频繁例如是否在每帧都重新绘制整个界面尝试使用脏区更新只重绘变化部分。检查是否使用了未缓存的绘制对于需要频繁读写的显示驱动确保已启用显示缓存GUI_GCACHE_*。检查复杂图形大量GUI_DrawPolygon或GUI_FillPolygon、大尺寸的GUI_DrawPie或抗锯齿样条曲线会消耗大量CPU。考虑用预渲染的位图替代。检查图片解码流式解码JPEG/GIF/PNG是CPU密集型操作。避免在动画过程中解码大图。可以预解码到内存或使用更低分辨率的图片。工具使用emWin的GUI_MeasureTime相关函数对关键绘制代码段进行性能分析。问题2显示出现撕裂Tearing。现象水平方向上图像被“撕开”上下部分错位。原因帧缓冲区在屏幕扫描显示期间被修改。解决如果硬件支持启用并利用GUI_SetRefreshHook在垂直消隐期更新。使用双缓冲Double Buffering。在后台缓冲区Back Buffer完成所有绘制然后通过GUI_MULTIBUF_*系列函数或驱动控制钩子在垂直消隐期交换前后缓冲区。如果以上都不行尝试同步绘制速度与刷新率或者减少每帧的绘制内容。问题3使用GUI_DM_XOR模式绘制但第二次绘制无法完全擦除。原因最常见的原因是绘制区域不完全重合或者中间有其他绘制操作改变了屏幕内容。排查确保两次绘制的坐标、形状、线宽等参数完全一致。在复杂的、动态的界面中使用XOR模式要格外小心因为任何第三方绘制都会破坏XOR的“可逆”前提。通常XOR模式更适合在静态背景上进行简单的、临时的交互绘制如框选。问题4显示图片或二维码时出现花屏或错位。排查数据源对于流式图片检查GetData回调函数是否正确处理了偏移量Off和字节请求数NumBytesReq返回的数据指针是否有效。内存对齐确保提供给图片绘制函数的数据指针符合平台的内存对齐要求。某些MCU访问非对齐数据会触发硬件错误或导致性能下降。色彩深度确认图片的色彩深度bpp与当前LCD配置的色彩模式兼容。例如试图在16位色模式下显示32位带Alpha的BMP可能需要调用GUI_BMP_EnableAlpha()并正确处理Alpha通道。二维码版本与容量检查输入的字符串是否超过了所选二维码版本和纠错等级的最大容量。可以使用GUI_QR_GetInfo检查创建后的二维码尺寸是否合理。问题5多图层Layer混合显示异常。现象上层该透明的地方不透明或者颜色混合不对。排查确认图层色彩模式确保各个图层的色彩深度和像素格式设置正确。例如一个ARGB8888的图层叠加在RGB565的图层上需要硬件或软件混合器支持。检查Alpha值使用GUI_SetLayerAlpha()设置的是整个图层的全局透明度。如果需要对单个绘制对象设置透明度需要使用支持Alpha的API如带Alpha的位图或GUI_SetUserAlpha()。绘制模式在透明图层上绘制时确认绘制模式是否为GUI_DM_TRANS并且前景/背景色设置正确。硬件混合器如果使用硬件混合检查相应的寄存器配置和时序。嵌入式GUI调试往往比较困难因为涉及软硬件协同。一个非常有效的方法是分步验证先从最简单的图形比如画一个矩形开始确保基础绘制和驱动没问题然后逐步增加复杂度颜色、线宽、模式再测试位图显示最后集成高级功能。同时充分利用emWin模拟器在PC上运行进行前期逻辑和UI布局的验证可以节省大量在目标板上调试的时间。