3. 点阵LCD 的驱动与显控

在适当的硬件的基础介绍之后,这里将以MzDesign 所提供的针对MzL02 的通用版LCD 驱动程序为对像介绍一种LCD 驱动程序的设计思想;将以在LCD 上的绘点功能程序为基础,构建较为完整的驱动程序(包括显示控制)。通过下图来大概了解一下这种驱动程序的结构以及各模块之间的关系。

MzT24-1 模块的基础驱动程序架构如所示:

此主题相关图片如下61mcu102705.jpg:


图 3.1 驱动程序架构

基础驱动程序由8 个文件组成, 分别为: 底层驱动程序文件LCD_PortConfig.h 、LCD_Driver_User.c、用户API 功能接口函数文件LCD_Driver_User.h、LCD_Config.h、LCD_ASCII.c、LCD_Dis.c 以及GB_Table.c、LCD_Dis.h。

    LCD_PortConfig.h:

该文件为底层驱动程序的头文件,主要对使用到的端口进行定义以及配置,用户在使用基础驱动程序时,要使端口的分配符合实际硬件的接线。

    LCD_Driver_User.c:

该文件为底层驱动程序,负责MCU 与MzT24-1 模块进行数据传输的任务,主要包括初始化模块、写控制指令、写数据、读数据等函数;这些函数仅供给上一层的LCD_Dis.c 调用,不建议用户在应用程序中调用这些函数。

    LCD_Driver_User.h:

该文件为LCD_Driver_User.c 的对应头文件,里面对应C 源文件当中的函数进行外部声明。

    LCD_Config.h:

此文件为MzT24-1 模块的功能配置文件,实际上此基础驱动程序是通用于各种不同的LCD 模块的,如果需要将驱动程序应用于其它的LCD 模块时,可以在该文件里面修改相关的配置;另外,功能配置文件里面提供了软件的坐标轴翻转功能配置(有些版本的代码中将此功能屏蔽了,仅为了让MCU 对LCD 的操作更快些)。

    LCD_Dis.c:

该文件中提供了供用户应用程序调用的MzT24-1 驱动API 功能函数,如绘点、绘线、绘矩形、绘圆等绘图函数,以及写字符、字符串等功能。

    LCD_ASCII.c:

为基础驱动程序的自带西文字库的数据文件。

    GB_Table.c:

为基础驱动程序的汉字/位图字库的数据文件,为用户自定义使用,另外如果定义了一些有别于基础程序所提供的范例汉定规格的字库,则用户需要自行修改LCD_Dis.c 当中的FontSet 函数。

    LCD_Dis.h:

该文件为LCD_Driver_User.c 的对应头文件,里面对应C 源文件当中的函数进行外部声明。

3.1. 基本驱动程序(LCD_Driver_User)

3.1.1. 端口配置头文件LCD_Portconfig

以MCS51 单片机版的通用LCD 驱动程序为例,看看基本驱动程序里是什么东西。

首先,LCD_PortCongif.h 文件里定义了驱动程序当中所占用MCU 对LCD 驱动控制的端口,这里是对这些端口进行了重定义,如下:

// this file for MCU I/O port or the orther`s hardware config
    // for LCD Display
    #include "REG51.h"
    #include "intrins.h"                       //包含此头文件可直接操作内核的寄存器以及一些定义好的宏
    #define DAT_PORT P0
    sbit LCD_EP=P2^3;
    sbit LCD_RW = P2^4;
    sbit LCD_A0=P2^5;
    sbit LCD_CS = P2^7;
    sbit LCD_RS=P2^6;                    //如果您认为有必要在程序里控制LCD 的复位,就定义它吧~呵呵

在上面的程序代码当中,对P0 端口进行了重定义,即:”#define DAT_PORT P0”,这样,在其它的代码里面都将使用DAT_PORT 进行操作P0 端口;实际上是定义了与MzL02 的8位数据端口连接的P0 口,如果在其它的应用当中,不再使用P0 口作数据口的话,如改用了P1 口之类的,就可以在LCD_PortConfig.h 当中修改这个重定义就可以了。

其它的端口定义也是类似的意思,LCD_EP 定义到了P2.3 的端口,即接MzL02 的EP 口;LCD_RW 定义为P2.4 口,接RW 读写控制;LCD_A0 定义到P2.5,接A0 数据命令选择端口;LCD_CS 定义到P2.7 口,接片选;LCD_RS 定义为P2.6 口,接LCD 模块的外部复位端口。

3.1.2. MCU 与LCD 基本时序控制程序

一般来说,LCD 模块的控制都是通过MCU 对LCD 模块的内部寄存器、显存进行操作来最终完成的;在此我们设计了三个基本的时序控制程序,分别是:

  • 写寄存器函数(LCD_RegWrite)
  • 数据写函数(LCD_DataWrite)
  • 数据读函数(LCD_DataRead)

这三个函数需要严格的按照LCD所要求的时序来编写,下面可以看看MzL02 模块的时序图:

此主题相关图片如下61mcu102706.jpg:

图 3.2 MzL02 模块的6800 时序示意

注意:上图是该模块的控制IC 资料中的原版时序图,其实有些示意不是太稳妥(少标出了RW 线信号的要求),或者说是不太严谨,不过这些不作讨论,请看分析即可;而EP 的有效触发沿在图中很有可能示意有误,实测为上升沿。

图中CS1B(CS2)的信号即为片选CS,RS 即为数据/寄存器的选择端口A0 信号,E 为EP;当作写入寄存器数据操作时,首先要将A0 置低,以通知LCD 模块即将进行的是对寄存器的操作;而RW 线需要置低,以示即将要进行的是写入的操作;然后片选CS 信号置低,装载数据至总线,然后在EP 线上产生一个上升沿以触发LCD 模块将总线上的数据最终载入;

