第七十六节:如何把一个任意数值的变量显示在液晶屏上。

开场白:

本来这一节打算开始讲液晶屏的菜单程序,但是我担心跳跃太大,恐怕很多初学者跟不上,所以多插入这一节讲讲后面菜单程序中经常用到的基本功能,如何把一个任意数值的变量显示在液晶屏上。我们需要做一个变量转换成字模的函数,以后只要调用这个转换函数就可以了。这一节就要把这个转换函数教给大家。

具体内容,请看源代码讲解。

  • (1)硬件平台:

    • 基于朱兆祺51单片机学习板。
  • (2)实现功能:我们定义一个char型的全局变量,把它默认初始化为218,开机上电后,能看到正中间恰好显示这个全局变量的数值218。大家也可以试着更改它的默认初始值,只要不超过char型最大数值255范围,我们就会看到它上电后显示的就是这个初始值。

  • (3)源代码讲解如下:

#include "REG52.H"

sbit  LCDCS_dr  = P1 ^ 6; //片选线
sbit  LCDSID_dr = P1 ^ 7; //串行数据线
sbit  LCDCLK_dr = P3 ^ 2; //串行时钟线
sbit  LCDRST_dr = P3 ^ 4; //复位线

void SendByteToLcd(unsigned char ucData);  //发送一个字节数据到液晶模块
void SPIWrite(unsigned char ucWData, unsigned char ucWRS); //模拟SPI发送一个字节的命令或者数据给液晶模块的底层驱动
void WriteCommand(unsigned char ucCommand); //发送一个字节的命令给液晶模块
void LCDWriteData(unsigned char ucData);   //发送一个字节的数据给液晶模块
void LCDInit(void);  //初始化  函数内部包括液晶模块的复位
void display_clear(unsigned char ucFillDate); // 清屏 全部显示空填充0x00   全部显示点阵用0xff
void insert_buffer_to_canvas(unsigned int x, unsigned int y, const unsigned char  *ucArray, unsigned char ucFbFlag, unsigned int x_amount, unsigned int y_amount); //把字模插入画布.
void display_lattice(unsigned int x, unsigned int y, const unsigned char  *ucArray, unsigned char ucFbFlag, unsigned int x_amount, unsigned int y_amount, unsigned int uiOffSetAddr); //显示任意点阵函数
unsigned char *number_to_matrix(unsigned char  ucBitNumber); //把一位数字转换成字模首地址的函数
void delay_short(unsigned int uiDelayshort); //延时
void delay_long(unsigned int uiDelayLong);

void initial_myself();
void initial_peripheral();

void lcd_display_service(void); //应用层面的液晶屏显示程序
void clear_all_canvas(void);  //把画布全部清零

code unsigned char Zf816_0[] =
{
    /*--  文字:  0  --*/
    /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
    0x00, 0x00, 0x00, 0x18, 0x24, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x24, 0x18, 0x00, 0x00,
};

code unsigned char Zf816_1[] =
{
    /*--  文字:  1  --*/
    /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
    0x00, 0x00, 0x00, 0x10, 0x70, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x7C, 0x00, 0x00,
};

code unsigned char Zf816_2[] =
{
    /*--  文字:  2  --*/
    /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
    0x00, 0x00, 0x00, 0x3C, 0x42, 0x42, 0x42, 0x04, 0x04, 0x08, 0x10, 0x20, 0x42, 0x7E, 0x00, 0x00,
};

code unsigned char Zf816_3[] =
{
    /*--  文字:  3  --*/
    /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
    0x00, 0x00, 0x00, 0x3C, 0x42, 0x42, 0x04, 0x18, 0x04, 0x02, 0x02, 0x42, 0x44, 0x38, 0x00, 0x00,
};

code unsigned char Zf816_4[] =
{
    /*--  文字:  4  --*/
    /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
    0x00, 0x00, 0x00, 0x04, 0x0C, 0x14, 0x24, 0x24, 0x44, 0x44, 0x7E, 0x04, 0x04, 0x1E, 0x00, 0x00,
};

code unsigned char Zf816_5[] =
{
    /*--  文字:  5  --*/
    /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
    0x00, 0x00, 0x00, 0x7E, 0x40, 0x40, 0x40, 0x58, 0x64, 0x02, 0x02, 0x42, 0x44, 0x38, 0x00, 0x00,
};

code unsigned char Zf816_6[] =
{
    /*--  文字:  6  --*/
    /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
    0x00, 0x00, 0x00, 0x1C, 0x24, 0x40, 0x40, 0x58, 0x64, 0x42, 0x42, 0x42, 0x24, 0x18, 0x00, 0x00,
};


