3 Star 15 Fork 4

建伟F4nniu / FN1895E-MCU101

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
MCU079.md 58.77 KB
一键复制 编辑 原始数据 按行查看 历史
建伟F4nniu 提交于 2016-12-24 10:44 . 整理完 70-90节。

#第七十九节:通过主菜单移动光标来进入子菜单窗口的液晶屏程序。

开场白:

其实主菜单窗口与子菜单窗口本质都是多窗口菜单程序,只不过我在按键服务程序里面建立起来了一条主窗口与子窗口的关系链。这个关系链还是用switch语句搭建起来的,在某个窗口某个局部显示上,操作某个按键就会切换到不同的窗口显示。

继续巩固上一节教给大家的两个知识点:

  • 第一个知识点:我在前面讲数码管显示的时候就提出了一个 “一二级菜单显示理论”:凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。局部就是二级菜单,用ucWdxPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。把每一个窗口的内容分为两种类型,一种类型是那些不用经常刷新显示的内容,只有在切换窗口的时候才需要更新的,这种内容放在整屏更新显示的括号里,比如清屏操作等内容。另外一种是那些经常需要刷新显示的内容,这种内容放在局部更新显示的括号里。

  • 第二个知识点:按键如何跟液晶屏显示有机的结合起来?只要遵循鸿哥总结出来的一个规律“在不同的窗口下,根据不同的局部变量来操作不同的参数”,这样再复杂的人机交互程序都会显得很简单清晰。

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

  • (1)硬件平台:基于朱兆祺51单片机学习板。加按键对应S1键,减按键对应S5键,切换“光标”移动按键对应S9键,设置参数按键对应S13键。

  • (2)实现功能:

  • 通过按键设置6个不同的参数。
  • 有4个窗口。第1个窗口是主菜单界面,通过光标切换可以进去设置不同参数的子菜单界面。第2个窗口是设置时间范围界面。第3个窗口是设置速度范围界面。第4个窗口是设置频率范围界面。每个设置界面显示2个参数。每个参数的范围是从0到99。
  • 有4个按键:
  • (a) 一个是进入和退出S13按键,按一次进入选中的子菜单。再按一次退出子菜单。
  • (b) 一个是移动光标S9按键,依次按下此按键,液晶屏上的光标会从上往下移动,表示选中不同的参数。当移动到每个窗口最下边那一行时,再按下此按键会把光标移动到第一个参数。
  • (c) 一个是减数S5按键,在设置参数模式下,依次按下此按键,被选中的参数会逐渐减小。
  • (d) 一个是加数S1按键,在设置参数模式下,依次按下此按键,被选中的参数会逐渐加大。
  • (3)源代码讲解如下:
#include "REG52.H"

/* 注释一:
* 本程序用到的变量比较多,所以在keil编译模式里要设置一下编译模式memory model,
* 否则编译会出错.右键单击Target选择“Options for Target'Target1'”就会出来一个框
* 在memory model中选择compact:variables in pdata 就可以了。
*/

#define const_voice_short  40   //蜂鸣器短叫的持续时间

#define const_key_time1  20    //按键去抖动延时的时间
#define const_key_time2  20    //按键去抖动延时的时间
#define const_key_time3  20    //按键去抖动延时的时间
#define const_key_time4  20    //按键去抖动延时的时间


sbit key_sr1 = P0 ^ 0; //对应朱兆祺学习板的S1键
sbit key_sr2 = P0 ^ 1; //对应朱兆祺学习板的S5键
sbit key_sr3 = P0 ^ 2; //对应朱兆祺学习板的S9键
sbit key_sr4 = P0 ^ 3; //对应朱兆祺学习板的S13键

sbit key_gnd_dr = P0 ^ 4; //模拟独立按键的地GND,因此必须一直输出低电平

sbit beep_dr = P2 ^ 7; //蜂鸣器的驱动IO口

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 T0_time(); //定时中断函数
void key_service(void); //按键服务的应用程序
void key_scan(void);//按键扫描函数 放在定时中断里

void initial_myself();
void initial_peripheral();


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

void wd1(void);//窗口1  主菜单
void wd2(void);//窗口2  设置时间
void wd3(void);//窗口3  设置速度
void wd4(void);//窗口4  设置频率

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,
};

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


code unsigned char Hz1616_zhu[] =
{
    /*--  文字:  主  --*/
    /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
    0x02, 0x00, 0x01, 0x80, 0x01, 0x00, 0x00, 0x08, 0x3F, 0xFC, 0x01, 0x00, 0x01, 0x00, 0x01, 0x08,
    0x3F, 0xFC, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x04, 0x7F, 0xFE, 0x00, 0x00, 0x00, 0x00,
};

code unsigned char Hz1616_cai[] =
{
    /*--  文字:  菜  --*/
    /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
    0x04, 0x40, 0xFF, 0xFE, 0x04, 0x40, 0x04, 0x40, 0x3F, 0xF8, 0x22, 0x08, 0x11, 0x10, 0x08, 0x20,
    0x01, 0x00, 0x7F, 0xFE, 0x03, 0x80, 0x05, 0x40, 0x09, 0x30, 0x11, 0x1C, 0x61, 0x08, 0x01, 0x00,
};

code unsigned char Hz1616_dan[] =
{
    /*--  文字:  单  --*/
    /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
    0x08, 0x20, 0x06, 0x30, 0x04, 0x40, 0x3F, 0xF8, 0x21, 0x08, 0x3F, 0xF8, 0x21, 0x08, 0x21, 0x08,
    0x3F, 0xF8, 0x21, 0x08, 0x01, 0x00, 0xFF, 0xFE, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
};

code unsigned char Hz1616_she[] =
{
    /*--  文字:  设  --*/
    /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
    0x40, 0x00, 0x21, 0xF0, 0x31, 0x10, 0x21, 0x10, 0x01, 0x10, 0x01, 0x10, 0xE2, 0x0E, 0x25, 0xF8,
    0x21, 0x08, 0x21, 0x08, 0x20, 0x90, 0x20, 0x90, 0x28, 0x60, 0x30, 0x90, 0x23, 0x0E, 0x0C, 0x04,
};

