浅谈LCD、OLED、TFT取模及其程序开发的算法取舍

做任何电子产品,必然有人机交互的部分,对于开发人员而已,可能性只需要一个简单的串口就可以完成产品所有功能上的开发,但是产品推广后期必然会加入一些显示器件,最常用的就是LCD、OLED、TFT(可能还有一些,毕竟天外有天),这里罗列出本人所使用过的一些显示器件,并对这些显示器件的开发小工具及其算法简单的进行说明(个人的理解,可能会有偏差)。

本文以OLED12864为硬件平台,以取模软件PCtoLCD2002为软件平台,分析逐列式、逐行式、列行式、行列式四种取模方式及其算法以及算法的选择。(算法主要针对16X16像素汉字,但亦可稍作修改移植运用到写16X8像素字符或图片显示)(为便于理解,算法没做太多优化)

一、常用的显示器件:(介绍最常见的6种)

(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界面:

 

 

三、OLED实验效果演示:

        首先,介绍一下我所使用的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显示屏之模块化编程

四、算法分析

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)逐行式:  (必须“打碎”数据,取模软件取出的数据不能直接运用,而是需要数据重组操作,原因可参照下面两图)

逐行式取模过程如图所示:
 
逐行式程序控制书写过程:
 

逐行式其算法思路是:
     (1)确定地址(页地址和列地址),准备写16x16汉字的上半部分;

 

    (2)设一个变量N为0;    

 

    (3)找到汉字对应的字模位置,然后将其后第0个(起始位置本身)、第2个、第4个、第6个、第8个、第10个、第12个、第14个数据的BitN(第一次为Bit0,以后依次类推)提取出来,重组成一个数据(低位在前);

    (4)写入重组的数据(列地址指针自动加一,无需程序控制);
    (5)N自加1,为下一个数据重组做准备,返回步骤3,重复操作8次后,完成汉字左上部分书写,转向步骤6;
    (6)变量N重新赋值0;
    (7)此时字模起始位置的第2、4、6、8、10、12、14字节数据已经被利用完,需要转为利用字模起始位置后的第1、3、5、7、9、11、13、1字节重组数据,方法类似于步骤3,具体如下:找到汉字对应的字模位置,然后将其后第1个、第3个、第5个、第7个、第9个、第11个、第13个、第15个数据的BitN(第一位为Bit0,以后依次类推)提取出来,重组成一个数据(低位在前);
    (8)写入重组数据(列地址自动加一,无需程序控制);
    (9)N自动加1,为下一次的数据做准备,返回步骤7,重复8次,完成汉字右上部分书写,整体上完成汉字上半部分书写,转向步骤(10);
    (10)页地址加1,准备写16x16汉字的下半部分;
    (11)变量N重新赋值0;
    (12)找到汉字对应的字模位置,然后将其后第16个、第17个、第18个、第19个、第20个、第21个、第22个、第23个数据的BitN(第一位为Bit0,以后依次类推)提取出来,重组成一个数据(低位在前);
    (13)写入重组的数据(列地址指针自动加一,无需程序控制);
    (14)N自加1,为下一个数据重组做准备,返回步骤12,重复操作8次后,完成汉字左下部分书写,转向步骤15;
    (15)变量N重新赋值0;
    (16)找到汉字对应的字模位置,然后将其后第24个、第25个、第26个、第27个、第28个、第29个、第30个、第31个数据的BitN(第一位为Bit0,以后依次类推)提取出来,重组成一个数据(低位在前);
    (17)写入重组的数据(列地址指针自动加一,无需程序控制);
    (18)N自加1,为下一个数据重组做准备,返回步骤16,重复操作8次后完成汉字右下部分书写,整体上完成了16x16汉
        

 
上面两个图片可以看出,取模和书写的数据是不对应的,所以需要“打碎”数据,进行重组 (为了便于理解下面的算法未做优化)
 
算法如下:

 