在前面的操作完成后一般都会将各个信号线的状态恢复。而数据(显存)写入、数据读出的操作时序也比较类似,这里就不多作介绍,直接参考例程即可。

//========================================================================
    // 函数: void LCD_RegWrite(unsigned char Command)
    // 描述: 写一个字节的数据至LCD 中的控制寄存器当中
    // 参数: Command 写入的数据,低八位有效(byte)
    // 返回: 无
    // 版本:
    // 2007/01/09 First version
    //========================================================================
    void LCD_RegWrite(unsigned char Command)
    {
    LCD_A0 = 0;         //A0 置低,示意进行寄存器操作
    LCD_RW = 0;         //RW置低,示意进行写入操作
    LCD_EP = 0;         //EP 先置低,以便后面产生跳变沿
    LCD_CS = 0;         //片选CS 置低
    DAT_PORT = Command; //装载数据置总线
    LCD_EP = 1;         //产生有效的跳变沿
    LCD_CS = 1;         //片选置高
    }

数据写入以及读出的函数源码如下:

//========================================================================
    // 函数: void LCD_DataWrite(unsigned char Dat)
    // 描述: 写一个字节的显示数据至LCD 中的显示缓冲RAM 当中
    // 参数: Data 写入的数据
    // 返回: 无
    // 版本:
    // 2007/01/09 First version
    //========================================================================
    void LCD_DataWrite(unsigned char Dat)
    {
        LCD_A0 = 1;         //A0 置高,示意进行显存数据操作
        LCD_RW = 0;         //RW置低,示意进行写入操作
        LCD_EP = 0;         //EP 先置低,以便后面产生跳变沿
        LCD_CS = 0;         //片选CS 置低
        DAT_PORT = Dat;     //装载数据置总线
        LCD_EP = 1;         //产生有效的跳变沿
        LCD_CS = 1;         //片选置高
    }
    //========================================================================
    // 函数: unsigned char LCD_DataRead(void)
    // 描述: 从LCD 中的显示缓冲RAM 当中读一个字节的显示数据
    // 参数: 无
    // 返回: 读出的数据,

// 版本:
    // 2007/01/09 First version
    //========================================================================
    unsigned char LCD_DataRead(void)
    {
        unsigned char Read_Data;
        DAT_PORT = 0xff;         //51 的端口想要输入前,要先给端口全置1
        LCD_A0 = 1;             //A0 置高,示意进行显存数据操作
        LCD_RW = 1;             //RW置高,示意进行读出操作
        LCD_EP = 0;             //EP 先置低,以便后面产生跳变沿
        LCD_CS = 0;             //片选CS 置低
        LCD_EP = 1;             //产生有效的跳变沿
        LCD_EP = 0;
        Read_Data = DAT_PORT; //读出数据
        LCD_CS = 1;             //片选置高
        return Read_Data;     //返回读到的数据
    }

以上便是要介绍的最基本的时序操作程序,它们几乎是整个LCD 驱动程序当中与底层硬件打交道的代码了,这样的话,当要改变驱动LCD 的MCU 端口时或者换用别的MCU 来驱动LCD 时,基本上只需要在这些代码里作一下修改即可。

关于读LCD 状态

而在一般的LCD 模块当中,还有一个功能同样重要,就是读LCD 状态;可以通过此操作获取当前LCD 模块的忙状态以及一些相关的状态信息,当LCD 模块正处于忙状态时,则不宜对它进行数据的写入或读出操作(有很多较老式的LCD 控制器规定在忙的状态下时不允许
写入或读出数据)。所以在很多LCD 的驱动程序当中,会在寄存器写入、数据写入/读出的操作前加入读取LCD状态并判别忙状态的代码;这点可以参考网上流传的很多LCD 驱动程序。不过,对于MzL02这样的较新出的LCD 控制器来说,已经对忙状态不是很在乎了,或者说影响已经很小甚至没有了;所以我们在前面的代码当中并没有加入这样的代码。

至于有没有必要加读状态判忙的代码,要视具体的LCD 控制器而定。

关于时序的时间要求

时序的一个非常重要的数据就是类似上图中标出的tAS88 之类的时间长短要求,只是上图中并没有标出它们的具体最大最小值要求而已;但在编写这类的时序接口程序时它们还是非常重要的,当然还要看MCU 的端口操作速度以及MCU 的指令执行速度。打个比方,有的时序里就会有要求某些信号的电平保持最小宽度,而如果MCU 的指令执行速度以及端口操作速度非常快的话,就需要酌情在连续操作端口的代码之间加入适量的延时(通用用空操作来
代替,具体多少个多少时长视具体的MCU 以及LCD 控制器而定)以保证该信号的脉冲宽度满足要求。

在本文的所列出的源代码当中,并没有如前所述的为时序的要求而插入空操作或延时处理,因为MCU 的速度并不是非常快,况且现在的LCD 控制器的总线速度都挺快的了,没有必要加入而已。

3.2. LCD 的初始化

LCD 模块的初始化主要就是对LCD 模块的寄存器进行初始化,也就是对LCD 控制器当中的寄存器写入要预设的数据,设置好LCD 的特性。MzL02 模块的初始化如下:

//========================================================================
    // 函数: void LCD_Init(void)
    // 描述: LCD 初始化程序,在里面会完成LCD 初始所需要设置的许多寄存器,具体如果
    // 用户想了解,建议查看DataSheet 当中各个寄存器的意义
    // 参数: 无
    // 返回: 无
    // 版本:
    // 2006/10/15 First version
    // 2007/01/09 V1.2
    //========================================================================
    //延时程序
    void TimeDelay(int Time)
    {
    int i;
    if(Time > 0)
        {
            for(i = 0;i < 800;i++)
            {
            }
        Time --;
        }
    }

void LCD_Init(void)
    {
            //LCD 驱动所使用到的端口的初始化(如果有必要的话)
            // LCD_PortInit();
    LCD_RS = 0;
    TimeDelay(200);
    LCD_RS = 1;
    LCD_RegWrite(0xaf);         //LCD On
    LCD_RegWrite(0x2f);         //设置上电控制模式
    LCD_RegWrite(0x81);         //电量设置模式(显示亮度)
    LCD_RegWrite(0x1f);         //指令数据0x0000~0x003f
    LCD_RegWrite(0x27);         //V5 内部电压调节电阻设置
    LCD_RegWrite(0xa2);         //LCD 偏压设置
    LCD_RegWrite(0xc8);         //Com 扫描方式设置,反向
    LCD_RegWrite(0xa0);         //Segment 方向选择,正常
    LCD_RegWrite(0xa4);         //全屏点亮/变暗指令
    LCD_RegWrite(0xa6);         //正向反向显示控制指令
    LCD_RegWrite(0xac);         //关闭静态指示器
    LCD_RegWrite(0x00);         //指令数据
    LCD_RegWrite(0x40 +32);     //设置显示起始行对应RAM
    LCD_RegWrite(0xe0);         //设置读写改模式
    }

在前面的代码当中,可以看到有一行屏蔽掉的代码:LCD_PortInit();如注释所言,像有的MCU 的端口在使用前是需要初始化的,这时就需要在LCD 初始化之前完成对端口的初始化,而MCS51 的端口是双向口,无需初始其的方向;所以在此将该函数屏蔽。

在对寄存器进行写入操作前,一般会控制端口对LCD 模块进行一次外部的硬件复位,即给LCD 模块的复位端口一个低电平的脉冲,这样可以确保LCD 完成正确的复位。当然,如果在硬件上可以保证MCU 在对LCD 进行操作前LCD 模块已经完全完成了复位的话,也可以不需要这样的代码。此外,从合理性来说,对LCD 的复位操作最好是放置在LCD_PortInit函数当中,因为放在上面的代码当中的那个位置的话,当需要移植驱动程序置另外的MCU时,这块的代码也可能会作修改,相对麻烦一些而已。

一般来说,LCD 模块的初始化代码都会由厂商提供参考的,因为有些设置是与集成LCD 模块时的配置有关,如不告知用户推荐设置的话,往往会让使用者浪费时间在摸索合适的配置上。

在初始化的代码当中,可以看到都是通过之前写好的寄存器写入子函数来对LCD 进行操作,当需要换用另外的MCU 来驱LCD 模块时,是不需要修改这部分的代码的。

如想了解LCD 屏的一些寄存器的设置的作用,最好的方法是作一些小实验修改一下设置看一下现像。

3.3. 绘点子程序

3.3.1. 基本绘点函数

前面我们已经提到了,MzDesign 所提供的通用版LCD 驱动程序是基于绘点功能的,下面将介绍这个基本的绘点子程序,源代码如下:

//========================================================================
   // 函数: void Write_Dot_LCD(unsigned char x,unsigned char y,unsigned char i)
   // 描述: 在LCD 的真实坐标系上的X、Y 点绘制填充色为i 的点
   // 参数: x X 轴坐标
   // y Y 轴坐标
   // i 要填充的点的颜色
   // 返回: 无
   // 版本:
   // 2006/10/15 First version
   // 2007/01/09 V1.2
   //========================================================================
   void Write_Dot_LCD(unsigned char x,unsigned char y,unsigned char i)
   {
   unsigned char x_low,x_hight;      //定义列地址的高低位指令
   unsigned char Dot_Mask_Buf=0x01;
   unsigned char y_Page;             //用于存放要画点的位置所在的byte 数据位置
   x = x+1;
   x_low = (x&0x0f);                //定位列地址设置的低位指令
   x_hight = ((x>>4)&0x0f)+0x10;    //定位列地址设置的高位指令
   switch(y&0x07)
   {
   case 0: Dot_Mask_Buf = 0x01;break;
   case 1: Dot_Mask_Buf = 0x02;break;
   case 2: Dot_Mask_Buf = 0x04;break;
   case 3: Dot_Mask_Buf = 0x08;break;
   case 4: Dot_Mask_Buf = 0x10;break;
   case 5: Dot_Mask_Buf = 0x20;break;
   case 6: Dot_Mask_Buf = 0x40;break;
   case 7: Dot_Mask_Buf = 0x80;break;
   }
   y_Page = (y>>3)+0xb0;             //Get the page of the byte
   LCD_RegWrite(y_Page);
   LCD_RegWrite(x_low);
   LCD_RegWrite(x_hight);
   y_Page = LCD_DataRead();          //
   if(i) y_Page |= Dot_Mask_Buf;
   else y_Page &= ~Dot_Mask_Buf;
   LCD_DataWrite(y_Page);             //
   }