code unsigned char Hz1616_zhi[] =
{
    /*--  文字:  置  --*/
    /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
    0x3F, 0xF8, 0x24, 0x48, 0x24, 0x48, 0x3F, 0xF8, 0x01, 0x00, 0x7F, 0xFC, 0x02, 0x00, 0x1F, 0xF0,
    0x10, 0x10, 0x1F, 0xF0, 0x10, 0x10, 0x1F, 0xF0, 0x10, 0x10, 0x1F, 0xF0, 0x10, 0x10, 0xFF, 0xFE,
};

code unsigned char Hz1616_su[] =
{
    /*--  文字:  速  --*/
    /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
    0x00, 0x80, 0x40, 0x80, 0x2F, 0xFC, 0x20, 0x80, 0x00, 0x80, 0x07, 0xF8, 0xE4, 0x88, 0x24, 0x88,
    0x27, 0xF8, 0x21, 0xA0, 0x22, 0x98, 0x2C, 0x88, 0x20, 0x80, 0x50, 0x80, 0x8F, 0xFE, 0x00, 0x00,
};

code unsigned char Hz1616_du[] =
{
    /*--  文字:  度  --*/
    /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
    0x01, 0x00, 0x00, 0x80, 0x3F, 0xFE, 0x22, 0x20, 0x22, 0x20, 0x2F, 0xFC, 0x22, 0x20, 0x23, 0xE0,
    0x20, 0x00, 0x27, 0xF8, 0x22, 0x10, 0x21, 0x20, 0x20, 0xC0, 0x41, 0x30, 0x46, 0x0E, 0x98, 0x04,
};

code unsigned char Hz1616_shi[] =
{
    /*--  文字:  时  --*/
    /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
    0x00, 0x10, 0x00, 0x10, 0x7C, 0x10, 0x44, 0x10, 0x47, 0xFE, 0x44, 0x10, 0x7C, 0x10, 0x45, 0x10,
    0x44, 0x90, 0x44, 0x90, 0x7C, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x50, 0x00, 0x20,
};

code unsigned char Hz1616_jian[] =
{
    /*--  文字:  间  --*/
    /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
    0x20, 0x00, 0x13, 0xFC, 0x10, 0x04, 0x40, 0x04, 0x47, 0xE4, 0x44, 0x24, 0x44, 0x24, 0x47, 0xE4,
    0x44, 0x24, 0x44, 0x24, 0x47, 0xE4, 0x40, 0x04, 0x40, 0x04, 0x40, 0x04, 0x40, 0x14, 0x40, 0x08,
};

code unsigned char Hz1616_pin[] =
{
    /*--  文字:  频  --*/
    /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
    0x08, 0x00, 0x08, 0xFE, 0x4E, 0x20, 0x48, 0x40, 0x48, 0xFC, 0xFE, 0x84, 0x00, 0xA4, 0x08, 0xA4,
    0x4A, 0xA4, 0x4A, 0xA4, 0x84, 0xA4, 0x08, 0x50, 0x10, 0x48, 0x20, 0x86, 0xC3, 0x02, 0x00, 0x00,
};

code unsigned char Hz1616_lv[] =
{
    /*--  文字:  率  --*/
    /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
    0x02, 0x00, 0x01, 0x00, 0x7F, 0xFE, 0x41, 0x00, 0x22, 0x28, 0x17, 0xD0, 0x04, 0x80, 0x11, 0x10,
    0x22, 0x48, 0x47, 0xC4, 0x01, 0x20, 0xFF, 0xFE, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,
};

code unsigned char Hz1616_fan[] =
{
    /*--  文字:  范  --*/
    /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
    0x04, 0x20, 0x04, 0x20, 0xFF, 0xFE, 0x04, 0x60, 0x40, 0x00, 0x31, 0xF8, 0x91, 0x08, 0x61, 0x08,
    0x49, 0x08, 0x09, 0x38, 0x11, 0x10, 0xE1, 0x00, 0x21, 0x04, 0x21, 0x04, 0x20, 0xFC, 0x20, 0x00,
};

code unsigned char Hz1616_wei[] =
{
    /*--  文字:  围  --*/
    /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
    0x7F, 0xFC, 0x42, 0x04, 0x42, 0x04, 0x5F, 0xF4, 0x42, 0x04, 0x4F, 0xE4, 0x42, 0x04, 0x5F, 0xE4,
    0x42, 0x24, 0x42, 0x24, 0x42, 0x24, 0x42, 0xA4, 0x42, 0x44, 0x40, 0x04, 0x7F, 0xFC, 0x40, 0x04,
};

code unsigned char Hz1616_shang[] =
{
    /*--  文字:  上  --*/
    /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
    0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0xF8, 0x01, 0x00,
    0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x04, 0x7F, 0xFE, 0x00, 0x00,
};

code unsigned char Hz1616_xia[] =
{
    /*--  文字:  下  --*/
    /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
    0x00, 0x04, 0x7F, 0xFE, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0xC0, 0x01, 0x60, 0x01, 0x30,
    0x01, 0x20, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00,
};

code unsigned char Hz1616_xian[] =
{
    /*--  文字:  限  --*/
    /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
    0x00, 0x00, 0xFB, 0xF8, 0x92, 0x08, 0x93, 0xF8, 0xA2, 0x08, 0xA2, 0x08, 0x93, 0xF8, 0x8A, 0x80,
    0x8A, 0x48, 0xAA, 0x50, 0x92, 0x20, 0x82, 0x20, 0x82, 0x10, 0x82, 0x8E, 0x83, 0x04, 0x82, 0x00,
};


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 ucKeySec = 0; //被触发的按键编号
unsigned int  uiVoiceCnt = 0; //蜂鸣器鸣叫的持续时间计数器

