jianhong_wu

    1. 第八十四节:实时同步把键盘输入的BCD码数组转换成数值的液晶屏显示程序。 开场白:    键盘直接输入的是带小数点的BCD码数组,要把它们转换成具体的数值才可以更好的在程序里运算或者处理。如何把BCD码数组实时同步转换成数值?这一节主要跟大家讲这方面的算法程序。另外,有一个地方值得注意:上一节键盘输入的小数点个数可以限制成最大2位,但是整数部分没有限制。这节为了也能限制整数部分的最大个数为3位,我修改了上一节的void set_data(…)函数。所以这节的void set_data(…)函数跟上一节的void set_data(…)函数有点不一样,需要特别注意。 具体内容,请看源代码讲解。 (1)     硬件平台: 基于朱兆祺51单片机学习板。数字1键对应S1键,数字2键对应S2键,数字3键对应S3键…. 数字9键对应S9键, 数字0键对应S10键。小数键对应S11,清零键对应S16,其它按键不用。 (2)     实现功能:用矩阵键盘输入任意数字或小数点。小数点不能超过2位,一旦超过2位,再按其它按键则输入无效。整数部分不能超过3位,一旦超过3位,再按其它按键则输入无效。想重新输入,必须按S16清零按键才能重新输入。每次键盘输入的第一行BCD码数组会同步更新显示在第二行的数值上。 (3)源代码讲解如下: #include "REG52.H" #define const_voice_short  40   //蜂鸣器短叫的持续时间 #define const_key_time  10    //按键去抖动延时的时间 sbit key_sr1=P0^0; //第一行输入 sbit key_sr2=P0^1; //第二行输入 sbit key_sr3=P0^2; //第三行输入 sbit key_sr4=P0^3; //第四行输入 sbit key_dr1=P0^4; //第一列输出 sbit key_dr2=P0^5; //第二列输出 sbit key_dr3=P0^6; //第三列输出 sbit key_dr4=P0^7; //第四列输出 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 key_number_input(unsigned char ucKeyNumber); //输入数字按键 void set_data(unsigned char ucKeyNumberTemp, //设置参数               unsigned char ucDotBitMax,               unsigned char ucDataCntMax,               unsigned char *p_ucDotCnt,               unsigned char *p_ucDotBitS,                           unsigned char *p_ucWdPartCnt,                           unsigned char *p_ucSetDataBuffer,                           unsigned char ucIntCntMax,                           unsigned char *p_ucIntCnt); unsigned long buffer_to_data(unsigned char ucConverDataSize,unsigned char ucConverDotCnt,unsigned char *p_ucConverBuffer); //把带小数点的BCD数组转换成long类型的数值。 void key_delete_input(void); //删除按键 void T0_time(); //定时中断函数 void key_service(); void key_scan(); //按键扫描函数 放在定时中断里 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, }; code unsigned char Zf816_dot[]=  //小数点 { /*--  文字:  .  --*/ /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x60,0x60,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_yi[]= { /*--  文字:  一  --*/ /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x7F,0xFE, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, }; code unsigned char Hz1616_xiang[]= { /*--  文字:  项  --*/ /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/ 0x00,0x00,0x03,0xFE,0xFC,0x20,0x10,0x40,0x11,0xFC,0x11,0x04,0x11,0x24,0x11,0x24, 0x11,0x24,0x11,0x24,0x1D,0x24,0xE1,0x34,0x00,0x48,0x01,0x86,0x06,0x02,0x00,0x00, }; code unsigned char Hz1616_shu[]= { /*--  文字:  数  --*/ /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/ 0x08,0x20,0x49,0x30,0x2A,0x20,0x1C,0x20,0xFF,0x7E,0x1C,0x44,0x2B,0x44,0x48,0xC4, 0x08,0x28,0xFF,0x28,0x12,0x10,0x34,0x10,0x0C,0x28,0x32,0x4E,0xC0,0x84,0x00,0x00, }; code unsigned char Hz1616_zhu[]= { /*--  文字:  组  --*/ /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/ 0x10,0x00,0x19,0xF8,0x11,0x08,0x25,0x08,0x25,0x08,0x79,0xF8,0x09,0x08,0x11,0x08, 0x21,0x08,0x7D,0xF8,0x01,0x08,0x01,0x08,0x0D,0x08,0x73,0xFE,0x00,0x00,0x00,0x00, }; code unsigned char Hz1616_zhi[]= { /*--  文字:  值  --*/ /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/ 0x10,0x40,0x18,0x60,0x17,0xFC,0x10,0x40,0x20,0x80,0x33,0xF8,0x62,0x08,0xA3,0xF8, 0x22,0x08,0x23,0xF8,0x22,0x08,0x23,0xF8,0x22,0x08,0x22,0x08,0x2F,0xFE,0x20,0x00, }; /* 注释一: * 以下是画布显示数组。横向是6个字节,纵向16行,可以显示3个16x16的汉字. *  注意,这节内容的画布跟前面79章节的画布大小不一样,79节前面的横向是4个字节,这节的横向是6个字节。 */ unsigned char ucCanvasBuffer[]= { 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,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, }; /* 注释二: * 以下5个变量记录一个参数的5种信息,包括小数点的数量,小数点个数,数据的位置,数组具体值,整数个数 */ unsigned char ucDotCnt_1=0;  //记录当前输入的小数点数量,如果小数点的数量不为0,说明当前数组已包含小数点,此时再按小数点按键则无效 unsigned char ucDotBitS_1=0; //记录当前输入的小数点个数,如果小数点的个数如果超过规定ucDotBitMax位,此时再按任何输入按键则无效 unsigned char ucWdPartCnt_1=0; //记录当前输入的数据在数组中的位置。 unsigned char ucDataBuffer_1[6]={0,10,10,10,10,10}; //一项的BCD码数组缓冲 unsigned char ucIntCnt_1=0; //记录当前输入的整数个数,如果整数的个数如果超过规定ucIntCntMax位,此时再按任何输入按键则无效 unsigned long ulData_1=0; //用一个long变量表示BCD码的具体数值。 unsigned char ucKeyStep=1;  //按键扫描步骤变量 unsigned char ucKeySec=0;   //被触发的按键编号 unsigned int  uiKeyTimeCnt=0; //按键去抖动延时计数器 unsigned char ucKeyLock=0; //按键触发后自锁的变量标志 unsigned char ucRowRecord=1; //记录当前扫描到第几列了 unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器 unsigned char ucWd=1; //窗口变量 unsigned char ucPart=1; //局部变量 0代表没有选中任何一行,其它数值1到4代表选中某一行 unsigned char ucWd1Update=1; //窗口1的整屏更新显示变量      1代表更新显示,响应函数内部会清零 unsigned char ucWd1Part1Update=0; //窗口1的第1行局部更新显示变量  1代表更新显示,响应函数内部会自动把它清零 unsigned char ucWd1Part2Update=0; //窗口1的第2行局部更新显示变量  1代表更新显示,响应函数内部会自动把它清零 void main()   {         initial_myself();      //第一区,上电后马上初始化         delay_long(100);       //一线,延时线。延时一段时间         initial_peripheral();  //第二区,上电后延时一段时间再初始化         while(1)   //第三区         {                     key_service(); //按键服务程序             lcd_display_service(); //应用层面的液晶屏显示程序         } } void initial_myself()  //第一区 上电后马上初始化 {    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()//按键扫描函数 放在定时中断里 {     switch(ucKeyStep)   {      case 1:   //按键扫描输出第ucRowRecord列低电平               if(ucRowRecord==1)  //第一列输出低电平                   {              key_dr1=0;                    key_dr2=1;              key_dr3=1;                 key_dr4=1;                   }               else if(ucRowRecord==2)  //第二列输出低电平                   {              key_dr1=1;                    key_dr2=0;              key_dr3=1;                 key_dr4=1;                   }               else if(ucRowRecord==3)  //第三列输出低电平                   {              key_dr1=1;                    key_dr2=1;              key_dr3=0;                 key_dr4=1;                   }               else   //第四列输出低电平                   {              key_dr1=1;                    key_dr2=1;              key_dr3=1;                 key_dr4=0;                   }           uiKeyTimeCnt=0;  //延时计数器清零           ucKeyStep++;     //切换到下一个运行步骤               break;      case 2:     //此处的小延时用来等待刚才列输出信号稳定,再判断输入信号。不是去抖动延时。           uiKeyTimeCnt++;                   if(uiKeyTimeCnt>1)                   {                      uiKeyTimeCnt=0;              ucKeyStep++;     //切换到下一个运行步骤                   }               break;      case 3:           if(key_sr1==1&&key_sr2==1&&key_sr3==1&&key_sr4==1)           {                ucKeyStep=1;  //如果没有按键按下,返回到第一个运行步骤重新开始扫描              ucKeyLock=0;  //按键自锁标志清零              uiKeyTimeCnt=0; //按键去抖动延时计数器清零,此行非常巧妙                                   ucRowRecord++;  //输出下一列                          if(ucRowRecord>4)                            {                             ucRowRecord=1; //依次输出完四列之后,继续从第一列开始输出低电平                          }           }                   else if(ucKeyLock==0)  //有按键按下,且是第一次触发                   {                      if(key_sr1==0&&key_sr2==1&&key_sr3==1&&key_sr4==1)                          {                             uiKeyTimeCnt++;  //去抖动延时计数器                                 if(uiKeyTimeCnt>const_key_time)                                 {                                    uiKeyTimeCnt=0;                                    ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零                        if(ucRowRecord==1)  //第一列输出低电平                            {                                       ucKeySec=1;  //触发1号键 对应朱兆祺学习板的S1键                            }                        else if(ucRowRecord==2)  //第二列输出低电平                            {                                       ucKeySec=2;  //触发2号键 对应朱兆祺学习板的S2键                            }                        else if(ucRowRecord==3)  //第三列输出低电平                            {                                       ucKeySec=3;  //触发3号键 对应朱兆祺学习板的S3键                            }                        else   //第四列输出低电平                            {                                       ucKeySec=4;  //触发4号键 对应朱兆祺学习板的S4键                            }                                 }                                                   }                      else if(key_sr1==1&&key_sr2==0&&key_sr3==1&&key_sr4==1)                          {                             uiKeyTimeCnt++;  //去抖动延时计数器                                 if(uiKeyTimeCnt>const_key_time)                                 {                                    uiKeyTimeCnt=0;                                    ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零                        if(ucRowRecord==1)  //第一列输出低电平                            {                                       ucKeySec=5;  //触发5号键 对应朱兆祺学习板的S5键                            }                        else if(ucRowRecord==2)  //第二列输出低电平                            {                                       ucKeySec=6;  //触发6号键 对应朱兆祺学习板的S6键                            }                        else if(ucRowRecord==3)  //第三列输出低电平                            {                                       ucKeySec=7;  //触发7号键 对应朱兆祺学习板的S7键                            }                        else   //第四列输出低电平                            {                                       ucKeySec=8;  //触发8号键 对应朱兆祺学习板的S8键                            }                                 }                                                   }                      else if(key_sr1==1&&key_sr2==1&&key_sr3==0&&key_sr4==1)                          {                             uiKeyTimeCnt++;  //去抖动延时计数器                                 if(uiKeyTimeCnt>const_key_time)                                 {                                    uiKeyTimeCnt=0;                                    ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零                        if(ucRowRecord==1)  //第一列输出低电平                            {                                       ucKeySec=9;  //触发9号键 对应朱兆祺学习板的S9键                            }                        else if(ucRowRecord==2)  //第二列输出低电平                            {                                       ucKeySec=10;  //触发10号键 对应朱兆祺学习板的S10键                            }                        else if(ucRowRecord==3)  //第三列输出低电平                            {                                       ucKeySec=11;  //触发11号键 对应朱兆祺学习板的S11键                            }                        else   //第四列输出低电平                            {                                       ucKeySec=12;  //触发12号键 对应朱兆祺学习板的S12键                            }                                 }                                                   }                      else if(key_sr1==1&&key_sr2==1&&key_sr3==1&&key_sr4==0)                          {                             uiKeyTimeCnt++;  //去抖动延时计数器                                 if(uiKeyTimeCnt>const_key_time)                                 {                                    uiKeyTimeCnt=0;                                    ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零                        if(ucRowRecord==1)  //第一列输出低电平                            {                                       ucKeySec=13;  //触发13号键 对应朱兆祺学习板的S13键                            }                        else if(ucRowRecord==2)  //第二列输出低电平                            {                                       ucKeySec=14;  //触发14号键 对应朱兆祺学习板的S14键                            }                        else if(ucRowRecord==3)  //第三列输出低电平                            {                                       ucKeySec=15;  //触发15号键 对应朱兆祺学习板的S15键                            }                        else   //第四列输出低电平                            {                                       ucKeySec=16;  //触发16号键 对应朱兆祺学习板的S16键                            }                                 }                                                   }                                      }               break;   } } void key_service() //按键服务的应用程序 {   switch(ucKeySec) //按键服务状态切换   {     case 1:// 数字1 对应朱兆祺学习板的S1键           key_number_input(1); //输入数字按键           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发           break;             case 2:// 数字2 对应朱兆祺学习板的S2键           key_number_input(2); //输入数字按键           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发           break;          case 3:// 数字3 对应朱兆祺学习板的S3键           key_number_input(3); //输入数字按键           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发           break;              case 4:// 数字4 对应朱兆祺学习板的S4键           key_number_input(4); //输入数字按键           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发           break;        case 5:// 数字5 对应朱兆祺学习板的S5键           key_number_input(5); //输入数字按键           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发           break;        case 6:// 数字6 对应朱兆祺学习板的S6键           key_number_input(6); //输入数字按键           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发           break;        case 7:// 数字7 对应朱兆祺学习板的S7键           key_number_input(7); //输入数字按键           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发           break;        case 8: //数字8 对应朱兆祺学习板的S8键           key_number_input(8); //输入数字按键           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发           break;        case 9:// 数字9 对应朱兆祺学习板的S9键           key_number_input(9); //输入数字按键           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发           break;        case 10:// 数字0  对应朱兆祺学习板的S10键           key_number_input(0); //输入数字按键           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发           break;        case 11:// 小数点按键 对应朱兆祺学习板的S11键           key_number_input(11); //输入数字按键  11代表小数点           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发           break;        case 12:// 本节暂时不用 对应朱兆祺学习板的S12键             uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发           break;        case 13:// 本节暂时不用 对应朱兆祺学习板的S13键              uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发           break;        case 14:// 本节暂时不用  对应朱兆祺学习板的S14键                       uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发           break;        case 15:// 本节暂时不用 对应朱兆祺学习板的S15键           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发           break;        case 16:// 清除按键 对应朱兆祺学习板的S16键           key_delete_input(); //删除按键           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发           break;      }                } void key_number_input(unsigned char ucKeyNumber) //输入数字按键 {     switch(ucWd)     {        case 1:   //第1窗口。本节程序只有1个窗口              switch(ucPart)              {                  case 1:  //1窗口第1项                       set_data(ucKeyNumber,  //本函数跟前面第83节内容有所改动,请看本函数具体内容。本节的核心内容,值得好好研究!                                                         2,  //小数点最大个数                                                            6,  //数组缓冲最大个数                                                        &ucDotCnt_1,                                                            &ucDotBitS_1,                                                            &ucWdPartCnt_1,                                                            ucDataBuffer_1,                                                            3, //整数部分的最大个数                                                            &ucIntCnt_1);                                           ulData_1=buffer_to_data(6,2,ucDataBuffer_1); //把带小数点的BCD码数组转换成long数值。                       ucWd1Part1Update=1;//第一行局部更新显示                                           ucWd1Part2Update=1;//第二行局部更新显示                       break;                             }                                                      break;     }                                                          } /* 注释三: * 本函数在前面第83节内容的函数上有改动,为了限制整数部分的个数,多添加了第8和第9这两个参数。 * 本节的核心函数,值得好好研究! * 涉及到参数的4种信息,包括小数点的数量,小数点的个数,数据的位置,数组具体值,整数的数量,整数的个数,以及它们之间的相互作用关系。 * 以下参数,指针类型的参数是让代入的全局变量在退出函数后维持它当前最新更改的数值不变。 * 第1个参数ucKeyNumberTemp是当前按键输入的数值。 * 第2个参数ucDotBitMax是限定被设置参数的小数点最大位数。 * 第3个参数ucDataCntMax是限定被设置参数的最大数组个数。 * 第4个参数*p_ucDotCnt是记录当前输入的小数点数量,如果小数点的数量不为0,说明当前数组已包含小数点,此时再按小数点按键则无效。 * 第5个参数*p_ucDotBitS是记录当前输入的小数点个数,如果小数点的个数如果超过规定ucDotBitMax位,此时再按任何输入按键则无效 * 第6个参数*p_ucWdPartCnt是记录当前输入的数据在数组中的位置,方便锁定每次按键输入的数字显示位置。 * 第7个参数*p_ucSetDataBuffer是BCD码数组缓冲的具体数字内容。 * 第8个参数ucIntCntMax是限定被设置参数的整数部分的最大位数。 * 第9个参数*p_ucIntCnt是记录当前输入的整数部分个数,如果整数部分的个数如果超过规定ucIntCntMax位,此时再按任何输入按键则无效 */ void set_data(unsigned char ucKeyNumberTemp,               unsigned char ucDotBitMax,               unsigned char ucDataCntMax,               unsigned char *p_ucDotCnt,               unsigned char *p_ucDotBitS,                           unsigned char *p_ucWdPartCnt,                           unsigned char *p_ucSetDataBuffer,                           unsigned char ucIntCntMax,                           unsigned char *p_ucIntCnt) {                     unsigned int i;                     if(ucKeyNumberTemp==11) //等于小数点                     {                        if(ucDotBitMax==0) //如果限定的小数点最大数是0,就意味着此数据不允许带小数点,必须是整数。                        {                            return; //直接返回退出                        }                        else if(*p_ucDotCnt>0)  //小数点个数大于0,意味着当前数组已经包含了小数点,此时再输入小数点则无效。                        {                            return; //直接返回退出                        }                        else  //否则有效,记录当前已经包含一个小数点的信息。                        {                            *p_ucDotCnt=1;  //只能包含一个小数点                        }                     }                     else  //如果输入的不是小数点                     {                         if(*p_ucDotCnt==1) //如果之前已经输入了一个小数点,那么此时输入的数字就是小数点后的数据                                                 {                            if(*p_ucDotBitS
    2. 第八十三节:矩阵键盘输入任意数字或小数点的液晶屏显示程序。 开场白:本来这节打算讲调用液晶屏内部字库时让某行内容反显的,但是在昨天调试过程中,发现一个很奇怪的问题,当调用内部字库时,按照数据手册,我执行一条反显指令时,应该是仅仅某一行反显,但是却同时出现两行反显。比如,当我执行        WriteCommand(0x34); //扩充指令集        WriteCommand(0x04); //第1行反显 指令时,发现第一行和第三行反显,后来想想,我猜测这种12864的屏应该是25632折成左右半屏,左半屏在上面,右半屏在下面。经过这次经验,我觉得大家以后尽量不要用液晶屏的内部字库模式,应该用自构字库的模式(图形模式)。因为我觉得用内部字库模式的时候,这个集成的反显扩展指令不好用。而用自构字库的模式(图形模式),却可以顺心所欲的灵活运用,适合做菜单程序。既然发现内部字库不好用,所以不再讲内部字库模式,这节仅仅接着前面第79节内容,继续讲在自构字库的模式(图形模式)下,如何通过矩阵键盘直接输入数字和小数点,就像普通的计算器一样键盘输入。这个功能表面简单,其实有以下四个地方值得注意:第一:如何用数组接收按键输入的BCD码数据。第二:如何限制输入参数的小数点个数和数组的有效个数。第三:如果第0个位置是0,那么继续输入的数据直接覆盖0,否则就移位再输入。第四:如果第0个位置是0,那么继续输入的小数点要移位输入。要仔细了解以上提到的关键点,必须好好研究本程序中的void set_data(…)函数。同时也要温习一下之前讲的自构字库模式的液晶屏显示内容,尤其是插入画布显示的内容。 具体内容,请看源代码讲解。 (1)     硬件平台: 基于朱兆祺51单片机学习板。数字1键对应S1键,数字2键对应S2键,数字3键对应S3键…. 数字9键对应S9键, 数字0键对应S10键。小数键对应S11,清零键对应S16,其它按键不用。 (2)     实现功能:用矩阵键盘输入任意数字或小数点。小数点不能超过2位,一旦超过2位,再按其它按键则输入无效。有效数字也不能超过6位(包括小数点),一旦超过6位,再按其它按键则输入无效。想重新输入,必须按S16清零按键才能重新输入。 (3)源代码讲解如下: #include "REG52.H" #define const_voice_short  40   //蜂鸣器短叫的持续时间 #define const_key_time  10    //按键去抖动延时的时间 sbit key_sr1=P0^0; //第一行输入 sbit key_sr2=P0^1; //第二行输入 sbit key_sr3=P0^2; //第三行输入 sbit key_sr4=P0^3; //第四行输入 sbit key_dr1=P0^4; //第一列输出 sbit key_dr2=P0^5; //第二列输出 sbit key_dr3=P0^6; //第三列输出 sbit key_dr4=P0^7; //第四列输出 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 key_number_input(unsigned char ucKeyNumber); //输入数字按键 void set_data(unsigned char ucKeyNumberTemp,unsigned char ucDotBitMax,unsigned char ucDataCntMax,unsigned char *p_ucDotCnt,unsigned char *p_ucDotBitS,unsigned char *p_ucWdPartCnt,unsigned char *p_ucSetDataBuffer); void key_delete_input(void); //删除按键 void T0_time(); //定时中断函数 void key_service(); void key_scan(); //按键扫描函数 放在定时中断里 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, }; code unsigned char Zf816_dot[]=  //小数点 { /*--  文字:  .  --*/ /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x60,0x60,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_yi[]= { /*--  文字:  一  --*/ /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x7F,0xFE, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, }; code unsigned char Hz1616_xiang[]= { /*--  文字:  项  --*/ /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/ 0x00,0x00,0x03,0xFE,0xFC,0x20,0x10,0x40,0x11,0xFC,0x11,0x04,0x11,0x24,0x11,0x24, 0x11,0x24,0x11,0x24,0x1D,0x24,0xE1,0x34,0x00,0x48,0x01,0x86,0x06,0x02,0x00,0x00, }; code unsigned char Hz1616_shu[]= { /*--  文字:  数  --*/ /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/ 0x08,0x20,0x49,0x30,0x2A,0x20,0x1C,0x20,0xFF,0x7E,0x1C,0x44,0x2B,0x44,0x48,0xC4, 0x08,0x28,0xFF,0x28,0x12,0x10,0x34,0x10,0x0C,0x28,0x32,0x4E,0xC0,0x84,0x00,0x00, }; code unsigned char Hz1616_zhu[]= { /*--  文字:  组  --*/ /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/ 0x10,0x00,0x19,0xF8,0x11,0x08,0x25,0x08,0x25,0x08,0x79,0xF8,0x09,0x08,0x11,0x08, 0x21,0x08,0x7D,0xF8,0x01,0x08,0x01,0x08,0x0D,0x08,0x73,0xFE,0x00,0x00,0x00,0x00, }; /* 注释一: * 以下是画布显示数组。横向是6个字节,纵向16行,可以显示3个16x16的汉字. *  注意,这节内容的画布跟前面章节的画布大小不一样,前面章节的横向是4个字节,这节的横向是6个字节。 */ unsigned char ucCanvasBuffer[]= { 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,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, }; /* 注释二: * 以下4个变量记录一个参数的4种信息,包括小数点的数量,个数,数据的位置,数组具体值. */ unsigned char ucDotCnt_1=0;  //记录当前输入的小数点数量,如果小数点的数量不为0,说明当前数组已包含小数点,此时再按小数点按键则无效 unsigned char ucDotBitS_1=0; //记录当前输入的小数点个数,如果小数点的个量如果超过规定2位,此时再按任何输入按键则无效 unsigned char ucWdPartCnt_1=0; //记录当前输入的数据在数组中的位置。 unsigned char ucDataBuffer_1[6]={0,10,10,10,10,10}; //一项的BCD码数组缓冲 unsigned char ucKeyStep=1;  //按键扫描步骤变量 unsigned char ucKeySec=0;   //被触发的按键编号 unsigned int  uiKeyTimeCnt=0; //按键去抖动延时计数器 unsigned char ucKeyLock=0; //按键触发后自锁的变量标志 unsigned char ucRowRecord=1; //记录当前扫描到第几列了 unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器 unsigned char ucWd=1; //窗口变量 unsigned char ucPart=1; //局部变量 0代表没有选中任何一行,其它数值1到4代表选中某一行 unsigned char ucWd1Update=1; //窗口1的整屏更新显示变量      1代表更新显示,响应函数内部会清零 unsigned char ucWd1Part1Update=0; //窗口1的第1行局部更新显示变量  1代表更新显示,响应函数内部会清零 void main()   {         initial_myself();      //第一区,上电后马上初始化         delay_long(100);       //一线,延时线。延时一段时间         initial_peripheral();  //第二区,上电后延时一段时间再初始化         while(1)   //第三区         {                     key_service(); //按键服务程序             lcd_display_service(); //应用层面的液晶屏显示程序         } } void initial_myself()  //第一区 上电后马上初始化 {    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()//按键扫描函数 放在定时中断里 {     switch(ucKeyStep)   {      case 1:   //按键扫描输出第ucRowRecord列低电平               if(ucRowRecord==1)  //第一列输出低电平                   {              key_dr1=0;                    key_dr2=1;              key_dr3=1;                 key_dr4=1;                   }               else if(ucRowRecord==2)  //第二列输出低电平                   {              key_dr1=1;                    key_dr2=0;              key_dr3=1;                 key_dr4=1;                   }               else if(ucRowRecord==3)  //第三列输出低电平                   {              key_dr1=1;                    key_dr2=1;              key_dr3=0;                 key_dr4=1;                   }               else   //第四列输出低电平                   {              key_dr1=1;                    key_dr2=1;              key_dr3=1;                 key_dr4=0;                   }           uiKeyTimeCnt=0;  //延时计数器清零           ucKeyStep++;     //切换到下一个运行步骤               break;      case 2:     //此处的小延时用来等待刚才列输出信号稳定,再判断输入信号。不是去抖动延时。           uiKeyTimeCnt++;                   if(uiKeyTimeCnt>1)                   {                      uiKeyTimeCnt=0;              ucKeyStep++;     //切换到下一个运行步骤                   }               break;      case 3:           if(key_sr1==1&&key_sr2==1&&key_sr3==1&&key_sr4==1)           {                ucKeyStep=1;  //如果没有按键按下,返回到第一个运行步骤重新开始扫描              ucKeyLock=0;  //按键自锁标志清零              uiKeyTimeCnt=0; //按键去抖动延时计数器清零,此行非常巧妙                                   ucRowRecord++;  //输出下一列                          if(ucRowRecord>4)                            {                             ucRowRecord=1; //依次输出完四列之后,继续从第一列开始输出低电平                          }           }                   else if(ucKeyLock==0)  //有按键按下,且是第一次触发                   {                      if(key_sr1==0&&key_sr2==1&&key_sr3==1&&key_sr4==1)                          {                             uiKeyTimeCnt++;  //去抖动延时计数器                                 if(uiKeyTimeCnt>const_key_time)                                 {                                    uiKeyTimeCnt=0;                                    ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零                        if(ucRowRecord==1)  //第一列输出低电平                            {                                       ucKeySec=1;  //触发1号键 对应朱兆祺学习板的S1键                            }                        else if(ucRowRecord==2)  //第二列输出低电平                            {                                       ucKeySec=2;  //触发2号键 对应朱兆祺学习板的S2键                            }                        else if(ucRowRecord==3)  //第三列输出低电平                            {                                       ucKeySec=3;  //触发3号键 对应朱兆祺学习板的S3键                            }                        else   //第四列输出低电平                            {                                       ucKeySec=4;  //触发4号键 对应朱兆祺学习板的S4键                            }                                 }                                                   }                      else if(key_sr1==1&&key_sr2==0&&key_sr3==1&&key_sr4==1)                          {                             uiKeyTimeCnt++;  //去抖动延时计数器                                 if(uiKeyTimeCnt>const_key_time)                                 {                                    uiKeyTimeCnt=0;                                    ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零                        if(ucRowRecord==1)  //第一列输出低电平                            {                                       ucKeySec=5;  //触发5号键 对应朱兆祺学习板的S5键                            }                        else if(ucRowRecord==2)  //第二列输出低电平                            {                                       ucKeySec=6;  //触发6号键 对应朱兆祺学习板的S6键                            }                        else if(ucRowRecord==3)  //第三列输出低电平                            {                                       ucKeySec=7;  //触发7号键 对应朱兆祺学习板的S7键                            }                        else   //第四列输出低电平                            {                                       ucKeySec=8;  //触发8号键 对应朱兆祺学习板的S8键                            }                                 }                                                   }                      else if(key_sr1==1&&key_sr2==1&&key_sr3==0&&key_sr4==1)                          {                             uiKeyTimeCnt++;  //去抖动延时计数器                                 if(uiKeyTimeCnt>const_key_time)                                 {                                    uiKeyTimeCnt=0;                                    ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零                        if(ucRowRecord==1)  //第一列输出低电平                            {                                       ucKeySec=9;  //触发9号键 对应朱兆祺学习板的S9键                            }                        else if(ucRowRecord==2)  //第二列输出低电平                            {                                       ucKeySec=10;  //触发10号键 对应朱兆祺学习板的S10键                            }                        else if(ucRowRecord==3)  //第三列输出低电平                            {                                       ucKeySec=11;  //触发11号键 对应朱兆祺学习板的S11键                            }                        else   //第四列输出低电平                            {                                       ucKeySec=12;  //触发12号键 对应朱兆祺学习板的S12键                            }                                 }                                                   }                      else if(key_sr1==1&&key_sr2==1&&key_sr3==1&&key_sr4==0)                          {                             uiKeyTimeCnt++;  //去抖动延时计数器                                 if(uiKeyTimeCnt>const_key_time)                                 {                                    uiKeyTimeCnt=0;                                    ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零                        if(ucRowRecord==1)  //第一列输出低电平                            {                                       ucKeySec=13;  //触发13号键 对应朱兆祺学习板的S13键                            }                        else if(ucRowRecord==2)  //第二列输出低电平                            {                                       ucKeySec=14;  //触发14号键 对应朱兆祺学习板的S14键                            }                        else if(ucRowRecord==3)  //第三列输出低电平                            {                                       ucKeySec=15;  //触发15号键 对应朱兆祺学习板的S15键                            }                        else   //第四列输出低电平                            {                                       ucKeySec=16;  //触发16号键 对应朱兆祺学习板的S16键                            }                                 }                                                   }                                      }               break;   } } void key_service() //按键服务的应用程序 {   switch(ucKeySec) //按键服务状态切换   {     case 1:// 数字1 对应朱兆祺学习板的S1键           key_number_input(1); //输入数字按键           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发           break;             case 2:// 数字2 对应朱兆祺学习板的S2键           key_number_input(2); //输入数字按键           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发           break;          case 3:// 数字3 对应朱兆祺学习板的S3键           key_number_input(3); //输入数字按键           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发           break;              case 4:// 数字4 对应朱兆祺学习板的S4键           key_number_input(4); //输入数字按键           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发           break;        case 5:// 数字5 对应朱兆祺学习板的S5键           key_number_input(5); //输入数字按键           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发           break;        case 6:// 数字6 对应朱兆祺学习板的S6键           key_number_input(6); //输入数字按键           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发           break;        case 7:// 数字7 对应朱兆祺学习板的S7键           key_number_input(7); //输入数字按键           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发           break;        case 8: //数字8 对应朱兆祺学习板的S8键           key_number_input(8); //输入数字按键           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发           break;        case 9:// 数字9 对应朱兆祺学习板的S9键           key_number_input(9); //输入数字按键           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发           break;        case 10:// 数字0  对应朱兆祺学习板的S10键           key_number_input(0); //输入数字按键           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发           break;        case 11:// 小数点按键 对应朱兆祺学习板的S11键           key_number_input(11); //输入数字按键  11代表小数点           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发           break;        case 12:// 本节暂时不用 对应朱兆祺学习板的S12键             uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发           break;        case 13:// 本节暂时不用 对应朱兆祺学习板的S13键              uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发           break;        case 14:// 本节暂时不用  对应朱兆祺学习板的S14键                       uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发           break;        case 15:// 本节暂时不用 对应朱兆祺学习板的S15键           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发           break;        case 16:// 清除按键 对应朱兆祺学习板的S16键           key_delete_input(); //删除按键           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发           break;      }                } void key_number_input(unsigned char ucKeyNumber) //输入数字按键 {         switch(ucWd)         {           case 1:   //第1窗口。本节程序只有1个窗口                    switch(ucPart)                    {              case 1:  //1窗口第1项                     set_data(ucKeyNumber,2,6,&ucDotCnt_1,&ucDotBitS_1,&ucWdPartCnt_1,ucDataBuffer_1); //设置参数,请看本函数具体内容。本节的核心内容,值得好好研究!                                                    ucWd1Part1Update=1;//更新显示                                     break;                          }                                                             break;     }                                                        } /* 注释三: * 本节的核心函数,值得好好研究! * 涉及到参数的4种信息,包括小数点的数量,个数,数据的位置,数组具体值。以及它们之间的相互作用关系。 * 以下参数,指针类型的参数是让代入的全局变量在退出函数后维持它当前最新更改的数值不变。 * 第1个参数ucKeyNumberTemp是当前按键输入的数值。 * 第2个参数ucDotBitMax是限定被设置参数的小数点最大位数。 * 第3个参数ucDataCntMax是限定被设置参数的最大数组个数。 * 第4个参数*p_ucDotCnt是记录当前输入的小数点数量,如果小数点的数量不为0,说明当前数组已包含小数点,此时再按小数点按键则无效。 * 第5个参数*p_ucDotBitS是记录当前输入的小数点个数,如果小数点的个量如果超过规定2位,此时再按任何输入按键则无效 * 第6个参数*p_ucWdPartCnt是记录当前输入的数据在数组中的位置,方便锁定每次按键输入的数字显示位置。 * 第7个参数*p_ucSetDataBuffer是BCD码数组缓冲的具体数字内容。 */ void set_data(unsigned char ucKeyNumberTemp,unsigned char ucDotBitMax,unsigned char ucDataCntMax,unsigned char *p_ucDotCnt,unsigned char *p_ucDotBitS,unsigned char *p_ucWdPartCnt,unsigned char *p_ucSetDataBuffer) {                     unsigned int i;                     if(ucKeyNumberTemp==11) //等于小数点                     {                        if(ucDotBitMax==0) //如果限定的小数点最大数是0,就意味着此数据不允许带小数点,必须是整数。                        {                            return; //直接返回退出                        }                        else if(*p_ucDotCnt>0)  //小数点个数大于0,意味着当前数组已经包含了小数点,此时再输入小数点则无效。                        {                            return; //直接返回退出                        }                        else  //否则有效,记录当前已经包含一个小数点的信息。                        {                            *p_ucDotCnt=1;  //只能包含一个小数点                        }                     }                     else if(*p_ucDotCnt==1) //如果输入的不是小数点,并且之前已经输入了一个小数点,那么此时输入的数字就是小数点后的数据                     {                         if(*p_ucDotBitS
    3. 第八十二节:如何通过调用液晶屏内部字库把一个任意数值的变量显示出来。 开场白: 本来这一节打算开始讲调用液晶屏内部字库时的反显程序,但是我担心跳跃太大,恐怕很多初学者跟不上,所以多插入这一节讲讲后面菜单程序中经常用到的基本功能,在调用内部字库的情况下,如何把一个任意数值的变量显示在液晶屏上。这一节的功能需求跟前面第76节是一模一样的,只不过前面的不是用自带字库,现在的是用自带字库而已。我们还是需要做一个变量转换成ASCII码的函数,以后只要调用这个转换函数就可以了。这一节就要把这个转换函数和框架思路教给大家。 具体内容,请看源代码讲解。 (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;  //复位线 sbit beep_dr=P2^7; //蜂鸣器的驱动IO口 void initial_myself(void);    void initial_peripheral(void); void delay_long(unsigned int uiDelaylong); unsigned char *number_to_ASCII(unsigned char  ucBitNumber); void display_service(void); //显示服务程序,在main函数里 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(void); // 清屏。4行8列的坐标点全部显示2个空字符相当于清屏了。 void display_double_code(unsigned int x,unsigned int y,const unsigned char ucArray1,const unsigned char  ucArray2); //在一个坐标点显示1个汉字或者2个字符的函数 void delay_short(unsigned int uiDelayshort); //延时 code unsigned char  ucAddrTable[]=  //调用内部字库时,液晶屏的坐标体系,位置编码,是驱动内容,读者可以不用深究它的含义。 {      0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87, 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97, 0x88,0x89,0x8a,0x8b,0x8c,0x8d,0x8e,0x8f, 0x98,0x99,0x9a,0x9b,0x9c,0x9d,0x9e,0x9f, }; code unsigned char ASCII816_0[]="0";   //0  对于数组内的字符,编译会自动翻译成 ASCII码(1字节) code unsigned char ASCII816_1[]="1";   //1 code unsigned char ASCII816_2[]="2";   //2 code unsigned char ASCII816_3[]="3";   //3   code unsigned char ASCII816_4[]="4";   //4 code unsigned char ASCII816_5[]="5";   //5   code unsigned char ASCII816_6[]="6";   //6   code unsigned char ASCII816_7[]="7";   //7 code unsigned char ASCII816_8[]="8";   //8 code unsigned char ASCII816_9[]="9";   //9   code unsigned char ASCII816_nc[]=" ";  //空格 /* 注释一: * 以下变量就是本程序的任意变量,网友可以自己更改它的大小来测试本程序,不要超过255. */ unsigned char ucAnyNumber=218;  //任意变量默认初始化为218。 unsigned char ucWd1Part1Update=1; //窗口1的第1个局部更新显示变量  1代表更新显示,响应函数内部会清零 void main()   {         initial_myself();           delay_long(100);            initial_peripheral();         while(1)           {            display_service(); //显示服务程序         } } /* 注释二:在一个坐标点显示1个汉字或者2个字符的函数 * 第1,2个参数x,y是坐标体系。x的范围是0至8,y的范围是0至3. * 第3个参数ucArray1是第1个汉字机内码或者ASCII码。 * 第4个参数ucArray2是第2个汉字机内码或者ASCII码。 */ void display_double_code(unsigned int x,unsigned int y,const unsigned char ucArray1,const unsigned char  ucArray2) {     WriteCommand(0x30);   //基本指令集     WriteCommand(ucAddrTable[8*y+x]);        //起始位置     LCDWriteData(ucArray1);     LCDWriteData(ucArray2); } void display_clear(void) // 清屏。4行8列的坐标点全部显示2个空字符相当于清屏了。 {        unsigned int i,j;         for(i=0;i
    4. 第八十一节:液晶屏显示串口发送过来的任意汉字和字符。 开场白: 通过上一节的学习,我们发现汉字的识别本质是机内码,字符的识别本质是ASCII码。不管是机内码还是ASCII码,这些都是16进制的数字,也就是我们手机平时接收和发送的信息本质都是这些数字编码,但是机内码是2个字节,ASCII码是1个字节,如果在一串随机的信息中,同时包含汉字和字符两种数字信息,我们的程序又该如何能筛选和识别它们,会不会把机内码和ASCII码搞混乱了?这一节要教大家三个知识点: 第一个:ASCII码与汉字机内码不一样的规律是,ASCII码都是小于128(0x80)的,根据这个特点可以编程序把它们区分开来。 第二个:当任意一串信息中既包含汉字机内码,又包含字符ASCII码时,并且当ASCII码左右相邻个数是以奇数存在的时候,如何巧妙地插入填充空格字符0x20使它们能够符合一个坐标点显示2个字符的要求。 第三个:本节程序串口部分是在第39节内容基础上移植修改而成,本节程序中多添加了如何通过结束标志0x0D 0x0A来提取有效数据的内容,读者可以学习一下其中的框架。 具体内容,请看源代码讲解。 (1)硬件平台:基于朱兆祺51单片机学习板。 (2)实现功能:      开机上电后,液晶屏第1行显示“请发送信息”。 任意时刻,从电脑“串口调试助手”根据以下协议要求,发送一串不超过24个汉字或者字符的信息,液晶屏就实时把这些信息显示在第2,3,4行。并且蜂鸣器会鸣叫一声表示数据接收正确。 波特率是:9600 。 通讯协议:EB 00 55  XX XX XX XX …XX XX 0D 0A 最前面3个字节EB 00 55 表示数据头。 最后面2个字节0D 0A表示信息的结束标志。 中间的XX是机内码和ASCII码信息。比如:要发送“曹健1人学习51单片机”的信息,它们对应的指令是: EB 00 55 B2 DC BD A1 31 C8 CB D1 A7 CF B0 35 31 B5 A5 C6 AC BB FA 0D 0A (3)源代码讲解如下: #include "REG52.H" /* 注释一: * 本程序的串口那部分内容是从《第三十九节:判断数据头来接收一串数据的串口通用程序框架。》 * 移植过来的,但是以下要把接收缓冲区的数据从10改成60.同时,协议后面多增加了数据结束标志0x0d 0x0a。 */ #define const_rc_size  60  //接收串口中断数据的缓冲区数组大小 #define const_receive_time  5  //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完,这个时间根据实际情况来调整大小 #define const_voice_short  40   //蜂鸣器短叫的持续时间 sbit  LCDCS_dr  = P1^6;  //片选线 sbit  LCDSID_dr = P1^7;  //串行数据线 sbit  LCDCLK_dr = P3^2;  //串行时钟线 sbit  LCDRST_dr = P3^4;  //复位线 sbit beep_dr=P2^7; //蜂鸣器的驱动IO口 void initial_myself(void);    void initial_peripheral(void); void delay_long(unsigned int uiDelaylong); void T0_time(void);  //定时中断函数 void usart_receive(void); //串口接收中断函数 void usart_service(void);  //串口服务程序,在main函数里 void display_service(void); //显示服务程序,在main函数里 void empty_diaplay_buffer(void); //把显示缓冲区全部填充空格字符0x20 void diaplay_all_buffer(void); //显示第2,3,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(void); // 清屏。4行8列的坐标点全部显示2个空字符相当于清屏了。 void display_double_code(unsigned int x,unsigned int y,const unsigned char ucArray1,const unsigned char  ucArray2); //在一个坐标点显示1个汉字或者2个字符的函数 void delay_short(unsigned int uiDelayshort); //延时 code unsigned char  ucAddrTable[]=  //调用内部字库时,液晶屏的坐标体系,位置编码,是驱动内容,读者可以不用深究它的含义。 {      0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87, 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97, 0x88,0x89,0x8a,0x8b,0x8c,0x8d,0x8e,0x8f, 0x98,0x99,0x9a,0x9b,0x9c,0x9d,0x9e,0x9f, }; code unsigned char JN1616_qing[]=  //机内码  请 { 0xC7,0xEB, //请 }; code unsigned char JN1616_fa[]=  //机内码  发 { 0xB7,0xA2, }; code unsigned char JN1616_song[]=  //机内码  送 { 0xCB,0xCD, }; code unsigned char JN1616_xin[]=  //机内码  信 { 0xD0,0xC5, }; code unsigned char JN1616_xi[]=  //机内码  息 { 0xCF,0xA2, }; unsigned int  uiSendCnt=0;     //用来识别串口是否接收完一串数据的计时器 unsigned char ucSendLock=1;    //串口服务程序的自锁变量,每次接收完一串数据只处理一次 unsigned int  uiRcregTotal=0;  //代表当前缓冲区已经接收了多少个数据 unsigned char ucRcregBuf[const_rc_size]; //接收串口中断数据的缓冲区数组 unsigned int  uiRcMoveIndex=0;  //用来解析数据协议的中间变量 unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器 unsigned char ucWd1Update=1; //窗口1的整屏更新显示变量      1代表更新显示,响应函数内部会清零 unsigned char ucWd1Part1Update=0; //窗口1的第1个局部更新显示变量  1代表更新显示,响应函数内部会清零 unsigned char ucDispplayBuffer[48]; //第2,3,4行显示内容的缓冲区 void main()   {         initial_myself();           delay_long(100);            initial_peripheral();         while(1)           {             usart_service();  //串口服务程序                         display_service(); //显示服务程序         } } /* 注释二:在一个坐标点显示1个汉字或者2个字符的函数 * 第1,2个参数x,y是坐标体系。x的范围是0至8,y的范围是0至3. * 第3个参数ucArray1是第1个汉字机内码或者ASCII码。 * 第4个参数ucArray2是第2个汉字机内码或者ASCII码。 */ void display_double_code(unsigned int x,unsigned int y,const unsigned char ucArray1,const unsigned char  ucArray2) {     WriteCommand(0x30);   //基本指令集     WriteCommand(ucAddrTable[8*y+x]);        //起始位置     LCDWriteData(ucArray1);     LCDWriteData(ucArray2); } void display_clear(void) // 清屏。4行8列的坐标点全部显示2个空字符相当于清屏了。 {        unsigned int i,j;         for(i=0;i
    5. 第八十节:调用液晶屏内部字库来显示汉字或字符的坐标体系和本质。 开场白: 前面章节讲的内容全部都是用自构字库的,相当于使用液晶屏的图像模式。其实这个款12864液晶屏的驱动芯片是st7920,它内部是自带16x16字库的,可以显示16x16的汉字或者8x16的字符。这一节开始就跟大家讲讲这方面的内容。要教会大家四个知识点: 第一个:内部字库的真实坐标体系的本质。当我们用内部字库的时候,它的坐标体系跟前面讲的自造字库坐标不一样,不再是256x32的液晶屏。它还原成为128x64的液晶屏,横坐标x轴坐标没办法精确到每个点,只能以16个点(2个字节)为一个单位,因此128个点的x轴坐标范围是0至8。而y轴的坐标也是以16个点(2个字节)为一个单位,因此64个点的x轴坐标范围是0至3。把12864液晶屏分成4行8列,每个数代表一个坐标点。 第二个:在使用内部字库时,C51编译器暗地里干了啥?如果使用液晶屏内部自带字库,编程的时候只要在源代码里直接写入所需要的汉字或者字符,就可以自动调用相对应的字库了。但是细心的网友一定会问,为什么在源代码上直接写入某个汉字就可以调用到这个汉字的字库?其实,表面上我们写下具体的某个汉字或者字符,但是C51编译器会自动对数组内的汉字翻译成 机内码(2字节),会自动对数组内的字符翻译成 ASCII码(1字节)。 第三个:12864的控制芯片st7920内部有两套驱动显示指令方式,一种是前面章节讲的自构字库模式,也是图像模式。另外一种就是本节讲的用内部字库模式。在切换模式的时候,发送命令字0x0c表示用内部字库模式,发送命令字0x36表示用自构字库模式。 第四个:12864整屏有4行8列,一共32个坐标点,每个坐标点可以显示一个16x16的汉字,但是在显示8x16字符时候,必须一次显示2个字符筹够16x16的点阵。例如,只想达到显示一个字符的时候,应该在另外一个空位置上显示空字符来填充。 具体内容,请看源代码讲解。 (1)硬件平台:基于朱兆祺51单片机学习板。 (2)实现功能:      开机上电后,液晶屏第一行调用直接汉字书写方式的数组来显示(馒头V5)的内容。第四行调用机内码和ASCII码的数组来显示(馒头V5)的内容。 (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(void); // 清屏。4行8列的坐标点全部显示2个空字符相当于清屏了。 void display_hz1616(unsigned int x,unsigned int y,const unsigned char  *ucArray); void display_double_zf816(unsigned int x,unsigned int y,const unsigned char  *ucArray1,const unsigned char  *ucArray2); void delay_short(unsigned int uiDelayshort); //延时 /* 注释一:内部字库的真实坐标体系的本质。 * 当我们用内部字库的时候,它的坐标体系跟前面讲的自造字库坐标不一样,不再是256x32的液晶屏。 * 它还原成为128x64的液晶屏,横坐标x轴坐标没办法精确到每个点,只能以16个点(2个字节)为一个单位, * 因此128个点的x轴坐标范围是0至8。而y轴的坐标也是以16个点(2个字节)为一个单位,因此64个点的x轴 * 坐标范围是0至3。以下是坐标地址的位置编码。把12864液晶屏分成4行8列,每个数代表一个坐标点, * 用深究具体含义,液晶驱动芯片ST7920的手册上有提到。 */ code unsigned char  ucAddrTable[]=  //调用内部字库时,液晶屏的坐标体系,位置编码,是驱动内容,读者可以不用深究它的含义。 {      0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87, 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97, 0x88,0x89,0x8a,0x8b,0x8c,0x8d,0x8e,0x8f, 0x98,0x99,0x9a,0x9b,0x9c,0x9d,0x9e,0x9f, }; /* 注释二:在使用内部字库时,C51编译器暗地里干了啥? * 如果使用液晶屏内部自带字库,以下编程的时候只要在源代码里直接写入所需要的汉字或者字符, * 就可以自动调用相对应的字库了。但是细心的网友一定会问,为什么在源代码上直接写入某个汉字 * 就可以调用到这个汉字的字库?其实,表面上我们写下具体的某个汉字或者字符,但是C51编译器 * 会自动对数组内的汉字翻译成 机内码(2字节),会自动对数组内的字符翻译成 ASCII码(1字节)。 * 本节程序会做这个实验来验证它。以下两种书写方式不一样,但本质是一样的。 */ code unsigned char Hz1616_man[]="馒"; //对于数组内的汉字,编译会自动翻译成 机内码(2字节) code unsigned char JN1616_man[]=  //机内码  馒  网上有很多把汉字或者字符转换成相关编码的工具软件 { 0xC2, 0xF8, }; code unsigned char Hz1616_tou[]="头"; //对于数组内的汉字,编译会自动翻译成 机内码(2字节) code unsigned char JN1616_tou[]=  //机内码  头  网上有很多把汉字或者字符转换成相关编码的工具软件 { 0xCD, 0xB7, }; code unsigned char Zf816_V[]="V";     //对于数组内的字符,编译会自动翻译成 ASCII码(1字节) code unsigned char ASCII816_V[]= //ASCII码  V  网上有很多把汉字或者字符转换成相关编码的工具软件 { 0x56, }; code unsigned char Zf816_5[]="5";     //对于数组内的字符,编译会自动翻译成 ASCII码(1字节) code unsigned char ASCII816_5[]= //ASCII码  5  网上有很多把汉字或者字符转换成相关编码的工具软件 { 0x35, }; code unsigned char Zf816_nc[]=" ";     //对于数组内的字符,编译会自动翻译成 ASCII码(1字节) code unsigned char ASCII816_nc[]= //ASCII码  空字符  网上有很多把汉字或者字符转换成相关编码的工具软件 { 0x20, }; void main()   {         LCDInit(); //初始化12864 内部包含液晶模块的复位 /* 注释三: * 12864的控制芯片st7920内部有两套驱动显示指令方式,一种是前面章节讲的自构字库模式,也是图像模式。 * 另外一种就是本节讲的用内部字库模式。以下是切换模式的命令,命令字0x0c表示用内部字库模式。 * 命令字0x36表示用自构字库模式。 */         WriteCommand(0x0C); //命令字0x0c表示用内部字库模式。命令字0x36表示用自构字库模式。         display_clear(); // 清屏。4行8列的坐标点全部显示2个空字符相当于清屏了。         display_hz1616(0,0,Hz1616_man);  //第一行,调用直接汉字书写方式的数组来显示(馒头V5),         display_hz1616(1,0,Hz1616_tou);         display_double_zf816(2,0,Zf816_V,Zf816_5);         display_hz1616(0,3,JN1616_man);  //第四行,调用机内码和ASCII码的数组来显示(馒头V5),         display_hz1616(1,3,JN1616_tou);         display_double_zf816(2,3,ASCII816_V,Zf816_5);         while(1)           {            ;         } } /* 注释四:在一个坐标点显示1个内部字库汉字的函数 * 第1,2个参数x,y是坐标体系。x的范围是0至8,y的范围是0至3. * 第3个参数*ucArray是汉字机内码,是有2个字节的数组。 */ void display_hz1616(unsigned int x,unsigned int y,const unsigned char  *ucArray) {     WriteCommand(0x30);   //基本指令集         WriteCommand(ucAddrTable[8*y+x]);        //起始位置         LCDWriteData(ucArray[0]);         LCDWriteData(ucArray[1]); } /* 注释五:在一个坐标点显示2个内部字库字符的函数 * 注意,由于一个坐标点是16x16点阵,而一个字符是8x16点阵的,所以务必要显示2个字符筹够1个坐标点。 * 第1,2个参数x,y是坐标体系。x的范围是0至8,y的范围是0至3. * 第3个参数*ucArray1是左边第1个字符ASCII码,是有1个字节的数组。 * 第4个参数*ucArray2是右边第2个字符ASCII码,是有1个字节的数组。 */ void display_double_zf816(unsigned int x,unsigned int y,const unsigned char *ucArray1,const unsigned char  *ucArray2) {     WriteCommand(0x30);   //基本指令集         WriteCommand(ucAddrTable[8*y+x]);        //起始位置         LCDWriteData(ucArray1[0]);         LCDWriteData(ucArray2[0]); } void display_clear(void) // 清屏。4行8列的坐标点全部显示2个空字符相当于清屏了。 {        unsigned int i,j;         for(i=0;i
    6. 第七十九节:通过主菜单移动光标来进入子菜单窗口的液晶屏程序。 开场白:     其实主菜单窗口与子菜单窗口本质都是多窗口菜单程序,只不过我在按键服务程序里面建立起来了一条主窗口与子窗口的关系链。这个关系链还是用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
    7. 第七十八节:在多个窗口里通过移动光标来设置不同参数的液晶屏菜单程序。 开场白: 上一节讲了1个窗口下如何设置参数的菜单程序,这一节多增加1个窗口变成2个窗口,看看它们两个窗口之间是如何通过按键程序进行切换的。继续巩固上一节教给大家的两个知识点:    第一个知识点:我在前面讲数码管显示的时候就提出了一个 “一二级菜单显示理论”:凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。把每一个窗口的内容分为两种类型,一种类型是那些不用经常刷新显示的内容,只有在切换窗口的时候才需要更新的,这种内容放在整屏更新显示的括号里,比如清屏操作等内容。另外一种是那些经常需要刷新显示的内容,这种内容放在局部更新显示的括号里。     第二个知识点:按键如何跟液晶屏显示有机的结合起来?只要遵循鸿哥总结出来的一个规律“在不同的窗口下,根据不同的局部变量来操作不同的参数”,这样再复杂的人机交互程序都会显得很简单清晰。 具体内容,请看源代码讲解。 (1)硬件平台:基于朱兆祺51单片机学习板。加按键对应S1键,减按键对应S5键,切换“光标”移动按键对应S9键,设置参数按键对应S13键。 (2)实现功能:      通过按键设置8个不同的参数。     有2个窗口。每个窗口显示4个参数。每个参数的范围是从0到99。     有4个按键: (a)        一个是设置参数S13按键,按下此按键,液晶屏的第1个窗口第一行会出现反显的光标,表示进入设置参数模式,再次按下此按键,反显光标会消失,并且强行切换到第1个窗口,表示退出设置参数模式。 (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显示的内容 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_yi[]= { /*--  文字:  一  --*/ /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x7F,0xFE, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, }; code unsigned char Hz1616_er[]= { /*--  文字:  二  --*/ /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/ 0x00,0x00,0x00,0x10,0x3F,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x7F,0xFE,0x00,0x00,0x00,0x00,0x00,0x00, }; code unsigned char Hz1616_san[]= { /*--  文字:  三  --*/ /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/ 0x00,0x00,0x00,0x00,0x7F,0xFC,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3F,0xF8, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7F,0xFE,0x00,0x00,0x00,0x00, }; code unsigned char Hz1616_si[]= { /*--  文字:  四  --*/ /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/ 0x00,0x00,0x7F,0xFC,0x44,0x84,0x44,0x84,0x44,0x84,0x44,0x84,0x44,0x84,0x44,0x84, 0x48,0x84,0x48,0x7C,0x50,0x04,0x60,0x04,0x40,0x04,0x7F,0xFC,0x40,0x04,0x00,0x00, }; code unsigned char Hz1616_chuang[]= { /*--  文字:  窗  --*/ /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/ 0x01,0x00,0x00,0x80,0x7F,0xFE,0x40,0x22,0x09,0x18,0x12,0x06,0x7F,0xF8,0x11,0x08, 0x13,0xE8,0x14,0x48,0x1A,0x88,0x11,0x08,0x12,0x88,0x14,0x08,0x1F,0xF8,0x10,0x08, }; code unsigned char Hz1616_kou[]= { /*--  文字:  口  --*/ /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/ 0x00,0x00,0x00,0x00,0x3F,0xF8,0x20,0x08,0x20,0x08,0x20,0x08,0x20,0x08,0x20,0x08, 0x20,0x08,0x20,0x08,0x20,0x08,0x3F,0xF8,0x20,0x08,0x20,0x08,0x00,0x00,0x00,0x00, }; code unsigned char Hz1616_hang[]= { /*--  文字:  行  --*/ /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/ 0x08,0x00,0x1C,0x00,0x31,0xFC,0x40,0x00,0x88,0x00,0x0C,0x00,0x1B,0xFE,0x30,0x20, 0x50,0x20,0x90,0x20,0x10,0x20,0x10,0x20,0x10,0x20,0x10,0x20,0x10,0xA0,0x10,0x40, }; 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 ucPart=0; //局部变量 0代表没有选中任何一行,其它数值1到4代表选中某一行 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 ucWd1Part4Update=0; //窗口1的第4行局部更新显示变量  1代表更新显示,响应函数内部会清零 unsigned char ucWd2Update=0; //窗口2的整屏更新显示变量      1代表更新显示,响应函数内部会清零 unsigned char ucWd2Part1Update=0; //窗口2的第1行局部更新显示变量  1代表更新显示,响应函数内部会清零 unsigned char ucWd2Part2Update=0; //窗口2的第2行局部更新显示变量  1代表更新显示,响应函数内部会清零 unsigned char ucWd2Part3Update=0; //窗口2的第3行局部更新显示变量  1代表更新显示,响应函数内部会清零 unsigned char ucWd2Part4Update=0; //窗口2的第4行局部更新显示变量  1代表更新显示,响应函数内部会清零 unsigned char ucData_1_1=8;  //第1个窗口第1行的被设置数据 unsigned char ucData_1_2=9;  //第1个窗口第2行的被设置数据 unsigned char ucData_1_3=10;  //第1个窗口第3行的被设置数据 unsigned char ucData_1_4=11;  //第1个窗口第4行的被设置数据 unsigned char ucData_2_1=12;  //第2个窗口第1行的被设置数据 unsigned char ucData_2_2=13;  //第2个窗口第2行的被设置数据 unsigned char ucData_2_3=14;  //第2个窗口第3行的被设置数据 unsigned char ucData_2_4=15;  //第2个窗口第4行的被设置数据 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 1:  //窗口1                    switch(ucPart)  //在窗口1下,根据不同的局部变量来设置不同的参数                    {                           case 0:   //无光标显示的状态 此处的case 0可以省略                                 break;                           case 1:   //设置第1行参数                                 ucData_1_1++;                                 if(ucData_1_1>99)                                 {                                    ucData_1_1=99;                                 }                                 ucWd1Part1Update=1; //1代表更新显示,响应函数内部会清零                                 break;                           case 2:   //设置第2行参数                                 ucData_1_2++;                                 if(ucData_1_2>99)                                 {                                    ucData_1_2=99;                                 }                                 ucWd1Part2Update=1; //1代表更新显示,响应函数内部会清零                                 break;                           case 3:   //设置第3行参数                                 ucData_1_3++;                                 if(ucData_1_3>99)                                 {                                    ucData_1_3=99;                                 }                                 ucWd1Part3Update=1; //1代表更新显示,响应函数内部会清零                                 break;                           case 4:   //设置第4行参数                                 ucData_1_4++;                                 if(ucData_1_4>99)                                 {                                    ucData_1_4=99;                                 }                                 ucWd1Part4Update=1; //1代表更新显示,响应函数内部会清零                                 break;                    }                    break;               case 2:  //窗口2                    switch(ucPart)  //在窗口2下,根据不同的局部变量来设置不同的参数                    {                           case 0:   //无光标显示的状态 此处的case 0可以省略                                 break;                           case 1:   //设置第1行参数                                 ucData_2_1++;                                 if(ucData_2_1>99)                                 {                                    ucData_2_1=99;                                 }                                 ucWd2Part1Update=1; //1代表更新显示,响应函数内部会清零                                 break;                           case 2:   //设置第2行参数                                 ucData_2_2++;                                 if(ucData_2_2>99)                                 {                                    ucData_2_2=99;                                 }                                 ucWd2Part2Update=1; //1代表更新显示,响应函数内部会清零                                 break;                           case 3:   //设置第3行参数                                 ucData_2_3++;                                 if(ucData_2_3>99)                                 {                                    ucData_2_3=99;                                 }                                 ucWd2Part3Update=1; //1代表更新显示,响应函数内部会清零                                 break;                           case 4:   //设置第4行参数                                 ucData_2_4++;                                 if(ucData_2_4>99)                                 {                                    ucData_2_4=99;                                 }                                 ucWd2Part4Update=1; //1代表更新显示,响应函数内部会清零                                 break;                    }                    break;                    }                uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发           break;            case 2:// 减按键 对应朱兆祺学习板的S5键           switch(ucWd)  //在不同的窗口下,设置不同的参数           {               case 1:  //窗口1                    switch(ucPart)  //在窗口1下,根据不同的局部变量来设置不同的参数                    {                           case 0:   //无光标显示的状态 此处的case 0可以省略                                 break;                           case 1:   //设置第1行参数                                 ucData_1_1--;                                 if(ucData_1_1>99) //一直减到最后,单片机C语言编译器有一个特征,0减去1会溢出变成255(0xff)                                 {                                    ucData_1_1=0;                                 }                                 ucWd1Part1Update=1; //1代表更新显示,响应函数内部会清零                                 break;                           case 2:   //设置第2行参数                                 ucData_1_2--;                                 if(ucData_1_2>99) //一直减到最后,单片机C语言编译器有一个特征,0减去1会溢出变成255(0xff)                                 {                                    ucData_1_2=0;                                 }                                 ucWd1Part2Update=1; //1代表更新显示,响应函数内部会清零                                 break;                           case 3:   //设置第3行参数                                 ucData_1_3--;                                 if(ucData_1_3>99) //一直减到最后,单片机C语言编译器有一个特征,0减去1会溢出变成255(0xff)                                 {                                    ucData_1_3=0;                                 }                                 ucWd1Part3Update=1; //1代表更新显示,响应函数内部会清零                                 break;                           case 4:   //设置第4行参数                                 ucData_1_4--;                                 if(ucData_1_4>99) //一直减到最后,单片机C语言编译器有一个特征,0减去1会溢出变成255(0xff)                                 {                                    ucData_1_4=0;                                 }                                 ucWd1Part4Update=1; //1代表更新显示,响应函数内部会清零                                 break;                    }                    break;               case 2:  //窗口2                    switch(ucPart)  //在窗口2下,根据不同的局部变量来设置不同的参数                    {                           case 0:   //无光标显示的状态 此处的case 0可以省略                                 break;                           case 1:   //设置第1行参数                                 ucData_2_1--;                                 if(ucData_2_1>99) //一直减到最后,单片机C语言编译器有一个特征,0减去1会溢出变成255(0xff)                                 {                                    ucData_2_1=0;                                 }                                 ucWd2Part1Update=1; //1代表更新显示,响应函数内部会清零                                 break;                           case 2:   //设置第2行参数                                 ucData_2_2--;                                 if(ucData_2_2>99) //一直减到最后,单片机C语言编译器有一个特征,0减去1会溢出变成255(0xff)                                 {                                    ucData_2_2=0;                                 }                                 ucWd2Part2Update=1; //1代表更新显示,响应函数内部会清零                                 break;                           case 3:   //设置第3行参数                                 ucData_2_3--;                                 if(ucData_2_3>99) //一直减到最后,单片机C语言编译器有一个特征,0减去1会溢出变成255(0xff)                                 {                                    ucData_2_3=0;                                 }                                 ucWd2Part3Update=1; //1代表更新显示,响应函数内部会清零                                 break;                           case 4:   //设置第4行参数                                 ucData_2_4--;                                 if(ucData_2_4>99) //一直减到最后,单片机C语言编译器有一个特征,0减去1会溢出变成255(0xff)                                 {                                    ucData_2_4=0;                                 }                                 ucWd2Part4Update=1; //1代表更新显示,响应函数内部会清零                                 break;                    }                    break;                    }                uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发           break;       case 3:// 切换"光标"移动按键 对应朱兆祺学习板的S9键           switch(ucWd)  //在不同的窗口下,设置不同的参数           {               case 1: //窗口1                    switch(ucPart)  //在窗口1下,根据不同的局部变量来设置不同的参数                    {                           case 0:   //无光标显示的状态 此处的case 0可以省略                                 break;                           case 1:   //设置第1行参数                                 ucPart=2; //光标切换到下一行                                 ucWd1Part1Update=1; //更新显示原来那一行,目的是更新反显光标的状态                                 ucWd1Part2Update=1; //更新显示下一行,    目的是更新反显光标的状态                                 break;                           case 2:   //设置第2行参数                                 ucPart=3; //光标切换到下一行                                 ucWd1Part2Update=1; //更新显示原来那一行,目的是更新反显光标的状态                                 ucWd1Part3Update=1; //更新显示下一行,    目的是更新反显光标的状态                                 break;                           case 3:   //设置第3行参数                                 ucPart=4; //光标切换到下一行                                 ucWd1Part3Update=1; //更新显示原来那一行,目的是更新反显光标的状态                                 ucWd1Part4Update=1; //更新显示下一行,    目的是更新反显光标的状态                                 break;                           case 4:   //设置第4行参数                                                         ucWd=2;  //切换到第2个窗口                                 ucPart=1; //光标返回到最上面第一行                                                                 ucWd2Update=1; //窗口2整屏更新                                 break;                    }                    break;               case 2: //窗口2                    switch(ucPart)  //在窗口2下,根据不同的局部变量来设置不同的参数                    {                           case 0:   //无光标显示的状态 此处的case 0可以省略                                 break;                           case 1:   //设置第1行参数                                 ucPart=2; //光标切换到下一行                                 ucWd2Part1Update=1; //更新显示原来那一行,目的是更新反显光标的状态                                 ucWd2Part2Update=1; //更新显示下一行,    目的是更新反显光标的状态                                 break;                           case 2:   //设置第2行参数                                 ucPart=3; //光标切换到下一行                                 ucWd2Part2Update=1; //更新显示原来那一行,目的是更新反显光标的状态                                 ucWd2Part3Update=1; //更新显示下一行,    目的是更新反显光标的状态                                 break;                           case 3:   //设置第3行参数                                 ucPart=4; //光标切换到下一行                                 ucWd2Part3Update=1; //更新显示原来那一行,目的是更新反显光标的状态                                 ucWd2Part4Update=1; //更新显示下一行,    目的是更新反显光标的状态                                 break;                           case 4:   //设置第4行参数                                                         ucWd=1;  //切换到第1个窗口                                 ucPart=1; //光标返回到最上面第一行                                                                 ucWd1Update=1; //窗口1整屏更新                                 break;                    }                    break;                    }                             uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发           break;               case 4: // 设置按键  对应朱兆祺学习板的S13键,按一次进入设置状态,出现反显光标。再按一次推出设置状态,消除反显光标,并且强行切换到第1个窗口           switch(ucWd)  //在不同的窗口下,设置不同的参数           {               case 1:  //窗口1                    switch(ucPart)  //在窗口1下,根据不同的局部变量来设置不同的参数                    {                           case 0:   //无光标显示的状态                                 ucPart=1; //光标显示第一行,进入设置模式                                 ucWd1Part1Update=1; //更新显示                                 break;                           case 1:   //设置第1行参数                                 ucPart=0; //无光标显示,退出设置模式                                 ucWd1Part1Update=1; //更新显示                                 break;                           case 2:   //设置第2行参数                                 ucPart=0; //无光标显示,退出设置模式                                 ucWd1Part2Update=1; //更新显示                                 break;                           case 3:   //设置第3行参数                                 ucPart=0; //无光标显示,退出设置模式                                 ucWd1Part3Update=1; //更新显示                                 break;                           case 4:   //设置第4行参数                                 ucPart=0; //无光标显示,退出设置模式                                 ucWd1Part4Update=1; //更新显示                                 break;                    }                    break;               case 2:  //窗口2                    switch(ucPart)  //在窗口2下,根据不同的局部变量来设置不同的参数                    {                           case 0:   //无光标显示的状态                                                         ucWd=1; //强行切换到第1个窗口                                 ucPart=1; //光标显示第一行,进入设置模式                                                                 ucWd1Update=1; //窗口1整屏更新                                 break;                           case 1:   //设置第1行参数                                                                   ucWd=1; //强行切换到第1个窗口                                 ucPart=0; //无光标显示,退出设置模式                                                                 ucWd1Update=1; //窗口1整屏更新                                 break;                           case 2:   //设置第2行参数                                                                   ucWd=1; //强行切换到第1个窗口                                 ucPart=0; //无光标显示,退出设置模式                                                                 ucWd1Update=1; //窗口1整屏更新                                 break;                           case 3:   //设置第3行参数                                                               ucWd=1; //强行切换到第1个窗口                                 ucPart=0; //无光标显示,退出设置模式                                                                 ucWd1Update=1; //窗口1整屏更新                                 break;                           case 4:   //设置第4行参数                                                                   ucWd=1; //强行切换到第1个窗口                                 ucPart=0; //无光标显示,退出设置模式                                                                 ucWd1Update=1; //窗口1整屏更新                                 break;                    }                    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();  //窗口1显示的内容               break;         case 2:                 wd2();  //窗口2显示的内容               break;         //本程序只有2个窗口,所以只有一个case 1,如果要增加窗口,就直接增加 case 2, case 3...             } } void wd1(void)  //窗口1显示的内容 {     unsigned char ucAnyNumber_1; //分解变量的个位     unsigned char ucAnyNumber_10; //分解变量的十位     unsigned char *p_ucAnyNumber_1; //经过数字转换成字模后,分解变量的个位字模首地址     unsigned char *p_ucAnyNumber_10; //经过数字转换成字模后,分解变量的十位字模首地址     unsigned char ucCursorFlag;  //光标标志,也就是反显的标志,它是根据局部变量ucPart来定的 /* 注释三: * 把每一个窗口的内容分为两种类型,一种类型是那些不用经常刷新显示的内容,只有在切换窗口的时候 * 才需要更新,这种内容放在整屏更新显示的括号里,比如清屏操作等内容。另外一种是那些经常需要 * 刷新显示的内容,这种内容放在局部更新显示的括号里。 */     if(ucWd1Update==1)  //窗口1整屏更新,里面只放那些不用经常刷新显示的内容     {         ucWd1Update=0;  //及时清零,避免一直更新         ucWd1Part1Update=1; //激活窗口1的第1行局部更新显示变量,这里在前面数码管显示框架上有所改进         ucWd1Part2Update=1; //激活窗口1的第2行局部更新显示变量,这里在前面数码管显示框架上有所改进         ucWd1Part3Update=1; //激活窗口1的第3行局部更新显示变量,这里在前面数码管显示框架上有所改进         ucWd1Part4Update=1; //激活窗口1的第4行局部更新显示变量,这里在前面数码管显示框架上有所改进         display_clear(0x00); // 清屏操作, 全部显示空填充0x00,全部显示点阵用0xff。         clear_all_canvas();  //把画布全部清零         insert_buffer_to_canvas(0,0,Zf816_mao_hao,0,1,16);//把冒号的字模插入画布         display_lattice(0,0,Hz1616_yi,0,2,16,0);    //一窗口一行,这些内容不用经常更新,只有在切换窗口的时候才更新显示         display_lattice(1,0,Hz1616_chuang,0,2,16,0);            display_lattice(2,0,Hz1616_kou,0,2,16,0);            display_lattice(3,0,Hz1616_yi,0,2,16,0);         display_lattice(4,0,Hz1616_hang,0,2,16,0);         display_lattice(0,16,Hz1616_yi,0,2,16,0);    //一窗口二行         display_lattice(1,16,Hz1616_chuang,0,2,16,0);            display_lattice(2,16,Hz1616_kou,0,2,16,0);            display_lattice(3,16,Hz1616_er,0,2,16,0);         display_lattice(4,16,Hz1616_hang,0,2,16,0);         display_lattice(8,0,Hz1616_yi,0,2,16,0);    //一窗口三行         display_lattice(9,0,Hz1616_chuang,0,2,16,0);            display_lattice(10,0,Hz1616_kou,0,2,16,0);            display_lattice(11,0,Hz1616_san,0,2,16,0);         display_lattice(12,0,Hz1616_hang,0,2,16,0);         display_lattice(8,16,Hz1616_yi,0,2,16,0);    //一窗口四行         display_lattice(9,16,Hz1616_chuang,0,2,16,0);            display_lattice(10,16,Hz1616_kou,0,2,16,0);            display_lattice(11,16,Hz1616_si,0,2,16,0);         display_lattice(12,16,Hz1616_hang,0,2,16,0);     } /* 注释四: * 注意!我前面讲数码管显示的时候有一句话讲错了,我那时说,这是不对的。 * 按照现在的显示程序框架,应该是 * 这样才对。 */     if(ucWd1Part1Update==1) //窗口1的第1行局部更新显示变量,里面放一些经常需要刷新显示的内容     {         ucWd1Part1Update=0; //及时清零,避免一直更新         if(ucPart==1) //被选中         {              ucCursorFlag=1; //反显 显示         }         else //没被选中         {              ucCursorFlag=0; //正常 显示         }         if(ucData_1_1>=10) //有2位数以上         {              ucAnyNumber_10=ucData_1_1/10;  //十位         }         else //否则显示空         {              ucAnyNumber_10=10;  //在下面的转换函数中,代码10表示空字模         }         ucAnyNumber_1=ucData_1_1%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(5,0,ucCanvasBuffer,0,4,16,0);   //显示整屏的画布,最后的参数0是偏移量                                }     if(ucWd1Part2Update==1) //窗口1的第2行局部更新显示变量,里面放一些经常需要刷新显示的内容     {          ucWd1Part2Update=0; //及时清零,避免一直更新          if(ucPart==2) //被选中          {              ucCursorFlag=1; //反显 显示          }          else //没被选中          {              ucCursorFlag=0; //正常 显示          }          if(ucData_1_2>=10) //有2位数以上          {              ucAnyNumber_10=ucData_1_2/10;  //十位          }          else //否则显示空          {              ucAnyNumber_10=10;  //在下面的转换函数中,代码10表示空字模          }          ucAnyNumber_1=ucData_1_2%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(5,16,ucCanvasBuffer,0,4,16,0);   //显示整屏的画布,最后的参数0是偏移量                                 }      if(ucWd1Part3Update==1) //窗口1的第3行局部更新显示变量,里面放一些经常需要刷新显示的内容      {          ucWd1Part3Update=0; //及时清零,避免一直更新          if(ucPart==3) //被选中          {              ucCursorFlag=1; //反显 显示          }          else //没被选中          {              ucCursorFlag=0; //正常 显示          }          if(ucData_1_3>=10) //有2位数以上          {              ucAnyNumber_10=ucData_1_3/10;  //十位          }          else //否则显示空          {              ucAnyNumber_10=10;  //在下面的转换函数中,代码10表示空字模          }          ucAnyNumber_1=ucData_1_3%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(13,0,ucCanvasBuffer,0,4,16,0);   //显示整屏的画布,最后的参数0是偏移量                                 }      if(ucWd1Part4Update==1) //窗口1的第4行局部更新显示变量,里面放一些经常需要刷新显示的内容      {          ucWd1Part4Update=0; //及时清零,避免一直更新          if(ucPart==4) //被选中          {              ucCursorFlag=1; //反显 显示          }          else //没被选中          {              ucCursorFlag=0; //正常 显示          }          if(ucData_1_4>=10) //有2位数以上          {              ucAnyNumber_10=ucData_1_4/10;  //十位          }          else //否则显示空          {              ucAnyNumber_10=10;  //在下面的转换函数中,代码10表示空字模          }          ucAnyNumber_1=ucData_1_4%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(13,16,ucCanvasBuffer,0,4,16,0);   //显示整屏的画布,最后的参数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行局部更新显示变量,这里在前面数码管显示框架上有所改进         ucWd2Part3Update=1; //激活窗口2的第3行局部更新显示变量,这里在前面数码管显示框架上有所改进         ucWd2Part4Update=1; //激活窗口2的第4行局部更新显示变量,这里在前面数码管显示框架上有所改进         display_clear(0x00); // 清屏操作, 全部显示空填充0x00,全部显示点阵用0xff。         clear_all_canvas();  //把画布全部清零         insert_buffer_to_canvas(0,0,Zf816_mao_hao,0,1,16);//把冒号的字模插入画布         display_lattice(0,0,Hz1616_er,0,2,16,0);    //二窗口一行,这些内容不用经常更新,只有在切换窗口的时候才更新显示         display_lattice(1,0,Hz1616_chuang,0,2,16,0);            display_lattice(2,0,Hz1616_kou,0,2,16,0);            display_lattice(3,0,Hz1616_yi,0,2,16,0);         display_lattice(4,0,Hz1616_hang,0,2,16,0);         display_lattice(0,16,Hz1616_er,0,2,16,0);    //二窗口二行         display_lattice(1,16,Hz1616_chuang,0,2,16,0);            display_lattice(2,16,Hz1616_kou,0,2,16,0);            display_lattice(3,16,Hz1616_er,0,2,16,0);         display_lattice(4,16,Hz1616_hang,0,2,16,0);         display_lattice(8,0,Hz1616_er,0,2,16,0);    //二窗口三行         display_lattice(9,0,Hz1616_chuang,0,2,16,0);            display_lattice(10,0,Hz1616_kou,0,2,16,0);            display_lattice(11,0,Hz1616_san,0,2,16,0);         display_lattice(12,0,Hz1616_hang,0,2,16,0);         display_lattice(8,16,Hz1616_er,0,2,16,0);    //二窗口四行         display_lattice(9,16,Hz1616_chuang,0,2,16,0);            display_lattice(10,16,Hz1616_kou,0,2,16,0);            display_lattice(11,16,Hz1616_si,0,2,16,0);         display_lattice(12,16,Hz1616_hang,0,2,16,0);     }     if(ucWd2Part1Update==1) //窗口2的第1行局部更新显示变量,里面放一些经常需要刷新显示的内容     {         ucWd2Part1Update=0; //及时清零,避免一直更新         if(ucPart==1) //被选中         {              ucCursorFlag=1; //反显 显示         }         else //没被选中         {              ucCursorFlag=0; //正常 显示         }         if(ucData_2_1>=10) //有2位数以上         {              ucAnyNumber_10=ucData_2_1/10;  //十位         }         else //否则显示空         {              ucAnyNumber_10=10;  //在下面的转换函数中,代码10表示空字模         }         ucAnyNumber_1=ucData_2_1%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(5,0,ucCanvasBuffer,0,4,16,0);   //显示整屏的画布,最后的参数0是偏移量                                }     if(ucWd2Part2Update==1) //窗口2的第2行局部更新显示变量,里面放一些经常需要刷新显示的内容     {          ucWd2Part2Update=0; //及时清零,避免一直更新          if(ucPart==2) //被选中          {              ucCursorFlag=1; //反显 显示          }          else //没被选中          {              ucCursorFlag=0; //正常 显示          }          if(ucData_2_2>=10) //有2位数以上          {              ucAnyNumber_10=ucData_2_2/10;  //十位          }          else //否则显示空          {              ucAnyNumber_10=10;  //在下面的转换函数中,代码10表示空字模          }          ucAnyNumber_1=ucData_2_2%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(5,16,ucCanvasBuffer,0,4,16,0);   //显示整屏的画布,最后的参数0是偏移量                                 }      if(ucWd2Part3Update==1) //窗口2的第3行局部更新显示变量,里面放一些经常需要刷新显示的内容      {          ucWd2Part3Update=0; //及时清零,避免一直更新          if(ucPart==3) //被选中          {              ucCursorFlag=1; //反显 显示          }          else //没被选中          {              ucCursorFlag=0; //正常 显示          }          if(ucData_2_3>=10) //有2位数以上          {              ucAnyNumber_10=ucData_2_3/10;  //十位          }          else //否则显示空          {              ucAnyNumber_10=10;  //在下面的转换函数中,代码10表示空字模          }          ucAnyNumber_1=ucData_2_3%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(13,0,ucCanvasBuffer,0,4,16,0);   //显示整屏的画布,最后的参数0是偏移量                                 }      if(ucWd2Part4Update==1) //窗口2的第4行局部更新显示变量,里面放一些经常需要刷新显示的内容      {          ucWd2Part4Update=0; //及时清零,避免一直更新          if(ucPart==4) //被选中          {              ucCursorFlag=1; //反显 显示          }          else //没被选中          {              ucCursorFlag=0; //正常 显示          }          if(ucData_2_4>=10) //有2位数以上          {              ucAnyNumber_10=ucData_2_4/10;  //十位          }          else //否则显示空          {              ucAnyNumber_10=10;  //在下面的转换函数中,代码10表示空字模          }          ucAnyNumber_1=ucData_2_4%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(13,16,ucCanvasBuffer,0,4,16,0);   //显示整屏的画布,最后的参数0是偏移量                                } } void clear_all_canvas(void)  //把画布全部清零 {    unsigned int j=0;    unsigned int i=0;    for(j=0;j
    8. 第七十七节:在1个窗口里通过移动光标来设置不同参数的液晶屏菜单程序。 开场白:     这一节要教会大家两个知识点: 第一个知识点:我在前面讲数码管显示的时候就提出了一个 “一二级菜单显示理论”:凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。把每一个窗口的内容分为两种类型,一种类型是那些不用经常刷新显示的内容,只有在切换窗口的时候才需要更新的,这种内容放在整屏更新显示的括号里,比如清屏操作等内容。另外一种是那些经常需要刷新显示的内容,这种内容放在局部更新显示的括号里。 第二个知识点:按键如何跟液晶屏显示有机的结合起来?只要遵循鸿哥总结出来的一个规律“在不同的窗口下,根据不同的局部变量来操作不同的参数”,这样再复杂的人机交互程序都会显得很简单清晰。 具体内容,请看源代码讲解。 (1)硬件平台:基于朱兆祺51单片机学习板。加按键对应S1键,减按键对应S5键,切换“光标”移动按键对应S9键,设置参数按键对应S13键。 (2)实现功能:      通过按键设置4个不同的参数。     有1个窗口。每个窗口显示4个参数。每个参数的范围是从0到99。    有4个按键: (a)        一个是设置参数S13按键,按下此按键,液晶屏的第一行会出现反显的光标,表示进入设置参数模式,再次按下此按键,反显光标会消失,表示退出设置参数模式。 (b)        一个是移动光标S9按键,在进入设置参数的模式下,依次按下此按键,液晶屏上的光标会从上往下移动,表示选中不同的参数。 (c)        一个是减数S5按键,在设置参数模式下,依次按下此按键,被选中的参数会逐渐减小。 (d)        一个是加数S1按键,在设置参数模式下,依次按下此按键,被选中的参数会逐渐加大。 (3)源代码讲解如下: #include "REG52.H" #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);  //把画布全部清零 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_yi[]= { /*--  文字:  一  --*/ /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x7F,0xFE, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, }; code unsigned char Hz1616_er[]= { /*--  文字:  二  --*/ /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/ 0x00,0x00,0x00,0x10,0x3F,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x7F,0xFE,0x00,0x00,0x00,0x00,0x00,0x00, }; code unsigned char Hz1616_san[]= { /*--  文字:  三  --*/ /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/ 0x00,0x00,0x00,0x00,0x7F,0xFC,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3F,0xF8, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7F,0xFE,0x00,0x00,0x00,0x00, }; code unsigned char Hz1616_si[]= { /*--  文字:  四  --*/ /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/ 0x00,0x00,0x7F,0xFC,0x44,0x84,0x44,0x84,0x44,0x84,0x44,0x84,0x44,0x84,0x44,0x84, 0x48,0x84,0x48,0x7C,0x50,0x04,0x60,0x04,0x40,0x04,0x7F,0xFC,0x40,0x04,0x00,0x00, }; code unsigned char Hz1616_chuang[]= { /*--  文字:  窗  --*/ /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/ 0x01,0x00,0x00,0x80,0x7F,0xFE,0x40,0x22,0x09,0x18,0x12,0x06,0x7F,0xF8,0x11,0x08, 0x13,0xE8,0x14,0x48,0x1A,0x88,0x11,0x08,0x12,0x88,0x14,0x08,0x1F,0xF8,0x10,0x08, }; code unsigned char Hz1616_kou[]= { /*--  文字:  口  --*/ /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/ 0x00,0x00,0x00,0x00,0x3F,0xF8,0x20,0x08,0x20,0x08,0x20,0x08,0x20,0x08,0x20,0x08, 0x20,0x08,0x20,0x08,0x20,0x08,0x3F,0xF8,0x20,0x08,0x20,0x08,0x00,0x00,0x00,0x00, }; code unsigned char Hz1616_hang[]= { /*--  文字:  行  --*/ /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/ 0x08,0x00,0x1C,0x00,0x31,0xFC,0x40,0x00,0x88,0x00,0x0C,0x00,0x1B,0xFE,0x30,0x20, 0x50,0x20,0x90,0x20,0x10,0x20,0x10,0x20,0x10,0x20,0x10,0x20,0x10,0xA0,0x10,0x40, }; 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 ucPart=0; //局部变量 0代表没有选中任何一行,其它数值1到4代表选中某一行 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 ucWd1Part4Update=0; //窗口1的第4行局部更新显示变量  1代表更新显示,响应函数内部会清零 unsigned char ucData_1_1=8;  //第1个窗口第1行的被设置数据 unsigned char ucData_1_2=9;  //第1个窗口第2行的被设置数据 unsigned char ucData_1_3=10;  //第1个窗口第3行的被设置数据 unsigned char ucData_1_4=11;  //第1个窗口第4行的被设置数据 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 1:                    switch(ucPart)  //在窗口1下,根据不同的局部变量来设置不同的参数                    {                           case 0:   //无光标显示的状态 此处的case 0可以省略                                 break;                           case 1:   //设置第1行参数                                 ucData_1_1++;                                                                 if(ucData_1_1>99)                                                                 {                                                                    ucData_1_1=99;                                                                 }                                 ucWd1Part1Update=1; //1代表更新显示,响应函数内部会清零                                 break;                           case 2:   //设置第2行参数                                 ucData_1_2++;                                                                 if(ucData_1_2>99)                                                                 {                                                                    ucData_1_2=99;                                                                 }                                 ucWd1Part2Update=1; //1代表更新显示,响应函数内部会清零                                 break;                           case 3:   //设置第3行参数                                 ucData_1_3++;                                                                 if(ucData_1_3>99)                                                                 {                                                                    ucData_1_3=99;                                                                 }                                 ucWd1Part3Update=1; //1代表更新显示,响应函数内部会清零                                 break;                           case 4:   //设置第4行参数                                 ucData_1_4++;                                                                 if(ucData_1_4>99)                                                                 {                                                                    ucData_1_4=99;                                                                 }                                 ucWd1Part4Update=1; //1代表更新显示,响应函数内部会清零                                 break;                    }                    break;                     }                uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发           break;            case 2:// 减按键 对应朱兆祺学习板的S5键           switch(ucWd)  //在不同的窗口下,设置不同的参数           {               case 1:                    switch(ucPart)  //在窗口1下,根据不同的局部变量来设置不同的参数                    {                           case 0:   //无光标显示的状态 此处的case 0可以省略                                 break;                           case 1:   //设置第1行参数                                 ucData_1_1--;                                                                 if(ucData_1_1>99) //一直减到最后,单片机C语言编译器有一个特征,0减去1会溢出变成255(0xff)                                                                 {                                                                    ucData_1_1=0;                                                                 }                                 ucWd1Part1Update=1; //1代表更新显示,响应函数内部会清零                                 break;                           case 2:   //设置第2行参数                                 ucData_1_2--;                                                                 if(ucData_1_2>99) //一直减到最后,单片机C语言编译器有一个特征,0减去1会溢出变成255(0xff)                                                                 {                                                                    ucData_1_2=0;                                                                 }                                 ucWd1Part2Update=1; //1代表更新显示,响应函数内部会清零                                 break;                           case 3:   //设置第3行参数                                 ucData_1_3--;                                                                 if(ucData_1_3>99) //一直减到最后,单片机C语言编译器有一个特征,0减去1会溢出变成255(0xff)                                                                 {                                                                    ucData_1_3=0;                                                                 }                                 ucWd1Part3Update=1; //1代表更新显示,响应函数内部会清零                                 break;                           case 4:   //设置第4行参数                                 ucData_1_4--;                                                                 if(ucData_1_4>99) //一直减到最后,单片机C语言编译器有一个特征,0减去1会溢出变成255(0xff)                                                                 {                                                                    ucData_1_4=0;                                                                 }                                 ucWd1Part4Update=1; //1代表更新显示,响应函数内部会清零                                 break;                    }                    break;                     }                uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发           break;       case 3:// 切换"光标"移动按键 对应朱兆祺学习板的S9键           switch(ucWd)  //在不同的窗口下,设置不同的参数           {               case 1:                    switch(ucPart)  //在窗口1下,根据不同的局部变量来设置不同的参数                    {                           case 0:   //无光标显示的状态 此处的case 0可以省略                                 break;                           case 1:   //设置第1行参数                                 ucPart=2; //光标切换到下一行                                 ucWd1Part1Update=1; //更新显示原来那一行,目的是更新反显光标的状态                                 ucWd1Part2Update=1; //更新显示下一行,    目的是更新反显光标的状态                                 break;                           case 2:   //设置第2行参数                                 ucPart=3; //光标切换到下一行                                 ucWd1Part2Update=1; //更新显示原来那一行,目的是更新反显光标的状态                                 ucWd1Part3Update=1; //更新显示下一行,    目的是更新反显光标的状态                                 break;                           case 3:   //设置第3行参数                                 ucPart=4; //光标切换到下一行                                 ucWd1Part3Update=1; //更新显示原来那一行,目的是更新反显光标的状态                                 ucWd1Part4Update=1; //更新显示下一行,    目的是更新反显光标的状态                                 break;                           case 4:   //设置第4行参数                                 ucPart=1; //光标返回到最上面第一行                                 ucWd1Part4Update=1; //更新显示原来那一行,目的是更新反显光标的状态                                 ucWd1Part1Update=1; //更新显示最上面第一行,    目的是更新反显光标的状态                                 break;                    }                    break;                     }                             uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发           break;               case 4: // 设置按键  对应朱兆祺学习板的S13键,按一次进入设置状态,出现反显光标。再按一次推出设置状态,消除反显光标           switch(ucWd)  //在不同的窗口下,设置不同的参数           {               case 1:                    switch(ucPart)  //在窗口1下,根据不同的局部变量来设置不同的参数                    {                           case 0:   //无光标显示的状态                                 ucPart=1; //光标显示第一行,进入设置模式                                 ucWd1Part1Update=1; //更新显示                                 break;                           case 1:   //设置第1行参数                                 ucPart=0; //无光标显示,退出设置模式                                 ucWd1Part1Update=1; //更新显示                                 break;                           case 2:   //设置第2行参数                                 ucPart=0; //无光标显示,退出设置模式                                 ucWd1Part2Update=1; //更新显示                                 break;                           case 3:   //设置第3行参数                                 ucPart=0; //无光标显示,退出设置模式                                 ucWd1Part3Update=1; //更新显示                                 break;                           case 4:   //设置第4行参数                                 ucPart=0; //无光标显示,退出设置模式                                 ucWd1Part4Update=1; //更新显示                                 break;                    }                    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) //应用层面的液晶屏显示程序 {     unsigned char ucAnyNumber_1; //分解变量的个位     unsigned char ucAnyNumber_10; //分解变量的十位     unsigned char *p_ucAnyNumber_1; //经过数字转换成字模后,分解变量的个位字模首地址     unsigned char *p_ucAnyNumber_10; //经过数字转换成字模后,分解变量的十位字模首地址         unsigned char ucCursorFlag;  //光标标志,也就是反显的标志,它是根据局部变量ucPart来定的     switch(ucWd)  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。     {         case 1:   //显示窗口1的数据 /* 注释二: * 把每一个窗口的内容分为两种类型,一种类型是那些不用经常刷新显示的内容,只有在切换窗口的时候 * 才需要更新,这种内容放在整屏更新显示的括号里,比如清屏操作等内容。另外一种是那些经常需要 * 刷新显示的内容,这种内容放在局部更新显示的括号里。 */                       if(ucWd1Update==1)  //窗口1整屏更新,里面只放那些不用经常刷新显示的内容                           {                              ucWd1Update=0;  //及时清零,避免一直更新                  ucWd1Part1Update=1; //激活窗口1的第1行局部更新显示变量,这里在前面数码管显示框架上有所改进                  ucWd1Part2Update=1; //激活窗口1的第2行局部更新显示变量,这里在前面数码管显示框架上有所改进                  ucWd1Part3Update=1; //激活窗口1的第3行局部更新显示变量,这里在前面数码管显示框架上有所改进                  ucWd1Part4Update=1; //激活窗口1的第4行局部更新显示变量,这里在前面数码管显示框架上有所改进                  display_clear(0x00); // 清屏操作, 全部显示空填充0x00,全部显示点阵用0xff。                  clear_all_canvas();  //把画布全部清零                  insert_buffer_to_canvas(0,0,Zf816_mao_hao,0,1,16);//把冒号的字模插入画布                  display_lattice(0,0,Hz1616_yi,0,2,16,0);    //一窗口一行,这些内容不用经常更新,只有在切换窗口的时候才更新显示                  display_lattice(1,0,Hz1616_chuang,0,2,16,0);                     display_lattice(2,0,Hz1616_kou,0,2,16,0);                     display_lattice(3,0,Hz1616_yi,0,2,16,0);                  display_lattice(4,0,Hz1616_hang,0,2,16,0);                  display_lattice(0,16,Hz1616_yi,0,2,16,0);    //一窗口二行                  display_lattice(1,16,Hz1616_chuang,0,2,16,0);                     display_lattice(2,16,Hz1616_kou,0,2,16,0);                     display_lattice(3,16,Hz1616_er,0,2,16,0);                  display_lattice(4,16,Hz1616_hang,0,2,16,0);                  display_lattice(8,0,Hz1616_yi,0,2,16,0);    //一窗口三行                  display_lattice(9,0,Hz1616_chuang,0,2,16,0);                     display_lattice(10,0,Hz1616_kou,0,2,16,0);                     display_lattice(11,0,Hz1616_san,0,2,16,0);                  display_lattice(12,0,Hz1616_hang,0,2,16,0);                  display_lattice(8,16,Hz1616_yi,0,2,16,0);    //一窗口四行                  display_lattice(9,16,Hz1616_chuang,0,2,16,0);                     display_lattice(10,16,Hz1616_kou,0,2,16,0);                     display_lattice(11,16,Hz1616_si,0,2,16,0);                  display_lattice(12,16,Hz1616_hang,0,2,16,0);                           } /* 注释三: * 注意!我前面讲数码管显示的时候有一句话讲错了,我那时说,这是不对的。 * 按照现在的显示程序框架,应该是 * 这样才对。 */                           if(ucWd1Part1Update==1) //窗口1的第1行局部更新显示变量,里面放一些经常需要刷新显示的内容                           {                              ucWd1Part1Update=0; //及时清零,避免一直更新                      if(ucPart==1) //被选中                                  {                                     ucCursorFlag=1; //反显 显示                                  }                      else //没被选中                                  {                                     ucCursorFlag=0; //正常 显示                                  }                  if(ucData_1_1>=10) //有2位数以上                  {                     ucAnyNumber_10=ucData_1_1/10;  //十位                  }                  else //否则显示空                  {                     ucAnyNumber_10=10;  //在下面的转换函数中,代码10表示空字模                  }                  ucAnyNumber_1=ucData_1_1%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(5,0,ucCanvasBuffer,0,4,16,0);   //显示整屏的画布,最后的参数0是偏移量                                                      }                           if(ucWd1Part2Update==1) //窗口1的第2行局部更新显示变量,里面放一些经常需要刷新显示的内容                           {                              ucWd1Part2Update=0; //及时清零,避免一直更新                      if(ucPart==2) //被选中                                  {                                     ucCursorFlag=1; //反显 显示                                  }                      else //没被选中                                  {                                     ucCursorFlag=0; //正常 显示                                  }                  if(ucData_1_2>=10) //有2位数以上                  {                     ucAnyNumber_10=ucData_1_2/10;  //十位                  }                  else //否则显示空                  {                     ucAnyNumber_10=10;  //在下面的转换函数中,代码10表示空字模                  }                  ucAnyNumber_1=ucData_1_2%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(5,16,ucCanvasBuffer,0,4,16,0);   //显示整屏的画布,最后的参数0是偏移量                                                      }                           if(ucWd1Part3Update==1) //窗口1的第3行局部更新显示变量,里面放一些经常需要刷新显示的内容                           {                              ucWd1Part3Update=0; //及时清零,避免一直更新                      if(ucPart==3) //被选中                                  {                                     ucCursorFlag=1; //反显 显示                                  }                      else //没被选中                                  {                                     ucCursorFlag=0; //正常 显示                                  }                  if(ucData_1_3>=10) //有2位数以上                  {                     ucAnyNumber_10=ucData_1_3/10;  //十位                  }                  else //否则显示空                  {                     ucAnyNumber_10=10;  //在下面的转换函数中,代码10表示空字模                  }                  ucAnyNumber_1=ucData_1_3%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(13,0,ucCanvasBuffer,0,4,16,0);   //显示整屏的画布,最后的参数0是偏移量                                                      }                           if(ucWd1Part4Update==1) //窗口1的第4行局部更新显示变量,里面放一些经常需要刷新显示的内容                           {                              ucWd1Part4Update=0; //及时清零,避免一直更新                      if(ucPart==4) //被选中                                  {                                     ucCursorFlag=1; //反显 显示                                  }                      else //没被选中                                  {                                     ucCursorFlag=0; //正常 显示                                  }                  if(ucData_1_4>=10) //有2位数以上                  {                     ucAnyNumber_10=ucData_1_4/10;  //十位                  }                  else //否则显示空                  {                     ucAnyNumber_10=10;  //在下面的转换函数中,代码10表示空字模                  }                  ucAnyNumber_1=ucData_1_4%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(13,16,ucCanvasBuffer,0,4,16,0);   //显示整屏的画布,最后的参数0是偏移量                                                     }               break;         //本程序只有1个窗口,所以只有一个case 1,如果要增加窗口,就直接增加 case 2, case 3...            } } void clear_all_canvas(void)  //把画布全部清零 {    unsigned int j=0;    unsigned int i=0;    for(j=0;j
    9. 第七十六节:如何把一个任意数值的变量显示在液晶屏上。 开场白: 本来这一节打算开始讲液晶屏的菜单程序,但是我担心跳跃太大,恐怕很多初学者跟不上,所以多插入这一节讲讲后面菜单程序中经常用到的基本功能,如何把一个任意数值的变量显示在液晶屏上。我们需要做一个变量转换成字模的函数,以后只要调用这个转换函数就可以了。这一节就要把这个转换函数教给大家。 具体内容,请看源代码讲解。 (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
    10. 第七十五节:在12864液晶屏中让字体以1个点阵为单位进行移动显示的算法程序。 开场白:     假设有一个固定的四方形透明窗口,在窗口里面放了一张画布,只要想办法让这个画布 往右边拖动,那么画布里面的内容就会跟着画布整体往右边移动,这个就是能以1个点阵为单位进行移动显示的本质。同理,这个画布有16行,每行有4个字节,我们只要把每行4个字节看作是一个首尾连接的二进制数据,把每一行的二进制数据每次整体往右边移动一位,就相当于移动一个点阵了。这一节就要把这个算法教给大家。 具体内容,请看源代码讲解。 (1)硬件平台:     基于朱兆祺51单片机学习板。 (2)实现功能:开机上电后,能看到正中间显示的两个字符“V5”整体以1个点阵为单位向右边慢慢移动。 (3)源代码讲解如下: #include "REG52.H" #define const_MoveTime 400  //每移动一位后的延时时间 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); //显示任意点阵函数 void delay_short(unsigned int uiDelayshort); //延时 void move_service(void); //整体画布移动的应用程序 void lcd_display_service(void); //应用层面的液晶屏显示程序 void move_canvas_to_one_bit(void);  //把画布整体往右边移动一个点阵 void clear_all_canvas(void);  //把画布全部清零 void T0_time(void);  //定时中断函数 code unsigned char Zf816_V[]= /*V   横向取模  8x16点阵 每一行只要1个字节,共16行 */ { 0x00, 0x00, 0x00, 0xE7, 0x42, 0x42, 0x44, 0x24, 0x24, 0x28, 0x28, 0x18, 0x10, 0x10, 0x00, 0x00, }; code unsigned char Zf816_5[]= /*5   横向取模  8x16点阵 每一行只要1个字节,共16行 */ { 0x00, 0x00, 0x00, 0x7E, 0x40, 0x40, 0x40, 0x58, 0x64, 0x02, 0x02, 0x42, 0x44, 0x38, 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;  //更新显示变量 unsigned char ucMoveStepReset=0;  //这个变量是为了方便外部程序初始化应用程序内部后缀为step的步骤变量 unsigned char ucMoveTimeStart=0; //定时器的开关标志  也相当于原子锁或互斥量的功能 unsigned int uiMoveTime=0;  //定时器累计时间 void main()   {         LCDInit(); //初始化12864 内部包含液晶模块的复位         display_clear(0xff); // 清屏 全部显示空填充0x00   全部显示点阵用0xff         TMOD=0x01;  //设置定时器0为工作方式1         TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f         TL0=0x2f;         EA=1;     //开总中断         ET0=1;    //允许定时中断         TR0=1;    //启动定时中断         while(1)           {            move_service(); //整体画布移动的应用程序            lcd_display_service(); //应用层面的液晶屏显示程序         } } void move_service(void) //整体画布移动的应用程序 {    static unsigned char ucMoveStep=0; //运行步骤。前面加关键字static表示上电后这个变量只初始化一次,以后每次进出函数此变量不会重新初始化,保存之前的更改数值不变。    static unsigned char ucMoveCnt=0; //统计当前已经往左边移动了多少位。关键字static表示此变量上电后只初始化一次,不会每次进入函数都初始化。    if(ucMoveStepReset==1)  //运行步骤的复位标志,此段代码结构方便外部程序初始化函数内部的步骤变量ucMoveStep    {       ucMoveStepReset=0; //及时把复位标志清零。避免一直处于复位的状态、           ucMoveStep=0; //运行步骤变量被外部程序通过复位标志初始化。    }    switch(ucMoveStep)    {       case 0:                clear_all_canvas();  //把画布全部清零            insert_buffer_to_canvas(0,0,Zf816_V,0,1,16);//把的字模插入画布            insert_buffer_to_canvas(1,0,Zf816_5,0,1,16);//把的字模插入画布            ucDisplayUpdate=1; //更新液晶屏显示                                       uiMoveTime=0;  //定时器清零                    ucMoveTimeStart=1; //开定时器     也相当于原子锁或互斥量的功能                    ucMoveCnt=0; //统计当前已经往左边移动了多少位                    ucMoveStep=1; //切换到下一个运行步骤                break;       case 1:                if(uiMoveTime>const_MoveTime)  //延时一定的时间后                    {                               ucMoveTimeStart=0; //关定时器    也相当于原子锁或互斥量的功能                        uiMoveTime=0;  //定时器清零                    if(ucMoveCnt
    11. 第七十四节:在液晶屏中让字体可以跨区域无缝对接显示的算法程序。 开场白: 细心的网友会发现,这种12864液晶屏在显示自造字库时普遍有个毛病,在坐标轴x方向上是以每16个点阵为一个单位的,如果显示两个8x16字符”V”和”5”,虽然它们的x坐标轴是相邻的,但是实际显示的效果是中间隔了8个点阵。另外,这种12864液晶屏是由上半屏和下半屏组成的,软件上的坐标体系并没有做到跟物理的坐标体系一致,需要转换的。如果我们想把一个整体字符的一半显示在上半屏,另一半显示在下半屏,那怎么办? 这一节就要教给大家这个算法程序: 为了实现跨区域无缝显示,就先在某个区域显示一块画布,我们只要在这块画布数组中插入字模数组,就可以达到跨区域无缝显示的目的。 具体内容,请看源代码讲解。 (1)硬件平台:      基于朱兆祺51单片机学习板。 (2)实现功能:开机上电后,看到液晶屏所有的点阵都显示。正中间露出一小方块空白的32x16点阵画布,从左到右分别显示“V5”两个字符。这两个字符是紧紧挨在一起的,中间并没有8个点阵的空格,同时这两个字符的上半部分显示在上半屏,下半部分显示在下半屏。实现了真正的跨区域无缝对接显示。 (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); //显示任意点阵函数 void delay_short(unsigned int uiDelayshort); //延时 code unsigned char Zf816_V[]= /*V   横向取模  8x16点阵 每一行只要1个字节,共16行 */ { 0x00, 0x00, 0x00, 0xE7, 0x42, 0x42, 0x44, 0x24, 0x24, 0x28, 0x28, 0x18, 0x10, 0x10, 0x00, 0x00, }; code unsigned char Zf816_5[]= /*5   横向取模  8x16点阵 每一行只要1个字节,共16行 */ { 0x00, 0x00, 0x00, 0x7E, 0x40, 0x40, 0x40, 0x58, 0x64, 0x02, 0x02, 0x42, 0x44, 0x38, 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, }; void main()   {         LCDInit(); //初始化12864 内部包含液晶模块的复位         display_clear(0xff); // 清屏 全部显示空填充0x00   全部显示点阵用0xff         insert_buffer_to_canvas(0,0,Zf816_V,0,1,16);//把的字模插入画布         insert_buffer_to_canvas(1,0,Zf816_5,0,1,16);//把的字模插入画布         display_lattice(3,24,ucCanvasBuffer,0,4,8,0);   //显示上半屏的画布,最后的参数0是偏移量         display_lattice(11,0,ucCanvasBuffer,0,4,8,32);  //显示下半屏的画布,最后的参数32是偏移量         while(1)           {              ;         } } void display_clear(unsigned char ucFillDate) // 清屏  全部显示空填充0x00   全部显示点阵用0xff {        unsigned char x,y;     WriteCommand(0x34);  //关显示缓冲指令                 WriteCommand(0x34);  //关显示缓冲指令  故意写2次,怕1次关不了 这个是因为我参考到某厂家的驱动程序也是这样写的     y=0;     while(y
    12. 第七十三节:在液晶屏中把字体镜像显示的算法程序。 开场白: 有的项目会要求把字体或者图像进行镜像显示处理,这一节把这个算法教给大家。     这个算法的本质是: 16x16点阵的图像或者字体有16行,每行有2个字节,如果把这2个字节看成是一个16位int型数据,那么就是要这个数据从原来左边是高位,右边是低位的顺序颠倒过来。本程序没有把2个字节合并成一个int型数据,而是直接在一个字节数据内把高低位顺序颠倒过来,然后把第1字节数据跟第2字节数据交换。 8x16点阵的图像或者字体有16行,每行有1个字节,把这个数据从原来左边是高位,右边是低位的顺序颠倒过来。 具体内容,请看源代码讲解。 (1)硬件平台:      基于朱兆祺51单片机学习板。 (2)实现功能:开机上电后,从上往下分别显示“馒头V5”四个字以及右边镜像后的“馒头V5”四个字。     (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_lattice(unsigned int x,unsigned int y,const unsigned char  *ucArray,unsigned char ucFbFlag,unsigned int x_amount,unsigned int y_amount); //显示任意点阵函数 void display_clear(void); // 清屏 void hz1616_mirror(const unsigned char  *p_ucHz,unsigned char *p_ucResult);  //把16x16点阵字库镜像 void hz816_mirror(const unsigned char  *p_ucHz,unsigned char *p_ucResult);  //把8x16点阵字库镜像 void delay_short(unsigned int uiDelayshort); //延时 code unsigned char Hz1616_man[]= /*馒   横向取模  16X16点阵 */ { 0x21,0xF8,0x21,0x08,0x21,0xF8,0x3D,0x08,0x45,0xF8,0x48,0x00,0x83,0xFC,0x22,0x94, 0x23,0xFC,0x20,0x00,0x21,0xF8,0x20,0x90,0x28,0x60,0x30,0x90,0x23,0x0E,0x00,0x00, }; code unsigned char Hz1616_tou[]= /*头   横向取模  16X16点阵 */ { 0x00,0x80,0x10,0x80,0x0C,0x80,0x04,0x80,0x10,0x80,0x0C,0x80,0x08,0x80,0x00,0x80, 0xFF,0xFE,0x00,0x80,0x01,0x40,0x02,0x20,0x04,0x30,0x08,0x18,0x10,0x0C,0x20,0x08, }; code unsigned char Zf816_V[]= /*V   横向取模  8x16点阵 */ { 0x00,0x00,0x00,0xE7,0x42,0x42,0x44,0x24,0x24,0x28,0x28,0x18,0x10,0x10,0x00,0x00, }; code unsigned char Zf816_5[]= /*5   横向取模  8x16点阵 */ { 0x00,0x00,0x00,0x7E,0x40,0x40,0x40,0x58,0x64,0x02,0x02,0x42,0x44,0x38,0x00,0x00, }; unsigned char ucBufferResult[32]; //用于临时存放转换结束后的字模数组 void main()   {         LCDInit(); //初始化12864 内部包含液晶模块的复位         display_clear(); // 清屏         display_lattice(0,0,Hz1616_man,0,2,16);  //显示镜像前的字         hz1616_mirror(Hz1616_man,ucBufferResult);  //把字镜像后放到ucBufferResult临时变量里。         display_lattice(1,0,ucBufferResult,0,2,16);  //显示镜像后的字         display_lattice(0,16,Hz1616_tou,0,2,16);  //显示镜像前的字         hz1616_mirror(Hz1616_tou,ucBufferResult);  //把字镜像后放到ucBufferResult临时变量里。         display_lattice(1,16,ucBufferResult,0,2,16);  //显示镜像后的字         display_lattice(8,0,Zf816_V,0,1,16);  //显示镜像前的字符         hz816_mirror(Zf816_V,ucBufferResult);  //把字符镜像后放到ucBufferResult临时变量里。         display_lattice(9,0,ucBufferResult,0,1,16);  //显示镜像后的字符         display_lattice(8,16,Zf816_5,0,1,16);  //显示镜像前的字符         hz816_mirror(Zf816_5,ucBufferResult);  //把字符镜像后放到ucBufferResult临时变量里。         display_lattice(9,16,ucBufferResult,0,1,16);  //显示镜像后的字符         while(1)           {              ;         } } void display_clear(void) // 清屏 {        unsigned char x,y;     WriteCommand(0x34);  //关显示缓冲指令                 WriteCommand(0x34);  //关显示缓冲指令  故意写2次,怕1次关不了 这个是因为我参考到某厂家的驱动程序也是这样写的     y=0;     while(y
    13. 第七十二节:在液晶屏中把字体顺时针旋转90度显示的算法程序。 开场白:我曾经遇到过这样的项目,客户由于外壳结果的原因,故意把液晶屏物理位置逆时针旋转了90度,在这种情况下,如果按之前的显示驱动就会发现字体也跟着倒了过来,影响了阅读。当时我的解决办法就是把字体的字库数组通过算法顺时针旋转90度就达到了目的。这一节把这个算法教给大家。 这个算法的本质是:请看以下附图1,附图2,附图3. 第一步:旋转90度的本质,就是把原来横向取模改成纵向去模。先把代表每一行16个点阵数的2个char型数据合并成1个int型数据。 第二步:再把每一列的16个点阵按2个字节分别取到一个数组里,就是纵向取模的过程了。 具体内容,请看源代码讲解。 (1)硬件平台:     基于朱兆祺51单片机学习板。 (2)实现功能:把液晶屏物理位置逆时针旋转了90度,开机上电后,可以看到液晶屏像对联的显示顺序一样,从上往下分别显示“馒头V5”四个字。   (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_lattice(unsigned int x,unsigned int y,const unsigned char  *ucArray,unsigned char ucFbFlag,unsigned int x_amount,unsigned int y_amount); //显示任意点阵函数 void display_clear(void); // 清屏 void hz1616_s90(const unsigned char  *p_ucHz,unsigned char *p_ucResult);  //把16x16汉字字模顺时针旋转90度的转换函数 void hz816_s90(const unsigned char  *p_ucHz,unsigned char *p_ucResult);  //把8x16字符字模顺时针旋转90度的转换函数 void delay_short(unsigned int uiDelayshort); //延时 code unsigned char Hz1616_man[]= /*馒   横向取模  16X16点阵 */ { 0x21,0xF8,0x21,0x08,0x21,0xF8,0x3D,0x08,0x45,0xF8,0x48,0x00,0x83,0xFC,0x22,0x94, 0x23,0xFC,0x20,0x00,0x21,0xF8,0x20,0x90,0x28,0x60,0x30,0x90,0x23,0x0E,0x00,0x00, }; code unsigned char Hz1616_tou[]= /*头   横向取模  16X16点阵 */ { 0x00,0x80,0x10,0x80,0x0C,0x80,0x04,0x80,0x10,0x80,0x0C,0x80,0x08,0x80,0x00,0x80, 0xFF,0xFE,0x00,0x80,0x01,0x40,0x02,0x20,0x04,0x30,0x08,0x18,0x10,0x0C,0x20,0x08, }; code unsigned char Zf816_V[]= /*V   横向取模  8x16点阵 */ { 0x00,0x00,0x00,0xE7,0x42,0x42,0x44,0x24,0x24,0x28,0x28,0x18,0x10,0x10,0x00,0x00, }; code unsigned char Zf816_5[]= /*5   横向取模  8x16点阵 */ { 0x00,0x00,0x00,0x7E,0x40,0x40,0x40,0x58,0x64,0x02,0x02,0x42,0x44,0x38,0x00,0x00, }; unsigned char ucBufferResult[32]; //用于临时存放转换结束后的字模数组 void main()   {         LCDInit(); //初始化12864 内部包含液晶模块的复位         display_clear(); // 清屏 /* 注释一: * (1)把原来的液晶屏物理位置逆时针旋转90度后,从上往下阅读,类似对联的阅读习惯。所以请注意坐标体系参数的变化。 * (2)为了让字符居中显示,请注意在显示V和5两个字符时坐标体系的变化。 * (3)字符8x16经过旋转处理后,变成了16x8,在调用display_lattice函数时,要注意修改响应的参数。 */         hz1616_s90(Hz1616_man,ucBufferResult);  //把字顺时针旋转90度放到ucBufferResult临时变量里。         display_lattice(7,0,ucBufferResult,0,2,16);  //显示旋转90度后的字         hz1616_s90(Hz1616_tou,ucBufferResult);  //把字顺时针旋转90度放到ucBufferResult临时变量里。         display_lattice(6,0,ucBufferResult,0,2,16);  //显示旋转90度后的字         hz816_s90(Zf816_V,ucBufferResult);  //把字符顺时针旋转90度放到ucBufferResult临时变量里。         display_lattice(5,4,ucBufferResult,0,2,8);  //显示旋转90度后的字符。注意在最后两个个参数,2表示每一行有2个字节,8表示8列。第二个坐标参数4是为了偏移居中显示。         hz816_s90(Zf816_5,ucBufferResult);  //把字符顺时针旋转90度放到ucBufferResult临时变量里。         display_lattice(4,4,ucBufferResult,0,2,8);  //显示旋转90度后的字符。注意在最后两个个参数,2表示每一行有2个字节,8表示8列。第二个坐标参数4是为了偏移居中显示。     while(1)       {        ;     } } void display_clear(void) // 清屏 {        unsigned char x,y;     WriteCommand(0x34);  //关显示缓冲指令                 WriteCommand(0x34);  //关显示缓冲指令  故意写2次,怕1次关不了 这个是因为我参考到某厂家的驱动程序也是这样写的     y=0;     while(y
    14. 第七十一节:液晶屏的字符,16点阵,24点阵和32点阵的显示程序。 开场白: 这一节要教会大家二个知识点: 第一个:如何利用任意点阵字体显示函数display_lattice来显示8x16的字符,16点阵汉字,24点阵汉字和32点阵汉字。 第二个:纠正上一节的一个小错误。C51编译器跟其它单片机的编译器有点不一样。想把常量数据保存在ROM程序存储区里并不是用const关键字,而是是用code关键字。 具体内容,请看源代码讲解。 (1)硬件平台:      基于朱兆祺51单片机学习板。 (2)实现功能:开机上电后,可以看到液晶屏分别显示32点阵,24点阵和16点阵的“馒头”两个字,还有“V5”这两个8x16点阵的字符。     (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_lattice(unsigned int x,unsigned int y,const unsigned char  *ucArray,unsigned char ucFbFlag,unsigned int x_amount,unsigned int y_amount); //显示任意点阵函数 void display_clear(void); // 清屏 void delay_short(unsigned int uiDelayshort); //延时 /* 注释一: * 纠正上一节的一个小错误。C51编译器跟其它的编译器有点不一样。 * 存在ROM程序存储区里的常量数据并不是用const关键字,而是是用code关键字。 */ code unsigned char Hz3232_man[]= /*馒   横向取模  32x32点阵 */ { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x07,0x03,0x00,0x0F,0x87,0xFF,0x80, 0x0F,0x07,0x03,0x80,0x0E,0x07,0x03,0x80,0x0E,0x37,0xFF,0x80,0x1C,0x7F,0x03,0x80, 0x1F,0xFF,0x03,0x80,0x18,0x77,0xFF,0x00,0x38,0xE0,0x00,0xC0,0x36,0xDF,0xFF,0xF0, 0x77,0x9C,0xCE,0xE0,0x67,0x1C,0xCE,0xE0,0xC7,0x1C,0xCE,0xE0,0x07,0x1C,0xCE,0xE0, 0x07,0x1F,0xFF,0xE0,0x07,0x18,0x00,0x00,0x07,0x00,0x03,0x80,0x07,0x0F,0xFF,0xC0, 0x07,0x71,0x8F,0x00,0x07,0xE0,0xDE,0x00,0x07,0xC0,0xFC,0x00,0x07,0x80,0x78,0x00, 0x0F,0x01,0xFE,0x00,0x07,0x03,0x8F,0xE0,0x00,0x1E,0x03,0xF0,0x00,0xF8,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, }; code unsigned char Hz3232_tou[]= /*头   横向取模  32x32点阵 */ { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0xC0,0x00,0x00,0x03,0xE0,0x00, 0x03,0xC3,0xC0,0x00,0x00,0xF3,0x80,0x00,0x00,0x7B,0x80,0x00,0x00,0x7B,0x80,0x00, 0x00,0x3B,0x80,0x00,0x0E,0x03,0x80,0x00,0x07,0x83,0x80,0x00,0x03,0xC3,0x80,0x00, 0x01,0xE3,0x80,0x00,0x01,0xE3,0x80,0x00,0x00,0xC3,0x80,0x00,0x00,0x03,0x81,0xE0, 0x7F,0xFF,0xFF,0xF0,0x00,0x07,0x80,0x30,0x00,0x07,0x00,0x00,0x00,0x07,0x80,0x00, 0x00,0x0E,0xE0,0x00,0x00,0x1E,0x7C,0x00,0x00,0x3C,0x1F,0x00,0x00,0x78,0x0F,0xC0, 0x00,0xF0,0x03,0xC0,0x03,0xC0,0x01,0xE0,0x0F,0x00,0x00,0xE0,0x78,0x00,0x00,0x00, 0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, }; code unsigned char Hz2424_man[]= /*馒   横向取模  24x24点阵 */ { 0x00,0x00,0x00,0x00,0x00,0x00,0x0C,0x18,0x30,0x1E,0x1F,0xF8,0x1C,0x1C,0x38,0x1C, 0x1F,0xF8,0x19,0xFC,0x38,0x3F,0xFF,0xF8,0x31,0x98,0x30,0x7B,0xE0,0x0E,0x6F,0x7F, 0xFE,0x6E,0x76,0xEE,0xCC,0x76,0xEE,0x0C,0x7F,0xFE,0x0C,0x70,0x0C,0x0C,0x00,0x38, 0x0C,0x3F,0xF8,0x0D,0xCE,0x70,0x0F,0x87,0xE0,0x0F,0x03,0x80,0x1E,0x07,0xE0,0x0C, 0x1C,0x7E,0x01,0xF0,0x1F,0x00,0x00,0x00, }; code unsigned char Hz2424_tou[]= /*头   横向取模  24x24点阵 */ { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0E,0x00,0x06,0x0F,0x00,0x07,0x8E,0x00,0x01, 0xEE,0x00,0x00,0xEE,0x00,0x00,0xEC,0x00,0x1C,0x0C,0x00,0x0F,0x0C,0x00,0x07,0x9C, 0x00,0x03,0x9C,0x00,0x00,0x1C,0x0C,0x00,0x1C,0x1E,0x7F,0xFF,0xF6,0x00,0x1C,0x00, 0x00,0x3C,0x00,0x00,0x3F,0x80,0x00,0x71,0xE0,0x00,0xE0,0xF8,0x01,0xC0,0x3C,0x07, 0x00,0x1C,0x3C,0x00,0x0C,0x70,0x00,0x00, }; code unsigned char Hz1616_man[]= /*馒   横向取模  16X16点阵 */ { 0x21,0xF8,0x21,0x08,0x21,0xF8,0x3D,0x08,0x45,0xF8,0x48,0x00,0x83,0xFC,0x22,0x94, 0x23,0xFC,0x20,0x00,0x21,0xF8,0x20,0x90,0x28,0x60,0x30,0x90,0x23,0x0E,0x00,0x00, }; code unsigned char Hz1616_tou[]= /*头   横向取模  16X16点阵 */ { 0x00,0x80,0x10,0x80,0x0C,0x80,0x04,0x80,0x10,0x80,0x0C,0x80,0x08,0x80,0x00,0x80, 0xFF,0xFE,0x00,0x80,0x01,0x40,0x02,0x20,0x04,0x30,0x08,0x18,0x10,0x0C,0x20,0x08, }; code unsigned char Zf816_V[]= /*V   横向取模  8x16点阵 */ { 0x00,0x00,0x00,0xE7,0x42,0x42,0x44,0x24,0x24,0x28,0x28,0x18,0x10,0x10,0x00,0x00, }; code unsigned char Zf816_5[]= /*5   横向取模  8x16点阵 */ { 0x00,0x00,0x00,0x7E,0x40,0x40,0x40,0x58,0x64,0x02,0x02,0x42,0x44,0x38,0x00,0x00, }; void main()   {         LCDInit(); //初始化12864 内部包含液晶模块的复位     display_clear(); // 清屏         display_lattice(0,0,Hz3232_man,0,4,32);  //显示32点阵的字         display_lattice(2,0,Hz3232_tou,0,4,32);  //显示32点阵的字         display_lattice(4,0,Hz2424_man,0,3,24);  //显示24点阵的字         display_lattice(6,0,Hz2424_tou,0,3,24);  //显示24点阵的字         display_lattice(8,0,Hz1616_man,0,2,16);  //显示16点阵的字         display_lattice(9,0,Hz1616_tou,0,2,16);  //显示16点阵的字         display_lattice(11,0,Zf816_V,0,1,16);  //显示8x16点阵的字符         display_lattice(12,0,Zf816_5,0,1,16);  //显示8x16点阵的字符     while(1)       {        ;     } } void display_clear(void) // 清屏 {            unsigned char x,y;     WriteCommand(0x34);  //关显示缓冲指令                 WriteCommand(0x34);  //关显示缓冲指令  故意写2次,怕1次关不了 这个是因为我参考到某厂家的驱动程序也是这样写的         y=0;         while(y
    15. 第七十节:深入讲解液晶屏的构字过程。 开场白:     液晶屏模块本身带控制芯片,驱动液晶屏的本质就是单片机通过串行或者并行方式,根据芯片资料指定的协议跟液晶芯片进行通讯的过程。这个详细的通讯协议驱动程序厂家都会免费提供的,也可以在网上找到大量的示范程序。那么我们最应该关注的核心是什么?我认为最核心的是要理清楚程序坐标与实际显示坐标之间的关系规律。本程序不使用模块自带的字库,而是使用自己构造的字库,目的就是为了让读者理解更底层的字模显示。 这一节要教会大家三个知识点: 第一个:对于驱动芯片是st7920的12864液晶屏,它的真实坐标体系的本质是256x32的点阵液晶屏。 第二个:鸿哥刻意在驱动显示函数里增加了大延时函数,目的是通过慢镜头回放,让大家观察到横向取模的字是如何一个字节一个字节构建而成的。 第三个:数组带const关键字,表示数据常量存放在ROM程序区,不占用RAM的变量。 具体内容,请看源代码讲解。 (1)硬件平台:      基于朱兆祺51单片机学习板。 (2)实现功能:开机上电后,可以观察到0x01,0x02,0x03,0x04这4个显示数字在不同的排列方式下,出现在不同的液晶屏显示位置。也可以观察到“馒头”这两个字是如何一个字节一个字节构建而成的,加深理解字模数组跟显示现象的关系。     (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_lattice(unsigned int x,unsigned int y,const unsigned char  *ucArray,unsigned char ucFbFlag,unsigned int x_amount,unsigned int y_amount); //显示任意点阵函数 void display_clear(void); // 清屏 void delay_short(unsigned int uiDelayshort); //延时 /* 注释一: * 数组带const关键字,表示数据常量存放在ROM程序区,不占用RAM的变量 */ const unsigned char Hz1616_man[]= /*馒   横向取模  16X16点阵 网上有很多免费的字模软件生成字模数组 */ { 0x21,0xF8,0x21,0x08,0x21,0xF8,0x3D,0x08,0x45,0xF8,0x48,0x00,0x83,0xFC,0x22,0x94, 0x23,0xFC,0x20,0x00,0x21,0xF8,0x20,0x90,0x28,0x60,0x30,0x90,0x23,0x0E,0x00,0x00, }; const unsigned char Hz1616_tou[]= /*头   横向取模  16X16点阵 网上有很多免费的字模软件生成字模数组 */ { 0x00,0x80,0x10,0x80,0x0C,0x80,0x04,0x80,0x10,0x80,0x0C,0x80,0x08,0x80,0x00,0x80, 0xFF,0xFE,0x00,0x80,0x01,0x40,0x02,0x20,0x04,0x30,0x08,0x18,0x10,0x0C,0x20,0x08, }; /* 注释二: * 为了方便观察字模的数字与显示的关系,以下3个数组的本质是完全一样的,只是排列不一样而已。 */ const unsigned char Byte_1[]=  //4横,1列 { 0x01,0x02,0x03,0x04, }; const unsigned char Byte_2[]= //2横,2列 { 0x01,0x02, 0x03,0x04, }; const unsigned char Byte_3[]= //1横,4列 { 0x01, 0x02, 0x03, 0x04, }; void main()   {         LCDInit(); //初始化12864 内部包含液晶模块的复位     display_clear(); // 清屏         display_lattice(0,0,Byte_1,0,4,1);    //显示的数组数字         display_lattice(0,16,Byte_1,1,4,1);   //显示的数组数字 反显         display_lattice(7,0,Byte_2,0,2,2);   //显示的数组数字         display_lattice(7,16,Byte_2,1,2,2);  //显示的数组数字 反显         display_lattice(8,0,Byte_3,0,1,4);  //显示的数组数字         display_lattice(8,16,Byte_3,1,1,4); //显示的数组数字 反显         display_lattice(14,0,Hz1616_man,0,2,16);  //显示字         display_lattice(15,0,Hz1616_tou,0,2,16);  //显示字         display_lattice(14,16,Hz1616_man,1,2,16); //显示字 反显         display_lattice(15,16,Hz1616_tou,1,2,16); //显示字 反显     while(1)       {        ;     } } /* 注释三:真实坐标体系的本质。 * 从坐标体系的角度来看,本液晶屏表面上是128x64的液晶屏,实际上可以看做是256x32的液晶屏。 * 把256x32的液晶屏分左右两半,把左半屏128x32放在上面,把右半屏128x32放下面,就合并成了 * 一个128x64的液晶屏。由于液晶模块内部控制器的原因,虽然横向有256个点阵,但是我们的x轴 * 坐标没办法精确到每个点,只能以16个点(2个字节)为一个单位,因此256个点的x轴坐标范围是0至15。 * 而y轴的坐标可以精确到每个点为一行,所以32个点的y轴坐标范围是0至31. */ void display_clear(void) // 清屏 {            unsigned char x,y;   //  WriteCommand(0x34);  //关显示缓冲指令                 WriteCommand(0x36); //这次为了观察每个数字在显示屏上的关系,所以把这个显示缓冲的命令提前打开,下一节放到本函数最后         y=0;         while(y
    16. 第六十九节:使用static关键字可以减少全局变量的使用。 开场白: 本来这一节打算开始讲液晶屏的,但是昨天经过网友“任军”的点拨,我发现了一个惊天秘密,原来static关键字是这么好的东西我却错过了那么多年。以前就有一些网友抱怨,鸿哥的程序好是好,就是全局变量满天飞,当时我觉得我也没招呀,C语言就全局变量和局部变量,单单靠局部变量肯定不行,局部变量每次进入函数内部数值都会被初始化改变,所以我在很多场合也只能靠全局变量了。但是自从昨天知道了static关键字的秘密后,我恍然大悟,很多场合只要在局部变量前面加上static关键字,就可以大大减少全局变量的使用了。 这一节要教会大家一个知识点:   大家都知道,普通的局部变量在每次程序执行到函数内部的时候,数值都会被重新初始化,数值会发生变化,不能保持之前的数值。但是在局部变量加上static关键字后,系统在刚上电的时候就已经把带static的局部变量赋初始值了,从此程序每次进入函数内部,都不会初始化带static关键字的局部变量,它会保持最近一次被程序执行更改的数值不变,像全局变量一样。跟全局变量唯一的差别是,带static关键字的局部变量的作用域仅仅在函数内部,而普通全局变量的作用域是整个工程。 本程序例程是直接在第八节程序上修改,大大减少了全局变量的使用。具体内容,请看源代码讲解。 (1)硬件平台:      基于朱兆祺51单片机学习板。用矩阵键盘中的S1和S5号键作为独立按键,记得把输出线P0.4一直输出低电平,模拟独立按键的触发地GND。 (2)实现功能:跟前面第八节的功能一模一样,有两个独立按键,每按一个独立按键,蜂鸣器发出“滴”的一声后就停。     (3)源代码讲解如下: #include "REG52.H" #define const_voice_short  40   #define const_key_time1  20    #define const_key_time2  20    void initial_myself();    void initial_peripheral(); void delay_long(unsigned int uiDelaylong); void T0_time(); void key_service(); void key_scan(); sbit key_sr1=P0^0; sbit key_sr2=P0^1; sbit key_gnd_dr=P0^4; sbit beep_dr=P2^7; unsigned char ucKeySec=0;   //一些需要在不同函数之间使用的核心变量,只能用全局变量 unsigned int  uiVoiceCnt=0;  //一些需要在不同函数之间使用的核心变量,只能用全局变量 void main()   {    initial_myself();      delay_long(100);       initial_peripheral();    while(1)      {        key_service();    } } void key_scan() {   /* 注释一:    (1)大家都知道,普通的局部变量在每次程序执行到函数内部的时候,数值都会被重新初始化,    数值会发生变化,不能保持之前的数值。    (2)但是在局部变量加上static关键字后,系统在刚上电的时候就已经把带static的局部变量    赋初始值了,从此程序每次进入函数内部,都不会初始化带static关键字的局部变量,它会保持    最近一次被程序执行更改的数值不变,像全局变量一样。跟全局变量唯一的差别是,带static关键字    的局部变量的作用域仅仅在函数内部,而普通全局变量的作用域是整个工程。    (3)以下这些变量我原来在第八节是用普通全局变量的,现在改成用static的局部变量了,减少了全局变量    的使用,让程序阅读起来更加简洁。大家也可以试试把以下变量的static去掉试试,结果会发现去掉了static后,    按键就不会被触发了。 */ static unsigned int  uiKeyTimeCnt1=0; //带static的局部变量 static unsigned char ucKeyLock1=0;    static unsigned int  uiKeyTimeCnt2=0; static unsigned char ucKeyLock2=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)   {      ucKeyLock2=0;          uiKeyTimeCnt2=0;   }   else if(ucKeyLock2==0)   {      uiKeyTimeCnt2++;      if(uiKeyTimeCnt2>const_key_time2)      {         uiKeyTimeCnt2=0;         ucKeyLock2=1;         ucKeySec=2;         }   } } void key_service() {   switch(ucKeySec)   {     case 1:           uiVoiceCnt=const_voice_short;           ucKeySec=0;           break;             case 2:           uiVoiceCnt=const_voice_short;           ucKeySec=0;           break;                       }                } void T0_time() interrupt 1 {   TF0=0;   TR0=0;   key_scan();   if(uiVoiceCnt!=0)   {      uiVoiceCnt--;      beep_dr=0;     }   else   {      ;      beep_dr=1;   }   TH0=0xf8;      TL0=0x2f;   TR0=1; } void delay_long(unsigned int uiDelayLong) {    unsigned int i;    unsigned int j;    for(i=0;i
    17. 第六十八节:单片机C语言的多文件编程技巧。 开场白:很多人也把多文件编程称作模块化编程,其实我觉得叫多文件编程会更加符合实际一些。多文件编程有两个最大的好处,一个是给我们的程序增加了目录,方便我们查找。另外一个好处是方便移植别人已经做好的功能程序模块,利用这个特点,特别适合团队一起做大型项目。很多初学者刚开始学多文件编程时,会经常遇到重复定义等问题,想知道怎么解决这些问题吗?只要按照以下鸿哥教的规则来做,这些问题就不存在了。第一个:每个文件保持成双成对出现。每个.c源文件必须有一个.h头文件跟它对应,每个.h头文件必须有一个.c源文件跟它对应。比如:main.c与main.h,delay.c与 delay.h。 第二个:.c源文件只负责函数的定义和变量的定义,但是不负责函数的声明和变量的声明。比如:unsigned char ucLedStep=0; //这个是全局变量的定义void led_flicker()   //这个是函数的定义{   //…里面是具体代码内容  } 第三个:.h头文件只负责函数的声明和变量的声明,以及常量和IO口的宏定义,但是不负责函数的定义和变量的定义。比如:#define const_time_level 200  //这个是常量的宏定义sbit led_dr=P3^5;    //这个是IO口的宏定义 void led_flicker();     //这个是函数的声明extern unsigned char ucLedStep;   //这个是全局变量的声明,不能赋初始值 第四个:每个.h头文件都必须固定以#ifndef,#define,#endif语句为模板,此模板是用来避免编译时由于重复包含头文件里面的内容而导致出错。其中标志变量_XXX_鸿哥建议用它本身的文件名称加前后下划线_。比如:#ifndef _LED_   //标志变量_LED_是用它本身的文件名称命名#define _LED_   //标志变量_LED_是用它本身的文件名称命名 #define const_time_level 200  //这个是常量的宏定义sbit led_dr=P3^5;    //这个是IO口的宏定义 void led_flicker();     //这个是函数的声明extern unsigned char ucLedStep;   //这个是全局变量的声明,不能赋初始值#endif   第五个:每个.h头文件里都必须声明它对应的.c源文件里的所有定义函数和全局变量,注意:.c源文件里所有的全局变量都要在它所对应的.h头文件里声明一次,不仅仅是函数,这个地方很容易被人忽略。比如:在led.h头文件中:void led_flicker();     //这个是函数的声明,因为在这个函数在led.c文件里定义了。   extern unsigned char ucLedStep;   //这个是全局变量的声明,不许赋初值 第六个:每个.c源文件里都必须包含两个文件,一个是单片机的系统头文件REG52.H,另外一个是它自己本身的头文件比如initial.h.剩下其它的头文件看实际情况来决定是否调用,我们用到了哪些文件的函数,全局变量或者宏定义,就需要调用对应的头文件。比如:在initial.c源文件中:#include"REG52.H"  //必须包含的单片机系统头文件#include"initial.h"  //必须包含它本身的头文件/* 注释:   由于本源文件中用到了led_dr的语句,而led_dr是在led.h文件里宏定义的,所以必须把led.h也包含进来*/  #include"led.h"  //由于本源文件中用到了led_dr的语句,所以必须把led.h也包含进来void initial_myself()  //这个是函数定义{  led_dr=0;  //led_dr是在led文件里定义和声明的} 第七个:声明一个全局变量必须加extern关键字,同时千万不能在声明全局变量的时候赋初始值,比如:extern unsigned char ucLedStep=0; //这样是绝对错误的。extern unsigned char ucLedStep; //这个是全局变量的声明,这个才是正确的 第八个:对于函数与全局变量的声明,编译器都不分配内存空间。对于函数与全局变量的定义,编译器都分配内存空间。函数与全局变量的定义只能在一个.c源文件中出现一次,而函数与全局变量的声明可以在多个.h文件中出现。 具体内容,请看源代码讲解,本程序例程是直接把前面第四节一个源文件更改成多文件编程方式。 (1)硬件平台:     基于朱兆祺51单片机学习板。把前面第四节一个源文件更改成多文件编程方式。 (2)实现功能:跟前面第四节的功能一模一样,让一个LED闪烁。     (3)keil多文件编程的截图预览: (4)整个源代码讲解工程文件下载: (5)源代码讲解如下(注意,以下代码不能直接放到一个源文件里编译): /*以下是 main.h 的内容*/ /* 注释一:   每个头文件都是固定以#ifndef,#define,#endif   为模板,其中标志变量_XXX_我建议用它本身的文件名称加前后下划线_。   此标志变量名称是用来预防多次包含出错的,详细讲解请看注释二。   每个头文件只做函数的声明和变量的声明,以及常量和IO口的宏定义,不做   函数的定义与变量的定义。 */   #ifndef _MAIN_   //标志变量_MAIN_是用它本身的文件名称命名 #define _MAIN_   //标志变量_MAIN_是用它本身的文件名称命名 void main();  //这个是函数的声明 #endif   /* 注释二: 以上语句 #ifndef   #define 插入其它内容... #endif 类似于把_MAIN_看成是一个标志变量 if(_MAIN_==0)  // 相当于#ifndef _MAIN_ {     _MAIN_=1;  // 相当于#define _MAIN_      插入其它内容... }               //相当于#endif 目的是通过一个标志位变量的赋值,让编译器在编译的时候,只包含一次此头文件,避免多次包含出错 */   /*------分割线--------------------------------------------------*/ /*以下是 main.c 的内容*/ /* 注释一:   每个源文件都必须包含两个文件,一个是单片机的系统头文件REG52.H,   另外一个是它自己本身的头文件main.h.剩下其它的头文件看实际情况来   决定是否调用,我们用到了哪些文件的函数,全局变量或者宏定义,就需要调用对应的头文件。   每个源文件只做函数的定义和变量的定义,不做函数的声明和变量的声明。 */   #include "REG52.H"  //必须包含的单片机系统头文件 #include "main.h"  //必须包含它本身的头文件 /* 注释二:    (1)由于本源文件中调用initial_myself()和initial_peripheral()函数,而这两个函数       都是在initial文件里定义和声明的,所以必须把initial.h也包含进来。    (2)由于本源文件中调用delay_long(100)函数,而这个函数       是在delay文件里定义和声明的,所以必须把delay.h也包含进来。    (2)由于本源文件中调用led_flicker()函数,而这个函数       是在led文件里定义和声明的,所以必须把led.h也包含进来。 */   #include "initial.h"  //由于本源文件中用到了initial_myself()和initial_peripheral()函数,所以必须把initial.h也包含进来 #include "delay.h"  //由于本源文件中用到了delay_long(100)函数,所以必须把delay.h也包含进来 #include "led.h"  //由于本源文件中用到了led_flicker()函数,所以必须把led.h也包含进来 void main()  //这个是函数的定义   {    initial_myself();      delay_long(100);       initial_peripheral();    while(1)       {       led_flicker();       } } /*------分割线--------------------------------------------------*/ /*以下是 delay.h 的内容*/ #ifndef _DELAY_   //标志变量_DELAY_是用它本身的文件名称命名 #define _DELAY_   //标志变量_DELAY_是用它本身的文件名称命名 void delay_long(unsigned int uiDelaylong); //这个是函数的声明,每一个源文件里的函数都要在它的头文件里声明 #endif   /*------分割线--------------------------------------------------*/ /*以下是 delay.c 的内容*/ #include "REG52.H"  //必须包含的单片机系统头文件 #include "delay.h"  //必须包含它本身的头文件 void delay_long(unsigned int uiDelayLong)  //这个是函数的定义 {    unsigned int i;   //这个是局部变量的定义    unsigned int j;   //这个是局部变量的定义    for(i=0;i
    18. 第六十七节:利用外部中断实现模拟串口数据的收发。 开场白:      鸿哥曾经亲自用外部中断做过红外遥控器的数据接收,步进电机圆周运动的光电反馈信号检测,输液器里瞬间即逝的水滴信号,以及本节的模拟串口数据的接收,其实这些项目的原理都大同小异,会一样即可触类旁通其它的。     这一节要教大家四个知识点:    第一个:如何利用外部中断实现模拟串口数据的收发。    第二个:在退出外部中断函数时,必须通过软件把外部中断标志位IE0清零,否则在接收到的数据包最后面会多收到一个无效的字节0xFF。    第三个:实际做项目的时候,尽量利用单片机内部自带的集成串口,不到万不得已尽量不要用自制的模拟串口,如果非要用本节讲的模拟串口,那么一次接收的数据包不要太长,尽可能越短越好,因为自己做的模拟串口在稳定性上肯定比不上单片机自带的串口。这种模拟串口在批量生产时容易因为晶振的误差,以及外界各地温度的温差而影响产品的一致性,是有隐患的。 第四个:用模拟串口时,尽量不要选用动态数码管的显示方案,因为单片机在收发串口数据时,只能专心干一件事,此时不能中途被动态数码管扫描程序占用。而动态数码管得不到均匀扫描,就会产生略微闪烁的现象瑕疵。 具体内容,请看源代码讲解。 (1)硬件平台:     基于朱兆祺51单片机学习板。当把程序下载到单片机之后,要做以下跳线处理:    单片机原来的P3.1引脚是TI串口输出引脚,P3.0是RI串口输入引脚,分别把P3.1和P3.0的黄颜色跳冒去掉,同时也把外部中断0的引脚P3.2和一根IO口P1.0引脚的换颜色跳冒去掉,把P3.2跳冒的右针连接到P3.0跳冒的左针,作为模拟串口的接收数据线。把P1.0跳冒的右针连接到P3.1跳冒的左针,作为模拟串口的发送数据线。 (2)实现功能:     波特率是:9600 。 通过电脑串口调试助手模拟上位机,往单片机任意发送一串不超过10个的数据包,单片机如实地返回接收到的整包数据给上位机。 例如: (a)上位机发送数据:01 02 03 04 05 06 07 08 09 0A 单片机返回:    01 02 03 04 05 06 07 08 09 0A (b)上位机发送数据: 05 07 EE A8 F9 单片机返回:     05 07 EE A8 F9 (3)源代码讲解如下: #include "REG52.H" #define const_voice_short  40   //蜂鸣器短叫的持续时间 #define const_rc_size  20  //接收串口中断数据的缓冲区数组大小 #define const_receive_time  5  //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完,这个时间根据实际情况来调整大小 /* 注释一: * 以下时序脉冲延时参数我是在keil uVision2 平台下,Memory Model在small模式,Code Rom Size在Large模式下编译的, * 如果在不同keil版本,不同的模式下,编译出来的程序有可能此参数会不一样。 * 以下的时序脉冲延时参数是需要一步一步慢慢调的。我一开始的时候先编写一个简单的发送数据测试程序, * 先确调试出合适的发送时序延时数据。然后再编写串口接收数据的程序,从而调试出接收时序的延时参数。 * 比如:我第一步发送数据的测试程序是这样的: void main()   {    initial_myself();      delay_long(100);       initial_peripheral();    while(1)      {   //      usart_service();  //串口服务程序        eusart_send(0x08);    //测试程序,让它不断发送数据给上位机观察,确保发送延时时序的参数准确性            delay_long(300);        eusart_send(0xE5);    //测试程序,让它不断发送数据给上位机观察,确保发送延时时序的参数准确性            delay_long(300);    } } */ #define const_t_1  10  //发送时序延时1  第一步先调出此数据 #define const_t_2  9  //发送时序延时2   第一步先调出此数据 #define const_r_1  7  //接收时序延时1   第二步再调出此数据 #define const_r_2  9 //接收时序延时2  第二步再调出此数据 void initial_myself(void);    void initial_peripheral(void); void delay_long(unsigned int uiDelaylong); void delay_short(unsigned int uiDelayShort); void delay_minimum(unsigned char ucDelayMinimum);  //细分度最小的延时,用char类型一个字节 void T0_time(void);  //定时中断函数 void INT0_int(void);  //外部0中断函数,在本系统中是模拟串口的接收中断函数。 void usart_service(void);  //串口服务程序,在main函数里 void eusart_send(unsigned char ucSendData); unsigned char read_eusart_byte();//从串口读一个字节 sbit beep_dr=P2^7; //蜂鸣器的驱动IO口 sbit ti_dr=P1^0;  //模拟串口发送数据的IO口 sbit ri_sr=P3^2;  //模拟串口接收数据的IO口 也是外部中断0的复用IO口 unsigned int  uiSendCnt=0;     //用来识别串口是否接收完一串数据的计时器 unsigned char ucSendLock=1;    //串口服务程序的自锁变量,每次接收完一串数据只处理一次 unsigned int  uiRcregTotal=0;  //代表当前缓冲区已经接收了多少个数据 unsigned char ucRcregBuf[const_rc_size]; //接收串口中断数据的缓冲区数组 unsigned char ucTest=0; void main()   {    initial_myself();      delay_long(100);       initial_peripheral();    while(1)      {         usart_service();  //串口服务程序    } } void usart_service(void)  //串口服务程序,在main函数里 {      unsigned char i=0;         if(uiSendCnt>=const_receive_time&&ucSendLock==1) //说明超过了一定的时间内,再也没有新数据从串口来      {             ucSendLock=0;    //处理一次就锁起来,不用每次都进来,除非有新接收的数据             //下面的代码进入数据协议解析和数据处理的阶段                         for(i=0;i>1;      }      ti_dr=1;  //发送结束位      delay_short(400);  //每个字节之间的延时,这里非常关键,也是最容易出错的地方。延时的大小请根据实际项目来调整      EA=1;     //开总中断 } //从串口读取一个字节 unsigned char read_eusart_byte() {     unsigned char  ucReadData=0;     unsigned char  i=8;          delay_minimum(const_r_1);  //接收时序延时1 。作用是等过起始位  delay_minimum是本程序细分度最小的延时      while(i--)      {          ucReadData >>=1;          if(ri_sr==1)                  {                        ucReadData|=0x80;      //先收低位                  }          if(ri_sr==0) //此处空指令,是为了让驱动时序的时间保持一致性                  {                        ;                  }              delay_minimum(const_r_2);    //接收时序延时2    delay_minimum是本程序细分度最小的延时         }      return ucReadData; } void T0_time(void) interrupt 1    //定时中断 {   TF0=0;  //清除中断标志   TR0=0; //关中断   if(uiSendCntconst_rc_size)  //超过缓冲区    {            uiRcregTotal=const_rc_size;    }    ucRcregBuf[uiRcregTotal-1]=read_eusart_byte();   //将串口接收到的数据缓存到接收缓冲区里    uiSendCnt=0;  //及时喂狗,虽然main函数那边不断在累加,但是只要串口的数据还没发送完毕,那么它永远也长不大,因为每个中断都被清零。 /* 注释二: * 注意,此处必须把IE0中断标志清零,否则在接收到的数据包最后面会多收到一个无效的字节0xFF。 */    IE0=0;  //外部中断0标志位清零,必须的!    EX0=1;  //打开外部0中断 } void delay_long(unsigned int uiDelayLong) {    unsigned int i;    unsigned int j;    for(i=0;i
    19. 第六十六节:单片机外部中断的基础。 开场白: 外部中断是单片机非常重要的内部资源,应用很广,它是单片机的高速开关感应器输入接口,它可以检测脉冲输入,可以接收红外遥控器的输入信号,可以检测高速运转的车轮或者电机圆周运动的反馈信号,可以检测输液器里瞬间即逝的水滴信号,可以接收模拟串口的数据信息,等等。     这一节要教大家两个知识点:    第一个:外部中断的初始化代码和中断函数的基本程序模板。    第二个:当系统存在两种中断以上时,如何设置外部中断0为最高优先级,实现中断嵌套功能。 具体内容,请看源代码讲解。 (1)硬件平台:     基于朱兆祺51单片机学习板。用S1按键作为模拟外部中断0的下降沿脉冲输入。原来S1按键是直接连接到P0^0口的,因此必须通过跳线把P0^0口连接到单片机外部中断0专用IO口P3^2上,只需把P0^0和P3^2的两根黄颜色跳冒去掉,通过一根线把P0^0和P3^2相互连接起来即可。这时每按下一次S1按键,就会给P3^2口产生一个下降沿的脉冲,然后程序会自动跳到中断函数中执行一次。 (2)实现功能:     用数码管低4位显示记录当前的下降沿脉冲数。用S1按键经过跳线后模拟外部中断0的下降沿输入,每按一次数码管就会显示往上累加的脉冲数。由于按键按下去的时候有抖动,也就按一次可能产生几个脉冲,所以按一次往往看到数据一次加了三四个,这种实验现象都是正常的。 (3)源代码讲解如下: #include "REG52.H" #define const_voice_short  40   //蜂鸣器短叫的持续时间 #define const_key_time1  20    //按键去抖动延时的时间 void initial_myself();    void initial_peripheral(); void delay_short(unsigned int uiDelayShort); void delay_long(unsigned int uiDelaylong); //驱动数码管的74HC595 void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);   void display_drive(); //显示数码管字模的驱动函数 void display_service(); //显示的窗口菜单服务程序 //驱动LED的74HC595 void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01); void T0_time();  //定时中断函数 void INT0_int();//外部0中断函数 sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平 sbit beep_dr=P2^7; //蜂鸣器的驱动IO口 sbit dig_hc595_sh_dr=P2^0;     //数码管的74HC595程序 sbit dig_hc595_st_dr=P2^1;   sbit dig_hc595_ds_dr=P2^2;   unsigned char ucDigShow8;  //第8位数码管要显示的内容 unsigned char ucDigShow7;  //第7位数码管要显示的内容 unsigned char ucDigShow6;  //第6位数码管要显示的内容 unsigned char ucDigShow5;  //第5位数码管要显示的内容 unsigned char ucDigShow4;  //第4位数码管要显示的内容 unsigned char ucDigShow3;  //第3位数码管要显示的内容 unsigned char ucDigShow2;  //第2位数码管要显示的内容 unsigned char ucDigShow1;  //第1位数码管要显示的内容 unsigned char ucDigDot8;  //数码管8的小数点是否显示的标志 unsigned char ucDigDot7;  //数码管7的小数点是否显示的标志 unsigned char ucDigDot6;  //数码管6的小数点是否显示的标志 unsigned char ucDigDot5;  //数码管5的小数点是否显示的标志 unsigned char ucDigDot4;  //数码管4的小数点是否显示的标志 unsigned char ucDigDot3;  //数码管3的小数点是否显示的标志 unsigned char ucDigDot2;  //数码管2的小数点是否显示的标志 unsigned char ucDigDot1;  //数码管1的小数点是否显示的标志 unsigned char ucDigShowTemp=0; //临时中间变量 unsigned char ucDisplayDriveStep=1;  //动态扫描数码管的步骤变量 unsigned char ucWd1Update=1; //窗口1更新显示标志 unsigned char ucWd=1;  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。本程序只有一个显示窗口 unsigned int  uiPluseCnt=0;  //本程序中累加中断脉冲数的变量 unsigned char ucTemp1=0;  //中间过渡变量 unsigned char ucTemp2=0;  //中间过渡变量 unsigned char ucTemp3=0;  //中间过渡变量 unsigned char ucTemp4=0;  //中间过渡变量 //根据原理图得出的共阴数码管字模表 code unsigned char dig_table[]= { 0x3f,  //0       序号0 0x06,  //1       序号1 0x5b,  //2       序号2 0x4f,  //3       序号3 0x66,  //4       序号4 0x6d,  //5       序号5 0x7d,  //6       序号6 0x07,  //7       序号7 0x7f,  //8       序号8 0x6f,  //9       序号9 0x00,  //无      序号10 0x40,  //-       序号11 0x73,  //P       序号12 }; void main()   {    initial_myself();      delay_long(100);       initial_peripheral();    while(1)      {        display_service(); //显示的窗口菜单服务程序    } } void display_service() //显示的窗口菜单服务程序 {    switch(ucWd)  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。    {        case 1:   //显示第一个窗口的数据  本系统中只有一个显示窗口             if(ucWd1Update==1)  //窗口1要全部更新显示             {                ucWd1Update=0;  //及时清零标志,避免一直进来扫描                ucDigShow8=10;  //第8位数码管显示无                ucDigShow7=10;  //第7位数码管显示无                ucDigShow6=10;  //第6位数码管显示无                ucDigShow5=10;  //第5位数码管显示无               //先分解数据                ucTemp4=uiPluseCnt/1000;                     ucTemp3=uiPluseCnt%1000/100;                ucTemp2=uiPluseCnt%100/10;                ucTemp1=uiPluseCnt%10;                 //再过渡需要显示的数据到缓冲变量里,让过渡的时间越短越好               //以下增加的if判断就是略作修改,把整个4位数据中高位为0的去掉不显示。                if(uiPluseCnt
    20. 第六十五节:大数据的除法运算。 开场白: 直接用C语言的“/”运算符进行除法运算时,“被除数”,“ 除数”,“商”,这三个数据的最大范围是unsigned long 类型,也就是数据最大范围是4个字节,十进制的范围是0至4294967295。一旦超过了这个范围,则运算会出错。因此,当进行大数据除法运算时,我们要额外编程序,实现大数据的算法。其实这种算法并不难,就是我们在小学里学的四则运算算法。       我们先要弄清楚一个新的概念。不考虑小数点的情况下,数据有两种表现形式。一种是常用的变量形式,另外一种是BCD码数组形式。变量的最大范围有限,而BCD码数组的形式是无限的,正因为这个特点,所以我们可以进行大数据运算。     这一节要教大家一个知识点:    第一个:如何编写涉及到大数据除法运算的算法程序函数,同时也复习了指针的用途。 具体内容,请看源代码讲解。 (1)硬件平台:     基于朱兆祺51单片机学习板。 (2)实现功能: 波特率是:9600 。 通过电脑串口调试助手模拟上位机,往单片机发送组合BCD码的被除数和除数,单片机把组合BCD码的运算结果返回到上位机。被除数与除数的最大范围都是从0到9999,如果运算的商超过允许保存的最大位数范围或者除数为0,则返回EE EE EE报错。 往单片机发送的数据格式:EB 00 55 XX XX 0d  0a  YY YY 0d  0a指令,其中EB 00 55是数据头,XX XX是被除数,是1到2个字节的组合BCD码。YY YY是除数,是1到2个字节的组合BCD码。0d 0a是固定的结束标志。 例如: (a)9816 ÷ 8= 1227 上位机发送数据:eb 00 55 98 16 0d 0a 08 0d 0a 单片机返回:12 27 (b)9816 ÷ 0= 出错了,除数不能为0。 上位机发送数据:eb 00 55 98 16 0d 0a 00 0d 0a 单片机返回:EE EE EE    (3)源代码讲解如下: #include "REG52.H" /* 注释一: * 本系统中的除法运算,规定被除数和除数的最大范围是0至9999. * 由于STC89C52单片机的RAM只有256个,也就是说系统的变量数最大 * 不能超过256个,如果超过了这个极限,编译器就会报错。由于51单片机RAM资源有限, * 因此规定除数的最大范围不能超过9999,如果这个算法移植到stm32或者PIC等RAM比较大 * 的单片机上,那么就可以把这个运算位数设置得更加大一点。调整下面 BCD4_MAX的大小, * 可以调整运算的数据范围。 */ #define  BCD4_MAX     3  //调整BCD4_MAX的大小,可以调整运算的数据范围。 #define  BCD8_MAX    (BCD4_MAX*2)  //本系统中,规定的非组合BCD码能保存的最大字节数,一个字节包含1位有效运算数 #define const_rc_size  30  //接收串口中断数据的缓冲区数组大小 #define const_receive_time  5  //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完,这个时间根据实际情况来调整大小 #define uchar unsigned char    //方便移植平台 #define ulong unsigned long   //方便移植平台 //如果在VC的平台模拟此算法,则都定义成int类型,如下: //#define uchar int   //#define ulong int void initial_myself(void);    void initial_peripheral(void); void delay_long(unsigned int uiDelaylong); void delay_short(unsigned int uiDelayShort); void T0_time(void);  //定时中断函数 void usart_receive(void); //串口接收中断函数 void usart_service(void);  //串口服务程序,在main函数里 void eusart_send(unsigned char ucSendData); void BCD4_to_BCD8(const unsigned char *p_ucBCD_bit4,unsigned char ucBCD4_cnt,unsigned char *p_ucBCD_bit8,unsigned char *p_ucBCD8_cnt); void BCD8_to_BCD4(const unsigned char *p_ucBCD_bit8,unsigned char ucBCD8_cnt,unsigned char *p_ucBCD_bit4,unsigned char *p_ucBCD4_cnt); void ClearAllData(uchar ucARRAY_MAX,uchar *destData); uchar GetDataLength(const uchar *destData,uchar ucARRAY_MAX); uchar AddData(const uchar *destData,const uchar *sourceData,uchar *resultData);  //两个数相加 uchar CmpData(const uchar *destData,const uchar *sourceData); //比较两个数的大小 uchar SubData(const uchar *destData,const uchar *sourceData,uchar *resultData);//两个数相减 void EnlargeData(uchar *destData,uchar enlarge_cnt); //数组向大索引值移位,移一位相当于放大10倍 uchar MultData(const uchar *destData,const uchar *sourceData,uchar *resultData); //两个数相乘 uchar DivLessTenData(const uchar *destData,const uchar *sourceData,uchar *resultData,uchar *remData);//局部两个数相除,商不超过10。当商为0时,余数等于被除数 uchar Div(const uchar *destData,const uchar *sourceData,uchar *resultData);//两个数相除 sbit beep_dr=P2^7; //蜂鸣器的驱动IO口 unsigned int  uiSendCnt=0;     //用来识别串口是否接收完一串数据的计时器 unsigned char ucSendLock=1;    //串口服务程序的自锁变量,每次接收完一串数据只处理一次 unsigned int  uiRcregTotal=0;  //代表当前缓冲区已经接收了多少个数据 unsigned char ucRcregBuf[const_rc_size]; //接收串口中断数据的缓冲区数组 unsigned int  uiRcMoveIndex=0;  //用来解析数据协议的中间变量 unsigned char ucDataBCD4_1[BCD4_MAX]; //接收到的第1个数组合BCD码数组形式  这里是指被乘数 unsigned char ucDataBCD4_cnt_1=0;  //接收到的第1个数组合BCD码数组的有效数据长度 unsigned char ucDataBCD4_2[BCD4_MAX]; //接收到的第2个数组合BCD码数组形式  这里是指乘数 unsigned char ucDataBCD4_cnt_2=0;  //接收到的第2个数组合BCD码数组的有效数据长度 unsigned char ucDataBCD4_3[BCD4_MAX]; //接收到的第3个数组合BCD码数组形式  这里是指积 unsigned char ucDataBCD4_cnt_3=0;  //接收到的第3个数组合BCD码数组的有效数据长度 unsigned char ucDataBCD8_1[BCD8_MAX]; //接收到的第1个数非组合BCD码数组形式   这里是指被乘数 unsigned char ucDataBCD8_cnt_1=0;  //接收到的第1个数非组合BCD码数组的有效数据长度 unsigned char ucDataBCD8_2[BCD8_MAX]; //接收到的第2个数非组合BCD码数组形式   这里是指乘数 unsigned char ucDataBCD8_cnt_2=0;  //接收到的第2个数非组合BCD码数组的有效数据长度 unsigned char ucDataBCD8_3[BCD8_MAX]; //接收到的第3个数非组合BCD码数组形式   这里是指积 unsigned char ucDataBCD8_cnt_3=0;  //接收到的第3个数非组合BCD码数组的有效数据长度 unsigned char ucResultFlag=11; //运算结果标志,10代表计算结果超出范围出错,11代表正常。 void main()   {    initial_myself();      delay_long(100);       initial_peripheral();    while(1)      {        usart_service();  //串口服务程序    } } /* 注释二: * 组合BCD码转成非组合BCD码。 * 这里的变量ucBCD4_cnt代表组合BCD码的有效字节数. * 这里的变量*p_ucBCD8_cnt代表经过转换后,非组合BCD码的有效字节数,记得加地址符号&传址进去 * 本程序在上一节的基础上,略作修改,用循环for语句压缩了代码, * 同时引进了组合BCD码的有效字节数变量。这样就不限定了数据的长度, * 可以让我们根据数据的实际大小灵活运用。 */ void BCD4_to_BCD8(const unsigned char *p_ucBCD_bit4,unsigned char ucBCD4_cnt,unsigned char *p_ucBCD_bit8,unsigned char *p_ucBCD8_cnt) {    unsigned char ucTmep;    unsigned char i;    for(i=0;i4;          p_ucBCD_bit8[ucBCD4_cnt*2-i*2-2]=ucTmep&0x0f;      } } /* 注释三: * 非组合BCD码转成组合BCD码。 * 这里的变量ucBCD8_cnt代表非组合BCD码的有效字节数. * 这里的变量*p_ucBCD4_cnt代表经过转换后,组合BCD码的有效字节数,记得加地址符号&传址进去 * 本程序在上一节的基础上,略作修改,用循环for语句压缩了代码, * 同时引进了非组合BCD码的有效字节数变量。这样就不限定了数据的长度, * 可以让我们根据数据的实际大小灵活运用。 */ void BCD8_to_BCD4(const unsigned char *p_ucBCD_bit8,unsigned char ucBCD8_cnt,unsigned char *p_ucBCD_bit4,unsigned char *p_ucBCD4_cnt) {    unsigned char ucTmep;    unsigned char i;    unsigned char ucBCD4_cnt;    for(i=0;i

最近访客

< 1/1 >

统计信息

已有98人来访过

  • 芯积分:15
  • 好友:12
  • 主题:1
  • 回复:97

留言

你需要登录后才可以留言 登录 | 注册


犀利百分百 2014-9-25
你好,看了你写的程序感觉非常不错,想和你交流交流
犀利百分百 2014-9-25
你好,看了你写的程序感觉非常不错,想和你交流交流
查看全部