/*逐行式*/
UB8 temp ;
OLED12864_SetPosition(startPage,columnAddress);
for(j=0;j<8  ;j++)
{
    temp =(((Font16x16[addre+0]&(0x01<<j))>>j)<<0) | (((Font16x16[addre+2]&(0x01<<j))>>j)<<1) |(((Font16x16[addre+4]&(0x01<<j))>>j)<<2) | \
          (((Font16x16[addre+6]&(0x01<<j))>>j)<<3) | (((Font16x16[addre+8]&(0x01<<j))>>j)<<4) |(((Font16x16[addre+10]&(0x01<<j))>>j)<<5) | \
          (((Font16x16[addre+12]&(0x01<<j))>>j)<<6) | (((Font16x16[addre+14]&(0x01<<j))>>j)<<7) ;
    OLED12864_WriteData(temp);
}
++addre;
for(j=0;j<8  ;j++)
{
    temp =(((Font16x16[addre+0]&(0x01<<j))>>j)<<0) | (((Font16x16[addre+2]&(0x01<<j))>>j)<<1) |(((Font16x16[addre+4]&(0x01<<j))>>j)<<2) | \
          (((Font16x16[addre+6]&(0x01<<j))>>j)<<3) | (((Font16x16[addre+8]&(0x01<<j))>>j)<<4) |(((Font16x16[addre+10]&(0x01<<j))>>j)<<5) | \
          (((Font16x16[addre+12]&(0x01<<j))>>j)<<6) | (((Font16x16[addre+14]&(0x01<<j))>>j)<<7) ;
    OLED12864_WriteData(temp);
}
addre += 15;
OLED12864_SetPosition(startPage+1,columnAddress);
for(j=0;j<8  ;j++)
{
    temp =(((Font16x16[addre+0]&(0x01<<j))>>j)<<0) | (((Font16x16[addre+2]&(0x01<<j))>>j)<<1) |(((Font16x16[addre+4]&(0x01<<j))>>j)<<2) | \
          (((Font16x16[addre+6]&(0x01<<j))>>j)<<3) | (((Font16x16[addre+8]&(0x01<<j))>>j)<<4) |(((Font16x16[addre+10]&(0x01<<j))>>j)<<5) | \
          (((Font16x16[addre+12]&(0x01<<j))>>j)<<6) | (((Font16x16[addre+14]&(0x01<<j))>>j)<<7) ;
    OLED12864_WriteData(temp);
}
++addre;
for(j=0;j<8  ;j++)
{
    temp =(((Font16x16[addre+0]&(0x01<<j))>>j)<<0) | (((Font16x16[addre+2]&(0x01<<j))>>j)<<1) |(((Font16x16[addre+4]&(0x01<<j))>>j)<<2) | \
          (((Font16x16[addre+6]&(0x01<<j))>>j)<<3) | (((Font16x16[addre+8]&(0x01<<j))>>j)<<4) |(((Font16x16[addre+10]&(0x01<<j))>>j)<<5) | \
          (((Font16x16[addre+12]&(0x01<<j))>>j)<<6) | (((Font16x16[addre+14]&(0x01<<j))>>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))>>j)<<0) | (((Font16x16[addre+1]&(0x01<<j))>>j)<<1) |(((Font16x16[addre+2]&(0x01<<j))>>j)<<2) | \
            (((Font16x16[addre+3]&(0x01<<j))>>j)<<3) | (((Font16x16[addre+4]&(0x01<<j))>>j)<<4) |(((Font16x16[addre+5]&(0x01<<j))>>j)<<5) | \
            (((Font16x16[addre+6]&(0x01<<j))>>j)<<6) | (((Font16x16[addre+7]&(0x01<<j))>>j)<<7);
     OLED12864_WriteData(temp) ;
}
OLED12864_SetPosition(startPage+1,columnAddress);
addre +=8 ;
for(j=0;j<8; j++)
{
    temp = (((Font16x16[addre+0]&(0x01<<j))>>j)<<0) | (((Font16x16[addre+1]&(0x01<<j))>>j)<<1) |(((Font16x16[addre+2]&(0x01<<j))>>j)<<2) | \
            (((Font16x16[addre+3]&(0x01<<j))>>j)<<3) | (((Font16x16[addre+4]&(0x01<<j))>>j)<<4) |(((Font16x16[addre+5]&(0x01<<j))>>j)<<5) | \
            (((Font16x16[addre+6]&(0x01<<j))>>j)<<6) | (((Font16x16[addre+7]&(0x01<<j))>>j)<<7);
     OLED12864_WriteData(temp) ;
}
addre +=8 ;
OLED12864_SetPosition(startPage,columnAddress+8);
for(j=0;j<8; j++)
{
    temp = (((Font16x16[addre+0]&(0x01<<j))>>j)<<0) | (((Font16x16[addre+1]&(0x01<<j))>>j)<<1) |(((Font16x16[addre+2]&(0x01<<j))>>j)<<2) | \
            (((Font16x16[addre+3]&(0x01<<j))>>j)<<3) | (((Font16x16[addre+4]&(0x01<<j))>>j)<<4) |(((Font16x16[addre+5]&(0x01<<j))>>j)<<5) | \
            (((Font16x16[addre+6]&(0x01<<j))>>j)<<6) | (((Font16x16[addre+7]&(0x01<<j))>>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))>>j)<<0) | (((Font16x16[addre+1]&(0x01<<j))>>j)<<1) |(((Font16x16[addre+2]&(0x01<<j))>>j)<<2) | \
            (((Font16x16[addre+3]&(0x01<<j))>>j)<<3) | (((Font16x16[addre+4]&(0x01<<j))>>j)<<4) |(((Font16x16[addre+5]&(0x01<<j))>>j)<<5) | \
            (((Font16x16[addre+6]&(0x01<<j))>>j)<<6) | (((Font16x16[addre+7]&(0x01<<j))>>j)<<7);
     OLED12864_WriteData(temp) ;
}

 