code unsigned char Zf816_7[] =
{
    /*--  文字:  7  --*/
    /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
    0x00, 0x00, 0x00, 0x7E, 0x44, 0x44, 0x08, 0x08, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00,
};

code unsigned char Zf816_8[] =
{
    /*--  文字:  8  --*/
    /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
    0x00, 0x00, 0x00, 0x3C, 0x42, 0x42, 0x42, 0x24, 0x18, 0x24, 0x42, 0x42, 0x42, 0x3C, 0x00, 0x00,
};

code unsigned char Zf816_9[] =
{
    /*--  文字:  9  --*/
    /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
    0x00, 0x00, 0x00, 0x18, 0x24, 0x42, 0x42, 0x42, 0x26, 0x1A, 0x02, 0x02, 0x24, 0x38, 0x00, 0x00,
};


code unsigned char Zf816_nc[] = //空字模
{
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};

/* 注释一:
* 为了实现跨区域无缝显示,就先在某个区域显示一块画布,我们只要在这块画布数组中插入字模数组,
* 就可以达到跨区域无缝显示的目的。根据上几节的介绍,12864液晶屏由上下两半屏组成,以下这块画布
* 显示在上半屏和下半屏之间。横向4个字节,纵向16行。其中上半屏显示8行,下半屏显示8行。注意,这个数组
* 不带code关键字,是全局变量,这样可读可写。画布的横向x坐标范围是0至3,因为画布的横向只要4个字节。
* 画布的纵向y坐标范围是0至15,因为画布的纵向只有16行。
*/
unsigned char ucCanvasBuffer[] = //画布显示数组。注意,这里没有code关键字,是全局变量。初始化全部填充0x00
{
    0x00, 0x00, 0x00, 0x00, //上半屏
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00,

    //------------上半屏和下半屏的分割线-----------

    0x00, 0x00, 0x00, 0x00, //下半屏
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00,
};

unsigned char ucDisplayUpdate = 1; //更新显示变量

/* 注释二:
* 以下变量就是本程序的任意变量,网友可以自己更改它的大小来测试本程序,不要超过255.
*/
unsigned char ucAnyNumber = 218; //任意变量默认初始化为218。

void main()
{
    initial_myself();      //第一区,上电后马上初始化
    delay_long(100);       //一线,延时线。延时一段时间
    initial_peripheral();  //第二区,上电后延时一段时间再初始化

    while(1)   //第三区
    {
        lcd_display_service(); //应用层面的液晶屏显示程序
    }
}

void initial_myself()  //第一区 上电后马上初始化
{
    ;
}
void initial_peripheral() //第二区 上电后延时一段时间再初始化
{
    LCDInit(); //初始化12864 内部包含液晶模块的复位
    display_clear(0xff); // 清屏 全部显示空填充0x00   全部显示点阵用0xff
}

/* 注释三:
* 本程序的核心转换函数。
* 是可以把一位任意数字变量的函数转换成对应的字模,由于字模是数组,所以返回的是指针,代表字模数组的首地址。
*/
unsigned char *number_to_matrix(unsigned char  ucBitNumber)
{
    unsigned char *p_ucAnyNumber;  //此指针根据ucBitNumber数值的大小,分别调用不同的字库。

    switch(ucBitNumber)  //根据ucBitNumber数值的大小,分别调用不同的字库。
    {
    case 0:
        p_ucAnyNumber = Zf816_0;
        break;
    case 1:
        p_ucAnyNumber = Zf816_1;
        break;
    case 2:
        p_ucAnyNumber = Zf816_2;
        break;
    case 3:
        p_ucAnyNumber = Zf816_3;
        break;
    case 4:
        p_ucAnyNumber = Zf816_4;
        break;
    case 5:
        p_ucAnyNumber = Zf816_5;
        break;
    case 6:
        p_ucAnyNumber = Zf816_6;
        break;
    case 7:
        p_ucAnyNumber = Zf816_7;
        break;
    case 8:
        p_ucAnyNumber = Zf816_8;
        break;
    case 9:
        p_ucAnyNumber = Zf816_9;
        break;
    case 10:
        p_ucAnyNumber = Zf816_nc;
        break;
    default:   //如果上面的条件都不符合,那么默认指向空字模
        p_ucAnyNumber = Zf816_nc;
        break;
    }

    return p_ucAnyNumber;  //返回转换结束后的指针
}