unsigned char ucWd = 1; //窗口变量

unsigned char ucWd1Part = 1; //窗口1的局部变量,代表选中某一行。
unsigned char ucWd1Update = 1; //窗口1的整屏更新显示变量      1代表更新显示,响应函数内部会清零
unsigned char ucWd1Part1Update = 0; //窗口1的第1个局部更新显示变量  1代表更新显示,响应函数内部会清零
unsigned char ucWd1Part2Update = 0; //窗口1的第2个局部更新显示变量  1代表更新显示,响应函数内部会清零
unsigned char ucWd1Part3Update = 0; //窗口1的第3个局部更新显示变量  1代表更新显示,响应函数内部会清零

unsigned char ucWd2Part = 1; //窗口2的局部变量,代表选中某一行。
unsigned char ucWd2Update = 0; //窗口2的整屏更新显示变量      1代表更新显示,响应函数内部会清零
unsigned char ucWd2Part1Update = 0; //窗口2的第1个局部更新显示变量  1代表更新显示,响应函数内部会清零
unsigned char ucWd2Part2Update = 0; //窗口2的第2个局部更新显示变量  1代表更新显示,响应函数内部会清零

unsigned char ucWd3Part = 1; //窗口3的局部变量,代表选中某一行。
unsigned char ucWd3Update = 0; //窗口3的整屏更新显示变量      1代表更新显示,响应函数内部会清零
unsigned char ucWd3Part1Update = 0; //窗口3的第1个局部更新显示变量  1代表更新显示,响应函数内部会清零
unsigned char ucWd3Part2Update = 0; //窗口3的第2个局部更新显示变量  1代表更新显示,响应函数内部会清零

unsigned char ucWd4Part = 1; //窗口4的局部变量,代表选中某一行。
unsigned char ucWd4Update = 0; //窗口4的整屏更新显示变量      1代表更新显示,响应函数内部会清零
unsigned char ucWd4Part1Update = 0; //窗口4的第1个局部更新显示变量  1代表更新显示,响应函数内部会清零
unsigned char ucWd4Part2Update = 0; //窗口4的第2个局部更新显示变量  1代表更新显示,响应函数内部会清零


unsigned char ucTimeH = 2; //设置时间的上限数据
unsigned char ucTimeL = 1; //设置时间的下限数据

unsigned char ucSpeedH = 4; //设置速度的上限数据
unsigned char ucSpeedL = 3; //设置速度的下限数据

unsigned char ucFreqH = 6; //设置频率的上限数据
unsigned char ucFreqL = 5; //设置频率的下限数据

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

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

}


void initial_myself()  //第一区 上电后马上初始化
{
    /* 注释二:
    * 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
    * 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
    * 朱兆祺51学习板的S1和S5两个按键就是本程序中用到的两个独立按键。
    */
    key_gnd_dr = 0; //模拟独立按键的地GND,因此必须一直输出低电平
    beep_dr = 1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

    TMOD = 0x01; //设置定时器0为工作方式1

    TH0 = 0xf8; //重装初始值(65535-2000)=63535=0xf82f
    TL0 = 0x2f;
}
void initial_peripheral() //第二区 上电后延时一段时间再初始化
{
    LCDInit(); //初始化12864 内部包含液晶模块的复位


    EA = 1;   //开总中断
    ET0 = 1;  //允许定时中断
    TR0 = 1;  //启动定时中断

}


void T0_time() interrupt 1
{
    TF0 = 0; //清除中断标志
    TR0 = 0; //关中断

    key_scan(); //按键扫描函数

    if(uiVoiceCnt != 0)
    {
        uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
        beep_dr = 0; //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
    }
    else
    {
        ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
        beep_dr = 1; //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
    }


    TH0 = 0xf8; //重装初始值(65535-2000)=63535=0xf82f
    TL0 = 0x2f;
    TR0 = 1; //开中断
}



void key_scan(void)//按键扫描函数 放在定时中断里
{


    static unsigned int  uiKeyTimeCnt1 = 0; //按键去抖动延时计数器
    static unsigned char ucKeyLock1 = 0; //按键触发后自锁的变量标志

    static unsigned int  uiKeyTimeCnt2 = 0; //按键去抖动延时计数器
    static unsigned char ucKeyLock2 = 0; //按键触发后自锁的变量标志

    static unsigned int  uiKeyTimeCnt3 = 0; //按键去抖动延时计数器
    static unsigned char ucKeyLock3 = 0; //按键触发后自锁的变量标志

    static unsigned int  uiKeyTimeCnt4 = 0; //按键去抖动延时计数器
    static unsigned char ucKeyLock4 = 0; //按键触发后自锁的变量标志

    if(key_sr1 == 1) //IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
    {
        ucKeyLock1 = 0; //按键自锁标志清零
        uiKeyTimeCnt1 = 0; //按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
    }
    else if(ucKeyLock1 == 0) //有按键按下,且是第一次被按下
    {
        uiKeyTimeCnt1++; //累加定时中断次数
        if(uiKeyTimeCnt1 > const_key_time1)
        {
            uiKeyTimeCnt1 = 0;
            ucKeyLock1 = 1; //自锁按键置位,避免一直触发
            ucKeySec = 1;  //触发1号键
        }
    }

    if(key_sr2 == 1) //IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
    {
        ucKeyLock2 = 0; //按键自锁标志清零
        uiKeyTimeCnt2 = 0; //按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
    }
    else if(ucKeyLock2 == 0) //有按键按下,且是第一次被按下
    {
        uiKeyTimeCnt2++; //累加定时中断次数
        if(uiKeyTimeCnt2 > const_key_time2)
        {
            uiKeyTimeCnt2 = 0;
            ucKeyLock2 = 1; //自锁按键置位,避免一直触发
            ucKeySec = 2;  //触发2号键
        }
    }

    if(key_sr3 == 1) //IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
    {
        ucKeyLock3 = 0; //按键自锁标志清零
        uiKeyTimeCnt3 = 0; //按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
    }
    else if(ucKeyLock3 == 0) //有按键按下,且是第一次被按下
    {
        uiKeyTimeCnt3++; //累加定时中断次数
        if(uiKeyTimeCnt3 > const_key_time3)
        {
            uiKeyTimeCnt3 = 0;
            ucKeyLock3 = 1; //自锁按键置位,避免一直触发
            ucKeySec = 3;  //触发3号键
        }
    }

    if(key_sr4 == 1) //IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
    {
        ucKeyLock4 = 0; //按键自锁标志清零
        uiKeyTimeCnt4 = 0; //按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
    }
    else if(ucKeyLock4 == 0) //有按键按下,且是第一次被按下
    {
        uiKeyTimeCnt4++; //累加定时中断次数
        if(uiKeyTimeCnt4 > const_key_time4)
        {
            uiKeyTimeCnt4 = 0;
            ucKeyLock4 = 1; //自锁按键置位,避免一直触发
            ucKeySec = 4;  //触发4号键
        }
    }

}


