胡平平 王晶杰
(北京信息科技大学自动化学院 北京 100192)
uC/GUI实现OLED显示的移植与优化研究
胡平平王晶杰
(北京信息科技大学自动化学院北京 100192)
为了扩展STM32系统中OLED显示处理功能、提高处理效率,给出了uC/GUI实现OLED最小显示系统的移植方法。并根据OLED显示器的特性,提出了新的程序结构,设计了快速判决算法,对关键代码进行了优化,在uC/GUI下实现了高效的OLED显示处理。实际测试数据分析证明,所设计的算法不仅降低了处理复杂度,还使显示处理速度成倍提高。
uC/GUIOLED移植算法优化
uC/GUI是著名的uC/OS-II开发者Micrium 公司的另一个优秀开源软件,是专为嵌入式应用而设计的、可移植、可裁减的图形用户界面系统软件[1]。uC/GUI可为任何使用图像LCD的应用程序提供高效的、独立于处理器和LCD控制器的图形用户接口[2]。uC/GUI可用于单任务和多任务嵌入式系统中,全部代码用C语言完成,可方便地移植到各种CPU上[3,4]。
有机发光二极管(简称OLED)具有纤薄、可制成各种形状、占用空间小等优势,它是继白炽灯、荧光灯、LED之后的又一次光源革命。由于它节能、环保、寿命长而应用广泛,被列入我国战略性新兴产业[5]。和LCD相比,OLED具有体积小、亮度高、视角宽、自发光、低功耗、寿命长、动态范围广和可弯曲等诸多优点[6]。
由于uC/GUI主要是为LCD设计的,向各种LCD显示器的移植不仅简单,而且执行效率非常高。OLED有着与LCD不同的控制方式和RAM映像,虽然按常规方法将uC/GUI移植到OLED显示器并不困难,但执行效率非常低。这既不符合嵌入式系统严苛的实时性要求,也没有充分发挥uC/GUI的优势。
本文在分析uC/GUI系统特点的基础上,先给出了实现OLED最小显示系统的移植方法,然后结合OLED显示的特点,设计了全新的程序结构并对关键代码进行了优化,提出了快速判决算法,不仅提高了软件的执行效率,且快速实现了复杂的处理。
本文所使用MCU是Cortex-M3系列芯片STM32F105,OLED为单色128×64点阵显示器,驱动芯片为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最大可驱动132列64行点阵显示器,内部有132×64位显示SRAM,MCU可对它进行读写操作。该SRAM的位和显示点阵的映射关系如图2所示。
图2 显示器点阵和SRAM的映射关系
显示点阵的64行分为8页,每页8行;页中的每一列映射为SRAM的一个字节,位序为bit7至bit0,每页中的132列映射为SRAM中连续的132个字节。读写一个SRAM字节的代码如下:
void WriteOledCmd(U8 Cmd)
{
SetOledData(Cmd);
Oled_DC= 0;Oled_WR= 0;
// 写命令字
Oled_CS= 0;Oled_CS= 1;
// 产生CS低脉冲
Oled_WR= 1;Oled_DC= 1;
// 写结束,默认数据字
}
void WriteOledByte(U8 Data, int Page, int Col)
{
WriteOledCmd(Page+0xB0);
// 设置页号
WriteOledCmd(Col&0xf);
// 设置列低地址
WriteOledCmd((Col>>4)+0x10);
//设置列高地址
Oled_WR = 0;
// 写操作
SetOledData(Data);
Oled_CS = 0;Oled_CS = 1;
// 产生CS脉冲
Oled_WR = 0;
// 写结束
}
U8 ReadOledByte(int Page, int Col)
{
U8 Data;
WriteOledCmd(Page+0xB0);
// 设置页号
WriteOledCmd(Col&0xf);
// 设置列低地址
WriteOledCmd((Col>>4)+0x10);
//设置列高地址
Oled_RD = 0; Oled_CS = 0;
// 读操作
Data = GetOledData();
// 得到数据字节
Oled_CS = 1; Oled_RD = 0;
// 读结束
return Data;
}
读(写)一个SRAM字节的处理需要92(95)条汇编指令,代码长度为109(110)个字。
本文使用的是V3.94版的uC/GUI,移植过程分为添加源程序、修改配置文件和编制关键代码三部分。
2.1添加uC/GUI源程序
uC/GUI的可裁剪性表现在其源程序按函数功能可细分为许多独立的小源程序文件,如GUI_DispChars.c文件只含GUI_Disp Chars()一个函数,仅两行代码!简单地将所有uC/GUI源程序都添加到项目文件中,对支持smart链接(仅链接用到的模块)的编译器来说不会增加目标代码的尺寸,但臃肿的项目文件不符合uC/GUI短小精干原则和“绿色”编程精神。好的做法是在项目文件夹中创建一个与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,该文件包含了移植中所有需要修改的函数。
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)
// 不支持操作系统
GUI_SUPPORT_TOUCH(0)
// 不支持触摸屏
GUI_SUPPORT_UNICODE(0)
// 不支持ASCII和Unicode混合使用
GUI_DEFAULT_FONT &GUI_Font8x16
// 原GUI_Font6x8
GUI_ALLOC_SIZE128*64
// 为WM和存储设备用
GUI_WINSUPPORT0
// 不支持窗口
GUI_SUPPORT_MEMDEV0
// 不支持存储设备
GUI_SUPPORT_AA0
// 不支持去锯齿
2.3编制关键代码
移植只需对LCDDummy.c里的相关代码进行修改即可,基本系统的移植仅需修改三个函数:
(1) 初始化代码
uC/GUI初始化由GUI_Init()完成,调用的一系列函数中仅GUI_X_Init()和LCD_L0_Init()需要修改。GUI_X_Init()用于初始化GUI运行之前需运行的硬件,本文系统没有这样的硬件,仅在LCDDummy.c中设置一个空的GUI_X_Init()函数即可。初始化移植只需将LCD_L0_Init()对LCD_INIT_CONTROL LER()的调用替换为对OLED控制器SSD1303的初始化处理就行了。
(2) 设置像素代码
模板中设置像素函数是一个空函数,移植后的代码如下:
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) 读取像素代码
模板中读取像素函数是一个仅返回0的函数,移植后的代码如下所示:
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虽然能够正确地实现OLED的显示处理,但其执行效率非常低。分析uC/GUI的代码发现,所有显示处理最终都是通过LCD_L0_SetPixelIndex()和LCD_L0_GetPixelIndex()两个函数实现的,而单独对每一个像素处理并没有充分利用OLED显示器的特点。
首先,向OLED显示器读写一个像素与读写一页中该列的8行像素的处理量完全一样。在上述读(写)SRAM字节代码中,设置地址(页号和列号)的指令数多达72条,占总代码92(95)条的78.3%(75.8%)。而实际上OLED显示器内部有一个可以自动加1的地址计数器,仅需设置一次页号和列号,就可以连续读写最多达132个SRAM字节,这一特性在上面的代码中完全没有利用。
以显示一个8×16点阵的字符为例,需要调用128次设置像素的函数,共需执行207×128=26 496条指令。而实际上需要设置SRAM的字节数量最多24个(若字符起始行正好在页边界上则仅为16个),如果连续设置,仅需设置3次地址和写信号( (72+6)×3=234条指令),然后3次连续写8个字节(13×8×3=312条指令),总指令数量为546条,是逐像素处理的2.06%。可见,提高显示处理速度存在很大的空间。
3.2新的处理策略和关键代码
为了充分利用OLED显示的优势,必须将像素的逐点处理改为批量处理,为此本文设计了新的处理策略:在MCU内存中设置一个OLEDSRAM的映像空间OledRamMap,同时设置一个SRAM更新标志OledRamUpdateFlag:
U8 OledRamMap[8*128];
// 长度1024B
U8 OledRamUpdateFlag[8*(128/8)];
// 长度128B
OledRamMap的每一个字节在OledRamUpdateFlag中都有一位标志表示该字节是否更新。所有处理像素的函数都不直接访问OLED的SRAM,而是对OledRamMap和OledRamUpdate Flag进行设置,必要时由更新函数UpdateOledDisp()按更新标志将OledRamMap写到OLED的SRAM中。
OledRamMap和OledRamUpdateFlag在初始化时被清零,OledRamUpdateFlag在每次更新后也清零。新策略关键代码的实现如下所示:
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更新处理的进一步优化和快速判决算法
上面的更新处理算法在更新数据分片位于连续空间的情况下效率确实很高,但在更新数据零碎且分散的情况下,效率并不高。最坏的情形是更新标志为1和0交替出现,此时要为每一个需更新的数据设置地址。分析发现,送一次数据需13条指令,而设置一次地址需要76条指令,是送数据的5.8倍。也就是说,如果两个需要更新的数据间不需要更新的数据数量小于6个时,就应该连续发送这些无需更新的数据而不重新设置地址。
图3 OLEDSRAM更新处理流程图
为此,在更新标志由1变0时要根据该0后面连续4个标志位的16种情况来决定是否停止发送数据。由于标志由1变0出现的位置有8种(按字节访问的),因此共有128种情况。显然,为决定是否停止发送数据而进行128种判决处理是完全不可接受的。本文设计了快速算法,通过逻辑运算和查表操作,仅需一次判断便可得出判决结果。实现方法如下:
新算法的原则是:如果判决结果是继续发送数据,则将当前0标志及后面4位中某些0标志置位即可。这样可保持处理的简洁性,少增加新的分支。此外,当前0标志的8个位置中,有4个位置的后续4位将延伸到下一个标志字节,因此必须将连续16个更新标志用一个U16变量CntFlagW联合处理。而检测字MaskW也由0x8000移位至0x100,新增一个与此对应的变量BitShift由0变至7。设置一个判决门限数组FlagLevel如下:
const u16 FlagLevel[]={0x0400,0x0200,0x0100,0x0080,
0x0040,0x0020,0x0010,0x0008};
令Tmp = CntFlagW&(MaskW-1),
如果Tmp≥FlagLevel[BitShift],则应该继续发送数据,此时应将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个无关二进制位,以下同),此时MaskW=0x4000,BitShift=1,Tmp=CntFlagW &0x3fff=001000X,显然Tmp≥FlagLevel[1]=0x200,应该继续发送数据且将CntFlagW与FlagSetV[8]<<10=0x4000逻辑或,变成111000X。此处的三个0是不能置位的,因为X的前3位为0时,后面的第1个0处应该停止发送数据。如果CntFlagW= 101001X,也应该继续发送数据且将CntFlagW与FlagSetV[9] <<10=0x16<<10=0x5800逻辑或,变成111111X。对于BitShift=1时,仅CntFlagW= 100000X且X的第一个位为0的情况下Tmp的最大值是0x1FF,才会停止发送数据,此时从当前0标志开始已经有6个连续的0标志了。对于BitShift为其它值的情况与此完全类似。
另外,如果连续的8个标志都是0,则无需进行最内层的MaskW移位循环,从而进一步提高了速度。改进后的更新处理流程如图4所示。
图4 改进的OLEDSRAM更新处理流程图
3.4优化结果的性能分析
1) 更新处理改进前后的性能比较
通过在实际代码上对标志位各种组合共32类情况的精确测试分析,在0和1标志交替出现,较短(5个以内)连续0标志和较长(8个及以上)连续0标志的情况下,改进后都变快。仅在6至14个连续0标志且非整字节0标志的情况下,改进后速度基本没变(最慢0.95)。大量连续0标志的情况下,改进后速度提升最多,最大为8.704倍。
2) 优化前后显示字符的性能比较
优化前,显示一个字符设置像素的指令数量为26 496条;优化后设置像素函数仅30条指令,显示一个字符设置像素的指令数量为30×128=3840,而更新处理(按最多的24字节算)则需要1330(全0标志)+ 666×1(送24字节)+252(设置3次地址)-12×1(减去0标志已含)=2236,共6076,速度提高了4.36倍。同理,连续显示N个字符再更新的速度比如表1所示。
表1 连续显示N个字符再更新的速度比
可见,一次更新处理前显示的字符越多,优化方案的速度提高越大。如果考虑到实际应用中显示区域重叠的情况,本文方案的优势就更大了。
3) 可优化的其它处理函数和使用注意事项
(1) 其他可以优化的函数
模板中的绘制函数LCD_L0_DrawHLine()、LCD_L0_Draw VLine()和LCD_L0_FillRect()也可以进行类似的优化。另外,由于经常会对全屏进行无条件更新,特编制一个将OledRamMap[]无条件传送到OLEDSRAM的函数,其执行速度分别是上面改进前后更新处理代码的1.497和1.596倍。
(2) 关于显示颜色
在使用颜色设置函数GUI_SetColor()和GUI_ SetBkColor()时需要特别注意。虽然单色OLED仅需0和1两种颜色,但uC/GUI系统的颜色仍然是由红r、绿g和蓝b三个分量构成的U32类型(即r×65536+g×256+b),要想将颜色设置为1,必须保证r+g+b≥(255×3/2)=383。如GUI_SetColor(0x80ff)将前景色设置为1,而GUI_SetColor(0x7fff)则将前景色设置为0。
本文给出的处理方法已在实际硬件上进行了完整的测试和验证,因移植uC/GUI而增加的代码为7754 B、点阵数据3374 B、RAM空间1488 B(含SRAM映像1024 B和标志128 B,实际uC/GUI使用的空间为336 B)。而不用uC/GUI实现的仅有字符(且只能在整页地址行上)显示的代码,其长度是:代码2040 B、点阵数据3376 B、RAM空间16 B。相比之下,具有强大功能和易用、易扩展性的uC/GUI的确是短小精干,具有很大的优势。此外本文所述的方法同样适用于彩色OLED,而其中的快速判决思想则可应用到其它类似的软件设计中。
[1] 石亿,黄辉先,赵娟,等.uC/OS-Ⅱ与uC/GUI在Cortex-M3上的移植研究与实现[J].微计算机信息,2012,28(9):159-161.
[2] 葛欣,孟凡荣.使用uC/GUI开发图形用户界面[J].计算机工程与设计,2005,26(1):253-255.
[3] 李向阳,曾旖,奚大顺.在uC/GUI中实现汉字显示[J].单片机与嵌入式系统应用,2005(5):76-77.
[4] 葛欣,孟凡荣.移植uC/GUI应用程序时有关LCD配置的研究[J].计算机工程与设计,2005,26(4):1006-1008.
[5] 刘飞.OLED照明技术及应用进展[J].照明工程学报,2014,25(3):93-97.
[6] 刘勇.基于MSP430F149的OLED显示系统的设计[J].电子技术与软件工程,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。北京市重点学科项目(5111523302)。胡平平,副教授,主研领域:信号处理,智能仪器。王晶杰,副教授。
TP311.54
A
10.3969/j.issn.1000-386x.2016.10.057