胡平平 王晶杰
(北京信息科技大學(xué)自動化學(xué)院 北京 100192)
?
uC/GUI實現(xiàn)OLED顯示的移植與優(yōu)化研究
胡平平王晶杰
(北京信息科技大學(xué)自動化學(xué)院北京 100192)
為了擴展STM32系統(tǒng)中OLED顯示處理功能、提高處理效率,給出了uC/GUI實現(xiàn)OLED最小顯示系統(tǒng)的移植方法。并根據(jù)OLED顯示器的特性,提出了新的程序結(jié)構(gòu),設(shè)計了快速判決算法,對關(guān)鍵代碼進行了優(yōu)化,在uC/GUI下實現(xiàn)了高效的OLED顯示處理。實際測試數(shù)據(jù)分析證明,所設(shè)計的算法不僅降低了處理復(fù)雜度,還使顯示處理速度成倍提高。
uC/GUIOLED移植算法優(yōu)化
uC/GUI是著名的uC/OS-II開發(fā)者Micrium 公司的另一個優(yōu)秀開源軟件,是專為嵌入式應(yīng)用而設(shè)計的、可移植、可裁減的圖形用戶界面系統(tǒng)軟件[1]。uC/GUI可為任何使用圖像LCD的應(yīng)用程序提供高效的、獨立于處理器和LCD控制器的圖形用戶接口[2]。uC/GUI可用于單任務(wù)和多任務(wù)嵌入式系統(tǒng)中,全部代碼用C語言完成,可方便地移植到各種CPU上[3,4]。
有機發(fā)光二極管(簡稱OLED)具有纖薄、可制成各種形狀、占用空間小等優(yōu)勢,它是繼白熾燈、熒光燈、LED之后的又一次光源革命。由于它節(jié)能、環(huán)保、壽命長而應(yīng)用廣泛,被列入我國戰(zhàn)略性新興產(chǎn)業(yè)[5]。和LCD相比,OLED具有體積小、亮度高、視角寬、自發(fā)光、低功耗、壽命長、動態(tài)范圍廣和可彎曲等諸多優(yōu)點[6]。
由于uC/GUI主要是為LCD設(shè)計的,向各種LCD顯示器的移植不僅簡單,而且執(zhí)行效率非常高。OLED有著與LCD不同的控制方式和RAM映像,雖然按常規(guī)方法將uC/GUI移植到OLED顯示器并不困難,但執(zhí)行效率非常低。這既不符合嵌入式系統(tǒng)嚴(yán)苛的實時性要求,也沒有充分發(fā)揮uC/GUI的優(yōu)勢。
本文在分析uC/GUI系統(tǒng)特點的基礎(chǔ)上,先給出了實現(xiàn)OLED最小顯示系統(tǒng)的移植方法,然后結(jié)合OLED顯示的特點,設(shè)計了全新的程序結(jié)構(gòu)并對關(guān)鍵代碼進行了優(yōu)化,提出了快速判決算法,不僅提高了軟件的執(zhí)行效率,且快速實現(xiàn)了復(fù)雜的處理。
本文所使用MCU是Cortex-M3系列芯片STM32F105,OLED為單色128×64點陣顯示器,驅(qū)動芯片為SSD1303。MCU與SSD1303的連接采用并行方式,如圖1所示。
圖1 MCU與SSD1303的連接圖
圖中PC0至PC7按8位I/O端口操作,其他四根控制線則按位進行輸出操作。操作宏的定義如下:
#define Oled_DCPAout(0)
#define Oled_CSPAout(2)
#define Oled_WRPBout(10)
#define Oled_RDPAout(11)
#define SetOledData(x)GPIOC->ODR=(GPIOC->ODR&0xff00)|x
#define GetOledData()(GPIOC->IDR&0xff)
SSD1303最大可驅(qū)動132列64行點陣顯示器,內(nèi)部有132×64位顯示SRAM,MCU可對它進行讀寫操作。該SRAM的位和顯示點陣的映射關(guān)系如圖2所示。
圖2 顯示器點陣和SRAM的映射關(guān)系
顯示點陣的64行分為8頁,每頁8行;頁中的每一列映射為SRAM的一個字節(jié),位序為bit7至bit0,每頁中的132列映射為SRAM中連續(xù)的132個字節(jié)。讀寫一個SRAM字節(jié)的代碼如下:
void WriteOledCmd(U8 Cmd)
{
SetOledData(Cmd);
Oled_DC= 0;Oled_WR= 0;
// 寫命令字
Oled_CS= 0;Oled_CS= 1;
// 產(chǎn)生CS低脈沖
Oled_WR= 1;Oled_DC= 1;
// 寫結(jié)束,默認(rèn)數(shù)據(jù)字
}
void WriteOledByte(U8 Data, int Page, int Col)
{
WriteOledCmd(Page+0xB0);
// 設(shè)置頁號
WriteOledCmd(Col&0xf);
// 設(shè)置列低地址
WriteOledCmd((Col>>4)+0x10);
//設(shè)置列高地址
Oled_WR = 0;
// 寫操作
SetOledData(Data);
Oled_CS = 0;Oled_CS = 1;
// 產(chǎn)生CS脈沖
Oled_WR = 0;
// 寫結(jié)束
}
U8 ReadOledByte(int Page, int Col)
{
U8 Data;
WriteOledCmd(Page+0xB0);
// 設(shè)置頁號
WriteOledCmd(Col&0xf);
// 設(shè)置列低地址
WriteOledCmd((Col>>4)+0x10);
//設(shè)置列高地址
Oled_RD = 0; Oled_CS = 0;
// 讀操作
Data = GetOledData();
// 得到數(shù)據(jù)字節(jié)
Oled_CS = 1; Oled_RD = 0;
// 讀結(jié)束
return Data;
}
讀(寫)一個SRAM字節(jié)的處理需要92(95)條匯編指令,代碼長度為109(110)個字。
本文使用的是V3.94版的uC/GUI,移植過程分為添加源程序、修改配置文件和編制關(guān)鍵代碼三部分。
2.1添加uC/GUI源程序
uC/GUI的可裁剪性表現(xiàn)在其源程序按函數(shù)功能可細(xì)分為許多獨立的小源程序文件,如GUI_DispChars.c文件只含GUI_Disp Chars()一個函數(shù),僅兩行代碼!簡單地將所有uC/GUI源程序都添加到項目文件中,對支持smart鏈接(僅鏈接用到的模塊)的編譯器來說不會增加目標(biāo)代碼的尺寸,但臃腫的項目文件不符合uC/GUI短小精干原則和“綠色”編程精神。好的做法是在項目文件夾中創(chuàng)建一個與uC/GUI軟件包Start文件夾類似的兩層共五個文件夾:
需要添加的具體源程序文件是:
Config下的GUIConf.h和LCDConf.h。
Core下的GUI.h、GUI_ConfDefaults.h、GUI_DispChar.c、GUI_DispChars.c和GUI_DispString.c等30個文件。
LCDDriver下的LCDDummy.c,該文件包含了移植中所有需要修改的函數(shù)。
Font下的F8x16.c(本文僅使用了8×16點陣字符集)。
2.2修改uC/GUI配置文件
文件LCDConf.h中符號的修改:
LCD_XSIZE(128)
// 原值640
LCD_YSIZE(64)
// 原值480
LCD_BITSPERPIXEL (1)
// 原值8
LCD_CONTROLLER(1303)
// 原值1375,h
其中的1303表示SSD1303,沒有特殊含義,其用法見下文。
文件GUIConf.h中符號的修改:
GUI_OS(0)
// 不支持操作系統(tǒng)
GUI_SUPPORT_TOUCH(0)
// 不支持觸摸屏
GUI_SUPPORT_UNICODE(0)
// 不支持ASCII和Unicode混合使用
GUI_DEFAULT_FONT &GUI_Font8x16
// 原GUI_Font6x8
GUI_ALLOC_SIZE128*64
// 為WM和存儲設(shè)備用
GUI_WINSUPPORT0
// 不支持窗口
GUI_SUPPORT_MEMDEV0
// 不支持存儲設(shè)備
GUI_SUPPORT_AA0
// 不支持去鋸齒
2.3編制關(guān)鍵代碼
移植只需對LCDDummy.c里的相關(guān)代碼進行修改即可,基本系統(tǒng)的移植僅需修改三個函數(shù):
(1) 初始化代碼
uC/GUI初始化由GUI_Init()完成,調(diào)用的一系列函數(shù)中僅GUI_X_Init()和LCD_L0_Init()需要修改。GUI_X_Init()用于初始化GUI運行之前需運行的硬件,本文系統(tǒng)沒有這樣的硬件,僅在LCDDummy.c中設(shè)置一個空的GUI_X_Init()函數(shù)即可。初始化移植只需將LCD_L0_Init()對LCD_INIT_CONTROL LER()的調(diào)用替換為對OLED控制器SSD1303的初始化處理就行了。
(2) 設(shè)置像素代碼
模板中設(shè)置像素函數(shù)是一個空函數(shù),移植后的代碼如下:
void LCD_L0_SetPixelIndex(int x, int y, int PixelIndex)
{
U8 Mask=0x80>>(y&7);
U8 Data = ReadOledByte(y>>3, x);
if ( PixelIndex ) Data |= Mask;
else Data &= ~Mask;
WriteOledByte(Data, y&7, x);
}
(3) 讀取像素代碼
模板中讀取像素函數(shù)是一個僅返回0的函數(shù),移植后的代碼如下所示:
unsigned int LCD_L0_GetPixelIndex(int x, int y)
{
return (ReadOledByte(y>>3, x)&(0x80>>(y&7))) ? 1 : 0;
}
另外,必須將原始LCDDummy.c的編譯條件:
#if (LCD_CONTROLLER==-1) && (!defined(WIN32) ‖ defined(LCD_SIMCONTROLLER) ) 改為:
#if (LCD_CONTROLLER==1303) 才能通過編譯。
3.1OLED顯示代碼存在的問題
上述移植完成的uC/GUI雖然能夠正確地實現(xiàn)OLED的顯示處理,但其執(zhí)行效率非常低。分析uC/GUI的代碼發(fā)現(xiàn),所有顯示處理最終都是通過LCD_L0_SetPixelIndex()和LCD_L0_GetPixelIndex()兩個函數(shù)實現(xiàn)的,而單獨對每一個像素處理并沒有充分利用OLED顯示器的特點。
首先,向OLED顯示器讀寫一個像素與讀寫一頁中該列的8行像素的處理量完全一樣。在上述讀(寫)SRAM字節(jié)代碼中,設(shè)置地址(頁號和列號)的指令數(shù)多達(dá)72條,占總代碼92(95)條的78.3%(75.8%)。而實際上OLED顯示器內(nèi)部有一個可以自動加1的地址計數(shù)器,僅需設(shè)置一次頁號和列號,就可以連續(xù)讀寫最多達(dá)132個SRAM字節(jié),這一特性在上面的代碼中完全沒有利用。
以顯示一個8×16點陣的字符為例,需要調(diào)用128次設(shè)置像素的函數(shù),共需執(zhí)行207×128=26 496條指令。而實際上需要設(shè)置SRAM的字節(jié)數(shù)量最多24個(若字符起始行正好在頁邊界上則僅為16個),如果連續(xù)設(shè)置,僅需設(shè)置3次地址和寫信號( (72+6)×3=234條指令),然后3次連續(xù)寫8個字節(jié)(13×8×3=312條指令),總指令數(shù)量為546條,是逐像素處理的2.06%??梢姡岣唢@示處理速度存在很大的空間。
3.2新的處理策略和關(guān)鍵代碼
為了充分利用OLED顯示的優(yōu)勢,必須將像素的逐點處理改為批量處理,為此本文設(shè)計了新的處理策略:在MCU內(nèi)存中設(shè)置一個OLEDSRAM的映像空間OledRamMap,同時設(shè)置一個SRAM更新標(biāo)志OledRamUpdateFlag:
U8 OledRamMap[8*128];
// 長度1024B
U8 OledRamUpdateFlag[8*(128/8)];
// 長度128B
OledRamMap的每一個字節(jié)在OledRamUpdateFlag中都有一位標(biāo)志表示該字節(jié)是否更新。所有處理像素的函數(shù)都不直接訪問OLED的SRAM,而是對OledRamMap和OledRamUpdate Flag進行設(shè)置,必要時由更新函數(shù)UpdateOledDisp()按更新標(biāo)志將OledRamMap寫到OLED的SRAM中。
OledRamMap和OledRamUpdateFlag在初始化時被清零,OledRamUpdateFlag在每次更新后也清零。新策略關(guān)鍵代碼的實現(xiàn)如下所示:
void LCD_L0_SetPixelIndex(int x, int y, int PixelIndex)
{
U8 BitMask = 0x80>>(y&7);
U8 *pCntData=&OledRamMap[(yPhys<<4)+xPhys];
if ( PixelIndex ) *pCntData |= BitMask;
else *pCntData &= ~BitMask;
OledRamUpdateFlag[(y<<1) +(x>>3)] |=0x80>> (x&7);
}
unsigned int LCD_L0_GetPixelIndex(int x, int y)
{
return (OledRamMap[(y<<4) +x]&(0x80>>(y&7)))?1:0;
}
void LCD_L0_XorPixel(int x, int y)
{
OledRamMap[(y<<4) +x] ^= (0x80>>(y&7));
OledRamUpdateFlag[(y<<1) +(x>>3)] |= 0x80>>(x&7);
}
UpdateOledDisp()的處理流程如圖3所示。
3.3更新處理的進一步優(yōu)化和快速判決算法
上面的更新處理算法在更新數(shù)據(jù)分片位于連續(xù)空間的情況下效率確實很高,但在更新數(shù)據(jù)零碎且分散的情況下,效率并不高。最壞的情形是更新標(biāo)志為1和0交替出現(xiàn),此時要為每一個需更新的數(shù)據(jù)設(shè)置地址。分析發(fā)現(xiàn),送一次數(shù)據(jù)需13條指令,而設(shè)置一次地址需要76條指令,是送數(shù)據(jù)的5.8倍。也就是說,如果兩個需要更新的數(shù)據(jù)間不需要更新的數(shù)據(jù)數(shù)量小于6個時,就應(yīng)該連續(xù)發(fā)送這些無需更新的數(shù)據(jù)而不重新設(shè)置地址。
圖3 OLEDSRAM更新處理流程圖
為此,在更新標(biāo)志由1變0時要根據(jù)該0后面連續(xù)4個標(biāo)志位的16種情況來決定是否停止發(fā)送數(shù)據(jù)。由于標(biāo)志由1變0出現(xiàn)的位置有8種(按字節(jié)訪問的),因此共有128種情況。顯然,為決定是否停止發(fā)送數(shù)據(jù)而進行128種判決處理是完全不可接受的。本文設(shè)計了快速算法,通過邏輯運算和查表操作,僅需一次判斷便可得出判決結(jié)果。實現(xiàn)方法如下:
新算法的原則是:如果判決結(jié)果是繼續(xù)發(fā)送數(shù)據(jù),則將當(dāng)前0標(biāo)志及后面4位中某些0標(biāo)志置位即可。這樣可保持處理的簡潔性,少增加新的分支。此外,當(dāng)前0標(biāo)志的8個位置中,有4個位置的后續(xù)4位將延伸到下一個標(biāo)志字節(jié),因此必須將連續(xù)16個更新標(biāo)志用一個U16變量CntFlagW聯(lián)合處理。而檢測字MaskW也由0x8000移位至0x100,新增一個與此對應(yīng)的變量BitShift由0變至7。設(shè)置一個判決門限數(shù)組FlagLevel如下:
const u16 FlagLevel[]={0x0400,0x0200,0x0100,0x0080,
0x0040,0x0020,0x0010,0x0008};
令Tmp = CntFlagW&(MaskW-1),
如果Tmp≥FlagLevel[BitShift],則應(yīng)該繼續(xù)發(fā)送數(shù)據(jù),此時應(yīng)將CntFlagW修改為:
CntFlagW |= FlagSetV[(Tmp>>(11-BitShift)])<<(11-BitShift)
其中FlagSetV的定義如下:
const u8 FlagSetV[] = {0x1F,0x1E,0x1C,0x1C,0x18,0x1A,
0x18, 0x18,0x10,0x16,0x14,0x14,0x10,0x12,0x10,0x10};
例如,若CntFlagW=101000X(X表示后面10個無關(guān)二進制位,以下同),此時MaskW=0x4000,BitShift=1,Tmp=CntFlagW &0x3fff=001000X,顯然Tmp≥FlagLevel[1]=0x200,應(yīng)該繼續(xù)發(fā)送數(shù)據(jù)且將CntFlagW與FlagSetV[8]<<10=0x4000邏輯或,變成111000X。此處的三個0是不能置位的,因為X的前3位為0時,后面的第1個0處應(yīng)該停止發(fā)送數(shù)據(jù)。如果CntFlagW= 101001X,也應(yīng)該繼續(xù)發(fā)送數(shù)據(jù)且將CntFlagW與FlagSetV[9] <<10=0x16<<10=0x5800邏輯或,變成111111X。對于BitShift=1時,僅CntFlagW= 100000X且X的第一個位為0的情況下Tmp的最大值是0x1FF,才會停止發(fā)送數(shù)據(jù),此時從當(dāng)前0標(biāo)志開始已經(jīng)有6個連續(xù)的0標(biāo)志了。對于BitShift為其它值的情況與此完全類似。
另外,如果連續(xù)的8個標(biāo)志都是0,則無需進行最內(nèi)層的MaskW移位循環(huán),從而進一步提高了速度。改進后的更新處理流程如圖4所示。
圖4 改進的OLEDSRAM更新處理流程圖
3.4優(yōu)化結(jié)果的性能分析
1) 更新處理改進前后的性能比較
通過在實際代碼上對標(biāo)志位各種組合共32類情況的精確測試分析,在0和1標(biāo)志交替出現(xiàn),較短(5個以內(nèi))連續(xù)0標(biāo)志和較長(8個及以上)連續(xù)0標(biāo)志的情況下,改進后都變快。僅在6至14個連續(xù)0標(biāo)志且非整字節(jié)0標(biāo)志的情況下,改進后速度基本沒變(最慢0.95)。大量連續(xù)0標(biāo)志的情況下,改進后速度提升最多,最大為8.704倍。
2) 優(yōu)化前后顯示字符的性能比較
優(yōu)化前,顯示一個字符設(shè)置像素的指令數(shù)量為26 496條;優(yōu)化后設(shè)置像素函數(shù)僅30條指令,顯示一個字符設(shè)置像素的指令數(shù)量為30×128=3840,而更新處理(按最多的24字節(jié)算)則需要1330(全0標(biāo)志)+ 666×1(送24字節(jié))+252(設(shè)置3次地址)-12×1(減去0標(biāo)志已含)=2236,共6076,速度提高了4.36倍。同理,連續(xù)顯示N個字符再更新的速度比如表1所示。
表1 連續(xù)顯示N個字符再更新的速度比
可見,一次更新處理前顯示的字符越多,優(yōu)化方案的速度提高越大。如果考慮到實際應(yīng)用中顯示區(qū)域重疊的情況,本文方案的優(yōu)勢就更大了。
3) 可優(yōu)化的其它處理函數(shù)和使用注意事項
(1) 其他可以優(yōu)化的函數(shù)
模板中的繪制函數(shù)LCD_L0_DrawHLine()、LCD_L0_Draw VLine()和LCD_L0_FillRect()也可以進行類似的優(yōu)化。另外,由于經(jīng)常會對全屏進行無條件更新,特編制一個將OledRamMap[]無條件傳送到OLEDSRAM的函數(shù),其執(zhí)行速度分別是上面改進前后更新處理代碼的1.497和1.596倍。
(2) 關(guān)于顯示顏色
在使用顏色設(shè)置函數(shù)GUI_SetColor()和GUI_ SetBkColor()時需要特別注意。雖然單色OLED僅需0和1兩種顏色,但uC/GUI系統(tǒng)的顏色仍然是由紅r、綠g和藍(lán)b三個分量構(gòu)成的U32類型(即r×65536+g×256+b),要想將顏色設(shè)置為1,必須保證r+g+b≥(255×3/2)=383。如GUI_SetColor(0x80ff)將前景色設(shè)置為1,而GUI_SetColor(0x7fff)則將前景色設(shè)置為0。
本文給出的處理方法已在實際硬件上進行了完整的測試和驗證,因移植uC/GUI而增加的代碼為7754 B、點陣數(shù)據(jù)3374 B、RAM空間1488 B(含SRAM映像1024 B和標(biāo)志128 B,實際uC/GUI使用的空間為336 B)。而不用uC/GUI實現(xiàn)的僅有字符(且只能在整頁地址行上)顯示的代碼,其長度是:代碼2040 B、點陣數(shù)據(jù)3376 B、RAM空間16 B。相比之下,具有強大功能和易用、易擴展性的uC/GUI的確是短小精干,具有很大的優(yōu)勢。此外本文所述的方法同樣適用于彩色OLED,而其中的快速判決思想則可應(yīng)用到其它類似的軟件設(shè)計中。
[1] 石億,黃輝先,趙娟,等.uC/OS-Ⅱ與uC/GUI在Cortex-M3上的移植研究與實現(xiàn)[J].微計算機信息,2012,28(9):159-161.
[2] 葛欣,孟凡榮.使用uC/GUI開發(fā)圖形用戶界面[J].計算機工程與設(shè)計,2005,26(1):253-255.
[3] 李向陽,曾旖,奚大順.在uC/GUI中實現(xiàn)漢字顯示[J].單片機與嵌入式系統(tǒng)應(yīng)用,2005(5):76-77.
[4] 葛欣,孟凡榮.移植uC/GUI應(yīng)用程序時有關(guān)LCD配置的研究[J].計算機工程與設(shè)計,2005,26(4):1006-1008.
[5] 劉飛.OLED照明技術(shù)及應(yīng)用進展[J].照明工程學(xué)報,2014,25(3):93-97.
[6] 劉勇.基于MSP430F149的OLED顯示系統(tǒng)的設(shè)計[J].電子技術(shù)與軟件工程,2013(21):177-179.
[7] Solomon Systech Limited.SSD1303用戶手冊[EB/OL].2006.6,V2.4. http://wenku.baidu.com/view/6aa1891aff00bed5b9f31d7f.html.
[8] Micrium.uC-GUI 5-26 user manual.pdf[EB/OL]. 2014.12,V5.26. https://doc.micrium.com/download/attachments/10753198.
RESEARCH ON TRANSPLANT AND OPTIMISATION OF OLED DISPLAY IMPLEMENTED WITH uC/GUI
Hu PingpingWang Jingjie
(SchoolofAutomation,BeijingInformationScienceandTechnologyUniversity,Beijing100192,China)
To expand the functions of OLED display and processing in STM32 system and to improve processing efficiency, we presented the transplant approach for OLED minimum display system implemented with uC/GUI. According to the features of OLED monitor we proposed new program structure, designed the fast discrimination algorithm, and optimised the key codes. The efficient OLED display and processing under uC/GUI is then implemented. It is proved by actual test data analysis that the designed algorithm decreases the complexity of processing and doubles the display and processing speed as well.
uC/GUIOLEDTransplantAlgorithm optimisation
2015-07-31。北京市重點學(xué)科項目(5111523302)。胡平平,副教授,主研領(lǐng)域:信號處理,智能儀器。王晶杰,副教授。
TP311.54
A
10.3969/j.issn.1000-386x.2016.10.057