void key_service(void) //按键服务的应用程序
{
    switch(ucKeySec) //按键服务状态切换
    {
    case 1:// 加按键 对应朱兆祺学习板的S1键
        switch(ucWd)  //在不同的窗口下,设置不同的参数
        {
        case 2:  //窗口2  设置时间
            switch(ucWd2Part)  //在窗口2下,根据不同的局部变量来设置不同的参数
            {

            case 1:   //设置时间上限
                ucTimeH++;
                if(ucTimeH > 99)
                {
                    ucTimeH = 99;
                }
                ucWd2Part1Update = 1; //1代表更新显示,响应函数内部会清零
                break;
            case 2:   //设置时间下限
                ucTimeL++;
                if(ucTimeL > 99)
                {
                    ucTimeL = 99;
                }
                ucWd2Part2Update = 1; //1代表更新显示,响应函数内部会清零
                break;

            }
            break;
        case 3:  //窗口3  设置速度
            switch(ucWd3Part)  //在窗口3下,根据不同的局部变量来设置不同的参数
            {

            case 1:   //设置速度上限
                ucSpeedH++;
                if(ucSpeedH > 99)
                {
                    ucSpeedH = 99;
                }
                ucWd3Part1Update = 1; //1代表更新显示,响应函数内部会清零
                break;
            case 2:   //设置速度下限
                ucSpeedL++;
                if(ucSpeedL > 99)
                {
                    ucSpeedL = 99;
                }
                ucWd3Part2Update = 1; //1代表更新显示,响应函数内部会清零
                break;

            }
            break;
        case 4:  //窗口4  设置速度
            switch(ucWd4Part)  //在窗口4下,根据不同的局部变量来设置不同的参数
            {

            case 1:   //设置频率上限
                ucFreqH++;
                if(ucFreqH > 99)
                {
                    ucFreqH = 99;
                }
                ucWd4Part1Update = 1; //1代表更新显示,响应函数内部会清零
                break;
            case 2:   //设置频率下限
                ucFreqL++;
                if(ucFreqL > 99)
                {
                    ucFreqL = 99;
                }
                ucWd4Part2Update = 1; //1代表更新显示,响应函数内部会清零
                break;

            }
            break;
        }
        uiVoiceCnt = const_voice_short; //按键声音触发,滴一声就停。
        ucKeySec = 0; //响应按键服务处理程序后,按键编号清零,避免一致触发
        break;

    case 2:// 减按键 对应朱兆祺学习板的S5键
        switch(ucWd)  //在不同的窗口下,设置不同的参数
        {
        case 2:  //窗口2 设置时间
            switch(ucWd2Part)  //在窗口2下,根据不同的局部变量来设置不同的参数
            {
            case 1:   //设置时间上限
                ucTimeH--;
                if(ucTimeH > 99) //一直减到最后,单片机C语言编译器有一个特征,0减去1会溢出变成255(0xff)
                {
                    ucTimeH = 0;
                }
                ucWd2Part1Update = 1; //1代表更新显示,响应函数内部会清零
                break;
            case 2:   //设置时间下限
                ucTimeL--;
                if(ucTimeL > 99) //一直减到最后,单片机C语言编译器有一个特征,0减去1会溢出变成255(0xff)
                {
                    ucTimeL = 0;
                }
                ucWd2Part2Update = 1; //1代表更新显示,响应函数内部会清零
                break;
            }
            break;
        case 3:  //窗口3  设置速度
            switch(ucWd3Part)  //在窗口3下,根据不同的局部变量来设置不同的参数
            {
            case 1:   //设置速度上限
                ucSpeedH--;
                if(ucSpeedH > 99) //一直减到最后,单片机C语言编译器有一个特征,0减去1会溢出变成255(0xff)
                {
                    ucSpeedH = 0;
                }
                ucWd3Part1Update = 1; //1代表更新显示,响应函数内部会清零
                break;
            case 2:   //设置速度下限
                ucSpeedL--;
                if(ucSpeedL > 99) //一直减到最后,单片机C语言编译器有一个特征,0减去1会溢出变成255(0xff)
                {
                    ucSpeedL = 0;
                }
                ucWd3Part2Update = 1; //1代表更新显示,响应函数内部会清零
                break;
            }
            break;
        case 4:  //窗口4  设置频率
            switch(ucWd4Part)  //在窗口4下,根据不同的局部变量来设置不同的参数
            {
            case 1:   //设置频率上限
                ucFreqH--;
                if(ucFreqH > 99) //一直减到最后,单片机C语言编译器有一个特征,0减去1会溢出变成255(0xff)
                {
                    ucFreqH = 0;
                }
                ucWd4Part1Update = 1; //1代表更新显示,响应函数内部会清零
                break;
            case 2:   //设置频率下限
                ucFreqL--;
                if(ucFreqL > 99) //一直减到最后,单片机C语言编译器有一个特征,0减去1会溢出变成255(0xff)
                {
                    ucFreqL = 0;
                }
                ucWd4Part2Update = 1; //1代表更新显示,响应函数内部会清零
                break;
            }
            break;
        }
        uiVoiceCnt = const_voice_short; //按键声音触发,滴一声就停。
        ucKeySec = 0; //响应按键服务处理程序后,按键编号清零,避免一致触发
        break;

    case 3:// 切换"光标"移动按键 对应朱兆祺学习板的S9键
        switch(ucWd)  //在不同的窗口下,设置不同的参数
        {
        case 1: //窗口1 主菜单
            switch(ucWd1Part)  //在窗口1下,根据不同的局部变量来设置不同的参数
            {

            case 1:   //设置时间
                ucWd1Part = 2; //光标切换到下一行
                ucWd1Part1Update = 1; //更新显示原来那一行,目的是更新反显光标的状态
                ucWd1Part2Update = 1; //更新显示下一行,    目的是更新反显光标的状态
                break;
            case 2:   //设置速度
                ucWd1Part = 3; //光标切换到下一行
                ucWd1Part2Update = 1; //更新显示原来那一行,目的是更新反显光标的状态
                ucWd1Part3Update = 1; //更新显示下一行,    目的是更新反显光标的状态
                break;
            case 3:   //设置第3行参数
                ucWd1Part = 1; //光标返回到第一行
                ucWd1Part3Update = 1; //更新显示原来那一行,目的是更新反显光标的状态
                ucWd1Part1Update = 1; //更新显示下一行,    目的是更新反显光标的状态
                break;


            }
            break;
        case 2: //窗口2 设置时间
            switch(ucWd2Part)  //在窗口2下,根据不同的局部变量来设置不同的参数
            {

            case 1:   //时间上限
                ucWd2Part = 2; //光标切换到下一行
                ucWd2Part1Update = 1; //更新显示原来那一行,目的是更新反显光标的状态
                ucWd2Part2Update = 1; //更新显示下一行,    目的是更新反显光标的状态
                break;
            case 2:   //时间下限
                ucWd2Part = 1; //光标返回到第一行
                ucWd2Part2Update = 1; //更新显示原来那一行,目的是更新反显光标的状态
                ucWd2Part1Update = 1; //更新显示下一行,    目的是更新反显光标的状态
                break;

            }
            break;
        case 3: //窗口3 设置速度
            switch(ucWd3Part)  //在窗口3下,根据不同的局部变量来设置不同的参数
            {

            case 1:   //速度上限
                ucWd3Part = 2; //光标切换到下一行
                ucWd3Part1Update = 1; //更新显示原来那一行,目的是更新反显光标的状态
                ucWd3Part2Update = 1; //更新显示下一行,    目的是更新反显光标的状态
                break;
            case 2:   //速度下限
                ucWd3Part = 1; //光标返回到第一行
                ucWd3Part2Update = 1; //更新显示原来那一行,目的是更新反显光标的状态
                ucWd3Part1Update = 1; //更新显示下一行,    目的是更新反显光标的状态
                break;

            }
            break;
        case 4: //窗口4 设置频率
            switch(ucWd4Part)  //在窗口4下,根据不同的局部变量来设置不同的参数
            {

            case 1:   //频率上限
                ucWd4Part = 2; //光标切换到下一行
                ucWd4Part1Update = 1; //更新显示原来那一行,目的是更新反显光标的状态
                ucWd4Part2Update = 1; //更新显示下一行,    目的是更新反显光标的状态
                break;
            case 2:   //频率下限
                ucWd4Part = 1; //光标返回到第一行
                ucWd4Part2Update = 1; //更新显示原来那一行,目的是更新反显光标的状态
                ucWd4Part1Update = 1; //更新显示下一行,    目的是更新反显光标的状态
                break;

            }
            break;
        }

        uiVoiceCnt = const_voice_short; //按键声音触发,滴一声就停。
        ucKeySec = 0; //响应按键服务处理程序后,按键编号清零,避免一致触发
        break;

    case 4: // 进入和退出按键  对应朱兆祺学习板的S13键,按一次进入选中的子菜单。再按一次退出子菜单。
        switch(ucWd)  //在不同的窗口下,设置不同的参数
        {
        case 1:  //窗口1
            switch(ucWd1Part)  //在窗口1下,根据不同的局部变量来设置不同的参数
            {

            case 1:   //设置时间
                ucWd = 2; //进入设置时间的窗口2
                ucWd2Update = 1; //窗口2整屏更新
                break;
            case 2:   //设置速度
                ucWd = 3; //进入设置速度的窗口3
                ucWd3Update = 1; //窗口3整屏更新
                break;
            case 3:   //设置频率
                ucWd = 4; //进入设置频率的窗口4
                ucWd4Update = 1; //窗口4整屏更新
                break;


            }
            break;
        case 2:  //窗口2
            ucWd = 1;      //返回主菜单窗口1
            ucWd1Update = 1; //窗口1整屏更新
            break;
        case 3:  //窗口3
            ucWd = 1;      //返回主菜单窗口1
            ucWd1Update = 1; //窗口1整屏更新
            break;
        case 4:  //窗口4
            ucWd = 1;      //返回主菜单窗口1
            ucWd1Update = 1; //窗口1整屏更新
            break;

        }

        uiVoiceCnt = const_voice_short; //按键声音触发,滴一声就停。
        ucKeySec = 0; //响应按键服务处理程序后,按键编号清零,避免一致触发
        break;

    }
}


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) //应用层面的液晶屏显示程序
{

    switch(ucWd)  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
    {
    case 1:
        wd1();  //主菜单
        break;
    case 2:
        wd2();  //设置时间
        break;
    case 3:
        wd3();  //设置速度
        break;
    case 4:
        wd4();  //设置频率
        break;

        //本程序只有4个窗口,所以只有4个case ,如果要增加窗口,就直接增加 case 5, case 6...
    }

}