void lcd_display_service(void) //应用层面的液晶屏显示程序
{
    static unsigned char ucAnyNumber_1; //分解变量的个位
    static unsigned char ucAnyNumber_10; //分解变量的十位
    static unsigned char ucAnyNumber_100; //分解变量的百位

    static unsigned char *p_ucAnyNumber_1; //经过数字转换成字模后,分解变量的个位字模首地址
    static unsigned char *p_ucAnyNumber_10; //经过数字转换成字模后,分解变量的十位字模首地址
    static unsigned char *p_ucAnyNumber_100; //经过数字转换成字模后,分解变量的百位字模首地址

    if(ucDisplayUpdate == 1) //需要更新显示
    {
        ucDisplayUpdate = 0; //及时把标志清零,避免一直处于不断更新的状态。

        if(ucAnyNumber >= 100) //有3位数以上
        {
            ucAnyNumber_100 = ucAnyNumber / 100; //百位
        }
        else //否则显示空
        {
            ucAnyNumber_100 = 10; //在下面的转换函数中,代码10表示空字模
        }

        if(ucAnyNumber >= 10) //有2位数以上
        {
            ucAnyNumber_10 = ucAnyNumber % 100 / 10; //十位
        }
        else //否则显示空
        {
            ucAnyNumber_10 = 10; //在下面的转换函数中,代码10表示空字模
        }

        ucAnyNumber_1 = ucAnyNumber % 10 / 1; //个位

        p_ucAnyNumber_100 = number_to_matrix(ucAnyNumber_100); //把数字转换成字模首地址
        p_ucAnyNumber_10 = number_to_matrix(ucAnyNumber_10); //把数字转换成字模首地址
        p_ucAnyNumber_1 = number_to_matrix(ucAnyNumber_1); //把数字转换成字模首地址

        clear_all_canvas();  //把画布全部清零
        insert_buffer_to_canvas(0, 0, p_ucAnyNumber_100, 0, 1, 16); //把百位的字模插入画布
        insert_buffer_to_canvas(1, 0, p_ucAnyNumber_10, 0, 1, 16); //把十的字模插入画布
        insert_buffer_to_canvas(2, 0, p_ucAnyNumber_1, 0, 1, 16); //把个的字模插入画布

        display_lattice(3, 24, ucCanvasBuffer, 0, 4, 8, 0); //显示上半屏的画布,最后的参数0是偏移量
        display_lattice(11, 0, ucCanvasBuffer, 0, 4, 8, 32); //显示下半屏的画布,最后的参数32是偏移量
    }
}

void clear_all_canvas(void)  //把画布全部清零
{
    unsigned int j = 0;
    unsigned int i = 0;

    for(j = 0; j < 16; j++) //这里的16表示画布有16行
    {
        for(i = 0; i < 4; i++) //这里的4表示画布每行有4个字节
        {
            ucCanvasBuffer[j * 4 + i] = 0x00;
        }
    }

}

void display_clear(unsigned char ucFillDate) // 清屏  全部显示空填充0x00   全部显示点阵用0xff
{

    unsigned char x, y;
    WriteCommand(0x34);  //关显示缓冲指令
    WriteCommand(0x34);  //关显示缓冲指令  故意写2次,怕1次关不了 这个是因为我参考到某厂家的驱动程序也是这样写的
    y = 0;
    while(y < 32) //y轴的范围0至31
    {
        WriteCommand(y + 0x80);      //垂直地址
        WriteCommand(0x80);          //水平地址
        for(x = 0; x < 32; x++) //256个横向点,有32个字节
        {
            LCDWriteData(ucFillDate);
        }
        y++;
    }
    WriteCommand(0x36); //开显示缓冲指令
}

/* 注释四:
* 把字模插入画布的函数.
* 这是本节的核心函数,读者尤其要搞懂x_amount和y_amount对应的显示关系。
* 第1,2个参数x,y是在画布中的坐标体系。
* x的范围是0至3,因为画布的横向只要4个字节。y的范围是0至15,因为画布的纵向只有16行。
* 第3个参数*ucArray是字模的数组。
* 第4个参数ucFbFlag是反白显示标志。0代表正常显示,1代表反白显示。
* 第5,6个参数x_amount,y_amount分别代表字模数组的横向有多少个字节,纵向有几横。
*/
void insert_buffer_to_canvas(unsigned int x, unsigned int y, const unsigned char  *ucArray, unsigned char ucFbFlag, unsigned int x_amount, unsigned int y_amount)
{
    unsigned int j = 0;
    unsigned int i = 0;
    unsigned char ucTemp;
    for(j = 0; j < y_amount; j++)
    {
        for(i = 0; i < x_amount; i++)
        {
            ucTemp = ucArray[j * x_amount + i];
            if(ucFbFlag == 0)
            {
                ucCanvasBuffer[(y + j) * 4 + x + i] = ucTemp; //这里的4代表画布每一行只有4个字节
            }
            else
            {
                ucCanvasBuffer[(y + j) * 4 + x + i] = ~ucTemp; //这里的4代表画布每一行只有4个字节
            }
        }
    }

}

