dj狂人 发表于 2014-11-29 10:27

给楼主赞一个

义剑仗乾坤 发表于 2014-12-14 17:57

赞一个!有时间慢慢学习!

jianhong_wu 发表于 2014-12-17 13:42

第八十三节:矩阵键盘输入任意数字或小数点的液晶屏显示程序。
开场白:本来这节打算讲调用液晶屏内部字库时让某行内容反显的,但是在昨天调试过程中,发现一个很奇怪的问题,当调用内部字库时,按照数据手册,我执行一条反显指令时,应该是仅仅某一行反显,但是却同时出现两行反显。比如,当我执行
       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_short40   //蜂鸣器短叫的持续时间

#define const_key_time10    //按键去抖动延时的时间

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口

sbitLCDCS_dr= P1^6;//片选线
sbitLCDSID_dr = P1^7;//串行数据线
sbitLCDCLK_dr = P3^2;//串行时钟线
sbitLCDRST_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 charucBitNumber); //把一位数字转换成字模首地址的函数
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={0,10,10,10,10,10}; //一项的BCD码数组缓冲


unsigned char ucKeyStep=1;//按键扫描步骤变量

unsigned char ucKeySec=0;   //被触发的按键编号
unsigned intuiKeyTimeCnt=0; //按键去抖动延时计数器
unsigned char ucKeyLock=0; //按键触发后自锁的变量标志

unsigned char ucRowRecord=1; //记录当前扫描到第几列了


unsigned intuiVoiceCnt=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<ucDotBitMax) //如果小数点位数还没超过最大限制位数,则继续加1记录当前小数点位数。
                        {
                            *p_ucDotBitS=(*p_ucDotBitS)+1;
                        }
                        else //如果小数点位数已经超过允许的范围,则输入的按键无效,直接退出。
                        {
                            return; //直接返回退出
                        }
                  }

      
                                  if(*p_ucWdPartCnt<(ucDataCntMax-1))//当输入的有效BCD码不超过最大数组缓冲时
                                  {               
                     if(*p_ucWdPartCnt==0&&p_ucSetDataBuffer==0&&ucKeyNumberTemp!=11)//如果当前默认位置是第0个位置,并且默认第0个数据是0,并且当前的按键输入不是小数点,则不用移位
                     {
                           ;
                     }       
                     else//否则,移位
                     {               
                           for(i=0;i<(ucDataCntMax-1);i++)//移位
                           {
                              p_ucSetDataBuffer=p_ucSetDataBuffer;
                           }
                                             *p_ucWdPartCnt=(*p_ucWdPartCnt)+1;
                     }
                     p_ucSetDataBuffer=ucKeyNumberTemp; //当前输入的数字或者小数点永远在第右边第0个位置。
                                                               
                  }

}


void key_delete_input(void) //删除按键
{
        static unsigned int i;

        switch(ucWd)
        {
          case 1:   //第1窗口。本节程序只有1个窗口
                   switch(ucPart)
                   {

             case 1://1窗口第1项
                  
                                //清零
                  ucDotBitS_1=0;
                  ucDotCnt_1=0;
                  ucWdPartCnt_1=0;               
                  for(i=0;i<6;i++)
                  {
                     ucDataBuffer_1=10;
                  }
                                        ucDataBuffer_1=0; //第0个位置填入0
       
                                  ucWd1Part1Update=1;//更新显示
                                  break;               
      
          }
                                       
                    break;
       
    }                       
                               
}

unsigned char *number_to_matrix(unsigned charucBitNumber)
{
    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;
                        case 11:   //小数点
             p_ucAnyNumber=Zf816_dot;
                     break;
                default:   //如果上面的条件都不符合,那么默认指向空字模
             p_ucAnyNumber=Zf816_nc;
                     break;
      }

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