void wd1(void)  //窗口1  主菜单
{

    unsigned char ucCursorFlag;  //光标标志,也就是反显的标志,它是根据局部变量ucPart来定的

    /* 注释三:
    * 把每一个窗口的内容分为两种类型,一种类型是那些不用经常刷新显示的内容,只有在切换窗口的时候
    * 才需要更新,这种内容放在整屏更新显示的括号里,比如清屏操作等内容。另外一种是那些经常需要
    * 刷新显示的内容,这种内容放在局部更新显示的括号里。
    */
    if(ucWd1Update == 1) //窗口1整屏更新,里面只放那些不用经常刷新显示的内容
    {
        ucWd1Update = 0; //及时清零,避免一直更新

        ucWd1Part1Update = 1; //激活窗口1的第1个局部更新显示变量
        ucWd1Part2Update = 1; //激活窗口1的第2个局部更新显示变量
        ucWd1Part3Update = 1; //激活窗口1的第3个局部更新显示变量


        display_clear(0x00); // 清屏操作, 全部显示空填充0x00,全部显示点阵用0xff。
        clear_all_canvas();  //把画布全部清零
        insert_buffer_to_canvas(0, 0, Zf816_mao_hao, 0, 1, 16); //把冒号的字模插入画布

        display_lattice(2, 0, Hz1616_zhu, 0, 2, 16, 0); //主菜单。这些内容不用经常更新,只有在切换窗口的时候才更新显示
        display_lattice(3, 0, Hz1616_cai, 0, 2, 16, 0);
        display_lattice(4, 0, Hz1616_dan, 0, 2, 16, 0);


    }

    /* 注释四:
    * 注意!我前面讲数码管显示的时候有一句话讲错了,我那时说<局部更新应该写在整屏更新之前>,这是不对的。
    * 按照现在的显示程序框架<即整屏显示更新括号里包含了所有局部变量的激活>,应该是<整屏更新应该写在局部更新之前>
    * 这样才对。
    */
    if(ucWd1Part1Update == 1) //窗口1的第1个局部更新显示变量,里面放一些经常需要刷新显示的内容
    {
        ucWd1Part1Update = 0; //及时清零,避免一直更新

        if(ucWd1Part == 1) //被选中
        {
            ucCursorFlag = 1; //反显 显示
        }
        else //没被选中
        {
            ucCursorFlag = 0; //正常 显示
        }

        display_lattice(0, 16, Hz1616_she, ucCursorFlag, 2, 16, 0); //设置时间范围
        display_lattice(1, 16, Hz1616_zhi, ucCursorFlag, 2, 16, 0);
        display_lattice(2, 16, Hz1616_shi, ucCursorFlag, 2, 16, 0);
        display_lattice(3, 16, Hz1616_jian, ucCursorFlag, 2, 16, 0);
        display_lattice(4, 16, Hz1616_fan, ucCursorFlag, 2, 16, 0);
        display_lattice(5, 16, Hz1616_wei, ucCursorFlag, 2, 16, 0);



    }

    if(ucWd1Part2Update == 1) //窗口1的第2个局部更新显示变量,里面放一些经常需要刷新显示的内容
    {
        ucWd1Part2Update = 0; //及时清零,避免一直更新

        if(ucWd1Part == 2) //被选中
        {
            ucCursorFlag = 1; //反显 显示
        }
        else //没被选中
        {
            ucCursorFlag = 0; //正常 显示
        }

        display_lattice(8, 0, Hz1616_she, ucCursorFlag, 2, 16, 0); //设置速度范围
        display_lattice(9, 0, Hz1616_zhi, ucCursorFlag, 2, 16, 0);
        display_lattice(10, 0, Hz1616_su, ucCursorFlag, 2, 16, 0);
        display_lattice(11, 0, Hz1616_du, ucCursorFlag, 2, 16, 0);
        display_lattice(12, 0, Hz1616_fan, ucCursorFlag, 2, 16, 0);
        display_lattice(13, 0, Hz1616_wei, ucCursorFlag, 2, 16, 0);

    }

    if(ucWd1Part3Update == 1) //窗口1的第3行局部更新显示变量,里面放一些经常需要刷新显示的内容
    {
        ucWd1Part3Update = 0; //及时清零,避免一直更新

        if(ucWd1Part == 3) //被选中
        {
            ucCursorFlag = 1; //反显 显示
        }
        else //没被选中
        {
            ucCursorFlag = 0; //正常 显示
        }

        display_lattice(8, 16, Hz1616_she, ucCursorFlag, 2, 16, 0); //设置频率范围
        display_lattice(9, 16, Hz1616_zhi, ucCursorFlag, 2, 16, 0);
        display_lattice(10, 16, Hz1616_pin, ucCursorFlag, 2, 16, 0);
        display_lattice(11, 16, Hz1616_lv, ucCursorFlag, 2, 16, 0);
        display_lattice(12, 16, Hz1616_fan, ucCursorFlag, 2, 16, 0);
        display_lattice(13, 16, Hz1616_wei, ucCursorFlag, 2, 16, 0);
    }


}