在第二章,已经介绍过了在LCD 屏上绘点的程序设计思想了,而从上面的代码当中,更具体的展现了这一基本的程序设计思路。在该函数执行时,首先会对x 轴坐标进行加1 的操作,实际上是因为LCD 的控制IC 当中显存的范围为132*65,而LCD 玻璃屏上只有128*64 点;所以会有那么4 列显存在LCD 屏上没有对应的点(在上一章已有介绍),而具体哪些列不对在LCD 屏上则由LCD 玻璃的封装厂在配置时决定的,通常都是开头的或者是最后的几列。笔者也是通过实验测出MzL02 模块当中当,Segment 采用正向扫描时第0 列和最后的3 列不对应对LCD 玻璃之上;所以在这里为了使坐标轴归零,才在这个函数当中对x 轴坐标加1 操作,这样使用上层的程序看起来,LCD 的显示依然从第0 列开始对应最左边的一列。

而x 轴的坐标需要分两个寄存器设置,所以在代码当中对处理过的x 轴坐标进行高低位数据的分离。Y 轴的坐标在传递入该函数时是以0~63 的点范围传递过来的,而在上一章中已有说明,MzL02 模块的Y 轴实际上是以page 形式存在的,每个page 有8 个点,分别对应一个byte的8 个位;所以也对y 值进行处理,提取出该点所对应的page 值以及该点对应的byte 当中的具体位的位置。

处理完x 和y 轴的坐标值后,代码当中调用寄存器写入函数来完成显存数据指针的指向设置操作,以表明即将进行的读写操作是针对于显存中的哪一个数据进行操作。在读-改-写的操作流程中,可以看到代码里依据i 的值来对读回的数据进行操作,如下列的片断代码:

……
   y_Page = LCD_DataRead();       //
   if(i) y_Page |= Dot_Mask_Buf;
   else y_Page &= ~Dot_Mask_Buf;
   LCD_DataWrite(y_Page);          //
   ……

当i 为1 时则将要绘点的位进行置位操作,即画出黑点;当i 为零时,则对其进行清零操作,即清除该点;操作完成之后再将数据写入显存当中,这些操作是不会影响屏上其它点状态的。以上便是MzL02 模块的绘点函数,其具体的操作流程是跟LCD 模块的特性相关的,即LCD屏的操作方法相关;所以在移植通用版LCD 驱动程序至别外一块LCD 屏时,这个函数是需要进行一定的修改的。

3.3.2. 一些扩展的基础功能函数

除了基本的绘点函数之外,在这版通用版的LCD 驱动程序当中还编写了一个全屏填充的函数,如下:

//========================================================================
   // 函数: void LCD_Fill(unsigned char Data)
   // 描述: 会屏填充以Data 的数据至各点中
   // 参数: Data 要填充的颜色数据
   // 返回: 无
   // 版本:
   // 2006/10/15 First version
   // 2007/01/09 V1.2
   //========================================================================
   void LCD_Fill(unsigned char Data)
   {
   unsigned char i,j;
   unsigned char uiTemp;
   uiTemp = Dis_Y_MAX;
   uiTemp = uiTemp>>3;
   for(i=0;i<=uiTemp;i++)       //往LCD 中填充初始化的显示数据
   {
   LCD_RegWrite(0xb0+i);
   LCD_RegWrite(0x01);
   LCD_RegWrite(0x10);
   for(j=0;j<=Dis_X_MAX;j++)
   {
   LCD_DataWrite(Data);
   }
   }
   }

这个函数用于清屏或者是全屏填充。

3.4. 驱动配置头文件LCD_Config

在驱动程序当中,还配备了一个头文件:LCD_Config.h;在该文件里对LCD 模块的点阵数量,以及坐标轴方向作了软件的定义,另外还定义了一些在LCD 初始化时的LCD 寄存器命令。代码如下:

#define LCD_X_MAX 128-1         //屏幕的X 轴的物理宽度
    #define LCD_Y_MAX 64-1          //屏幕的Y 轴的物理宽度
    #define LCD_XY_Switch 0         //显示时X 轴和Y 由交换
    #define LCD_X_Rev 0             //显示时X 轴反转
    #define LCD_Y_Rev 0             //显示时Y 轴反转
    #if LCD_XY_Switch == 0
    #define Dis_X_MAX LCD_X_MAX
    #define Dis_Y_MAX LCD_Y_MAX
    #endif
    #if LCD_XY_Switch == 1
    #define Dis_X_MAX LCD_Y_MAX
    #define Dis_Y_MAX LCD_X_MAX
    #endif
    #define LCD_INITIAL_COLOR 0x00   //定义LCD 屏初始化时的背景色