void lcd_display_service(void) //应用层面的液晶屏显示程序
{


    static unsigned char *p_ucAnyNumber; //经过数字转换成字模后,分解变量的某位字模首地址
    static unsigned char ucCursorFlag;//光标标志,也就是反显的标志,它是根据局部变量ucPart来定的
    static unsigned int i;

    switch(ucWd)//本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
    {
      case 1:   //显示窗口1的数据
               if(ucWd1Update==1)//窗口1整屏更新,里面只放那些不用经常刷新显示的内容
               {
                     ucWd1Update=0;//及时清零,避免一直更新

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


                     display_clear(0x00); // 清屏操作, 全部显示空填充0x00,全部显示点阵用0xff。
                     clear_all_canvas();//把画布全部清零

                     display_lattice(0,0,Hz1616_yi,0,2,16,0);    //一窗口一行,这些内容不用经常更新,只有在切换窗口的时候才更新显示
                     display_lattice(1,0,Hz1616_xiang,0,2,16,0);   
                     display_lattice(2,0,Hz1616_shu,0,2,16,0);   
                     display_lattice(3,0,Hz1616_zhu,0,2,16,0);
                     display_lattice(4,0,Zf816_mao_hao,0,1,16,0); //冒号

               

               }

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

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

                        
                                                for(i=0;i<6;i++) //把每个数组缓冲的字模依次插入画布
                        {
                                                    p_ucAnyNumber=number_to_matrix(ucDataBuffer_1);
                        insert_buffer_to_canvas(i,0,p_ucAnyNumber,0,1,16);//这里的i是画布的横向地址,一共可以显示6个字符,因此取值范围是0到5
                        }

                        display_lattice(5,0,ucCanvasBuffer,ucCursorFlag,6,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<16;j++)//这里的16表示画布有16行
   {
      for(i=0;i<4;i++) //这里的4表示画布每行有4个字节
      {
                  ucCanvasBuffer=0x00;
      }
   }         

}





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

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

}

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

}

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

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




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

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


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

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

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

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



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


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




总结陈词:这节讲的是键盘输入数字或者小数点的BCD码用来显示,实际项目中,我们经常要知道所输入的BCD码数组到底有效数值是多少,这个该怎么办?欲知详情,请听下回分解----实时同步把键盘输入的BCD码数组转换成数值的液晶屏显示程序。
(未完待续,下节更精彩,不要走开哦)

jianhong_wu 发表于 2014-12-19 16:40

第八十四节:实时同步把键盘输入的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_short40   //蜂鸣器短叫的持续时间

#define const_key_time10    //按键去抖动延时的时间

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口

sbitLCDCS_dr= P1^6;//片选线
sbitLCDSID_dr = P1^7;//串行数据线
sbitLCDCLK_dr = P3^2;//串行时钟线
sbitLCDRST_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 charucBitNumber); //把一位数字转换成字模首地址的函数
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={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 intuiKeyTimeCnt=0; //按键去抖动延时计数器
unsigned char ucKeyLock=0; //按键触发后自锁的变量标志

unsigned char ucRowRecord=1; //记录当前扫描到第几列了


unsigned intuiVoiceCnt=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<ucDotBitMax) //如果小数点位数还没超过最大限制位数,则继续加1记录当前小数点位数。
                           {
                               *p_ucDotBitS=(*p_ucDotBitS)+1;
                           }
                           else //如果小数点位数已经超过允许的范围,则输入的按键无效,直接退出。
                           {
                              return; //直接返回退出
                           }
                                          }
                                                else if(*p_ucIntCnt<ucIntCntMax)//如果之前没有输入小数点,那么输入的就是整数个数超,整数个数没有超过极限
                                                {
                                                  *p_ucIntCnt=(*p_ucIntCnt)+1;
                                                }
                                                else //整数个数超过极限
                                                {
                           return; //直接返回退出
                                                }
                  }

            
            
                  if(*p_ucWdPartCnt==0&&p_ucSetDataBuffer==0&&ucKeyNumberTemp!=11)//如果当前默认位置是第0个位置,并且默认第0个数据是0,并且当前的按键输入不是小数点,则不用移位
                  {
                        ;
                  }      
                  else//否则,移位
                  {               
                     for(i=0;i<(ucDataCntMax-1);i++)//移位
                     {
                        p_ucSetDataBuffer=p_ucSetDataBuffer;
                     }
                     *p_ucWdPartCnt=(*p_ucWdPartCnt)+1;
                  }
                  p_ucSetDataBuffer=ucKeyNumberTemp; //当前输入的数字或者小数点永远在第右边第0个位置。
                                                               
      

}