void wd2(void)  //窗口2 设置时间
{
    unsigned char ucAnyNumber_1; //分解变量的个位
    unsigned char ucAnyNumber_10; //分解变量的十位


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

    unsigned char ucCursorFlag;  //光标标志,也就是反显的标志,它是根据局部变量ucPart来定的

    if(ucWd2Update == 1) //窗口2整屏更新,里面只放那些不用经常刷新显示的内容
    {
        ucWd2Update = 0; //及时清零,避免一直更新

        ucWd2Part1Update = 1; //激活窗口2的第1个局部更新显示变量,这里在前面数码管显示框架上有所改进
        ucWd2Part2Update = 1; //激活窗口2的第2个局部更新显示变量,这里在前面数码管显示框架上有所改进

        display_clear(0x00); // 清屏操作, 全部显示空填充0x00,全部显示点阵用0xff。
        clear_all_canvas();  //把画布全部清零
        insert_buffer_to_canvas(0, 0, Zf816_mao_hao, 0, 1, 16); //把冒号的字模插入画布


        display_lattice(2, 0, Hz1616_she, 0, 2, 16, 0); //设置时间。这些内容不用经常更新,只有在切换窗口的时候才更新显示
        display_lattice(3, 0, Hz1616_zhi, 0, 2, 16, 0);
        display_lattice(4, 0, Hz1616_shi, 0, 2, 16, 0);
        display_lattice(5, 0, Hz1616_jian, 0, 2, 16, 0);


        display_lattice(0, 16, Hz1616_shi, 0, 2, 16, 0); //时间上限
        display_lattice(1, 16, Hz1616_jian, 0, 2, 16, 0);
        display_lattice(2, 16, Hz1616_shang, 0, 2, 16, 0);
        display_lattice(3, 16, Hz1616_xian, 0, 2, 16, 0);

        display_lattice(8, 0, Hz1616_shi, 0, 2, 16, 0); //时间下限
        display_lattice(9, 0, Hz1616_jian, 0, 2, 16, 0);
        display_lattice(10, 0, Hz1616_xia, 0, 2, 16, 0);
        display_lattice(11, 0, Hz1616_xian, 0, 2, 16, 0);

    }

    if(ucWd2Part1Update == 1) //窗口2的第1个局部更新显示变量,里面放一些经常需要刷新显示的内容
    {
        ucWd2Part1Update = 0; //及时清零,避免一直更新

        if(ucWd2Part == 1) //被选中
        {
            ucCursorFlag = 1; //反显 显示
        }
        else //没被选中
        {
            ucCursorFlag = 0; //正常 显示
        }

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

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


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


        insert_buffer_to_canvas(2, 0, p_ucAnyNumber_10, ucCursorFlag, 1, 16); //把十的字模插入画布
        insert_buffer_to_canvas(3, 0, p_ucAnyNumber_1, ucCursorFlag, 1, 16); //把个的字模插入画布

        display_lattice(4, 16, ucCanvasBuffer, 0, 4, 16, 0); //显示整屏的画布,最后的参数0是偏移量


    }

    if(ucWd2Part2Update == 1) //窗口2的第2行局部更新显示变量,里面放一些经常需要刷新显示的内容
    {
        ucWd2Part2Update = 0; //及时清零,避免一直更新

        if(ucWd2Part == 2) //被选中
        {
            ucCursorFlag = 1; //反显 显示
        }
        else //没被选中
        {
            ucCursorFlag = 0; //正常 显示
        }

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

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


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


        insert_buffer_to_canvas(2, 0, p_ucAnyNumber_10, ucCursorFlag, 1, 16); //把十的字模插入画布
        insert_buffer_to_canvas(3, 0, p_ucAnyNumber_1, ucCursorFlag, 1, 16); //把个的字模插入画布

        display_lattice(12, 0, ucCanvasBuffer, 0, 4, 16, 0); //显示整屏的画布,最后的参数0是偏移量

    }


}



