第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 總結
建議大家實際測試下不同方案,這樣會有一個深刻的體會。