/* 注释四:
* 本节的核心函数,值得好好研究!
* 功能:把一个带小数点的BCD码数组转换成一个long类型的数值。
* 第1个参数ucConverDataSize是这个数组的最大有效个数。
* 第2个参数ucConverDotCnt是这个数组要转换成的long数值带几个小数点
* 第3个参数*p_ucConverBuffer是具体此数组的数据
* 函数最后返回被转换的long数值。
*/
unsigned long buffer_to_data(unsigned char ucConverDataSize,unsigned char ucConverDotCnt,unsigned char *p_ucConverBuffer)
{
   unsigned long ulConverResult=0;
   unsigned long ulConverResultTemp=0;
   unsigned char ucConverResultBuffer; //因为本节内容的ucConverDataSize是6,所以取6.
   unsigned char i;
   unsigned char j;
   unsigned char ucConverFlag;

   for(i=0;i<ucConverDataSize;i++)
   {
      ucConverResultBuffer=0;//先把临时缓冲区清零
   }

   j=0;
   ucConverFlag=0;
   for(i=0;i<ucConverDataSize;i++)
   {
       if(p_ucConverBuffer==11) //小数点
       {
          ucConverFlag=i; //记录小数点的位置
       }
       else if(p_ucConverBuffer<10)
       {
          ucConverResultBuffer=p_ucConverBuffer;//提取数组中的有效数字
          j++;
       }


   }


   for(i=0;i<ucConverDataSize;i++)   //通过处理每一位从而合成一个long类型的数值
   {
       ulConverResultTemp=0;
       ulConverResultTemp=ucConverResultBuffer;
       for(j=0;j<i;j++)
       {
         ulConverResultTemp=ulConverResultTemp*10;//把每一位对应的进位扩大到对应的倍数
       }
       ulConverResult=ulConverResult+ulConverResultTemp;
   }


   for(i=ucConverFlag;i<ucConverDotCnt;i++) //根据数组小数点的位置和实际要转换成的小数点个数,来扩大到对应的倍数。
   {
      ulConverResult=ulConverResult*10;
   }

   return ulConverResult;
}

void key_delete_input(void) //删除按键
{
    static unsigned int i;

   switch(ucWd)
   {
      case 1:   //第1窗口。本节程序只有1个窗口
         switch(ucPart)
         {
            case 1://1窗口第1项
                  //清零
                                        ulData_1=0; //long数值清零
                                       ucIntCnt_1=0;
                  ucDotBitS_1=0;
                  ucDotCnt_1=0;
                  ucWdPartCnt_1=0;               
                  for(i=0;i<6;i++)
                  {
                     ucDataBuffer_1=10;
                  }
                  ucDataBuffer_1=0; //第0个位置填入0
      
                  ucWd1Part1Update=1;//第一行局部更新显示
                                  ucWd1Part2Update=1;//第二行局部更新显示
                  break;               
      
         }
                                       
         break;
      
   }                        
                              
}

unsigned char *number_to_matrix(unsigned charucBitNumber)
{
    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;
                        case 11:   //小数点
             p_ucAnyNumber=Zf816_dot;
                     break;
                default:   //如果上面的条件都不符合,那么默认指向空字模
             p_ucAnyNumber=Zf816_nc;
                     break;
      }

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