void wd3(void)  //窗口3 设置速度
{
    unsigned char ucAnyNumber_1; //分解变量的个位
    unsigned char ucAnyNumber_10; //分解变量的十位


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

    unsigned char ucCursorFlag;  //光标标志,也就是反显的标志,它是根据局部变量ucPart来定的

    if(ucWd3Update == 1) //窗口3整屏更新,里面只放那些不用经常刷新显示的内容
    {
        ucWd3Update = 0; //及时清零,避免一直更新

        ucWd3Part1Update = 1; //激活窗口3的第1个局部更新显示变量,这里在前面数码管显示框架上有所改进
        ucWd3Part2Update = 1; //激活窗口3的第2个局部更新显示变量,这里在前面数码管显示框架上有所改进

        display_clear(0x00); // 清屏操作, 全部显示空填充0x00,全部显示点阵用0xff。
        clear_all_canvas();  //把画布全部清零
        insert_buffer_to_canvas(0, 0, Zf816_mao_hao, 0, 1, 16); //把冒号的字模插入画布


        display_lattice(2, 0, Hz1616_she, 0, 2, 16, 0); //设置速度。这些内容不用经常更新,只有在切换窗口的时候才更新显示
        display_lattice(3, 0, Hz1616_zhi, 0, 2, 16, 0);
        display_lattice(4, 0, Hz1616_su, 0, 2, 16, 0);
        display_lattice(5, 0, Hz1616_du, 0, 2, 16, 0);


        display_lattice(0, 16, Hz1616_su, 0, 2, 16, 0); //速度上限
        display_lattice(1, 16, Hz1616_du, 0, 2, 16, 0);
        display_lattice(2, 16, Hz1616_shang, 0, 2, 16, 0);
        display_lattice(3, 16, Hz1616_xian, 0, 2, 16, 0);

        display_lattice(8, 0, Hz1616_su, 0, 2, 16, 0); //速度下限
        display_lattice(9, 0, Hz1616_du, 0, 2, 16, 0);
        display_lattice(10, 0, Hz1616_xia, 0, 2, 16, 0);
        display_lattice(11, 0, Hz1616_xian, 0, 2, 16, 0);

    }

    if(ucWd3Part1Update == 1) //窗口3的第1个局部更新显示变量,里面放一些经常需要刷新显示的内容
    {
        ucWd3Part1Update = 0; //及时清零,避免一直更新

        if(ucWd3Part == 1) //被选中
        {
            ucCursorFlag = 1; //反显 显示
        }
        else //没被选中
        {
            ucCursorFlag = 0; //正常 显示
        }

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

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


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


        insert_buffer_to_canvas(2, 0, p_ucAnyNumber_10, ucCursorFlag, 1, 16); //把十的字模插入画布
        insert_buffer_to_canvas(3, 0, p_ucAnyNumber_1, ucCursorFlag, 1, 16); //把个的字模插入画布

        display_lattice(4, 16, ucCanvasBuffer, 0, 4, 16, 0); //显示整屏的画布,最后的参数0是偏移量


    }

    if(ucWd3Part2Update == 1) //窗口3的第2行局部更新显示变量,里面放一些经常需要刷新显示的内容
    {
        ucWd3Part2Update = 0; //及时清零,避免一直更新

        if(ucWd3Part == 2) //被选中
        {
            ucCursorFlag = 1; //反显 显示
        }
        else //没被选中
        {
            ucCursorFlag = 0; //正常 显示
        }

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

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


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


        insert_buffer_to_canvas(2, 0, p_ucAnyNumber_10, ucCursorFlag, 1, 16); //把十的字模插入画布
        insert_buffer_to_canvas(3, 0, p_ucAnyNumber_1, ucCursorFlag, 1, 16); //把个的字模插入画布

        display_lattice(12, 0, ucCanvasBuffer, 0, 4, 16, 0); //显示整屏的画布,最后的参数0是偏移量

    }


}



