【安富莱二代示波器教程】第5章 示波器设计—波形快速刷新方案
第5章 示波器设计—波形快速刷新方案
本章节比较重要,推荐的波形刷新方式都经过了大量测试验证。
5.1 波形快速刷新方案
5.2 示波器背景的快速刷新
5.3 系统上电,主界面无闪烁感
5.4 测量窗口的刷新
5.5 开关对话框时,界面的刷新方式
5.6 五个数值显示窗口的快速刷新
5.7 总结
5.1 波形快速刷新方案选择
波形快速刷新有很多方案需要测试,由于我们的GUI是采用的emWin,所以下面的这些测试都是基于emWin实现的。
(1)选择8位色还是16位色。
(2)使能三缓冲还是窗口存储设备。
(3)选用ARGB格式的emWin库还是ABGR格式的库。
(4)将STM32F429超频后刷新是否有提升。
(5)使用存储设备函数GUI_MEMDEV_Draw绘制还是多缓冲函数GUI_MULTIBUF_Begin()和GUI_MULTIBUF_End()。
(6)使用函数GUI_DrawGraph还是GUI_DrawPolyLine绘制波形。
下面将这几个方案以此为大家做个说明。
5.1.1 选择8位色还是16位色
之前测试emWin刷颜色块,8位色可以跑到2亿多点每秒,而16位色是1亿多点每秒。实际测试发现8位色刷颜色块还行,刷波形却不行,比较卡,所以采用16位色。
也许有读者会问为什么不使用32位色?对于单片机来说,刷32位色比16位色要吃力。所以32位色也不考虑。
知识点拓展
新版emWin教程第34章:STemWin支持的颜色格式:http://forum.armfly.com/forum.php?mod=viewthread&tid=19834 。
5.1.2 使用三缓冲还是窗口存储设备
对于STM32F429而言,使能三缓冲是指的用户要在LCDConf_Lin_Template.c文件中配置多缓冲,并在应用程序中调用函数WM_MULTIBUF_Enable(1)来使能。
使用内存设备是调用函数WM_SetCreateFlags(WM_CF_MEMDEV)来实现。
如果emWin的配置支持多缓冲和窗口存储设备,务必优先选择使用多缓冲,实际使用STM32F429BIT6 + 32位SDRAM + RGB565/RGB888平台测试,多缓冲可以有效地降低窗口移动或者滑动时的撕裂感,并有效地提高流畅性,通过使能窗口使用内存设备是做不到的。所以我们示波器也是选择使用三缓冲。使能多缓冲方式如下:
/* ********************************************************************************************************* * 函 数 名: MainTask * 功能说明: GUI主函数 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void MainTask(void) { /* emWin初始化 */ GUI_Init(); /* 使能多缓冲 */ WM_MULTIBUF_Enable(1); /* 省略 */ }
5.1.3 使用ARGB格式emWin库还是ABGR格式库
由于前面我们已经选择了使用RGB565颜色格式,对于这个颜色格式,使用ARGB格式的emWin库并没有性能提升,而且实际做应用代码还要注意额外的事项。所以继续使用ABGR格式库。
知识点拓展
专题教程第1期:基于STM32的硬件RGB888接口实现emWin的快速刷新方案,32位色或24位色:http://forum.armfly.com/forum.php?mod=viewthread&tid=44512 。
5.1.4 将STM32F429超频后刷新是否有提升
STM32F429操作自带的Flash无法实现0延迟,随着设置的主频越高,延迟参数就要设置的越大,
参数手册中给的测试结果如下:
实际测试,将其超频到216MHz并没有任何性能提升,为了稳定起见,依然使用的168MHz。至于为什么不使用F429支持的180MHz,其原因在这个帖子里面有描述:http://forum.armfly.com/forum.php?mod=viewthread&tid=16830 。
5.1.5 使用存储设备函数还是三缓冲函数做整体刷新
之前的一代示波器是采用下面的方式进行绘制的:
GUI_MEMDEV_Draw(&Rect, _Draw, &Param, 0, GUI_MEMDEV_NOTRANS);
在函数_Draw里面实现波形显示区和波形的绘制,但是速度比较慢,600*480显示区的刷新率差不多10帧左右,现在做二代示波器显然不能再使用这种方法了,速度太慢,而且实际测试发现F429使用这种方式比一代示波器中F407采用这种方式要慢一点,这样的结果显然不是我们想要的。现在的绘制方式是采用的多缓冲函数:
GUI_MULTIBUF_Begin(); /* 在这两个函数之间实现波形绘制 */ GUI_MULTIBUF_End();
使用这两个函数做整体刷新,可以有效的避免波形刷新时的闪烁和撕裂感。
5.1.6 使用哪个函数绘制波形
之前一代示波器中波形的绘制是采用的函数GUI_DrawPolyLine,二代示波器中也采用这个函数的话,发现速度慢了好多,而使用函数GUI_DrawGraph却不存在这个问题。而且重新优化底层,函数GUI_DrawPolyLine的性能并没有提升,估计是内部执行机制的问题。使用函数GUI_DrawPolyLine的好处就是可以实现各种波形效果,鉴于这个函数性能比较差,所以还是继续使用GUI_DrawGraph。
5.1.7 使用单图层还是双图层
由于STM32F429的硬件双图层性能有限,而且使用emWin管理比较麻烦,所以二代示波器就采用单图层来实现。
5.2 示波器背景的快速刷新
示波器的界面显示效果如下:
波形显示区背景是固定的,所以上电后就将其绘制到存储设备里面,以后显示背景就可以直接调用存储设备的API函数。下面是示波器背景的绘制:
/* ********************************************************************************************************* * 函 数 名: CreateWindowAmplitude * 功能说明: 创建幅值窗口 * 形 参: x0 左上角x坐标 * y0 左上角y坐标 * x1 右下角x坐标 * y1 右下角y坐标 * 返 回 值: 无 ********************************************************************************************************* */ void DSO_DrawBakFrame(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) { uint16_t x; uint16_t y; /* 填充背景 */ GUI_SetBkColor(GUI_BLACK); GUI_ClearRect(x0, y0, x1, y1); GUI_SetColor(GUI_WHITE); /* 绘制水平刻度点 */ for (y = 0; y < 9; y++) { for (x = 0; x < 61; x++) { GUI_DrawPoint(x0 + (x * 10), y0 + (y * 50)); } } for (x = 0; x < 61; x++) { GUI_DrawPoint(x0 + (x * 10), y1); } /* 绘制垂直刻度点 */ for (x = 0; x < 12; x++) { for (y = 0; y < 41; y++) { GUI_DrawPoint(x0 + (x * 50), y0 + (y * 10)); } } for (y = 0; y < 41; y++) { GUI_DrawPixel(x1, y0 + (y * 10)); } /* 绘制最后脚上的那个点 */ GUI_DrawPixel(x1 - 1, y1 - 1); /* 绘制垂直中心刻度点 */ for (y = 0; y < 41; y++) { GUI_DrawPixel(x0 - 1 + (300), y0 + (y * 10)); GUI_DrawPixel(x0 + 1 + (300), y0 + (y * 10)); } GUI_DrawPixel(x0 - 1 + (300), y1); GUI_DrawPixel(x0 + 1 + (300), y1); /* 绘制水平中心刻度点 */ for (x = 0; x < 61; x++) { GUI_DrawPixel(x0 + (x * 10), y0 - 1 + (200)); GUI_DrawPixel(x0 + (x * 10), y0 + 1 + (200)); } GUI_DrawPixel(x1, y0 - 1 + (200)); GUI_DrawPixel(x1, y0 + 1 + (200)); }
通过下面的方式将示波器背景绘制到存储设备里面:
hMemDSO = GUI_MEMDEV_CreateFixed(0, 0, 600, 400, GUI_MEMDEV_HASTRANS, GUI_MEMDEV_APILIST_16, GUICC_M565); /* 绘制示波器窗口背景 */ GUI_MEMDEV_Select(hMemDSO); DSO_DrawBakFrame(0,0,599,399); GUI_MEMDEV_Select(0);
以后程序中绘制示波器背景,仅需调用如下函数就可以实现
GUI_MEMDEV_WriteAt(hMemDSO, 40, 40);
实际测试这个函数仅需9-10ms就可以完成。
5.3 系统上电,主界面无闪烁感
为了追求极致的用户体验,需要解决板子开机后存在的两个问题。
1、界面整体加载
有时候界面设计比较复杂,开机后不能保证所有的控件同时加载出来,这个时候有个非常简单的解决办法,绘制前隐藏桌面窗口,绘制完毕后显示桌面窗口。
WM_HideWin(WM_HBKWIN); /* 初始化 DSO */ DSO_Init(1); WM_ShowWin(WM_HBKWIN);
2、上电瞬间产生的高亮
这个问题不仔细注意的话,不容易看到。起初以为是软件配置问题,调试了下没有效果,后来又设置背光PWM的不同占空比,发现也不行。
后来干脆关闭PWM,而且也关闭emWin和LCD的图层,仅显示背景层,直接操作PWM引脚的高低电平。测试发现将其配置为低电平后,要延迟200ms左右再点亮LCD就没有问题了,延迟时间短了则会有个瞬间的高亮。知道了这个原因,程序中就好解决了,可以直接延迟200ms后再点亮,而这个二代示波器无需这么做,因为上电后需要将各种测量窗口的背景和波形显示区背景绘制到存储设备里面,正好用于替代者200ms延迟。
GUI_Init(); /* 先关闭背光 */ LCD_SetBackLight(0); /* 在这里加载各种窗口背景 */ /* 显示出所有的窗口 */ GUI_Exec(); /* 再开启背光 */ LCD_SetBackLight(255); /* 开启其它操作 */
通过这种方式就完美解决了瞬间高亮的问题。
5.4 测量窗口的刷新
测量功能是指的下面的水平测量和垂直测量:
测量功能的数据显示不要以窗口的形式呈现,因为将窗口显示在波形显示区上会造成波形刷新慢。当前的方案是在绘制完毕波形后,直接2D函数绘制测量窗口,这种方式的实际效果好很多。
5.5 开关对话框时,界面的刷新方式
二代示波器主界面上有如下五个按键,点击后会弹出一个对话框。
我们这里要讨论的是关闭这个对话框时存在的问题。关闭这个对话框时,为了保证主界面整体的刷新效果,需要清背景的同时,将示波器波形显示区背景也一起刷出来(文件DSO_Init.c):
GUI_SetBkColor(0x905040); GUI_Clear(); GUI_MEMDEV_WriteAt(hMemDSO, 40, 40);
通过这种方式,关闭对话框绘制的效果就好很多。
5.6 五个数值显示窗口的快速刷新
五个数值显示窗口是指的下面这五个:
主要有幅值窗口,两个状态窗口,频率窗口和系统信息窗口。这几个窗口的创建都是以对话框的形式创建,方便管理。对话框回调函数的WM_PAINT消息里面通过大量的2D函数进行绘制,每次刷新数值还是比较影响系统性能的,为了降低影响,需要提前将其绘制到存储设备里面,跟本章节5.2小节的方法类似。我们这里以其中一个状态窗口为例进行说明,WM_PAINT消息里面要绘制如下这些图形和字符:
/* ********************************************************************************************************* * 函 数 名: PaintDialogStatus1 * 功能说明: 状态窗口的回调函数重绘消息 * 形 参:pMsg 指针地址 * 返 回 值: 无 ********************************************************************************************************* */ void PaintDialogStatus1(WM_MESSAGE * pMsg) { /* 清背景色 */ GUI_SetBkColor(0x905040); GUI_Clear(); /* 绘制填充的抗锯齿圆角矩形 */ GUI_SetColor(GUI_BLACK); GUI_AA_FillRoundedRect(0, 0, 509, 34, 10); /* 绘制抗锯齿圆角矩形 */ GUI_SetColor(0XEBCD9E); GUI_SetPenSize(2); GUI_AA_DrawRoundedRect(0, 0, 509, 34, 10); /* 绘制抗锯齿圆角矩形,并填数值1 */ GUI_SetColor(GUI_YELLOW); GUI_AA_FillRoundedRect(10, 4, 30, 16, 3); GUI_SetColor(GUI_BLACK); GUI_SetFont(&GUI_Font16_1); GUI_SetTextMode(GUI_TEXTMODE_TRANS); GUI_DispCharAt(\'1\', 16, 3); /* 绘制抗锯齿圆角矩形,并填数值1 */ GUI_SetColor(GUI_YELLOW); GUI_AA_FillRoundedRect(135, 4, 155, 16, 3); GUI_SetColor(GUI_BLACK); GUI_SetFont(&GUI_Font16_1); GUI_SetTextMode(GUI_TEXTMODE_TRANS); GUI_DispCharAt(\'1\', 141, 3); /* 绘制抗锯齿圆角矩形,并填数值1 */ GUI_SetColor(GUI_YELLOW); GUI_AA_FillRoundedRect(260, 4, 280, 16, 3); GUI_SetColor(GUI_BLACK); GUI_SetFont(&GUI_Font16_1); GUI_SetTextMode(GUI_TEXTMODE_TRANS); GUI_DispCharAt(\'1\', 266, 2); /* 绘制抗锯齿圆角矩形,并填数值1 */ GUI_SetColor(GUI_YELLOW); GUI_AA_FillRoundedRect(385, 4, 405, 16, 3); GUI_SetColor(GUI_BLACK); GUI_SetFont(&GUI_Font16_1); GUI_SetTextMode(GUI_TEXTMODE_TRANS); GUI_DispCharAt(\'1\', 391, 2); /* 绘制抗锯齿圆角矩形,并填数值2 */ GUI_SetColor(GUI_GREEN); GUI_AA_FillRoundedRect(10, 19, 30, 31, 3); GUI_SetColor(GUI_BLACK); GUI_SetFont(&GUI_Font16_1); GUI_SetTextMode(GUI_TEXTMODE_TRANS); GUI_DispCharAt(\'2\', 16, 18); /* 绘制抗锯齿圆角矩形,并填数值2 */ GUI_SetColor(GUI_GREEN); GUI_AA_FillRoundedRect(135, 19, 155, 31, 3); GUI_SetColor(GUI_BLACK); GUI_SetFont(&GUI_Font16_1); GUI_SetTextMode(GUI_TEXTMODE_TRANS); GUI_DispCharAt(\'2\', 141, 18); /* 绘制抗锯齿圆角矩形,并填数值2 */ GUI_SetColor(GUI_GREEN); GUI_AA_FillRoundedRect(260, 19, 280, 31, 3); GUI_SetColor(GUI_BLACK); GUI_SetFont(&GUI_Font16_1); GUI_SetTextMode(GUI_TEXTMODE_TRANS); GUI_DispCharAt(\'2\', 266, 18); /* 绘制抗锯齿圆角矩形,并填数值2 */ GUI_SetColor(GUI_GREEN); GUI_AA_FillRoundedRect(385, 19, 405, 31, 3); GUI_SetColor(GUI_BLACK); GUI_SetFont(&GUI_Font16_1); GUI_SetTextMode(GUI_TEXTMODE_TRANS); GUI_DispCharAt(\'2\', 391, 18); }
通过下面的方式将其绘制到存储设备里面(系统上电时就进行加载):
/* ********************************************************************************************************* * 函 数 名: DrawWinStatus1Bk * 功能说明: 将窗口背景绘制到存储设备里面 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void DrawWinStatus1Bk(void) { hMemStatus1Dlg = GUI_MEMDEV_CreateFixed(0, 0, 510, 35, GUI_MEMDEV_HASTRANS, GUI_MEMDEV_APILIST_16, GUICC_M565); GUI_MEMDEV_Select(hMemStatus1Dlg); PaintDialogStatus1(NULL); GUI_MEMDEV_Select(0); }
之后将如下函数填到状态窗口回调函数WM_PAINT消息里面即可
GUI_MEMDEV_WriteAt(hMemStatus1Dlg, 145, 444);
其它四个窗口的设计方法同理,通过这样的优化,一定程度上降低了GUI刷新的负担。
5.7 总结
建议大家实际测试下不同方案,这样会有一个深刻的体会。