五、算法分析:(以16x16汉字入手,写16x8字符算法分析需做修改移植运用即可)    

各自特点:

(1)逐列式
            <a>不需要进行数据重组,取模软件取出数据直接运用。
            <b>写完一字节数据就需要重新设置一次地址,写一个16x16汉字,需要经历32次地址设置 : 每一列数据书写需要设置2次,共16列。
    (2)逐行式
            <a>需要进行数据重组,取模软件取出的数值无法直接运用。
            <b>写1个字节数据需要对8个数据进行Bit位数值的提取和重组操作,写完16字节数据需要重新设置一次地址,写一个16x16汉字,需要经历2次地址设置 : 汉字上半部分的页地址和汉字下半部分的页地址分别设置一次。
    (3)列行式
            <a>不需要进行数据重组,取模软件取出数据直接运用。
            <b>写完16字节数据需要重新设置一次地址,写一个16x16汉字,需要经历2次地址变换: 汉字上半部分的页地址和汉字下半部分的页地址分别设置一次。(注意这里和逐行式的做法本质上是一样的,只是在于数据是否可以直接运用)
    (4)行列式
            <a>需要进行数据重组,取模软件取出的数值无法直接运用。
            <b>写1个字节数据需要对8个数据进行Bit位数值的提取和重组操作,写完8个字节数据就需要重新设置一次地址,写一个16x16汉字,需要经历4次地址设置:汉字分为左上部分、左下部分、右上部分、右下部分,共需要设置4次地址

对比:

    (1)从数据处理方面看,不进行数据重组,其书写速度上可以更快,因此逐列式和列行式更有优势。
    (2)从地址设置方面看,尽量少的程序设置地址,而尽量多的利用硬件自身的列地址自增的性质,这样速度更快,因此逐行式=列行式>行列式>逐列式。
    
    综合以上两个方面的比较,对于显示16x16汉字,列行式是最为突出的算法,不仅直接利用数据无需重组,而且尽量多的利用了其硬件特性,是最好的选择(注意,这里主要针对16x16像素汉字而言,对于显示字符分析方法类似,对于不同的显示内容,结论也会有所差异,不做解析)

 

 

  • 34
    点赞
  • 134
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 17
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

刺客阿瑞

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值