void wd4(void)  //窗口4 设置频率
{
    unsigned char ucAnyNumber_1; //分解变量的个位
    unsigned char ucAnyNumber_10; //分解变量的十位


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

    unsigned char ucCursorFlag;  //光标标志,也就是反显的标志,它是根据局部变量ucPart来定的

    if(ucWd4Update == 1) //窗口4整屏更新,里面只放那些不用经常刷新显示的内容
    {
        ucWd4Update = 0; //及时清零,避免一直更新

        ucWd4Part1Update = 1; //激活窗口4的第1个局部更新显示变量,这里在前面数码管显示框架上有所改进
        ucWd4Part2Update = 1; //激活窗口4的第2个局部更新显示变量,这里在前面数码管显示框架上有所改进

        display_clear(0x00); // 清屏操作, 全部显示空填充0x00,全部显示点阵用0xff。
        clear_all_canvas();  //把画布全部清零
        insert_buffer_to_canvas(0, 0, Zf816_mao_hao, 0, 1, 16); //把冒号的字模插入画布


        display_lattice(2, 0, Hz1616_she, 0, 2, 16, 0); //设置频率。这些内容不用经常更新,只有在切换窗口的时候才更新显示
        display_lattice(3, 0, Hz1616_zhi, 0, 2, 16, 0);
        display_lattice(4, 0, Hz1616_pin, 0, 2, 16, 0);
        display_lattice(5, 0, Hz1616_lv, 0, 2, 16, 0);


        display_lattice(0, 16, Hz1616_pin, 0, 2, 16, 0); //频率上限
        display_lattice(1, 16, Hz1616_lv, 0, 2, 16, 0);
        display_lattice(2, 16, Hz1616_shang, 0, 2, 16, 0);
        display_lattice(3, 16, Hz1616_xian, 0, 2, 16, 0);

        display_lattice(8, 0, Hz1616_pin, 0, 2, 16, 0); //频率下限
        display_lattice(9, 0, Hz1616_lv, 0, 2, 16, 0);
        display_lattice(10, 0, Hz1616_xia, 0, 2, 16, 0);
        display_lattice(11, 0, Hz1616_xian, 0, 2, 16, 0);

    }

    if(ucWd4Part1Update == 1) //窗口4的第1个局部更新显示变量,里面放一些经常需要刷新显示的内容
    {
        ucWd4Part1Update = 0; //及时清零,避免一直更新

        if(ucWd4Part == 1) //被选中
        {
            ucCursorFlag = 1; //反显 显示
        }
        else //没被选中
        {
            ucCursorFlag = 0; //正常 显示
        }

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

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


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


        insert_buffer_to_canvas(2, 0, p_ucAnyNumber_10, ucCursorFlag, 1, 16); //把十的字模插入画布
        insert_buffer_to_canvas(3, 0, p_ucAnyNumber_1, ucCursorFlag, 1, 16); //把个的字模插入画布

        display_lattice(4, 16, ucCanvasBuffer, 0, 4, 16, 0); //显示整屏的画布,最后的参数0是偏移量
    }

    if(ucWd4Part2Update == 1) //窗口4的第2行局部更新显示变量,里面放一些经常需要刷新显示的内容
    {
        ucWd4Part2Update = 0; //及时清零,避免一直更新

        if(ucWd4Part == 2) //被选中
        {
            ucCursorFlag = 1; //反显 显示
        }
        else //没被选中
        {
            ucCursorFlag = 0; //正常 显示
        }

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

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


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


        insert_buffer_to_canvas(2, 0, p_ucAnyNumber_10, ucCursorFlag, 1, 16); //把十的字模插入画布
        insert_buffer_to_canvas(3, 0, p_ucAnyNumber_1, ucCursorFlag, 1, 16); //把个的字模插入画布

        display_lattice(12, 0, ucCanvasBuffer, 0, 4, 16, 0); //显示整屏的画布,最后的参数0是偏移量
    }

}

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++) //内嵌循环的空指令数量
        {
            ; //一个分号相当于执行一条空语句
        }
    }
}

总结陈词:

我前面几节液晶屏程序的字模都是通过外围工具软件生成的,其实这款12864液晶模块本身就是自带字库,编程的时候只要在源代码里直接写入所需要的汉字或者字符,就可以自动调用相对应的字库了。但是细心的网友一定会问,为什么在源代码上直接写入某个汉字就可以调用到这个汉字的字库?在这个过程中,C51编译器到底还干了哪些鲜为人知的好事?欲知详情,请听下回分解-----液晶屏自带字库跟汉字机内码的关系。

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

C
1
https://gitee.com/F4NNIU/FN1895E-MCU101.git
git@gitee.com:F4NNIU/FN1895E-MCU101.git
F4NNIU
FN1895E-MCU101
FN1895E-MCU101
Development

搜索帮助