void lcd_display_service(void) //应用层面的液晶屏显示程序
{


    static unsigned char *p_ucAnyNumber; //经过数字转换成字模后,分解变量的某位字模首地址
    static unsigned char ucCursorFlag;//光标标志,也就是反显的标志,它是根据局部变量ucPart来定的
    static unsigned int i;
        static unsigned char ucDataBuffer_temp; //分解一个10进制的long类型数据的每一位

    switch(ucWd)//本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
    {
      case 1:   //显示窗口1的数据
               if(ucWd1Update==1)//窗口1整屏更新,里面只放那些不用经常刷新显示的内容
               {
                     ucWd1Update=0;//及时清零,避免一直更新

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

                     display_clear(0x00); // 清屏操作, 全部显示空填充0x00,全部显示点阵用0xff。
                     clear_all_canvas();//把画布全部清零

                     display_lattice(0,0,Hz1616_yi,0,2,16,0);    //一项数组
                     display_lattice(1,0,Hz1616_xiang,0,2,16,0);   
                     display_lattice(2,0,Hz1616_shu,0,2,16,0);   
                     display_lattice(3,0,Hz1616_zhu,0,2,16,0);
                     display_lattice(4,0,Zf816_mao_hao,0,1,16,0); //冒号

                     display_lattice(0,16,Hz1616_yi,0,2,16,0);    //一项数值
                     display_lattice(1,16,Hz1616_xiang,0,2,16,0);   
                     display_lattice(2,16,Hz1616_shu,0,2,16,0);   
                     display_lattice(3,16,Hz1616_zhi,0,2,16,0);
                     display_lattice(4,16,Zf816_mao_hao,0,1,16,0); //冒号            

               }

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

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

                        
                        for(i=0;i<6;i++) //把每个数组缓冲的字模依次插入画布
                        {
                            p_ucAnyNumber=number_to_matrix(ucDataBuffer_1);
                            insert_buffer_to_canvas(i,0,p_ucAnyNumber,0,1,16);//这里的i是画布的横向地址,一共可以显示6个字符,因此取值范围是0到5
                        }

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

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

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

                        if(ulData_1>=10000)
                                                {
                                                   ucDataBuffer_temp=ulData_1%100000/10000;
                                                }
                                                else
                                                {
                                                   ucDataBuffer_temp=10; //空格
                                                }

                        if(ulData_1>=1000)
                                                {
                                                      ucDataBuffer_temp=ulData_1%10000/1000;
                        }
                                                else
                                                {
                                                      ucDataBuffer_temp=10; //空格
                        }

                                                ucDataBuffer_temp=ulData_1%1000/100;
                                                ucDataBuffer_temp=11;//11代表小数点
                                                ucDataBuffer_temp=ulData_1%100/10;
                                                ucDataBuffer_temp=ulData_1%10/1;
                        
                        for(i=0;i<6;i++) //把每个数组缓冲的字模依次插入画布
                        {
                            p_ucAnyNumber=number_to_matrix(ucDataBuffer_temp);
                            insert_buffer_to_canvas(i,0,p_ucAnyNumber,0,1,16);//这里的i是画布的横向地址,一共可以显示6个字符,因此取值范围是0到5
                        }

                        display_lattice(5,16,ucCanvasBuffer,ucCursorFlag,6,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<16;j++)//这里的16表示画布有16行
   {
      for(i=0;i<4;i++) //这里的4表示画布每行有4个字节
      {
                  ucCanvasBuffer=0x00;
      }
   }         

}





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

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

}

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

}

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

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




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

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


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

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

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

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



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


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



总结陈词:    这节讲了把BCD码数组同步实时转换成数值的算法程序,相反,把数值转换成BCD码数组的逆运算程序应该怎么写?欲知详情,请听下回分解----实时同步把加减按键输入的数值转换成BCD码数组的液晶屏显示程序。
(未完待续,下节更精彩,不要走开哦)

ctscy2012 发表于 2014-12-21 11:21

向你学习,谢谢辛苦了。

随风暗影 发表于 2015-1-6 22:29

鸿哥,能加个好友吗?

apleilx 发表于 2015-1-8 09:50

jianhong_wu 发表于 2014-6-10 01:15
第一节:吴坚鸿谈初学单片机的误区。

(1)很难记住繁杂的寄存器?寄存器不用死记硬背,鸿哥我行走江湖多年,连一个寄存器都记不住。需要配置寄存器的时候,直接在网上或者书本上参考别人现成的配置程序是上策,查找芯片数据手册是中策,死记硬背寄存器是最最下策。

(2)很难记住繁杂的汇编语言指令?除非是在校学生要应付考试或者少数工作中绕不开汇编,否则学汇编就是浪费时间。鸿哥我行走江湖多年,从来就没有用汇编帮客户做过一个项目。

(3)C语言很难学?你不用学指针,你不用学带形参的函数,你不用学结构体,你不用学宏定义,你不用学文件操作,你也不用死记繁琐的数据类型。你只要会:
      5条指令语句switch语句,if else语句,while语句,for语句,=赋值语句。
      7个运算符+,-,*,/,|,&,!。
      4个逻辑关系符||,&&,!=,==.
      3个数据类型unsigned char, unsigned int, unsigned long。
      3个进制相互转化,二进制,十六进制,十进制。
      1个void函数。            
      1个一维数组code(或const) unsigned char array[]。
      那么世界上任何一种逻辑功能的单片机软件你都能做出来。
      鸿哥我当年刚毕业出来工作的时候才知道可以用C语言开发单片机,一开始只用if语句就把项目做出来了,没有用指针,没有用带形参的函数等复杂的功能。再到后来才慢慢开始用C语言其他的高级功能,但是我发现C语言其他的高级功能,本质上都是用我前面列举出来的最基本功能集合而成,只是书写更加简单方便了一点,编译后的机器码都大同小异。所以不会指针等高级功能你不用自卑,恰恰相反,当你会最简单的几个语句,就把这些高级功能的程序都做出来了,你才发现你对底层了解得更加透切,再学那些高级功能轻而易举。当你裸机跑的程序都能够协调得很好的时候,你才发现所谓高深的操作系统也不过如此,只要给你时间和金钱你也可以写个操作系统来玩玩。