//以下定义为针对于SPLC501 的功能指令进行定义的,局部可修改~
    //LCD 供电电平选择
    #define M_LCD_VDD_SET M_LCD_SETR_4     //3.3V 供电时选此二项
    #define M_LCD_VDD M_LCD_BIAS_9         //....
    //#define M_LCD_VDD_SET M_LCD_SETR_4   //5.0V 供电时选此二项
    //#define M_LCD_VDD M_LCD_BIAS_9       //...
    //LCD 指令
    //LCD 开关命令
    #define M_LCD_ON 0xaf
    #define M_LCD_OFF 0xae
    //设置上电控制模式
    #define M_LCD_POWER_BC 0x2c
    #define M_LCD_POWER_VR 0x2a
    #define M_LCD_POWER_VC 0x29
    #define M_LCD_POWER_ALL 0x2f
    //V5 内部电压调节电阻设置……
    #define M_LCD_SETR_0 0x20
    #define M_LCD_SETR_1 0x21
    #define M_LCD_SETR_2 0x22
    #define M_LCD_SETR_3 0x23
    #define M_LCD_SETR_4 0x24
    #define M_LCD_SETR_5 0x25
    #define M_LCD_SETR_6 0x26
    #define M_LCD_SETR_7 0x27
    //...end
    #define M_LCD_ELE_VOL 0x81 //电量设置模式(显示亮度)
    //偏压设置
    #define M_LCD_BIAS_9 0xa2 //V5 时选此选项设置
    #define M_LCD_BIAS_7 0xa1 //V3 时选此选项设置
    //Com 扫描方式设置命令
    #define M_LCD_COM_NOR 0xc0 //正常方式
    #define M_LCD_COM_REV 0xc8 //反相
    //Segment 方向选择
    #define M_LCD_SEG_NOR 0xa0 //正常
    #define M_LCD_SEG_REV 0xa1 //反向
    //全屏点亮/变暗指令
    #define M_LCD_ALL_LIGNT 0xa5 //LCD ALL paint ON
    #define M_LCD_ALL_LOW 0xa4 //Normal Display mode
    //正相反相显示控制指令,RAM 中数据不变
    #define M_LCD_ALL_NOR 0xa6 //正相
    #define M_LCD_ALL_REV 0xa7 //反相
    //静态指示器控制指令
    #define M_LCD_STATIC_ON 0xad //ON
    #define M_LCD_STATIC_OFF 0xac //OFF
    //设置显示起始行对应RAM 行号
    #define M_LCD_BEGIN_LINE 0x40 //基数,后面可加的尾数可为0~63
    //设置当前页基数
    #define M_LCD_COL_PAGE 0xb0 //基数指令,后可加尾数0~8
    //设置当前列基数
    #define M_LCD_COL_LINE_LOW 0x04 //基数指令,低四位有效
    #define M_LCD_COL_LINE_HIG 0x10 //基数指令,低四位有效

配置文件中对LCD 的物理尺寸(点数)进行了定义,并在随后定义了软件上进行坐标变换以及坐标轴反转;在LCD 驱动的功能接口程序当中,将会引用这些定义,来限制对LCD 的显控操作,以防止操作超出LCD 的显示范围;不过,在MzL02 的通用版LCD 驱动程序当中,并没有开放坐标轴变换的程序,也就是坐标轴变换的代码被屏蔽掉了,所以在此有关坐标轴变换(包括反转)的定义是没有实际意义的,但用户不要去更改它的定义。

在完整版的驱动程序的功能程序当中,程序将会从Dis_X_MAX 和Dis_Y_MAX 获得定义的LCD 屏物理点数,所以LCD_XY_Switch 的定义将决定X 轴和Y 轴的坐标是否对调了,当然在代码当中还会有一些代码根据LCD_XY_Switch 的定义而有选择性的进行编译,以配合坐标轴的变换。而类似的定义还有LCD_X_Rev 和LCD_Y_Rev,都在功能程序当中有相应的定义选择对应的代码进行编译。只不过在MzL02 的驱动程序当中,作了一定的精简,把这部分的功能删减掉了。

再接下来的定义中,定义了LCD 屏初始化时的背景色,以及LCD 控制器的寄存器设置命令(实际上就是命令与数据的结合),这些定义在LCD 的初始化代码当中使用(前面列出的LCD 初始化代码是作了修改的,只是把宏定义换为数字表示而已),下面可以再看看实际程序中的初始化代码:

void LCD_Init(void)
    {
    //LCD 驱动所使用到的端口的初始化(如果有必要的话)
    // LCD_PortInit();
    LCD_RS = 0;
    TimeDelay(200);
    LCD_RS = 1;
    TimeDelay(20);
    LCD_RegWrite(M_LCD_ON); //LCD On
    LCD_RegWrite(M_LCD_POWER_ALL); //设置上电控制模式
    LCD_RegWrite(M_LCD_ELE_VOL); //电量设置模式(显示亮度)
    LCD_RegWrite(0x1f); //指令数据0x0000~0x003f
    LCD_RegWrite(M_LCD_VDD_SET); //V5内部电压调节电阻设置
    LCD_RegWrite(M_LCD_VDD); //LCD偏压设置,V3 时选
    LCD_RegWrite(M_LCD_COM_REV); //Com 扫描方式设置
    LCD_RegWrite(M_LCD_SEG_NOR); //Segment 方向选择
    LCD_RegWrite(M_LCD_ALL_LOW); //全屏点亮/变暗指令
    LCD_RegWrite(M_LCD_ALL_NOR); //正向反向显示控制指令
    LCD_RegWrite(M_LCD_STATIC_OFF); //关闭静态指示器
    LCD_RegWrite(0x00); //指令数据
    LCD_RegWrite(M_LCD_BEGIN_LINE+32); //设置显示起始行对应RAM
    LCD_Fill(LCD_INITIAL_COLOR);
    }

3.5. LCD 驱动功能接口程序(LCD_Dis)

LCD 驱动功能接口程序主要包括两大类:

  • 基本绘图功能函数:绘点、直线、矩形、矩形框、圆形、圆框等;
  • 字符显示功能函数:西文字符显示、西文字符串显示、中文字符显示;

这些功能接口程序都是基于前面介绍的绘点函数的,其实,这样的做法并不是很高效,可以说是利用软件而牺牲了硬件的速度;但主要考虑到普通的人机界面程序要求的并不是非常快,只要在人眼能反映过来的时间里完成显示的刷新就可以了,而且主要考虑了移植性、可读性,才选用了这样的程序架构。

一般LCD 模块都会提供了这样或者那样的特性,以便于MCU 对其的显示控制,但通常都是每种LCD 模块都会有自己的一套特殊功能;所以想要编写最高效的LCD 驱动程序的,就不心参考本书所介绍的架构了,直接根据每个LCD 屏的特性来编写;不过现在常用的MCU性能已经不同于原始的MCS51 了,利用一下软件牺牲一下硬件也何偿不可。

