做任何电子产品,必然有人机交互的部分,对于开发人员而已,可能性只需要一个简单的串口就可以完成产品所有功能上的开发,但是产品推广后期必然会加入一些显示器件,最常用的就是LCD、OLED、TFT(可能还有一些,毕竟天外有天),这里罗列出本人所使用过的一些显示器件,并对这些显示器件的开发小工具及其算法简单的进行说明(个人的理解,可能会有偏差)。
本文以OLED12864为硬件平台,以取模软件PCtoLCD2002为软件平台,分析逐列式、逐行式、列行式、行列式四种取模方式及其算法以及算法的选择。(算法主要针对16X16像素汉字,但亦可稍作修改移植运用到写16X8像素字符或图片显示)(为便于理解,算法没做太多优化)
(1)LCD1602(单片机初学者最熟悉不过的型号,实际上LCD1602也可以显示中文,但是效果不是很好,这里不再详细说明)
(2)LCD2004(每行可显示20个字符,共4行,操作方法和LCD1602几乎相同,只是把LCD1602的80字节的内存数据全部显示出来而已)
(3)NOKIA5110(SPI协议,可显示中文)
(4)LCD12864(又分为有字库液晶12864和无字库液晶12864,但是都可以显示中文)
(5)OLED12864(和LCD12864类似,但是其优点更多,未来趋势,本文章的实验硬件平台)
(6)TFT液晶屏(暂无图片)
(1)《Image2Lcd 2.9(破解版)》 下载链接:http://pan.baidu.com/s/1mg9ZFAw
Image2Lcd 界面:
对于这个软件,本人目前没有使用过,所以暂时没有发言权,如果有使用过的朋友,希望可以不吝赐教。
(2)《zimoV2.2》 下载链接:http://pan.baidu.com/s/1qWuYy8s
zimoV2.2界面:
(3)《LcmSim LcmZimo TakeDotLib 组合软件》 下载链接:http://pan.baidu.com/s/1gd2csP9
LcmSim 界面:
LcmZimo界面:
TakeDotLib界面:
(4) 《PCtoLCD2002》 (本文章的实验软件平台 ) 下载链接:http://pan.baidu.com/s/1qWKGR16
PCtoLCD2002界面:
首先,介绍一下我所使用的OLED12864,其驱动为SSD1306,支持以下5中通信模式:
1. IIC通信模式
2. 3线式SPI通信模式
3. 4线式SPI通信模式
4. 6800系列并行通信模式
5. 8080系列并行通信模式
本次实验,我采用IIC通信模式。(实际上不论是哪一种通信模式,区别只是在写命令函数和写数据函数的具体操作不同,所以只需要修改这两个函数,就可以达到移植的目的)
(1)OLED12864显示ASCII值:
(2)OLED12864横向滚屏:
(3)OLED12864纵向滚屏
(4)OLED12864斜向滚屏
(5)OLED12864综合滚屏:
(6)OLED12864画棋盘;
(7)OLED12864画图片
(8)OLED12864反转显示:
OLED12864内部没有集成字库,所以需要我们自己利用PCtoLCD2002取模软件取出我们需要的文字,然后在程序中对照数据,进行编写程序。在讲解具体算法之前,需要先了解OLED12864的地址结构。蓝色部分表示默认情况下的页和列配置,红色部分是进行页和列的重映射后的情况页地址和列地址。
正因为OLED12864可以进行页地址和列地址的重映射,所以可以做到下图的效果:(实际上很多液晶屏都有这种特性)
(默认情况:上图Figure8-13中的蓝色部分配置时显示文字)
(重映射情况:上图Figure8-13中的红色部分配置时显示文字)
另外,
OLED12864的三种地址模式,分别是:页地址模式、水平地址模式、垂直地址模式。(默认页地址模式)
(1)页地址模式
在页地址模式中,当RAM数据被读取或者写入后,列地址指针自动加一。如果列地址指针到达本页的最后一列后,再增加一则回到本页的第一个Column0列地址,而页地址指针不变(还在本页)。用户需要设置新的一页的第一列地址然后再去写RAM数据。如下图所示:
(2)水平地址模式:
在水平地址模式下,当RAM被读取或者写入后,列地址指针增一,当列地址到达本页的最后一个地址时,再增加一则列地址变为Column0,但页地址同时也会增加一。当列地址和页地址都到达最后时,再增加一则列地址和页地址都会跳转到最开始的位置,即Column0和Page0.
(3)垂直地址模式:
在垂直地址模式下,当RAM被读取或者写入后,页地址自动增加一,如果页地址到达Page7,再增加一会变为Page0,同时列地址加一。当页地址和列地址都到达最后,再增加一就会变为Page0和Column0.
以上主要以OLED12864入手,讲解了其内部的地址模式,尽管其他的显示模块和OLED12864的模式会有不同之处,但是主要思路是相似。下面结合PCtoLCD2002来讲解具体算法。取模软件PCtoLCD2002有四种取模方式,如下图红色部分所示(蓝色部分默认即可),
(1)逐列式
逐列式取模过程如图所示:
逐列式程序控制书写过程:
逐列式其算法思路是:
(1)确定地址(页地址和列地址)
(2)写数据
(3)页地址加一
(4)写数据
(5)返回步骤1所处的页地址(列地址指针自动加一,无需程序控制)
(6)上面步骤1~5重复16次,完成16X16像素汉字书写。
算法如下:
/*逐列式*/
for(j=0;j<16; j++)
{
OLED12864_SetPosition(startPage,columnAddress+j);
OLED12864_WriteData(Font16x16[addre++]);
OLED12864_SetPosition(startPage + 1 , columnAddress+j);
OLED12864_WriteData(Font16x16[addre++]);
}
(2)逐行式: (必须“打碎”数据,取模软件取出的数据不能直接运用,而是需要数据重组操作,原因可参照下面两图)
(2)设一个变量N为0;
(3)找到汉字对应的字模位置,然后将其后第0个(起始位置本身)、第2个、第4个、第6个、第8个、第10个、第12个、第14个数据的BitN(第一次为Bit0,以后依次类推)提取出来,重组成一个数据(低位在前);
(4)写入重组的数据(列地址指针自动加一,无需程序控制);/*逐行式*/
UB8 temp ;
OLED12864_SetPosition(startPage,columnAddress);
for(j=0;j<8 ;j++)
{
temp =(((Font16x16[addre+0]&(0x01<>j)<<0) | (((Font16x16[addre+2]&(0x01<>j)<<1) |(((Font16x16[addre+4]&(0x01<>j)<<2) | \
(((Font16x16[addre+6]&(0x01<>j)<<3) | (((Font16x16[addre+8]&(0x01<>j)<<4) |(((Font16x16[addre+10]&(0x01<>j)<<5) | \
(((Font16x16[addre+12]&(0x01<>j)<<6) | (((Font16x16[addre+14]&(0x01<>j)<<7) ;
OLED12864_WriteData(temp);
}
++addre;
for(j=0;j<8 ;j++)
{
temp =(((Font16x16[addre+0]&(0x01<>j)<<0) | (((Font16x16[addre+2]&(0x01<>j)<<1) |(((Font16x16[addre+4]&(0x01<>j)<<2) | \
(((Font16x16[addre+6]&(0x01<>j)<<3) | (((Font16x16[addre+8]&(0x01<>j)<<4) |(((Font16x16[addre+10]&(0x01<>j)<<5) | \
(((Font16x16[addre+12]&(0x01<>j)<<6) | (((Font16x16[addre+14]&(0x01<>j)<<7) ;
OLED12864_WriteData(temp);
}
addre += 15;
OLED12864_SetPosition(startPage+1,columnAddress);
for(j=0;j<8 ;j++)
{
temp =(((Font16x16[addre+0]&(0x01<>j)<<0) | (((Font16x16[addre+2]&(0x01<>j)<<1) |(((Font16x16[addre+4]&(0x01<>j)<<2) | \
(((Font16x16[addre+6]&(0x01<>j)<<3) | (((Font16x16[addre+8]&(0x01<>j)<<4) |(((Font16x16[addre+10]&(0x01<>j)<<5) | \
(((Font16x16[addre+12]&(0x01<>j)<<6) | (((Font16x16[addre+14]&(0x01<>j)<<7) ;
OLED12864_WriteData(temp);
}
++addre;
for(j=0;j<8 ;j++)
{
temp =(((Font16x16[addre+0]&(0x01<>j)<<0) | (((Font16x16[addre+2]&(0x01<>j)<<1) |(((Font16x16[addre+4]&(0x01<>j)<<2) | \
(((Font16x16[addre+6]&(0x01<>j)<<3) | (((Font16x16[addre+8]&(0x01<>j)<<4) |(((Font16x16[addre+10]&(0x01<>j)<<5) | \
(((Font16x16[addre+12]&(0x01<>j)<<6) | (((Font16x16[addre+14]&(0x01<>j)<<7) ;
OLED12864_WriteData(temp);
}
为什么是上面的算法,这里需要仔细分析:
已知OLED12864具有以下的结构:在每一页中,从最上面一行到最下面一行是一组数据,上面的为低位,下面的为高位。蓝色为书写方式,这是由OLED12864硬件决定的,但是红色是我们利用取模软件取模的时候的方向,所以取模的数据是不能够直接利用的,而是需要“打碎”重组数据的。
(3)列行式
列行式取模过程如图所示:
列行式程序控制书写过程如图所示:
列行式算法思路是:
(1)确定地址(页地址和列地址) ;
(2)写数据(列地址自动加一,无需程序控制);
(3)重复步骤(2)共16次,完成16x16汉字上半部分书写 ;
(4)重新设置地址,页地址加1,列地址恢复步骤(1)时的列地址
(5)重复步骤步骤(4)共16次,完成16x16汉字下半部分
/*列行式*/
OLED12864_SetPosition(startPage,columnAddress);
for(j=0;j<16; j++)
{
OLED12864_WriteData(Font16x16[addre++]);
}
OLED12864_SetPosition(startPage+1,columnAddress);
for(j=0;j<16; j++)
{
OLED12864_WriteData(Font16x16[addre++]);
}
(4)行列式 (必须“打碎”数据,取模软件取出的数据不能直接运用,而是需要数据重组操作,原因可参照下面两图)
行列式取模过程如图所示:
行列式程序控制书写过程如图所示:
行列式算法思路是:
(1)确定地址(页地址和列地址),准备写16x16汉字的左上半部分;
(2)设一个变量N为0;
(3)找到汉字对应的字模位置,然后将其后第0个(起始位置本身)、第1个、第2个、第3个、第4个、第5个、第6个、第7个数据的BitN(第一次为Bit0,以后依次类推)提取出来,重组成一个数据(低位在前);
(4)写入重组的数据(列地址指针自动加一,无需程序控制);
(5)N自加1,为下一个数据重组做准备,返回步骤3,重复操作8次后,完成汉字左上部分书写,转向步骤6;
(6)变量N重新赋值0;
(7)页地址加1,列地址和步骤1列地址相同,准备写16x16汉字的左下半部分;
(8)变量N重新赋值0; (9)找到汉字对应的字模位置,然后将其后第8个、第9个、第10个、第11个、第12个、第13个、第14个、第15个数据的BitN(第一位为Bit0,以后依次类推)提取出来,重组成一个数据(低位在前);
(10)写入重组的数据(列地址指针自动加一,无需程序控制);
(11)N自加1,为下一个数据重组做准备,返回步骤9,重复操作8次后,完成汉字左下部分书写,转向步骤12;
(12)变量N重新赋值0;
(13)设置地址,页地址和步骤1的页地址相同,列地址为步骤1列地址加上8 ;
(14)变量N重新赋值0; (15)找到汉字对应的字模位置,然后将其后第16个、第17个、第18个、第19个、第20个、第21个、第22个、第23个数据的BitN(第一位为Bit0,以后依次类推)提取出来,重组成一个数据(低位在前);
(16)写入重组的数据(列地址指针自动加一,无需程序控制);
(17)N自加1,为下一个数据重组做准备,返回步骤15,重复操作8次后,完成汉字右上部分书写,转向步骤18;
(18)页地址加1,列地址和步骤13相同,即步骤1的列地址加上8.
(19)N重新赋值为0 (20)找到汉字对应的字模位置,然后将其后第24个、第25个、第26个、第27个、第28个、第28个、第30个、第31个数据的BitN(第一位为Bit0,以后依次类推)提取出来,重组成一个数据(低位在前);
(21)写入重组的数据(列地址指针自动加一,无需程序控制);
(22)N自加1,为下一个数据重组做准备,返回步骤20,重复操作8次后,完成汉字右下部分书写,完成。
/*行列式*/
UB8 temp ;
OLED12864_SetPosition(startPage,columnAddress);
for(j=0;j<8; j++)
{
temp = (((Font16x16[addre+0]&(0x01<>j)<<0) | (((Font16x16[addre+1]&(0x01<>j)<<1) |(((Font16x16[addre+2]&(0x01<>j)<<2) | \
(((Font16x16[addre+3]&(0x01<>j)<<3) | (((Font16x16[addre+4]&(0x01<>j)<<4) |(((Font16x16[addre+5]&(0x01<>j)<<5) | \
(((Font16x16[addre+6]&(0x01<>j)<<6) | (((Font16x16[addre+7]&(0x01<>j)<<7);
OLED12864_WriteData(temp) ;
}
OLED12864_SetPosition(startPage+1,columnAddress);
addre +=8 ;
for(j=0;j<8; j++)
{
temp = (((Font16x16[addre+0]&(0x01<>j)<<0) | (((Font16x16[addre+1]&(0x01<>j)<<1) |(((Font16x16[addre+2]&(0x01<>j)<<2) | \
(((Font16x16[addre+3]&(0x01<>j)<<3) | (((Font16x16[addre+4]&(0x01<>j)<<4) |(((Font16x16[addre+5]&(0x01<>j)<<5) | \
(((Font16x16[addre+6]&(0x01<>j)<<6) | (((Font16x16[addre+7]&(0x01<>j)<<7);
OLED12864_WriteData(temp) ;
}
addre +=8 ;
OLED12864_SetPosition(startPage,columnAddress+8);
for(j=0;j<8; j++)
{
temp = (((Font16x16[addre+0]&(0x01<>j)<<0) | (((Font16x16[addre+1]&(0x01<>j)<<1) |(((Font16x16[addre+2]&(0x01<>j)<<2) | \
(((Font16x16[addre+3]&(0x01<>j)<<3) | (((Font16x16[addre+4]&(0x01<>j)<<4) |(((Font16x16[addre+5]&(0x01<>j)<<5) | \
(((Font16x16[addre+6]&(0x01<>j)<<6) | (((Font16x16[addre+7]&(0x01<>j)<<7);
OLED12864_WriteData(temp) ;
}
addre +=8 ;
OLED12864_SetPosition(startPage+1,columnAddress+8);
for(j=0;j<8; j++)
{
temp = (((Font16x16[addre+0]&(0x01<>j)<<0) | (((Font16x16[addre+1]&(0x01<>j)<<1) |(((Font16x16[addre+2]&(0x01<>j)<<2) | \
(((Font16x16[addre+3]&(0x01<>j)<<3) | (((Font16x16[addre+4]&(0x01<>j)<<4) |(((Font16x16[addre+5]&(0x01<>j)<<5) | \
(((Font16x16[addre+6]&(0x01<>j)<<6) | (((Font16x16[addre+7]&(0x01<>j)<<7);
OLED12864_WriteData(temp) ;
}
(1)逐列式:
不需要进行数据重组,取模软件取出数据直接运用。
写完一字节数据就需要重新设置一次地址,写一个16x16汉字,需要经历32次地址设置 : 每一列数据书写需要设置2次,共16列。
(2)逐行式:
需要进行数据重组,取模软件取出的数值无法直接运用。
写1个字节数据需要对8个数据进行Bit位数值的提取和重组操作,写完16字节数据需要重新设置一次地址,写一个16x16汉字,需要经历2次地址设置 : 汉字上半部分的页地址和汉字下半部分的页地址分别设置一次。
(3)列行式:
不需要进行数据重组,取模软件取出数据直接运用。
写完16字节数据需要重新设置一次地址,写一个16x16汉字,需要经历2次地址变换: 汉字上半部分的页地址和汉字下半部分的页地址分别设置一次。(注意这里和逐行式的做法本质上是一样的,只是在于数据是否可以直接运用)
(4)行列式:
需要进行数据重组,取模软件取出的数值无法直接运用。
写1个字节数据需要对8个数据进行Bit位数值的提取和重组操作,写完8个字节数据就需要重新设置一次地址,写一个16x16汉字,需要经历4次地址设置:汉字分为左上部分、左下部分、右上部分、右下部分,共需要设置4次地址
(1)从数据处理方面看,不进行数据重组,其书写速度上可以更快,因此逐列式和列行式更有优势。
(2)从地址设置方面看,尽量少的程序设置地址,而尽量多的利用硬件自身的列地址自增的性质,这样速度更快,因此逐行式=列行式>行列式>逐列式。
综合以上两个方面的比较,对于显示16x16汉字,列行式是最为突出的算法,不仅直接利用数据无需重组,而且尽量多的利用了其硬件特性,是最好的选择。(注意,这里主要针对16x16像素汉字而言,对于显示字符分析方法类似,对于不同的显示内容,结论也会有所差异,不做解析)