(4)很难记住精确时间的计算公式?经常看到时间公式等于晶振,时钟周期,执行指令次数他们之间的乘除关系式。鸿哥我认为这些都是浮云,不用纠结也不用去记,大概了解一下就可以了。不管你对公式掌握得有多精确,你都不可能做出非常精确的时间。想用单片机做一个非常精确的时间这种想法一开始就是错的,不可能的。真想做一个比较精确的时间,应该用外围时钟芯片或者FPGA和CPLD,而不是单片机。

(5)很难记住繁杂的各种通信协议?什么IIC,SPI,232串口通讯,CAN,USB等等。这些都是浮云,你不用记那么多,你只要理解两种通讯方式就够了,那就是串行通讯方式和并行通讯方式。不管世界上有多少种通讯协议,物理世界上只有这两种通讯方式,其他各种名称的通讯协议都基于此两种方式演变而来。

(6)很难写短小精悍的程序?初学者不要纠结于此。做项目开发,程序容量不是刻意追求的目标,程序多一点少一点没关系,现在大容量的单片机品种非常多,容量不会是寸土寸金的事情,我们更加要关注程序的运行效率,可读性和可修改性。

      既然鸿哥列出了那么多误区,那么什么才是初学者关注的核心?预知详情,请听下回分解----delay()延时实现LED灯的闪烁。

(未完待续,下节更精彩,不要走开哦)指针和结构体之妙用无穷,居然是没用的东西?



master_kang 发表于 2015-1-9 18:38

这么好的文章,应该多多支持!我也有你说的那个朋友类似的经历,学了好久不得其门。希望你能够继续发文章!

qwdsegsgweer4t3 发表于 2015-1-9 20:17

哇,程序这么长啊,感谢大神的无私奉献,小弟学习一下啦。

jiamswei 发表于 2015-1-12 17:51

不错,谢谢楼主

GkJdman 发表于 2015-1-20 18:33

楼主神勇,点赞点赞~~~~~~~~~

GkJdman 发表于 2015-1-20 18:35

楼主好人,好好学习下~~~~~~~~

jk20141227 发表于 2015-2-7 22:13

初学者来学习

wellgod 发表于 2015-2-20 22:52

好东西,仔细阅读中

armxu 发表于 2015-2-21 07:41

换地方了?

zhou792008 发表于 2015-2-24 03:25

太好了!无法形容!多谢楼主

zhou792008 发表于 2015-2-24 03:26

太好了!谢谢楼主

lytl 发表于 2015-4-2 11:50

通俗易懂,很好的学习资料,谢谢!

lytl 发表于 2015-4-2 14:04

jianhong_wu 发表于 2014-6-10 01:18
第四节:累计定时中断次数使LED灯闪烁。

开场白:
上一节提到在累计主循环次数来实现计时,随着主函数里任务量的增加,为了保证延时时间的准确性,要不断修正设定上限阀值const_time_level 。我们该怎么解决这个问题呢?本节教大家利用累计定时中断次数的方法来解决这个问题。这一节要教会大家四个知识点:
第一点:利用累计定时中断次数的方法实现时间延时
第二点:展现鸿哥最完整的实战程序框架。在主函数循环里用switch语句实现状态机的切换,在定时中断里累计中断次数,这两个的结合就是我写代码最本质的框架思想。
第三点:提醒大家C语言中的int ,long变量是由几个字节构成的数据,凡是在main函数和中断函数里有可能同时改变的变量,这个变量应该在主函数中被更改之前,先关闭相应的中断,更改完了此变量,再打开中断,否则会留下不宜察觉的漏洞。当然在大部分的项目中可以不用这么操作,但是在一些要求非常高的项目中,有一些核心变量必须这么做。
第四点:定时中断的初始值该怎么设置。不用严格按公式来计算时间,一般取个经验值是最大初始值减去1000就可以了。
具体内容,请看源代码讲解。