/* 注释五:
* 显示任意点阵函数.
* 注意,本函数在前几节的基础上多增加了第7个参数uiOffSetAddr,它是偏移地址。
* 对于这个函数,读者尤其要搞懂x_amount和y_amount对应的显示关系。
* 第1,2个参数x,y是坐标体系。x的范围是0至15,y的范围是0至31.
* 第3个参数*ucArray是字模的数组。
* 第4个参数ucFbFlag是反白显示标志。0代表正常显示,1代表反白显示。
* 第5,6个参数x_amount,y_amount分别代表字模数组的横向有多少个字节,纵向有几横。
* 第7个参数uiOffSetAddr是偏移地址,代表字模数组的从第几个数据开始显示。
*/
void display_lattice(unsigned int x, unsigned int y, const unsigned char  *ucArray, unsigned char ucFbFlag, unsigned int x_amount, unsigned int y_amount, unsigned int uiOffSetAddr)
{
    unsigned int j = 0;
    unsigned int i = 0;
    unsigned char ucTemp;

    //注意,要把以下两行指令屏蔽,否则屏幕在更新显示时会整屏闪动
    //  WriteCommand(0x34);  //关显示缓冲指令
    //  WriteCommand(0x34);  //关显示缓冲指令  故意写2次,怕1次关不了 这个是因为我参考到某厂家的驱动程序也是这样写的
    for(j = 0; j < y_amount; j++) //y_amount代表y轴有多少横
    {
        WriteCommand(y + j + 0x80);    //垂直地址
        WriteCommand(x + 0x80);        //水平地址
        for(i = 0; i < x_amount; i++) //x_amount代表x轴有多少列
        {
            ucTemp = ucArray[j * x_amount + i + uiOffSetAddr]; //uiOffSetAddr是字模数组的偏移地址
            if(ucFbFlag == 1) //反白显示
            {
                ucTemp = ~ucTemp;
            }
            LCDWriteData(ucTemp);
            //         delay_short(30000);  //把上一节这个延时函数去掉,加快刷屏速度
        }
    }
    WriteCommand(0x36); //开显示缓冲指令
}

void SendByteToLcd(unsigned char ucData)  //发送一个字节数据到液晶模块
{
    unsigned char i;
    for ( i = 0; i < 8; i++ )
    {
        if ( (ucData << i) & 0x80 )
        {
            LCDSID_dr = 1;
        }
        else
        {
            LCDSID_dr = 0;
        }
        LCDCLK_dr = 0;
        LCDCLK_dr = 1;
    }
}

void SPIWrite(unsigned char ucWData, unsigned char ucWRS) //模拟SPI发送一个字节的命令或者数据给液晶模块的底层驱动
{
    SendByteToLcd( 0xf8 + (ucWRS << 1) );
    SendByteToLcd( ucWData & 0xf0 );
    SendByteToLcd( (ucWData << 4) & 0xf0);
}


void WriteCommand(unsigned char ucCommand) //发送一个字节的命令给液晶模块
{

    LCDCS_dr = 0;
    LCDCS_dr = 1;
    SPIWrite(ucCommand, 0);
    delay_short(90);
}

void LCDWriteData(unsigned char ucData)  //发送一个字节的数据给液晶模块
{
    LCDCS_dr = 0;
    LCDCS_dr = 1;
    SPIWrite(ucData, 1);
}

void LCDInit(void) //初始化  函数内部包括液晶模块的复位
{
    LCDRST_dr = 1;  //复位
    LCDRST_dr = 0;
    LCDRST_dr = 1;
}

void delay_short(unsigned int uiDelayShort) //延时函数
{
    unsigned int i;
    for(i = 0; i < uiDelayShort; i++)
    {
        ;
    }
}

void delay_long(unsigned int uiDelayLong)
{
    unsigned int i;
    unsigned int j;
    for(i = 0; i < uiDelayLong; i++)
    {
        for(j = 0; j < 500; j++) //内嵌循环的空指令数量
        {
            ; //一个分号相当于执行一条空语句
        }
    }
}

总结陈词:

有了这一节的基础,我们继续循序渐进,下一节将会讲到液晶屏的菜单程序。欲知详情,请听下回分解-----在1个窗口里通过移动光标来设置不同参数的液晶屏菜单程序。

(未完待续,下节更精彩,不要走开哦)

results matching ""

    No results matching ""