3.5.1. 基本绘图功能函数

在我们提供的MzL02 的通用版LCD驱动程序当中,基本绘图功能函数一共提供了如下几个:

  • 绘点:PutPixel
  • 绘直线:Line
  • 绘矩形(框):Rectangle

而鉴于圆形(框)绘制的功能极少用到,将这块的代码裁减掉了,感兴趣的朋友可以参考MzDesign 为其它的LCD 模块所提供的驱动程序。

绘点程序的代码如下:

//========================================================================
    // 函数: void PutPixel(int x,int y)
    // 描述: 在x、y 点上绘制一个前景色的点
    // 参数: x X 轴坐标 y Y 轴坐标
    // 返回: 无
    // 备注: 使用前景色
    // 版本:
    // 2006/10/15 First version
    //========================================================================
    void PutPixel(unsigned char x,unsigned char y)
    {
    Write_Dot_LCD/*Writ_Dot*/(x,y,BMP_Color);
    }

绘点程序非常简单,就是直接调用了在前些节中介绍的绘点函数;这里只不过重新作一下包装,统一下一函数接口。

不过,在代码中可以看到有一个屏蔽掉的函数:Writ_Dot,它就是我们前面所说的实现坐标变换功能的软绘点子程序,这个函数中与LCD 驱动配置头文件里的坐标轴相关的定义有在联,实现了在软件上的坐标轴变换处理。只是这版的驱动当中删减了这项功能,将其屏蔽而已。它的源代码如下,仅供参考:

//========================================================================
    // 函数: void Writ_Dot(int x,int y,unsigned int Color)
    // 描述: 填充以x,y 为坐标的象素
    // 参数: x X 轴坐标 y Y 轴坐标 Color 像素颜色
    // 返回: 无
    // 备注: 这里以及之前的所有x 和y 坐标系都是用户层的,并不是实际LCD 的坐标体系
    // 本函数提供可进行坐标变换的接口
    // 版本:
    // 2006/10/15 First version
    //========================================================================
    void Writ_Dot(int x,int y,unsigned int Color)
    {
    #if LCD_XY_Switch == 0
    #if (LCD_X_Rev == 0)&&(LCD_Y_Rev == 0)
    Write_Dot_LCD(x,y,Color);
    #endif
    #if (LCD_X_Rev == 1)&&(LCD_Y_Rev == 0)
    Write_Dot_LCD(LCD_X_MAX - x,y,Color);
    #endif
    #if (LCD_X_Rev == 0)&&(LCD_Y_Rev == 1)
    Write_Dot_LCD(x,LCD_Y_MAX - y,Color);
    #endif
    #if (LCD_X_Rev == 1)&&(LCD_Y_Rev == 1)
    Write_Dot_LCD(LCD_X_MAX - x,LCD_Y_MAX - y,Color);
    #endif
    #endif
    #if LCD_XY_Switch == 1
    #if (LCD_X_Rev == 0)&&(LCD_Y_Rev == 0)
    Write_Dot_LCD(y,x,Color);
    #endif
    #if (LCD_X_Rev == 1)&&(LCD_Y_Rev == 0)
    Write_Dot_LCD(y,LCD_Y_MAX - x,Color);
    #endif
    #if (LCD_X_Rev == 0)&&(LCD_Y_Rev == 1)
    Write_Dot_LCD(LCD_X_MAX - y,x,Color);
    #endif
    #if (LCD_X_Rev == 1)&&(LCD_Y_Rev == 1)
    Write_Dot_LCD(LCD_X_MAX - y,LCD_Y_MAX - x,Color);
    #endif
    #endif
    }

绘直线程序代码:

绘直线程序是基于绘点函数的,依据的是直线起始坐标位置算出的斜率,然后从起点开始一个点一个点的绘制。源码如下:

//========================================================================
    // 函数: void Line(unsigned char s_x,unsigned char s_y,unsigned char e_x,unsigned char e_y)
    // 描述: 在s_x、s_y 为起始坐标,e_x、e_y 为结束坐标绘制一条直线
    // 参数: x X 轴坐标 y Y 轴坐标
    // 返回: 无
    // 备注: 使用前景色
    // 版本:
    // 2006/10/15 First version
    //========================================================================
    void Line(unsigned char s_x,unsigned char s_y,unsigned char e_x,unsigned char e_y)
    {
    char Offset_x,Offset_y,Offset_k = 0;
    char Err_d = 1;
    if(s_y>e_y)
    {
    Offset_x = s_x;
    s_x = e_x;
    e_x = Offset_x;
    Offset_x = s_y;
    s_y = e_y;
    e_y = Offset_x;
    }
    Offset_x = e_x-s_x;
    Offset_y = e_y-s_y;
    Write_Dot_LCD/*Writ_Dot*/(s_x,s_y,BMP_Color);
    if(Offset_x<=0)
    {
    Offset_x = s_x-e_x;
    Err_d = -1;
    }
    if(Offset_x>Offset_y)
    {
    Offset_k += Offset_y;
    while(s_x!=e_x)
    {
    if(Offset_k>0)
    {
    s_y+=1;
    Offset_k += (Offset_y-Offset_x);
    }
    else Offset_k += Offset_y;
    s_x+=Err_d;
    if(s_x>Dis_X_MAX||s_y>Dis_Y_MAX) break;
    Write_Dot_LCD/*Writ_Dot*/(s_x,s_y,BMP_Color);
    }
    }
    else
    {
    Offset_k += Offset_x;
    while(s_y!=e_y)
    {
    if(Offset_k>=0)
    {
    s_x+=Err_d;
    Offset_k += (Offset_x-Offset_y);
    }
    else Offset_k += Offset_x;
    s_y+=1;
    if(s_x>=Dis_X_MAX||s_y>=Dis_Y_MAX) break;
    Write_Dot_LCD/*Writ_Dot*/(s_x,s_y,BMP_Color);
    }
    }
    }