(1)硬件平台:基于朱兆祺51单片机学习板。

(2)实现功能:让一个LED闪烁。

(3)源代码讲解如下:

总结陈词:
本节程序麻雀虽小五脏俱全。在本节中已经展示了我最完整的实战程序框架。
本节程序只有一个LED灯闪烁的单任务,如果要多增加一个任务来并行处理,该怎么办?
欲知详情,请听下回分解-----蜂鸣器的驱动程序。

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

不知道是不是有点错误还是我没看懂:uiTimeCnt>=const_time_level为假,不能执行if里面的语句;中断函数好像还没有调用。

盛夏之蝉 发表于 2015-4-3 02:26

本帖最后由 盛夏之蝉 于 2015-4-3 02:31 编辑

jianhong_wu 发表于 2014-7-6 11:12
第五十四节:指针的第二大好处,指针作为数组在函数中的输入接口。

开场白:
如果不会指针,当我们想把一个数组的数据传递进某个函数内部的时候,只能通过全局变量的方式,这种方法的缺点是阅读不直观,封装性不强,没有面对用户的输入接口。
针对以上问题,这一节要教大家一个知识点:通过指针,为函数增加一个数组输入接口。

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

(1)硬件平台:
基于朱兆祺51单片机学习板。

(2)实现功能:
把5个随机数据按从大到小排序,用冒泡法来排序。
通过电脑串口调试助手,往单片机发送EB 00 55 08 06 09 05 07指令,其中EB 00 55是数据头,08 06 09 05 07 是参与排序的5个随机原始数据。单片机收到指令后就会返回13个数据,最前面5个数据是第1种方法的排序结果,中间3个数据EE EE EE是第1种和第2种的分割线,为了方便观察,没实际意义。最后5个数据是第2种方法的排序结果.

比如电脑发送:EB 00 55 08 06 09 05 07
单片机就返回:09 08 07 06 05 EE EE EE 09 08 07 06 05

串口程序的接收部分请参考第39节。串口程序的发送部分请参考第42节。

波特率是:9600 。

(3)源代码讲解如下:

总结陈词:
第2种方法通过指针,为函数增加了一个数组输入接口,已经比第1种纯粹用全局变量的方法直观多了,但是还有一个小小的遗憾,因为它的输出排序结果仍然要靠全局变量。为了让函数更加完美,我们能不能为函数再增加一个输出接口?当然可以。欲知详情,请听下回分解-----指针的第三大好处,指针作为数组在函数中的输出接口。

(未完待续,下节更精彩,不要走开哦)
TH1=TL1=-(11059200L/12/32/9600);//这段配置代码具体是什么意思,我也不太清楚,反正是跟串口波特率有关。
我解释一下这个,给定时器1的2个寄存器赋值,11059200L/12/32/9600这个公式是计算在11.0592Mhz晶振的情况下产生9600bps需要给定时器加载的值是多少,这个有专门公式暂时不讨论,可能大家疑惑的主要是为什么有个-(负号)这个问题,正常来说赋值应该是,TH1=TL1=256-128;(假设需要加载的值为128,只是为了举例说明)。256和128在单片机中是以原码(正数的补码和原码一样)的形式存在即0x100和0x80,当进行减法运算时,实际上是256+(-128的补码)并且结果舍弃进位,由于256(0x100)低8位为0,结果又舍弃进位,所以实际运算结果就是(-128的补码)。
因为负数在单片机中以补码存在,所以-128单片机自动会转换为补码的形式,而TH1和TL1又是无符号的,单片机强制把(-128)的补码复制到TH1和TL1中,完毕。可以实际给大家算一下:
256-128=128,即0x80.而-128 的补码是首先取得128(0x80)的反码也就是(0111111B)0x7f,再加一,得-128的补码为0x80,把0x80按照无符号读取那么就是128.
继续一个例子:256-100(0x64)=156(0x9c)。-100的反码是(10011011B)0x9b,再加一,得-100的补码为0x9c,把0x9c按照无符号读取就是156.

页: 1 2 3 4 5 6 7 [8] 9 10
查看完整版本: 从业将近十年!手把手教你单片机程序框架(连载)