在绘直线的子程序里面,首先会对起始点和结束点的坐标值进行判断,以便统一从x 轴由小到大的方向绘直线上的点;然后计算直线的起始点和结束点相对的偏移值,并绘制起始点。

假设直线的相对斜率K=offset_x/offset_y,同在后续的代码当中会判断K 值是否大于1,并据此选择以X 轴还是以Y 轴为基准绘制直线;在绘制直线的过程中,实际上就是从起始点开始一个点一个点的绘制,每个绘制的点的位置都是紧随着上一个绘制的点的位置依据K值的计算而得出的,程序中使用的是简化的算法,如果不太理解的话,建议自行将假设的起始点和结束点的值代入程序中,心算一下。

在上面的代码当中,利用LCD 驱动配置头文件中的定义Dis_X_MAX 和Dis_Y_MAX 限制X 和Y 轴的范围,以防止绘直线过程中直线超出LCD 的显示范围。

矩形绘制程序代码:

矩形绘制程序则是基于绘制直线函数的,代码如下:

//========================================================================
    // 函数: void Rectangle(unsigned char left, unsigned char top, unsigned char right,
    // unsigned char bottom, unsigned char Mode)
    // 描述: 以x,y 为圆心R 为半径画一个圆(mode = 0) or 圆面(mode = 1)
    // 参数: left - 矩形的左上角横坐标,范围0 到127
    // top - 矩形的左上角纵坐标,范围0 到63
    // right - 矩形的右下角横坐标,范围1 到127
    // bottom - 矩形的右下角纵坐标,范围1 到63
    // Mode - 绘制模式,可以是下列数值之一:
    // 0: 矩形框(空心矩形)
    // 1: 矩形面(实心矩形)
    // 返回: 无
    // 备注: 使用前景色
    // 版本:
    // 2007/01/21 First version
    //========================================================================
    void Rectangle(unsigned char left, unsigned char top, unsigned char right, unsigned char bottom,
    unsigned char Mode)
    {
    unsigned char uiTemp;
    if(Mode==0)
    {
    Line(left,top,left,bottom);
    Line(left,top,right,top);
    Line(right,bottom,left+1,bottom);
    Line(right,bottom,right,top+1);
    }
    else
    {
    if(left>right)
    {
    uiTemp = left;
    left = right;
    right = uiTemp;
    }
    if(top>bottom)
    {
    uiTemp = top;
    top = bottom;
    bottom = uiTemp;
    }
    for(uiTemp=top;uiTemp<=bottom;uiTemp++)
    {
    Line(left,uiTemp,right,uiTemp);
    }
    }
    }

绘制矩形的程序较简单,这里不作过多的分析了。

调用该程序时需要传递进来矩形的左上角的坐标值以及右下角的坐标值,还有要绘制的类形:Mode,包括矩形框和矩形块。

绘图设置子程序

在前面介绍的三个绘图功能接口程序当中,实际上有一个全局的变量是很重要的:BMP_Color;它管控着绘图的前景色,其实对于单色的LCD 模块无非就是黑与白的区分,只不过为了扩展通用版的驱动程序应用于彩色LCD 模块上,所以使用了一个变量来表示,它可以通过一个接口函数来进行设置,如下:

//========================================================================
    // 函数: void SetPaintMode(int Mode,unsigned int Color)
    // 描述: 绘图模式设置
    // 参数: Mode 绘图模式 Color 像素点的颜色,相当于前景色
    // 返回: 无
    // 备注: Mode 无效
    // 版本:
    // 2006/10/15 First version
    //========================================================================
    void SetPaintMode(unsigned char Mode,unsigned char Color)
    {
    Mode = Mode;//Plot_Mode = Mode;     //仅仅是为了保持与其它驱动的一至性,绘图模
    //式在该版驱动中未用
    BMP_Color = Color;
    }

在整个LCD 的驱动程序当中,Mode 并没有使用到,仅仅是为了保留功能的扩展。

关于数据类型

从前面的程序可看出,使用的基本上是unsigned char 型数据;其实是为了针对MCS51 的系列MCU 而修改的,仅仅是为了减少RAM 的占用;所以这版的LCD 通用版驱动程序会与其它的稍稍有些不太一样,希望读者理解,反正本书所介绍的并不在于某一个驱动程序,而真正的目的在于介绍一种编程的思想和方法。

3.5.2. 字符显示功能函数

MzL02 模块的通用版LCD 驱动程序提供了ASCII 西文字符显示以及中文字符的显示控制函数接口,在驱动程序当中直接集成了ASCII 西文字符库,而中文字符需要用户在使用时自行利用字模提取工具按需提取。在这一节,将只简单介绍有关字符显示的三个函数,在下一节当中会从字符显示的原理出发分析点阵LCD 的字符显示原理。

驱动程序当中,提供了三个有关字符显示控制的函数,如下:

  • 字体选择设置函数:FontSet;
  • 单个字符显示函数:PutChar;
  • 字符串显示函数:PutString;

以上三个函数当中,字符串显示函数只能实现ASCII 码即西文字符串的显示,对中文是无效的;而PutChar 函数即可用于ASCII 西文字符显示,也可用于用户定义好的中文字符显示,或者是图形显示。

字符的显示实际上还需要有字符的字模库才可以实现,在驱动程序当中配备了两个字库文件,分别是LCD_ASCII(ASCII 码西文字符库字模集)、GB_Table(留给用户进行中文字符字模定义用的)。

有关字符显示的功能函数,将在下一节作出详细的分析。

【点阵液晶编程连载三】点阵LCD 的驱动与显控相关推荐

  1. 【点阵液晶编程连载三/B】点阵LCD 的驱动与显控

    3.6. 字符显示原理 3.6.1. 字符与字模 驱动程序当中,字符库(也就是字模的集全)的数据采用了与一般的单色点阵LCD 的数据组成方式,即字模当中的一个位代表LCD 显示中的一个像素点,取点方式 ...

  2. 【点阵液晶编程连载二】LCD 驱动的基本流程

    2. LCD 驱动的基本流程 介绍基本的流程控制方法,这里重在介绍方法,从时序的模拟或者是总线的连接,到利用LCD 的特性来做一些显示的处理,如单色液晶如何显示一个点,彩色LCD 如何显示一个点的关系 ...

  3. 【点阵液晶编程连载四】MenuGUI 菜单应用

    4. Mz_MenuGUI 菜单应用 4.1. Mz_MenuGUI 在一些带有点阵LCD 显示界面的产品当中,通常会涉及到一些菜单界面的应用,特别是一些带有设置功能的仪器仪表产品:结合自己的设计经验 ...

  4. 【点阵液晶编程连载一】写在前面

    1. 写在前面 1.1. 本书更适合什么样的LCD 模块? 在本书的开始之处,先将本书将要介绍的LCD 圈定一个小的范围,即本书所说的LCD 指的是哪类型的LCD? 在这里将主要针对单色的点阵液晶屏( ...

  5. 【点阵液晶编程连载五】液晶驱动代码的移植

    5. 移植通用版LCD 驱动程序到另一颗MCU 将通用版的LCD 驱动程序移植到另外的MCU 上并不复杂,而需要做的工作也很少,在前面介绍驱动程序代码时已经介绍过了,基本上只需要修改驱动当中与MCU ...

  6. 什么是点阵液晶屏和段码液晶屏

    液晶屏的叫法非常多样,主要分为点阵液晶屏和段码液晶屏,今天我们就来聊聊这两类的区别在哪. 点阵液晶屏是依照一定的标准排序起來的阵列,比较普遍的是图型点阵lcd屏模组.点阵液晶屏是由许多个显示点(等同于 ...

  7. 点阵液晶屏和段码液晶屏有何区别

    液晶屏的叫法非常多样,主要分为点阵液晶屏和段码液晶屏,今天我们就来聊聊这两类的区别在哪. ​ 点阵液晶屏是依照一定的标准排序起來的阵列,比较普遍的是图型点阵lcd屏模组.点阵液晶屏是由许多个显示点(等 ...

  8. VC++动态链接库(DLL)编程(三)――MFC规则DLL

    VC++动态链接库(DLL)编程(三) ――MFC规则DLL 作者:宋宝华  e-mail:21cnbao@21cn.com 第4节我们对非MFC DLL进行了介绍,这一节将详细地讲述MFC规则DLL ...

  9. 51单片机教程:51单片机驱动四个8*8点阵,拼凑16*16点阵显示标准汉字。

    看此篇博文之前建议先看博主的上一篇博文: 51单片机教程:8*8 点阵显示字符.数字.简单汉字 取走点赞哦~ 教你如何用4个8乘8点阵拼成一个16乘16点阵 资料链接:点阵16乘16.rar 一.点阵 ...

最新文章

  1. 干货|全面理解无监督学习基础知识
  2. 9999元雷军新宠亮相!机器狗铁蛋,能走能遛能空翻,小米机器人实验室第一款产品...
  3. jquery ajax IE
  4. qt自定义插件creator不显示_让Qt Creator更懂我们的自定义模块
  5. cura-engine学习(1)
  6. 命中率_数据说话!詹姆斯的“皇家射手团”命中率为近十年最低
  7. leetcode:203. 移除链表元素(两种方法)
  8. 软件架构自学笔记---架构分析
  9. 【Elasticsearch】腾讯Elasticsearch海量规模背后的内核优化剖析
  10. 好友伤害_家暴,对一个孩子的伤害到底有多大?
  11. USACO Section 2.1 Sorting a Three-Valued Sequence 解题报告
  12. 调用未知DLL中的导出函数[转]
  13. ActionScript 中的字符串替换函数
  14. python的装饰器和find函数的使用
  15. HTML前端特效集合
  16. vm虚拟机配置动态ip和静态ip的方法
  17. maven install 报错 error:java找不到符号
  18. 【教程】如何批量将记事本文件或word文本转换成简体/繁体中文,下面教你方法
  19. ValueError: The name None occurs multiple times, use a level number
  20. linux中利用k键杀死进程号,linux下杀死进程的若干方法

热门文章

  1. 联合概率,条件概率,边缘概率的通俗理解
  2. 原创第13篇~while循环
  3. 做一个好女朋友的59条 ,你能做到几条?
  4. ADO.NET 的最佳实践技巧
  5. 百度飞桨 如何撑起了AI产业生态?
  6. 华硕无双新品首爆:H45标压处理器+全球首款2.8K 120Hz OLED屏
  7. 蚂蚁集团2021反诈骗治理报告:“注销校园贷”类诈骗资损降85%
  8. 越来越像QQ?微信支持批量删好友啦!安卓用户“不配”
  9. 胖球为李佳琦做数据?官方回应:系不实信息 已提出了投诉举报
  10. 关店9000家,市值蒸发90%,女装巨头大崩溃的根源找到了