独闷闷网

标题: 从业将近十年!手把手教你单片机程序框架(连载) [打印本页]

作者: jianhong_wu    时间: 2013-12-17 15:40
标题: 从业将近十年!手把手教你单片机程序框架(连载)
本帖最后由 jianhong_wu 于 2016-5-15 20:18 编辑

      大家好,我是吴坚鸿。
      坚鸿51学习板原理图: 坚鸿51学习板(第三版).pdf (905.66 KB, 下载次数: 7490)

第一节:吴坚鸿谈初学单片机的误区

第二节:delay()延时实现LED灯的闪烁

第三节:累计主循环次数使LED灯闪烁

第四节:累计定时中断次数使LED灯闪烁

第五节:蜂鸣器的驱动程序

第六节:在主函数中利用累计主循环次数来实现独立按键的检测

第七节:在主函数中利用累计定时中断的次数来实现独立按键的检测

第八节:在定时中断函数里执行独立按键的扫描程序

第九节:独立按键的双击按键触发

第十节:两个独立按键的组合按键触发

第十一节:同一个按键短按与长按的区别触发。

第十二节:按住一个独立按键不松手的连续步进触发

第十三节:按住一个独立按键不松手的加速匀速触发。

第十四节:矩阵键盘的单个触发。

第十五节:矩阵键盘单个触发的压缩代码编程。

第十六节:矩阵键盘的组合按键触发。

第十七节:两片联级74HC595驱动16个LED灯的基本驱动程序。

第十八节:把74HC595驱动程序翻译成类似单片机IO口直接驱动的方式。

第十九节:依次逐个点亮LED之后,再依次逐个熄灭LED的跑马灯程序。

第二十节:依次逐个亮灯并且每次只能亮一个灯的跑马灯程序。

第二十一节:多任务并行处理两路跑马灯。

第二十二节:独立按键控制跑马灯的方向。

第二十三节:独立按键控制跑马灯的速度。

第二十四节:独立按键控制跑马灯的启动和暂停。

第二十五节:用LED灯和按键来模拟工业自动化设备的运动控制。

第二十六节:在主函数while循环中驱动数码管的动态扫描程序。

第二十七节:在定时中断里动态扫描数码管的程序。

第二十八节:数码管通过切换窗口来设置参数。

第二十九节:数码管通过切换窗口来设置参数,并且不显示为0的高位。

第三十节:数码管通过闪烁来设置数据。

第三十一节:数码管通过一二级菜单来设置数据的综合程序。

第三十二节:数码管中的倒计时程序。

第三十三节:能设置速度档位的数码管倒计时程序。

第三十四节:在数码管中实现iphone4S开机密码锁的程序。

第三十五节:带数码管显示的象棋比赛专用计时器。

第三十六节:带数码管显示的加法简易计算器。

第三十七节:数码管作为仪表盘显示跑马灯的方向,速度和运行状态。

第三十八节:判断数据尾来接收一串数据的串口通用程序框架。

第三十九节:判断数据头来接收一串数据的串口通用程序框架。

第四十节:常用的自定义串口通讯协议。

第四十一节:在串口接收中断里即时解析数据头的特殊程序框架。

第四十二节:通过串口用delay延时方式发送一串数据。

第四十三节:通过串口用计数延时方式发送一串数据。

第四十四节:从机的串口收发综合程序框架

第四十五节:主机的串口收发综合程序框架

第四十六节:利用AT24C02进行掉电后的数据保存。

第四十七节:操作AT24C02时,利用“一气呵成的定时器延时”改善数码管的闪烁现象。

第四十八节:利用DS1302做一个实时时钟  。

第四十九节:利用DS18B20做一个温控器  。

第五十节:利用ADC0832采集电压信号,用平均法和区间法进行软件滤波处理。

第五十一节:利用ADC0832采集电压信号,用连续N次一致性的方法进行滤波处理。

第五十二节:程序后续升级修改的利器,return语句鲜为人知的用法。

第五十三节:指针的第一大好处,让一个函数可以封装多个相当于return语句返回的参数。

第五十四节:指针的第二大好处,指针作为数组在函数中的输入接口。

第五十五节:指针的第三大好处,指针作为数组在函数中的输出接口。

第五十六节:指针的第四大好处,指针作为数组在函数中的输入输出接口。

第五十七节:为指针加上紧箍咒const,避免意外修改了只做输入接口的数据。

第五十八节:指针的第五大好处,指针在众多数组中的中转站作用。

第五十九节:串口程序第40,44,45节中存在一个bug,特此紧急公告。

第六十节:用关中断和互斥量来保护多线程共享的全局变量。

第六十一节:组合BCD码,非组合BCD码,以及数值三者之间的相互转换和关系。

第六十二节:大数据的加法运算。

第六十三节:大数据的减法运算。

第六十四节:大数据的乘法运算。

第六十五节:大数据的除法运算。

第六十六节:单片机外部中断的基础。

第六十七节:利用外部中断实现模拟串口数据的收发。

第六十八节:单片机C语言的多文件编程技巧。

第六十九节:使用static关键字可以减少全局变量的使用

第七十节:深入讲解液晶屏的构字过程。

第七十一节:液晶屏的字符,16点阵,24点阵和32点阵的显示程序。

第七十二节:在液晶屏中把字体顺时针旋转90度显示的算法程序。

第七十三节:在液晶屏中把字体镜像显示的算法程序。

第七十四节:在液晶屏中让字体可以跨区域无缝对接显示的算法程序。

第七十五节:在12864液晶屏中让字体以1个点阵为单位进行移动显示的算法程序。

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

第七十七节:在1个窗口里通过移动光标来设置不同参数的液晶屏菜单程序。

第七十八节:在多个窗口里通过移动光标来设置不同参数的液晶屏菜单程序。

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

第八十节:调用液晶屏内部字库来显示汉字或字符的坐标体系和本质。

第八十一节:液晶屏显示串口发送过来的任意汉字和字符。

第八十二节:如何通过调用液晶屏内部字库把一个任意数值的变量显示出来。

第八十三节:矩阵键盘输入任意数字或小数点的液晶屏显示程序。

第八十四节:实时同步把键盘输入的BCD码数组转换成数值的液晶屏显示程序。

第八十五节:实时同步把加减按键输入的数值转换成BCD码数组的液晶屏显示程序。

第八十六节:数字键盘与液晶菜单的综合程序。

第八十七节:郑文显捐赠的工控项目源代码。

第八十八节:电子称连续不断从串口对外发送数据,单片机靠关键字快速截取有效数据串。

第八十九节:用单片机内部定时器做一个时钟。

第九十节:针对行程开关感应器,分享一种既能及时响应,又能抗干扰处理的识别思路。

作者: jianhong_wu    时间: 2013-12-17 15:44
本帖最后由 jianhong_wu 于 2014-6-20 00:46 编辑

第一节:吴坚鸿谈初学单片机的误区。

(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灯的闪烁。

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



作者: jianhong_wu    时间: 2013-12-17 15:45
本帖最后由 jianhong_wu 于 2014-6-20 00:47 编辑

第二节:delay()延时实现LED灯的闪烁。

开场白:                
       上一节鸿哥列出了初学者六大误区,到底什么才是初学者关注的核心?那就是裸机奔跑的程序结构。一个好的程序结构,本身就是一个微型的多任务操作系统。鸿哥教给大家的就是如何编写这个简单的操作系统。在main函数循环中用switch语句实现多任务并行处理的任务切换,再外加一个定时器中断,这两者的结合就是鸿哥多年来所有实战项目的核心。鸿哥的程序结构看似简单,实际上就是那么简单。大家不用着急,本篇连载文章现在才正式开始,这一节我要教会大家两个知识点:
第一点:鸿哥首次提出的“三区一线”理论。此理论把程序代码分成三个区,一个延时分割线。
第二点:delay()延时的用途。

(1)硬件平台:基于坚鸿51单片机学习板。

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

(3)源代码讲解如下:

#include "REG52.H"

void initial_myself();   
void initial_peripheral();

void delay_short(unsigned int uiDelayshort);
void delay_long(unsigned int uiDelaylong);
void led_flicker();

/* 注释一:
* 吴坚鸿个人的命名风格:凡是输出后缀都是_dr,凡是输入后缀都是_sr。
* dr代表drive驱动,sr代表sensor感应器
*/
sbit led_dr=P3^5;  

void main()  //学习要点:深刻理解鸿哥首次提出的三区一线理论
  {
/* 注释二:
* initial_myself()函数属于鸿哥三区一线理论的第一区,
* 专门用来初始化单片机自己的寄存器以及个别外围要求响应速度快的输出设备,
* 防止刚上电之后,由于输出IO口电平状态不确定而导致外围设备误动作,
* 比如继电器的误动作等等。
*/
   initial_myself();

/* 注释三:
* 此处的delay_long()延时函数属于第一区与第二区的分割线,
* 延时时间一般是0.3秒到2秒之间,等待外围芯片和模块上电稳定。
* 比如液晶模块,AT24C02存储芯片,DS1302时钟芯片,
* 这类芯片有个特点,一般都是跟单片机进行串口或并口通讯的,
* 并且不要求上电立即处理的。
*/
   delay_long(100);

/* 注释四:
* initial_peripheral()函数属于鸿哥三区一线理论的第二区,
* 专门用来初始化不要求上电立即处理的外围芯片和模块.
* 比如液晶模块,AT24C02存储芯片,DS1302时钟芯片。
* 本程序基于坚鸿51单片机学习板。
*/
   initial_peripheral();

/* 注释五:
* while(1){}主函数循环区属于鸿哥三区一线理论的第三区,
* 专门用来编写被循环扫描到的非中断应用程序
*/
   while(1)
   {
      led_flicker();   //LED闪烁应用程序
   }

}

void led_flicker() //LED闪烁应用程序
{
  led_dr=1;  //LED亮
  delay_short(50000);  //延时50000个空指令的时间

/* 注释六:
* delay_long(100)延时50000个空指令的时间,因为内嵌了一个500次的for循环
*/
  led_dr=0;  //LED灭
  delay_long(100);    //延时50000个空指令的时间  
}


/* 注释七:
* delay_short(unsigned int uiDelayShort)是小延时函数,
* 专门用在时序驱动的小延时,一般uiDelayShort的数值取10左右,
* 最大一般也不超过100.本例为了解释此函数的特点,取值范围超过100。
* 此函数的特点是时间的细分度高,延时时间不宜过长。uiDelayShort数值
* 的大小就代表里面执行了多少条空指令的时间。数值越大,延时越长。
* 时间精度不要刻意去计算,感觉差不多就行。
*/
void delay_short(unsigned int uiDelayShort)
{
   unsigned int i;  
   for(i=0;i<uiDelayShort;i++)
   {
     ;   //一个分号相当于执行一条空语句
   }
}


/* 注释八:
* delay_long(unsigned int uiDelayLong)是大延时函数,
* 专门用在上电初始化的大延时,
* 此函数的特点是能实现比较长时间的延时,细分度取决于内嵌for循环的次数,
* uiDelayLong的数值的大小就代表里面执行了多少次500条空指令的时间。
* 数值越大,延时越长。时间精度不要刻意去计算,感觉差不多就行。
*/
void delay_long(unsigned int uiDelayLong)
{
   unsigned int i;
   unsigned int j;
   for(i=0;i<uiDelayLong;i++)
   {
      for(j=0;j<500;j++)  //内嵌循环的空指令数量
          {
             ; //一个分号相当于执行一条空语句
          }
   }
}



void initial_myself()  //初始化单片机
{
  led_dr=0;  //LED灭
}
void initial_peripheral() //初始化外围
{
  ;   //本例为空
}

总结陈词:
鸿哥首次提出的“三区一线”理论概况了各种项目程序的基本分区。我后续的程序就按此分区编写。
Delay()函数的长延时适用在上电初始化。
Delay()函数的短延时适用在驱动时序的脉冲延时,此时的时间不能太长,本例中暂时没有列出这方面的例子,在后面的章节中会提到。
在本例源代码中,在led_flicker()闪烁应用程序里用到的两个延时delay,它们的延时时间都太长了,在实战项目中肯定不能用这种延时,因为消耗的时间太长了,其它任务根本没有机会执行。那怎么办呢?我们应该如何改善?欲知详情,请听下回分解-----累计主循环次数使LED灯闪烁。

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





作者: jianhong_wu    时间: 2013-12-17 15:46
第三节:累计主循环次数使LED灯闪烁。

开场白:
上一节鸿哥提到delay()延时函数消耗的时间太长了,其它任务根本没有机会执行,我们该怎么改善?本节教大家利用累计主循环次数的方法来解决这个问题。这一节要教会大家两个知识点:
第一点:利用累计主循环次数的方法实现时间延时
第二点:switch核心语句之初体验。 鸿哥所有的实战项目都是基于switch语句实现多任务并行处理。
(1)硬件平台:基于坚鸿51单片机学习板。
(2)实现功能:让一个LED闪烁。
(3)源代码讲解如下:
#include "REG52.H"


/* 注释一:
* const_time_level是统计循环次数的设定上限,数值越大,LED延时的时间越久
*/
#define const_time_level 10000  

void initial_myself();   
void initial_peripheral();
void delay_long(unsigned int uiDelaylong);
void led_flicker();

sbit led_dr=P3^5;  

/* 注释二:
* 吴坚鸿个人的命名风格:凡是switch语句里面的步骤变量后缀都是Step.
* 前缀带uc,ui,ul分别表示此变量是unsigned char,unsigned int,unsigned long.
*/
unsigned char ucLedStep=0; //步骤变量
unsigned int  uiTimeCnt=0; //统计循环次数的延时计数器
void main()
  {
   initial_myself();  
   delay_long(100);   
   initial_peripheral();
   while(1)   
   {
      led_flicker();   
   }

}

void led_flicker() ////第三区 LED闪烁应用程序
{
  
  switch(ucLedStep)
  {
     case 0:
/* 注释三:
* uiTimeCnt累加循环次数,只有当它的次数大于或等于设定上限const_time_level时,
* 才会去改变LED灯的状态,否则CPU退出led_flicker()任务,继续快速扫描其他的任务,
* 这样的程序结构就可以达到多任务并行处理的目的。
* 本程序基于坚鸿51单片机学习板
*/
          uiTimeCnt++;  //累加循环次数,
                  if(uiTimeCnt>=const_time_level) //时间到
                  {
                     uiTimeCnt=0; //时间计数器清零
             led_dr=1;    //让LED亮
                         ucLedStep=1; //切换到下一个步骤
                  }
              break;
     case 1:
          uiTimeCnt++;  //累加循环次数,
                  if(uiTimeCnt>=const_time_level) //时间到
                  {
                     uiTimeCnt=0; //时间计数器清零
             led_dr=0;    //让LED灭
                         ucLedStep=0; //返回到上一个步骤
                  }
              break;
  
  }

}


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


void initial_myself()  //第一区 初始化单片机
{
  led_dr=0;  //LED灭
}
void initial_peripheral() //第二区 初始化外围
{
  ;   //本例为空
}

总结陈词:
    在实际项目中,用累计主循环次数实现时间延时是一个不错的选择。这种方法能胜任多任务处理的程序框架,但是它本身也有一个小小的不足。随着主函数里任务量的增加,我们为了保证延时时间的准确性,要不断修正设定上限const_time_level 。我们该怎么解决这个问题呢?欲知详情,请听下回分解-----累计定时中断次数使LED灯闪烁。

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

作者: jianhong_wu    时间: 2013-12-17 15:47
第四节:累计定时中断次数使LED灯闪烁。

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

(1)硬件平台:基于坚鸿51单片机学习板。

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

(3)源代码讲解如下:

#include "REG52.H"

#define const_time_level 200  

void initial_myself();   
void initial_peripheral();
void delay_long(unsigned int uiDelaylong);
void led_flicker();
void T0_time();  //定时中断函数

sbit led_dr=P3^5;  

unsigned char ucLedStep=0; //步骤变量
unsigned int  uiTimeCnt=0; //统计定时中断次数的延时计数器


void main()
  {
   initial_myself();  
   delay_long(100);   
   initial_peripheral();
   while(1)   
   {
      led_flicker();   
   }

}

void led_flicker() ////第三区 LED闪烁应用程序
{
  
  switch(ucLedStep)
  {
     case 0:
/* 注释一:
* uiTimeCnt累加定时中断的次数,每一次定时中断它都会在中断函数里自加一。
* 只有当它的次数大于或等于设定上限const_time_level时,
* 才会去改变LED灯的状态,否则CPU退出led_flicker()任务,继续快速扫描其他的任务,
* 这样的程序结构就可以达到多任务并行处理的目的。这就是鸿哥在所有开发项目中的核心框架。
*/
                  if(uiTimeCnt>=const_time_level) //时间到
                  {

/* 注释二:
* ET0=0;uiTimeCnt=0;ET0=1;----在清零uiTimeCnt之前,为什么要先禁止定时中断?
* 因为uiTimeCnt是unsigned int类型,本质上是由两个字节组成。
* 在C语言中uiTimeCnt=0看似一条指令,实际上经过编译之后它不只一条汇编指令。
* 由于定时中断函数里也对这个变量进行累加操作,如果不禁止定时中断,
* 那么uiTimeCnt这个变量在main()函数中还没被完全清零的时候,如果这个时候
* 突然来一个定时中断,并且在中断里又更改了此变量,这种情况在某些要求高的
* 项目上会是一个不容易察觉的漏洞,为项目带来隐患。当然,大部分的普通项目,
* 都可以不用那么严格,可以不用禁止定时中断。在这里只是提醒各位初学者有这种情况。
*/
             ET0=0;  //禁止定时中断
                     uiTimeCnt=0; //时间计数器清零
             ET0=1; //开启定时中断
             led_dr=1;    //让LED亮
                         ucLedStep=1; //切换到下一个步骤
                  }
              break;
     case 1:
                  if(uiTimeCnt>=const_time_level) //时间到
                  {
             ET0=0;  //禁止定时中断
                     uiTimeCnt=0; //时间计数器清零
             ET0=1;   //开启定时中断
             led_dr=0;    //让LED灭
                         ucLedStep=0; //返回到上一个步骤
                  }
              break;
  
  }

}


/* 注释三:
* C51的中断函数格式如下:
* void 函数名() interrupt 中断号
* {
*    中断程序内容
* }
* 函数名可以随便取,只要不是编译器已经征用的关键字。
* 这里最关键的是中断号,不同的中断号代表不同类型的中断。
* 定时中断的中断号是 1.至于其它中断的中断号,大家可以查找
* 相关书籍和资料。大家进入中断时,必须先清除中断标志,并且
* 关闭中断,然后再写代码,最后出来时,记得重装初始值,并且
* 打开中断。
*/
void T0_time() interrupt 1
{
  TF0=0;  //清除中断标志
  TR0=0; //关中断

  if(uiTimeCnt<0xffff)  //设定这个条件,防止uiTimeCnt超范围。
  {
      uiTimeCnt++;  //累加定时中断的次数,
  }

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


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


void initial_myself()  //第一区 初始化单片机
{

/* 注释四:
* 单片机有几个定时器,每个定时器又有几种工作方式,
* 那么多种变化,我们记不了那么多,怎么办?
* 大家记住鸿哥的话,无论一个单片机有多少内置资源,
* 我们做系统框架的,只需要一个定时器,一种工作方式。
* 开定时器越多这个系统越不好。需要哪种定时工作方式呢?
* 就需要响应定时中断后重装一下初始值继续跑那种。
* 在51单片机中就是工作方式1。其它的工作方式很少项目能用到。
*/
  TMOD=0x01;  //设置定时器0为工作方式1


  /* 注释五:
* 装定时器的初始值,就像一个水桶里装的水。如果这个桶是空桶,那么想
* 把这个桶灌满水的时间就很长,如果是里面已经装了大半的水,那么想
* 把这个桶灌满水的时间就相对比较短。也就是定时器初始值越小,产生一次
* 定时中断的时间就越长。如果初始值太小了,每次产生定时中断
* 的时间分辨率太粗,如果初始值太大了,虽然每次产生定时中断的时间分辨率很细,
* 但是太频繁的产生中断,不但会影响主函数main()的执行效率,而且累记中断次数
* 的时间误差也会很大。凭鸿哥多年的江湖经验,
* 我觉得最大初始值减去2000是比较好的经验值。当然,大一点小一点没关系。不要走
* 两个极端就行。
*/
TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
TL0=0x2f;

  led_dr=0;  //LED灭
}
void initial_peripheral() //第二区 初始化外围
{
  EA=1;     //开总中断
  ET0=1;    //允许定时中断
  TR0=1;    //启动定时中断

}

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

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

作者: jianhong_wu    时间: 2013-12-17 15:48
第五节:蜂鸣器的驱动程序。

开场白:
上一节讲了利用累计定时中断次数实现LED灯闪烁,这个例子同时也第一次展示了我最完整的实战程序框架:用switch语句实现状态机,外加定时中断。这个框架看似简单,实际上就是那么简单。我做的所有开发项目都是基于这个简单框架,但是非常好用。上一节只有一个单任务的LED灯在闪烁,这节开始,我们多增加一个蜂鸣器报警的任务,要教会大家四个知识点:
第一点:蜂鸣器的驱动程序框架编写。
第二点:多任务处理的程序框架。
第三点:如何控制蜂鸣器声音的长叫和短叫。
第四点:如何知道1秒钟需要多少个定时中断,也就是如何按比例修正时间精度。

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

(1)硬件平台:基于坚鸿51单片机学习板。

(2)实现功能:同时跑两个任务,第一个任务让一个LED灯1秒钟闪烁一次。第二个任务让蜂鸣器在前面3秒发生一次短叫报警,在后面6秒发生一次长叫报警,反复循环。

(3)源代码讲解如下:

#include "REG52.H"

/* 注释一:
* 如何知道1秒钟需要多少个定时中断?
* 这个需要编写一段小程序测试,得到测试的结果后再按比例修正。
* 步骤:
* 第一步:在程序代码上先写入1秒钟大概需要200个定时中断。
* 第二步:基于以上1秒钟的基准,编写一个60秒的简单测试程序(如果编写超过
* 60秒的时间,这个精度还会更高)。比如,编写一个用蜂鸣器的声音来识别计时的
* 起始和终止的测试程序。
* 第三步:把程序烧录进单片机后,上电开始测试,手上同步打开手机里的秒表。
*         如果单片机仅仅跑了27秒。
* 第四步:那么最终得出1秒钟需要的定时中断次数是:const_time_1s=(200*60)/27=444
*/
#define const_time_05s 222   //0.5秒钟的时间需要的定时中断次数
#define const_time_1s 444   //1秒钟的时间需要的定时中断次数
#define const_time_3s 1332   //3秒钟的时间需要的定时中断次数
#define const_time_6s 2664   //6秒钟的时间需要的定时中断次数

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

void initial_myself();   
void initial_peripheral();
void delay_long(unsigned int uiDelaylong);
void led_flicker();
void alarm_run();   
void T0_time();  //定时中断函数

sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
sbit led_dr=P3^5;  //LED灯的驱动IO口

unsigned char ucLedStep=0; //LED灯的步骤变量
unsigned int  uiTimeLedCnt=0; //LED灯统计定时中断次数的延时计数器

unsigned char ucAlarmStep=0; //报警的步骤变量
unsigned int  uiTimeAlarmCnt=0; //报警统计定时中断次数的延时计数器

unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器

void main()
  {
   initial_myself();  
   delay_long(100);   
   initial_peripheral();
   while(1)  
   {
      led_flicker();  //第一个任务LED灯闪烁
          alarm_run();    //第二个任务报警器定时报警
   }

}

void led_flicker() //第三区 LED闪烁应用程序
{
  
  switch(ucLedStep)
  {
     case 0:

           if(uiTimeLedCnt>=const_time_05s) //时间到
           {
             uiTimeLedCnt=0; //时间计数器清零
             led_dr=1;    //让LED亮
             ucLedStep=1; //切换到下一个步骤
           }
           break;
     case 1:
           if(uiTimeLedCnt>=const_time_05s) //时间到
           {
              uiTimeLedCnt=0; //时间计数器清零
              led_dr=0;    //让LED灭
              ucLedStep=0; //返回到上一个步骤
           }
           break;
  }

}

void alarm_run() //第三区 报警器的应用程序
{
  
  switch(ucAlarmStep)
  {
     case 0:

           if(uiTimeAlarmCnt>=const_time_3s) //时间到
           {
             uiTimeAlarmCnt=0; //时间计数器清零
/* 注释二:
* 只要变量uiVoiceCnt不为0,蜂鸣器就会在定时中断函数里启动鸣叫,并且自减uiVoiceCnt
* 直到uiVoiceCnt为0时才停止鸣叫。因此控制uiVoiceCnt变量的大小就是控制声音的长短。
*/
             uiVoiceCnt=const_voice_short;  //蜂鸣器短叫
             ucAlarmStep=1; //切换到下一个步骤
           }
           break;
     case 1:
           if(uiTimeAlarmCnt>=const_time_6s) //时间到
           {
              uiTimeAlarmCnt=0; //时间计数器清零
              uiVoiceCnt=const_voice_long;  //蜂鸣器长叫
              ucAlarmStep=0; //返回到上一个步骤
           }
           break;
  }

}

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

  if(uiTimeLedCnt<0xffff)  //设定这个条件,防止uiTimeLedCnt超范围。
  {
      uiTimeLedCnt++;  //LED灯的时间计数器,累加定时中断的次数,
  }

  if(uiTimeAlarmCnt<0xffff)  //设定这个条件,防止uiTimeAlarmCnt超范围。
  {
      uiTimeAlarmCnt++;  //报警的时间计数器,累加定时中断的次数,
  }


/* 注释三:
* 为什么不把驱动蜂鸣器这段代码放到main函数的循环里去?
* 因为放在定时中断里,能保证蜂鸣器的声音长度是一致的,
* 如果放在main循环里,声音的长度就有可能受到某些必须
* 一气呵成的任务干扰,得不到及时响应,影响声音长度的一致性。
*/


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


void initial_myself()  //第一区 初始化单片机
{
  beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
  led_dr=0;  //LED灭

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


  TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
  TL0=0x2f;

}
void initial_peripheral() //第二区 初始化外围
{
  EA=1;     //开总中断
  ET0=1;    //允许定时中断
  TR0=1;    //启动定时中断

}

总结陈词:
本节程序已经展示了一个多任务处理的基本思路,假如要实现一个独立按键检测,能不能也按照这种思路来处理呢?欲知详情,请听下回分解-----在主函数中利用累计主循环次数来实现独立按键的检测。

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

作者: jianhong_wu    时间: 2013-12-17 15:49
本帖最后由 jianhong_wu 于 2014-1-8 02:58 编辑

第六节:在主函数中利用累计主循环次数来实现独立按键的检测。

开场白:
上一节讲了多任务中蜂鸣器驱动程序的框架,这节继续利用多任务处理的方式,在主函数中利用累计主循环次数来实现独立按键的检测。要教会大家四个知识点:
第一点:独立按键的驱动程序框架。
第二点:用累计主循环次数来实现去抖动的延时。
第三点:灵活运用防止按键不松手后一直触发的按键自锁标志。
第四点:在按键去抖动延时计时中,添加一个抗干扰的软件监控判断。一旦发现瞬间杂波干扰,马上把延时计数器清零。这种方法是我在复杂的工控项目中总结出来的。以后凡是用到开关感应器的地方,都可以用类似的方法实现软件上的抗干扰处理。

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

(1)硬件平台:基于坚鸿51单片机学习板。用矩阵键盘中的S1和S5号键作为独立按键,记得把输出线P0.4一直输出低电平,模拟独立按键的触发地GND。

(2)实现功能:有两个独立按键,每按一个独立按键,蜂鸣器发出“滴”的一声后就停。

(3)源代码讲解如下:
#include "REG52.H"

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


/* 注释一:
* 调整抖动时间阀值的大小,可以更改按键的触发灵敏度。
* 去抖动的时间本质上等于累计主循环次数的时间。
*/
#define const_key_time1  500    //按键去抖动延时的时间
#define const_key_time2  500    //按键去抖动延时的时间

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; //对应朱兆祺学习板的S1键
sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键
sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平

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

unsigned char ucKeySec=0;   //被触发的按键编号

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

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

unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器

void main()
  {
   initial_myself();  
   delay_long(100);   
   initial_peripheral();
   while(1)  
   {
       key_scan(); //按键扫描函数
       key_service(); //按键服务的应用程序
   }

}

void key_scan()//按键扫描函数
{  
/* 注释二:
* 独立按键扫描的详细过程:
* 第一步:平时没有按键被触发时,按键的自锁标志和去抖动延时计数器一直被清零。
* 第二步:一旦有按键被按下,去抖动延时计数器开始累加,在还没累加到
*         阀值const_key_time1时,如果在这期间由于受外界干扰或者按键抖动,而使
*         IO口突然瞬间触发成高电平,这个时候马上又把延时计数器uiKeyTimeCnt1
*         清零了,这个过程非常巧妙,非常有效地去除瞬间的杂波干扰。这是我实战中摸索出来的。
*         以后凡是用到开关感应器的时候,都可以用类似这样的方法去干扰。
* 第三步:如果按键按下的时间超过了阀值const_key_time1,则触发按键,把编号ucKeySec赋值。
*         同时,马上把自锁标志ucKeyLock1置位,防止按住按键不松手后一直触发。
* 第四步:等按键松开后,自锁标志ucKeyLock1及时清零,为下一次自锁做准备。
* 第五步:以上整个过程,就是识别按键IO口下降沿触发的过程。
*/
  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;     //触发2号键
     }
  }

}


void key_service() //第三区 按键服务的应用程序
{
  switch(ucKeySec) //按键服务状态切换
  {
    case 1:// 1号键 对应朱兆祺学习板的S1键

              uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
              ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;        
    case 2:// 2号键 对应朱兆祺学习板的S5键

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



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

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


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() //第二区 初始化外围
{
  EA=1;     //开总中断
  ET0=1;    //允许定时中断
  TR0=1;    //启动定时中断

}

总结陈词:
    本节程序已经展示了在主函数中,利用累计主循环次数来实现独立按键的检测。这种方法我经常在实战用应用,但是它也有一个小小的不足,随着在主函数循环中任务量的增加,为了保证去抖动延时的时间一致性,要适当调整一下去抖动的阀值const_key_time1。如何解决这个问题呢?欲知详情,请听下回分解-----在主函数中利用累计定时中断的次数来实现独立按键的检测。

(未完待续,下节更精彩,不要走开哦)
作者: jianhong_wu    时间: 2013-12-17 15:50
本帖最后由 jianhong_wu 于 2014-1-8 02:58 编辑

第七节:在主函数中利用累计定时中断的次数来实现独立按键的检测。

开场白:
上一节讲了在主函数中利用累计主循环次数来实现独立按键的检测,但是它也有一个小小的不足,随着在主函数中任务量的增加,为了保证去抖动延时的时间一致性,要适当调整一下去抖动的时间阀值const_key_time1。如何解决这个问题呢?这一节教大家在主函数中利用累计定时中断的次数来实现独立按键的检测,可以有效地避免这个问题。要教会大家一个知识点:如何在上一节的基础上,略作修改,就可以在主函数中,利用累计定时中断的次数来实现去抖动的延时。

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

(1)硬件平台:基于坚鸿51单片机学习板。用矩阵键盘中的S1和S5号键作为独立按键,记得把输出线P0.4一直输出低电平,模拟独立按键的触发地GND。

(2)实现功能:有两个独立按键,每按一个独立按键,蜂鸣器发出“滴”的一声后就停。

(3)源代码讲解如下:
#include "REG52.H"

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


/* 注释一:
* 调整抖动时间阀值的大小,可以更改按键的触发灵敏度。
* 去抖动的时间本质上等于累计定时中断次数的时间。
*/
#define const_key_time1  30    //按键去抖动延时的时间
#define const_key_time2  30    //按键去抖动延时的时间

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; //对应朱兆祺学习板的S1键
sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键
sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平

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

unsigned char ucKeySec=0;   //被触发的按键编号

unsigned char ucKeyStartFlag1=0; //启动定时中断计数的开关
unsigned int  uiKeyTimeCnt1=0; //按键去抖动延时计数器
unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志

unsigned char ucKeyStartFlag2=0; //启动定时中断计数的开关
unsigned int  uiKeyTimeCnt2=0; //按键去抖动延时计数器
unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志

unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器

void main()
  {
   initial_myself();  
   delay_long(100);   
   initial_peripheral();
   while(1)  
   {
       key_scan(); //按键扫描函数
       key_service(); //按键服务的应用程序
   }

}

void key_scan()//按键扫描函数
{  
/* 注释二:
* 独立按键扫描的详细过程:
* 第一步:平时没有按键被触发时,按键的自锁标志,计时器开关和去抖动延时计数器一直被清零。
* 第二步:一旦有按键被按下,启动计时器,去抖动延时计数器开始在定时中断函数里累加,在还没累加到
*         阀值const_key_time1时,如果在这期间由于受外界干扰或者按键抖动,而使
*         IO口突然瞬间触发成高电平,这个时候马上停止计时,并且把延时计数器uiKeyTimeCnt1
*         清零了,这个过程非常巧妙,非常有效地去除瞬间的杂波干扰。这是我实战中摸索出来的。
*         以后凡是用到开关感应器的时候,都可以用类似这样的方法去干扰。
* 第三步:如果按键按下的时间超过了阀值const_key_time1,则触发按键,把编号ucKeySec赋值。
*         同时,马上把自锁标志ucKeyLock1置位,防止按住按键不松手后一直触发。
* 第四步:等按键松开后,自锁标志ucKeyLock1及时清零,为下一次自锁做准备。
* 第五步:以上整个过程,就是识别按键IO口下降沿触发的过程。
*/
  if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  {
     ucKeyLock1=0; //按键自锁标志清零
         ucKeyStartFlag1=0; //停止计数器
         uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  }
  else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
  {
         ucKeyStartFlag1=1; //启动计数器
     if(uiKeyTimeCnt1>const_key_time1)
     {
                 ucKeyStartFlag1=0; //停止计数器
        uiKeyTimeCnt1=0;
        ucKeyLock1=1;  //自锁按键置位,避免一直触发
        ucKeySec=1;    //触发1号键
     }
  }

  if(key_sr2==1)
  {
     ucKeyLock2=0;
         ucKeyStartFlag2=0; //停止计数器
         uiKeyTimeCnt2=0;
  }
  else if(ucKeyLock2==0)
  {
         ucKeyStartFlag2=1; //启动计数器
     if(uiKeyTimeCnt2>const_key_time2)
     {
            ucKeyStartFlag2=0; //停止计数器
        uiKeyTimeCnt2=0;
        ucKeyLock2=1;
        ucKeySec=2;     //触发2号键
     }
  }

}


void key_service() //第三区 按键服务的应用程序
{
  switch(ucKeySec) //按键服务状态切换
  {
    case 1:// 1号键 对应朱兆祺学习板的S1键

              uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
              ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;        
    case 2:// 2号键 对应朱兆祺学习板的S5键

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



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


  if(ucKeyStartFlag1==1)//启动计数器
  {
     if(uiKeyTimeCnt1<0xffff)  //防止计数器超范围
         {
            uiKeyTimeCnt1++;
         }
  }

   if(ucKeyStartFlag2==1)//启动计数器
  {
     if(uiKeyTimeCnt2<0xffff) //防止计数器超范围
         {
            uiKeyTimeCnt2++;
         }
  }

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


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() //第二区 初始化外围
{
  EA=1;     //开总中断
  ET0=1;    //允许定时中断
  TR0=1;    //启动定时中断

}

总结陈词:
本节程序已经展示了在主函数中,利用累计定时中断次数来实现独立按键的检测。这种方法我也经常在实战用应用,但是如果在某些项目中,需要在主函数里间歇性地执行一些一气呵成的耗时任务,这种方法就不是很实用,因为当主函数正在处理一气呵成的耗时任务时,这个时候如果有按键按下来,就有可能没有及时被响应到而遗漏了。那有什么方法可以解决这类项目中遇到的问题?欲知详情,请听下回分解-----在定时中断函数里执行独立按键的扫描程序。

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

作者: jianhong_wu    时间: 2013-12-18 00:33
本帖最后由 jianhong_wu 于 2014-1-8 02:59 编辑

第八节:在定时中断函数里执行独立按键的扫描程序。

开场白:
上一节讲了在主函数中利用累计定时中断的次数来实现独立按键的检测,但是如果在某些项目中,需要在主函数里间歇性地执行一些一气呵成的耗时任务,当主函数正在处理一气呵成的耗时任务时(前提是没有关闭定时器中断),这个时候如果有按键按下来,就有可能没有及时被响应到而遗漏了。在定时中断函数里处理独立按键的扫描程序,可以避免这个问题。要教会大家一个知识点:如何在上一节的基础上,略作修改,就可以在定时中断函数里处理独立按键的扫描程序。

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

(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; //对应朱兆祺学习板的S1键
sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键
sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平

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

unsigned char ucKeySec=0;   //被触发的按键编号

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


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

unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器

void main()
  {
   initial_myself();  
   delay_long(100);   
   initial_peripheral();
   while(1)  
   {
       key_service(); //按键服务的应用程序
   }

}

void key_scan()//按键扫描函数 放在定时中断里
{  
/* 注释二:
* 独立按键扫描的详细过程:
* 第一步:平时没有按键被触发时,按键的自锁标志,去抖动延时计数器一直被清零。
* 第二步:一旦有按键被按下,去抖动延时计数器开始在定时中断函数里累加,在还没累加到
*         阀值const_key_time1时,如果在这期间由于受外界干扰或者按键抖动,而使
*         IO口突然瞬间触发成高电平,这个时候马上把延时计数器uiKeyTimeCnt1
*         清零了,这个过程非常巧妙,非常有效地去除瞬间的杂波干扰。这是我实战中摸索出来的。
*         以后凡是用到开关感应器的时候,都可以用类似这样的方法去干扰。
* 第三步:如果按键按下的时间超过了阀值const_key_time1,则触发按键,把编号ucKeySec赋值。
*         同时,马上把自锁标志ucKeyLock1置位,防止按住按键不松手后一直触发。
* 第四步:等按键松开后,自锁标志ucKeyLock1及时清零,为下一次自锁做准备。
* 第五步:以上整个过程,就是识别按键IO口下降沿触发的过程。
*/
  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;     //触发2号键
     }
  }

}


void key_service() //第三区 按键服务的应用程序
{
  switch(ucKeySec) //按键服务状态切换
  {
    case 1:// 1号键 对应朱兆祺学习板的S1键

              uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
              ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;        
    case 2:// 2号键 对应朱兆祺学习板的S5键

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



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


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() //第二区 初始化外围
{
  EA=1;     //开总中断
  ET0=1;    //允许定时中断
  TR0=1;    //启动定时中断

}

总结陈词:
本节程序已经展示了在定时中断函数里执行独立按键的扫描程序。这节和前面两节所讲的扫描方式,我都在项目上用过,具体跟项目的侧重点不同来选择不同的方式,我本人用得最多的就是当前这种方式。假如要独立按键实现类似鼠标的双击功能,我们改怎么写程序?欲知详情,请听下回分解-----独立按键的双击按键触发。

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

作者: luoyouren    时间: 2013-12-19 21:21
提示: 作者被禁止或删除 内容自动屏蔽
作者: jianhong_wu    时间: 2013-12-19 22:09
luoyouren 发表于 2013-12-19 21:21
我在等下一节呢!!!!

两三天后就更新。
作者: 东东    时间: 2013-12-19 23:40
赞一个加油!
作者: 曾凯    时间: 2013-12-20 15:10
受教了,写的好!
作者: jianhong_wu    时间: 2013-12-20 15:35
曾凯 发表于 2013-12-20 15:10
受教了,写的好!

谢谢鼓励。后面还有很多,请继续关注本贴。
作者: 1624655383    时间: 2013-12-20 17:58
好东西啊,受教了,敬礼!
作者: jianhong_wu    时间: 2013-12-24 12:22
本帖最后由 jianhong_wu 于 2014-1-8 02:59 编辑

第九节:独立按键的双击按键触发。

开场白:
上一节讲了在定时中断函数里处理独立按键的扫描程序,这种结构的程序我用在了很多项目上。这一节教大家如何实现按键双击触发的功能,这种功能类似鼠标的双击。要教会大家一个知识点:如何在上一节的基础上,略作修改,就可以实现按键的双击功能。

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

(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    //按键去抖动延时的时间

/* 注释二:
* 有效时间差,是指连续两次按键触发的最大有效间隔时间。
* 如果双击的两个按键按下的时间间隔太长,则视为无效双击。
*/
#define const_interval_time1  200     //连续两次按键之间的有效时间差
#define const_interval_time2  200     //连续两次按键之间的有效时间差

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; //对应朱兆祺学习板的S1键
sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键
sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平

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

unsigned char ucKeySec=0;   //被触发的按键编号

unsigned int  uiKeyTimeCnt1=0; //按键去抖动延时计数器
unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志
unsigned char ucKeyTouchCnt1=0; //按键按下的次数记录
unsigned int  uiKeyIntervalCnt1=0; //按键间隔的时间计数器

unsigned int  uiKeyTimeCnt2=0; //按键去抖动延时计数器
unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志
unsigned char ucKeyTouchCnt2=0; //按键按下的次数记录
unsigned int  uiKeyIntervalCnt2=0; //按键间隔的时间计数器

unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器

void main()
  {
   initial_myself();  
   delay_long(100);   
   initial_peripheral();
   while(1)  
   {
       key_service(); //按键服务的应用程序
   }

}

void key_scan()//按键扫描函数 放在定时中断里
{  
/* 注释三:
* 独立双击按键扫描的详细过程:
* 第一步:平时没有按键被触发时,按键的自锁标志,去抖动延时计数器一直被清零。
*         如果之前已经有按键触发过一次,那么启动时间间隔计数器uiKeyIntervalCnt1,
*         在这个允许的时间差范围内,如果一直没有第二次按键触发,则把累加按键触发的
*         次数ucKeyTouchCnt1也清零。
* 第二步:一旦有按键被按下,去抖动延时计数器开始在定时中断函数里累加,在还没累加到
*         阀值const_key_time1时,如果在这期间由于受外界干扰或者按键抖动,而使
*         IO口突然瞬间触发成高电平,这个时候马上把延时计数器uiKeyTimeCnt1
*         清零了,这个过程非常巧妙,非常有效地去除瞬间的杂波干扰。这是我实战中摸索出来的。
*         以后凡是用到开关感应器的时候,都可以用类似这样的方法去干扰。
* 第三步:如果按键按下的时间超过了阀值const_key_time1,马上把自锁标志ucKeyLock1置位,
*         防止按住按键不松手后一直触发。与此同时,累加一次按键次数,如果按键次数累加有两次以上,
*         则认为触发双击按键,并把编号ucKeySec赋值。
* 第四步:等按键松开后,自锁标志ucKeyLock1及时清零,为下一次自锁做准备。并且累加间隔时间,
*         防止两次按键的间隔时间太长。
* 第五步:以上整个过程,就是识别按键IO口下降沿触发的过程。
*/
  if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  {
         ucKeyLock1=0; //按键自锁标志清零
         uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
                 if(ucKeyTouchCnt1>0) //之前已经有按键触发过一次,再来一次就构成双击
                 {
                     uiKeyIntervalCnt1++; //按键间隔的时间计数器累加
                         if(uiKeyIntervalCnt1>const_interval_time1) //超过最大允许的间隔时间
                         {
                            uiKeyIntervalCnt1=0; //时间计数器清零
                            ucKeyTouchCnt1=0; //清零按键的按下的次数
                         }
                 }
  }
  else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
  {
     uiKeyTimeCnt1++; //累加定时中断次数
     if(uiKeyTimeCnt1>const_key_time1)
     {
        uiKeyTimeCnt1=0;
        ucKeyLock1=1;  //自锁按键置位,避免一直触发
                uiKeyIntervalCnt1=0; //按键有效间隔的时间计数器清零

                ucKeyTouchCnt1++;
                if(ucKeyTouchCnt1>1)  //连续被按了两次以上
                {
                    ucKeyTouchCnt1=0;  //统计按键次数清零
                    ucKeySec=1;    //触发1号键
                }

     }
  }




  if(key_sr2==1)
  {
         ucKeyLock2=0;
         uiKeyTimeCnt2=0;
                 if(ucKeyTouchCnt2>0)
                 {
                     uiKeyIntervalCnt2++; //按键间隔的时间计数器累加
                         if(uiKeyIntervalCnt2>const_interval_time2) //超过最大允许的间隔时间
                         {
                            uiKeyIntervalCnt2=0; //时间计数器清零
                            ucKeyTouchCnt2=0; //清零按键的按下的次数
                         }
                 }
  }
  else if(ucKeyLock2==0)
  {
     uiKeyTimeCnt2++; //累加定时中断次数
     if(uiKeyTimeCnt2>const_key_time2)
     {
        uiKeyTimeCnt2=0;
        ucKeyLock2=1;
                uiKeyIntervalCnt2=0; //按键有效间隔的时间计数器清零

                ucKeyTouchCnt2++;
                if(ucKeyTouchCnt2>1)  //连续被按了两次以上
                {
                    ucKeyTouchCnt2=0;  //统计按键次数清零
                    ucKeySec=2;    //触发2号键
                }
     }
  }

}


void key_service() //第三区 按键服务的应用程序
{
  switch(ucKeySec) //按键服务状态切换
  {
    case 1:// 1号键 双击  对应朱兆祺学习板的S1键

              uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
              ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;        
    case 2:// 2号键 双击  对应朱兆祺学习板的S5键

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



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


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() //第二区 初始化外围
{
  EA=1;     //开总中断
  ET0=1;    //允许定时中断
  TR0=1;    //启动定时中断

}

总结陈词:
假如要两个独立按键实现组合按键的功能,我们该怎么写程序?欲知详情,请听下回分解-----独立按键的组合按键触发。

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


作者: jianhong_wu    时间: 2013-12-25 01:17
本帖最后由 jianhong_wu 于 2014-1-8 03:00 编辑

第十节:两个独立按键的组合按键触发。

开场白:
上一节讲了按键双击触发功能的程序,这一节讲类似电脑键盘组合按键触发的功能,要教会大家一个知识点:如何在上一节的基础上,略作修改,就可以实现两个独立按键的组合按键触发功能。

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

(1)硬件平台:基于坚鸿51单片机学习板。用矩阵键盘中的S1和S5号键作为独立按键,记得把输出线P0.4一直输出低电平,模拟独立按键的触发地GND。

(2)实现功能:有两个独立按键,当把两个独立按键都按下后,蜂鸣器发出“滴”的一声后就停。直到松开任一个按键后,才能重新进行下一次的组合按键触发。

(3)源代码讲解如下:
#include "REG52.H"

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


/* 注释一:
* 调整抖动时间阀值的大小,可以更改按键的触发灵敏度。
* 去抖动的时间本质上等于累计定时中断次数的时间。
*/
#define const_key_time12  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; //对应朱兆祺学习板的S1键
sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键
sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平

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

unsigned char ucKeySec=0;   //被触发的按键编号

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


unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器

void main()
  {
   initial_myself();  
   delay_long(100);   
   initial_peripheral();
   while(1)  
   {
       key_service(); //按键服务的应用程序
   }

}

void key_scan()//按键扫描函数 放在定时中断里
{  
/* 注释二:
* 独立组合按键扫描的详细过程:
* 第一步:平时只要两个按键中有一个没有被按下时,按键的自锁标志,去抖动延时计数器一直被清零。
* 第二步:一旦两个按键都被按下,去抖动延时计数器开始在定时中断函数里累加,在还没累加到
*         阀值const_key_time12时,如果在这期间由于受外界干扰或者按键抖动,而使
*         IO口突然瞬间触发成高电平,这个时候马上把延时计数器uiKeyTimeCnt12
*         清零了,这个过程非常巧妙,非常有效地去除瞬间的杂波干扰。这是我实战中摸索出来的。
*         以后凡是用到开关感应器的时候,都可以用类似这样的方法去干扰。
* 第三步:如果按键按下的时间超过了阀值const_key_time12,马上把自锁标志ucKeyLock12置位,
*         防止按住按键不松手后一直触发。并把编号ucKeySec赋值。 组合按键触发
* 第四步:等按键松开后,自锁标志ucKeyLock12及时清零,为下一次自锁做准备。
* 第五步:以上整个过程,就是识别按键IO口下降沿触发的过程。
*/
  if(key_sr1==1||key_sr2==1)//IO是高电平,说明两个按键没有全部被按下,这时要及时清零一些标志位
  {
         ucKeyLock12=0; //按键自锁标志清零
         uiKeyTimeCnt12=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  }
  else if(ucKeyLock12==0)//有按键按下,且是第一次被按下
  {
     uiKeyTimeCnt12++; //累加定时中断次数
     if(uiKeyTimeCnt12>const_key_time12)
     {
        uiKeyTimeCnt12=0;
        ucKeyLock12=1;  //自锁按键置位,避免一直触发
        ucKeySec=1;    //触发1号键
              
     }
  }




}


void key_service() //第三区 按键服务的应用程序
{
  switch(ucKeySec) //按键服务状态切换
  {
    case 1:// 1号键 组合按键  对应朱兆祺学习板的S1键和S5键

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



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


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() //第二区 初始化外围
{
  EA=1;     //开总中断
  ET0=1;    //允许定时中断
  TR0=1;    //启动定时中断

}

总结陈词:
以前寻呼机流行的时候,寻呼机往往只有一个设置按键,它要求用一个按键来设置不同的参数,这个时候就要用到同一个按键来实现短按和长按的区别触发功能。要现实这种功能,我们该怎么写程序?欲知详情,请听下回分解-----同一个按键短按与长按的区别触发。

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

作者: jianhong_wu    时间: 2013-12-25 15:24
本帖最后由 jianhong_wu 于 2014-1-8 03:01 编辑

第十一节:同一个按键短按与长按的区别触发。

开场白:
上一节讲了类似电脑键盘组合按键触发的功能,这节要教会大家一个知识点:如何在上一节的基础上,略作修改,就可以实现同一个按键短按与长按的区别触发。

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

(1)硬件平台:基于坚鸿51单片机学习板。用矩阵键盘中的S1和S5号键作为独立按键,记得把输出线P0.4一直输出低电平,模拟独立按键的触发地GND。

(2)实现功能:两个独立按键S1和S5,按住其中一个按键,在短时间内松手,则认为是短按,触发蜂鸣器短鸣一声。如果一直按住这个按键不松手,那么超过规定的长时间内,则认为是长按,触发蜂鸣器长鸣一声。

(3)源代码讲解如下:
#include "REG52.H"

#define const_voice_short  20   //蜂鸣器短叫的持续时间
#define const_voice_long   140   //蜂鸣器长叫的持续时间

/* 注释一:
* 调整抖动时间阀值的大小,可以更改按键的触发灵敏度。
* 去抖动的时间本质上等于累计定时中断次数的时间。
*/
#define const_key_time_short1  20    //短按的按键去抖动延时的时间
#define const_key_time_long1   400     //长按的按键去抖动延时的时间

#define const_key_time_short2  20    //短按的按键去抖动延时的时间
#define const_key_time_long2   400     //长按的按键去抖动延时的时间

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; //对应朱兆祺学习板的S1键
sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键
sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平

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

unsigned char ucKeySec=0;   //被触发的按键编号

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

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

unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器

void main()
  {
   initial_myself();  
   delay_long(100);   
   initial_peripheral();
   while(1)  
   {
       key_service(); //按键服务的应用程序
   }

}

void key_scan()//按键扫描函数 放在定时中断里
{  
/* 注释二:
* 长按与短按的按键扫描的详细过程:
* 第一步:平时只要按键没有被按下时,按键的自锁标志,去抖动延时计数器一直被清零。
* 第二步:一旦两个按键都被按下,去抖动延时计数器开始在定时中断函数里累加,在还没累加到
*         阀值const_key_time_short1或者const_key_time_long1时,如果在这期间由于受外界干扰或者按键抖动,而使
*         IO口突然瞬间触发成高电平,这个时候马上把延时计数器uiKeyTimeCnt1
*         清零了,这个过程非常巧妙,非常有效地去除瞬间的杂波干扰。这是我实战中摸索出来的。
*         以后凡是用到开关感应器的时候,都可以用类似这样的方法去干扰。
* 第三步:如果按键按下的时间超过了短按阀值const_key_time_short1,则马上把短按标志ucShortTouchFlag1=1;
*         如果还没有松手,一旦发现按下的时间超过长按阀值const_key_time_long1时,
*         先把短按标志ucShortTouchFlag1清零,然后触发长按。在这段程序里,把自锁标志ucKeyLock1置位,
*         是为了防止按住按键不松手后一直触发。
* 第四步:等按键松开后,自锁标志ucKeyLock12及时清零,为下一次自锁做准备。如果发现ucShortTouchFlag1等于1,
*         说明短按有效,这时触发一次短按。
* 第五步:以上整个过程,就是识别按键IO口下降沿触发的过程。
*/
  if(key_sr1==1)//IO是高电平,说明两个按键没有全部被按下,这时要及时清零一些标志位
  {
      ucKeyLock1=0; //按键自锁标志清零
      uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。   
            if(ucShortTouchFlag1==1)  //短按触发标志
          {
             ucShortTouchFlag1=0;
                 ucKeySec=1;    //触发一号键的短按
          }
  }
  else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
  {
     uiKeyTimeCnt1++; //累加定时中断次数
     if(uiKeyTimeCnt1>const_key_time_short1)
     {
            ucShortTouchFlag1=1;   //激活按键短按的有效标志  
     }

     if(uiKeyTimeCnt1>const_key_time_long1)
     {
            ucShortTouchFlag1=0;  //清除按键短按的有效标志

        uiKeyTimeCnt1=0;
        ucKeyLock1=1;  //自锁按键置位,避免一直触发

        ucKeySec=2;    //触发1号键的长按
              
     }

  }

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

     if(uiKeyTimeCnt2>const_key_time_long2)
     {
            ucShortTouchFlag2=0;  //清除按键短按的有效标志

        uiKeyTimeCnt2=0;
        ucKeyLock2=1;  //自锁按键置位,避免一直触发

        ucKeySec=4;    //触发2号键的长按
              
     }

  }


}


void key_service() //第三区 按键服务的应用程序
{
  switch(ucKeySec) //按键服务状态切换
  {
    case 1:// 1号键的短按  对应朱兆祺学习板的S1键

          uiVoiceCnt=const_voice_short; //按键声音的短触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;        
    case 2:// 1号键的长按  对应朱兆祺学习板的S1键

          uiVoiceCnt=const_voice_long; //按键声音的长触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;      
    case 3:// 2号键的短按  对应朱兆祺学习板的S5键

          uiVoiceCnt=const_voice_short; //按键声音的短触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;        
    case 4:// 2号键的长按  对应朱兆祺学习板的S5键

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



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


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() //第二区 初始化外围
{
  EA=1;     //开总中断
  ET0=1;    //允许定时中断
  TR0=1;    //启动定时中断

}

总结陈词:
    在很多需要人机交互的项目中,需要用按键来快速加减某个数值,这个时候如果按住一个按键不松手,这个数值要有节奏地快速往上加或者快速往下减。要现实这种功能,我们该怎么写程序?欲知详情,请听下回分解-----按住一个独立按键不松手的连续步进触发。

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

作者: 明月当年明月    时间: 2013-12-29 15:21
好东西  顶一个!
作者: jianhong_wu    时间: 2013-12-30 22:45
本帖最后由 jianhong_wu 于 2014-1-8 03:02 编辑

第十二节:按住一个独立按键不松手的连续步进触发。

开场白:
上一节讲了同一个按键短按与长按的区别触发功能,这节要教会大家两个知识点:
第一个知识点:如何在上一节的基础上,略作修改,就可以实现按住一个独立按键不松手的连续步进触发。
第二个知识点:在单片机的C语言编译器中,当无符号数据0减去1时,就会溢出,变成这个类型数据的最大值。比如是unsigned int类型的0减去1就等于65535(0xffff),unsigned char类型的0减去1就等于255(0xff)。这个常识经常要用在判断数据临界点的地方。比如一个数最大值是20,最小值是0。这个数据一直往下减,当我们发现它突然大于20的时候,就知道它溢出了,这个时候要及时把它赋值成0就达到我们的目的。

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

(1)硬件平台:基于坚鸿51单片机学习板。用矩阵键盘中的S1和S5号键作为独立按键,记得把输出线P0.4一直输出低电平,模拟独立按键的触发地GND。

(2)实现功能:两个独立按键S1和S5,S1键作为加键。S5键做为减键。每按一次S1键则被设置参数uiSetNumber自加1。如果按住S1键不松手超过1秒钟,被设置参数uiSetNumber以每0.25秒的时间间隔往上自加1,一直加到20为止。每按一次S5键则被设置参数uiSetNumber自减1。如果按住S5键不松手超过1秒钟,被设置参数uiSetNumber以每0.25秒的时间间隔往下自减1,一直减到0为止。当被设置参数uiSetNumber小于10的时候,LED灯灭;当大于或者等于10的时候,LED灯亮。


(3)源代码讲解如下:
#include "REG52.H"

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

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

#define const_time_0_25s  111   //0.25秒钟的时间需要的定时中断次数
#define const_time_1s     444   //2秒钟的时间需要的定时中断次数



void initial_myself();   
void initial_peripheral();
void delay_long(unsigned int uiDelaylong);
void T0_time();  //定时中断函数
void key_service(); //按键服务的应用程序
void key_scan(); //按键扫描函数 放在定时中断里
void led_run();  //led灯的应用程序

sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键
sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平

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

sbit led_dr=P3^5;  //LED的驱动IO口


unsigned char ucKeySec=0;   //被触发的按键编号

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


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

unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器

unsigned int  uiSetNumber=0; //设置的数据

void main()
  {
   initial_myself();  
   delay_long(100);   
   initial_peripheral();
   while(1)  
   {
       key_service(); //按键服务的应用程序
           led_run();  //led灯的应用程序
   }

}

void led_run()  //led灯的应用程序
{
   if(uiSetNumber<10)  //如果被设置的参数uiSetNumber小于10,LED灯则灭。否则亮。
   {
      led_dr=0;  //灭
   }
   else
   {
      led_dr=1;  //亮
   }
}


void key_scan()//按键扫描函数 放在定时中断里
{  
/* 注释一:
* 独立按键扫描的详细过程:
* 第一步:平时没有按键被触发时,按键的自锁标志,去抖动延时计数器,以及时间间隔延时计数器一直被清零。
* 第二步:一旦有按键被按下,去抖动延时计数器开始在定时中断函数里累加,在还没累加到
*         阀值const_key_time1时,如果在这期间由于受外界干扰或者按键抖动,而使
*         IO口突然瞬间触发成高电平,这个时候马上把延时计数器uiKeyTimeCnt1
*         清零了,这个过程非常巧妙,非常有效地去除瞬间的杂波干扰。这是我实战中摸索出来的。
*         以后凡是用到开关感应器的时候,都可以用类似这样的方法去干扰。
* 第三步:如果按键按下的时间超过了阀值const_key_time1,则触发按键,把编号ucKeySec赋值。
*         同时,马上把自锁标志ucKeyLock1置位,防止按住按键不松手后一直触发。
* 第四步:如果此时触发了一次按键后,一直不松手,去抖动延时计时器继续累加,直到超过了1秒钟。进入连续触发模式的程序
* 第五步:在连续触发模式的程序中,连续累加延时计数器开始累加,每0.25秒就触发一次。
* 第六步:等按键松开后,自锁标志ucKeyLock1和两个延时计时器及时清零,为下一次自锁做准备。
*/
  if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  {
     ucKeyLock1=0; //按键自锁标志清零
     uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。   
     uiKeyCtntyCnt1=0; //连续累加的时间间隔延时计数器清零
  }
  else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
  {
     uiKeyTimeCnt1++; //累加定时中断次数
     if(uiKeyTimeCnt1>const_key_time1)
     {
        uiKeyTimeCnt1=0;
        ucKeyLock1=1;  //自锁按键置位,避免一直触发
        ucKeySec=1;    //触发1号键
     }
  }
  else if(uiKeyTimeCnt1<const_time_1s) //按住累加到1秒
  {
     uiKeyTimeCnt1++;
  }
  else  //按住累加到2秒后仍然不放手,这个时候进入有节奏的连续触发
  {
     uiKeyCtntyCnt1++; //连续触发延时计数器累加
         if(uiKeyCtntyCnt1>const_time_0_25s)  //按住没松手,每0.25秒就触发一次
         {
             uiKeyCtntyCnt1=0; //
         ucKeySec=1;    //触发1号键
         }
   
  }



  if(key_sr2==1)
  {
     ucKeyLock2=0;
     uiKeyTimeCnt2=0;
         uiKeyCtntyCnt2=0;
  }
  else if(ucKeyLock2==0)
  {
     uiKeyTimeCnt2++; //累加定时中断次数
     if(uiKeyTimeCnt2>const_key_time2)
     {
        uiKeyTimeCnt2=0;
        ucKeyLock2=1;
        ucKeySec=2;     //触发2号键
     }
  }
  else if(uiKeyTimeCnt2<const_time_1s)
  {
      uiKeyTimeCnt2++;
  }
  else
  {
      uiKeyCtntyCnt2++;
          if(uiKeyCtntyCnt2>const_time_0_25s)
          {
             uiKeyCtntyCnt2=0;
                 ucKeySec=2;     //触发2号键
          }
  }

}


void key_service() //第三区 按键服务的应用程序
{
  switch(ucKeySec) //按键服务状态切换
  {
    case 1:// 1号键 连续加键  对应朱兆祺学习板的S1键  
              uiSetNumber++; //被设置的参数连续往上加
                          if(uiSetNumber>20) //最大是20
                          {
                            uiSetNumber=20;
                          }
              uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
              ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;        
    case 2:// 2号键 连续减键  对应朱兆祺学习板的S5键
/* 注释二:
* 在单片机的C语言编译器中,当无符号数据0减去1时,就会溢出,变成这个类型数据的最大值。
* 比如是unsigned int的0减去1就等于65535(0xffff),unsigned char的0减去1就等于255(0xff)
*/
              uiSetNumber--; //被设置的参数连续往下减
                          if(uiSetNumber>20) //最小是0.为什么这里用20?因为0减去1就是溢出变成了65535(0xffff)
                          {
                            uiSetNumber=0;
                          }
              uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
              ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;                    
  }               
}



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


void initial_myself()  //第一区 初始化单片机
{
/* 注释三:
* 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
* 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
* 坚鸿51学习板的S1和S5两个按键就是本程序中用到的两个独立按键。
*/
  key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平


  beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
  led_dr=0;  //LED灯灭

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


  TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
  TL0=0x2f;

}
void initial_peripheral() //第二区 初始化外围
{
  EA=1;     //开总中断
  ET0=1;    //允许定时中断
  TR0=1;    //启动定时中断

}

总结陈词:
本程序可以有节奏地快速往上加或者快速往下减。假如被设置数据的范围不是20,而是1000。如果按0.25秒的节奏往上加,那不是累死人了?如果直接把0.25秒的节奏调快到0.01秒,那么到达999的时候,还来不及松手就很容易超过头,不好微调。有没有完整的方案解决这个问题?当然有。欲知详情,请听下回分解-----按住一个独立按键不松手的加速匀速触发。

(未完待续,下节更精彩,不要走开哦)
作者: jianhong_wu    时间: 2014-1-8 01:54
本帖最后由 jianhong_wu 于 2014-1-8 03:03 编辑

第十三节:按住一个独立按键不松手的加速匀速触发。

开场白:
上一节讲了按住一个独立按键不松手的连续步进触发功能,这节要教会大家如何在上一节的基础上,略作修改,就可以实现按键的加速匀速触发。

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

(1)硬件平台:基于坚鸿51单片机学习板。用矩阵键盘中的S1和S5号键作为独立按键,记得把输出线P0.4一直输出低电平,模拟独立按键的触发地GND。

(2)实现功能:两个独立按键S1和S5,S1键作为加键。S5键做为减键。每按一次S1键则被设置参数uiSetNumber自加1。如果按住S1键不松手超过1秒钟,被设置参数uiSetNumber以不断变快的时间间隔往上自加1,这个称为加速触发的功能,直到到达极限值,则以固定的速度加1,这个过程叫匀速。S5作为减法按键,每触发一次,uiSetNumber就减1,其加速和匀速触发功能跟S1按键一样。当被设置参数uiSetNumber小于500的时候,LED灯灭;当大于或者等于500的时候,LED灯亮。需要注意的是:
第一步:每次按下去触发一次单击按键,如果按下去到松手的时间不超过1秒,则不会进入连续加速触发模式。
第二步:如果按下去不松手的时间超过1秒,则进入连续加速触发模式。按键触发节奏不断加快,蜂鸣器鸣叫的节奏也不断加快。直到它们都到达一个极限值,然后以此极限值间隔匀速触发。在刚开始加速的时候,按键触发与蜂鸣器触发的步骤是一致的,等它们任意一个达到极限值的时候,急促的声音跟按键的触发不一致,并不是蜂鸣器每叫一次,按键就触发一次。实际上加速到最后,按键触发的速度远远比蜂鸣器的触发速度快。

(3)源代码讲解如下:
#include "REG52.H"

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

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

#define const_time_1s     444   //1秒钟的时间需要的定时中断次数

#define const_initial_set 160  //连续触发模式的时候,按键刚开始的间隔触发时间
#define const_min_level  30    //连续触发模式的时候,按键经过加速后,如果一旦发现小于这个值,则直接变到最后的间隔触发时间
#define const_sub_dt  10       //按键的"加速度",相当于按键间隔时间每次的变化量

#define const_last_min_set 5    //连续触发模式的时候,按键经过加速后,最后的间隔触发时间

#define const_syn_min_level  45 //产生同步声音的最小阀值 这个时间必须要比蜂鸣器的时间略长一点。


void initial_myself();   
void initial_peripheral();
void delay_long(unsigned int uiDelaylong);
void T0_time();  //定时中断函数
void key_service(); //按键服务的应用程序
void key_scan(); //按键扫描函数 放在定时中断里
void led_run();  //led灯的应用程序

sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键
sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平

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

sbit led_dr=P3^5;  //LED的驱动IO口


unsigned char ucKeySec=0;   //被触发的按键编号

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

unsigned int  uiSynCtntyCnt1=0;   //产生按键同步声音的计数器
unsigned int  uiCtntyTimeSet1=const_initial_set; //按键每次触发的时间间隔,这数值不断变小,导致速度不断加快
unsigned int  uiCtntySynSet1=const_initial_set;//同步声音的时间间隔,这数值不断变小,导致速度不断加快
unsigned char ucCtntyFlag1=0;  //是否处于连续加速触发模式的标志位


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

unsigned int  uiSynCtntyCnt2=0; //产生按键同步声音的计数器
unsigned int  uiCtntyTimeSet2=const_initial_set; //按键每次触发的时间间隔,这数值不断变小,导致速度不断加快
unsigned int  uiCtntySynSet2=const_initial_set; //同步声音的时间间隔,这数值不断变小,导致速度不断加快
unsigned char ucCtntyFlag2=0; //是否处于连续加速触发模式的标志位

unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器

unsigned int  uiSetNumber=0; //设置的数据

void main()
  {
   initial_myself();  
   delay_long(100);   
   initial_peripheral();
   while(1)  
   {
       key_service(); //按键服务的应用程序
       led_run();  //led灯的应用程序
   }

}

void led_run()  //led灯的应用程序
{
   if(uiSetNumber<500)  //如果被设置的参数uiSetNumber小于500,LED灯则灭。否则亮。
   {
      led_dr=0;  //灭
   }
   else
   {
      led_dr=1;  //亮
   }
}


void key_scan()//按键扫描函数 放在定时中断里
{  
/* 注释一:
* 独立按键连续加速扫描的过程:
* 第一步:每次按下去触发一次单击按键,如果按下去到松手的时间不超过1秒,则不会进入连续加速触发模式。
* 第二步:如果按下去不松手的时间超过1秒,则进入连续加速触发模式。按键触发节奏不断加快,蜂鸣器鸣叫的节奏
*         也不断加快。直到它们都到达一个极限值,然后以此极限值间隔匀速触发。在刚开始加速的时候,按键触发与
*         蜂鸣器触发的步骤是一致的,等它们任意一个达到极限值的时候,急促的声音跟按键的触发不一致,并不是
*         蜂鸣器每叫一次,按键就触发一次。实际上加速到最后,按键触发的速度远远比蜂鸣器的触发速度快。
*/
  if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  {
     ucKeyLock1=0; //按键自锁标志清零
     uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。   
     uiKeyCtntyCnt1=0; //按键连续加速的时间间隔延时计数器清零
         uiSynCtntyCnt1=0;  //蜂鸣器连续加速的时间间隔延时计数器清零
     uiCtntyTimeSet1=const_initial_set; //按键每次触发的时间间隔初始值,这数值不断变小,导致速度不断加快
     uiCtntySynSet1=const_initial_set; //同步声音的时间间隔初始值,这数值不断变小,导致鸣叫的节奏不断加快

  }
  else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
  {
     uiKeyTimeCnt1++; //累加定时中断次数
     if(uiKeyTimeCnt1>const_key_time1)
     {
        uiKeyTimeCnt1=0;
        ucKeyLock1=1;  //自锁按键置位,避免一直触发
                ucCtntyFlag1=0; //连续加速触发模式标志位 0代表单击  1代表连续加速触发
        ucKeySec=1;    //触发1号键
     }
  }
  else if(uiKeyTimeCnt1<const_time_1s) //按住累加到1秒
  {
     uiKeyTimeCnt1++;
  }
  else  //按住累加到1秒后仍然不放手,这个时候进入有节奏的连续加速触发
  {
         uiKeyCtntyCnt1++; //按键连续触发延时计数器累加

//按住没松手,每隔一段uiCtntyTimeSet1时间按键就触发一次,而且uiCtntyTimeSet1不断减小,速度就越来越快
         if(uiKeyCtntyCnt1>uiCtntyTimeSet1)
         {
                     if(uiCtntyTimeSet1>const_min_level)
                         {
                            uiCtntyTimeSet1=uiCtntyTimeSet1-const_sub_dt; //uiCtntyTimeSet1不断减小,速度就越来越快
                         }
                         else
                         {
                             uiCtntyTimeSet1=const_last_min_set; //uiCtntyTimeSet1不断减小,到达一个极限值
                         }
             uiKeyCtntyCnt1=0;
                         ucCtntyFlag1=1;  //进入连续加速触发模式
             ucKeySec=1;    //触发1号键
         }


                 uiSynCtntyCnt1++; //蜂鸣器连续触发延时计数器累加

//按住没松手,每隔一段uiCtntySynSet1时间蜂鸣器就触发一次,而且uiCtntySynSet1不断减小,鸣叫的节奏就越来越快
                 if(uiSynCtntyCnt1>uiCtntySynSet1)
                 {
                uiCtntySynSet1=uiCtntySynSet1-const_sub_dt; //uiCtntySynSet1不断减小,鸣叫的节奏就越来越快
                        if(uiCtntySynSet1<const_syn_min_level)
                        {
                             uiCtntySynSet1=const_syn_min_level; //uiCtntySynSet1不断减小,达到一个极限值
                        }

                        uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
                        uiSynCtntyCnt1=0;
                 
                 }


   
  }


  if(key_sr2==1)
  {
     ucKeyLock2=0;
     uiKeyTimeCnt2=0;
     uiKeyCtntyCnt2=0;
         uiSynCtntyCnt2=0;
     uiCtntyTimeSet2=const_initial_set;
     uiCtntySynSet2=const_initial_set;

  }
  else if(ucKeyLock2==0)
  {
     uiKeyTimeCnt2++;
     if(uiKeyTimeCnt2>const_key_time2)
     {
        uiKeyTimeCnt2=0;
        ucKeyLock2=1;  
                ucCtntyFlag2=0;
        ucKeySec=2;  
     }
  }
  else if(uiKeyTimeCnt2<const_time_1s)
  {
     uiKeyTimeCnt2++;
  }
  else  
  {
         uiKeyCtntyCnt2++;
         if(uiKeyCtntyCnt2>uiCtntyTimeSet2)
         {
                     if(uiCtntyTimeSet2>const_min_level)
                         {
                            uiCtntyTimeSet2=uiCtntyTimeSet2-const_sub_dt;
                         }
                         else
                         {
                             uiCtntyTimeSet2=const_last_min_set;
                         }
             uiKeyCtntyCnt2=0;
                         ucCtntyFlag2=1;
             ucKeySec=2;   
         }

                 uiSynCtntyCnt2++;
                 if(uiSynCtntyCnt2>uiCtntySynSet2)
                 {
                uiCtntySynSet2=uiCtntySynSet2-const_sub_dt;
                        if(uiCtntySynSet2<const_syn_min_level)
                        {
                             uiCtntySynSet2=const_syn_min_level;
                        }

                        uiVoiceCnt=const_voice_short;
                        uiSynCtntyCnt2=0;
                 
                 }


   
  }
}


void key_service() //第三区 按键服务的应用程序
{
  switch(ucKeySec) //按键服务状态切换
  {
    case 1:// 1号键 连续加键  对应朱兆祺学习板的S1键  
              uiSetNumber++; //被设置的参数连续往上加
              if(uiSetNumber>1000) //最大是1000
              {
                   uiSetNumber=1000;
              }

                          if(ucCtntyFlag1==0) //如果是在单击按键的情况下,则蜂鸣器鸣叫,否则蜂鸣器在按键扫描key_scan里鸣叫
                          {
                  uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
                          }
              ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;        
    case 2:// 2号键 连续减键  对应朱兆祺学习板的S5键
/* 注释二:
* 在单片机的C语言编译器中,当无符号数据0减去1时,就会溢出,变成这个类型数据的最大值。
* 比如是unsigned int的0减去1就等于65535(0xffff),unsigned char的0减去1就等于255(0xff)
*/
              uiSetNumber--; //被设置的参数连续往下减
              if(uiSetNumber>1000) //最小是0.为什么这里用1000?因为0减去1就是溢出变成了65535(0xffff)
              {
                  uiSetNumber=0;
              }
                          if(ucCtntyFlag2==0)  //如果是在单击按键的情况下,则蜂鸣器鸣叫,否则蜂鸣器在按键扫描key_scan里鸣叫
                          {
                  uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
                          }
              ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;                    
  }               
}



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


void initial_myself()  //第一区 初始化单片机
{
/* 注释三:
* 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
* 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
* 坚鸿51学习板的S1和S5两个按键就是本程序中用到的两个独立按键。
*/
  key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平


  beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
  led_dr=0;  //LED灯灭

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


  TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
  TL0=0x2f;

}
void initial_peripheral() //第二区 初始化外围
{
  EA=1;     //开总中断
  ET0=1;    //允许定时中断
  TR0=1;    //启动定时中断

}


总结陈词:
    到目前为止,前面一共花了8节内容仔细讲解了独立按键的扫描程序,如果是矩阵键盘,我们该怎么写程序?欲知详情,请听下回分解-----矩阵键盘的单个触发。

(未完待续,下节更精彩,不要走开哦)
作者: 翼翔天开    时间: 2014-1-9 15:20
鸿哥,V5!我继续跟进学习哦!{:soso_e113:}
作者: jianhong_wu    时间: 2014-1-10 12:55
翼翔天开 发表于 2014-1-9 15:20
鸿哥,V5!我继续跟进学习哦!

欢迎欢迎。学习中有什么问题欢迎提出来。
作者: jianhong_wu    时间: 2014-1-12 08:52
第十四节:矩阵键盘的单个触发。

开场白:
上一节讲了按键的加速匀速触发。这节开始讲矩阵键盘的单个触发。

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

(1)硬件平台:基于坚鸿51单片机学习板。。

(2)实现功能:16个按键中,每按一个按键都能触发一次蜂鸣器发出“滴”的一声。

(3)源代码讲解如下:
#include "REG52.H"

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

#define const_key_time  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_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口

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

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


unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器

void main()
  {
   initial_myself();  
   delay_long(100);   
   initial_peripheral();
   while(1)  
   {
       key_service(); //按键服务的应用程序
   }

}

void key_scan()//按键扫描函数 放在定时中断里
{  
/* 注释一:
*  矩阵按键扫描的详细过程:
*  先输出某一列低电平,其它三列输出高电平,这个时候再分别判断输入的四行,
*  如果发现哪一行是低电平,就说明对应的某个按键被触发。依次分别输出另外三列
*  中的某一列为低电平,再分别判断输入的四行,就可以检测完16个按键。内部详细的
*  去抖动处理方法跟我前面讲的独立按键去抖动方法是一样的。
*/

  switch(ucKeyStep)
  {
     case 1:   //按键扫描输出第一列低电平
          key_dr1=0;      
          key_dr2=1;
          key_dr3=1;   
          key_dr4=1;

          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++;  //如果没有按键按下,切换到下一个运行步骤
             ucKeyLock=0;  //按键自锁标志清零
             uiKeyTimeCnt=0; //按键去抖动延时计数器清零,此行非常巧妙        

          }
                  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;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
                                   ucKeySec=1;  //触发1号键 对应朱兆祺学习板的S1键
                                }
                        
                         }
                     else if(key_sr1==1&&key_sr2==0&&key_sr3==1&&key_sr4==1)
                         {
                            uiKeyTimeCnt++;  //去抖动延时计数器
                                if(uiKeyTimeCnt>const_key_time)
                                {
                                   uiKeyTimeCnt=0;
                                   ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
                                   ucKeySec=5;  //触发5号键 对应朱兆祺学习板的S5键
                                }
                        
                         }
                     else if(key_sr1==1&&key_sr2==1&&key_sr3==0&&key_sr4==1)
                         {
                            uiKeyTimeCnt++;  //去抖动延时计数器
                                if(uiKeyTimeCnt>const_key_time)
                                {
                                   uiKeyTimeCnt=0;
                                   ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
                                   ucKeySec=9;  //触发9号键 对应朱兆祺学习板的S9键
                                }
                        
                         }
                     else if(key_sr1==1&&key_sr2==1&&key_sr3==1&&key_sr4==0)
                         {
                            uiKeyTimeCnt++;  //去抖动延时计数器
                                if(uiKeyTimeCnt>const_key_time)
                                {
                                   uiKeyTimeCnt=0;
                                   ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
                                   ucKeySec=13;  //触发13号键 对应朱兆祺学习板的S13键
                                }
                        
                         }
                  
                  }
              break;

     case 4:   //按键扫描输出第二列低电平
          key_dr1=1;      
          key_dr2=0;
          key_dr3=1;   
          key_dr4=1;

          uiKeyTimeCnt=0;  //延时计数器清零
          ucKeyStep++;     //切换到下一个运行步骤
              break;

     case 5:     //此处的小延时用来等待刚才列输出信号稳定,再判断输入信号。不是去抖动延时。
          uiKeyTimeCnt++;
                  if(uiKeyTimeCnt>1)
                  {
                     uiKeyTimeCnt=0;
             ucKeyStep++;     //切换到下一个运行步骤
                  }
              break;

     case 6:
          if(key_sr1==1&&key_sr2==1&&key_sr3==1&&key_sr4==1)
          {  
             ucKeyStep++;  //如果没有按键按下,切换到下一个运行步骤
             ucKeyLock=0;  //按键自锁标志清零
             uiKeyTimeCnt=0; //按键去抖动延时计数器清零,此行非常巧妙        

          }
                  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;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
                                   ucKeySec=2;  //触发2号键 对应朱兆祺学习板的S2键
                                }
                        
                         }
                     else if(key_sr1==1&&key_sr2==0&&key_sr3==1&&key_sr4==1)
                         {
                            uiKeyTimeCnt++;  //去抖动延时计数器
                                if(uiKeyTimeCnt>const_key_time)
                                {
                                   uiKeyTimeCnt=0;
                                   ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
                                   ucKeySec=6;  //触发6号键 对应朱兆祺学习板的S6键
                                }
                        
                         }
                     else if(key_sr1==1&&key_sr2==1&&key_sr3==0&&key_sr4==1)
                         {
                            uiKeyTimeCnt++;  //去抖动延时计数器
                                if(uiKeyTimeCnt>const_key_time)
                                {
                                   uiKeyTimeCnt=0;
                                   ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
                                   ucKeySec=10;  //触发10号键 对应朱兆祺学习板的S9键
                                }
                        
                         }
                     else if(key_sr1==1&&key_sr2==1&&key_sr3==1&&key_sr4==0)
                         {
                            uiKeyTimeCnt++;  //去抖动延时计数器
                                if(uiKeyTimeCnt>const_key_time)
                                {
                                   uiKeyTimeCnt=0;
                                   ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
                                   ucKeySec=14;  //触发14号键 对应朱兆祺学习板的S13键
                                }
                        
                         }
                  
                  }
              break;

     case 7:   //按键扫描输出第三列低电平
          key_dr1=1;      
          key_dr2=1;
          key_dr3=0;   
          key_dr4=1;

          uiKeyTimeCnt=0;  //延时计数器清零
          ucKeyStep++;     //切换到下一个运行步骤
              break;

     case 8:     //此处的小延时用来等待刚才列输出信号稳定,再判断输入信号。不是去抖动延时。
          uiKeyTimeCnt++;
                  if(uiKeyTimeCnt>1)
                  {
                     uiKeyTimeCnt=0;
             ucKeyStep++;     //切换到下一个运行步骤
                  }
              break;

     case 9:
          if(key_sr1==1&&key_sr2==1&&key_sr3==1&&key_sr4==1)
          {  
             ucKeyStep++;  //如果没有按键按下,切换到下一个运行步骤
             ucKeyLock=0;  //按键自锁标志清零
             uiKeyTimeCnt=0; //按键去抖动延时计数器清零,此行非常巧妙        

          }
                  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;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
                                   ucKeySec=3;  //触发3号键 对应朱兆祺学习板的S3键
                                }
                        
                         }
                     else if(key_sr1==1&&key_sr2==0&&key_sr3==1&&key_sr4==1)
                         {
                            uiKeyTimeCnt++;  //去抖动延时计数器
                                if(uiKeyTimeCnt>const_key_time)
                                {
                                   uiKeyTimeCnt=0;
                                   ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
                                   ucKeySec=7;  //触发7号键 对应朱兆祺学习板的S7键
                                }
                        
                         }
                     else if(key_sr1==1&&key_sr2==1&&key_sr3==0&&key_sr4==1)
                         {
                            uiKeyTimeCnt++;  //去抖动延时计数器
                                if(uiKeyTimeCnt>const_key_time)
                                {
                                   uiKeyTimeCnt=0;
                                   ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
                                   ucKeySec=11;  //触发11号键 对应朱兆祺学习板的S11键
                                }
                        
                         }
                     else if(key_sr1==1&&key_sr2==1&&key_sr3==1&&key_sr4==0)
                         {
                            uiKeyTimeCnt++;  //去抖动延时计数器
                                if(uiKeyTimeCnt>const_key_time)
                                {
                                   uiKeyTimeCnt=0;
                                   ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
                                   ucKeySec=15;  //触发15号键 对应朱兆祺学习板的S15键
                                }
                        
                         }
                  
                  }
              break;

     case 10:   //按键扫描输出第四列低电平
          key_dr1=1;      
          key_dr2=1;
          key_dr3=1;   
          key_dr4=0;

          uiKeyTimeCnt=0;  //延时计数器清零
          ucKeyStep++;     //切换到下一个运行步骤
              break;

     case 11:     //此处的小延时用来等待刚才列输出信号稳定,再判断输入信号。不是去抖动延时。
          uiKeyTimeCnt++;
                  if(uiKeyTimeCnt>1)
                  {
                     uiKeyTimeCnt=0;
             ucKeyStep++;     //切换到下一个运行步骤
                  }
              break;

     case 12:
          if(key_sr1==1&&key_sr2==1&&key_sr3==1&&key_sr4==1)
          {  
             ucKeyStep=1;  //如果没有按键按下,返回到第一步,重新开始扫描
             ucKeyLock=0;  //按键自锁标志清零
             uiKeyTimeCnt=0; //按键去抖动延时计数器清零,此行非常巧妙        

          }
                  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;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
                                   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;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
                                   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;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
                                   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;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
                                   ucKeySec=16;  //触发16号键 对应朱兆祺学习板的S16键
                                }
                        
                         }
                  
                  }
              break;

  
  }


}


void key_service() //第三区 按键服务的应用程序
{
  switch(ucKeySec) //按键服务状态切换
  {
    case 1:// 1号键 对应朱兆祺学习板的S1键

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;        
    case 2:// 2号键 对应朱兆祺学习板的S2键

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;     
    case 3:// 3号键 对应朱兆祺学习板的S3键

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;         
    case 4:// 4号键 对应朱兆祺学习板的S4键

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 5:// 5号键 对应朱兆祺学习板的S5键

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 6:// 6号键 对应朱兆祺学习板的S6键

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 7:// 7号键 对应朱兆祺学习板的S7键

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 8:// 8号键 对应朱兆祺学习板的S8键

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 9:// 9号键 对应朱兆祺学习板的S9键

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 10:// 10号键 对应朱兆祺学习板的S10键

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 11:// 11号键 对应朱兆祺学习板的S11键

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 12:// 12号键 对应朱兆祺学习板的S12键

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 13:// 13号键 对应朱兆祺学习板的S13键

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 14:// 14号键 对应朱兆祺学习板的S14键

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 15:// 15号键 对应朱兆祺学习板的S15键

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 16:// 16号键 对应朱兆祺学习板的S16键

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



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


void initial_myself()  //第一区 初始化单片机
{


  beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。


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


  TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
  TL0=0x2f;

}
void initial_peripheral() //第二区 初始化外围
{
  EA=1;     //开总中断
  ET0=1;    //允许定时中断
  TR0=1;    //启动定时中断

}

总结陈词:
    在这一节中,有的人咋看我的按键扫描代码,会觉得代码太多了。我一直认为,只要单片机容量够,代码多一点少一点并不重要,只要不影响运行效率就行。而且有时候,代码写多一点,可读性非常强,修改起来也非常方便。如果一味的追求压缩代码,就会刻意用很多循环,数组等元素,代码虽然紧凑了,但是可分离性,可改性,可读性就没那么强。我说那么多并不是因为我技术有限而不懂压缩,就找个借口敷衍大家,不信?我下一节把这节的代码压缩一下分享给大家。凡是相似度高的那部分代码都可以压缩,具体怎么压缩?欲知详情,请听下回分解-----矩阵键盘单个触发的压缩代码编程。

(未完待续,下节更精彩,不要走开哦)
作者: jianhong_wu    时间: 2014-1-12 08:53
第十五节:矩阵键盘单个触发的压缩代码编程。

开场白:
上一节讲了矩阵键盘的单个触发。这节要教会大家在不改变其它任何性能的情况下,把上一节的按键扫描程序压缩一下容量。经过压缩后,把原来1558个字节压缩到860个字节的程序容量。

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

(1)硬件平台:基于坚鸿51单片机学习板。。

(2)实现功能:16个按键中,每按一个按键都能触发一次蜂鸣器发出“滴”的一声。

(3)源代码讲解如下:
#include "REG52.H"

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

#define const_key_time  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_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口

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

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

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

unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器

void main()
  {
   initial_myself();  
   delay_long(100);   
   initial_peripheral();
   while(1)  
   {
       key_service(); //按键服务的应用程序
   }

}

void key_scan()//按键扫描函数 放在定时中断里
{  
/* 注释一:
*  矩阵按键扫描的详细过程:
*  先输出某一列低电平,其它三列输出高电平,这个时候再分别判断输入的四行,
*  如果发现哪一行是低电平,就说明对应的某个按键被触发。依次分别输出另外三列
*  中的某一列为低电平,再分别判断输入的四行,就可以检测完16个按键。内部详细的
*  去抖动处理方法跟我前面讲的独立按键去抖动方法是一样的。
*/

  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键

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;        
    case 2:// 2号键 对应朱兆祺学习板的S2键

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;     
    case 3:// 3号键 对应朱兆祺学习板的S3键

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;         
    case 4:// 4号键 对应朱兆祺学习板的S4键

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 5:// 5号键 对应朱兆祺学习板的S5键

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 6:// 6号键 对应朱兆祺学习板的S6键

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 7:// 7号键 对应朱兆祺学习板的S7键

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 8:// 8号键 对应朱兆祺学习板的S8键

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 9:// 9号键 对应朱兆祺学习板的S9键

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 10:// 10号键 对应朱兆祺学习板的S10键

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 11:// 11号键 对应朱兆祺学习板的S11键

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 12:// 12号键 对应朱兆祺学习板的S12键

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 13:// 13号键 对应朱兆祺学习板的S13键

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 14:// 14号键 对应朱兆祺学习板的S14键

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 15:// 15号键 对应朱兆祺学习板的S15键

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 16:// 16号键 对应朱兆祺学习板的S16键

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



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


void initial_myself()  //第一区 初始化单片机
{


  beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。


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


  TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
  TL0=0x2f;

}
void initial_peripheral() //第二区 初始化外围
{
  EA=1;     //开总中断
  ET0=1;    //允许定时中断
  TR0=1;    //启动定时中断

}

总结陈词:
    已经花了两节讲矩阵键盘的单个触发程序。那么,矩阵键盘可不可以实现类似独立按键的组合按键功能?当然可以,但是也有一些附加限制条件。欲知详情,请听下回分解-----矩阵键盘的组合按键触发。

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

作者: jianhong_wu    时间: 2014-1-16 11:39
第十六节:矩阵键盘的组合按键触发。

开场白:
上一节讲了矩阵键盘单个触发的压缩代码编程。这节讲矩阵键盘的组合按键触发。要教会大家三个知识点:
第一点:如何把矩阵键盘翻译成独立按盘的处理方式。然后按独立按键的方式来实现组合按键的功能。
第二点:要提醒大家在设计矩阵键盘时,很容易犯的一个错误。任意两个组合按键不能处于同一行,否则触发性能大打折扣。在做产品的时候,硬件电路设计中,除了四路行输入的要加上拉电阻,四路列输出也应该串入一个470欧左右的限流电阻,否则当同一行的两个按键同时按下时,很容易烧坏单片机IO口。为什么?大家仔细想想原因。因为如果没有限流电阻,同一行的两个按键同时按下时,在某一瞬间,输出的两路高低电平将会直接短接在一起,引起短路。在朱兆祺的学习板中,S1至S4是同一行,S5至S8是同一行,S9至S12是同一行,S13至S16是同一行。
第三点:在鸿哥矩阵键盘的组合按键处理程序中,组合按键的去抖动延时const_key_time_comb千万不能等于单击按键的去抖动延时const_key_time,否则组合按键会覆盖单击按键的触发。

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

(1)硬件平台:基于坚鸿51单片机学习板。

(2)实现功能:16个按键中,每按一个按键都能触发一次蜂鸣器发出“滴”的一声。在同时按下S1和S16按键时,将会点亮一个LED灯。在同时按下S4和S13按键时,将会熄灭一个LED灯。

(3)源代码讲解如下:
#include "REG52.H"

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


/* 注释一:
*  注意:组合按键的去抖动延时const_key_time_comb千万不能等于单击按键
*  的去抖动延时const_key_time,否则组合按键会覆盖单击按键的触发。
*/
#define const_key_time  12    //按键去抖动延时的时间
#define const_key_time_comb  14    //组合按键去抖动延时的时间

void initial_myself();   
void initial_peripheral();
void delay_long(unsigned int uiDelaylong);
void T0_time();  //定时中断函数
void key_service(); //按键服务的应用程序
void key_scan(); //按键扫描函数 放在定时中断里

/* 注释二:
*  注意:任意两个组合按键不能处于同一行,否则触发性能大打折扣。
*  在做产品的时候,硬件电路设计中,除了四路行输入的要加上拉电阻,
*  四路列输出也应该串入一个470欧左右的限流电阻,否则当同一行的两个
*  按键同时按下时,很容易烧坏单片机IO口。为什么?大家仔细想想原因。
*  因为如果没有限流电阻,同一行的两个按键同时按下时,在某一瞬间,
*  输出的两路高低电平将会直接短接在一起,引起短路。
*  在朱兆祺的学习板中,S1至S4是同一行,S5至S8是同一行,S9至S12是同一行,S13至S16是同一行。
*/
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 led_dr=P3^5;  //LED灯的输出

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

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

unsigned char ucKeySec=0;   //被触发的按键编号

unsigned int  uiKeyTimeCnt[16]=0; //16个按键去抖动延时计数器
unsigned char ucKeyLock[16]=0; //16个按键触发后自锁的变量标志

unsigned int  uiKeyTimeCnt_01_16=0; //S1和S16组合按键去抖动延时计数器
unsigned char ucKeyLock_01_16=0; //S1和S16组合按键触发后自锁的变量标志

unsigned int  uiKeyTimeCnt_04_13=0; //S4和S13组合按键去抖动延时计数器
unsigned char ucKeyLock_04_13=0; //S4和S13组合按键触发后自锁的变量标志

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

unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器

unsigned int  uiKeyStatus=0xffff;  //此变量每一位代表一个按键的状态,共16个按键。1代表没有被按下,0代表被按下。

void main()
  {
   initial_myself();  
   delay_long(100);   
   initial_peripheral();
   while(1)  
   {
       key_service(); //按键服务的应用程序
   }

}

void key_scan()//按键扫描函数 放在定时中断里
{  
/* 注释三:
*  第一步:先把16个按键翻译成独立按键。
*  第二步: 再按独立按键的去抖动方式进行按键识别。
*  第三步: 本程序把矩阵键盘翻译成独立按键的处理方式后,大家可以按独立按键的方式
*          来实现组合按键,双击,长按和短按,按住连续触发等功能。
*          我本人不再详细介绍这方面的内容。有兴趣的朋友,可以参考一下我前面章节讲的独立按键。
*/

  switch(ucKeyStep)
  {
     case 1:   //把16个按键的状态快速记录在uiKeyStatus变量的每一位中,相当于把矩阵键盘翻译成独立按键。
              for(ucRowRecord=1;ucRowRecord<5;ucRowRecord++)
                    {
                 if(ucRowRecord==1)  //第一列输出低电平
                     {
               key_dr1=0;      
               key_dr2=1;
               key_dr3=1;   
               key_dr4=1;
                //如果是薄膜按键或者走线比较长的按键,此处应该加几个空延时,等待列输出信号稳定再判断输入的状态
                       if(key_sr1==0)
                           {
                              uiKeyStatus=uiKeyStatus&0xfffe; //对应朱兆祺学习板的S1键被按下
                           }
                       if(key_sr2==0)
                           {
                              uiKeyStatus=uiKeyStatus&0xffef; //对应朱兆祺学习板的S5键被按下
                           }
                       if(key_sr3==0)
                           {
                              uiKeyStatus=uiKeyStatus&0xfeff; //对应朱兆祺学习板的S9键被按下
                           }
                       if(key_sr4==0)
                           {
                              uiKeyStatus=uiKeyStatus&0xefff; //对应朱兆祺学习板的S13键被按下
                           }
                     }
                 else if(ucRowRecord==2)  //第二列输出低电平
                     {
                key_dr1=1;      
                key_dr2=0;
                key_dr3=1;   
                key_dr4=1;
                //如果是薄膜按键或者走线比较长的按键,此处应该加几个空延时,等待列输出信号稳定再判断输入的状态
                        if(key_sr1==0)
                              {
                              uiKeyStatus=uiKeyStatus&0xfffd; //对应朱兆祺学习板的S2键被按下
                             }
                        if(key_sr2==0)
                            {
                              uiKeyStatus=uiKeyStatus&0xffdf; //对应朱兆祺学习板的S6键被按下
                            }
                        if(key_sr3==0)
                            {
                               uiKeyStatus=uiKeyStatus&0xfdff; //对应朱兆祺学习板的S10键被按下
                            }
                        if(key_sr4==0)
                            {
                               uiKeyStatus=uiKeyStatus&0xdfff; //对应朱兆祺学习板的S14键被按下
                              }
                     }
                 else if(ucRowRecord==3)  //第三列输出低电平
                     {
                key_dr1=1;      
                key_dr2=1;
                key_dr3=0;   
                key_dr4=1;
                 //如果是薄膜按键或者走线比较长的按键,此处应该加几个空延时,等待列输出信号稳定再判断输入的状态
                        if(key_sr1==0)
                            {
                               uiKeyStatus=uiKeyStatus&0xfffb; //对应朱兆祺学习板的S3键被按下
                            }
                        if(key_sr2==0)
                            {
                               uiKeyStatus=uiKeyStatus&0xffbf; //对应朱兆祺学习板的S7键被按下
                            }
                        if(key_sr3==0)
                            {
                               uiKeyStatus=uiKeyStatus&0xfbff; //对应朱兆祺学习板的S11键被按下
                              }
                        if(key_sr4==0)
                            {
                               uiKeyStatus=uiKeyStatus&0xbfff; //对应朱兆祺学习板的S15键被按下
                            }
                     }
                 else   //第四列输出低电平
                     {
                key_dr1=1;      
                key_dr2=1;
                key_dr3=1;   
                key_dr4=0;
                 //如果是薄膜按键或者走线比较长的按键,此处应该加几个空延时,等待列输出信号稳定再判断输入的状态
                        if(key_sr1==0)
                             {
                               uiKeyStatus=uiKeyStatus&0xfff7; //对应朱兆祺学习板的S4键被按下
                            }
                        if(key_sr2==0)
                            {
                               uiKeyStatus=uiKeyStatus&0xff7f; //对应朱兆祺学习板的S8键被按下
                            }
                        if(key_sr3==0)
                            {
                               uiKeyStatus=uiKeyStatus&0xf7ff; //对应朱兆祺学习板的S12键被按下
                            }
                        if(key_sr4==0)
                            {
                               uiKeyStatus=uiKeyStatus&0x7fff; //对应朱兆祺学习板的S16键被按下
                            }
                     }
          }

          ucKeyStep=2;     //切换到下一个运行步骤
              break;

     case 2:  //像独立按键一样进行去抖动和翻译。以下代码相似度很高,大家有兴趣的话还可以加for循环来压缩代码
              if((uiKeyStatus&0x0001)==0x0001)  //说明1号键没有被按下来
                  {
             uiKeyTimeCnt[0]=0;
             ucKeyLock[0]=0;
                  }
                  else if(ucKeyLock[0]==0)
                  {
                     uiKeyTimeCnt[0]++;
                         if(uiKeyTimeCnt[0]>const_key_time)
                         {
                            uiKeyTimeCnt[0]=0;
                            ucKeyLock[0]=1; //自锁按键,防止不断触发
                            ucKeySec=1;   //被触发1号键
                         }
                  }

              if((uiKeyStatus&0x0002)==0x0002)  //说明2号键没有被按下来
                  {
             uiKeyTimeCnt[1]=0;
             ucKeyLock[1]=0;
                  }
                  else if(ucKeyLock[1]==0)
                  {
                     uiKeyTimeCnt[1]++;
                         if(uiKeyTimeCnt[1]>const_key_time)
                         {
                            uiKeyTimeCnt[1]=0;
                            ucKeyLock[1]=1; //自锁按键,防止不断触发
                            ucKeySec=2;   //被触发2号键
                         }
                  }

              if((uiKeyStatus&0x0004)==0x0004)  //说明3号键没有被按下来
                  {
             uiKeyTimeCnt[2]=0;
             ucKeyLock[2]=0;
                  }
                  else if(ucKeyLock[2]==0)
                  {
                     uiKeyTimeCnt[2]++;
                         if(uiKeyTimeCnt[2]>const_key_time)
                         {
                            uiKeyTimeCnt[2]=0;
                            ucKeyLock[2]=1; //自锁按键,防止不断触发
                            ucKeySec=3;   //被触发3号键
                         }
                  }

              if((uiKeyStatus&0x0008)==0x0008)  //说明4号键没有被按下来
                  {
             uiKeyTimeCnt[3]=0;
             ucKeyLock[3]=0;
                  }
                  else if(ucKeyLock[3]==0)
                  {
                     uiKeyTimeCnt[3]++;
                         if(uiKeyTimeCnt[3]>const_key_time)
                         {
                            uiKeyTimeCnt[3]=0;
                            ucKeyLock[3]=1; //自锁按键,防止不断触发
                            ucKeySec=4;   //被触发4号键
                         }
                  }

              if((uiKeyStatus&0x0010)==0x0010)  //说明5号键没有被按下来
                  {
             uiKeyTimeCnt[4]=0;
             ucKeyLock[4]=0;
                  }
                  else if(ucKeyLock[4]==0)
                  {
                     uiKeyTimeCnt[4]++;
                         if(uiKeyTimeCnt[4]>const_key_time)
                         {
                            uiKeyTimeCnt[4]=0;
                            ucKeyLock[4]=1; //自锁按键,防止不断触发
                            ucKeySec=5;   //被触发5号键
                         }
                  }

              if((uiKeyStatus&0x0020)==0x0020)  //说明6号键没有被按下来
                  {
             uiKeyTimeCnt[5]=0;
             ucKeyLock[5]=0;
                  }
                  else if(ucKeyLock[5]==0)
                  {
                     uiKeyTimeCnt[5]++;
                         if(uiKeyTimeCnt[5]>const_key_time)
                         {
                            uiKeyTimeCnt[5]=0;
                            ucKeyLock[5]=1; //自锁按键,防止不断触发
                            ucKeySec=6;   //被触发6号键
                         }
                  }

              if((uiKeyStatus&0x0040)==0x0040)  //说明7号键没有被按下来
                  {
             uiKeyTimeCnt[6]=0;
             ucKeyLock[6]=0;
                  }
                  else if(ucKeyLock[6]==0)
                  {
                     uiKeyTimeCnt[6]++;
                         if(uiKeyTimeCnt[6]>const_key_time)
                         {
                            uiKeyTimeCnt[6]=0;
                            ucKeyLock[6]=1; //自锁按键,防止不断触发
                            ucKeySec=7;   //被触发7号键
                         }
                  }

              if((uiKeyStatus&0x0080)==0x0080)  //说明8号键没有被按下来
                  {
             uiKeyTimeCnt[7]=0;
             ucKeyLock[7]=0;
                  }
                  else if(ucKeyLock[7]==0)
                  {
                     uiKeyTimeCnt[7]++;
                         if(uiKeyTimeCnt[7]>const_key_time)
                         {
                            uiKeyTimeCnt[7]=0;
                            ucKeyLock[7]=1; //自锁按键,防止不断触发
                            ucKeySec=8;   //被触发8号键
                         }
                  }

              if((uiKeyStatus&0x0100)==0x0100)  //说明9号键没有被按下来
                  {
             uiKeyTimeCnt[8]=0;
             ucKeyLock[8]=0;
                  }
                  else if(ucKeyLock[8]==0)
                  {
                     uiKeyTimeCnt[8]++;
                         if(uiKeyTimeCnt[8]>const_key_time)
                         {
                            uiKeyTimeCnt[8]=0;
                            ucKeyLock[8]=1; //自锁按键,防止不断触发
                            ucKeySec=9;   //被触发9号键
                         }
                  }

              if((uiKeyStatus&0x0200)==0x0200)  //说明10号键没有被按下来
                  {
             uiKeyTimeCnt[9]=0;
             ucKeyLock[9]=0;
                  }
                  else if(ucKeyLock[9]==0)
                  {
                     uiKeyTimeCnt[9]++;
                         if(uiKeyTimeCnt[9]>const_key_time)
                         {
                            uiKeyTimeCnt[9]=0;
                            ucKeyLock[9]=1; //自锁按键,防止不断触发
                            ucKeySec=10;   //被触发10号键
                         }
                  }

              if((uiKeyStatus&0x0400)==0x0400)  //说明11号键没有被按下来
                  {
             uiKeyTimeCnt[10]=0;
             ucKeyLock[10]=0;
                  }
                  else if(ucKeyLock[10]==0)
                  {
                     uiKeyTimeCnt[10]++;
                         if(uiKeyTimeCnt[10]>const_key_time)
                         {
                            uiKeyTimeCnt[10]=0;
                            ucKeyLock[10]=1; //自锁按键,防止不断触发
                            ucKeySec=11;   //被触发11号键
                         }
                  }

              if((uiKeyStatus&0x0800)==0x0800)  //说明12号键没有被按下来
                  {
             uiKeyTimeCnt[11]=0;
             ucKeyLock[11]=0;
                  }
                  else if(ucKeyLock[11]==0)
                  {
                     uiKeyTimeCnt[11]++;
                         if(uiKeyTimeCnt[11]>const_key_time)
                         {
                            uiKeyTimeCnt[11]=0;
                            ucKeyLock[11]=1; //自锁按键,防止不断触发
                            ucKeySec=12;   //被触发12号键
                         }
                  }

              if((uiKeyStatus&0x0800)==0x0800)  //说明12号键没有被按下来
                  {
             uiKeyTimeCnt[11]=0;
             ucKeyLock[11]=0;
                  }
                  else if(ucKeyLock[11]==0)
                  {
                     uiKeyTimeCnt[11]++;
                         if(uiKeyTimeCnt[11]>const_key_time)
                         {
                            uiKeyTimeCnt[11]=0;
                            ucKeyLock[11]=1; //自锁按键,防止不断触发
                            ucKeySec=12;   //被触发12号键
                         }
                  }

              if((uiKeyStatus&0x1000)==0x1000)  //说明13号键没有被按下来
                  {
             uiKeyTimeCnt[12]=0;
             ucKeyLock[12]=0;
                  }
                  else if(ucKeyLock[12]==0)
                  {
                     uiKeyTimeCnt[12]++;
                         if(uiKeyTimeCnt[12]>const_key_time)
                         {
                            uiKeyTimeCnt[12]=0;
                            ucKeyLock[12]=1; //自锁按键,防止不断触发
                            ucKeySec=13;   //被触发13号键
                         }
                  }


              if((uiKeyStatus&0x2000)==0x2000)  //说明14号键没有被按下来
                  {
             uiKeyTimeCnt[13]=0;
             ucKeyLock[13]=0;
                  }
                  else if(ucKeyLock[13]==0)
                  {
                     uiKeyTimeCnt[13]++;
                         if(uiKeyTimeCnt[13]>const_key_time)
                         {
                            uiKeyTimeCnt[13]=0;
                            ucKeyLock[13]=1; //自锁按键,防止不断触发
                            ucKeySec=14;   //被触发14号键
                         }
                  }

              if((uiKeyStatus&0x4000)==0x4000)  //说明15号键没有被按下来
                  {
             uiKeyTimeCnt[14]=0;
             ucKeyLock[14]=0;
                  }
                  else if(ucKeyLock[14]==0)
                  {
                     uiKeyTimeCnt[14]++;
                         if(uiKeyTimeCnt[14]>const_key_time)
                         {
                            uiKeyTimeCnt[14]=0;
                            ucKeyLock[14]=1; //自锁按键,防止不断触发
                            ucKeySec=15;   //被触发15号键
                         }
                  }

              if((uiKeyStatus&0x8000)==0x8000)  //说明16号键没有被按下来
                  {
             uiKeyTimeCnt[15]=0;
             ucKeyLock[15]=0;
                  }
                  else if(ucKeyLock[15]==0)
                  {
                     uiKeyTimeCnt[15]++;
                         if(uiKeyTimeCnt[15]>const_key_time)
                         {
                            uiKeyTimeCnt[15]=0;
                            ucKeyLock[15]=1; //自锁按键,防止不断触发
                            ucKeySec=16;   //被触发16号键
                         }
                  }


              if((uiKeyStatus&0x8001)==0x0000)  //S1和S16的组合键盘被按下。
                  {
             if(ucKeyLock_01_16==0)
                         {
                             uiKeyTimeCnt_01_16++;
                                 if(uiKeyTimeCnt_01_16>const_key_time_comb)
                                 {
                                    uiKeyTimeCnt_01_16=0;
                                        ucKeyLock_01_16=1;
                                        ucKeySec=17;   //被触发17号组合键                           
                                 }
                             
                         }
                  }
                  else
                  {
             uiKeyTimeCnt_01_16=0; //S1和S16组合按键去抖动延时计数器
             ucKeyLock_01_16=0; //S1和S16组合按键触发后自锁的变量标志
                  }


              if((uiKeyStatus&0x1008)==0x0000)  //S4和S13的组合键盘被按下。
                  {
             if(ucKeyLock_04_13==0)
                         {
                             uiKeyTimeCnt_04_13++;
                                 if(uiKeyTimeCnt_04_13>const_key_time_comb)
                                 {
                                    uiKeyTimeCnt_04_13=0;
                                        ucKeyLock_04_13=1;
                                        ucKeySec=18;   //被触发18号组合键                           
                                 }
                             
                         }
                  }
                  else
                  {
             uiKeyTimeCnt_04_13=0; //S4和S13组合按键去抖动延时计数器
             ucKeyLock_04_13=0; //S4和S13组合按键触发后自锁的变量标志
                  }

          uiKeyStatus=0xffff;   //及时恢复状态,方便下一次扫描
                  ucKeyStep=1;  //返回到第一个运行步骤重新开始扫描
              break;

  }


}


void key_service() //第三区 按键服务的应用程序
{
  switch(ucKeySec) //按键服务状态切换
  {
    case 1:// 1号键 对应朱兆祺学习板的S1键

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;        
    case 2:// 2号键 对应朱兆祺学习板的S2键

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;     
    case 3:// 3号键 对应朱兆祺学习板的S3键

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;         
    case 4:// 4号键 对应朱兆祺学习板的S4键

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 5:// 5号键 对应朱兆祺学习板的S5键

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 6:// 6号键 对应朱兆祺学习板的S6键

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 7:// 7号键 对应朱兆祺学习板的S7键

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 8:// 8号键 对应朱兆祺学习板的S8键

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 9:// 9号键 对应朱兆祺学习板的S9键

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 10:// 10号键 对应朱兆祺学习板的S10键

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 11:// 11号键 对应朱兆祺学习板的S11键

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 12:// 12号键 对应朱兆祺学习板的S12键

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 13:// 13号键 对应朱兆祺学习板的S13键

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 14:// 14号键 对应朱兆祺学习板的S14键

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 15:// 15号键 对应朱兆祺学习板的S15键

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
    case 16:// 16号键 对应朱兆祺学习板的S16键

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

    case 17:// 17号组合键 对应朱兆祺学习板的S1和S16键的组合按键

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

    case 18:// 18号组合键 对应朱兆祺学习板的S4和S13键的组合按键

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



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


void initial_myself()  //第一区 初始化单片机
{

  led_dr=0; //LED灯灭
  beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。


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


  TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
  TL0=0x2f;

}
void initial_peripheral() //第二区 初始化外围
{
  EA=1;     //开总中断
  ET0=1;    //允许定时中断
  TR0=1;    //启动定时中断

}

总结陈词:
    这节讲了如何把矩阵键盘翻译成独立按键的处理方式,然后像独立按键一样实现组合按键的功能,关于矩阵按键的双击,长按和短按,按键连续触发等功能我不再详细介绍,有兴趣的朋友可以参考我前面章节讲的独立按键。在实际的项目中,按键可以控制很多外设。为了以后进一步讲按键控制外设等功能,接下来我会讲哪些新内容呢?欲知详情,请听下回分解-----两片联级74HC595驱动16个LED灯的基本驱动程序。

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

作者: jianhong_wu    时间: 2014-1-29 12:27
第十七节:两片联级74HC595驱动16个LED灯的基本驱动程序。

开场白:
      上一节讲了如何把矩阵键盘翻译成独立按键的处理方式。这节讲74HC595的驱动程序。要教会大家两个知识点:
      第一点:朱兆祺的学习板是用74HC595控制LED,因此可以直接把595的OE引脚接地。如果在工控中,用来控制继电器,那么此芯片的片选脚OE不要为了省一个IO口而直接接地,否则会引起上电瞬间继电器莫名其妙地动作。为了解决这个问题,OE脚应该用一个IO口单独驱动,并且千万要记住,此IO必须接一个15K左右的上拉电阻,然后在程序刚上电运行时,先把OE置高,并且尽快把所有的74HC595输出口置低,然后再把OE置低.当然还有另外一种解决办法,就是用一个10uF的电解电容跟一个100K的下拉电阻,组成跟51单片机外围复位电路原理一样的电路,连接到OE口,这样确保上电瞬间OE口有一小段时间是处于高电平状态,在此期间,尽快通过软件把74hc595的所有输出口置低。
      第二点:两个联级74HC595的工作过程:每个74HC595内部都有一个8位的寄存器,两个联级起来就有两个寄存器。ST引脚就相当于一个刷新信号引脚,当ST引脚产生一个上升沿信号时,就会把寄存器的数值输出到74HC595的输出引脚并且锁存起来,DS是数据引脚,SH是把新数据送入寄存器的时钟信号。也就是说,SH引脚负责把数据送入到寄存器里,ST引脚负责把寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来。

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

(1)硬件平台:基于坚鸿51单片机学习板。

(2)实现功能:两片联级的74HC595驱动的16个LED灯交叉闪烁。比如,先是第1,3,5,7,9,11,13,15八个灯亮,其它的灯都灭。然后再反过来,原来亮的就灭,原来灭的就亮。交替闪烁。

(3)源代码讲解如下:
#include "REG52.H"

#define const_time_level 200  

void initial_myself();   
void initial_peripheral();
void delay_short(unsigned int uiDelayShort);
void delay_long(unsigned int uiDelaylong);
void led_flicker();
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
void T0_time();  //定时中断函数

/* 注释一:
* 朱兆祺的学习板是用74HC595控制LED,因此可以直接把595的OE引脚接地。如果在工控中,用来控制继电器,
* 那么此芯片的片选脚OE不要为了省一个IO口而直接接地,否则会引起上电瞬间继电器莫名其妙地动作。
* 为了解决这个问题,OE脚应该用一个IO口单独驱动,并且千万要记住,此IO必须接一个15K左右的
* 上拉电阻,然后在程序刚上电运行时,先把OE置高,并且尽快把所有的74HC595输出口置低,然后再把OE置低.
* 当然还有另外一种解决办法,就是用一个10uF的电解电容跟一个100K的下拉电阻,组成跟51单片机外围复位电路原理
* 一样的电路,连接到OE口,这样确保上电瞬间OE口有一小段时间是处于高电平状态,在此 期间,
* 尽快通过软件把74hc595的所有输出口置低。
*/
sbit hc595_sh_dr=P2^3;   
sbit hc595_st_dr=P2^4;  
sbit hc595_ds_dr=P2^5;  


unsigned char ucLedStep=0; //步骤变量
unsigned int  uiTimeCnt=0; //统计定时中断次数的延时计数器


void main()
  {
   initial_myself();  
   delay_long(100);   
   initial_peripheral();
   while(1)   
   {
      led_flicker();   
   }

}

/* 注释二:
* 两个联级74HC595的工作过程:
* 每个74HC595内部都有一个8位的寄存器,两个联级起来就有两个寄存器。ST引脚就相当于一个刷新
* 信号引脚,当ST引脚产生一个上升沿信号时,就会把寄存器的数值输出到74HC595的输出引脚并且锁存起来,
* DS是数据引脚,SH是把新数据送入寄存器的时钟信号。也就是说,SH引脚负责把数据送入到寄存器里,ST引脚
* 负责把寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来。
*/
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
{
   unsigned char i;
   unsigned char ucTempData;
   hc595_sh_dr=0;
   hc595_st_dr=0;

   ucTempData=ucLedStatusTemp16_09;  //先送高8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;

         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(15);
         hc595_sh_dr=1;
         delay_short(15);

         ucTempData=ucTempData<<1;
   }

   ucTempData=ucLedStatusTemp08_01;  //再先送低8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;

         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(15);
         hc595_sh_dr=1;
         delay_short(15);

         ucTempData=ucTempData<<1;
   }

   hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
   delay_short(15);
   hc595_st_dr=1;
   delay_short(15);

   hc595_sh_dr=0;    //拉低,抗干扰就增强
   hc595_st_dr=0;
   hc595_ds_dr=0;

}


void led_flicker() ////第三区 LED闪烁应用程序
{
  switch(ucLedStep)
  {
     case 0:
           if(uiTimeCnt>=const_time_level) //时间到
           {
               uiTimeCnt=0; //时间计数器清零
               hc595_drive(0x55,0x55);
               ucLedStep=1; //切换到下一个步骤
           }
           break;
     case 1:
           if(uiTimeCnt>=const_time_level) //时间到
           {
               uiTimeCnt=0; //时间计数器清零
               hc595_drive(0xaa,0xaa);
               ucLedStep=0; //返回到上一个步骤
           }
           break;
  
   }

}


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

  if(uiTimeCnt<0xffff)  //设定这个条件,防止uiTimeCnt超范围。
  {
      uiTimeCnt++;  //累加定时中断的次数,
  }

  TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
  TL0=0x2f;
  TR0=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++)  //内嵌循环的空指令数量
          {
             ; //一个分号相当于执行一条空语句
          }
   }
}


void initial_myself()  //第一区 初始化单片机
{

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


  TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
  TL0=0x2f;


}

void initial_peripheral() //第二区 初始化外围
{
  EA=1;     //开总中断
  ET0=1;    //允许定时中断
  TR0=1;    //启动定时中断

}

总结陈词:
       这节讲了74HC595的驱动程序,它是一次控制16个LED同时亮灭的,在实际中应用不太方便,如果我们想要像单片机IO口直接控制LED那样方便,我们该怎么编写程序呢?欲知详情,请听下回分解-----把74HC595驱动程序翻译成类似单片机IO口直接驱动的方式。

(未完待续,下节更精彩,不要走开哦)
作者: jianhong_wu    时间: 2014-1-29 13:21
第十八节:把74HC595驱动程序翻译成类似单片机IO口直接驱动的方式。

开场白:
上一节讲了74HC595的驱动程序。为了更加方便操作74HC595输出的每个IO状态,这节讲如何把74HC595驱动程序翻译成类似单片机IO口直接驱动的方式。要教会大家两个知识点:
第一点:如何灵活运用与和非的运算符来实现位的操作。
第二点:如何灵活运用一个更新变量来实现静态刷新输出或者静态刷新显示的功能。
具体内容,请看源代码讲解。

(1)硬件平台:基于坚鸿51单片机学习板。

(2)实现功能:两片联级的74HC595驱动的16个LED灯交叉闪烁。比如,先是第1,3,5,7,9,11,13,15八个灯亮,其它的灯都灭。然后再反过来,原来亮的就灭,原来灭的就亮。交替闪烁。

(3)源代码讲解如下:
#include "REG52.H"

#define const_time_level 200  

void initial_myself();   
void initial_peripheral();
void delay_short(unsigned int uiDelayShort);
void delay_long(unsigned int uiDelaylong);
void led_flicker();
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
void led_update();  //LED更新函数
void T0_time();  //定时中断函数


sbit hc595_sh_dr=P2^3;   
sbit hc595_st_dr=P2^4;  
sbit hc595_ds_dr=P2^5;  

unsigned char ucLed_dr1=0;   //代表16个灯的亮灭状态,0代表灭,1代表亮
unsigned char ucLed_dr2=0;
unsigned char ucLed_dr3=0;
unsigned char ucLed_dr4=0;
unsigned char ucLed_dr5=0;
unsigned char ucLed_dr6=0;
unsigned char ucLed_dr7=0;
unsigned char ucLed_dr8=0;
unsigned char ucLed_dr9=0;
unsigned char ucLed_dr10=0;
unsigned char ucLed_dr11=0;
unsigned char ucLed_dr12=0;
unsigned char ucLed_dr13=0;
unsigned char ucLed_dr14=0;
unsigned char ucLed_dr15=0;
unsigned char ucLed_dr16=0;

unsigned char ucLed_update=0;  //刷新变量。每次更改LED灯的状态都要更新一次。

unsigned char ucLedStep=0; //步骤变量
unsigned int  uiTimeCnt=0; //统计定时中断次数的延时计数器

unsigned char ucLedStatus16_09=0;   //代表底层74HC595输出状态的中间变量
unsigned char ucLedStatus08_01=0;   //代表底层74HC595输出状态的中间变量

void main()
  {
   initial_myself();  
   delay_long(100);   
   initial_peripheral();
   while(1)   
   {
      led_flicker();   
          led_update();  //LED更新函数
   }

}


/* 注释一:
* 把74HC595驱动程序翻译成类似单片机IO口直接驱动方式的过程。
* 每次更新LED输出,记得都要把ucLed_update置1表示更新。
*/
void led_update()  //LED更新函数
{

   if(ucLed_update==1)
   {
       ucLed_update=0;   //及时清零,让它产生只更新一次的效果,避免一直更新。

       if(ucLed_dr1==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x01;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xfe;
           }

       if(ucLed_dr2==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x02;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xfd;
           }

       if(ucLed_dr3==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x04;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xfb;
           }

       if(ucLed_dr4==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x08;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xf7;
           }


       if(ucLed_dr5==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x10;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xef;
           }


       if(ucLed_dr6==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x20;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xdf;
           }


       if(ucLed_dr7==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x40;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xbf;
           }


       if(ucLed_dr8==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x80;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0x7f;
           }

       if(ucLed_dr9==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x01;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xfe;
           }

       if(ucLed_dr10==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x02;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xfd;
           }

       if(ucLed_dr11==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x04;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xfb;
           }

       if(ucLed_dr12==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x08;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xf7;
           }


       if(ucLed_dr13==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x10;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xef;
           }


       if(ucLed_dr14==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x20;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xdf;
           }


       if(ucLed_dr15==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x40;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xbf;
           }


       if(ucLed_dr16==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x80;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0x7f;
           }

       hc595_drive(ucLedStatus16_09,ucLedStatus08_01);  //74HC595底层驱动函数

   }
}

void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
{
   unsigned char i;
   unsigned char ucTempData;
   hc595_sh_dr=0;
   hc595_st_dr=0;

   ucTempData=ucLedStatusTemp16_09;  //先送高8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;

         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(15);
         hc595_sh_dr=1;
         delay_short(15);

         ucTempData=ucTempData<<1;
   }

   ucTempData=ucLedStatusTemp08_01;  //再先送低8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;

         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(15);
         hc595_sh_dr=1;
         delay_short(15);

         ucTempData=ucTempData<<1;
   }

   hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
   delay_short(15);
   hc595_st_dr=1;
   delay_short(15);

   hc595_sh_dr=0;    //拉低,抗干扰就增强
   hc595_st_dr=0;
   hc595_ds_dr=0;

}


void led_flicker() ////第三区 LED闪烁应用程序
{
  switch(ucLedStep)
  {
     case 0:
           if(uiTimeCnt>=const_time_level) //时间到
           {
               uiTimeCnt=0; //时间计数器清零

               ucLed_dr1=1;  //每个变量都代表一个LED灯的状态
               ucLed_dr2=0;
               ucLed_dr3=1;
               ucLed_dr4=0;
               ucLed_dr5=1;
               ucLed_dr6=0;
               ucLed_dr7=1;
               ucLed_dr8=0;
               ucLed_dr9=1;
               ucLed_dr10=0;
               ucLed_dr11=1;
               ucLed_dr12=0;
               ucLed_dr13=1;
               ucLed_dr14=0;
               ucLed_dr15=1;
               ucLed_dr16=0;

               ucLed_update=1;  //更新显示
               ucLedStep=1; //切换到下一个步骤
           }
           break;
     case 1:
           if(uiTimeCnt>=const_time_level) //时间到
           {
               uiTimeCnt=0; //时间计数器清零

               ucLed_dr1=0;  //每个变量都代表一个LED灯的状态
               ucLed_dr2=1;
               ucLed_dr3=0;
               ucLed_dr4=1;
               ucLed_dr5=0;
               ucLed_dr6=1;
               ucLed_dr7=0;
               ucLed_dr8=1;
               ucLed_dr9=0;
               ucLed_dr10=1;
               ucLed_dr11=0;
               ucLed_dr12=1;
               ucLed_dr13=0;
               ucLed_dr14=1;
               ucLed_dr15=0;
               ucLed_dr16=1;

               ucLed_update=1;  //更新显示
               ucLedStep=0; //返回到上一个步骤
           }
           break;
  
   }

}


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

  if(uiTimeCnt<0xffff)  //设定这个条件,防止uiTimeCnt超范围。
  {
      uiTimeCnt++;  //累加定时中断的次数,
  }

  TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
  TL0=0x2f;
  TR0=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++)  //内嵌循环的空指令数量
          {
             ; //一个分号相当于执行一条空语句
          }
   }
}


void initial_myself()  //第一区 初始化单片机
{

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


  TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
  TL0=0x2f;


}

void initial_peripheral() //第二区 初始化外围
{
  EA=1;     //开总中断
  ET0=1;    //允许定时中断
  TR0=1;    //启动定时中断

}

总结陈词:
这节讲了把74HC595驱动程序翻译成类似单片机IO口直接驱动的方式,接下来,我们该如何来运用这种驱动方式实现跑马灯的程序?欲知详情,请听下回分解-----依次逐个点亮LED之后,再依次逐个熄灭LED的跑马灯程序。

(未完待续,下节更精彩,不要走开哦)
作者: jianhong_wu    时间: 2014-2-1 05:32
第十九节:依次逐个点亮LED之后,再依次逐个熄灭LED的跑马灯程序。

开场白:
上一节讲了把74HC595驱动程序翻译成类似单片机IO口直接驱动的方式。这节在上一节的驱动程序基础上,开始讲跑马灯程序。我的跑马灯程序看似简单而且重复,其实蕴含着鸿哥的大智慧。它是基于鸿哥的switch状态机思想,领略到了它的简单和精髓,以后任何所谓复杂的工程项目,都不再复杂。要教会大家一个知识点:通过本跑马灯程序,加深理解鸿哥所有实战项目中switch状态机的思想精髓。
具体内容,请看源代码讲解。

(1)硬件平台:基于坚鸿51单片机学习板。

(2)实现功能:第1个至第8个LED灯,先依次逐个亮,再依次逐个灭。第9至第16个LED灯一直灭。

(3)源代码讲解如下:
#include "REG52.H"

#define const_time_level_01_08  200  //第1个至第8个LED跑马灯的速度延时时间

void initial_myself();   
void initial_peripheral();
void delay_short(unsigned int uiDelayShort);
void delay_long(unsigned int uiDelaylong);
void led_flicker_01_08(); // 第1个至第8个LED的跑马灯程序,逐个亮,逐个灭.
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
void led_update();  //LED更新函数
void T0_time();  //定时中断函数


sbit hc595_sh_dr=P2^3;   
sbit hc595_st_dr=P2^4;  
sbit hc595_ds_dr=P2^5;  

unsigned char ucLed_dr1=0;   //代表16个灯的亮灭状态,0代表灭,1代表亮
unsigned char ucLed_dr2=0;
unsigned char ucLed_dr3=0;
unsigned char ucLed_dr4=0;
unsigned char ucLed_dr5=0;
unsigned char ucLed_dr6=0;
unsigned char ucLed_dr7=0;
unsigned char ucLed_dr8=0;
unsigned char ucLed_dr9=0;
unsigned char ucLed_dr10=0;
unsigned char ucLed_dr11=0;
unsigned char ucLed_dr12=0;
unsigned char ucLed_dr13=0;
unsigned char ucLed_dr14=0;
unsigned char ucLed_dr15=0;
unsigned char ucLed_dr16=0;

unsigned char ucLed_update=0;  //刷新变量。每次更改LED灯的状态都要更新一次。

unsigned char ucLedStep_01_08=0; //第1个至第8个LED跑马灯的步骤变量
unsigned int  uiTimeCnt_01_08=0; //第1个至第8个LED跑马灯的统计定时中断次数的延时计数器

unsigned char ucLedStatus16_09=0;   //代表底层74HC595输出状态的中间变量
unsigned char ucLedStatus08_01=0;   //代表底层74HC595输出状态的中间变量

void main()
  {
   initial_myself();  
   delay_long(100);   
   initial_peripheral();
   while(1)   
   {
      led_flicker_01_08(); // 第1个至第8个LED的跑马灯程序,逐个亮,逐个灭.
          led_update();  //LED更新函数
   }

}


void led_update()  //LED更新函数
{

   if(ucLed_update==1)
   {
       ucLed_update=0;   //及时清零,让它产生只更新一次的效果,避免一直更新。

       if(ucLed_dr1==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x01;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xfe;
           }

       if(ucLed_dr2==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x02;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xfd;
           }

       if(ucLed_dr3==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x04;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xfb;
           }

       if(ucLed_dr4==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x08;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xf7;
           }


       if(ucLed_dr5==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x10;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xef;
           }


       if(ucLed_dr6==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x20;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xdf;
           }


       if(ucLed_dr7==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x40;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xbf;
           }


       if(ucLed_dr8==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x80;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0x7f;
           }

       if(ucLed_dr9==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x01;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xfe;
           }

       if(ucLed_dr10==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x02;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xfd;
           }

       if(ucLed_dr11==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x04;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xfb;
           }

       if(ucLed_dr12==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x08;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xf7;
           }


       if(ucLed_dr13==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x10;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xef;
           }


       if(ucLed_dr14==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x20;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xdf;
           }


       if(ucLed_dr15==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x40;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xbf;
           }


       if(ucLed_dr16==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x80;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0x7f;
           }

       hc595_drive(ucLedStatus16_09,ucLedStatus08_01);  //74HC595底层驱动函数

   }
}

void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
{
   unsigned char i;
   unsigned char ucTempData;
   hc595_sh_dr=0;
   hc595_st_dr=0;

   ucTempData=ucLedStatusTemp16_09;  //先送高8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;

         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(15);
         hc595_sh_dr=1;
         delay_short(15);

         ucTempData=ucTempData<<1;
   }

   ucTempData=ucLedStatusTemp08_01;  //再先送低8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;

         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(15);
         hc595_sh_dr=1;
         delay_short(15);

         ucTempData=ucTempData<<1;
   }

   hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
   delay_short(15);
   hc595_st_dr=1;
   delay_short(15);

   hc595_sh_dr=0;    //拉低,抗干扰就增强
   hc595_st_dr=0;
   hc595_ds_dr=0;

}

/* 注释一:
* 以下程序,看似简单而且重复,其实蕴含着鸿哥的大智慧。
* 它是基于鸿哥的switch状态机思想,领略到了它的简单和精髓,
* 以后任何所谓复杂的工程项目,都不再复杂。
*/
void led_flicker_01_08() //第1个至第8个LED的跑马灯程序,逐个亮,逐个灭.
{
  switch(ucLedStep_01_08)
  {
     case 0:
           if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
           {
               uiTimeCnt_01_08=0; //时间计数器清零

               ucLed_dr1=1;  //第1个亮

               ucLed_update=1;  //更新显示
               ucLedStep_01_08=1; //切换到下一个步骤
           }
           break;
     case 1:
           if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
           {
               uiTimeCnt_01_08=0; //时间计数器清零

               ucLed_dr2=1;  //第2个亮

               ucLed_update=1;  //更新显示
               ucLedStep_01_08=2; //切换到下一个步骤
           }
           break;
     case 2:
           if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
           {
               uiTimeCnt_01_08=0; //时间计数器清零

               ucLed_dr3=1;  //第3个亮

               ucLed_update=1;  //更新显示
               ucLedStep_01_08=3; //切换到下一个步骤
           }
           break;
     case 3:
           if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
           {
               uiTimeCnt_01_08=0; //时间计数器清零

               ucLed_dr4=1;  //第4个亮

               ucLed_update=1;  //更新显示
               ucLedStep_01_08=4; //切换到下一个步骤
           }
           break;
     case 4:
           if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
           {
               uiTimeCnt_01_08=0; //时间计数器清零

               ucLed_dr5=1;  //第5个亮

               ucLed_update=1;  //更新显示
               ucLedStep_01_08=5; //切换到下一个步骤
           }
           break;
     case 5:
           if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
           {
               uiTimeCnt_01_08=0; //时间计数器清零

               ucLed_dr6=1;  //第6个亮

               ucLed_update=1;  //更新显示
               ucLedStep_01_08=6; //切换到下一个步骤
           }
           break;
     case 6:
           if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
           {
               uiTimeCnt_01_08=0; //时间计数器清零

               ucLed_dr7=1;  //第7个亮

               ucLed_update=1;  //更新显示
               ucLedStep_01_08=7; //切换到下一个步骤
           }
           break;
     case 7:
           if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
           {
               uiTimeCnt_01_08=0; //时间计数器清零

               ucLed_dr8=1;  //第8个亮

               ucLed_update=1;  //更新显示
               ucLedStep_01_08=8; //切换到下一个步骤
           }
           break;
     case 8:
           if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
           {
               uiTimeCnt_01_08=0; //时间计数器清零

               ucLed_dr8=0;  //第8个灭

               ucLed_update=1;  //更新显示
               ucLedStep_01_08=9; //切换到下一个步骤
           }
           break;
     case 9:
           if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
           {
               uiTimeCnt_01_08=0; //时间计数器清零

               ucLed_dr7=0;  //第7个灭

               ucLed_update=1;  //更新显示
               ucLedStep_01_08=10; //切换到下一个步骤
           }
           break;
     case 10:
           if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
           {
               uiTimeCnt_01_08=0; //时间计数器清零

               ucLed_dr6=0;  //第6个灭

               ucLed_update=1;  //更新显示
               ucLedStep_01_08=11; //切换到下一个步骤
           }
           break;
     case 11:
           if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
           {
               uiTimeCnt_01_08=0; //时间计数器清零

               ucLed_dr5=0;  //第5个灭

               ucLed_update=1;  //更新显示
               ucLedStep_01_08=12; //切换到下一个步骤
           }
           break;
     case 12:
           if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
           {
               uiTimeCnt_01_08=0; //时间计数器清零

               ucLed_dr4=0;  //第4个灭

               ucLed_update=1;  //更新显示
               ucLedStep_01_08=13; //切换到下一个步骤
           }
           break;
     case 13:
           if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
           {
               uiTimeCnt_01_08=0; //时间计数器清零

               ucLed_dr3=0;  //第3个灭

               ucLed_update=1;  //更新显示
               ucLedStep_01_08=14; //切换到下一个步骤
           }
           break;
     case 14:
           if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
           {
               uiTimeCnt_01_08=0; //时间计数器清零

               ucLed_dr2=0;  //第2个灭

               ucLed_update=1;  //更新显示
               ucLedStep_01_08=15; //切换到下一个步骤
           }
           break;
     case 15:
           if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
           {
               uiTimeCnt_01_08=0; //时间计数器清零

               ucLed_dr1=0;  //第1个灭

               ucLed_update=1;  //更新显示
               ucLedStep_01_08=0; //返回到最开始处,重新开始新的一次循环。
           }
           break;

   }

}


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

  if(uiTimeCnt_01_08<0xffff)  //设定这个条件,防止uiTimeCnt超范围。
  {
      uiTimeCnt_01_08++;  //累加定时中断的次数,
  }

  TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
  TL0=0x2f;
  TR0=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++)  //内嵌循环的空指令数量
          {
             ; //一个分号相当于执行一条空语句
          }
   }
}


void initial_myself()  //第一区 初始化单片机
{

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


  TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
  TL0=0x2f;


}

void initial_peripheral() //第二区 初始化外围
{
  EA=1;     //开总中断
  ET0=1;    //允许定时中断
  TR0=1;    //启动定时中断

}

总结陈词:
这节讲了在第1个至第8个LED灯中,先依次逐个亮再依次逐个灭的跑马灯程序。下一节我们略作修改,继续做跑马灯的程序,要求在第9个至第16个LED灯中,依次逐个亮灯并且每次只能亮一个灯(其它的都灭),依次循环,我们该如何编写程序?欲知详情,请听下回分解-----依次逐个亮灯并且每次只能亮一个灯的跑马灯程序。

(未完待续,下节更精彩,不要走开哦)
作者: jianhong_wu    时间: 2014-2-1 05:33
本帖最后由 jianhong_wu 于 2014-2-1 05:38 编辑

第二十节:依次逐个亮灯并且每次只能亮一个灯的跑马灯程序。

开场白:
上一节讲了先依次逐个亮再依次逐个灭的跑马灯程序。这一节在上一节的基础上,略作修改,继续讲跑马灯程序。我的跑马灯程序看似简单而且重复,其实蕴含着鸿哥的大智慧。它是基于鸿哥的switch状态机思想,领略到了它的简单和精髓,以后任何所谓复杂的工程项目,都不再复杂。要教会大家一个知识点:通过本跑马灯程序,加深理解鸿哥所有实战项目中switch状态机的思想精髓。
具体内容,请看源代码讲解。

(1)硬件平台:基于坚鸿51单片机学习板。

(2)实现功能:第9个至第16个LED灯,依次逐个亮灯并且每次只能亮一个灯。第1至第8个LED灯一直灭。

(3)源代码讲解如下:
#include "REG52.H"

#define const_time_level_09_16  300  //第9个至第16个LED跑马灯的速度延时时间

void initial_myself();   
void initial_peripheral();
void delay_short(unsigned int uiDelayShort);
void delay_long(unsigned int uiDelaylong);
void led_flicker_09_16(); // 第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
void led_update();  //LED更新函数
void T0_time();  //定时中断函数


sbit hc595_sh_dr=P2^3;   
sbit hc595_st_dr=P2^4;  
sbit hc595_ds_dr=P2^5;  

unsigned char ucLed_dr1=0;   //代表16个灯的亮灭状态,0代表灭,1代表亮
unsigned char ucLed_dr2=0;
unsigned char ucLed_dr3=0;
unsigned char ucLed_dr4=0;
unsigned char ucLed_dr5=0;
unsigned char ucLed_dr6=0;
unsigned char ucLed_dr7=0;
unsigned char ucLed_dr8=0;
unsigned char ucLed_dr9=0;
unsigned char ucLed_dr10=0;
unsigned char ucLed_dr11=0;
unsigned char ucLed_dr12=0;
unsigned char ucLed_dr13=0;
unsigned char ucLed_dr14=0;
unsigned char ucLed_dr15=0;
unsigned char ucLed_dr16=0;

unsigned char ucLed_update=0;  //刷新变量。每次更改LED灯的状态都要更新一次。

unsigned char ucLedStep_09_16=0; //第9个至第16个LED跑马灯的步骤变量
unsigned int  uiTimeCnt_09_16=0; //第9个至第16个LED跑马灯的统计定时中断次数的延时计数器

unsigned char ucLedStatus16_09=0;   //代表底层74HC595输出状态的中间变量
unsigned char ucLedStatus08_01=0;   //代表底层74HC595输出状态的中间变量

void main()
  {
   initial_myself();  
   delay_long(100);   
   initial_peripheral();
   while(1)   
   {
      led_flicker_09_16(); // 第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
          led_update();  //LED更新函数
   }

}


void led_update()  //LED更新函数
{

   if(ucLed_update==1)
   {
       ucLed_update=0;   //及时清零,让它产生只更新一次的效果,避免一直更新。

       if(ucLed_dr1==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x01;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xfe;
           }

       if(ucLed_dr2==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x02;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xfd;
           }

       if(ucLed_dr3==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x04;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xfb;
           }

       if(ucLed_dr4==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x08;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xf7;
           }


       if(ucLed_dr5==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x10;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xef;
           }


       if(ucLed_dr6==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x20;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xdf;
           }


       if(ucLed_dr7==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x40;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xbf;
           }


       if(ucLed_dr8==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x80;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0x7f;
           }

       if(ucLed_dr9==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x01;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xfe;
           }

       if(ucLed_dr10==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x02;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xfd;
           }

       if(ucLed_dr11==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x04;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xfb;
           }

       if(ucLed_dr12==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x08;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xf7;
           }


       if(ucLed_dr13==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x10;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xef;
           }


       if(ucLed_dr14==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x20;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xdf;
           }


       if(ucLed_dr15==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x40;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xbf;
           }


       if(ucLed_dr16==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x80;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0x7f;
           }

       hc595_drive(ucLedStatus16_09,ucLedStatus08_01);  //74HC595底层驱动函数

   }
}

void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
{
   unsigned char i;
   unsigned char ucTempData;
   hc595_sh_dr=0;
   hc595_st_dr=0;

   ucTempData=ucLedStatusTemp16_09;  //先送高8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;

         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(15);
         hc595_sh_dr=1;
         delay_short(15);

         ucTempData=ucTempData<<1;
   }

   ucTempData=ucLedStatusTemp08_01;  //再先送低8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;

         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(15);
         hc595_sh_dr=1;
         delay_short(15);

         ucTempData=ucTempData<<1;
   }

   hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
   delay_short(15);
   hc595_st_dr=1;
   delay_short(15);

   hc595_sh_dr=0;    //拉低,抗干扰就增强
   hc595_st_dr=0;
   hc595_ds_dr=0;

}

/* 注释一:
* 以下程序,看似简单而且重复,其实蕴含着鸿哥的大智慧。
* 它是基于鸿哥的switch状态机思想,领略到了它的简单和精髓,
* 以后任何所谓复杂的工程项目,都不再复杂。
*/
void led_flicker_09_16() //第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
{
  switch(ucLedStep_09_16)
  {
     case 0:
           if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

               ucLed_dr16=0;  //第16个灭
               ucLed_dr9=1;  //第9个亮

               ucLed_update=1;  //更新显示
               ucLedStep_09_16=1; //切换到下一个步骤
           }
           break;
     case 1:
           if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

               ucLed_dr9=0;  //第9个灭
               ucLed_dr10=1;  //第10个亮

               ucLed_update=1;  //更新显示
               ucLedStep_09_16=2; //切换到下一个步骤
           }
           break;
     case 2:
           if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

               ucLed_dr10=0;  //第10个灭
               ucLed_dr11=1;  //第11个亮

               ucLed_update=1;  //更新显示
               ucLedStep_09_16=3; //切换到下一个步骤
           }
           break;
     case 3:
           if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

               ucLed_dr11=0;  //第11个灭
               ucLed_dr12=1;  //第12个亮

               ucLed_update=1;  //更新显示
               ucLedStep_09_16=4; //切换到下一个步骤
           }
           break;
     case 4:
           if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

               ucLed_dr12=0;  //第12个灭
               ucLed_dr13=1;  //第13个亮

               ucLed_update=1;  //更新显示
               ucLedStep_09_16=5; //切换到下一个步骤
           }
           break;
     case 5:
           if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

               ucLed_dr13=0;  //第13个灭
               ucLed_dr14=1;  //第14个亮

               ucLed_update=1;  //更新显示
               ucLedStep_09_16=6; //切换到下一个步骤
           }
           break;
     case 6:
           if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

               ucLed_dr14=0;  //第14个灭
               ucLed_dr15=1;  //第15个亮

               ucLed_update=1;  //更新显示
               ucLedStep_09_16=7; //切换到下一个步骤
           }
           break;
     case 7:
           if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

               ucLed_dr15=0;  //第15个灭
               ucLed_dr16=1;  //第16个亮

               ucLed_update=1;  //更新显示
               ucLedStep_09_16=0; //返回到开始处,重新开始新的一次循环
           }
           break;
   
   }

}


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

  if(uiTimeCnt_09_16<0xffff)  //设定这个条件,防止uiTimeCnt超范围。
  {
      uiTimeCnt_09_16++;  //累加定时中断的次数,
  }

  TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
  TL0=0x2f;
  TR0=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++)  //内嵌循环的空指令数量
          {
             ; //一个分号相当于执行一条空语句
          }
   }
}


void initial_myself()  //第一区 初始化单片机
{

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


  TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
  TL0=0x2f;


}

void initial_peripheral() //第二区 初始化外围
{
  EA=1;     //开总中断
  ET0=1;    //允许定时中断
  TR0=1;    //启动定时中断

}

总结陈词:
上一节和这一节讲了两种不同的跑马灯程序,如果要让这两种不同的跑马灯程序都能各自独立运行,就涉及到多任务并行处理的程序框架。没错,下一节就讲多任务并行处理这方面的知识,欲知详情,请听下回分解-----多任务并行处理两路跑马灯。
(未完待续,下节更精彩,不要走开哦)

作者: jianhong_wu    时间: 2014-2-1 05:34
第二十一节:多任务并行处理两路跑马灯。

开场白:
上一节讲了依次逐个亮灯并且每次只能亮一个灯的跑马灯程序。这一节要结合前面两节的内容,实现多任务并行处理两路跑马灯。要教会大家一个知识点:利用鸿哥的switch状态机思想,实现多任务并行处理的程序。
具体内容,请看源代码讲解。

(1)硬件平台:基于坚鸿51单片机学习板。

(2)实现功能:
第一路独立运行的任务是:第1个至第8个LED灯,先依次逐个亮,再依次逐个灭。
第二路独立运行的任务是:第9个至第16个LED灯,依次逐个亮灯并且每次只能亮一个灯。

(3)源代码讲解如下:
#include "REG52.H"

#define const_time_level_01_08  200  //第1个至第8个LED跑马灯的速度延时时间
#define const_time_level_09_16  300  //第9个至第16个LED跑马灯的速度延时时间

void initial_myself();   
void initial_peripheral();
void delay_short(unsigned int uiDelayShort);
void delay_long(unsigned int uiDelaylong);
void led_flicker_01_08(); //第一路独立运行的任务 第1个至第8个LED的跑马灯程序,逐个亮,逐个灭.
void led_flicker_09_16(); //第二路独立运行的任务 第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
void led_update();  //LED更新函数
void T0_time();  //定时中断函数


sbit hc595_sh_dr=P2^3;   
sbit hc595_st_dr=P2^4;  
sbit hc595_ds_dr=P2^5;  

unsigned char ucLed_dr1=0;   //代表16个灯的亮灭状态,0代表灭,1代表亮
unsigned char ucLed_dr2=0;
unsigned char ucLed_dr3=0;
unsigned char ucLed_dr4=0;
unsigned char ucLed_dr5=0;
unsigned char ucLed_dr6=0;
unsigned char ucLed_dr7=0;
unsigned char ucLed_dr8=0;
unsigned char ucLed_dr9=0;
unsigned char ucLed_dr10=0;
unsigned char ucLed_dr11=0;
unsigned char ucLed_dr12=0;
unsigned char ucLed_dr13=0;
unsigned char ucLed_dr14=0;
unsigned char ucLed_dr15=0;
unsigned char ucLed_dr16=0;

unsigned char ucLed_update=0;  //刷新变量。每次更改LED灯的状态都要更新一次。


unsigned char ucLedStep_01_08=0; //第1个至第8个LED跑马灯的步骤变量
unsigned int  uiTimeCnt_01_08=0; //第1个至第8个LED跑马灯的统计定时中断次数的延时计数器

unsigned char ucLedStep_09_16=0; //第9个至第16个LED跑马灯的步骤变量
unsigned int  uiTimeCnt_09_16=0; //第9个至第16个LED跑马灯的统计定时中断次数的延时计数器

unsigned char ucLedStatus16_09=0;   //代表底层74HC595输出状态的中间变量
unsigned char ucLedStatus08_01=0;   //代表底层74HC595输出状态的中间变量

void main()
  {
   initial_myself();  
   delay_long(100);   
   initial_peripheral();
   while(1)   
   {
      led_flicker_01_08(); //第一路独立运行的任务 第1个至第8个LED的跑马灯程序,逐个亮,逐个灭.
      led_flicker_09_16(); //第二路独立运行的任务 第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
          led_update();  //LED更新函数
   }

}


void led_update()  //LED更新函数
{

   if(ucLed_update==1)
   {
       ucLed_update=0;   //及时清零,让它产生只更新一次的效果,避免一直更新。

       if(ucLed_dr1==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x01;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xfe;
           }

       if(ucLed_dr2==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x02;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xfd;
           }

       if(ucLed_dr3==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x04;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xfb;
           }

       if(ucLed_dr4==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x08;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xf7;
           }


       if(ucLed_dr5==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x10;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xef;
           }


       if(ucLed_dr6==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x20;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xdf;
           }


       if(ucLed_dr7==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x40;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xbf;
           }


       if(ucLed_dr8==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x80;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0x7f;
           }

       if(ucLed_dr9==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x01;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xfe;
           }

       if(ucLed_dr10==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x02;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xfd;
           }

       if(ucLed_dr11==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x04;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xfb;
           }

       if(ucLed_dr12==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x08;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xf7;
           }


       if(ucLed_dr13==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x10;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xef;
           }


       if(ucLed_dr14==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x20;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xdf;
           }


       if(ucLed_dr15==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x40;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xbf;
           }


       if(ucLed_dr16==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x80;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0x7f;
           }

       hc595_drive(ucLedStatus16_09,ucLedStatus08_01);  //74HC595底层驱动函数

   }
}

void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
{
   unsigned char i;
   unsigned char ucTempData;
   hc595_sh_dr=0;
   hc595_st_dr=0;

   ucTempData=ucLedStatusTemp16_09;  //先送高8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;

         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(15);
         hc595_sh_dr=1;
         delay_short(15);

         ucTempData=ucTempData<<1;
   }

   ucTempData=ucLedStatusTemp08_01;  //再先送低8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;

         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(15);
         hc595_sh_dr=1;
         delay_short(15);

         ucTempData=ucTempData<<1;
   }

   hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
   delay_short(15);
   hc595_st_dr=1;
   delay_short(15);

   hc595_sh_dr=0;    //拉低,抗干扰就增强
   hc595_st_dr=0;
   hc595_ds_dr=0;

}

/* 注释一:
* 以下程序,看似简单而且重复,其实蕴含着鸿哥的大智慧。
* 它是基于鸿哥的switch状态机思想,领略到了它的简单和精髓,
* 以后任何所谓复杂的工程项目,都不再复杂。
*/

void led_flicker_01_08() //第一路独立运行的任务 第1个至第8个LED的跑马灯程序,逐个亮,逐个灭.
{
  switch(ucLedStep_01_08)
  {
     case 0:
           if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
           {
               uiTimeCnt_01_08=0; //时间计数器清零

               ucLed_dr1=1;  //第1个亮

               ucLed_update=1;  //更新显示
               ucLedStep_01_08=1; //切换到下一个步骤
           }
           break;
     case 1:
           if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
           {
               uiTimeCnt_01_08=0; //时间计数器清零

               ucLed_dr2=1;  //第2个亮

               ucLed_update=1;  //更新显示
               ucLedStep_01_08=2; //切换到下一个步骤
           }
           break;
     case 2:
           if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
           {
               uiTimeCnt_01_08=0; //时间计数器清零

               ucLed_dr3=1;  //第3个亮

               ucLed_update=1;  //更新显示
               ucLedStep_01_08=3; //切换到下一个步骤
           }
           break;
     case 3:
           if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
           {
               uiTimeCnt_01_08=0; //时间计数器清零

               ucLed_dr4=1;  //第4个亮

               ucLed_update=1;  //更新显示
               ucLedStep_01_08=4; //切换到下一个步骤
           }
           break;
     case 4:
           if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
           {
               uiTimeCnt_01_08=0; //时间计数器清零

               ucLed_dr5=1;  //第5个亮

               ucLed_update=1;  //更新显示
               ucLedStep_01_08=5; //切换到下一个步骤
           }
           break;
     case 5:
           if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
           {
               uiTimeCnt_01_08=0; //时间计数器清零

               ucLed_dr6=1;  //第6个亮

               ucLed_update=1;  //更新显示
               ucLedStep_01_08=6; //切换到下一个步骤
           }
           break;
     case 6:
           if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
           {
               uiTimeCnt_01_08=0; //时间计数器清零

               ucLed_dr7=1;  //第7个亮

               ucLed_update=1;  //更新显示
               ucLedStep_01_08=7; //切换到下一个步骤
           }
           break;
     case 7:
           if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
           {
               uiTimeCnt_01_08=0; //时间计数器清零

               ucLed_dr8=1;  //第8个亮

               ucLed_update=1;  //更新显示
               ucLedStep_01_08=8; //切换到下一个步骤
           }
           break;
     case 8:
           if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
           {
               uiTimeCnt_01_08=0; //时间计数器清零

               ucLed_dr8=0;  //第8个灭

               ucLed_update=1;  //更新显示
               ucLedStep_01_08=9; //切换到下一个步骤
           }
           break;
     case 9:
           if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
           {
               uiTimeCnt_01_08=0; //时间计数器清零

               ucLed_dr7=0;  //第7个灭

               ucLed_update=1;  //更新显示
               ucLedStep_01_08=10; //切换到下一个步骤
           }
           break;
     case 10:
           if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
           {
               uiTimeCnt_01_08=0; //时间计数器清零

               ucLed_dr6=0;  //第6个灭

               ucLed_update=1;  //更新显示
               ucLedStep_01_08=11; //切换到下一个步骤
           }
           break;
     case 11:
           if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
           {
               uiTimeCnt_01_08=0; //时间计数器清零

               ucLed_dr5=0;  //第5个灭

               ucLed_update=1;  //更新显示
               ucLedStep_01_08=12; //切换到下一个步骤
           }
           break;
     case 12:
           if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
           {
               uiTimeCnt_01_08=0; //时间计数器清零

               ucLed_dr4=0;  //第4个灭

               ucLed_update=1;  //更新显示
               ucLedStep_01_08=13; //切换到下一个步骤
           }
           break;
     case 13:
           if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
           {
               uiTimeCnt_01_08=0; //时间计数器清零

               ucLed_dr3=0;  //第3个灭

               ucLed_update=1;  //更新显示
               ucLedStep_01_08=14; //切换到下一个步骤
           }
           break;
     case 14:
           if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
           {
               uiTimeCnt_01_08=0; //时间计数器清零

               ucLed_dr2=0;  //第2个灭

               ucLed_update=1;  //更新显示
               ucLedStep_01_08=15; //切换到下一个步骤
           }
           break;
     case 15:
           if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
           {
               uiTimeCnt_01_08=0; //时间计数器清零

               ucLed_dr1=0;  //第1个灭

               ucLed_update=1;  //更新显示
               ucLedStep_01_08=0; //返回到最开始处,重新开始新的一次循环。
           }
           break;

   }

}


void led_flicker_09_16() //第二路独立运行的任务  第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
{
  switch(ucLedStep_09_16)
  {
     case 0:
           if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

               ucLed_dr16=0;  //第16个灭
               ucLed_dr9=1;  //第9个亮

               ucLed_update=1;  //更新显示
               ucLedStep_09_16=1; //切换到下一个步骤
           }
           break;
     case 1:
           if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

               ucLed_dr9=0;  //第9个灭
               ucLed_dr10=1;  //第10个亮

               ucLed_update=1;  //更新显示
               ucLedStep_09_16=2; //切换到下一个步骤
           }
           break;
     case 2:
           if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

               ucLed_dr10=0;  //第10个灭
               ucLed_dr11=1;  //第11个亮

               ucLed_update=1;  //更新显示
               ucLedStep_09_16=3; //切换到下一个步骤
           }
           break;
     case 3:
           if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

               ucLed_dr11=0;  //第11个灭
               ucLed_dr12=1;  //第12个亮

               ucLed_update=1;  //更新显示
               ucLedStep_09_16=4; //切换到下一个步骤
           }
           break;
     case 4:
           if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

               ucLed_dr12=0;  //第12个灭
               ucLed_dr13=1;  //第13个亮

               ucLed_update=1;  //更新显示
               ucLedStep_09_16=5; //切换到下一个步骤
           }
           break;
     case 5:
           if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

               ucLed_dr13=0;  //第13个灭
               ucLed_dr14=1;  //第14个亮

               ucLed_update=1;  //更新显示
               ucLedStep_09_16=6; //切换到下一个步骤
           }
           break;
     case 6:
           if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

               ucLed_dr14=0;  //第14个灭
               ucLed_dr15=1;  //第15个亮

               ucLed_update=1;  //更新显示
               ucLedStep_09_16=7; //切换到下一个步骤
           }
           break;
     case 7:
           if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

               ucLed_dr15=0;  //第15个灭
               ucLed_dr16=1;  //第16个亮

               ucLed_update=1;  //更新显示
               ucLedStep_09_16=0; //返回到开始处,重新开始新的一次循环
           }
           break;
   
   }

}


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

  if(uiTimeCnt_01_08<0xffff)  //设定这个条件,防止uiTimeCnt超范围。
  {
      uiTimeCnt_01_08++;  //累加定时中断的次数,
  }

  if(uiTimeCnt_09_16<0xffff)  //设定这个条件,防止uiTimeCnt超范围。
  {
      uiTimeCnt_09_16++;  //累加定时中断的次数,
  }

  TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
  TL0=0x2f;
  TR0=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++)  //内嵌循环的空指令数量
          {
             ; //一个分号相当于执行一条空语句
          }
   }
}


void initial_myself()  //第一区 初始化单片机
{

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


  TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
  TL0=0x2f;


}

void initial_peripheral() //第二区 初始化外围
{
  EA=1;     //开总中断
  ET0=1;    //允许定时中断
  TR0=1;    //启动定时中断

}

总结陈词:
这一节讲了多任务并行处理两路跑马灯的程序,从下一节开始,将会在跑马灯的基础上,新加入按键这个元素。如何把按键跟跑马灯的任务有效的关联起来,欲知详情,请听下回分解-----独立按键控制跑马灯的方向。

(未完待续,下节更精彩,不要走开哦)
作者: jianhong_wu    时间: 2014-2-1 05:35
第二十二节:独立按键控制跑马灯的方向。

开场白:
上一节讲了多任务并行处理两路跑马灯的程序。这一节要教会大家一个知识点:如何通过一个中间变量把按键跟跑马灯的任务有效的关联起来。
具体内容,请看源代码讲解。

(1)硬件平台:基于坚鸿51单片机学习板。用矩阵键盘中的S1键作为改变方向的独立按键,记得把输出线P0.4一直输出低电平,模拟独立按键的触发地GND。

(2)实现功能:
第1个至第8个LED灯一直不亮。在第9个至第16个LED灯,依次逐个亮灯并且每次只能亮一个灯。按一次独立按键S1,将会更改跑马灯的运动方向。

(3)源代码讲解如下:
#include "REG52.H"

#define const_time_level_09_16  300  //第9个至第16个LED跑马灯的速度延时时间

#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);

void led_flicker_09_16(); //第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
void led_update();  //LED更新函数
void T0_time();  //定时中断函数

void key_service(); //按键服务的应用程序
void key_scan(); //按键扫描函数 放在定时中断里

sbit hc595_sh_dr=P2^3;   
sbit hc595_st_dr=P2^4;  
sbit hc595_ds_dr=P2^5;  

sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平

unsigned char ucKeySec=0;   //被触发的按键编号

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

unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器

unsigned char ucLed_dr1=0;   //代表16个灯的亮灭状态,0代表灭,1代表亮
unsigned char ucLed_dr2=0;
unsigned char ucLed_dr3=0;
unsigned char ucLed_dr4=0;
unsigned char ucLed_dr5=0;
unsigned char ucLed_dr6=0;
unsigned char ucLed_dr7=0;
unsigned char ucLed_dr8=0;
unsigned char ucLed_dr9=0;
unsigned char ucLed_dr10=0;
unsigned char ucLed_dr11=0;
unsigned char ucLed_dr12=0;
unsigned char ucLed_dr13=0;
unsigned char ucLed_dr14=0;
unsigned char ucLed_dr15=0;
unsigned char ucLed_dr16=0;

unsigned char ucLed_update=0;  //刷新变量。每次更改LED灯的状态都要更新一次。


unsigned char ucLedStep_09_16=0; //第9个至第16个LED跑马灯的步骤变量
unsigned int  uiTimeCnt_09_16=0; //第9个至第16个LED跑马灯的统计定时中断次数的延时计数器

unsigned char ucLedStatus16_09=0;   //代表底层74HC595输出状态的中间变量
unsigned char ucLedStatus08_01=0;   //代表底层74HC595输出状态的中间变量

unsigned char ucLedDirFlag=0;   //方向变量,把按键与跑马灯关联起来的核心变量,0代表正方向,1代表反方向

void main()
  {
   initial_myself();  
   delay_long(100);   
   initial_peripheral();
   while(1)   
   {
      led_flicker_09_16(); //第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
          led_update();  //LED更新函数
      key_service(); //按键服务的应用程序
   }

}


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

  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号键
     }
  }



}


void key_service() //按键服务的应用程序
{
  switch(ucKeySec) //按键服务状态切换
  {
    case 1:// 改变跑马灯方向的按键 对应朱兆祺学习板的S1键

          if(ucLedDirFlag==0) //通过中间变量改变跑马灯的方向
                  {
                     ucLedDirFlag=1;
                  }
                  else
                  {
                           ucLedDirFlag=0;
                  }

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




void led_update()  //LED更新函数
{

   if(ucLed_update==1)
   {
       ucLed_update=0;   //及时清零,让它产生只更新一次的效果,避免一直更新。

       if(ucLed_dr1==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x01;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xfe;
           }

       if(ucLed_dr2==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x02;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xfd;
           }

       if(ucLed_dr3==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x04;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xfb;
           }

       if(ucLed_dr4==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x08;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xf7;
           }


       if(ucLed_dr5==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x10;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xef;
           }


       if(ucLed_dr6==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x20;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xdf;
           }


       if(ucLed_dr7==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x40;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xbf;
           }


       if(ucLed_dr8==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x80;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0x7f;
           }

       if(ucLed_dr9==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x01;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xfe;
           }

       if(ucLed_dr10==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x02;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xfd;
           }

       if(ucLed_dr11==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x04;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xfb;
           }

       if(ucLed_dr12==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x08;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xf7;
           }


       if(ucLed_dr13==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x10;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xef;
           }


       if(ucLed_dr14==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x20;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xdf;
           }


       if(ucLed_dr15==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x40;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xbf;
           }


       if(ucLed_dr16==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x80;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0x7f;
           }

       hc595_drive(ucLedStatus16_09,ucLedStatus08_01);  //74HC595底层驱动函数

   }
}

void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
{
   unsigned char i;
   unsigned char ucTempData;
   hc595_sh_dr=0;
   hc595_st_dr=0;

   ucTempData=ucLedStatusTemp16_09;  //先送高8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;

         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(15);
         hc595_sh_dr=1;
         delay_short(15);

         ucTempData=ucTempData<<1;
   }

   ucTempData=ucLedStatusTemp08_01;  //再先送低8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;

         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(15);
         hc595_sh_dr=1;
         delay_short(15);

         ucTempData=ucTempData<<1;
   }

   hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
   delay_short(15);
   hc595_st_dr=1;
   delay_short(15);

   hc595_sh_dr=0;    //拉低,抗干扰就增强
   hc595_st_dr=0;
   hc595_ds_dr=0;

}

/* 注释一:
* 以下程序,要学会如何通过中间变量,把按键和跑马灯的任务关联起来
*/

void led_flicker_09_16() //第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
{
  switch(ucLedStep_09_16)
  {
     case 0:
           if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

                           if(ucLedDirFlag==0)  //正方向
                           {
                  ucLed_dr16=0;  //第16个灭
                  ucLed_dr9=1;  //第9个亮

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=1; //切换到下一个步骤
                           }
                           else  //反方向
                           {
                  ucLed_dr15=1;  //第15个亮
                  ucLed_dr16=0;  //第16个灭

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=7; //返回上一个步骤
                           }
           }
           break;
     case 1:
           if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

                           if(ucLedDirFlag==0)  //正方向
                           {
                  ucLed_dr9=0;  //第9个灭
                  ucLed_dr10=1;  //第10个亮

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=2; //切换到下一个步骤
                           }
                           else  //反方向
                           {
                  ucLed_dr16=1;  //第16个亮
                  ucLed_dr9=0;  //第9个灭

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=0; //返回上一个步骤
                           }
           }
           break;
     case 2:
           if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

                           if(ucLedDirFlag==0)  //正方向
                           {
                  ucLed_dr10=0;  //第10个灭
                  ucLed_dr11=1;  //第11个亮

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=3; //切换到下一个步骤
                           }
                           else  //反方向
                           {
                  ucLed_dr9=1;  //第9个亮
                  ucLed_dr10=0;  //第10个灭

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=1; //返回上一个步骤
                           }
           }
           break;
     case 3:
           if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

                           if(ucLedDirFlag==0)  //正方向
                           {
                  ucLed_dr11=0;  //第11个灭
                  ucLed_dr12=1;  //第12个亮

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=4; //切换到下一个步骤
                           }
                           else  //反方向
                           {
                  ucLed_dr10=1;  //第10个亮
                  ucLed_dr11=0;  //第11个灭

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=2; //返回上一个步骤
                           }
           }
           break;
     case 4:
           if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

                           if(ucLedDirFlag==0)  //正方向
                           {
                  ucLed_dr12=0;  //第12个灭
                  ucLed_dr13=1;  //第13个亮

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=5; //切换到下一个步骤
                           }
                           else  //反方向
                           {
                  ucLed_dr11=1;  //第11个亮
                  ucLed_dr12=0;  //第12个灭

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=3; //返回上一个步骤
                           }
           }
           break;
     case 5:
           if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

                           if(ucLedDirFlag==0)  //正方向
                           {
                  ucLed_dr13=0;  //第13个灭
                  ucLed_dr14=1;  //第14个亮

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=6; //切换到下一个步骤
                           }
                           else  //反方向
                           {
                  ucLed_dr12=1;  //第12个亮
                  ucLed_dr13=0;  //第13个灭

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=4; //返回上一个步骤
                           }
           }
           break;
     case 6:
           if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

                           if(ucLedDirFlag==0)  //正方向
                           {
                  ucLed_dr14=0;  //第14个灭
                  ucLed_dr15=1;  //第15个亮

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=7; //切换到下一个步骤
                           }
                           else  //反方向
                           {
                  ucLed_dr13=1;  //第13个亮
                  ucLed_dr14=0;  //第14个灭

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=5; //返回上一个步骤
                           }
           }
           break;
     case 7:
           if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

                           if(ucLedDirFlag==0)  //正方向
                           {
                  ucLed_dr15=0;  //第15个灭
                  ucLed_dr16=1;  //第16个亮

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=0; //返回到开始处,重新开始新的一次循环
                           }
                           else  //反方向
                           {
                  ucLed_dr14=1;  //第14个亮
                  ucLed_dr15=0;  //第15个灭

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=6; //返回上一个步骤
                           }
           }
           break;
   
   }

}


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


  if(uiTimeCnt_09_16<0xffff)  //设定这个条件,防止uiTimeCnt超范围。
  {
      uiTimeCnt_09_16++;  //累加定时中断的次数,
  }

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


void initial_myself()  //第一区 初始化单片机
{
/* 注释二:
* 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
* 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
* 坚鸿51学习板的S1就是本程序中用到的一个独立按键。
*/
  key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平

  beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

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


  TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
  TL0=0x2f;


}

void initial_peripheral() //第二区 初始化外围
{
  EA=1;     //开总中断
  ET0=1;    //允许定时中断
  TR0=1;    //启动定时中断

}

总结陈词:
这一节讲了独立按键控制跑马灯的方向。如果按键要控制跑马灯的速度,我们该怎么编写程序呢?欲知详情,请听下回分解-----独立按键控制跑马灯的速度。

(未完待续,下节更精彩,不要走开哦)
作者: jianhong_wu    时间: 2014-2-1 05:36
第二十三节:独立按键控制跑马灯的速度。

开场白:
上一节讲了独立按键控制跑马灯的方向。这一节继续要教会大家一个知识点:如何通过一个中间变量把按键跟跑马灯的速度有效关联起来。
具体内容,请看源代码讲解。

(1)硬件平台:基于坚鸿51单片机学习板。在上一节的基础上,增加一个加速按键和一个减速按键,用矩阵键盘中的S5键作为加速独立按键,用矩阵键盘中的S9键作为减速独立按键,记得把输出线P0.4一直输出低电平,模拟独立按键的触发地GND。

(2)实现功能:
在上一节的基础上,第1个至第8个LED灯一直不亮。在第9个至第16个LED灯,依次逐个亮灯并且每次只能亮一个灯。每按一次独立按键S5,速度都会加快。每按一次独立按键S9,速度都会减慢。跟上一节一样,用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    //按键去抖动延时的时间

void initial_myself();   
void initial_peripheral();
void delay_short(unsigned int uiDelayShort);
void delay_long(unsigned int uiDelaylong);

void led_flicker_09_16(); //第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
void led_update();  //LED更新函数
void T0_time();  //定时中断函数

void key_service(); //按键服务的应用程序
void key_scan(); //按键扫描函数 放在定时中断里

sbit hc595_sh_dr=P2^3;   
sbit hc595_st_dr=P2^4;  
sbit hc595_ds_dr=P2^5;  

sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键
sbit key_sr3=P0^2; //对应朱兆祺学习板的S9键

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

unsigned char ucKeySec=0;   //被触发的按键编号

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

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

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

unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器

unsigned char ucLed_dr1=0;   //代表16个灯的亮灭状态,0代表灭,1代表亮
unsigned char ucLed_dr2=0;
unsigned char ucLed_dr3=0;
unsigned char ucLed_dr4=0;
unsigned char ucLed_dr5=0;
unsigned char ucLed_dr6=0;
unsigned char ucLed_dr7=0;
unsigned char ucLed_dr8=0;
unsigned char ucLed_dr9=0;
unsigned char ucLed_dr10=0;
unsigned char ucLed_dr11=0;
unsigned char ucLed_dr12=0;
unsigned char ucLed_dr13=0;
unsigned char ucLed_dr14=0;
unsigned char ucLed_dr15=0;
unsigned char ucLed_dr16=0;

unsigned char ucLed_update=0;  //刷新变量。每次更改LED灯的状态都要更新一次。


unsigned char ucLedStep_09_16=0; //第9个至第16个LED跑马灯的步骤变量
unsigned int  uiTimeCnt_09_16=0; //第9个至第16个LED跑马灯的统计定时中断次数的延时计数器

unsigned char ucLedStatus16_09=0;   //代表底层74HC595输出状态的中间变量
unsigned char ucLedStatus08_01=0;   //代表底层74HC595输出状态的中间变量

unsigned char ucLedDirFlag=0;   //方向变量,把按键与跑马灯关联起来的核心变量,0代表正方向,1代表反方向
unsigned int  uiSetTimeLevel_09_16=300;  //速度变量,此数值越大速度越慢,此数值越小速度越快。

void main()
  {
   initial_myself();  
   delay_long(100);   
   initial_peripheral();
   while(1)   
   {
      led_flicker_09_16(); //第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
          led_update();  //LED更新函数
      key_service(); //按键服务的应用程序
   }

}


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

  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号键
     }
  }

}


void key_service() //按键服务的应用程序
{
  switch(ucKeySec) //按键服务状态切换
  {
    case 1:// 改变跑马灯方向的按键 对应朱兆祺学习板的S1键

          if(ucLedDirFlag==0) //通过中间变量改变跑马灯的方向
                  {
                     ucLedDirFlag=1;
                  }
                  else
                  {
                           ucLedDirFlag=0;
                  }

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
   
    case 2:// 加速按键 对应朱兆祺学习板的S5键 uiSetTimeLevel_09_16越小速度越快
          uiSetTimeLevel_09_16=uiSetTimeLevel_09_16-10;
                  if(uiSetTimeLevel_09_16<50)  //最快限定在50
                  {
                      uiSetTimeLevel_09_16=50;
                  }

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

    case 3:// 减速按键 对应朱兆祺学习板的S9键  uiSetTimeLevel_09_16越大速度越慢
          uiSetTimeLevel_09_16=uiSetTimeLevel_09_16+10;
                  if(uiSetTimeLevel_09_16>550)  //最慢限定在550
                  {
                      uiSetTimeLevel_09_16=550;
                  }

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




void led_update()  //LED更新函数
{

   if(ucLed_update==1)
   {
       ucLed_update=0;   //及时清零,让它产生只更新一次的效果,避免一直更新。

       if(ucLed_dr1==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x01;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xfe;
           }

       if(ucLed_dr2==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x02;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xfd;
           }

       if(ucLed_dr3==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x04;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xfb;
           }

       if(ucLed_dr4==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x08;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xf7;
           }


       if(ucLed_dr5==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x10;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xef;
           }


       if(ucLed_dr6==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x20;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xdf;
           }


       if(ucLed_dr7==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x40;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xbf;
           }


       if(ucLed_dr8==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x80;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0x7f;
           }

       if(ucLed_dr9==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x01;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xfe;
           }

       if(ucLed_dr10==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x02;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xfd;
           }

       if(ucLed_dr11==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x04;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xfb;
           }

       if(ucLed_dr12==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x08;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xf7;
           }


       if(ucLed_dr13==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x10;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xef;
           }


       if(ucLed_dr14==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x20;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xdf;
           }


       if(ucLed_dr15==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x40;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xbf;
           }


       if(ucLed_dr16==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x80;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0x7f;
           }

       hc595_drive(ucLedStatus16_09,ucLedStatus08_01);  //74HC595底层驱动函数

   }
}

void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
{
   unsigned char i;
   unsigned char ucTempData;
   hc595_sh_dr=0;
   hc595_st_dr=0;

   ucTempData=ucLedStatusTemp16_09;  //先送高8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;

         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(15);
         hc595_sh_dr=1;
         delay_short(15);

         ucTempData=ucTempData<<1;
   }

   ucTempData=ucLedStatusTemp08_01;  //再先送低8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;

         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(15);
         hc595_sh_dr=1;
         delay_short(15);

         ucTempData=ucTempData<<1;
   }

   hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
   delay_short(15);
   hc595_st_dr=1;
   delay_short(15);

   hc595_sh_dr=0;    //拉低,抗干扰就增强
   hc595_st_dr=0;
   hc595_ds_dr=0;

}

/* 注释一:
* 以下程序,要学会如何通过中间变量,把按键和跑马灯的任务关联起来
*/

void led_flicker_09_16() //第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
{
  switch(ucLedStep_09_16)
  {
     case 0:
           if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

                           if(ucLedDirFlag==0)  //正方向
                           {
                  ucLed_dr16=0;  //第16个灭
                  ucLed_dr9=1;  //第9个亮

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=1; //切换到下一个步骤
                           }
                           else  //反方向
                           {
                  ucLed_dr15=1;  //第15个亮
                  ucLed_dr16=0;  //第16个灭

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=7; //返回上一个步骤
                           }
           }
           break;
     case 1:
           if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

                           if(ucLedDirFlag==0)  //正方向
                           {
                  ucLed_dr9=0;  //第9个灭
                  ucLed_dr10=1;  //第10个亮

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=2; //切换到下一个步骤
                           }
                           else  //反方向
                           {
                  ucLed_dr16=1;  //第16个亮
                  ucLed_dr9=0;  //第9个灭

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=0; //返回上一个步骤
                           }
           }
           break;
     case 2:
           if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

                           if(ucLedDirFlag==0)  //正方向
                           {
                  ucLed_dr10=0;  //第10个灭
                  ucLed_dr11=1;  //第11个亮

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=3; //切换到下一个步骤
                           }
                           else  //反方向
                           {
                  ucLed_dr9=1;  //第9个亮
                  ucLed_dr10=0;  //第10个灭

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=1; //返回上一个步骤
                           }
           }
           break;
     case 3:
           if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

                           if(ucLedDirFlag==0)  //正方向
                           {
                  ucLed_dr11=0;  //第11个灭
                  ucLed_dr12=1;  //第12个亮

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=4; //切换到下一个步骤
                           }
                           else  //反方向
                           {
                  ucLed_dr10=1;  //第10个亮
                  ucLed_dr11=0;  //第11个灭

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=2; //返回上一个步骤
                           }
           }
           break;
     case 4:
           if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

                           if(ucLedDirFlag==0)  //正方向
                           {
                  ucLed_dr12=0;  //第12个灭
                  ucLed_dr13=1;  //第13个亮

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=5; //切换到下一个步骤
                           }
                           else  //反方向
                           {
                  ucLed_dr11=1;  //第11个亮
                  ucLed_dr12=0;  //第12个灭

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=3; //返回上一个步骤
                           }
           }
           break;
     case 5:
           if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

                           if(ucLedDirFlag==0)  //正方向
                           {
                  ucLed_dr13=0;  //第13个灭
                  ucLed_dr14=1;  //第14个亮

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=6; //切换到下一个步骤
                           }
                           else  //反方向
                           {
                  ucLed_dr12=1;  //第12个亮
                  ucLed_dr13=0;  //第13个灭

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=4; //返回上一个步骤
                           }
           }
           break;
     case 6:
           if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

                           if(ucLedDirFlag==0)  //正方向
                           {
                  ucLed_dr14=0;  //第14个灭
                  ucLed_dr15=1;  //第15个亮

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=7; //切换到下一个步骤
                           }
                           else  //反方向
                           {
                  ucLed_dr13=1;  //第13个亮
                  ucLed_dr14=0;  //第14个灭

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=5; //返回上一个步骤
                           }
           }
           break;
     case 7:
           if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

                           if(ucLedDirFlag==0)  //正方向
                           {
                  ucLed_dr15=0;  //第15个灭
                  ucLed_dr16=1;  //第16个亮

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=0; //返回到开始处,重新开始新的一次循环
                           }
                           else  //反方向
                           {
                  ucLed_dr14=1;  //第14个亮
                  ucLed_dr15=0;  //第15个灭

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=6; //返回上一个步骤
                           }
           }
           break;
   
   }

}


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


  if(uiTimeCnt_09_16<0xffff)  //设定这个条件,防止uiTimeCnt超范围。
  {
      uiTimeCnt_09_16++;  //累加定时中断的次数,
  }

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


void initial_myself()  //第一区 初始化单片机
{
/* 注释二:
* 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
* 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
* 坚鸿51学习板的S1就是本程序中用到的一个独立按键。
*/
  key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平

  beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

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


  TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
  TL0=0x2f;


}

void initial_peripheral() //第二区 初始化外围
{
  EA=1;     //开总中断
  ET0=1;    //允许定时中断
  TR0=1;    //启动定时中断

}

总结陈词:
这一节讲了独立按键控制跑马灯的速度。如果按键要控制跑马灯的启动和暂停,我们该怎么编写程序呢?欲知详情,请听下回分解-----独立按键控制跑马灯的启动和暂停。

(未完待续,下节更精彩,不要走开哦)
作者: jianhong_wu    时间: 2014-2-1 05:37
第二十四节:独立按键控制跑马灯的启动和暂停。

开场白:
上一节讲了独立按键控制跑马灯的速度。这一节继续要教会大家一个知识点:如何通过一个中间变量把按键跟跑马灯的启动和暂停有效关联起来。
具体内容,请看源代码讲解。

(1)硬件平台:基于坚鸿51单片机学习板。在上一节的基础上,增加一个启动和暂停按键,用矩阵键盘中的S13键作为启动和暂停独立按键,记得把输出线P0.4一直输出低电平,模拟独立按键的触发地GND。

(2)实现功能:
在上一节的基础上,第1个至第8个LED灯一直不亮。在第9个至第16个LED灯,依次逐个亮灯并且每次只能亮一个灯。每按一次独立按键S13键,原来运行的跑马灯会暂停,原来暂停的跑马灯会运行。其它跟上一节一样,用S1来改变方向,用S5和S9来改变速度。

(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    //按键去抖动延时的时间

void initial_myself();   
void initial_peripheral();
void delay_short(unsigned int uiDelayShort);
void delay_long(unsigned int uiDelaylong);

void led_flicker_09_16(); //第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
void led_update();  //LED更新函数
void T0_time();  //定时中断函数

void key_service(); //按键服务的应用程序
void key_scan(); //按键扫描函数 放在定时中断里

sbit hc595_sh_dr=P2^3;   
sbit hc595_st_dr=P2^4;  
sbit hc595_ds_dr=P2^5;  

sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
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,因此必须一直输出低电平

unsigned char ucKeySec=0;   //被触发的按键编号

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

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

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


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

unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器

unsigned char ucLed_dr1=0;   //代表16个灯的亮灭状态,0代表灭,1代表亮
unsigned char ucLed_dr2=0;
unsigned char ucLed_dr3=0;
unsigned char ucLed_dr4=0;
unsigned char ucLed_dr5=0;
unsigned char ucLed_dr6=0;
unsigned char ucLed_dr7=0;
unsigned char ucLed_dr8=0;
unsigned char ucLed_dr9=0;
unsigned char ucLed_dr10=0;
unsigned char ucLed_dr11=0;
unsigned char ucLed_dr12=0;
unsigned char ucLed_dr13=0;
unsigned char ucLed_dr14=0;
unsigned char ucLed_dr15=0;
unsigned char ucLed_dr16=0;

unsigned char ucLed_update=0;  //刷新变量。每次更改LED灯的状态都要更新一次。


unsigned char ucLedStep_09_16=0; //第9个至第16个LED跑马灯的步骤变量
unsigned int  uiTimeCnt_09_16=0; //第9个至第16个LED跑马灯的统计定时中断次数的延时计数器

unsigned char ucLedStatus16_09=0;   //代表底层74HC595输出状态的中间变量
unsigned char ucLedStatus08_01=0;   //代表底层74HC595输出状态的中间变量

unsigned char ucLedDirFlag=0;   //方向变量,把按键与跑马灯关联起来的核心变量,0代表正方向,1代表反方向
unsigned int  uiSetTimeLevel_09_16=300;  //速度变量,此数值越大速度越慢,此数值越小速度越快。
unsigned char ucLedStartFlag=1;   //启动和暂停的变量,0代表暂停,1代表启动

void main()
  {
   initial_myself();  
   delay_long(100);   
   initial_peripheral();
   while(1)   
   {
      led_flicker_09_16(); //第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
          led_update();  //LED更新函数
      key_service(); //按键服务的应用程序
   }

}


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

  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() //按键服务的应用程序
{
  switch(ucKeySec) //按键服务状态切换
  {
    case 1:// 改变跑马灯方向的按键 对应朱兆祺学习板的S1键

          if(ucLedDirFlag==0) //通过中间变量改变跑马灯的方向
                  {
                     ucLedDirFlag=1;
                  }
                  else
                  {
                           ucLedDirFlag=0;
                  }

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
   
    case 2:// 加速按键 对应朱兆祺学习板的S5键 uiSetTimeLevel_09_16越小速度越快
          uiSetTimeLevel_09_16=uiSetTimeLevel_09_16-10;
                  if(uiSetTimeLevel_09_16<50)  //最快限定在50
                  {
                      uiSetTimeLevel_09_16=50;
                  }

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

    case 3:// 减速按键 对应朱兆祺学习板的S9键  uiSetTimeLevel_09_16越大速度越慢
          uiSetTimeLevel_09_16=uiSetTimeLevel_09_16+10;
                  if(uiSetTimeLevel_09_16>550)  //最慢限定在550
                  {
                      uiSetTimeLevel_09_16=550;
                  }

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;         
         
    case 4:// 启动和暂停按键 对应朱兆祺学习板的S13键  ucLedStartFlag为0时代表暂停,为1时代表启动

              if(ucLedStartFlag==1)  //启动和暂停两种状态循环切换
                  {
                     ucLedStartFlag=0;
                  }
                  else                   //启动和暂停两种状态循环切换
                  {
                           ucLedStartFlag=1;
                  }

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




void led_update()  //LED更新函数
{

   if(ucLed_update==1)
   {
       ucLed_update=0;   //及时清零,让它产生只更新一次的效果,避免一直更新。

       if(ucLed_dr1==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x01;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xfe;
           }

       if(ucLed_dr2==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x02;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xfd;
           }

       if(ucLed_dr3==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x04;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xfb;
           }

       if(ucLed_dr4==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x08;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xf7;
           }


       if(ucLed_dr5==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x10;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xef;
           }


       if(ucLed_dr6==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x20;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xdf;
           }


       if(ucLed_dr7==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x40;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xbf;
           }


       if(ucLed_dr8==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x80;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0x7f;
           }

       if(ucLed_dr9==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x01;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xfe;
           }

       if(ucLed_dr10==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x02;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xfd;
           }

       if(ucLed_dr11==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x04;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xfb;
           }

       if(ucLed_dr12==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x08;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xf7;
           }


       if(ucLed_dr13==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x10;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xef;
           }


       if(ucLed_dr14==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x20;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xdf;
           }


       if(ucLed_dr15==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x40;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xbf;
           }


       if(ucLed_dr16==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x80;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0x7f;
           }

       hc595_drive(ucLedStatus16_09,ucLedStatus08_01);  //74HC595底层驱动函数

   }
}

void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
{
   unsigned char i;
   unsigned char ucTempData;
   hc595_sh_dr=0;
   hc595_st_dr=0;

   ucTempData=ucLedStatusTemp16_09;  //先送高8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;

         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(15);
         hc595_sh_dr=1;
         delay_short(15);

         ucTempData=ucTempData<<1;
   }

   ucTempData=ucLedStatusTemp08_01;  //再先送低8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;

         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(15);
         hc595_sh_dr=1;
         delay_short(15);

         ucTempData=ucTempData<<1;
   }

   hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
   delay_short(15);
   hc595_st_dr=1;
   delay_short(15);

   hc595_sh_dr=0;    //拉低,抗干扰就增强
   hc595_st_dr=0;
   hc595_ds_dr=0;

}

/* 注释一:
* 以下程序,要学会如何通过中间变量,把按键和跑马灯的任务关联起来
*/

void led_flicker_09_16() //第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
{
  if(ucLedStartFlag==1)  //此变量为1时代表启动
  {
     switch(ucLedStep_09_16)
     {
     case 0:
           if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

                           if(ucLedDirFlag==0)  //正方向
                           {
                  ucLed_dr16=0;  //第16个灭
                  ucLed_dr9=1;  //第9个亮

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=1; //切换到下一个步骤
                           }
                           else  //反方向
                           {
                  ucLed_dr15=1;  //第15个亮
                  ucLed_dr16=0;  //第16个灭

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=7; //返回上一个步骤
                           }
           }
           break;
     case 1:
           if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

                           if(ucLedDirFlag==0)  //正方向
                           {
                  ucLed_dr9=0;  //第9个灭
                  ucLed_dr10=1;  //第10个亮

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=2; //切换到下一个步骤
                           }
                           else  //反方向
                           {
                  ucLed_dr16=1;  //第16个亮
                  ucLed_dr9=0;  //第9个灭

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=0; //返回上一个步骤
                           }
           }
           break;
     case 2:
           if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

                           if(ucLedDirFlag==0)  //正方向
                           {
                  ucLed_dr10=0;  //第10个灭
                  ucLed_dr11=1;  //第11个亮

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=3; //切换到下一个步骤
                           }
                           else  //反方向
                           {
                  ucLed_dr9=1;  //第9个亮
                  ucLed_dr10=0;  //第10个灭

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=1; //返回上一个步骤
                           }
           }
           break;
     case 3:
           if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

                           if(ucLedDirFlag==0)  //正方向
                           {
                  ucLed_dr11=0;  //第11个灭
                  ucLed_dr12=1;  //第12个亮

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=4; //切换到下一个步骤
                           }
                           else  //反方向
                           {
                  ucLed_dr10=1;  //第10个亮
                  ucLed_dr11=0;  //第11个灭

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=2; //返回上一个步骤
                           }
           }
           break;
     case 4:
           if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

                           if(ucLedDirFlag==0)  //正方向
                           {
                  ucLed_dr12=0;  //第12个灭
                  ucLed_dr13=1;  //第13个亮

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=5; //切换到下一个步骤
                           }
                           else  //反方向
                           {
                  ucLed_dr11=1;  //第11个亮
                  ucLed_dr12=0;  //第12个灭

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=3; //返回上一个步骤
                           }
           }
           break;
     case 5:
           if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

                           if(ucLedDirFlag==0)  //正方向
                           {
                  ucLed_dr13=0;  //第13个灭
                  ucLed_dr14=1;  //第14个亮

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=6; //切换到下一个步骤
                           }
                           else  //反方向
                           {
                  ucLed_dr12=1;  //第12个亮
                  ucLed_dr13=0;  //第13个灭

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=4; //返回上一个步骤
                           }
           }
           break;
     case 6:
           if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

                           if(ucLedDirFlag==0)  //正方向
                           {
                  ucLed_dr14=0;  //第14个灭
                  ucLed_dr15=1;  //第15个亮

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=7; //切换到下一个步骤
                           }
                           else  //反方向
                           {
                  ucLed_dr13=1;  //第13个亮
                  ucLed_dr14=0;  //第14个灭

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=5; //返回上一个步骤
                           }
           }
           break;
     case 7:
           if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
           {
               uiTimeCnt_09_16=0; //时间计数器清零

                           if(ucLedDirFlag==0)  //正方向
                           {
                  ucLed_dr15=0;  //第15个灭
                  ucLed_dr16=1;  //第16个亮

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=0; //返回到开始处,重新开始新的一次循环
                           }
                           else  //反方向
                           {
                  ucLed_dr14=1;  //第14个亮
                  ucLed_dr15=0;  //第15个灭

                  ucLed_update=1;  //更新显示
                  ucLedStep_09_16=6; //返回上一个步骤
                           }
           }
           break;
   
      }
   }

}


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


  if(uiTimeCnt_09_16<0xffff)  //设定这个条件,防止uiTimeCnt超范围。
  {
      if(ucLedStartFlag==1)  //此变量为1时代表启动
          {
         uiTimeCnt_09_16++;  //累加定时中断的次数,
          }
  }

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


void initial_myself()  //第一区 初始化单片机
{
/* 注释二:
* 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
* 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
* 坚鸿51学习板的S1就是本程序中用到的一个独立按键。
*/
  key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平

  beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

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


  TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
  TL0=0x2f;


}

void initial_peripheral() //第二区 初始化外围
{
  EA=1;     //开总中断
  ET0=1;    //允许定时中断
  TR0=1;    //启动定时中断

}

总结陈词:
这几节循序渐进地讲了独立按键控制跑马灯各种状态的程序。在很多实际工控项目中,经常会涉及到运动的自动控制,运动的自动控制就必然会涉及到感应器。下一节我将会讲感应器和运动控制的程序框架,欲知详情,请听下回分解-----用LED灯和按键来模拟工业自动化设备的运动控制。

(未完待续,下节更精彩,不要走开哦)
作者: jianhong_wu    时间: 2014-2-10 02:18
第二十五节:用LED灯和按键来模拟工业自动化设备的运动控制。

开场白:
      前面三节讲了独立按键控制跑马灯的各种状态,这一节我们要做一个机械手控制程序,这个机械手可以左右移动,最左边有一个开关感应器,最右边也有一个开关感应器。它也可以上下移动,最下面有一个开关感应器。左右移动是通过一个气缸控制,上下移动也是通过一个气缸控制。而单片机控制气缸,本质上是通过三极管把信号放大,然后控制气缸上的电磁阀。这个系统机械手驱动部分的输出和输入信号如下:
    2个输出IO口,分别控制2个气缸。对于左右移动的气缸,当IO口为0时往左边跑,当IO口为1时往右边跑。对于上下移动的气缸,当IO口为0时往上边跑,当IO口为1时往下边跑。
      3个输入IO口,分别检测3个开关感应器。感应器没有被触发时,IO口检测为高电平1。被触发时,IO口检测为低电平0。
这一节继续要教会大家两个知识点:
第一点:如何用软件进行开关感应器的抗干扰处理。
第二点:如何用Switch语句搭建工业自动控制的程序框架。还是那句话,我们只要以Switch语句为支点,再复杂再繁琐的程序都可以轻松地编写出来。

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

(1)硬件平台:基于坚鸿51单片机学习板。用矩阵键盘中的S1键作为启动独立按键,用S5按键模拟左边的开关感应器,用S9按键模拟右边的开关感应器,用S13按键模拟下边的开关感应器。记得把输出线P0.4一直输出低电平,模拟独立按键的触发地GND。

(2)实现功能:
      开机默认机械手在左上方的原点位置。按下启动按键后,机械手从左边开始往右边移动,当机械手移动到最右边时,机械手马上开始往下移动,最后机械手移动到最右下角的位置时,延时1秒,然后原路返回,一直返回到左上角的原点位置。注意:启动按键必须等机械手处于左上角原点位置时,启动按键的触发才有效。

(3)源代码讲解如下:
#include "REG52.H"

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

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


#define const_sensor  20   //开关感应器去抖动延时的时间

#define const_1s  500  //1秒钟大概的定时中断次数

void initial_myself();   
void initial_peripheral();
void delay_short(unsigned int uiDelayShort);
void delay_long(unsigned int uiDelaylong);

void left_to_right();  //从左边移动到右边
void right_to_left(); //从右边返回到左边
void up_to_dowm();   //从上边移动到下边
void down_to_up();    //从下边返回到上边


void run(); //设备自动控制程序
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
void led_update();  //LED更新函数
void T0_time();  //定时中断函数

void key_service(); //按键服务的应用程序
void key_scan(); //按键扫描函数 放在定时中断里
void sensor_scan(); //开关感应器软件抗干扰处理函数,放在定时中断里。

sbit hc595_sh_dr=P2^3;   
sbit hc595_st_dr=P2^4;  
sbit hc595_ds_dr=P2^5;  

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

sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键

sbit left_sr=P0^1; //左边的开关感应器    对应朱兆祺学习板的S5键   
sbit right_sr=P0^2; //右边的开关感应器   有对应朱兆祺学习板的S9键
sbit down_sr=P0^3; //下边的开关感应器    对应朱兆祺学习板的S13键

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

unsigned char ucKeySec=0;   //被触发的按键编号

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


unsigned char ucLeftSr=0;  //左边感应器经过软件抗干扰处理后的状态标志
unsigned char ucRightSr=0;  //右边感应器经过软件抗干扰处理后的状态标志
unsigned char ucDownSr=0;  //下边感应器经过软件抗干扰处理后的状态标志

unsigned int  uiLeftCnt1=0;  //左边感应器软件抗干扰所需的计数器变量
unsigned int  uiLeftCnt2=0;

unsigned int  uiRightCnt1=0;  //右边感应器软件抗干扰所需的计数器变量
unsigned int  uiRightCnt2=0;

unsigned int  uiDownCnt1=0;   //下边软件抗干扰所需的计数器变量
unsigned int  uiDownCnt2=0;

unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器

unsigned char ucLed_dr1=0;   //代表16个灯的亮灭状态,0代表灭,1代表亮
unsigned char ucLed_dr2=0;
unsigned char ucLed_dr3=0;
unsigned char ucLed_dr4=0;
unsigned char ucLed_dr5=0;
unsigned char ucLed_dr6=0;
unsigned char ucLed_dr7=0;
unsigned char ucLed_dr8=0;
unsigned char ucLed_dr9=0;
unsigned char ucLed_dr10=0;
unsigned char ucLed_dr11=0;
unsigned char ucLed_dr12=0;
unsigned char ucLed_dr13=0;
unsigned char ucLed_dr14=0;
unsigned char ucLed_dr15=0;
unsigned char ucLed_dr16=0;

unsigned char ucLed_update=1;  //刷新变量。每次更改LED灯的状态都要更新一次。



unsigned char ucLedStatus16_09=0;   //代表底层74HC595输出状态的中间变量
unsigned char ucLedStatus08_01=0;   //代表底层74HC595输出状态的中间变量



unsigned int  uiRunTimeCnt=0;  //运动中的时间延时计数器变量
unsigned char ucRunStep=0;  //运动控制的步骤变量

void main()
  {
   initial_myself();  
   delay_long(100);   
   initial_peripheral();
   while(1)   
   {
      run(); //设备自动控制程序
      led_update();  //LED更新函数
      key_service(); //按键服务的应用程序
   }

}


/* 注释一:
* 开关感应器的抗干扰处理,本质上类似按键的去抖动处理。唯一的区别是:
* 按键去抖动关注的是IO口的一种状态,而开关感应器关注的是IO口的两种状态。
* 当开关感应器从原来的1状态切换到0状态之前,要进行软件滤波处理过程,一旦成功地
* 切换到0状态了,再想从0状态切换到1状态的时候,又要经过软件滤波处理过程,符合
* 条件后才能切换到1的状态。通俗的话来说,按键的去抖动从1变成0难,从0变成1容易。
* 开关感应器从1变成0难,从0变成1也难。这里所说的"难"是指要经过去抖处理。
*/

void sensor_scan() //开关感应器软件抗干扰处理函数,放在定时中断里。
{
   if(left_sr==1)  //左边感应器是高电平,说明有可能没有被接触    对应朱兆祺学习板的S5键  
   {
       uiLeftCnt1=0; //在软件滤波中,非常关键的语句!!!类似按键去抖动程序的及时清零
       uiLeftCnt2++; //类似独立按键去抖动的软件抗干扰处理
           if(uiLeftCnt2>const_sensor)
           {
              uiLeftCnt2=0;
                  ucLeftSr=1;   //说明感应器确实没有被接触
           }
   }
   else    //左边感应器是低电平,说明有可能被接触到了
   {
       uiLeftCnt2=0; //在软件滤波中,非常关键的语句!!!类似按键去抖动程序的及时清零
       uiLeftCnt1++;
           if(uiLeftCnt1>const_sensor)
           {
              uiLeftCnt1=0;
                  ucLeftSr=0;   //说明感应器确实被接触到了
           }
   }

   if(right_sr==1)  //右边感应器是高电平,说明有可能没有被接触    对应朱兆祺学习板的S9键  
   {
       uiRightCnt1=0; //在软件滤波中,非常关键的语句!!!类似按键去抖动程序的及时清零
       uiRightCnt2++; //类似独立按键去抖动的软件抗干扰处理
           if(uiRightCnt2>const_sensor)
           {
              uiRightCnt2=0;
                  ucRightSr=1;   //说明感应器确实没有被接触
           }
   }
   else    //右边感应器是低电平,说明有可能被接触到了   
   {
       uiRightCnt2=0; //在软件滤波中,非常关键的语句!!!类似按键去抖动程序的及时清零
       uiRightCnt1++;
           if(uiRightCnt1>const_sensor)
           {
              uiRightCnt1=0;
                  ucRightSr=0;   //说明感应器确实被接触到了
           }
   }

   if(down_sr==1)  //下边感应器是高电平,说明有可能没有被接触    对应朱兆祺学习板的S13键  
   {
       uiDownCnt1=0; //在软件滤波中,非常关键的语句!!!类似按键去抖动程序的及时清零
       uiDownCnt2++; //类似独立按键去抖动的软件抗干扰处理
           if(uiDownCnt2>const_sensor)
           {
              uiDownCnt2=0;
                  ucDownSr=1;   //说明感应器确实没有被接触
           }
   }
   else    //下边感应器是低电平,说明有可能被接触到了
   {
       uiDownCnt2=0; //在软件滤波中,非常关键的语句!!!类似按键去抖动程序的及时清零
       uiDownCnt1++;
           if(uiDownCnt1>const_sensor)
           {
              uiDownCnt1=0;
                  ucDownSr=0;   //说明感应器确实被接触到了
           }
   }
}


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

  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号键
     }
  }



}


void key_service() //按键服务的应用程序
{
  switch(ucKeySec) //按键服务状态切换
  {
    case 1:// 启动按键   对应朱兆祺学习板的S1键
         if(ucLeftSr==0)  //处于左上角原点位置
         {
             ucRunStep=1; //启动
             uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。  
         }           
   
         ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
         break;   
     
  }               
}



void led_update()  //LED更新函数
{

   if(ucLed_update==1)
   {
       ucLed_update=0;   //及时清零,让它产生只更新一次的效果,避免一直更新。

       if(ucLed_dr1==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x01;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xfe;
           }

       if(ucLed_dr2==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x02;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xfd;
           }

       if(ucLed_dr3==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x04;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xfb;
           }

       if(ucLed_dr4==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x08;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xf7;
           }


       if(ucLed_dr5==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x10;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xef;
           }


       if(ucLed_dr6==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x20;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xdf;
           }


       if(ucLed_dr7==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x40;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0xbf;
           }


       if(ucLed_dr8==1)
           {
              ucLedStatus08_01=ucLedStatus08_01|0x80;
           }
           else
           {
              ucLedStatus08_01=ucLedStatus08_01&0x7f;
           }

       if(ucLed_dr9==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x01;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xfe;
           }

       if(ucLed_dr10==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x02;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xfd;
           }

       if(ucLed_dr11==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x04;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xfb;
           }

       if(ucLed_dr12==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x08;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xf7;
           }


       if(ucLed_dr13==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x10;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xef;
           }


       if(ucLed_dr14==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x20;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xdf;
           }


       if(ucLed_dr15==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x40;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0xbf;
           }


       if(ucLed_dr16==1)
           {
              ucLedStatus16_09=ucLedStatus16_09|0x80;
           }
           else
           {
              ucLedStatus16_09=ucLedStatus16_09&0x7f;
           }

       hc595_drive(ucLedStatus16_09,ucLedStatus08_01);  //74HC595底层驱动函数

   }
}

void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
{
   unsigned char i;
   unsigned char ucTempData;
   hc595_sh_dr=0;
   hc595_st_dr=0;

   ucTempData=ucLedStatusTemp16_09;  //先送高8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;

         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(15);
         hc595_sh_dr=1;
         delay_short(15);

         ucTempData=ucTempData<<1;
   }

   ucTempData=ucLedStatusTemp08_01;  //再先送低8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;

         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(15);
         hc595_sh_dr=1;
         delay_short(15);

         ucTempData=ucTempData<<1;
   }

   hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
   delay_short(15);
   hc595_st_dr=1;
   delay_short(15);

   hc595_sh_dr=0;    //拉低,抗干扰就增强
   hc595_st_dr=0;
   hc595_ds_dr=0;

}


void left_to_right()  //从左边移动到右边
{
   ucLed_dr1=1;   // 1代表左右气缸从左边移动到右边

   ucLed_update=1;  //刷新变量。每次更改LED灯的状态都要更新一次。
}
void right_to_left() //从右边返回到左边
{
   ucLed_dr1=0;   // 0代表左右气缸从右边返回到左边

   ucLed_update=1;  //刷新变量。每次更改LED灯的状态都要更新一次。
}
void up_to_down()   //从上边移动到下边
{
   ucLed_dr2=1;   // 1代表上下气缸从上边移动到下边

   ucLed_update=1;  //刷新变量。每次更改LED灯的状态都要更新一次。
}
void down_to_up()    //从下边返回到上边
{
   ucLed_dr2=0;   // 0代表上下气缸从下边返回到上边

   ucLed_update=1;  //刷新变量。每次更改LED灯的状态都要更新一次。
}


void run() //设备自动控制程序
{

switch(ucRunStep)
{
       case 0:    //机械手处于左上角原点的位置,待命状态。此时触发启动按键ucRunStep=1,就触发后续一些列的连续动作。

            break;

       case 1:    //机械手从左边往右边移动
            left_to_right();
            ucRunStep=2;  //这就是鸿哥传说中的怎样灵活控制步骤变量
            break;

       case 2:    //等待机械手移动到最右边,直到触发了最右边的开关感应器。
            if(ucRightSr==0)  //右边感应器被触发
            {
               ucRunStep=3;  //这就是鸿哥传说中的怎样灵活控制步骤变量
            }
            break;

       case 3:    //机械手从右上边往右下边移动,从上往下。
            up_to_down();
            ucRunStep=4;  //这就是鸿哥传说中的怎样灵活控制步骤变量
            break;

       case 4:    //等待机械手从右上边移动到右下边,直到触发了右下边的开关感应器。
            if(ucDownSr==0)  //右下边感应器被触发
            {
               uiRunTimeCnt=0;  //时间计数器清零,为接下来延时1秒钟做准备
               ucRunStep=5;  //这就是鸿哥传说中的怎样灵活控制步骤变量
            }
            break;

       case 5:    //机械手在右下边延时1秒
            if(uiRunTimeCnt>const_1s)  //延时1秒
            {
               ucRunStep=6;  //这就是鸿哥传说中的怎样灵活控制步骤变量
            }
            break;
       case 6:    //原路返回,机械手从右下边往右上边移动。
            down_to_up();
            ucRunStep=7;  //这就是鸿哥传说中的怎样灵活控制步骤变量
            break;

       case 7:    //原路返回,等待机械手移动到最右边的感应开关
            if(ucRightSr==0)
            {
               ucRunStep=8;  //这就是鸿哥传说中的怎样灵活控制步骤变量
            }
            break;

       case 8:    //原路返回,等待机械手从右边往左边移动
            right_to_left();
            ucRunStep=9;  //这就是鸿哥传说中的怎样灵活控制步骤变量

            break;

       case 9:    //原路返回,等待机械手移动到最左边的感应开关,表示返回到了原点
            if(ucLeftSr==0) //返回到左上角的原点位置
            {
               ucRunStep=0;  //这就是鸿哥传说中的怎样灵活控制步骤变量
            }
            break;
   }
}


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


  sensor_scan(); //开关感应器软件抗干扰处理函数
  key_scan(); //按键扫描函数

  if(uiRunTimeCnt<0xffff) //不要超过最大int类型范围
  {
     uiRunTimeCnt++; //延时计数器
  }
  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 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++)  //内嵌循环的空指令数量
          {
             ; //一个分号相当于执行一条空语句
          }
   }
}


void initial_myself()  //第一区 初始化单片机
{
/* 注释二:
* 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
* 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
* 坚鸿51学习板的S1就是本程序中用到的一个独立按键。
*/
  key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平

  beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

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


  TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
  TL0=0x2f;


}

void initial_peripheral() //第二区 初始化外围
{
  EA=1;     //开总中断
  ET0=1;    //允许定时中断
  TR0=1;    //启动定时中断

}

总结陈词:
前面花了很多节内容在讲按键和跑马灯的关系,但是一直没涉及到人机界面,在大多数的实际项目中,人机界面是必不可少的。人机界面的程序框架该怎么样写?欲知详情,请听下回分解-----在主函数while循环中驱动数码管的动态扫描程序。

(未完待续,下节更精彩,不要走开哦)
作者: jianhong_wu    时间: 2014-2-17 02:56
第二十六节:在主函数while循环中驱动数码管的动态扫描程序。

开场白:
上一节通过一个机械手自动控制程序展示了我在工控常用的编程框架,但是一直没涉及到人机界面,在大多数的实际项目中,人机界面是必不可少的,这一节开始讲最常用的人机界面------动态数码管的驱动。

这一节要教会大家两个知识点:
第一点:数码管的动态驱动原理。
第二点:如何通过编程,让数码管显示的内容转移到几个变量接口上,方便以后编写更上一层的窗口程序。

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

(1)硬件平台:基于坚鸿51单片机学习板。用两片74HC595动态驱动八位共阴数码管。

(2)实现功能:
      开机后显示  8765.4321  的内容,注意,其中有一个小数点。
(3)源代码讲解如下:
#include "REG52.H"


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(); //显示数码管字模的驱动函数

//驱动LED的74HC595
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);


sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
sbit led_dr=P3^5;  //作为中途暂停指示灯 亮的时候表示中途暂停


sbit dig_hc595_sh_dr=P2^0;     //数码管的74HC595程序
sbit dig_hc595_st_dr=P2^1;  
sbit dig_hc595_ds_dr=P2^2;  

sbit hc595_sh_dr=P2^3;    //LED灯的74HC595程序
sbit hc595_st_dr=P2^4;  
sbit hc595_ds_dr=P2^5;  


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 ucDisplayUpdate=1; //更新显示标志

//根据原理图得出的共阴数码管字模表
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
};

void main()
  {
   initial_myself();  
   delay_long(100);   
   initial_peripheral();
   while(1)  
   {
      display_drive();  //显示数码管字模的驱动函数
   }

}

/* 注释一:
* 动态驱动数码管的原理是,在八位数码管中,在任何一个瞬间,每次只显示其中一位数码管,另外的七个数码管
* 通过设置其公共位com为高电平来关闭显示,只要切换画面的速度足够快,人的视觉就分辨不出来,感觉八个数码管
* 是同时亮的。以下dig_hc595_drive(xx,yy)函数,其中第一个形参xx是驱动数码管段seg的引脚,第二个形参yy是驱动
* 数码管公共位com的引脚。
*/

void display_drive()  
{
   //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
   switch(ucDisplayDriveStep)
   {
      case 1:  //显示第1位
           ucDigShowTemp=dig_table[ucDigShow1];
                   if(ucDigDot1==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xfe);
               break;
      case 2:  //显示第2位
           ucDigShowTemp=dig_table[ucDigShow2];
                   if(ucDigDot2==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xfd);
               break;
      case 3:  //显示第3位
           ucDigShowTemp=dig_table[ucDigShow3];
                   if(ucDigDot3==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xfb);
               break;
      case 4:  //显示第4位
           ucDigShowTemp=dig_table[ucDigShow4];
                   if(ucDigDot4==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xf7);
               break;
      case 5:  //显示第5位
           ucDigShowTemp=dig_table[ucDigShow5];
                   if(ucDigDot5==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xef);
               break;
      case 6:  //显示第6位
           ucDigShowTemp=dig_table[ucDigShow6];
                   if(ucDigDot6==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xdf);
               break;
      case 7:  //显示第7位
           ucDigShowTemp=dig_table[ucDigShow7];
                   if(ucDigDot7==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
           }
           dig_hc595_drive(ucDigShowTemp,0xbf);
               break;
      case 8:  //显示第8位
           ucDigShowTemp=dig_table[ucDigShow8];
                   if(ucDigDot8==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0x7f);
               break;
   }

   ucDisplayDriveStep++;
   if(ucDisplayDriveStep>8)  //扫描完8个数码管后,重新从第一个开始扫描
   {
     ucDisplayDriveStep=1;
   }

/* 注释二:
* 如果直接是单片机的IO口引脚驱动的数码管,由于驱动的速度太快,此处应该适当增加一点delay延时或者
* 用计数延时的方式来延时,目的是在八位数码管中切换到每位数码管显示的时候,都能停留一会再切换到其它
* 位的数码管界面,这样可以增加显示的效果。但是,由于坚鸿51学习板是间接经过74HC595驱动数码管的,
* 在单片机驱动74HC595的时候,dig_hc595_drive函数本身内部需要执行很多指令,已经相当于delay延时了,
* 因此这里不再需要加delay延时函数或者计数延时。
*/


}


//数码管的74HC595驱动函数
void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
{
   unsigned char i;
   unsigned char ucTempData;
   dig_hc595_sh_dr=0;
   dig_hc595_st_dr=0;

   ucTempData=ucDigStatusTemp16_09;  //先送高8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)dig_hc595_ds_dr=1;
         else dig_hc595_ds_dr=0;

/* 注释三:
*  注意,此处的延时delay_short必须尽可能小,否则动态扫描数码管的速度就不够。
*/
         dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         dig_hc595_sh_dr=1;
         delay_short(1);

         ucTempData=ucTempData<<1;
   }

   ucTempData=ucDigStatusTemp08_01;  //再先送低8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)dig_hc595_ds_dr=1;
         else dig_hc595_ds_dr=0;

         dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         dig_hc595_sh_dr=1;
         delay_short(1);

         ucTempData=ucTempData<<1;
   }

   dig_hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
   delay_short(1);
   dig_hc595_st_dr=1;
   delay_short(1);

   dig_hc595_sh_dr=0;    //拉低,抗干扰就增强
   dig_hc595_st_dr=0;
   dig_hc595_ds_dr=0;

}


//LED灯的74HC595驱动函数
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
{
   unsigned char i;
   unsigned char ucTempData;
   hc595_sh_dr=0;
   hc595_st_dr=0;

   ucTempData=ucLedStatusTemp16_09;  //先送高8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;

         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         hc595_sh_dr=1;
         delay_short(1);

         ucTempData=ucTempData<<1;
   }

   ucTempData=ucLedStatusTemp08_01;  //再先送低8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;

         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         hc595_sh_dr=1;
         delay_short(1);

         ucTempData=ucTempData<<1;
   }

   hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
   delay_short(1);
   hc595_st_dr=1;
   delay_short(1);

   hc595_sh_dr=0;    //拉低,抗干扰就增强
   hc595_st_dr=0;
   hc595_ds_dr=0;

}





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


void initial_myself()  //第一区 初始化单片机
{

  led_dr=0;  //关闭独立LED灯
  beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

  hc595_drive(0x00,0x00);  //关闭所有经过另外两个74HC595驱动的LED灯

}
void initial_peripheral() //第二区 初始化外围
{
/* 注释四:
* 让数码管显示的内容转移到以下几个变量接口上,方便以后编写更上一层的窗口程序。
* 只要更改以下对应变量的内容,就可以显示你想显示的数字。初学者应该仔细看看display_drive等函数,
* 了解来龙去脉,就可以知道本驱动程序的框架原理了。
*/
   ucDigShow8=8;  //第8位数码管要显示的内容
   ucDigShow7=7;  //第7位数码管要显示的内容
   ucDigShow6=6;  //第6位数码管要显示的内容
   ucDigShow5=5;  //第5位数码管要显示的内容
   ucDigShow4=4;  //第4位数码管要显示的内容
   ucDigShow3=3;  //第3位数码管要显示的内容
   ucDigShow2=2;  //第2位数码管要显示的内容
   ucDigShow1=1;  //第1位数码管要显示的内容


   ucDigDot8=0;  
   ucDigDot7=0;  
   ucDigDot6=0;
   ucDigDot5=1;  //显示第5位的小数点
   ucDigDot4=0;
   ucDigDot3=0;  
   ucDigDot2=0;
   ucDigDot1=0;
}

总结陈词:
把本程序下载到坚鸿51学习板上,发现显示的效果还是挺不错的。但是,本程序也有一个弱点,在一些项目中 ,主函数循环中的任务越多,就意味着在某一瞬间,每显示一位数码管停留的时间就会越久,一旦超过某个值,会严重影响显示的效果,有没有办法改善它?当然有。欲知详情,请听下回分解-----在定时中断里动态扫描数码管的程序。

(未完待续,下节更精彩,不要走开哦)
作者: jianhong_wu    时间: 2014-2-17 02:57
本帖最后由 jianhong_wu 于 2014-2-22 22:48 编辑

第二十七节:在定时中断里动态扫描数码管的程序。

开场白:
上一节讲了在主函数循环中动态扫描数码管的程序,但是该程序有一个隐患,在一些项目中 ,主函数循环中的任务越多,就意味着在某一瞬间,每显示一位数码管停留的时间就会越久,一旦超过某个值,会严重影响显示的效果。这一节要教会大家两个知识点:
第一个:如何把动态扫描数码管的程序放在定时中断里,彻底解决上节的显示隐患。
第二个:在定时中断里的重装初始值不能太大,否则动态扫描数码管的速度就不够。我把原来常用的初始值2000改成了500。

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

(1)硬件平台:基于坚鸿51单片机学习板。用两片74HC595动态驱动八位共阴数码管。

(2)实现功能:
      开机后显示  8765.4321  的内容,注意,其中有一个小数点。
(3)源代码讲解如下:
#include "REG52.H"


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(); //显示数码管字模的驱动函数

//驱动LED的74HC595
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);

void T0_time();  //定时中断函数

sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
sbit led_dr=P3^5;  //作为中途暂停指示灯 亮的时候表示中途暂停


sbit dig_hc595_sh_dr=P2^0;     //数码管的74HC595程序
sbit dig_hc595_st_dr=P2^1;  
sbit dig_hc595_ds_dr=P2^2;  

sbit hc595_sh_dr=P2^3;    //LED灯的74HC595程序
sbit hc595_st_dr=P2^4;  
sbit hc595_ds_dr=P2^5;  


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 ucDisplayUpdate=1; //更新显示标志

//根据原理图得出的共阴数码管字模表
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
};

void main()
  {
   initial_myself();  
   delay_long(100);   
   initial_peripheral();
   while(1)  
   {
      ;
   }

}

/* 注释一:
* 动态驱动数码管的原理是,在八位数码管中,在任何一个瞬间,每次只显示其中一位数码管,另外的七个数码管
* 通过设置其公共位com为高电平来关闭显示,只要切换画面的速度足够快,人的视觉就分辨不出来,感觉八个数码管
* 是同时亮的。以下dig_hc595_drive(xx,yy)函数,其中第一个形参xx是驱动数码管段seg的引脚,第二个形参yy是驱动
* 数码管公共位com的引脚。
*/

void display_drive()  
{
   //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
   switch(ucDisplayDriveStep)
   {
      case 1:  //显示第1位
           ucDigShowTemp=dig_table[ucDigShow1];
                   if(ucDigDot1==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xfe);
               break;
      case 2:  //显示第2位
           ucDigShowTemp=dig_table[ucDigShow2];
                   if(ucDigDot2==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xfd);
               break;
      case 3:  //显示第3位
           ucDigShowTemp=dig_table[ucDigShow3];
                   if(ucDigDot3==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xfb);
               break;
      case 4:  //显示第4位
           ucDigShowTemp=dig_table[ucDigShow4];
                   if(ucDigDot4==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xf7);
               break;
      case 5:  //显示第5位
           ucDigShowTemp=dig_table[ucDigShow5];
                   if(ucDigDot5==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xef);
               break;
      case 6:  //显示第6位
           ucDigShowTemp=dig_table[ucDigShow6];
                   if(ucDigDot6==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xdf);
               break;
      case 7:  //显示第7位
           ucDigShowTemp=dig_table[ucDigShow7];
                   if(ucDigDot7==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
           }
           dig_hc595_drive(ucDigShowTemp,0xbf);
               break;
      case 8:  //显示第8位
           ucDigShowTemp=dig_table[ucDigShow8];
                   if(ucDigDot8==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0x7f);
               break;
   }

   ucDisplayDriveStep++;
   if(ucDisplayDriveStep>8)  //扫描完8个数码管后,重新从第一个开始扫描
   {
     ucDisplayDriveStep=1;
   }

/* 注释二:
* 如果直接是单片机的IO口引脚驱动的数码管,由于驱动的速度太快,此处应该适当增加一点delay延时或者
* 用计数延时的方式来延时,目的是在八位数码管中切换到每位数码管显示的时候,都能停留一会再切换到其它
* 位的数码管界面,这样可以增加显示的效果。但是,由于坚鸿51学习板是间接经过74HC595驱动数码管的,
* 在单片机驱动74HC595的时候,dig_hc595_drive函数本身内部需要执行很多指令,已经相当于delay延时了,
* 因此这里不再需要加delay延时函数或者计数延时。
*/


}


//数码管的74HC595驱动函数
void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
{
   unsigned char i;
   unsigned char ucTempData;
   dig_hc595_sh_dr=0;
   dig_hc595_st_dr=0;

   ucTempData=ucDigStatusTemp16_09;  //先送高8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)dig_hc595_ds_dr=1;
         else dig_hc595_ds_dr=0;

/* 注释三:
*  注意,此处的延时delay_short必须尽可能小,否则动态扫描数码管的速度就不够。
*/
         dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         dig_hc595_sh_dr=1;
         delay_short(1);

         ucTempData=ucTempData<<1;
   }

   ucTempData=ucDigStatusTemp08_01;  //再先送低8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)dig_hc595_ds_dr=1;
         else dig_hc595_ds_dr=0;

         dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         dig_hc595_sh_dr=1;
         delay_short(1);

         ucTempData=ucTempData<<1;
   }

   dig_hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
   delay_short(1);
   dig_hc595_st_dr=1;
   delay_short(1);

   dig_hc595_sh_dr=0;    //拉低,抗干扰就增强
   dig_hc595_st_dr=0;
   dig_hc595_ds_dr=0;

}


//LED灯的74HC595驱动函数
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
{
   unsigned char i;
   unsigned char ucTempData;
   hc595_sh_dr=0;
   hc595_st_dr=0;

   ucTempData=ucLedStatusTemp16_09;  //先送高8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;

         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         hc595_sh_dr=1;
         delay_short(1);

         ucTempData=ucTempData<<1;
   }

   ucTempData=ucLedStatusTemp08_01;  //再先送低8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;

         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         hc595_sh_dr=1;
         delay_short(1);

         ucTempData=ucTempData<<1;
   }

   hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
   delay_short(1);
   hc595_st_dr=1;
   delay_short(1);

   hc595_sh_dr=0;    //拉低,抗干扰就增强
   hc595_st_dr=0;
   hc595_ds_dr=0;

}


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

  display_drive();  //数码管字模的驱动函数

/* 注释四:
*  注意,此处的重装初始值不能太大,否则动态扫描数码管的速度就不够。我把原来常用的2000改成了500。
*/
  TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  TL0=0x0b;
  TR0=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++)  //内嵌循环的空指令数量
          {
             ; //一个分号相当于执行一条空语句
          }
   }
}


void initial_myself()  //第一区 初始化单片机
{

  led_dr=0;  //关闭独立LED灯
  beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

  hc595_drive(0x00,0x00);  //关闭所有经过另外两个74HC595驱动的LED灯

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

  TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  TL0=0x0b;

}
void initial_peripheral() //第二区 初始化外围
{
/* 注释五:
* 让数码管显示的内容转移到以下几个变量接口上,方便以后编写更上一层的窗口程序。
* 只要更改以下对应变量的内容,就可以显示你想显示的数字。初学者应该仔细看看display_drive等函数,
* 了解来龙去脉,就可以知道本驱动程序的框架原理了。
*/
   ucDigShow8=8;  //第8位数码管要显示的内容
   ucDigShow7=7;  //第7位数码管要显示的内容
   ucDigShow6=6;  //第6位数码管要显示的内容
   ucDigShow5=5;  //第5位数码管要显示的内容
   ucDigShow4=4;  //第4位数码管要显示的内容
   ucDigShow3=3;  //第3位数码管要显示的内容
   ucDigShow2=2;  //第2位数码管要显示的内容
   ucDigShow1=1;  //第1位数码管要显示的内容


   ucDigDot8=0;  
   ucDigDot7=0;  
   ucDigDot6=0;
   ucDigDot5=1;  //显示第5位的小数点
   ucDigDot4=0;
   ucDigDot3=0;  
   ucDigDot2=0;
   ucDigDot1=0;

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

}

总结陈词:
     有的朋友会质疑,很多教科书上说,定时中断函数里面的内容应该越少越好,你把动态驱动数码管的函数放在中断里面,难道不会影响其它任务的执行吗?我的回答是,大部分的小项目都不会影响,只有少数实时性要求非常高的项目会影响,而对于这类项目,我的做法是从一开始设计硬件电路板的时候,就应该放弃用动态扫描数码管的方案,而是应该选数码管专用驱动芯片来实现静态驱动。因为动态扫描数码管本来就不适合应用在实时性非常高的项目。
      前面这两节都讲了数码管的驱动程序,要在此基础上,做一些项目中经常遇到的界面应用,我们该怎么写程序?欲知详情,请听下回分解----数码管通过切换窗口来设置参数

(未完待续,下节更精彩,不要走开哦)
作者: jianhong_wu    时间: 2014-2-23 01:28
本帖最后由 jianhong_wu 于 2014-2-28 14:42 编辑

第二十八节:数码管通过切换窗口来设置参数。

开场白:
上一节讲了数码管的驱动程序,这节在上节的基础上,通过按键切换不同的窗口来设置不同的参数。
这一节要教会大家三个知识点:
第一个:鸿哥首次提出的“一二级菜单显示理论”:凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。
第二个:如何通过一个窗口变量来把按键,数码管,被设置的参数关联起来。
第三个:需要特别注意,在显示被设置参数时,应该先分解出每一位,然后再把分解出来的数据过渡到显示缓冲变量里。

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

1)硬件平台:基于朱兆祺51单片机学习板。加按键对应S1键,减按键对应S5键,切换窗口按键对应S9

2)实现功能:
     通过按键设置4个不同的参数。
      一共有4个窗口。每个窗口显示一个参数。
8,7,6,5位数码管显示当前窗口,P-1代表第1个窗口,P-2代表第2个窗口,P-3代表第3个窗口,P-4代表第1个窗口。
4,3,2,1位数码管显示当前窗口被设置的参数。范围是从09999
有三个按键。一个是加按键,按下此按键会依次增加当前窗口的参数。一个是减按键,按下此按键会依次减少当前窗口的参数。一个是切换窗口按键,按下此按键会依次循环切换不同的窗口。

3)源代码讲解如下:

#include "REG52.H"

#define const_voice_short  40   //蜂鸣器短叫的持续时间
#define const_key_time1  20    //按键去抖动延时的时间
#define const_key_time2  20    //按键去抖动延时的时间
#define const_key_time3  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 key_service(); //按键服务的应用程序
void key_scan();//按键扫描函数 放在定时中断里

sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键
sbit key_sr3=P0^2; //对应朱兆祺学习板的S9键
sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平
sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
sbit led_dr=P3^5;  //作为中途暂停指示灯 亮的时候表示中途暂停

sbit dig_hc595_sh_dr=P2^0;     //数码管的74HC595程序
sbit dig_hc595_st_dr=P2^1;  
sbit dig_hc595_ds_dr=P2^2;  
sbit hc595_sh_dr=P2^3;    //LED灯的74HC595程序
sbit hc595_st_dr=P2^4;  
sbit hc595_ds_dr=P2^5;  
unsigned char ucKeySec=0;   //被触发的按键编号
unsigned int  uiKeyTimeCnt1=0; //按键去抖动延时计数器
unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志
unsigned int  uiKeyTimeCnt2=0; //按键去抖动延时计数器
unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志
unsigned int  uiKeyTimeCnt3=0; //按键去抖动延时计数器
unsigned char ucKeyLock3=0; //按键触发后自锁的变量标志
unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器

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 ucWd2Update=0; //窗口2更新显示标志
unsigned char ucWd3Update=0; //窗口3更新显示标志
unsigned char ucWd4Update=0; //窗口4更新显示标志
unsigned char ucWd=1;  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
unsigned int  uiSetData1=0;  //本程序中需要被设置的参数1
unsigned int  uiSetData2=0;  //本程序中需要被设置的参数2
unsigned int  uiSetData3=0;  //本程序中需要被设置的参数3
unsigned int  uiSetData4=0;  //本程序中需要被设置的参数4

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)  
   {
     key_service(); //按键服务的应用程序
         display_service(); //显示的窗口菜单服务程序
   }
}
/* 注释一:
*鸿哥首次提出的"一二级菜单显示理论":
*凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,
*每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。
*局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,
*表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。
*/

void display_service() //显示的窗口菜单服务程序
{

   switch(ucWd)  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
   {
       case 1:   //显示P--1窗口的数据
            if(ucWd1Update==1)  //窗口1要全部更新显示
   {
               ucWd1Update=0;  //及时清零标志,避免一直进来扫描
               ucDigShow8=12;  //第8位数码管显示P
               ucDigShow7=11;  //第7位数码管显示-
               ucDigShow6=1;   //第6位数码管显示1
               ucDigShow5=10;  //第5位数码管显示无
/* 注释二:
* 此处为什么要多加4个中间过渡变量ucTemp?是因为uiSetData1分解数据的时候
* 需要进行除法和求余数的运算,就会用到好多条指令,就会耗掉一点时间,类似延时
* 了一会。我们的定时器每隔一段时间都会产生中断,然后在中断里驱动数码管显示,
* 当uiSetData1还没完全分解出4位有效数据时,这个时候来的定时中断,就有可能导致
* 显示的数据瞬间产生不完整,影响显示效果。因此,为了把需要显示的数据过渡最快,
* 所以采取了先分解,再过渡显示的方法。
*/
              //先分解数据
                       ucTemp4=uiSetData1/1000;     
                       ucTemp3=uiSetData1%1000/100;
                       ucTemp2=uiSetData1%100/10;
                       ucTemp1=uiSetData1%10;
  
                          //再过渡需要显示的数据到缓冲变量里,让过渡的时间越短越好
               ucDigShow4=ucTemp4;  //第4位数码管要显示的内容
               ucDigShow3=ucTemp3;  //第3位数码管要显示的内容
               ucDigShow2=ucTemp2;  //第2位数码管要显示的内容
               ucDigShow1=ucTemp1;  //第1位数码管要显示的内容
            }
            break;
        case 2:  //显示P--2窗口的数据
            if(ucWd2Update==1)  //窗口2要全部更新显示
   {
               ucWd2Update=0;  //及时清零标志,避免一直进来扫描
               ucDigShow8=12;  //第8位数码管显示P
               ucDigShow7=11;  //第7位数码管显示-
               ucDigShow6=2;  //第6位数码管显示2
               ucDigShow5=10;   //第5位数码管显示无
                       ucTemp4=uiSetData2/1000;     //分解数据
                       ucTemp3=uiSetData2%1000/100;
                       ucTemp2=uiSetData2%100/10;
                       ucTemp1=uiSetData2%10;
               ucDigShow4=ucTemp4;  //第4位数码管要显示的内容
               ucDigShow3=ucTemp3;  //第3位数码管要显示的内容
               ucDigShow2=ucTemp2;  //第2位数码管要显示的内容
               ucDigShow1=ucTemp1;  //第1位数码管要显示的内容
    }
             break;
        case 3:  //显示P--3窗口的数据
            if(ucWd3Update==1)  //窗口3要全部更新显示
   {
               ucWd3Update=0;  //及时清零标志,避免一直进来扫描
               ucDigShow8=12;  //第8位数码管显示P
               ucDigShow7=11;  //第7位数码管显示-
               ucDigShow6=3;  //第6位数码管显示3
               ucDigShow5=10;   //第5位数码管显示无
                       ucTemp4=uiSetData3/1000;     //分解数据
                       ucTemp3=uiSetData3%1000/100;
                       ucTemp2=uiSetData3%100/10;
                       ucTemp1=uiSetData3%10;
               ucDigShow4=ucTemp4;  //第4位数码管要显示的内容
               ucDigShow3=ucTemp3;  //第3位数码管要显示的内容
               ucDigShow2=ucTemp2;  //第2位数码管要显示的内容
               ucDigShow1=ucTemp1;  //第1位数码管要显示的内容
   }
            break;
        case 4:  //显示P--4窗口的数据
            if(ucWd4Update==1)  //窗口4要全部更新显示
   {
               ucWd4Update=0;  //及时清零标志,避免一直进来扫描
               ucDigShow8=12;  //第8位数码管显示P
               ucDigShow7=11;  //第7位数码管显示-
               ucDigShow6=4;  //第6位数码管显示4
               ucDigShow5=10;   //第5位数码管显示无
                       ucTemp4=uiSetData4/1000;     //分解数据
                       ucTemp3=uiSetData4%1000/100;
                       ucTemp2=uiSetData4%100/10;
                       ucTemp1=uiSetData4%10;
               ucDigShow4=ucTemp4;  //第4位数码管要显示的内容
               ucDigShow3=ucTemp3;  //第3位数码管要显示的内容
               ucDigShow2=ucTemp2;  //第2位数码管要显示的内容
               ucDigShow1=ucTemp1;  //第1位数码管要显示的内容
    }
             break;
           }
   

}

void key_scan()//按键扫描函数 放在定时中断里
{  
  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号键
     }
  }

}

void key_service() //按键服务的应用程序
{
  switch(ucKeySec) //按键服务状态切换
  {
    case 1:// 加按键 对应朱兆祺学习板的S1键
          switch(ucWd)  //在不同的窗口下,设置不同的参数
                  {
                     case 1:
                  uiSetData1++;   
                                  if(uiSetData1>9999) //最大值是9999
                                  {
                                     uiSetData1=9999;
                                  }
                           ucWd1Update=1;  //窗口1更新显示
                              break;
                     case 2:
                  uiSetData2++;
                                  if(uiSetData2>9999) //最大值是9999
                                  {
                                     uiSetData2=9999;
                                  }
                           ucWd2Update=1;  //窗口2更新显示
                              break;
                     case 3:
                  uiSetData3++;
                                  if(uiSetData3>9999) //最大值是9999
                                  {
                                     uiSetData3=9999;
                                  }
                           ucWd3Update=1;  //窗口3更新显示
                              break;
                     case 4:
                  uiSetData4++;
                                  if(uiSetData4>9999) //最大值是9999
                                  {
                                     uiSetData4=9999;
                                  }
                           ucWd4Update=1;  //窗口4更新显示
                              break;
                  }
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
   
    case 2:// 减按键 对应朱兆祺学习板的S5键
          switch(ucWd)  //在不同的窗口下,设置不同的参数
                  {
                     case 1:
                  uiSetData1--;   
/* 注释三:
* 单片机C编译有一个特点,当一个无符号类型的数据0减去1时,就会溢出反而变成这个类型数据的最大值
* 对于int类型的数据,最大值肯定比9999大,因此下面的临界点用if(uiSetData1>9999)来判断。
*/
                                  if(uiSetData1>9999)  
                                  {
                                     uiSetData1=0;  //最小值是0
                                  }
                           ucWd1Update=1;  //窗口1更新显示
                              break;
                     case 2:
                  uiSetData2--;
                                  if(uiSetData2>9999)
                                  {
                                     uiSetData2=0;  //最小值是0
                                  }
                           ucWd2Update=1;  //窗口2更新显示
                              break;
                     case 3:
                  uiSetData3--;
                                  if(uiSetData3>9999)
                                  {
                                     uiSetData3=0;  //最小值是0
                                  }
                           ucWd3Update=1;  //窗口3更新显示
                              break;
                     case 4:
                  uiSetData4--;
                                  if(uiSetData4>9999)
                                  {
                                     uiSetData4=0;  //最小值是0
                                  }
                           ucWd4Update=1;  //窗口4更新显示
                              break;
                  }

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;  
    case 3:// 切换窗口按键 对应朱兆祺学习板的S9键
          ucWd++;  //切换窗口
                  if(ucWd>4)
                  {
                    ucWd=1;
                  }
          switch(ucWd)  //在不同的窗口下,在不同的窗口下,更新显示不同的窗口
                  {
                     case 1:
                           ucWd1Update=1;  //窗口1更新显示
                              break;
                     case 2:
                           ucWd2Update=1;  //窗口2更新显示
                              break;
                     case 3:
                           ucWd3Update=1;  //窗口3更新显示
                              break;
                     case 4:
                           ucWd4Update=1;  //窗口4更新显示
                              break;
                  }
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;         
         
  }               
}

void display_drive()  
{
   //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
   switch(ucDisplayDriveStep)
   {
      case 1:  //显示第1位
           ucDigShowTemp=dig_table[ucDigShow1];
                   if(ucDigDot1==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xfe);
               break;
      case 2:  //显示第2位
           ucDigShowTemp=dig_table[ucDigShow2];
                   if(ucDigDot2==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xfd);
               break;
      case 3:  //显示第3位
           ucDigShowTemp=dig_table[ucDigShow3];
                   if(ucDigDot3==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xfb);
               break;
      case 4:  //显示第4位
           ucDigShowTemp=dig_table[ucDigShow4];
                   if(ucDigDot4==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xf7);
               break;
      case 5:  //显示第5位
           ucDigShowTemp=dig_table[ucDigShow5];
                   if(ucDigDot5==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xef);
               break;
      case 6:  //显示第6位
           ucDigShowTemp=dig_table[ucDigShow6];
                   if(ucDigDot6==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xdf);
               break;
      case 7:  //显示第7位
           ucDigShowTemp=dig_table[ucDigShow7];
                   if(ucDigDot7==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
           }
           dig_hc595_drive(ucDigShowTemp,0xbf);
               break;
      case 8:  //显示第8位
           ucDigShowTemp=dig_table[ucDigShow8];
                   if(ucDigDot8==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0x7f);
               break;
   }
   ucDisplayDriveStep++;
   if(ucDisplayDriveStep>8)  //扫描完8个数码管后,重新从第一个开始扫描
   {
     ucDisplayDriveStep=1;
   }

}

//数码管的74HC595驱动函数
void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
{
   unsigned char i;
   unsigned char ucTempData;
   dig_hc595_sh_dr=0;
   dig_hc595_st_dr=0;
   ucTempData=ucDigStatusTemp16_09;  //先送高8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)dig_hc595_ds_dr=1;
         else dig_hc595_ds_dr=0;
         dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         dig_hc595_sh_dr=1;
         delay_short(1);
         ucTempData=ucTempData<<1;
   }
   ucTempData=ucDigStatusTemp08_01;  //再先送低8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)dig_hc595_ds_dr=1;
         else dig_hc595_ds_dr=0;
         dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         dig_hc595_sh_dr=1;
         delay_short(1);
         ucTempData=ucTempData<<1;
   }
   dig_hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
   delay_short(1);
   dig_hc595_st_dr=1;
   delay_short(1);
   dig_hc595_sh_dr=0;    //拉低,抗干扰就增强
   dig_hc595_st_dr=0;
   dig_hc595_ds_dr=0;
}

//LED灯的74HC595驱动函数
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
{
   unsigned char i;
   unsigned char ucTempData;
   hc595_sh_dr=0;
   hc595_st_dr=0;
   ucTempData=ucLedStatusTemp16_09;  //先送高8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;
         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         hc595_sh_dr=1;
         delay_short(1);
         ucTempData=ucTempData<<1;
   }
   ucTempData=ucLedStatusTemp08_01;  //再先送低8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;
         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         hc595_sh_dr=1;
         delay_short(1);
         ucTempData=ucTempData<<1;
   }
   hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
   delay_short(1);
   hc595_st_dr=1;
   delay_short(1);
   hc595_sh_dr=0;    //拉低,抗干扰就增强
   hc595_st_dr=0;
   hc595_ds_dr=0;
}

void T0_time() interrupt 1
{
  TF0=0;  //清除中断标志
  TR0=0; //关中断
  key_scan(); //按键扫描函数
  if(uiVoiceCnt!=0)
  {
     uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
     beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
//     beep_dr=1;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  }
  else
  {
     ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
     beep_dr=1;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
//     beep_dr=0;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  }
  display_drive();  //数码管字模的驱动函数

  TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  TL0=0x0b;
  TR0=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++)  //内嵌循环的空指令数量
          {
             ; //一个分号相当于执行一条空语句
          }
   }
}

void initial_myself()  //第一区 初始化单片机
{
/* 注释四:
* 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
* 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
* 坚鸿51学习板的S1就是本程序中用到的一个独立按键。
*/
  key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平
  led_dr=0;  //关闭独立LED灯
  beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
  hc595_drive(0x00,0x00);  //关闭所有经过另外两个74HC595驱动的LED灯
  TMOD=0x01;  //设置定时器0为工作方式1
  TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  TL0=0x0b;
}
void initial_peripheral() //第二区 初始化外围
{

   ucDigDot8=0;   //小数点全部不显示
   ucDigDot7=0;  
   ucDigDot6=0;
   ucDigDot5=0;  
   ucDigDot4=0;
   ucDigDot3=0;  
   ucDigDot2=0;
   ucDigDot1=0;
   EA=1;     //开总中断
   ET0=1;    //允许定时中断
   TR0=1;    //启动定时中断
}


总结陈词:
    这节在第4,3,2,1位显示设置的参数时,还有一点小瑕疵。比如设置参数等于56时,实际显示的是“0056”,也就是高位为0的如果不显示,效果才会更好。我们要把高位为0的去掉不显示,该怎么改程序呢?欲知详情,请听下回分解-----数码管通过切换窗口来设置参数,并且不显示为0的高位。

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


作者: jianhong_wu    时间: 2014-2-23 01:30
本帖最后由 jianhong_wu 于 2014-2-28 14:41 编辑

第二十九节:数码管通过切换窗口来设置参数,并且不显示为0的高位。

开场白:
上一节在第4,3,2,1位显示设置的参数时,还有一点小瑕疵。比如设置参数等于56时,实际显示的是“0056”,也就是高位为0的如果不显示,效果才会更好。
这一节要教会大家两个知识点:
第一个:在上一节display_service()函数里略作修改,把高位为0的去掉不显示。
第二个:加深熟悉鸿哥首次提出的“一二级菜单显示理论”:凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。


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

1)硬件平台:基于朱兆祺51单片机学习板。加按键对应S1键,减按键对应S5键,切换窗口按键对应S9

2)实现功能:
     通过按键设置4个不同的参数。
      一共有4个窗口。每个窗口显示一个参数。
8,7,6,5位数码管显示当前窗口,P-1代表第1个窗口,P-2代表第2个窗口,P-3代表第3个窗口,P-4代表第1个窗口。
4,3,2,1位数码管显示当前窗口被设置的参数。范围是从09999
有三个按键。一个是加按键,按下此按键会依次增加当前窗口的参数。一个是减按键,按下此按键会依次减少当前窗口的参数。一个是切换窗口按键,按下此按键会依次循环切换不同的窗口。
并且要求被设置的数据不显示为0的高位。比如参数是12时,不能显示“0012”,只能第4,3位不显示,第2,1位显示“12”

3)源代码讲解如下:

#include "REG52.H"

#define const_voice_short  40   //蜂鸣器短叫的持续时间
#define const_key_time1  20    //按键去抖动延时的时间
#define const_key_time2  20    //按键去抖动延时的时间
#define const_key_time3  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 key_service(); //按键服务的应用程序
void key_scan();//按键扫描函数 放在定时中断里

sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键
sbit key_sr3=P0^2; //对应朱兆祺学习板的S9键
sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平
sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
sbit led_dr=P3^5;  //作为中途暂停指示灯 亮的时候表示中途暂停

sbit dig_hc595_sh_dr=P2^0;     //数码管的74HC595程序
sbit dig_hc595_st_dr=P2^1;  
sbit dig_hc595_ds_dr=P2^2;  
sbit hc595_sh_dr=P2^3;    //LED灯的74HC595程序
sbit hc595_st_dr=P2^4;  
sbit hc595_ds_dr=P2^5;  
unsigned char ucKeySec=0;   //被触发的按键编号
unsigned int  uiKeyTimeCnt1=0; //按键去抖动延时计数器
unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志
unsigned int  uiKeyTimeCnt2=0; //按键去抖动延时计数器
unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志
unsigned int  uiKeyTimeCnt3=0; //按键去抖动延时计数器
unsigned char ucKeyLock3=0; //按键触发后自锁的变量标志
unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器

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 ucWd2Update=0; //窗口2更新显示标志
unsigned char ucWd3Update=0; //窗口3更新显示标志
unsigned char ucWd4Update=0; //窗口4更新显示标志
unsigned char ucWd=1;  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
unsigned int  uiSetData1=0;  //本程序中需要被设置的参数1
unsigned int  uiSetData2=0;  //本程序中需要被设置的参数2
unsigned int  uiSetData3=0;  //本程序中需要被设置的参数3
unsigned int  uiSetData4=0;  //本程序中需要被设置的参数4

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)  
   {
     key_service(); //按键服务的应用程序
         display_service(); //显示的窗口菜单服务程序
   }
}
/* 注释一:
*鸿哥首次提出的"一二级菜单显示理论":
*凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,
*每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。
*局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,
*表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。
*/

void display_service() //显示的窗口菜单服务程序
{

   switch(ucWd)  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
   {
       case 1:   //显示P--1窗口的数据
            if(ucWd1Update==1)  //窗口1要全部更新显示
   {
               ucWd1Update=0;  //及时清零标志,避免一直进来扫描
               ucDigShow8=12;  //第8位数码管显示P
               ucDigShow7=11;  //第7位数码管显示-
               ucDigShow6=1;   //第6位数码管显示1
               ucDigShow5=10;  //第5位数码管显示无

              //先分解数据
                       ucTemp4=uiSetData1/1000;     
                       ucTemp3=uiSetData1%1000/100;
                       ucTemp2=uiSetData1%100/10;
                       ucTemp1=uiSetData1%10;
  
                          //再过渡需要显示的数据到缓冲变量里,让过渡的时间越短越好
/* 注释二:
* 就是在这里略作修改,把高位为0的去掉不显示。
*/
               if(uiSetData1<1000)   
                           {
                              ucDigShow4=10;  //如果小于1000,千位显示无
                           }
               else
                           {
                  ucDigShow4=ucTemp4;  //第4位数码管要显示的内容
                           }
               if(uiSetData1<100)
                           {
                  ucDigShow3=10;  //如果小于100,百位显示无
                           }
                           else
                           {
                  ucDigShow3=ucTemp3;  //第3位数码管要显示的内容
                           }
               if(uiSetData1<10)
                           {
                  ucDigShow2=10;  //如果小于10,十位显示无
                           }
                           else
                           {
                  ucDigShow2=ucTemp2;  //第2位数码管要显示的内容
               }
               ucDigShow1=ucTemp1;  //第1位数码管要显示的内容
            }
            break;
        case 2:  //显示P--2窗口的数据
            if(ucWd2Update==1)  //窗口2要全部更新显示
   {
               ucWd2Update=0;  //及时清零标志,避免一直进来扫描
               ucDigShow8=12;  //第8位数码管显示P
               ucDigShow7=11;  //第7位数码管显示-
               ucDigShow6=2;  //第6位数码管显示2
               ucDigShow5=10;   //第5位数码管显示无
                       ucTemp4=uiSetData2/1000;     //分解数据
                       ucTemp3=uiSetData2%1000/100;
                       ucTemp2=uiSetData2%100/10;
                       ucTemp1=uiSetData2%10;

               if(uiSetData2<1000)   
                           {
                              ucDigShow4=10;  //如果小于1000,千位显示无
                           }
               else
                           {
                  ucDigShow4=ucTemp4;  //第4位数码管要显示的内容
                           }
               if(uiSetData2<100)
                           {
                  ucDigShow3=10;  //如果小于100,百位显示无
                           }
                           else
                           {
                  ucDigShow3=ucTemp3;  //第3位数码管要显示的内容
                           }
               if(uiSetData2<10)
                           {
                  ucDigShow2=10;  //如果小于10,十位显示无
                           }
                           else
                           {
                  ucDigShow2=ucTemp2;  //第2位数码管要显示的内容
               }
               ucDigShow1=ucTemp1;  //第1位数码管要显示的内容
    }
             break;
        case 3:  //显示P--3窗口的数据
            if(ucWd3Update==1)  //窗口3要全部更新显示
   {
               ucWd3Update=0;  //及时清零标志,避免一直进来扫描
               ucDigShow8=12;  //第8位数码管显示P
               ucDigShow7=11;  //第7位数码管显示-
               ucDigShow6=3;  //第6位数码管显示3
               ucDigShow5=10;   //第5位数码管显示无
                       ucTemp4=uiSetData3/1000;     //分解数据
                       ucTemp3=uiSetData3%1000/100;
                       ucTemp2=uiSetData3%100/10;
                       ucTemp1=uiSetData3%10;
               if(uiSetData3<1000)   
                           {
                              ucDigShow4=10;  //如果小于1000,千位显示无
                           }
               else
                           {
                  ucDigShow4=ucTemp4;  //第4位数码管要显示的内容
                           }
               if(uiSetData3<100)
                           {
                  ucDigShow3=10;  //如果小于100,百位显示无
                           }
                           else
                           {
                  ucDigShow3=ucTemp3;  //第3位数码管要显示的内容
                           }
               if(uiSetData3<10)
                           {
                  ucDigShow2=10;  //如果小于10,十位显示无
                           }
                           else
                           {
                  ucDigShow2=ucTemp2;  //第2位数码管要显示的内容
               }
               ucDigShow1=ucTemp1;  //第1位数码管要显示的内容
   }
            break;
        case 4:  //显示P--4窗口的数据
            if(ucWd4Update==1)  //窗口4要全部更新显示
   {
               ucWd4Update=0;  //及时清零标志,避免一直进来扫描
               ucDigShow8=12;  //第8位数码管显示P
               ucDigShow7=11;  //第7位数码管显示-
               ucDigShow6=4;  //第6位数码管显示4
               ucDigShow5=10;   //第5位数码管显示无
                       ucTemp4=uiSetData4/1000;     //分解数据
                       ucTemp3=uiSetData4%1000/100;
                       ucTemp2=uiSetData4%100/10;
                       ucTemp1=uiSetData4%10;

               if(uiSetData4<1000)   
                           {
                              ucDigShow4=10;  //如果小于1000,千位显示无
                           }
               else
                           {
                  ucDigShow4=ucTemp4;  //第4位数码管要显示的内容
                           }
               if(uiSetData4<100)
                           {
                  ucDigShow3=10;  //如果小于100,百位显示无
                           }
                           else
                           {
                  ucDigShow3=ucTemp3;  //第3位数码管要显示的内容
                           }
               if(uiSetData4<10)
                           {
                  ucDigShow2=10;  //如果小于10,十位显示无
                           }
                           else
                           {
                  ucDigShow2=ucTemp2;  //第2位数码管要显示的内容
               }
               ucDigShow1=ucTemp1;  //第1位数码管要显示的内容
    }
             break;
           }
   

}

void key_scan()//按键扫描函数 放在定时中断里
{  
  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号键
     }
  }

}

void key_service() //按键服务的应用程序
{
  switch(ucKeySec) //按键服务状态切换
  {
    case 1:// 加按键 对应朱兆祺学习板的S1键
          switch(ucWd)  //在不同的窗口下,设置不同的参数
                  {
                     case 1:
                  uiSetData1++;   
                                  if(uiSetData1>9999) //最大值是9999
                                  {
                                     uiSetData1=9999;
                                  }
                           ucWd1Update=1;  //窗口1更新显示
                              break;
                     case 2:
                  uiSetData2++;
                                  if(uiSetData2>9999) //最大值是9999
                                  {
                                     uiSetData2=9999;
                                  }
                           ucWd2Update=1;  //窗口2更新显示
                              break;
                     case 3:
                  uiSetData3++;
                                  if(uiSetData3>9999) //最大值是9999
                                  {
                                     uiSetData3=9999;
                                  }
                           ucWd3Update=1;  //窗口3更新显示
                              break;
                     case 4:
                  uiSetData4++;
                                  if(uiSetData4>9999) //最大值是9999
                                  {
                                     uiSetData4=9999;
                                  }
                           ucWd4Update=1;  //窗口4更新显示
                              break;
                  }
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
   
    case 2:// 减按键 对应朱兆祺学习板的S5键
          switch(ucWd)  //在不同的窗口下,设置不同的参数
                  {
                     case 1:
                  uiSetData1--;   

                                  if(uiSetData1>9999)  
                                  {
                                     uiSetData1=0;  //最小值是0
                                  }
                           ucWd1Update=1;  //窗口1更新显示
                              break;
                     case 2:
                  uiSetData2--;
                                  if(uiSetData2>9999)
                                  {
                                     uiSetData2=0;  //最小值是0
                                  }
                           ucWd2Update=1;  //窗口2更新显示
                              break;
                     case 3:
                  uiSetData3--;
                                  if(uiSetData3>9999)
                                  {
                                     uiSetData3=0;  //最小值是0
                                  }
                           ucWd3Update=1;  //窗口3更新显示
                              break;
                     case 4:
                  uiSetData4--;
                                  if(uiSetData4>9999)
                                  {
                                     uiSetData4=0;  //最小值是0
                                  }
                           ucWd4Update=1;  //窗口4更新显示
                              break;
                  }

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;  
    case 3:// 切换窗口按键 对应朱兆祺学习板的S9键
          ucWd++;  //切换窗口
                  if(ucWd>4)
                  {
                    ucWd=1;
                  }
          switch(ucWd)  //在不同的窗口下,在不同的窗口下,更新显示不同的窗口
                  {
                     case 1:
                           ucWd1Update=1;  //窗口1更新显示
                              break;
                     case 2:
                           ucWd2Update=1;  //窗口2更新显示
                              break;
                     case 3:
                           ucWd3Update=1;  //窗口3更新显示
                              break;
                     case 4:
                           ucWd4Update=1;  //窗口4更新显示
                              break;
                  }
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;         
         
  }               
}

void display_drive()  
{
   //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
   switch(ucDisplayDriveStep)
   {
      case 1:  //显示第1位
           ucDigShowTemp=dig_table[ucDigShow1];
                   if(ucDigDot1==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xfe);
               break;
      case 2:  //显示第2位
           ucDigShowTemp=dig_table[ucDigShow2];
                   if(ucDigDot2==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xfd);
               break;
      case 3:  //显示第3位
           ucDigShowTemp=dig_table[ucDigShow3];
                   if(ucDigDot3==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xfb);
               break;
      case 4:  //显示第4位
           ucDigShowTemp=dig_table[ucDigShow4];
                   if(ucDigDot4==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xf7);
               break;
      case 5:  //显示第5位
           ucDigShowTemp=dig_table[ucDigShow5];
                   if(ucDigDot5==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xef);
               break;
      case 6:  //显示第6位
           ucDigShowTemp=dig_table[ucDigShow6];
                   if(ucDigDot6==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xdf);
               break;
      case 7:  //显示第7位
           ucDigShowTemp=dig_table[ucDigShow7];
                   if(ucDigDot7==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
           }
           dig_hc595_drive(ucDigShowTemp,0xbf);
               break;
      case 8:  //显示第8位
           ucDigShowTemp=dig_table[ucDigShow8];
                   if(ucDigDot8==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0x7f);
               break;
   }
   ucDisplayDriveStep++;
   if(ucDisplayDriveStep>8)  //扫描完8个数码管后,重新从第一个开始扫描
   {
     ucDisplayDriveStep=1;
   }

}

//数码管的74HC595驱动函数
void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
{
   unsigned char i;
   unsigned char ucTempData;
   dig_hc595_sh_dr=0;
   dig_hc595_st_dr=0;
   ucTempData=ucDigStatusTemp16_09;  //先送高8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)dig_hc595_ds_dr=1;
         else dig_hc595_ds_dr=0;
         dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         dig_hc595_sh_dr=1;
         delay_short(1);
         ucTempData=ucTempData<<1;
   }
   ucTempData=ucDigStatusTemp08_01;  //再先送低8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)dig_hc595_ds_dr=1;
         else dig_hc595_ds_dr=0;
         dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         dig_hc595_sh_dr=1;
         delay_short(1);
         ucTempData=ucTempData<<1;
   }
   dig_hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
   delay_short(1);
   dig_hc595_st_dr=1;
   delay_short(1);
   dig_hc595_sh_dr=0;    //拉低,抗干扰就增强
   dig_hc595_st_dr=0;
   dig_hc595_ds_dr=0;
}

//LED灯的74HC595驱动函数
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
{
   unsigned char i;
   unsigned char ucTempData;
   hc595_sh_dr=0;
   hc595_st_dr=0;
   ucTempData=ucLedStatusTemp16_09;  //先送高8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;
         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         hc595_sh_dr=1;
         delay_short(1);
         ucTempData=ucTempData<<1;
   }
   ucTempData=ucLedStatusTemp08_01;  //再先送低8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;
         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         hc595_sh_dr=1;
         delay_short(1);
         ucTempData=ucTempData<<1;
   }
   hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
   delay_short(1);
   hc595_st_dr=1;
   delay_short(1);
   hc595_sh_dr=0;    //拉低,抗干扰就增强
   hc595_st_dr=0;
   hc595_ds_dr=0;
}

void T0_time() interrupt 1
{
  TF0=0;  //清除中断标志
  TR0=0; //关中断
  key_scan(); //按键扫描函数
  if(uiVoiceCnt!=0)
  {
     uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
     beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
//     beep_dr=1;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  }
  else
  {
     ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
     beep_dr=1;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
//     beep_dr=0;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  }
  display_drive();  //数码管字模的驱动函数

  TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  TL0=0x0b;
  TR0=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++)  //内嵌循环的空指令数量
          {
             ; //一个分号相当于执行一条空语句
          }
   }
}

void initial_myself()  //第一区 初始化单片机
{
/* 注释三:
* 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
* 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
* 坚鸿51学习板的S1就是本程序中用到的一个独立按键。
*/
  key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平
  led_dr=0;  //关闭独立LED灯
  beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
  hc595_drive(0x00,0x00);  //关闭所有经过另外两个74HC595驱动的LED灯
  TMOD=0x01;  //设置定时器0为工作方式1
  TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  TL0=0x0b;
}
void initial_peripheral() //第二区 初始化外围
{

   ucDigDot8=0;   //小数点全部不显示
   ucDigDot7=0;  
   ucDigDot6=0;
   ucDigDot5=0;  
   ucDigDot4=0;
   ucDigDot3=0;  
   ucDigDot2=0;
   ucDigDot1=0;
   EA=1;     //开总中断
   ET0=1;    //允许定时中断
   TR0=1;    //启动定时中断
}


总结陈词:
数码管通过切换窗口来设置参数,这里的窗口类似于一级菜单,在一级菜单下,还可以分解出二级菜单。一级菜单的特点是整屏数码管的显示内容全部都改变,而二级菜单的特点是只改变其中一部分数码管的内容。二级菜单的程序怎么编写?欲知详情,请听下回分解-----数码管通过闪烁来设置数据。

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


作者: jianhong_wu    时间: 2014-2-28 15:54
本帖最后由 jianhong_wu 于 2014-2-28 16:34 编辑

第三十节:数码管通过闪烁来设置数据。

开场白:
   上一节讲了一级菜单,这一节要教会大家两个知识点:
第一个:二级菜单的程序的程序框架。
第二个:继续加深熟悉鸿哥首次提出的“一二级菜单显示理论”:凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。


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

(1)硬件平台:基于坚鸿51单片机学习板。加按键对应S1键,减按键对应S5键,切换“光标闪烁”按键对应S9键

(2)实现功能:
     通过按键设置4个不同的参数。
    只有1个窗口。这个窗口显示4个参数。
第8,7位数码管显示第1个参数。第6,5位数码管显示第2个参数。第4,3位数码管显示第3个参数。第2,1位数码管显示第4个参数。每个参数的范围是从0到99。
有三个按键。一个是“光标闪烁”按键,依次按下此按键,每两位数码管会依次处于闪烁的状态,哪两位数码管处于闪烁状态时,此时按加键或者减键就可以设置当前选中的参数。依次按下“光标闪烁”按键,数码管会在以下5种状态中循环:只有第8,7位数码管闪烁---只有第6,5位数码管闪烁---只有第4,3位数码管闪烁---只有第2,1位数码管闪烁---所有的数码管都不闪烁。

(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_dpy_time_half  200  //数码管闪烁时间的半值
#define const_dpy_time_all   400  //数码管闪烁时间的全值 一定要比const_dpy_time_half 大

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


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

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

sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
sbit led_dr=P3^5;  //作为中途暂停指示灯 亮的时候表示中途暂停


sbit dig_hc595_sh_dr=P2^0;     //数码管的74HC595程序
sbit dig_hc595_st_dr=P2^1;  
sbit dig_hc595_ds_dr=P2^2;  

sbit hc595_sh_dr=P2^3;    //LED灯的74HC595程序
sbit hc595_st_dr=P2^4;  
sbit hc595_ds_dr=P2^5;  

unsigned char ucKeySec=0;   //被触发的按键编号

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

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

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

unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器


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 ucWd=1;  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
unsigned char ucWd1Update=1; //窗口1更新显示标志

unsigned char ucPart=0;//本程序的核心变量,局部显示变量。类似于二级菜单的变量。代表显示不同的局部。

unsigned char ucWd1Part1Update=0;  //在窗口1中,局部1的更新显示标志
unsigned char ucWd1Part2Update=0; //在窗口1中,局部2的更新显示标志
unsigned char ucWd1Part3Update=0; //在窗口1中,局部3的更新显示标志
unsigned char ucWd1Part4Update=0; //在窗口1中,局部4的更新显示标志

unsigned int  uiSetData1=0;  //本程序中需要被设置的参数1
unsigned int  uiSetData2=0;  //本程序中需要被设置的参数2
unsigned int  uiSetData3=0;  //本程序中需要被设置的参数3
unsigned int  uiSetData4=0;  //本程序中需要被设置的参数4


unsigned char ucTemp1=0;  //中间过渡变量
unsigned char ucTemp2=0;  //中间过渡变量
unsigned char ucTemp3=0;  //中间过渡变量
unsigned char ucTemp4=0;  //中间过渡变量
unsigned char ucTemp5=0;  //中间过渡变量
unsigned char ucTemp6=0;  //中间过渡变量
unsigned char ucTemp7=0;  //中间过渡变量
unsigned char ucTemp8=0;  //中间过渡变量

unsigned int  uiDpyTimeCnt=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)  
   {
     key_service(); //按键服务的应用程序
         display_service(); //显示的窗口菜单服务程序
   }

}

/* 注释一:
*鸿哥首次提出的"一二级菜单显示理论":
*凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,
*每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。
*局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,
*表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。
*/


void display_service() //显示的窗口菜单服务程序
{


   switch(ucWd)  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
   {
       case 1:   //显示窗口1的数据

                        if(ucWd1Part1Update==1)  //仅仅参数1局部更新
                        {
                           ucWd1Part1Update=0;   //及时清零标志,避免一直进来扫描

               ucTemp8=uiSetData1/10;  //第1个参数
               ucTemp7=uiSetData1%10;
                           if(uiSetData1<10)
                           {
                              ucDigShow8=10;
                           }
                           else
                           {
                              ucDigShow8=ucTemp8;
                           }
                           ucDigShow7=ucTemp7;
                        }

                        if(ucWd1Part2Update==1)  //仅仅参数2局部更新
                        {
                           ucWd1Part2Update=0;  //及时清零标志,避免一直进来扫描

               ucTemp6=uiSetData2/10;  //第2个参数
               ucTemp5=uiSetData2%10;
                           if(uiSetData2<10)
                           {
                              ucDigShow6=10;
                           }
                           else
                           {
                              ucDigShow6=ucTemp6;
                           }
                           ucDigShow5=ucTemp5;

                        }

                        if(ucWd1Part3Update==1)  //仅仅参数3局部更新
                        {
                           ucWd1Part3Update=0;  //及时清零标志,避免一直进来扫描

               ucTemp4=uiSetData3/10;  //第3个参数
               ucTemp3=uiSetData3%10;
                           if(uiSetData3<10)
                           {
                              ucDigShow4=10;
                           }
                           else
                           {
                              ucDigShow4=ucTemp4;
                           }
                           ucDigShow3=ucTemp3;
                        }

                        if(ucWd1Part4Update==1)  //仅仅参数4局部更新
                        {
                           ucWd1Part4Update=0;   //及时清零标志,避免一直进来扫描

               ucTemp2=uiSetData4/10;  //第4个参数
               ucTemp1=uiSetData4%10;

                           if(uiSetData4<10)
                           {
                              ucDigShow2=10;
                           }
                           else
                           {
                              ucDigShow2=ucTemp2;
                           }
                           ucDigShow1=ucTemp1;
                        }

/* 注释二:
* 必须注意局部更新和全部更新的编写顺序,局部更新应该写在全部更新之前,
* 当局部更新和全部更新同时发生时,这样就能保证到全部更新的优先响应。
*/

            if(ucWd1Update==1)  //窗口1要全部更新显示
                        {
               ucWd1Update=0;  //及时清零标志,避免一直进来扫描

               ucTemp8=uiSetData1/10;  //第1个参数
               ucTemp7=uiSetData1%10;

               ucTemp6=uiSetData2/10;  //第2个参数
               ucTemp5=uiSetData2%10;


               ucTemp4=uiSetData3/10;  //第3个参数
               ucTemp3=uiSetData3%10;

               ucTemp2=uiSetData4/10;  //第4个参数
               ucTemp1=uiSetData4%10;


                           if(uiSetData1<10)
                           {
                              ucDigShow8=10;
                           }
                           else
                           {
                              ucDigShow8=ucTemp8;
                           }
                           ucDigShow7=ucTemp7;


                           if(uiSetData2<10)
                           {
                              ucDigShow6=10;
                           }
                           else
                           {
                              ucDigShow6=ucTemp6;
                           }
                           ucDigShow5=ucTemp5;

                           if(uiSetData3<10)
                           {
                              ucDigShow4=10;
                           }
                           else
                           {
                              ucDigShow4=ucTemp4;
                           }
                           ucDigShow3=ucTemp3;

                           if(uiSetData4<10)
                           {
                              ucDigShow2=10;
                           }
                           else
                           {
                              ucDigShow2=ucTemp2;
                           }
                           ucDigShow1=ucTemp1;

            }


                        //数码管闪烁
            switch(ucPart)  //根据局部变量的值,使对应的参数产生闪烁的动态效果。
                        {
                           case 0:  //4个参数都不闪烁

                                break;
                           case 1:  //第1个参数闪烁
                                if(uiDpyTimeCnt==const_dpy_time_half)
                                        {
                                   if(uiSetData1<10)        //数码管显示内容
                                   {
                                      ucDigShow8=10;
                                   }
                                   else
                                   {
                                      ucDigShow8=ucTemp8;
                                   }
                                   ucDigShow7=ucTemp7;
                                        }
                                else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
                                        {
                                           uiDpyTimeCnt=0;   //及时把闪烁记时器清零

                                   ucDigShow8=10;   //数码管显示空,什么都不显示
                                   ucDigShow7=10;

                                        }
                                break;
                           case 2:   //第2个参数闪烁
                                if(uiDpyTimeCnt==const_dpy_time_half)
                                        {
                                   if(uiSetData2<10)        //数码管显示内容
                                   {
                                      ucDigShow6=10;
                                   }
                                   else
                                   {
                                      ucDigShow6=ucTemp6;
                                   }
                                   ucDigShow5=ucTemp5;
                                        }
                                else if(uiDpyTimeCnt>const_dpy_time_all)  //const_dpy_time_all一定要比const_dpy_time_half 大
                                        {
                                           uiDpyTimeCnt=0;   //及时把闪烁记时器清零

                                   ucDigShow6=10;   //数码管显示空,什么都不显示
                                   ucDigShow5=10;

                                        }
                                break;
                           case 3:  //第3个参数闪烁
                                if(uiDpyTimeCnt==const_dpy_time_half)
                                        {
                                   if(uiSetData3<10)        //数码管显示内容
                                   {
                                      ucDigShow4=10;
                                   }
                                   else
                                   {
                                      ucDigShow4=ucTemp4;
                                   }
                                   ucDigShow3=ucTemp3;
                                        }
                                else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
                                        {
                                           uiDpyTimeCnt=0;   //及时把闪烁记时器清零

                                   ucDigShow4=10;   //数码管显示空,什么都不显示
                                   ucDigShow3=10;

                                        }
                                break;
                           case 4:  //第4个参数闪烁
                                if(uiDpyTimeCnt==const_dpy_time_half)
                                        {
                                   if(uiSetData4<10)        //数码管显示内容
                                   {
                                      ucDigShow2=10;
                                   }
                                   else
                                   {
                                      ucDigShow2=ucTemp2;
                                   }
                                   ucDigShow1=ucTemp1;
                                        }
                                else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
                                        {
                                           uiDpyTimeCnt=0;   //及时把闪烁记时器清零

                                   ucDigShow2=10;   //数码管显示空,什么都不显示
                                   ucDigShow1=10;

                                        }
                                break;
                        }

            break;
   
     }
   


}


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

  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号键
     }
  }



}


void key_service() //按键服务的应用程序
{
  switch(ucKeySec) //按键服务状态切换
  {
    case 1:// 加按键 对应朱兆祺学习板的S1键
          switch(ucWd)  //在不同的窗口下,设置不同的参数
          {
              case 1:
                   switch(ucPart)  //在窗口1下,根据不同的局部闪烁位置来设置不同的参数
                                   {
                                      case 0:
                                               break;
                                      case 1:
                           uiSetData1++;   
                           if(uiSetData1>99) //最大值是99
                           {
                               uiSetData1=99;
                           }
                                                   ucWd1Part1Update=1; //局部更新显示参数1
                                               break;
                                      case 2:
                           uiSetData2++;   
                           if(uiSetData2>99) //最大值是99
                           {
                               uiSetData2=99;
                           }
                                                   ucWd1Part2Update=1; //局部更新显示参数2
                                               break;
                                      case 3:
                           uiSetData3++;   
                           if(uiSetData3>99) //最大值是99
                           {
                               uiSetData3=99;
                           }
                                                   ucWd1Part3Update=1; //局部更新显示参数3
                                               break;
                                      case 4:
                           uiSetData4++;   
                           if(uiSetData4>99) //最大值是99
                           {
                               uiSetData4=99;
                           }
                                                   ucWd1Part4Update=1; //局部更新显示参数4
                                               break;
                                   }
                   break;
          }     
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
   
    case 2:// 减按键 对应朱兆祺学习板的S5键
          switch(ucWd)  //在不同的窗口下,设置不同的参数
          {
              case 1:
                   switch(ucPart)  //在窗口1下,根据不同的局部闪烁位置来设置不同的参数
                                   {
                                      case 0:
                                               break;
                                      case 1:
                           uiSetData1--;   
                           if(uiSetData1>99) //0减去1溢出肯定大于99
                           {
                               uiSetData1=0;
                           }
                                                   ucWd1Part1Update=1; //局部更新显示参数1
                                               break;
                                      case 2:
                           uiSetData2--;   
                           if(uiSetData2>99) //0减去1溢出肯定大于99
                           {
                               uiSetData2=0;
                           }
                                                   ucWd1Part2Update=1; //局部更新显示参数2
                                               break;
                                      case 3:
                           uiSetData3--;   
                           if(uiSetData3>99) //0减去1溢出肯定大于99
                           {
                               uiSetData3=0;
                           }
                                                   ucWd1Part3Update=1; //局部更新显示参数3
                                               break;
                                      case 4:
                           uiSetData4--;  
                           if(uiSetData4>99) //0减去1溢出肯定大于99
                           {
                               uiSetData4=0;
                           }
                                                   ucWd1Part4Update=1; //局部更新显示参数4
                                               break;
                                   }
                   break;
          }  
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;  

    case 3:// 切换"光标闪烁"按键 对应朱兆祺学习板的S9键
          switch(ucWd)  //在不同的窗口下,设置不同的参数
          {
              case 1:  //在窗口1下,切换"光标闪烁"
                   ucPart++;
                                   if(ucPart>4)
                                   {
                                     ucPart=0;
                                   }
                                   ucWd1Update=1;  //窗口1全部更新显示
                   break;
          }  
        
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;         
         

  }               
}


void display_drive()  
{
   //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
   switch(ucDisplayDriveStep)
   {
      case 1:  //显示第1位
           ucDigShowTemp=dig_table[ucDigShow1];
                   if(ucDigDot1==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xfe);
               break;
      case 2:  //显示第2位
           ucDigShowTemp=dig_table[ucDigShow2];
                   if(ucDigDot2==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xfd);
               break;
      case 3:  //显示第3位
           ucDigShowTemp=dig_table[ucDigShow3];
                   if(ucDigDot3==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xfb);
               break;
      case 4:  //显示第4位
           ucDigShowTemp=dig_table[ucDigShow4];
                   if(ucDigDot4==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xf7);
               break;
      case 5:  //显示第5位
           ucDigShowTemp=dig_table[ucDigShow5];
                   if(ucDigDot5==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xef);
               break;
      case 6:  //显示第6位
           ucDigShowTemp=dig_table[ucDigShow6];
                   if(ucDigDot6==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xdf);
               break;
      case 7:  //显示第7位
           ucDigShowTemp=dig_table[ucDigShow7];
                   if(ucDigDot7==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
           }
           dig_hc595_drive(ucDigShowTemp,0xbf);
               break;
      case 8:  //显示第8位
           ucDigShowTemp=dig_table[ucDigShow8];
                   if(ucDigDot8==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0x7f);
               break;
   }

   ucDisplayDriveStep++;
   if(ucDisplayDriveStep>8)  //扫描完8个数码管后,重新从第一个开始扫描
   {
     ucDisplayDriveStep=1;
   }



}


//数码管的74HC595驱动函数
void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
{
   unsigned char i;
   unsigned char ucTempData;
   dig_hc595_sh_dr=0;
   dig_hc595_st_dr=0;

   ucTempData=ucDigStatusTemp16_09;  //先送高8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)dig_hc595_ds_dr=1;
         else dig_hc595_ds_dr=0;

         dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         dig_hc595_sh_dr=1;
         delay_short(1);

         ucTempData=ucTempData<<1;
   }

   ucTempData=ucDigStatusTemp08_01;  //再先送低8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)dig_hc595_ds_dr=1;
         else dig_hc595_ds_dr=0;

         dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         dig_hc595_sh_dr=1;
         delay_short(1);

         ucTempData=ucTempData<<1;
   }

   dig_hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
   delay_short(1);
   dig_hc595_st_dr=1;
   delay_short(1);

   dig_hc595_sh_dr=0;    //拉低,抗干扰就增强
   dig_hc595_st_dr=0;
   dig_hc595_ds_dr=0;

}


//LED灯的74HC595驱动函数
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
{
   unsigned char i;
   unsigned char ucTempData;
   hc595_sh_dr=0;
   hc595_st_dr=0;

   ucTempData=ucLedStatusTemp16_09;  //先送高8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;

         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         hc595_sh_dr=1;
         delay_short(1);

         ucTempData=ucTempData<<1;
   }

   ucTempData=ucLedStatusTemp08_01;  //再先送低8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;

         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         hc595_sh_dr=1;
         delay_short(1);

         ucTempData=ucTempData<<1;
   }

   hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
   delay_short(1);
   hc595_st_dr=1;
   delay_short(1);

   hc595_sh_dr=0;    //拉低,抗干扰就增强
   hc595_st_dr=0;
   hc595_ds_dr=0;

}


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

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

  uiDpyTimeCnt++;  //数码管的闪烁计时器

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

  display_drive();  //数码管字模的驱动函数


  TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  TL0=0x0b;
  TR0=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++)  //内嵌循环的空指令数量
          {
             ; //一个分号相当于执行一条空语句
          }
   }
}


void initial_myself()  //第一区 初始化单片机
{

/* 注释三:
* 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
* 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
* 坚鸿51学习板的S1就是本程序中用到的一个独立按键。
*/
  key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平

  led_dr=0;  //关闭独立LED灯
  beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

  hc595_drive(0x00,0x00);  //关闭所有经过另外两个74HC595驱动的LED灯

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

  TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  TL0=0x0b;

}

void initial_peripheral() //第二区 初始化外围
{


   ucDigDot8=0;   //小数点全部不显示
   ucDigDot7=0;  
   ucDigDot6=0;
   ucDigDot5=0;  
   ucDigDot4=0;
   ucDigDot3=0;  
   ucDigDot2=0;
   ucDigDot1=0;

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

}


总结陈词:
这节讲了数码管通过闪烁来设置数据的基本程序,但是该程序只有一个窗口。实际应用中,有些项目会有几个窗口,而且每个窗口都要设置几个参数,这样的程序该怎么写?欲知详情,请听下回分解-----数码管通过一二级菜单来设置数据的综合程序。

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

作者: jianhong_wu    时间: 2014-2-28 17:07
第三十一节:数码管通过一二级菜单来设置数据的综合程序。

开场白:
   上一节讲了二级菜单,这一节要教会大家两个知识点:
第一个:数码管通过一二级菜单来设置数据的综合程序框架。
第二个:继续加深熟悉鸿哥首次提出的“一二级菜单显示理论”:凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。


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

(1)硬件平台:基于坚鸿51单片机学习板。加按键对应S1键,减按键对应S5键,切换“光标闪烁”按键对应S9键,切换窗口按键对应S13键。

(2)实现功能:
     通过按键设置4个不同的参数。
    有2个窗口。每个窗口显示2个参数。
   第8,7,6,5位数码管显示”P-1 ”代表第1个窗口,显示”P-2 ”代表第2个窗口。第4,3位数码管显示该窗口下其中一个参数,第2,1位数码管显示该窗口下其中另外一个参数。每个参数的范围是从0到99。
有四个按键。
一个是切换窗口按键,依次按下此按键,会依次切换窗口显示。一个是“光标闪烁”按键,依次按下此按键,每两位数码管会依次处于闪烁的状态,哪两位数码管处于闪烁状态时,此时按加键或者减键就可以设置当前选中的参数。依次按下“光标闪烁”按键,数码管会在以下3种状态中循环:只有第4,3位数码管闪烁---只有第2,1位数码管闪烁---所有的数码管都不闪烁。

(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    //按键去抖动延时的时间

#define const_dpy_time_half  200  //数码管闪烁时间的半值
#define const_dpy_time_all   400  //数码管闪烁时间的全值 一定要比const_dpy_time_half 大

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


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 led_dr=P3^5;  //作为中途暂停指示灯 亮的时候表示中途暂停


sbit dig_hc595_sh_dr=P2^0;     //数码管的74HC595程序
sbit dig_hc595_st_dr=P2^1;  
sbit dig_hc595_ds_dr=P2^2;  

sbit hc595_sh_dr=P2^3;    //LED灯的74HC595程序
sbit hc595_st_dr=P2^4;  
sbit hc595_ds_dr=P2^5;  

unsigned char ucKeySec=0;   //被触发的按键编号

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

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

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

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

unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器


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 ucWd=1;  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
unsigned char ucWd1Update=1; //窗口1更新显示标志
unsigned char ucWd2Update=0; //窗口2更新显示标志
unsigned char ucPart=0;//本程序的核心变量,局部显示变量。类似于二级菜单的变量。代表显示不同的局部。

unsigned char ucWd1Part1Update=0;  //在窗口1中,局部1的更新显示标志
unsigned char ucWd1Part2Update=0; //在窗口1中,局部2的更新显示标志
unsigned char ucWd2Part1Update=0; //在窗口2中,局部1的更新显示标志
unsigned char ucWd2Part2Update=0; //在窗口2中,局部2的更新显示标志

unsigned int  uiSetData1=0;  //本程序中需要被设置的参数1
unsigned int  uiSetData2=0;  //本程序中需要被设置的参数2
unsigned int  uiSetData3=0;  //本程序中需要被设置的参数3
unsigned int  uiSetData4=0;  //本程序中需要被设置的参数4


unsigned char ucTemp1=0;  //中间过渡变量
unsigned char ucTemp2=0;  //中间过渡变量
unsigned char ucTemp3=0;  //中间过渡变量
unsigned char ucTemp4=0;  //中间过渡变量
unsigned char ucTemp5=0;  //中间过渡变量
unsigned char ucTemp6=0;  //中间过渡变量
unsigned char ucTemp7=0;  //中间过渡变量
unsigned char ucTemp8=0;  //中间过渡变量

unsigned int  uiDpyTimeCnt=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)  
   {
     key_service(); //按键服务的应用程序
         display_service(); //显示的窗口菜单服务程序
   }

}

/* 注释一:
*鸿哥首次提出的"一二级菜单显示理论":
*凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,
*每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。
*局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,
*表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。
*/


void display_service() //显示的窗口菜单服务程序
{


   switch(ucWd)  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
   {
       case 1:   //显示窗口1的数据

                        if(ucWd1Part1Update==1)  //仅仅参数1局部更新
                        {
                           ucWd1Part1Update=0;   //及时清零标志,避免一直进来扫描

               ucTemp4=uiSetData1/10;  //第1个参数
               ucTemp3=uiSetData1%10;
                           if(uiSetData1<10)
                           {
                              ucDigShow4=10;
                           }
                           else
                           {
                              ucDigShow4=ucTemp4;
                           }
                           ucDigShow3=ucTemp3;
                        }

                        if(ucWd1Part2Update==1)  //仅仅参数2局部更新
                        {
                           ucWd1Part2Update=0;  //及时清零标志,避免一直进来扫描

               ucTemp2=uiSetData2/10;  //第2个参数
               ucTemp1=uiSetData2%10;
                           if(uiSetData2<10)
                           {
                              ucDigShow2=10;
                           }
                           else
                           {
                              ucDigShow2=ucTemp2;
                           }
                           ucDigShow1=ucTemp1;

                        }

       

/* 注释二:
* 必须注意局部更新和全部更新的编写顺序,局部更新应该写在全部更新之前,
* 当局部更新和全部更新同时发生时,这样就能保证到全部更新的优先响应。
*/

            if(ucWd1Update==1)  //窗口1要全部更新显示
                        {
               ucWd1Update=0;  //及时清零标志,避免一直进来扫描

               ucTemp8=12;  //显示P
               ucTemp7=11;  //显示-
               ucTemp6=1;  //显示1
               ucTemp5=10;  //显示空

               ucTemp4=uiSetData1/10;  //第1个参数
               ucTemp3=uiSetData1%10;

               ucTemp2=uiSetData2/10;  //第2个参数
               ucTemp1=uiSetData2%10;


               ucDigShow8=ucTemp8;  
               ucDigShow7=ucTemp7;  
               ucDigShow6=ucTemp6;  
               ucDigShow5=ucTemp5;

                           if(uiSetData1<10)
                           {
                              ucDigShow4=10;
                           }
                           else
                           {
                              ucDigShow4=ucTemp4;
                           }
                           ucDigShow3=ucTemp3;


                           if(uiSetData2<10)
                           {
                              ucDigShow2=10;
                           }
                           else
                           {
                              ucDigShow2=ucTemp2;
                           }
                           ucDigShow1=ucTemp1;

                       

            }


                        //数码管闪烁
            switch(ucPart)  //根据局部变量的值,使对应的参数产生闪烁的动态效果。
                        {
                           case 0:  //2个参数都不闪烁

                                break;
                           case 1:  //第1个参数闪烁
                                if(uiDpyTimeCnt==const_dpy_time_half)
                                        {
                                   if(uiSetData1<10)        //数码管显示内容
                                   {
                                      ucDigShow4=10;
                                   }
                                   else
                                   {
                                      ucDigShow4=ucTemp4;
                                   }
                                   ucDigShow3=ucTemp3;
                                        }
                                else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
                                        {
                                           uiDpyTimeCnt=0;   //及时把闪烁记时器清零

                                   ucDigShow4=10;   //数码管显示空,什么都不显示
                                   ucDigShow3=10;

                                        }
                                break;
                           case 2:   //第2个参数闪烁
                                if(uiDpyTimeCnt==const_dpy_time_half)
                                        {
                                   if(uiSetData2<10)        //数码管显示内容
                                   {
                                      ucDigShow2=10;
                                   }
                                   else
                                   {
                                      ucDigShow2=ucTemp2;
                                   }
                                   ucDigShow1=ucTemp1;
                                        }
                                else if(uiDpyTimeCnt>const_dpy_time_all)  //const_dpy_time_all一定要比const_dpy_time_half 大
                                        {
                                           uiDpyTimeCnt=0;   //及时把闪烁记时器清零

                                   ucDigShow2=10;   //数码管显示空,什么都不显示
                                   ucDigShow1=10;

                                        }
                                break;
       
                        }

            break;
       case 2:   //显示窗口2的数据

                        if(ucWd2Part1Update==1)  //在窗口2中,仅仅参数1局部更新
                        {
                           ucWd2Part1Update=0;   //及时清零标志,避免一直进来扫描

               ucTemp4=uiSetData3/10;  //第3个参数
               ucTemp3=uiSetData3%10;
                           if(uiSetData3<10)
                           {
                              ucDigShow4=10;
                           }
                           else
                           {
                              ucDigShow4=ucTemp4;
                           }
                           ucDigShow3=ucTemp3;
                        }

                        if(ucWd2Part2Update==1)  //在窗口2中,仅仅参数2局部更新
                        {
                           ucWd2Part2Update=0;  //及时清零标志,避免一直进来扫描

               ucTemp2=uiSetData4/10;  //第4个参数
               ucTemp1=uiSetData4%10;
                           if(uiSetData4<10)
                           {
                              ucDigShow2=10;
                           }
                           else
                           {
                              ucDigShow2=ucTemp2;
                           }
                           ucDigShow1=ucTemp1;

                        }

            if(ucWd2Update==1)  //窗口2要全部更新显示
                        {
               ucWd2Update=0;  //及时清零标志,避免一直进来扫描

               ucTemp8=12;  //显示P
               ucTemp7=11;  //显示-
               ucTemp6=2;  //显示2
               ucTemp5=10;  //显示空

               ucTemp4=uiSetData3/10;  //第3个参数
               ucTemp3=uiSetData3%10;

               ucTemp2=uiSetData4/10;  //第4个参数
               ucTemp1=uiSetData4%10;


               ucDigShow8=ucTemp8;  
               ucDigShow7=ucTemp7;  
               ucDigShow6=ucTemp6;  
               ucDigShow5=ucTemp5;

                           if(uiSetData3<10)
                           {
                              ucDigShow4=10;
                           }
                           else
                           {
                              ucDigShow4=ucTemp4;
                           }
                           ucDigShow3=ucTemp3;


                           if(uiSetData4<10)
                           {
                              ucDigShow2=10;
                           }
                           else
                           {
                              ucDigShow2=ucTemp2;
                           }
                           ucDigShow1=ucTemp1;

                       

            }


                        //数码管闪烁
            switch(ucPart)  //根据局部变量的值,使对应的参数产生闪烁的动态效果。
                        {
                           case 0:  //2个参数都不闪烁

                                break;
                           case 1:  //第3个参数闪烁
                                if(uiDpyTimeCnt==const_dpy_time_half)
                                        {
                                   if(uiSetData3<10)        //数码管显示内容
                                   {
                                      ucDigShow4=10;
                                   }
                                   else
                                   {
                                      ucDigShow4=ucTemp4;
                                   }
                                   ucDigShow3=ucTemp3;
                                        }
                                else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
                                        {
                                           uiDpyTimeCnt=0;   //及时把闪烁记时器清零

                                   ucDigShow4=10;   //数码管显示空,什么都不显示
                                   ucDigShow3=10;

                                        }
                                break;
                           case 2:   //第4个参数闪烁
                                if(uiDpyTimeCnt==const_dpy_time_half)
                                        {
                                   if(uiSetData4<10)        //数码管显示内容
                                   {
                                      ucDigShow2=10;
                                   }
                                   else
                                   {
                                      ucDigShow2=ucTemp2;
                                   }
                                   ucDigShow1=ucTemp1;
                                        }
                                else if(uiDpyTimeCnt>const_dpy_time_all)  //const_dpy_time_all一定要比const_dpy_time_half 大
                                        {
                                           uiDpyTimeCnt=0;   //及时把闪烁记时器清零

                                   ucDigShow2=10;   //数码管显示空,什么都不显示
                                   ucDigShow1=10;

                                        }
                                break;
       
                        }

            break;   
     }
   


}


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

  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() //按键服务的应用程序
{
  switch(ucKeySec) //按键服务状态切换
  {
    case 1:// 加按键 对应朱兆祺学习板的S1键
          switch(ucWd)  //在不同的窗口下,设置不同的参数
          {
              case 1:
                   switch(ucPart)  //在窗口1下,根据不同的局部闪烁位置来设置不同的参数
                                   {
                                      case 0:
                                               break;
                                      case 1:
                           uiSetData1++;   
                           if(uiSetData1>99) //最大值是99
                           {
                               uiSetData1=99;
                           }
                                                   ucWd1Part1Update=1; //局部更新显示参数1
                                               break;
                                      case 2:
                           uiSetData2++;   
                           if(uiSetData2>99) //最大值是99
                           {
                               uiSetData2=99;
                           }
                                                   ucWd1Part2Update=1; //局部更新显示参数2
                                               break;
                                   }
                   break;
              case 2:
                   switch(ucPart)  //在窗口2下,根据不同的局部闪烁位置来设置不同的参数
                                   {
                                      case 0:
                                               break;
                                      case 1:
                           uiSetData3++;   
                           if(uiSetData3>99) //最大值是99
                           {
                               uiSetData3=99;
                           }
                                                   ucWd2Part1Update=1; //局部更新显示参数1
                                               break;
                                      case 2:
                           uiSetData4++;   
                           if(uiSetData4>99) //最大值是99
                           {
                               uiSetData4=99;
                           }
                                                   ucWd2Part2Update=1; //局部更新显示参数2
                                               break;
                                   }
                   break;
          }     
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
   
    case 2:// 减按键 对应朱兆祺学习板的S5键
          switch(ucWd)  //在不同的窗口下,设置不同的参数
          {
              case 1:
                   switch(ucPart)  //在窗口1下,根据不同的局部闪烁位置来设置不同的参数
                                   {
                                      case 0:
                                               break;
                                      case 1:
                           uiSetData1--;   
                           if(uiSetData1>99) //0减去1溢出肯定大于99
                           {
                               uiSetData1=0;
                           }
                                                   ucWd1Part1Update=1; //局部更新显示参数1
                                               break;
                                      case 2:
                           uiSetData2--;   
                           if(uiSetData2>99) //0减去1溢出肯定大于99
                           {
                               uiSetData2=0;
                           }
                                                   ucWd1Part2Update=1; //局部更新显示参数2
                                               break;
                                   }
                   break;
              case 2:
                   switch(ucPart)  //在窗口2下,根据不同的局部闪烁位置来设置不同的参数
                                   {
                                      case 0:
                                               break;
                                      case 1:
                           uiSetData3--;   
                           if(uiSetData3>99) //0减去1溢出肯定大于99
                           {
                               uiSetData3=0;
                           }
                                                   ucWd2Part1Update=1; //局部更新显示参数1
                                               break;
                                      case 2:
                           uiSetData4--;   
                           if(uiSetData4>99) //0减去1溢出肯定大于99
                           {
                               uiSetData4=0;
                           }
                                                   ucWd2Part2Update=1; //局部更新显示参数2
                                               break;
                                   }
                   break;
          }  
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;  

    case 3:// 切换"光标闪烁"按键 对应朱兆祺学习板的S9键
          switch(ucWd)  //在不同的窗口下,设置不同的参数
          {
              case 1:  //在窗口1下,切换"光标闪烁"
                   ucPart++;
                                   if(ucPart>2)
                                   {
                                     ucPart=0;
                                   }
                                   ucWd1Update=1;  //窗口1全部更新显示
                   break;
              case 2:  //在窗口2下,切换"光标闪烁"
                   ucPart++;
                                   if(ucPart>2)
                                   {
                                     ucPart=0;
                                   }
                                   ucWd2Update=1;  //窗口2全部更新显示
                   break;
          }  
        
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;      
   
    case 4:// 切换窗口按键 对应朱兆祺学习板的S13键
              ucWd++;
                  if(ucWd>2)
                  {
                     ucWd=1;
                  }

                  ucPart=0; //强行把局部变量复位,让新切换的窗口不闪烁

          switch(ucWd)  //在不同的窗口下,更新显示不同的窗口
          {
              case 1:  //在窗口1下
                                   ucWd1Update=1;  //窗口1全部更新显示
                   break;
              case 2:  //在窗口2下
                                   ucWd2Update=1;  //窗口2全部更新显示
                   break;
          }  
        
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;         

  }               
}


void display_drive()  
{
   //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
   switch(ucDisplayDriveStep)
   {
      case 1:  //显示第1位
           ucDigShowTemp=dig_table[ucDigShow1];
                   if(ucDigDot1==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xfe);
               break;
      case 2:  //显示第2位
           ucDigShowTemp=dig_table[ucDigShow2];
                   if(ucDigDot2==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xfd);
               break;
      case 3:  //显示第3位
           ucDigShowTemp=dig_table[ucDigShow3];
                   if(ucDigDot3==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xfb);
               break;
      case 4:  //显示第4位
           ucDigShowTemp=dig_table[ucDigShow4];
                   if(ucDigDot4==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xf7);
               break;
      case 5:  //显示第5位
           ucDigShowTemp=dig_table[ucDigShow5];
                   if(ucDigDot5==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xef);
               break;
      case 6:  //显示第6位
           ucDigShowTemp=dig_table[ucDigShow6];
                   if(ucDigDot6==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xdf);
               break;
      case 7:  //显示第7位
           ucDigShowTemp=dig_table[ucDigShow7];
                   if(ucDigDot7==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
           }
           dig_hc595_drive(ucDigShowTemp,0xbf);
               break;
      case 8:  //显示第8位
           ucDigShowTemp=dig_table[ucDigShow8];
                   if(ucDigDot8==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0x7f);
               break;
   }

   ucDisplayDriveStep++;
   if(ucDisplayDriveStep>8)  //扫描完8个数码管后,重新从第一个开始扫描
   {
     ucDisplayDriveStep=1;
   }



}


//数码管的74HC595驱动函数
void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
{
   unsigned char i;
   unsigned char ucTempData;
   dig_hc595_sh_dr=0;
   dig_hc595_st_dr=0;

   ucTempData=ucDigStatusTemp16_09;  //先送高8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)dig_hc595_ds_dr=1;
         else dig_hc595_ds_dr=0;

         dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         dig_hc595_sh_dr=1;
         delay_short(1);

         ucTempData=ucTempData<<1;
   }

   ucTempData=ucDigStatusTemp08_01;  //再先送低8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)dig_hc595_ds_dr=1;
         else dig_hc595_ds_dr=0;

         dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         dig_hc595_sh_dr=1;
         delay_short(1);

         ucTempData=ucTempData<<1;
   }

   dig_hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
   delay_short(1);
   dig_hc595_st_dr=1;
   delay_short(1);

   dig_hc595_sh_dr=0;    //拉低,抗干扰就增强
   dig_hc595_st_dr=0;
   dig_hc595_ds_dr=0;

}


//LED灯的74HC595驱动函数
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
{
   unsigned char i;
   unsigned char ucTempData;
   hc595_sh_dr=0;
   hc595_st_dr=0;

   ucTempData=ucLedStatusTemp16_09;  //先送高8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;

         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         hc595_sh_dr=1;
         delay_short(1);

         ucTempData=ucTempData<<1;
   }

   ucTempData=ucLedStatusTemp08_01;  //再先送低8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;

         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         hc595_sh_dr=1;
         delay_short(1);

         ucTempData=ucTempData<<1;
   }

   hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
   delay_short(1);
   hc595_st_dr=1;
   delay_short(1);

   hc595_sh_dr=0;    //拉低,抗干扰就增强
   hc595_st_dr=0;
   hc595_ds_dr=0;

}


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

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

  uiDpyTimeCnt++;  //数码管的闪烁计时器

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

  display_drive();  //数码管字模的驱动函数


  TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  TL0=0x0b;
  TR0=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++)  //内嵌循环的空指令数量
          {
             ; //一个分号相当于执行一条空语句
          }
   }
}


void initial_myself()  //第一区 初始化单片机
{

/* 注释三:
* 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
* 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
* 坚鸿51学习板的S1就是本程序中用到的一个独立按键。
*/
  key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平

  led_dr=0;  //关闭独立LED灯
  beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

  hc595_drive(0x00,0x00);  //关闭所有经过另外两个74HC595驱动的LED灯

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

  TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  TL0=0x0b;

}

void initial_peripheral() //第二区 初始化外围
{


   ucDigDot8=0;   //小数点全部不显示
   ucDigDot7=0;  
   ucDigDot6=0;
   ucDigDot5=0;  
   ucDigDot4=0;
   ucDigDot3=0;  
   ucDigDot2=0;
   ucDigDot1=0;

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

}


总结陈词:
这节讲了数码管通过一二级菜单来设置数据的综合程序,鸿哥的人机界面程序框架基本上都涉及到了,为了继续加深熟悉鸿哥的“一二级菜单显示理论”,下一节会继续讲一个常用的数码管项目小程序,这个项目小程序鸿哥是怎么写的?欲知详情,请听下回分解-----数码管中的倒计时程序。
(未完待续,下节更精彩,不要走开哦)

作者: jianhong_wu    时间: 2014-3-9 20:21
第三十二节:数码管中的倒计时程序。

开场白:
   上一节讲了一二级菜单的综合程序,这一节要教会大家三个知识点:
第一个:通过本程序,继续加深理解按键与数码管的关联方法。
第二个:复习一下我在第五节教给大家的时间校正法。
第三个:继续加深熟悉鸿哥首次提出的“一二级菜单显示理论”:凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。


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

(1)硬件平台:基于坚鸿51单片机学习板。启动和暂停键对应S1键,复位键对应S5键。

(2)实现功能:按下启动暂停按键时,倒计时开始工作,再按一次启动暂停按键时,则暂停倒计时。在任何时候,按下复位按键,倒计时将暂停工作,并且恢复倒计时当前默认值99。
     
(3)源代码讲解如下:

#include "REG52.H"


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

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


#define const_dpy_time_half  200  //数码管闪烁时间的半值
#define const_dpy_time_all   400  //数码管闪烁时间的全值 一定要比const_dpy_time_half 大

/* 注释一:
* 如何知道1秒钟需要多少个定时中断?
* 这个需要编写一段小程序测试,得到测试的结果后再按比例修正。
* 步骤:
* 第一步:在程序代码上先写入1秒钟大概需要200个定时中断。
* 第二步:把程序烧录进单片机后,上电开始测试,手上同步打开手机里的秒表。
*         如果单片机倒计时跑完了99秒,而手机上的秒表才走了45秒。
* 第三步:那么最终得出1秒钟需要的定时中断次数是:const_1s=(200*99)/45=440
*/


#define const_1s  440   //大概一秒钟所需要的定时中断次数

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


sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键

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

sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
sbit led_dr=P3^5;  //作为中途暂停指示灯 亮的时候表示中途暂停


sbit dig_hc595_sh_dr=P2^0;     //数码管的74HC595程序
sbit dig_hc595_st_dr=P2^1;  
sbit dig_hc595_ds_dr=P2^2;  

sbit hc595_sh_dr=P2^3;    //LED灯的74HC595程序
sbit hc595_st_dr=P2^4;  
sbit hc595_ds_dr=P2^5;  

unsigned char ucKeySec=0;   //被触发的按键编号

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

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


unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器


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 ucWd=1;  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
unsigned char ucWd1Update=1; //窗口1更新显示标志


unsigned char ucCountDown=99;  //倒计时的当前值
unsigned char ucStartFlag=0;  //暂停与启动的标志位
unsigned int  uiTimeCnt=0;  //倒计时的时间计时器

unsigned char ucTemp1=0;  //中间过渡变量
unsigned char ucTemp2=0;  //中间过渡变量
unsigned char ucTemp3=0;  //中间过渡变量
unsigned char ucTemp4=0;  //中间过渡变量
unsigned char ucTemp5=0;  //中间过渡变量
unsigned char ucTemp6=0;  //中间过渡变量
unsigned char ucTemp7=0;  //中间过渡变量
unsigned char ucTemp8=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)  
   {
       key_service(); //按键服务的应用程序
       display_service(); //显示的窗口菜单服务程序
   }

}


/* 注释二:
*鸿哥首次提出的"一二级菜单显示理论":
*凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,
*每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。
*局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,
*表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。
*/


void display_service() //显示的窗口菜单服务程序
{



  //由于本程序只有一个窗口,读者在做实际项目的时候,可以省略switch(ucWd)
   switch(ucWd)  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
   {
       case 1:   //显示窗口1的数据
            if(ucWd1Update==1)  //窗口1要全部更新显示
                        {
               ucWd1Update=0;  //及时清零标志,避免一直进来扫描

               ucTemp8=10;  //显示空
               ucTemp7=10;  //显示空
               ucTemp6=10;  //显示空
               ucTemp5=10;  //显示空
               ucTemp4=10;  //显示空
               ucTemp3=10;  //显示空

               ucTemp2=ucCountDown/10;  //倒计时的当前值
               ucTemp1=ucCountDown%10;


               ucDigShow8=ucTemp8;  
               ucDigShow7=ucTemp7;  
               ucDigShow6=ucTemp6;  
               ucDigShow5=ucTemp5;
               ucDigShow4=ucTemp4;  
               ucDigShow3=ucTemp3;


                           if(ucCountDown<10)
                           {
                              ucDigShow2=10;
                           }
                           else
                           {
                              ucDigShow2=ucTemp2;
                           }
                           ucDigShow1=ucTemp1;

                       

            }
            break;
   
     }
   


}


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

  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号键
     }
  }

}


void key_service() //按键服务的应用程序
{
  switch(ucKeySec) //按键服务状态切换
  {
    case 1:// 启动和暂停按键 对应朱兆祺学习板的S1键

       
         //由于本程序只有一个窗口,读者在做实际项目的时候,可以省略switch(ucWd)
          switch(ucWd)  //在不同的窗口下,设置不同的参数
          {
              case 1:
                   if(ucStartFlag==0)  //如果原来处于暂停的状态,则启动
                                   {
                      ucStartFlag=1; //启动
                                   }
                                   else     //如果原来处于启动的状态,则暂停
                                   {
                                      ucStartFlag=0;  //暂停
                                   }
                   break;
           
          }     
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
   
    case 2:// 复位按键 对应朱兆祺学习板的S5键

         //由于本程序只有一个窗口,读者在做实际项目的时候,可以省略switch(ucWd)
          switch(ucWd)  //在不同的窗口下,设置不同的参数
          {
              case 1:
                                   ucStartFlag=0;  //暂停
                   ucCountDown=99;  //恢复倒计时的默认值99
                   uiTimeCnt=0;  //倒计时的时间计时器清零
                                   ucWd1Update=1; //窗口1更新显示标志  只要ucCountDown变化了,就要更新显示一次
                   break;
         
          }  
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;  

  }               
}


void display_drive()  
{
   //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
   switch(ucDisplayDriveStep)
   {
      case 1:  //显示第1位
           ucDigShowTemp=dig_table[ucDigShow1];
                   if(ucDigDot1==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xfe);
               break;
      case 2:  //显示第2位
           ucDigShowTemp=dig_table[ucDigShow2];
                   if(ucDigDot2==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xfd);
               break;
      case 3:  //显示第3位
           ucDigShowTemp=dig_table[ucDigShow3];
                   if(ucDigDot3==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xfb);
               break;
      case 4:  //显示第4位
           ucDigShowTemp=dig_table[ucDigShow4];
                   if(ucDigDot4==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xf7);
               break;
      case 5:  //显示第5位
           ucDigShowTemp=dig_table[ucDigShow5];
                   if(ucDigDot5==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xef);
               break;
      case 6:  //显示第6位
           ucDigShowTemp=dig_table[ucDigShow6];
                   if(ucDigDot6==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xdf);
               break;
      case 7:  //显示第7位
           ucDigShowTemp=dig_table[ucDigShow7];
                   if(ucDigDot7==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
           }
           dig_hc595_drive(ucDigShowTemp,0xbf);
               break;
      case 8:  //显示第8位
           ucDigShowTemp=dig_table[ucDigShow8];
                   if(ucDigDot8==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0x7f);
               break;
   }

   ucDisplayDriveStep++;
   if(ucDisplayDriveStep>8)  //扫描完8个数码管后,重新从第一个开始扫描
   {
     ucDisplayDriveStep=1;
   }



}


//数码管的74HC595驱动函数
void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
{
   unsigned char i;
   unsigned char ucTempData;
   dig_hc595_sh_dr=0;
   dig_hc595_st_dr=0;

   ucTempData=ucDigStatusTemp16_09;  //先送高8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)dig_hc595_ds_dr=1;
         else dig_hc595_ds_dr=0;

         dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         dig_hc595_sh_dr=1;
         delay_short(1);

         ucTempData=ucTempData<<1;
   }

   ucTempData=ucDigStatusTemp08_01;  //再先送低8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)dig_hc595_ds_dr=1;
         else dig_hc595_ds_dr=0;

         dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         dig_hc595_sh_dr=1;
         delay_short(1);

         ucTempData=ucTempData<<1;
   }

   dig_hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
   delay_short(1);
   dig_hc595_st_dr=1;
   delay_short(1);

   dig_hc595_sh_dr=0;    //拉低,抗干扰就增强
   dig_hc595_st_dr=0;
   dig_hc595_ds_dr=0;

}


//LED灯的74HC595驱动函数
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
{
   unsigned char i;
   unsigned char ucTempData;
   hc595_sh_dr=0;
   hc595_st_dr=0;

   ucTempData=ucLedStatusTemp16_09;  //先送高8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;

         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         hc595_sh_dr=1;
         delay_short(1);

         ucTempData=ucTempData<<1;
   }

   ucTempData=ucLedStatusTemp08_01;  //再先送低8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;

         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         hc595_sh_dr=1;
         delay_short(1);

         ucTempData=ucTempData<<1;
   }

   hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
   delay_short(1);
   hc595_st_dr=1;
   delay_short(1);

   hc595_sh_dr=0;    //拉低,抗干扰就增强
   hc595_st_dr=0;
   hc595_ds_dr=0;

}


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

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


  if(ucStartFlag==1)  //启动倒计时的计时器
  {
     uiTimeCnt++;
     if(uiTimeCnt>=const_1s)    //1秒钟的时间到
     {
            if(ucCountDown!=0) //加这个判断,就是避免在0的情况下减1
            {
               ucCountDown--;  //倒计时当前显示值减1
            }

        if(ucCountDown==0)  //倒计时结束
            {
               ucStartFlag=0;  //暂停
           uiVoiceCnt=const_voice_long; //蜂鸣器触发提醒,滴一声就停。
            }

        ucWd1Update=1; //窗口1更新显示标志
        uiTimeCnt=0;   //计时器清零,准备从新开始计时
     }
  }



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

  display_drive();  //数码管字模的驱动函数


  TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  TL0=0x0b;
  TR0=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++)  //内嵌循环的空指令数量
          {
             ; //一个分号相当于执行一条空语句
          }
   }
}


void initial_myself()  //第一区 初始化单片机
{

/* 注释三:
* 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
* 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
* 坚鸿51学习板的S1就是本程序中用到的一个独立按键。
*/
  key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平

  led_dr=0;  //关闭独立LED灯
  beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

  hc595_drive(0x00,0x00);  //关闭所有经过另外两个74HC595驱动的LED灯

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

  TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  TL0=0x0b;

}

void initial_peripheral() //第二区 初始化外围
{


   ucDigDot8=0;   //小数点全部不显示
   ucDigDot7=0;  
   ucDigDot6=0;
   ucDigDot5=0;  
   ucDigDot4=0;
   ucDigDot3=0;  
   ucDigDot2=0;
   ucDigDot1=0;

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

}


总结陈词:
这节讲了数码管中的倒计时程序。如果要在此程序上多增加两个按键,用来控制数码管倒计时的速度档位,并且需要在数码管中闪烁显示被设置的速度档位,该怎么编写这个程序?欲知详情,请听下回分解-----能设置速度档位的数码管倒计时程序。

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

作者: jianhong_wu    时间: 2014-3-9 20:22
第三十三节:能设置速度档位的数码管倒计时程序。

开场白:
   上一节讲了数码管中的倒计时程序。这节要在此程序上多增加两个按键,用来控制数码管倒计时的速度档位,并且需要在数码管中闪烁显示被设置的速度档位。这一节要教会大家三个知识点:
第一个:把一个按键的短按与长按复合应用在项目中的程序结构。
第二个:通过本程序,继续加深理解按键与数码管的关联方法。
第三个:继续加深熟悉鸿哥首次提出的“一二级菜单显示理论”:凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。

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

(1)硬件平台:基于坚鸿51单片机学习板。启动和暂停键对应S1键,复位键对应S5键。加键对应S9键,减键对应S13键。

(2)实现功能:按下启动暂停按键时,倒计时开始工作,再按一次启动暂停按键时,则暂停倒计时。在任何时候,按下复位按键,倒计时将暂停工作,并且恢复倒计时当前默认值99。如果长按复位按键,在数码管会切换到第2个闪烁窗口,用来设置速度档位,修改完速度档位后,再一次按下复位按键,或者直接按启动暂停按键,会切换回窗口1显示倒计时的当前数据。
     
(3)源代码讲解如下:

#include "REG52.H"


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

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

#define const_key_long_time 200  //长按复位按键的时间


#define const_dpy_time_half  200  //数码管闪烁时间的半值
#define const_dpy_time_all   400  //数码管闪烁时间的全值 一定要比const_dpy_time_half 大


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


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 led_dr=P3^5;  //作为中途暂停指示灯 亮的时候表示中途暂停


sbit dig_hc595_sh_dr=P2^0;     //数码管的74HC595程序
sbit dig_hc595_st_dr=P2^1;  
sbit dig_hc595_ds_dr=P2^2;  

sbit hc595_sh_dr=P2^3;    //LED灯的74HC595程序
sbit hc595_st_dr=P2^4;  
sbit hc595_ds_dr=P2^5;  

unsigned char ucKeySec=0;   //被触发的按键编号

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

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

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

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




unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器


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 ucWd=1;  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
unsigned char ucWd1Update=1; //窗口1更新显示标志
unsigned char ucWd2Update=1; //窗口2更新显示标志

unsigned char ucCountDown=99;  //倒计时的当前值
unsigned char ucStartFlag=0;  //暂停与启动的标志位
unsigned int  uiTimeCnt=0;  //倒计时的时间计时器

unsigned int  uiDpyTimeCnt=0;  //数码管的闪烁计时器,放在定时中断里不断累加

unsigned int  uiSetData1=50;  //速度档位
unsigned int  uiSpeedCnt=400;  //影响速度变量,它跟速度档位uiSetData1有关联

unsigned char ucTemp1=0;  //中间过渡变量
unsigned char ucTemp2=0;  //中间过渡变量
unsigned char ucTemp3=0;  //中间过渡变量
unsigned char ucTemp4=0;  //中间过渡变量
unsigned char ucTemp5=0;  //中间过渡变量
unsigned char ucTemp6=0;  //中间过渡变量
unsigned char ucTemp7=0;  //中间过渡变量
unsigned char ucTemp8=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)  
   {
       key_service(); //按键服务的应用程序
       display_service(); //显示的窗口菜单服务程序
   }

}



/* 注释一:
*鸿哥首次提出的"一二级菜单显示理论":
*凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,
*每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。
*局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,
*表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。
*/


void display_service() //显示的窗口菜单服务程序
{


   switch(ucWd)  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
   {
       case 1:   //显示窗口1的数据
            if(ucWd1Update==1)  //窗口1要全部更新显示
                        {
               ucWd1Update=0;  //及时清零标志,避免一直进来扫描

               ucTemp8=10;  //显示空
               ucTemp7=10;  //显示空
               ucTemp6=10;  //显示空
               ucTemp5=10;  //显示空
               ucTemp4=10;  //显示空
               ucTemp3=10;  //显示空

               ucTemp2=ucCountDown/10;  //倒计时的当前值
               ucTemp1=ucCountDown%10;


               ucDigShow8=ucTemp8;  
               ucDigShow7=ucTemp7;  
               ucDigShow6=ucTemp6;  
               ucDigShow5=ucTemp5;
               ucDigShow4=ucTemp4;  
               ucDigShow3=ucTemp3;


                           if(ucCountDown<10)
                           {
                              ucDigShow2=10;
                           }
                           else
                           {
                              ucDigShow2=ucTemp2;
                           }
                           ucDigShow1=ucTemp1;

                       

            }
            break;
       case 2:   //显示窗口2的数据
            if(ucWd2Update==1)  //窗口2要全部更新显示
            {
               ucWd2Update=0;  //及时清零标志,避免一直进来扫描

               ucTemp8=10;  //显示空
               ucTemp7=10;  //显示空
               ucTemp6=10;  //显示空
               ucTemp5=10;  //显示空
               ucTemp4=10;  //显示空
               ucTemp3=10;  //显示空

               ucTemp2=uiSetData1/10;  //倒计时的速度档位
               ucTemp1=uiSetData1%10;


               ucDigShow8=ucTemp8;  
               ucDigShow7=ucTemp7;  
               ucDigShow6=ucTemp6;  
               ucDigShow5=ucTemp5;
               ucDigShow4=ucTemp4;  
               ucDigShow3=ucTemp3;


                           if(uiSetData1<10)
                           {
                              ucDigShow2=10;
                           }
                           else
                           {
                              ucDigShow2=ucTemp2;
                           }
                           ucDigShow1=ucTemp1;

                        

            }

            //数码管闪烁
            if(uiDpyTimeCnt==const_dpy_time_half)
            {
               if(uiSetData1<10)        //数码管显示内容
               {
                    ucDigShow2=10;
               }
               else
               {
                    ucDigShow2=ucTemp2;
               }
               ucDigShow1=ucTemp1;
            }
            else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
            {                       
               uiDpyTimeCnt=0;   //及时把闪烁记时器清零                          

               ucDigShow2=10;   //数码管显示空,什么都不显示
               ucDigShow1=10;

            }

            break;

   
     }
   


}


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

  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号键
     }
  }
  else if(uiKeyTimeCnt2<const_key_long_time)   //长按复位按键
  {
      uiKeyTimeCnt2++;
          if(uiKeyTimeCnt2==const_key_long_time)
          {
             ucKeySec=17;    //触发17号长按复位键
          }
  }

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() //按键服务的应用程序
{
  switch(ucKeySec) //按键服务状态切换
  {
    case 1:// 启动和暂停按键 对应朱兆祺学习板的S1键

          switch(ucWd)  //在不同的窗口下,设置不同的参数
          {
              case 1:
                   if(ucStartFlag==0)  //如果原来处于暂停的状态,则启动
                                   {
                      ucStartFlag=1; //启动
                                   }
                                   else     //如果原来处于启动的状态,则暂停
                                   {
                                      ucStartFlag=0;  //暂停
                                   }
                   break;


           
          }   

          ucWd=1;  //不管在哪个窗口,强行切换回窗口1
                  ucWd1Update=1; //窗口1更新显示标志

          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
   
    case 2:// 复位按键 对应朱兆祺学习板的S5键

          switch(ucWd)  //在不同的窗口下,设置不同的参数
          {
              case 1:    //在窗口1中
                                   ucStartFlag=0;  //暂停
                   ucCountDown=99;  //恢复倒计时的默认值99
                   uiTimeCnt=0;  //倒计时的时间计时器清零
                                   ucWd1Update=1; //窗口1更新显示标志  只要ucCountDown变化了,就要更新显示一次
                   break;
              case 2:    //在窗口2中
                   ucWd=1;  //切换回窗口1
                                   ucWd1Update=1; //窗口1更新显示标志
                   break;
          }  
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;  

    case 3:// 加按键 对应朱兆祺学习板的S9键


          switch(ucWd)  //在不同的窗口下,设置不同的参数
          {
              case 2:   //在窗口2中
                   uiSetData1++;       //速度档位累加,档位越大,速度越快.
                                   if(uiSetData1>99)
                                   {
                                      uiSetData1=99;
                                   }
                   uiSpeedCnt=440-(uiSetData1*2);  //速度档位越大,累计中断数uiSpeedCnt越小,从而倒计时的时间越快

                                   ucWd2Update=1; //窗口2更新显示
                   break;
          }     
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;   
   
    case 4:// 减按键 对应朱兆祺学习板的S13键
          switch(ucWd)  //在不同的窗口下,设置不同的参数
          {
              case 2:   //在窗口2中
                               if(uiSetData1>0)  //加此条件判断,避免0减1
                                   {
                      uiSetData1--;       //速度档位累减,档位越小,速度越慢.
                                   }

                   uiSpeedCnt=440-(uiSetData1*2);  //速度档位越小,累计中断数uiSpeedCnt越大,从而倒计时的时间越慢

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

    case 17:// 长按复位按键 对应朱兆祺学习板的S5键

          switch(ucWd)  //在不同的窗口下,设置不同的参数
          {
              case 1:  //窗口1下
                   ucWd=2;  //切换到闪烁窗口2  进行设置速度档位显示
                                   ucWd2Update=1; //窗口2更新显示标志
                   break;
         
          }  
          uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
          break;

  }               
}


void display_drive()  
{
   //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
   switch(ucDisplayDriveStep)
   {
      case 1:  //显示第1位
           ucDigShowTemp=dig_table[ucDigShow1];
                   if(ucDigDot1==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xfe);
               break;
      case 2:  //显示第2位
           ucDigShowTemp=dig_table[ucDigShow2];
                   if(ucDigDot2==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xfd);
               break;
      case 3:  //显示第3位
           ucDigShowTemp=dig_table[ucDigShow3];
                   if(ucDigDot3==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xfb);
               break;
      case 4:  //显示第4位
           ucDigShowTemp=dig_table[ucDigShow4];
                   if(ucDigDot4==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xf7);
               break;
      case 5:  //显示第5位
           ucDigShowTemp=dig_table[ucDigShow5];
                   if(ucDigDot5==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xef);
               break;
      case 6:  //显示第6位
           ucDigShowTemp=dig_table[ucDigShow6];
                   if(ucDigDot6==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0xdf);
               break;
      case 7:  //显示第7位
           ucDigShowTemp=dig_table[ucDigShow7];
                   if(ucDigDot7==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
           }
           dig_hc595_drive(ucDigShowTemp,0xbf);
               break;
      case 8:  //显示第8位
           ucDigShowTemp=dig_table[ucDigShow8];
                   if(ucDigDot8==1)
                   {
                      ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
                   }
           dig_hc595_drive(ucDigShowTemp,0x7f);
               break;
   }

   ucDisplayDriveStep++;
   if(ucDisplayDriveStep>8)  //扫描完8个数码管后,重新从第一个开始扫描
   {
     ucDisplayDriveStep=1;
   }



}


//数码管的74HC595驱动函数
void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
{
   unsigned char i;
   unsigned char ucTempData;
   dig_hc595_sh_dr=0;
   dig_hc595_st_dr=0;

   ucTempData=ucDigStatusTemp16_09;  //先送高8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)dig_hc595_ds_dr=1;
         else dig_hc595_ds_dr=0;

         dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         dig_hc595_sh_dr=1;
         delay_short(1);

         ucTempData=ucTempData<<1;
   }

   ucTempData=ucDigStatusTemp08_01;  //再先送低8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)dig_hc595_ds_dr=1;
         else dig_hc595_ds_dr=0;

         dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         dig_hc595_sh_dr=1;
         delay_short(1);

         ucTempData=ucTempData<<1;
   }

   dig_hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
   delay_short(1);
   dig_hc595_st_dr=1;
   delay_short(1);

   dig_hc595_sh_dr=0;    //拉低,抗干扰就增强
   dig_hc595_st_dr=0;
   dig_hc595_ds_dr=0;

}


//LED灯的74HC595驱动函数
void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
{
   unsigned char i;
   unsigned char ucTempData;
   hc595_sh_dr=0;
   hc595_st_dr=0;

   ucTempData=ucLedStatusTemp16_09;  //先送高8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;

         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         hc595_sh_dr=1;
         delay_short(1);

         ucTempData=ucTempData<<1;
   }

   ucTempData=ucLedStatusTemp08_01;  //再先送低8位
   for(i=0;i<8;i++)
   {
         if(ucTempData>=0x80)hc595_ds_dr=1;
         else hc595_ds_dr=0;

         hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
         delay_short(1);
         hc595_sh_dr=1;
         delay_short(1);

         ucTempData=ucTempData<<1;
   }

   hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
   delay_short(1);
   hc595_st_dr=1;
   delay_short(1);

   hc595_sh_dr=0;    //拉低,抗干扰就增强
   hc595_st_dr=0;
   hc595_ds_dr=0;

}


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

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


  if(ucStartFlag==1)  //启动倒计时的计时器
  {
     uiTimeCnt++;
     if(uiTimeCnt>=uiSpeedCnt)    //时间到
     {
            if(ucCountDown!=0) //加这个判断,就是避免在0的情况下减1
            {
               ucCountDown--;  //倒计时当前显示值减1
            }

        if(ucCountDown==0)  //倒计时结束
            {
               ucStartFlag=0;  //暂停
           uiVoiceCnt=const_voice_long; //蜂鸣器触发提醒,滴一声就停。
            }

        ucWd1Update=1; //窗口1更新显示标志
        uiTimeCnt=0;   //计时器清零,准备从新开始计时
     }
  }


  uiDpyTimeCnt++;  //数码管的闪烁计时器


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

  display_drive();  //数码管字模的驱动函数


  TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  TL0=0x0b;
  TR0=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++)  //内嵌循环的空指令数量
          {
             ; //一个分号相当于执行一条空语句
          }
   }
}


void initial_myself()  //第一区 初始化单片机
{

/* 注释三:
* 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
* 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
* 坚鸿51学习板的S1就是本程序中用到的一个独立按键。
*/
  key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平

  led_dr=0;  //关闭独立LED灯
  beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

  hc595_drive(0x00,0x00);  //关闭所有经过另外两个74HC595驱动的LED灯

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

  TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  TL0=0x0b;

}

void initial_peripheral() //第二区 初始化外围
{


   ucDigDot8=0;   //小数点全部不显示
   ucDigDot7=0;  
   ucDigDot6=0;
   ucDigDot5=0;  
   ucDigDot4=0;
   ucDigDot3=0;  
   ucDigDot2=0;
   ucDigDot1=0;

   uiSpeedCnt=440-(uiSetData1*2);  //速度档位越大,累计中断数uiSpeedCnt越小,从而倒计时的时间越快

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

}


总结陈词:
这节讲了能设置速度档位的数码管倒计时程序。现在很多人用iphone4S的手机,这个手机每次开机显示的时候,都要通过4个密码开锁,如果我们要用4位数码管来实现这个密码锁功能,该怎么编写这个程序?欲知详情,请听下回分解-----在数码管中实现iphone4S开机密码锁的程序。

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

作者: jianhong_wu    时间: 2014-3-15 10:11
第三十四节:在数码管中实现iphone4S开机密码锁的程序。

开场白:
    这一节要教会大家四个知识点:
第一个:类似手机上10秒钟内无按键操作将自动进入锁屏的程序。
第二个:如何用一个数组来接收按键的一串数字输入。
第三个:矩阵键盘中,数字按键的输入,由于这部分按键的代码相似度非常高,因此把它封装在一个函数里可以非常简洁方便。
第四个:继续加深熟悉鸿哥首次提出的“一二级菜单显示理论”:凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。

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

(1)硬件平台:基于坚鸿51单片机学习板。数字1键对应S1键,数字2键对应S2键,数字3键对应S3键…. 数字9键对应S9键,数字0键对应S10键。其他的按键不用。

(2)实现功能:
本程序有3个窗口。
开机显示第1个密码登录框窗口“----”,在这个窗口下输入密码,如果密码等于”9922”表示密码正确,将会切换到第2个显示按键值的窗口。在窗口2下,按不同的按键会显示不同的按键值,如果10秒内没有按键操作,将会自动切换到第1个密码登录窗口,类似手机上的自动锁屏操作。在密码登录窗口1下,如果密码不正确,会自动清除密码的数字,继续在窗口1下显示”----”。  
窗口3是用来停留0.5秒显示全部密码的信息,然后根据密码的正确与否自动切换到对应的窗口。

3)源代码讲解如下:
  1. #include "REG52.H"


  2. #define const_no_key_push 4400   //大概10秒内无按键按下的时间
  3. #define const_0_1s  220            //大概0.5秒的时间

  4. #define const_voice_short  40   //蜂鸣器短叫的持续时间
  5. #define const_key_time  20    //按键去抖动延时的时间


  6. void initial_myself();   
  7. void initial_peripheral();
  8. void delay_short(unsigned int uiDelayShort);
  9. void delay_long(unsigned int uiDelaylong);
  10. //驱动数码管的74HC595
  11. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);  
  12. void display_drive(); //显示数码管字模的驱动函数
  13. void display_service(); //显示的窗口菜单服务程序
  14. //驱动LED的74HC595
  15. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
  16. void T0_time();  //定时中断函数

  17. void number_key_input(unsigned char ucWhichKey);  //由于数字按键的代码相似度高,因此封装在这个函数里
  18. void key_service(); //按键服务的应用程序
  19. void key_scan();//按键扫描函数 放在定时中断里

  20. sbit key_sr1=P0^0; //第一行输入
  21. sbit key_sr2=P0^1; //第二行输入
  22. sbit key_sr3=P0^2; //第三行输入
  23. sbit key_sr4=P0^3; //第四行输入

  24. sbit key_dr1=P0^4; //第一列输出
  25. sbit key_dr2=P0^5; //第二列输出
  26. sbit key_dr3=P0^6; //第三列输出
  27. sbit key_dr4=P0^7; //第四列输出


  28. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
  29. sbit led_dr=P3^5;  //作为中途暂停指示灯 亮的时候表示中途暂停

  30. sbit dig_hc595_sh_dr=P2^0;     //数码管的74HC595程序
  31. sbit dig_hc595_st_dr=P2^1;  
  32. sbit dig_hc595_ds_dr=P2^2;  
  33. sbit hc595_sh_dr=P2^3;    //LED灯的74HC595程序
  34. sbit hc595_st_dr=P2^4;  
  35. sbit hc595_ds_dr=P2^5;  

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

  37. unsigned int  uiKeyTimeCnt=0; //按键去抖动延时计数器
  38. unsigned char ucKeyLock=0; //按键触发后自锁的变量标志

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


  40. unsigned char ucKeySec=0;   //被触发的按键编号
  41. unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器

  42. unsigned char ucDigShow8;  //第8位数码管要显示的内容
  43. unsigned char ucDigShow7;  //第7位数码管要显示的内容
  44. unsigned char ucDigShow6;  //第6位数码管要显示的内容
  45. unsigned char ucDigShow5;  //第5位数码管要显示的内容
  46. unsigned char ucDigShow4;  //第4位数码管要显示的内容
  47. unsigned char ucDigShow3;  //第3位数码管要显示的内容
  48. unsigned char ucDigShow2;  //第2位数码管要显示的内容
  49. unsigned char ucDigShow1;  //第1位数码管要显示的内容

  50. unsigned char ucDigDot8;  //数码管8的小数点是否显示的标志
  51. unsigned char ucDigDot7;  //数码管7的小数点是否显示的标志
  52. unsigned char ucDigDot6;  //数码管6的小数点是否显示的标志
  53. unsigned char ucDigDot5;  //数码管5的小数点是否显示的标志
  54. unsigned char ucDigDot4;  //数码管4的小数点是否显示的标志
  55. unsigned char ucDigDot3;  //数码管3的小数点是否显示的标志
  56. unsigned char ucDigDot2;  //数码管2的小数点是否显示的标志
  57. unsigned char ucDigDot1;  //数码管1的小数点是否显示的标志
  58. unsigned char ucDigShowTemp=0; //临时中间变量
  59. unsigned char ucDisplayDriveStep=1;  //动态扫描数码管的步骤变量

  60. unsigned char ucWd1Update=1; //窗口1更新显示标志
  61. unsigned char ucWd2Update=0; //窗口2更新显示标志
  62. unsigned char ucWd3Update=0; //窗口3更新显示标志
  63. unsigned char ucWd=1;  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。


  64. unsigned char ucInputPassword[4];  //在第1个窗口下,显示输入的4个密码
  65. unsigned char ucPasswordCnt=0; //记录当前已经输入到哪一位密码了
  66. unsigned char ucKeyNumber=1;  //在第2个窗口下,显示当前被按下的按键

  67. unsigned int  uiNoKeyPushTimer=const_no_key_push;  //10秒内无按键按下的计时器
  68. unsigned int  uiPasswordTimer=const_0_1s;  //显示0.5秒钟全部密码的计时器,让窗口3停留显示0.5秒钟之后自动消失

  69. unsigned char ucTemp1=0;  //中间过渡变量
  70. unsigned char ucTemp2=0;  //中间过渡变量
  71. unsigned char ucTemp3=0;  //中间过渡变量
  72. unsigned char ucTemp4=0;  //中间过渡变量

  73. //根据原理图得出的共阴数码管字模表
  74. code unsigned char dig_table[]=
  75. {
  76. 0x3f,  //0       序号0
  77. 0x06,  //1       序号1
  78. 0x5b,  //2       序号2
  79. 0x4f,  //3       序号3
  80. 0x66,  //4       序号4
  81. 0x6d,  //5       序号5
  82. 0x7d,  //6       序号6
  83. 0x07,  //7       序号7
  84. 0x7f,  //8       序号8
  85. 0x6f,  //9       序号9
  86. 0x00,  //无      序号10
  87. 0x40,  //-       序号11
  88. 0x73,  //P       序号12
  89. };
  90. void main()
  91.   {
  92.    initial_myself();  
  93.    delay_long(100);   
  94.    initial_peripheral();
  95.    while(1)  
  96.    {
  97.      key_service(); //按键服务的应用程序
  98.          display_service(); //显示的窗口菜单服务程序
  99.    }
  100. }
  101. /* 注释一:
  102. *鸿哥首次提出的"一二级菜单显示理论":
  103. *凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,
  104. *每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。
  105. *局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,
  106. *表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。
  107. */

  108. void display_service() //显示的窗口菜单服务程序
  109. {

  110.    switch(ucWd)  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
  111.    {
  112.        case 1:   //显示输入密码的登录框
  113.             if(ucWd1Update==1)  //窗口1要全部更新显示
  114.             {
  115.                ucWd1Update=0;  //及时清零标志,避免一直进来扫描

  116.                ucDigShow8=10;  //第8位数码管显示无
  117.                ucDigShow7=10;  //第7位数码管显示无
  118.                ucDigShow6=10;  //第6位数码管显示无
  119.                ucDigShow5=10;  //第5位数码管显示无

  120.                ucDigShow4=ucInputPassword[0];  //第4位数码管显示输入的密码
  121.                ucDigShow3=ucInputPassword[1];  //第3位数码管显示输入的密码
  122.                ucDigShow2=ucInputPassword[2];  //第2位数码管显示输入的密码
  123.                ucDigShow1=ucInputPassword[3];  //第1位数码管显示输入的密码

  124.             }
  125.             break;
  126.         case 2:  //显示被按下的键值
  127.             if(ucWd2Update==1)  //窗口2要全部更新显示
  128.             {
  129.                ucWd2Update=0;  //及时清零标志,避免一直进来扫描

  130.                ucDigShow8=10;  //第8位数码管显示无
  131.                ucDigShow7=10;  //第7位数码管显示无
  132.                ucDigShow6=10;  //第6位数码管显示无
  133.                ucDigShow5=10;  //第5位数码管显示无

  134.                ucDigShow4=10;  //第4位数码管显示无
  135.                ucDigShow3=10;  //第3位数码管显示无
  136.                ucDigShow2=10;  //第2位数码管显示无
  137.                ucDigShow1=ucKeyNumber; //第1位数码管显示被按下的键值
  138.             }
  139.             break;
  140.        case 3:   //当输入完4个密码后,显示1秒钟的密码登录框,
  141.             if(ucWd3Update==1)  //窗口3要全部更新显示
  142.             {
  143.                ucWd3Update=0;  //及时清零标志,避免一直进来扫描

  144.                ucDigShow8=10;  //第8位数码管显示无
  145.                ucDigShow7=10;  //第7位数码管显示无
  146.                ucDigShow6=10;  //第6位数码管显示无
  147.                ucDigShow5=10;  //第5位数码管显示无

  148.                ucDigShow4=ucInputPassword[0];  //第4位数码管显示输入的密码
  149.                ucDigShow3=ucInputPassword[1];  //第3位数码管显示输入的密码
  150.                ucDigShow2=ucInputPassword[2];  //第2位数码管显示输入的密码
  151.                ucDigShow1=ucInputPassword[3];  //第1位数码管显示输入的密码

  152.             }
  153.             break;     
  154.     }
  155.    

  156. }
  157. void key_scan()//按键扫描函数 放在定时中断里
  158. {  


  159.   switch(ucKeyStep)
  160.   {
  161.      case 1:   //按键扫描输出第ucRowRecord列低电平
  162.               if(ucRowRecord==1)  //第一列输出低电平
  163.                   {
  164.              key_dr1=0;      
  165.              key_dr2=1;
  166.              key_dr3=1;   
  167.              key_dr4=1;
  168.                   }
  169.               else if(ucRowRecord==2)  //第二列输出低电平
  170.                   {
  171.              key_dr1=1;      
  172.              key_dr2=0;
  173.              key_dr3=1;   
  174.              key_dr4=1;
  175.                   }
  176.               else if(ucRowRecord==3)  //第三列输出低电平
  177.                   {
  178.              key_dr1=1;      
  179.              key_dr2=1;
  180.              key_dr3=0;   
  181.              key_dr4=1;
  182.                   }
  183.               else   //第四列输出低电平
  184.                   {
  185.              key_dr1=1;      
  186.              key_dr2=1;
  187.              key_dr3=1;   
  188.              key_dr4=0;
  189.                   }

  190.           uiKeyTimeCnt=0;  //延时计数器清零
  191.           ucKeyStep++;     //切换到下一个运行步骤
  192.               break;

  193.      case 2:     //此处的小延时用来等待刚才列输出信号稳定,再判断输入信号。不是去抖动延时。
  194.           uiKeyTimeCnt++;
  195.                   if(uiKeyTimeCnt>1)
  196.                   {
  197.                      uiKeyTimeCnt=0;
  198.              ucKeyStep++;     //切换到下一个运行步骤
  199.                   }
  200.               break;

  201.      case 3:
  202.           if(key_sr1==1&&key_sr2==1&&key_sr3==1&&key_sr4==1)
  203.           {  
  204.              ucKeyStep=1;  //如果没有按键按下,返回到第一个运行步骤重新开始扫描
  205.              ucKeyLock=0;  //按键自锁标志清零
  206.              uiKeyTimeCnt=0; //按键去抖动延时计数器清零,此行非常巧妙     
  207.    
  208.                          ucRowRecord++;  //输出下一列
  209.                          if(ucRowRecord>4)  
  210.                          {
  211.                             ucRowRecord=1; //依次输出完四列之后,继续从第一列开始输出低电平
  212.                          }

  213.           }
  214.                   else if(ucKeyLock==0)  //有按键按下,且是第一次触发
  215.                   {
  216.                      if(key_sr1==0&&key_sr2==1&&key_sr3==1&&key_sr4==1)
  217.                          {
  218.                             uiKeyTimeCnt++;  //去抖动延时计数器
  219.                                 if(uiKeyTimeCnt>const_key_time)
  220.                                 {
  221.                                    uiKeyTimeCnt=0;
  222.                                    ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零

  223.                        if(ucRowRecord==1)  //第一列输出低电平
  224.                            {
  225.                                       ucKeySec=1;  //触发1号键 对应朱兆祺学习板的S1键
  226.                            }
  227.                        else if(ucRowRecord==2)  //第二列输出低电平
  228.                            {
  229.                                       ucKeySec=2;  //触发2号键 对应朱兆祺学习板的S2键
  230.                            }
  231.                        else if(ucRowRecord==3)  //第三列输出低电平
  232.                            {
  233.                                       ucKeySec=3;  //触发3号键 对应朱兆祺学习板的S3键
  234.                            }
  235.                        else   //第四列输出低电平
  236.                            {
  237.                                       ucKeySec=4;  //触发4号键 对应朱兆祺学习板的S4键
  238.                            }

  239.                                 }
  240.                         
  241.                          }
  242.                      else if(key_sr1==1&&key_sr2==0&&key_sr3==1&&key_sr4==1)
  243.                          {
  244.                             uiKeyTimeCnt++;  //去抖动延时计数器
  245.                                 if(uiKeyTimeCnt>const_key_time)
  246.                                 {
  247.                                    uiKeyTimeCnt=0;
  248.                                    ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
  249.                        if(ucRowRecord==1)  //第一列输出低电平
  250.                            {
  251.                                       ucKeySec=5;  //触发5号键 对应朱兆祺学习板的S5键
  252.                            }
  253.                        else if(ucRowRecord==2)  //第二列输出低电平
  254.                            {
  255.                                       ucKeySec=6;  //触发6号键 对应朱兆祺学习板的S6键
  256.                            }
  257.                        else if(ucRowRecord==3)  //第三列输出低电平
  258.                            {
  259.                                       ucKeySec=7;  //触发7号键 对应朱兆祺学习板的S7键
  260.                            }
  261.                        else   //第四列输出低电平
  262.                            {
  263.                                       ucKeySec=8;  //触发8号键 对应朱兆祺学习板的S8键
  264.                            }
  265.                                 }
  266.                         
  267.                          }
  268.                      else if(key_sr1==1&&key_sr2==1&&key_sr3==0&&key_sr4==1)
  269.                          {
  270.                             uiKeyTimeCnt++;  //去抖动延时计数器
  271.                                 if(uiKeyTimeCnt>const_key_time)
  272.                                 {
  273.                                    uiKeyTimeCnt=0;
  274.                                    ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
  275.                        if(ucRowRecord==1)  //第一列输出低电平
  276.                            {
  277.                                       ucKeySec=9;  //触发9号键 对应朱兆祺学习板的S9键
  278.                            }
  279.                        else if(ucRowRecord==2)  //第二列输出低电平
  280.                            {
  281.                                       ucKeySec=10;  //触发10号键 对应朱兆祺学习板的S10键
  282.                            }
  283.                        else if(ucRowRecord==3)  //第三列输出低电平
  284.                            {
  285.                                       ucKeySec=11;  //触发11号键 对应朱兆祺学习板的S11键
  286.                            }
  287.                        else   //第四列输出低电平
  288.                            {
  289.                                       ucKeySec=12;  //触发12号键 对应朱兆祺学习板的S12键
  290.                            }
  291.                                 }
  292.                         
  293.                          }
  294.                      else if(key_sr1==1&&key_sr2==1&&key_sr3==1&&key_sr4==0)
  295.                          {
  296.                             uiKeyTimeCnt++;  //去抖动延时计数器
  297.                                 if(uiKeyTimeCnt>const_key_time)
  298.                                 {
  299.                                    uiKeyTimeCnt=0;
  300.                                    ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
  301.                        if(ucRowRecord==1)  //第一列输出低电平
  302.                            {
  303.                                       ucKeySec=13;  //触发13号键 对应朱兆祺学习板的S13键
  304.                            }
  305.                        else if(ucRowRecord==2)  //第二列输出低电平
  306.                            {
  307.                                       ucKeySec=14;  //触发14号键 对应朱兆祺学习板的S14键
  308.                            }
  309.                        else if(ucRowRecord==3)  //第三列输出低电平
  310.                            {
  311.                                       ucKeySec=15;  //触发15号键 对应朱兆祺学习板的S15键
  312.                            }
  313.                        else   //第四列输出低电平
  314.                            {
  315.                                       ucKeySec=16;  //触发16号键 对应朱兆祺学习板的S16键
  316.                            }
  317.                                 }
  318.                         
  319.                          }
  320.                   
  321.                   }
  322.               break;

  323.   }


  324. }


  325. void key_service() //第三区 按键服务的应用程序
  326. {
  327.   switch(ucKeySec) //按键服务状态切换
  328.   {
  329.     case 1:// 1号键 对应朱兆祺学习板的S1键
  330.           number_key_input(1);  //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
  331.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  332.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  333.           break;        
  334.     case 2:// 2号键 对应朱兆祺学习板的S2键
  335.           number_key_input(2);  //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
  336.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  337.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  338.           break;     
  339.     case 3:// 3号键 对应朱兆祺学习板的S3键
  340.           number_key_input(3);  //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
  341.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  342.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  343.           break;         
  344.     case 4:// 4号键 对应朱兆祺学习板的S4键
  345.           number_key_input(4);  //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
  346.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  347.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  348.           break;   
  349.     case 5:// 5号键 对应朱兆祺学习板的S5键
  350.           number_key_input(5);  //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
  351.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  352.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  353.           break;   
  354.     case 6:// 6号键 对应朱兆祺学习板的S6键
  355.           number_key_input(6);  //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
  356.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  357.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  358.           break;   
  359.     case 7:// 7号键 对应朱兆祺学习板的S7键
  360.           number_key_input(7);  //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
  361.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  362.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  363.           break;   
  364.     case 8:// 8号键 对应朱兆祺学习板的S8键
  365.           number_key_input(8);  //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
  366.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  367.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  368.           break;   
  369.     case 9:// 9号键 对应朱兆祺学习板的S9键
  370.           number_key_input(9);  //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
  371.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  372.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  373.           break;   
  374.     case 10:// 把这个按键专门用来输入数字0    对应朱兆祺学习板的S10键
  375.           number_key_input(0);  //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
  376.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  377.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  378.           break;   
  379.     case 11:// 11号键 对应朱兆祺学习板的S11键

  380.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  381.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  382.           break;   
  383.     case 12:// 12号键 对应朱兆祺学习板的S12键

  384.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  385.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  386.           break;   
  387.     case 13:// 13号键 对应朱兆祺学习板的S13键

  388.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  389.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  390.           break;   
  391.     case 14:// 14号键 对应朱兆祺学习板的S14键

  392.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  393.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  394.           break;   
  395.     case 15:// 15号键 对应朱兆祺学习板的S15键

  396.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  397.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  398.           break;   
  399.     case 16:// 16号键 对应朱兆祺学习板的S16键

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

  405. void number_key_input(unsigned char ucWhichKey)  //由于数字按键的代码相似度高,因此封装在这个函数里
  406. {


  407.     switch(ucWd)
  408.            {
  409.            case 1:   //在显示密码登录框的窗口下
  410.             ucInputPassword[ucPasswordCnt]=ucWhichKey;   //输入的密码值显示
  411.             ucPasswordCnt++;
  412.                         if(ucPasswordCnt>=4)
  413.                         {
  414.                             ucPasswordCnt=0;
  415.                                 ucWd=3;//切换到第3个的窗口,停留显示1秒钟全部密码
  416.                             ucWd3Update=1;  //更新显示窗口3
  417.                 uiPasswordTimer=const_0_1s;  //显示0.5秒钟全部密码的计时器,让窗口3停留显示0.5秒钟之后自动消失
  418.                         }

  419.                         ucWd1Update=1; //更新显示窗口1

  420.                         uiNoKeyPushTimer=const_no_key_push;  //10秒内无按键按下的计时器赋新值
  421.                         break;
  422.            case 2:   //在显示按键值的窗口下
  423.                 ucKeyNumber=ucWhichKey; //输入的按键数值显示
  424.                     ucWd2Update=1;  //更新显示窗口2

  425.                         uiNoKeyPushTimer=const_no_key_push;  //10秒内无按键按下的计时器赋新值
  426.                     break;
  427.     }

  428. }


  429. void display_drive()  
  430. {
  431.    //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
  432.    switch(ucDisplayDriveStep)
  433.    {
  434.       case 1:  //显示第1位
  435.            ucDigShowTemp=dig_table[ucDigShow1];
  436.                    if(ucDigDot1==1)
  437.                    {
  438.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  439.                    }
  440.            dig_hc595_drive(ucDigShowTemp,0xfe);
  441.                break;
  442.       case 2:  //显示第2位
  443.            ucDigShowTemp=dig_table[ucDigShow2];
  444.                    if(ucDigDot2==1)
  445.                    {
  446.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  447.                    }
  448.            dig_hc595_drive(ucDigShowTemp,0xfd);
  449.                break;
  450.       case 3:  //显示第3位
  451.            ucDigShowTemp=dig_table[ucDigShow3];
  452.                    if(ucDigDot3==1)
  453.                    {
  454.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  455.                    }
  456.            dig_hc595_drive(ucDigShowTemp,0xfb);
  457.                break;
  458.       case 4:  //显示第4位
  459.            ucDigShowTemp=dig_table[ucDigShow4];
  460.                    if(ucDigDot4==1)
  461.                    {
  462.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  463.                    }
  464.            dig_hc595_drive(ucDigShowTemp,0xf7);
  465.                break;
  466.       case 5:  //显示第5位
  467.            ucDigShowTemp=dig_table[ucDigShow5];
  468.                    if(ucDigDot5==1)
  469.                    {
  470.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  471.                    }
  472.            dig_hc595_drive(ucDigShowTemp,0xef);
  473.                break;
  474.       case 6:  //显示第6位
  475.            ucDigShowTemp=dig_table[ucDigShow6];
  476.                    if(ucDigDot6==1)
  477.                    {
  478.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  479.                    }
  480.            dig_hc595_drive(ucDigShowTemp,0xdf);
  481.                break;
  482.       case 7:  //显示第7位
  483.            ucDigShowTemp=dig_table[ucDigShow7];
  484.                    if(ucDigDot7==1)
  485.                    {
  486.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  487.            }
  488.            dig_hc595_drive(ucDigShowTemp,0xbf);
  489.                break;
  490.       case 8:  //显示第8位
  491.            ucDigShowTemp=dig_table[ucDigShow8];
  492.                    if(ucDigDot8==1)
  493.                    {
  494.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  495.                    }
  496.            dig_hc595_drive(ucDigShowTemp,0x7f);
  497.                break;
  498.    }
  499.    ucDisplayDriveStep++;
  500.    if(ucDisplayDriveStep>8)  //扫描完8个数码管后,重新从第一个开始扫描
  501.    {
  502.      ucDisplayDriveStep=1;
  503.    }

  504. }

  505. //数码管的74HC595驱动函数
  506. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
  507. {
  508.    unsigned char i;
  509.    unsigned char ucTempData;
  510.    dig_hc595_sh_dr=0;
  511.    dig_hc595_st_dr=0;
  512.    ucTempData=ucDigStatusTemp16_09;  //先送高8位
  513.    for(i=0;i<8;i++)
  514.    {
  515.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  516.          else dig_hc595_ds_dr=0;
  517.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  518.          delay_short(1);
  519.          dig_hc595_sh_dr=1;
  520.          delay_short(1);
  521.          ucTempData=ucTempData<<1;
  522.    }
  523.    ucTempData=ucDigStatusTemp08_01;  //再先送低8位
  524.    for(i=0;i<8;i++)
  525.    {
  526.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  527.          else dig_hc595_ds_dr=0;
  528.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  529.          delay_short(1);
  530.          dig_hc595_sh_dr=1;
  531.          delay_short(1);
  532.          ucTempData=ucTempData<<1;
  533.    }
  534.    dig_hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  535.    delay_short(1);
  536.    dig_hc595_st_dr=1;
  537.    delay_short(1);
  538.    dig_hc595_sh_dr=0;    //拉低,抗干扰就增强
  539.    dig_hc595_st_dr=0;
  540.    dig_hc595_ds_dr=0;
  541. }

  542. //LED灯的74HC595驱动函数
  543. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
  544. {
  545.    unsigned char i;
  546.    unsigned char ucTempData;
  547.    hc595_sh_dr=0;
  548.    hc595_st_dr=0;
  549.    ucTempData=ucLedStatusTemp16_09;  //先送高8位
  550.    for(i=0;i<8;i++)
  551.    {
  552.          if(ucTempData>=0x80)hc595_ds_dr=1;
  553.          else hc595_ds_dr=0;
  554.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  555.          delay_short(1);
  556.          hc595_sh_dr=1;
  557.          delay_short(1);
  558.          ucTempData=ucTempData<<1;
  559.    }
  560.    ucTempData=ucLedStatusTemp08_01;  //再先送低8位
  561.    for(i=0;i<8;i++)
  562.    {
  563.          if(ucTempData>=0x80)hc595_ds_dr=1;
  564.          else hc595_ds_dr=0;
  565.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  566.          delay_short(1);
  567.          hc595_sh_dr=1;
  568.          delay_short(1);
  569.          ucTempData=ucTempData<<1;
  570.    }
  571.    hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  572.    delay_short(1);
  573.    hc595_st_dr=1;
  574.    delay_short(1);
  575.    hc595_sh_dr=0;    //拉低,抗干扰就增强
  576.    hc595_st_dr=0;
  577.    hc595_ds_dr=0;
  578. }

  579. void T0_time() interrupt 1
  580. {
  581.   unsigned int i;
  582.   TF0=0;  //清除中断标志
  583.   TR0=0; //关中断

  584.   if(ucWd==3)  //在窗口3下
  585.   {
  586.      if(uiPasswordTimer>0)   
  587.          {
  588.             uiPasswordTimer--;  
  589.          }
  590.      if(uiPasswordTimer==0)
  591.          {
  592.                 if(ucInputPassword[0]==9&&ucInputPassword[1]==9&&ucInputPassword[2]==2&&ucInputPassword[3]==2)  
  593.             {     //如果密码等于9922,则正确
  594.                       ucWd=2;//切换到第2个显示按键的窗口
  595.                       ucWd2Update=1;  //更新显示窗口2
  596.             }
  597.                 else //如果密码不正确,则继续显示----
  598.             {
  599.               for(i=0;i<4;i++)
  600.               {
  601.                  ucInputPassword[i]=11;  //开机默认密码全部显示"----"
  602.               }
  603.                           ucWd=1;
  604.                   ucWd1Update=1; //更新显示窗口1
  605.                 }

  606.          }
  607.   }


  608.   if(ucWd==2)  //在窗口2下
  609.   {
  610.      if(uiNoKeyPushTimer>0)   
  611.          {
  612.             uiNoKeyPushTimer--;  
  613.          }
  614.      if(uiNoKeyPushTimer==0)//如果10秒内无按键按下,则自动切换到显示密码登录框的界面
  615.          {
  616.        for(i=0;i<4;i++)
  617.        {
  618.           ucInputPassword[i]=11;  //开机默认密码全部显示"----"
  619.        }
  620.            ucWd=1;
  621.            ucWd1Update=1; //更新显示窗口1
  622.          }
  623.   }


  624.   key_scan(); //按键扫描函数
  625.   if(uiVoiceCnt!=0)
  626.   {
  627.      uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
  628.      beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  629. //     beep_dr=1;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  630.   }
  631.   else
  632.   {
  633.      ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
  634.      beep_dr=1;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  635. //     beep_dr=0;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  636.   }
  637.   display_drive();  //数码管字模的驱动函数

  638.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  639.   TL0=0x0b;
  640.   TR0=1;  //开中断
  641. }

  642. void delay_short(unsigned int uiDelayShort)
  643. {
  644.    unsigned int i;  
  645.    for(i=0;i<uiDelayShort;i++)
  646.    {
  647.      ;   //一个分号相当于执行一条空语句
  648.    }
  649. }

  650. void delay_long(unsigned int uiDelayLong)
  651. {
  652.    unsigned int i;
  653.    unsigned int j;
  654.    for(i=0;i<uiDelayLong;i++)
  655.    {
  656.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  657.           {
  658.              ; //一个分号相当于执行一条空语句
  659.           }
  660.    }
  661. }

  662. void initial_myself()  //第一区 初始化单片机
  663. {

  664.   led_dr=0;  //关闭独立LED灯
  665.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
  666.   hc595_drive(0x00,0x00);  //关闭所有经过另外两个74HC595驱动的LED灯
  667.   TMOD=0x01;  //设置定时器0为工作方式1
  668.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  669.   TL0=0x0b;
  670. }
  671. void initial_peripheral() //第二区 初始化外围
  672. {
  673.    unsigned int i; //个人的变量命名习惯,i,j,k等单个字母的变量名只用在for循环里
  674.    for(i=0;i<4;i++)
  675.    {
  676.      ucInputPassword[i]=11;  //开机默认密码全部显示"----"
  677.    }

  678.    ucDigDot8=0;   //小数点全部不显示
  679.    ucDigDot7=0;  
  680.    ucDigDot6=0;
  681.    ucDigDot5=0;  
  682.    ucDigDot4=0;
  683.    ucDigDot3=0;  
  684.    ucDigDot2=0;
  685.    ucDigDot1=0;
  686.    EA=1;     //开总中断
  687.    ET0=1;    //允许定时中断
  688.    TR0=1;    //启动定时中断
  689. }
复制代码


总结陈词:
这节讲了iphone4S开机密码锁的程序。2014年春节的时候,一帮朋友举行小规模的象棋比赛,有一些朋友下棋的速度实在是太慢了,为了限制比赛时间,我专门用朱兆祺的51学习板做了一个棋类比赛专用计时器给他们用,这个程序该怎么编写?欲知详情,请听下回分解-----带数码管显示的象棋比赛专用计时器。

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

作者: jianhong_wu    时间: 2014-3-21 01:34
第三十五节:带数码管显示的象棋比赛专用计时器。

开场白:
2014年春节的时候,一帮朋友举行小规模的象棋比赛,有一些朋友下棋的速度实在是太慢了,为了限制比赛时间,我专门用朱兆祺的51学习板做了一个棋类比赛专用计时器给他们用。这一节要教会大家两个知识点:
第一个:按键服务程序操作的精髓在于根据当前系统处于什么窗口状态下就执行什么操作。紧紧围绕着不同的窗口ucWd来执行不同的操作。
第二个:继续加深熟悉鸿哥首次提出的“一二级菜单显示理论”:凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。

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

(1)硬件平台:基于坚鸿51单片机学习板。
刚上电开机时,红棋加时键对应S1键,红棋减时键对应S2键.。
刚上电开机时,黑棋加时键对应S3键,黑棋减时键对应S4键.。
比赛中途暂停双方计时的暂停按键对应S6键。刚上电时,复位双方默认20分时间的复位按键对应S7按键。
红棋的抢时按键对应S13键,黑棋的抢时按键对应S16按键。

(2)实现功能:
棋类计时器有点像抢答器,本质上有两个计时器。比赛的时候对弈的两个棋友各用一个不同的按键抢时间,红棋走一步棋后,就按一下自己的抢时按键,这个时候红棋的计时器停止计时,而黑棋的计时器开始计时,黑棋走了一步棋后,按一下自己的计时器,黑棋停止计时,红棋继续计时,依次循环,谁的时间最先用完谁就输,蜂鸣器也会发出长鸣的声音提示时间到。
上电开机默认双方各有20分钟的时间,左边显示的是红棋的时间,右边显示的是黑棋的时间。此时可以通过S1,S2.,S3,S4的加减按键来设置各自的最大倒计时时间。此时如果按下复位按键S7,会自动把双方的时间设置为默认的20分钟。
设置好最大倒计时的时间后,此时任意一方按下各自的抢时按键(S13或者S16),则自己的计时器停止计时,而对方开始倒计时。此时数码管显示的是对方的时间,而自己的时间屏蔽不显示。
在开始倒计时的时候,如果中途有棋友要接听电话或者忙别的事情,需要暂时暂停一下双方的时间,这个时候可以按S6暂停按键来暂停双方的计时,忙完后再次按下暂停按键会继续倒计时。任何一方的时间走完,都会蜂鸣器长鸣提示。
(3)源代码讲解如下:
  1. #include "REG52.H"

  2. #define const_voice_short  40   //蜂鸣器短叫的持续时间
  3. #define const_voice_long   900   //蜂鸣器长叫的持续时间

  4. #define const_key_time  10    //按键去抖动延时的时间

  5. #define const_1s     422   //产生一秒钟的时间基准

  6. void initial_myself();   
  7. void initial_peripheral();
  8. void delay_short(unsigned int uiDelayShort);
  9. void delay_long(unsigned int uiDelaylong);
  10. void T0_time();  //定时中断函数
  11. void key_service();
  12. void key_scan(); //按键扫描函数 放在定时中断里

  13. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);
  14. void display_drive();  //放在定时中断里的数码管驱动函数
  15. void time_service();  //放在定时中断里的时间应用程序
  16. void display_service();  


  17. sbit key_sr1=P0^0; //第一行输入
  18. sbit key_sr2=P0^1; //第二行输入
  19. sbit key_sr3=P0^2; //第三行输入
  20. sbit key_sr4=P0^3; //第四行输入

  21. sbit key_dr1=P0^4; //第一列输出
  22. sbit key_dr2=P0^5; //第二列输出
  23. sbit key_dr3=P0^6; //第三列输出
  24. sbit key_dr4=P0^7; //第四列输出

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


  26. sbit led_dr=P3^5;  //作为中途暂停指示灯 亮的时候表示中途暂停


  27. sbit dig_hc595_sh_dr=P2^0;     //数码管 的74HC595程序
  28. sbit dig_hc595_st_dr=P2^1;  
  29. sbit dig_hc595_ds_dr=P2^2;  

  30. sbit hc595_sh_dr=P2^3;    //LED灯的74HC595程序
  31. sbit hc595_st_dr=P2^4;  
  32. sbit hc595_ds_dr=P2^5;  


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

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

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

  38. unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器

  39. unsigned char ucDigShow8=0;  //第8位数码管要显示的内容
  40. unsigned char ucDigShow7=0;  //第7位数码管要显示的内容
  41. unsigned char ucDigShow6=0;  //第6位数码管要显示的内容
  42. unsigned char ucDigShow5=0;  //第5位数码管要显示的内容
  43. unsigned char ucDigShow4=0;  //第4位数码管要显示的内容
  44. unsigned char ucDigShow3=0;  //第3位数码管要显示的内容
  45. unsigned char ucDigShow2=0;  //第2位数码管要显示的内容
  46. unsigned char ucDigShow1=0;  //第1位数码管要显示的内容
  47. unsigned char ucDigDot3=1;  //数码管3的小数点是否显示的标志
  48. unsigned char ucDigDot7=1;  //数码管7的小数点是否显示的标志

  49. unsigned char ucDigShowTemp=0; //临时中间变量

  50. unsigned char ucDisplayDriveStep=1;  //动态扫描数码管的步骤变量

  51. unsigned int uiRedTimeCnt=0;    //红棋产生秒基准的时间计时器
  52. unsigned int uiBlackTimeCnt=0;  //黑棋产生秒基准的时间计时器

  53. unsigned int uiRedTotal=1200;    //红棋的总时间
  54. unsigned int uiBlackTotal=1200;  //黑棋的总时间

  55. unsigned char ucRedFlag=0;  //红棋是否开始计时的标志
  56. unsigned char ucBlackFlag=0;  //黑棋是否开始计时的标志

  57. unsigned char ucDisplayUpdate=1; //更新显示标志

  58. /* 注释一:
  59. *  ucWd变量是本程序最核心的变量,代表显示哪一个窗口和系统处于当前哪种状态
  60. */
  61. unsigned char ucWd=1;

  62. code unsigned char dig_table[]=
  63. {
  64. 0x3f,  //0       序号0
  65. 0x06,  //1       序号1
  66. 0x5b,  //2       序号2
  67. 0x4f,  //3       序号3
  68. 0x66,  //4       序号4
  69. 0x6d,  //5       序号5
  70. 0x7d,  //6       序号6
  71. 0x07,  //7       序号7
  72. 0x7f,  //8       序号8
  73. 0x6f,  //9       序号9
  74. 0x00,  //不显示  序号10
  75. };

  76. void main()
  77.   {
  78.    initial_myself();  
  79.    delay_long(100);   
  80.    initial_peripheral();
  81.    while(1)  
  82.    {
  83.        key_service();
  84.        display_service();  
  85.    }

  86. }


  87. void time_service()  //放在定时中断里的时间应用程序
  88. {
  89.   if(ucRedFlag==1)  //1代表红棋在运行中
  90.   {
  91.      uiRedTimeCnt++;
  92.          if(uiRedTimeCnt>const_1s)
  93.          {
  94.         uiRedTimeCnt=0;
  95.         if(uiRedTotal>0)
  96.                 {
  97.                    uiRedTotal--;
  98.                 }
  99.                 else  //时间到
  100.                 {
  101.                     ucRedFlag=0;    //红棋和黑棋同时停止计时
  102.                    ucBlackFlag=0;
  103.                    ucWd=1;  //切换到第一个窗口的状态
  104.                    uiVoiceCnt=const_voice_long; //报警声音触发
  105.                 }
  106.                

  107.         ucDisplayUpdate=1;  //更新显示
  108.          }
  109.   }


  110.   if(ucBlackFlag==1)  //1代表黑棋在运行中
  111.   {
  112.      uiBlackTimeCnt++;
  113.          if(uiBlackTimeCnt>const_1s)
  114.          {
  115.         uiBlackTimeCnt=0;
  116.         if(uiBlackTotal>0)
  117.                 {
  118.                    uiBlackTotal--;
  119.                 }
  120.                 else  //时间到
  121.                 {
  122.                     ucRedFlag=0;  //红棋和黑棋同时停止计时
  123.                    ucBlackFlag=0;
  124.                    ucWd=1;  //切换到第一个窗口的状态
  125.                    uiVoiceCnt=const_voice_long; //报警声音触发
  126.                 }
  127.                

  128.         ucDisplayUpdate=1;  //更新显示
  129.          }
  130.   }
  131. }

  132. void display_service()  //放在定时中断里的显示应用程序
  133. {
  134.   if(ucDisplayUpdate==1)  //有数据更新显示
  135.   {
  136.      ucDisplayUpdate=0;
  137.          switch(ucWd)     //本程序最核心的变量ucWd
  138.          {
  139.            case 1:  //窗口1,代表刚上电或者复位后的状态
  140.                       //红棋分解出分
  141.                        ucDigShowTemp=uiRedTotal/60;
  142.             ucDigShow8=ucDigShowTemp/10;
  143.             ucDigShow7=ucDigShowTemp%10;

  144.                        //红棋分解出秒
  145.                     ucDigShowTemp=uiRedTotal%60;
  146.             ucDigShow6=ucDigShowTemp/10;
  147.             ucDigShow5=ucDigShowTemp%10;
  148.                         ucDigDot7=1;  //数码管7的小数点显示

  149.                       //黑棋分解出分
  150.                        ucDigShowTemp=uiBlackTotal/60;
  151.             ucDigShow4=ucDigShowTemp/10;
  152.             ucDigShow3=ucDigShowTemp%10;

  153.                        //黑棋分解出秒
  154.                     ucDigShowTemp=uiBlackTotal%60;
  155.             ucDigShow2=ucDigShowTemp/10;
  156.             ucDigShow1=ucDigShowTemp%10;
  157.                         ucDigDot3=1;  //数码管3的小数点显示

  158.             led_dr=1;  //计时器处于停止状态,LED亮

  159.                 break;
  160.            case 2:  //窗口2,代表黑棋正在运行中的状态

  161.                       //红棋全部不显示
  162.             ucDigShow8=10;
  163.             ucDigShow7=10;
  164.             ucDigShow6=10;
  165.             ucDigShow5=10;
  166.                         ucDigDot7=0;  //数码管7的小数点不显示

  167.                       //黑棋分解出分
  168.                        ucDigShowTemp=uiBlackTotal/60;
  169.             ucDigShow4=ucDigShowTemp/10;
  170.             ucDigShow3=ucDigShowTemp%10;

  171.                        //黑棋分解出秒
  172.                     ucDigShowTemp=uiBlackTotal%60;
  173.             ucDigShow2=ucDigShowTemp/10;
  174.             ucDigShow1=ucDigShowTemp%10;
  175.                         ucDigDot3=1;  //数码管3的小数点显示

  176.             led_dr=0;  //计时器处于计时状态,LED灭

  177.                 break;

  178.            case 3:  //窗口3,代表黑棋在中途暂停的状态

  179.                       //红棋全部不显示
  180.             ucDigShow8=10;
  181.             ucDigShow7=10;
  182.             ucDigShow6=10;
  183.             ucDigShow5=10;
  184.                         ucDigDot7=0;  //数码管7的小数点不显示

  185.                       //黑棋分解出分
  186.                        ucDigShowTemp=uiBlackTotal/60;
  187.             ucDigShow4=ucDigShowTemp/10;
  188.             ucDigShow3=ucDigShowTemp%10;

  189.                        //黑棋分解出秒
  190.                     ucDigShowTemp=uiBlackTotal%60;
  191.             ucDigShow2=ucDigShowTemp/10;
  192.             ucDigShow1=ucDigShowTemp%10;
  193.                         ucDigDot3=1;  //数码管3的小数点显示


  194.             led_dr=1;  //计时器处于暂停状态,LED亮

  195.                 break;
  196.            case 4:  //窗口4,代表红棋正在运行中的状态
  197.                       //红棋分解出分
  198.                        ucDigShowTemp=uiRedTotal/60;
  199.             ucDigShow8=ucDigShowTemp/10;
  200.             ucDigShow7=ucDigShowTemp%10;

  201.                        //红棋分解出秒
  202.                     ucDigShowTemp=uiRedTotal%60;
  203.             ucDigShow6=ucDigShowTemp/10;
  204.             ucDigShow5=ucDigShowTemp%10;
  205.                         ucDigDot7=1;  //数码管7的小数点显示


  206.                       //黑棋全部不显示
  207.             ucDigShow4=10;
  208.             ucDigShow3=10;
  209.             ucDigShow2=10;
  210.             ucDigShow1=10;
  211.                         ucDigDot3=0;  //数码管3的小数点不显示

  212.             led_dr=0;  //计时器处于倒计时状态,LED灭

  213.                 break;

  214.            case 5:  //窗口5,代表红棋在中途暂停的状态
  215.                       //红棋分解出分
  216.                        ucDigShowTemp=uiRedTotal/60;
  217.             ucDigShow8=ucDigShowTemp/10;
  218.             ucDigShow7=ucDigShowTemp%10;

  219.                        //红棋分解出秒
  220.                     ucDigShowTemp=uiRedTotal%60;
  221.             ucDigShow6=ucDigShowTemp/10;
  222.             ucDigShow5=ucDigShowTemp%10;
  223.                         ucDigDot7=1;  //数码管7的小数点显示


  224.                       //黑棋全部不显示
  225.             ucDigShow4=10;
  226.             ucDigShow3=10;
  227.             ucDigShow2=10;
  228.             ucDigShow1=10;
  229.                         ucDigDot3=0;  //数码管3的小数点不显示

  230.             led_dr=1;  //计时器处于暂停状态,LED亮

  231.                 break;
  232.          }
  233.   }
  234. }

  235. void display_drive()  //放在定时中断里的数码管驱动函数
  236. {
  237.    //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
  238.    switch(ucDisplayDriveStep)
  239.    {
  240.       case 1:  //显示第1位
  241.            ucDigShowTemp=dig_table[ucDigShow1];
  242.            dig_hc595_drive(ucDigShowTemp,0xfe);
  243.                break;
  244.       case 2:  //显示第2位
  245.            ucDigShowTemp=dig_table[ucDigShow2];
  246.            dig_hc595_drive(ucDigShowTemp,0xfd);
  247.                break;
  248.       case 3:  //显示第3位
  249.            ucDigShowTemp=dig_table[ucDigShow3];
  250.                    if(ucDigDot3==1)
  251.                    {
  252.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  253.                    }
  254.            dig_hc595_drive(ucDigShowTemp,0xfb);
  255.                break;
  256.       case 4:  //显示第4位
  257.            ucDigShowTemp=dig_table[ucDigShow4];
  258.            dig_hc595_drive(ucDigShowTemp,0xf7);
  259.                break;
  260.       case 5:  //显示第5位
  261.            ucDigShowTemp=dig_table[ucDigShow5];
  262.            dig_hc595_drive(ucDigShowTemp,0xef);
  263.                break;
  264.       case 6:  //显示第6位
  265.            ucDigShowTemp=dig_table[ucDigShow6];
  266.            dig_hc595_drive(ucDigShowTemp,0xdf);
  267.                break;
  268.       case 7:  //显示第7位
  269.            ucDigShowTemp=dig_table[ucDigShow7];
  270.                    if(ucDigDot7==1)
  271.                    {
  272.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  273.            }
  274.            dig_hc595_drive(ucDigShowTemp,0xbf);
  275.                break;
  276.       case 8:  //显示第8位
  277.            ucDigShowTemp=dig_table[ucDigShow8];
  278.            dig_hc595_drive(ucDigShowTemp,0x7f);
  279.                break;
  280.    }

  281.    ucDisplayDriveStep++;
  282.    if(ucDisplayDriveStep>8)  //扫描完8个数码管后,重新从第一个开始扫描
  283.    {
  284.      ucDisplayDriveStep=1;
  285.    }
  286. }


  287. //数码管的74HC595驱动函数
  288. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
  289. {
  290.    unsigned char i;
  291.    unsigned char ucTempData;
  292.    dig_hc595_sh_dr=0;
  293.    dig_hc595_st_dr=0;

  294.    ucTempData=ucDigStatusTemp16_09;  //先送高8位
  295.    for(i=0;i<8;i++)
  296.    {
  297.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  298.          else dig_hc595_ds_dr=0;

  299. /* 注释二:
  300. *  注意,此处的延时delay_short必须尽可能小,否则动态扫描数码管的速度就不够。
  301. */
  302.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  303.          delay_short(1);
  304.          dig_hc595_sh_dr=1;
  305.          delay_short(1);

  306.          ucTempData=ucTempData<<1;
  307.    }

  308.    ucTempData=ucDigStatusTemp08_01;  //再先送低8位
  309.    for(i=0;i<8;i++)
  310.    {
  311.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  312.          else dig_hc595_ds_dr=0;

  313.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  314.          delay_short(1);
  315.          dig_hc595_sh_dr=1;
  316.          delay_short(1);

  317.          ucTempData=ucTempData<<1;
  318.    }

  319.    dig_hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  320.    delay_short(1);
  321.    dig_hc595_st_dr=1;
  322.    delay_short(1);

  323.    dig_hc595_sh_dr=0;    //拉低,抗干扰就增强
  324.    dig_hc595_st_dr=0;
  325.    dig_hc595_ds_dr=0;

  326. }


  327. //LED灯的74HC595驱动函数
  328. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
  329. {
  330.    unsigned char i;
  331.    unsigned char ucTempData;
  332.    hc595_sh_dr=0;
  333.    hc595_st_dr=0;

  334.    ucTempData=ucLedStatusTemp16_09;  //先送高8位
  335.    for(i=0;i<8;i++)
  336.    {
  337.          if(ucTempData>=0x80)hc595_ds_dr=1;
  338.          else hc595_ds_dr=0;

  339.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  340.          delay_short(1);
  341.          hc595_sh_dr=1;
  342.          delay_short(1);

  343.          ucTempData=ucTempData<<1;
  344.    }

  345.    ucTempData=ucLedStatusTemp08_01;  //再先送低8位
  346.    for(i=0;i<8;i++)
  347.    {
  348.          if(ucTempData>=0x80)hc595_ds_dr=1;
  349.          else hc595_ds_dr=0;

  350.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  351.          delay_short(1);
  352.          hc595_sh_dr=1;
  353.          delay_short(1);

  354.          ucTempData=ucTempData<<1;
  355.    }

  356.    hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  357.    delay_short(1);
  358.    hc595_st_dr=1;
  359.    delay_short(1);

  360.    hc595_sh_dr=0;    //拉低,抗干扰就增强
  361.    hc595_st_dr=0;
  362.    hc595_ds_dr=0;

  363. }


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

  366.   switch(ucKeyStep)
  367.   {
  368.      case 1:   //按键扫描输出第ucRowRecord列低电平
  369.               if(ucRowRecord==1)  //第一列输出低电平
  370.                   {
  371.              key_dr1=0;      
  372.              key_dr2=1;
  373.              key_dr3=1;   
  374.              key_dr4=1;
  375.                   }
  376.               else if(ucRowRecord==2)  //第二列输出低电平
  377.                   {
  378.              key_dr1=1;      
  379.              key_dr2=0;
  380.              key_dr3=1;   
  381.              key_dr4=1;
  382.                   }
  383.               else if(ucRowRecord==3)  //第三列输出低电平
  384.                   {
  385.              key_dr1=1;      
  386.              key_dr2=1;
  387.              key_dr3=0;   
  388.              key_dr4=1;
  389.                   }
  390.               else   //第四列输出低电平
  391.                   {
  392.              key_dr1=1;      
  393.              key_dr2=1;
  394.              key_dr3=1;   
  395.              key_dr4=0;
  396.                   }

  397.           uiKeyTimeCnt=0;  //延时计数器清零
  398.           ucKeyStep++;     //切换到下一个运行步骤
  399.               break;

  400.      case 2:     //此处的小延时用来等待刚才列输出信号稳定,再判断输入信号。不是去抖动延时。
  401.           uiKeyTimeCnt++;
  402.                   if(uiKeyTimeCnt>1)
  403.                   {
  404.                      uiKeyTimeCnt=0;
  405.              ucKeyStep++;     //切换到下一个运行步骤
  406.                   }
  407.               break;

  408.      case 3:
  409.           if(key_sr1==1&&key_sr2==1&&key_sr3==1&&key_sr4==1)
  410.           {  
  411.              ucKeyStep=1;  //如果没有按键按下,返回到第一个运行步骤重新开始扫描
  412.              ucKeyLock=0;  //按键自锁标志清零
  413.              uiKeyTimeCnt=0; //按键去抖动延时计数器清零,此行非常巧妙     
  414.    
  415.                          ucRowRecord++;  //输出下一列
  416.                          if(ucRowRecord>4)  
  417.                          {
  418.                             ucRowRecord=1; //依次输出完四列之后,继续从第一列开始输出低电平
  419.                          }

  420.           }
  421.                   else if(ucKeyLock==0)  //有按键按下,且是第一次触发
  422.                   {
  423.                      if(key_sr1==0&&key_sr2==1&&key_sr3==1&&key_sr4==1)
  424.                          {
  425.                             uiKeyTimeCnt++;  //去抖动延时计数器
  426.                                 if(uiKeyTimeCnt>const_key_time)
  427.                                 {
  428.                                    uiKeyTimeCnt=0;
  429.                                    ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零

  430.                        if(ucRowRecord==1)  //第一列输出低电平
  431.                            {
  432.                                       ucKeySec=1;  //触发1号键 对应朱兆祺学习板的S1键
  433.                            }
  434.                        else if(ucRowRecord==2)  //第二列输出低电平
  435.                            {
  436.                                       ucKeySec=2;  //触发2号键 对应朱兆祺学习板的S2键
  437.                            }
  438.                        else if(ucRowRecord==3)  //第三列输出低电平
  439.                            {
  440.                                       ucKeySec=3;  //触发3号键 对应朱兆祺学习板的S3键
  441.                            }
  442.                        else   //第四列输出低电平
  443.                            {
  444.                                       ucKeySec=4;  //触发4号键 对应朱兆祺学习板的S4键
  445.                            }

  446.                                 }
  447.                         
  448.                          }
  449.                      else if(key_sr1==1&&key_sr2==0&&key_sr3==1&&key_sr4==1)
  450.                          {
  451.                             uiKeyTimeCnt++;  //去抖动延时计数器
  452.                                 if(uiKeyTimeCnt>const_key_time)
  453.                                 {
  454.                                    uiKeyTimeCnt=0;
  455.                                    ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
  456.                        if(ucRowRecord==1)  //第一列输出低电平
  457.                            {
  458.                                       ucKeySec=5;  //触发5号键 对应朱兆祺学习板的S5键
  459.                            }
  460.                        else if(ucRowRecord==2)  //第二列输出低电平
  461.                            {
  462.                                       ucKeySec=6;  //触发6号键 对应朱兆祺学习板的S6键
  463.                            }
  464.                        else if(ucRowRecord==3)  //第三列输出低电平
  465.                            {
  466.                                       ucKeySec=7;  //触发7号键 对应朱兆祺学习板的S7键
  467.                            }
  468.                        else   //第四列输出低电平
  469.                            {
  470.                                       ucKeySec=8;  //触发8号键 对应朱兆祺学习板的S8键
  471.                            }
  472.                                 }
  473.                         
  474.                          }
  475.                      else if(key_sr1==1&&key_sr2==1&&key_sr3==0&&key_sr4==1)
  476.                          {
  477.                             uiKeyTimeCnt++;  //去抖动延时计数器
  478.                                 if(uiKeyTimeCnt>const_key_time)
  479.                                 {
  480.                                    uiKeyTimeCnt=0;
  481.                                    ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
  482.                        if(ucRowRecord==1)  //第一列输出低电平
  483.                            {
  484.                                       ucKeySec=9;  //触发9号键 对应朱兆祺学习板的S9键
  485.                            }
  486.                        else if(ucRowRecord==2)  //第二列输出低电平
  487.                            {
  488.                                       ucKeySec=10;  //触发10号键 对应朱兆祺学习板的S10键
  489.                            }
  490.                        else if(ucRowRecord==3)  //第三列输出低电平
  491.                            {
  492.                                       ucKeySec=11;  //触发11号键 对应朱兆祺学习板的S11键
  493.                            }
  494.                        else   //第四列输出低电平
  495.                            {
  496.                                       ucKeySec=12;  //触发12号键 对应朱兆祺学习板的S12键
  497.                            }
  498.                                 }
  499.                         
  500.                          }
  501.                      else if(key_sr1==1&&key_sr2==1&&key_sr3==1&&key_sr4==0)
  502.                          {
  503.                             uiKeyTimeCnt++;  //去抖动延时计数器
  504.                                 if(uiKeyTimeCnt>const_key_time)
  505.                                 {
  506.                                    uiKeyTimeCnt=0;
  507.                                    ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
  508.                        if(ucRowRecord==1)  //第一列输出低电平
  509.                            {
  510.                                       ucKeySec=13;  //触发13号键 对应朱兆祺学习板的S13键
  511.                            }
  512.                        else if(ucRowRecord==2)  //第二列输出低电平
  513.                            {
  514.                                       ucKeySec=14;  //触发14号键 对应朱兆祺学习板的S14键
  515.                            }
  516.                        else if(ucRowRecord==3)  //第三列输出低电平
  517.                            {
  518.                                       ucKeySec=15;  //触发15号键 对应朱兆祺学习板的S15键
  519.                            }
  520.                        else   //第四列输出低电平
  521.                            {
  522.                                       ucKeySec=16;  //触发16号键 对应朱兆祺学习板的S16键
  523.                            }
  524.                                 }
  525.                         
  526.                          }
  527.                   
  528.                   }
  529.               break;

  530.   }


  531. }

  532. /* 注释三:
  533. *  按键服务程序操作的精髓在于根据当前系统处于什么窗口下就执行什么操作。
  534. *  紧紧围绕着不同的窗口ucWd来执行不同的操作。
  535. */
  536. void key_service() //第三区 放在定时中断里的按键服务应用程序
  537. {
  538.   switch(ucKeySec) //按键服务状态切换
  539.   {
  540.     case 1:// 1号键 对应朱兆祺学习板的S1键  红棋加分 按键
  541.               switch(ucWd)  //本程序最核心的变量ucWd
  542.               {
  543.                  case 1:  //窗口1,代表刚上电,完成或者复位后的状态          
  544.                               uiRedTotal=uiRedTotal+60;  //加红棋分的时间,此处60秒代表一分
  545.                                   if(uiRedTotal>5940)
  546.                                   {
  547.                                      uiRedTotal=5940;
  548.                                   }
  549.                   uiRedTotal=uiRedTotal-(uiRedTotal%60);  //去秒取整分

  550.                                   ucDisplayUpdate=1;  //更新显示
  551.                   uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  552.                       break;

  553.                  case 2:  //窗口2,代表黑棋正在运行中的状态       
  554.                       break;

  555.                  case 3:  //窗口3,代表黑棋在中途暂停的状态
  556.                       break;

  557.                  case 4:  //窗口4,代表红棋正在运行中的状态          
  558.                       break;

  559.                  case 5:  //窗口5,代表红棋在中途暂停的状态
  560.                       break;

  561.           }
  562.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  563.           break;        

  564.     case 2:// 2号键 对应朱兆祺学习板的S2键  红棋减分 按键
  565.               switch(ucWd)  //本程序最核心的变量ucWd
  566.               {
  567.                  case 1:  //窗口1,代表刚上电,完成或者复位后的状态          
  568.                               if(uiRedTotal>=60)
  569.                                   {
  570.                                  uiRedTotal=uiRedTotal-60;  //减红棋分的时间,此处60秒代表一分
  571.                                   }
  572.                   uiRedTotal=uiRedTotal-(uiRedTotal%60);  //去秒取整分

  573.                                   ucDisplayUpdate=1;  //更新显示
  574.                   uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  575.                       break;

  576.                  case 2:  //窗口2,代表黑棋正在运行中的状态       
  577.                       break;

  578.                  case 3:  //窗口3,代表黑棋在中途暂停的状态
  579.                       break;

  580.                  case 4:  //窗口4,代表红棋正在运行中的状态          
  581.                       break;

  582.                  case 5:  //窗口5,代表红棋在中途暂停的状态
  583.                       break;

  584.           }
  585.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  586.           break;     

  587.     case 3:// 3号键 对应朱兆祺学习板的S3键  黑棋加分 按键
  588.               switch(ucWd)  //本程序最核心的变量ucWd
  589.               {
  590.                  case 1:  //窗口1,代表刚上电,完成或者复位后的状态          
  591.                               uiBlackTotal=uiBlackTotal+60;  //加黑棋分的时间,此处60秒代表一分
  592.                                   if(uiBlackTotal>5940)
  593.                                   {
  594.                                      uiBlackTotal=5940;
  595.                                   }
  596.                   uiBlackTotal=uiBlackTotal-(uiBlackTotal%60);  //去秒取整分

  597.                                   ucDisplayUpdate=1;  //更新显示
  598.                   uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  599.                       break;

  600.                  case 2:  //窗口2,代表黑棋正在运行中的状态       
  601.                       break;

  602.                  case 3:  //窗口3,代表黑棋在中途暂停的状态
  603.                       break;

  604.                  case 4:  //窗口4,代表红棋正在运行中的状态          
  605.                       break;

  606.                  case 5:  //窗口5,代表红棋在中途暂停的状态
  607.                       break;

  608.           }
  609.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  610.           break;         

  611.     case 4:// 4号键 对应朱兆祺学习板的S4键  黑棋减分 按键
  612.               switch(ucWd)  //本程序最核心的变量ucWd
  613.               {
  614.                  case 1:  //窗口1,代表刚上电,完成或者复位后的状态          
  615.                               if(uiBlackTotal>=60)
  616.                                   {
  617.                                  uiBlackTotal=uiBlackTotal-60;  //减黑棋分的时间,此处60秒代表一分
  618.                                   }
  619.                   uiBlackTotal=uiBlackTotal-(uiBlackTotal%60);  //去秒取整分

  620.                                   ucDisplayUpdate=1;  //更新显示
  621.                   uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  622.                       break;

  623.                  case 2:  //窗口2,代表黑棋正在运行中的状态       
  624.                       break;

  625.                  case 3:  //窗口3,代表黑棋在中途暂停的状态
  626.                       break;

  627.                  case 4:  //窗口4,代表红棋正在运行中的状态          
  628.                       break;

  629.                  case 5:  //窗口5,代表红棋在中途暂停的状态
  630.                       break;

  631.           }
  632.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  633.           break;   

  634.     case 5:// 5号键 对应朱兆祺学习板的S5键


  635.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  636.           break;   

  637.     case 6:// 6号键 对应朱兆祺学习板的S6键  中途暂停和启动按键
  638.               switch(ucWd)  //本程序最核心的变量ucWd
  639.               {
  640.                  case 1:  //窗口1,代表刚上电,完成或者复位后的状态          

  641.                       break;

  642.                  case 2:  //窗口2,代表黑棋正在运行中的状态       
  643.                   ucRedFlag=0;    //暂停计时
  644.                   ucBlackFlag=0;//暂停计时
  645.                                   ucWd=3; //切换到黑棋中途暂停的状态

  646.                                   ucDisplayUpdate=1;  //更新显示
  647.                   uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  648.                       break;

  649.                  case 3:  //窗口3,代表黑棋在中途暂停的状态
  650.                   ucRedFlag=0;   //红棋暂停计时
  651.                   ucBlackFlag=1; //黑棋继续计时
  652.                                   ucWd=2;       //切换到黑棋正在运行中的状态

  653.                                   ucDisplayUpdate=1;  //更新显示
  654.                   uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  655.                       break;

  656.                  case 4:  //窗口4,代表红棋正在运行中的状态          
  657.                   ucRedFlag=0;    //暂停计时
  658.                   ucBlackFlag=0;//暂停计时
  659.                                   ucWd=5;       //切换到红棋中途暂停的状态

  660.                                   ucDisplayUpdate=1;  //更新显示
  661.                   uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  662.                       break;

  663.                  case 5:  //窗口5,代表红棋在中途暂停的状态
  664.                   ucRedFlag=1;   //红棋继续计时
  665.                   ucBlackFlag=0; //黑棋暂停计时
  666.                                   ucWd=4;       //切换到红棋正在运行中的状态

  667.                                   ucDisplayUpdate=1;  //更新显示
  668.                   uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。

  669.                       break;

  670.           }
  671.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  672.           break;   

  673.     case 7:// 7号键 对应朱兆祺学习板的S7键  在第一个窗口下,把计时器的值恢复为开机时的默认值20分钟
  674.               switch(ucWd)  //本程序最核心的变量ucWd
  675.               {
  676.                  case 1:  //窗口1,代表刚上电,完成或者复位后的状态          
  677.                   uiRedTotal=1200;    //红棋的总时间
  678.                   uiBlackTotal=1200;  //黑棋的总时间

  679.                                   ucDisplayUpdate=1;  //更新显示
  680.                                   uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  681.                       break;

  682.                  case 2:  //窗口2,代表黑棋正在运行中的状态       

  683.                       break;

  684.                  case 3:  //窗口3,代表黑棋在中途暂停的状态

  685.                       break;

  686.                  case 4:  //窗口4,代表红棋正在运行中的状态          

  687.                       break;

  688.                  case 5:  //窗口5,代表红棋在中途暂停的状态

  689.                       break;

  690.           }
  691.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  692.           break;   
  693.     case 8:// 8号键 对应朱兆祺学习板的S8键

  694.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  695.           break;   
  696.     case 9:// 9号键 对应朱兆祺学习板的S9键

  697.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  698.           break;   
  699.     case 10:// 10号键 对应朱兆祺学习板的S10键

  700.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  701.           break;   
  702.     case 11:// 11号键 对应朱兆祺学习板的S11键

  703.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  704.           break;   
  705.     case 12:// 12号键 对应朱兆祺学习板的S12键

  706.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  707.           break;   
  708.     case 13:// 13号键 对应朱兆祺学习板的S13键  红棋按下
  709.               switch(ucWd)  //本程序最核心的变量ucWd
  710.               {
  711.                  case 1:  //窗口1,代表刚上电,完成或者复位后的状态          
  712.                   ucRedFlag=0;    //红棋暂停计时
  713.                   ucBlackFlag=1;  //黑棋继续计时
  714.                                   ucWd=2; //切换到黑棋正在运行中的状态

  715.                                   ucDisplayUpdate=1;  //更新显示
  716.                       break;

  717.                  case 2:  //窗口2,代表黑棋正在运行中的状态       

  718.                       break;

  719.                  case 3:  //窗口3,代表黑棋在中途暂停的状态

  720.                       break;

  721.                  case 4:  //窗口4,代表红棋正在运行中的状态          
  722.                   ucRedFlag=0;    //红棋暂停计时
  723.                   ucBlackFlag=1;  //黑棋继续计时
  724.                                   ucWd=2; //切换到黑棋正在运行中的状态

  725.                                   ucDisplayUpdate=1;  //更新显示
  726.                       break;

  727.                  case 5:  //窗口5,代表红棋在中途暂停的状态

  728.                       break;

  729.           }

  730.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  731.           break;   
  732.     case 14:// 14号键 对应朱兆祺学习板的S14键

  733.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  734.           break;   
  735.     case 15:// 15号键 对应朱兆祺学习板的S15键

  736.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  737.           break;   

  738.     case 16:// 16号键 对应朱兆祺学习板的S16键    黑棋按下
  739.               switch(ucWd)  //本程序最核心的变量ucWd
  740.               {
  741.                  case 1:  //窗口1,代表刚上电,完成或者复位后的状态          
  742.                   ucRedFlag=1;    //红棋继续计时
  743.                   ucBlackFlag=0;  //黑棋暂停计时
  744.                                   ucWd=4; //切换到红棋正在运行中的状态

  745.                                   ucDisplayUpdate=1;  //更新显示
  746.                       break;

  747.                  case 2:  //窗口2,代表黑棋正在运行中的状态       
  748.                   ucRedFlag=1;    //红棋继续计时
  749.                   ucBlackFlag=0;  //黑棋暂停计时
  750.                                   ucWd=4; //切换到红棋正在运行中的状态

  751.                                   ucDisplayUpdate=1;  //更新显示
  752.                       break;

  753.                  case 3:  //窗口3,代表黑棋在中途暂停的状态

  754.                       break;

  755.                  case 4:  //窗口4,代表红棋正在运行中的状态          

  756.                       break;

  757.                  case 5:  //窗口5,代表红棋在中途暂停的状态

  758.                       break;

  759.           }
  760.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  761.           break;   
  762.   }               
  763. }



  764. void T0_time() interrupt 1
  765. {
  766.   TF0=0;  //清除中断标志
  767.   TR0=0; //关中断
  768.   key_scan(); //放在定时中断里的按键扫描函数
  769.   time_service();  //放在定时中断里的时间应用程序

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

  780.   display_drive();  //放在定时中断里的数码管驱动函数

  781. /* 注释四:
  782. *  注意,此处的重装初始值不能太大,否则动态扫描数码管的速度就不够。我把原来常用的2000改成了500。
  783. */
  784.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  785.   TL0=0x0b;
  786.   TR0=1;  //开中断
  787. }

  788. void delay_short(unsigned int uiDelayShort)
  789. {
  790.    unsigned int i;  
  791.    for(i=0;i<uiDelayShort;i++)
  792.    {
  793.      ;   //一个分号相当于执行一条空语句
  794.    }
  795. }


  796. void delay_long(unsigned int uiDelayLong)
  797. {
  798.    unsigned int i;
  799.    unsigned int j;
  800.    for(i=0;i<uiDelayLong;i++)
  801.    {
  802.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  803.           {
  804.              ; //一个分号相当于执行一条空语句
  805.           }
  806.    }
  807. }


  808. void initial_myself()  //第一区 初始化单片机
  809. {

  810.   led_dr=1;
  811.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

  812.   hc595_drive(0x00,0x00);
  813.   TMOD=0x01;  //设置定时器0为工作方式1

  814.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  815.   TL0=0x0b;
  816. }
  817. void initial_peripheral() //第二区 初始化外围
  818. {


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



  822. }
复制代码


总结陈词:
这节讲了象棋比赛专用计时器的项目程序。为了继续加深读者理解按键和显示是如何有规律关联起来的,下节会继续讲一个相关的小项目程序。欲知详情,请听下回分解-----带数码管显示的加法简易计算器。

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


作者: jianhong_wu    时间: 2014-3-27 12:54
第三十六节:带数码管显示的加法简易计算器。

开场白:
    这一节要做一个简单的计算器。这个计算器不带小数点,只能进行不超过8位数据的加法运算,它麻雀虽小但是五脏俱全,它能清晰地勾勒出商业计算器的程序框架和思路。读者只要看懂本节程序框架的规律,以后自己想做一个复杂一点的计算器应该是没问题的。复杂的计算器在算法上要用数组进行特殊处理,不能简单地直接用C语言的+,-,*,/运算符,这方面的内容我会在以后的章节中跟大家分享。
这一节要教会大家两个知识点:
第一个:数字按键的输入和十进制数值的移位方法。
第二个:继续加深理解按键与数码管的关联程序框架。

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

(1)硬件平台:
基于坚鸿51单片机学习板。数字1键对应S1键,数字2键对应S2键,数字3键对应S3键…. 数字9键对应S9键, 数字0键对应S10键。加号键对应S13,等于号键对应S14,清除复位按键对应S16。其它按键不用。

(2)实现功能:
常用的加法计算器功能。有连加功能。
本程序有2个窗口。
第1个窗口:原始数据和运算结果窗口。  比如加法运算中的被加数
第2个窗口:第二个参与运行的数据窗口。比如加法运算中的加数

(3)源代码讲解如下:
  1. #include "REG52.H"

  2. #define const_voice_short  40   //蜂鸣器短叫的持续时间
  3. #define const_voice_long   900   //蜂鸣器长叫的持续时间

  4. #define const_key_time  10    //按键去抖动延时的时间

  5. #define const_1s     422   //产生一秒钟的时间基准

  6. void initial_myself();   
  7. void initial_peripheral();
  8. void delay_short(unsigned int uiDelayShort);
  9. void delay_long(unsigned int uiDelaylong);
  10. void T0_time();  //定时中断函数
  11. void key_service();
  12. void key_scan(); //按键扫描函数 放在定时中断里

  13. void number_key_input(unsigned long ucWhichKey);  //由于数字按键的代码相似度高,因此封装在这个函数里

  14. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);
  15. void display_drive();  //放在定时中断里的数码管驱动函数
  16. void display_service();  


  17. sbit key_sr1=P0^0; //第一行输入
  18. sbit key_sr2=P0^1; //第二行输入
  19. sbit key_sr3=P0^2; //第三行输入
  20. sbit key_sr4=P0^3; //第四行输入

  21. sbit key_dr1=P0^4; //第一列输出
  22. sbit key_dr2=P0^5; //第二列输出
  23. sbit key_dr3=P0^6; //第三列输出
  24. sbit key_dr4=P0^7; //第四列输出

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


  26. sbit led_dr=P3^5; //LED指示灯


  27. sbit dig_hc595_sh_dr=P2^0;     //数码管 的74HC595程序
  28. sbit dig_hc595_st_dr=P2^1;  
  29. sbit dig_hc595_ds_dr=P2^2;  

  30. sbit hc595_sh_dr=P2^3;    //LED灯的74HC595程序
  31. sbit hc595_st_dr=P2^4;  
  32. sbit hc595_ds_dr=P2^5;  


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

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

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

  38. unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器

  39. unsigned char ucDigShow8=0;  //第8位数码管要显示的内容
  40. unsigned char ucDigShow7=0;  //第7位数码管要显示的内容
  41. unsigned char ucDigShow6=0;  //第6位数码管要显示的内容
  42. unsigned char ucDigShow5=0;  //第5位数码管要显示的内容
  43. unsigned char ucDigShow4=0;  //第4位数码管要显示的内容
  44. unsigned char ucDigShow3=0;  //第3位数码管要显示的内容
  45. unsigned char ucDigShow2=0;  //第2位数码管要显示的内容
  46. unsigned char ucDigShow1=0;  //第1位数码管要显示的内容


  47. unsigned char ucDigShowTemp=0; //临时中间变量

  48. unsigned char ucDisplayDriveStep=1;  //动态扫描数码管的步骤变量


  49. unsigned char ucDisplayUpdate=1; //更新显示标志

  50. unsigned long ulSource=0;  //原始数据    比如在加法运算中的被加数
  51. unsigned long ulOther=0; //另外一个参与运算的数据  比如在加法运算中的加数
  52. unsigned long ulResult=0; //运算结果
  53. unsigned char ucOperator=0; //运行符号。0代表当前没有选择运行符号。1代表当前的运算符是加法。

  54. /* 注释一:
  55. *  ucWd变量是本程序最核心的变量,代表数码管显示哪一个窗口
  56. *  本程序只有两个窗口,他们分别是:
  57. *  第一个窗口:原始数据和运算结果窗口。  比如加法运算中的被加数
  58. *  第二个窗口:第二个参与运行的数据窗口。比如加法运算中的加数
  59. */
  60. unsigned char ucWd=1;

  61. code unsigned char dig_table[]=
  62. {
  63. 0x3f,  //0       序号0
  64. 0x06,  //1       序号1
  65. 0x5b,  //2       序号2
  66. 0x4f,  //3       序号3
  67. 0x66,  //4       序号4
  68. 0x6d,  //5       序号5
  69. 0x7d,  //6       序号6
  70. 0x07,  //7       序号7
  71. 0x7f,  //8       序号8
  72. 0x6f,  //9       序号9
  73. 0x00,  //不显示  序号10
  74. };

  75. void main()
  76.   {
  77.    initial_myself();  
  78.    delay_long(100);   
  79.    initial_peripheral();
  80.    while(1)  
  81.    {
  82.        key_service();
  83.        display_service();  
  84.    }

  85. }



  86. void display_service()  //放在定时中断里的显示应用程序
  87. {
  88.   if(ucDisplayUpdate==1)  //有数据更新显示
  89.   {
  90.      ucDisplayUpdate=0;
  91.          switch(ucWd)     //本程序最核心的变量ucWd
  92.          {
  93.            case 1:  //窗口1  原始数据和运算结果窗口
  94.                 if(ulSource>=10000000)
  95.                                 {
  96.                                    ucDigShow8=ulSource/10000000;
  97.                                 }
  98.                                 else
  99.                                 {
  100.                                ucDigShow8=10;//数据显示空
  101.                                 }


  102.                 if(ulSource>=1000000)
  103.                                 {
  104.                                    ucDigShow7=ulSource%10000000/1000000;
  105.                                 }
  106.                                 else
  107.                                 {
  108.                                ucDigShow7=10;//数据显示空
  109.                                 }


  110.                 if(ulSource>=100000)
  111.                                 {
  112.                                    ucDigShow6=ulSource%1000000/100000;
  113.                                 }
  114.                                 else
  115.                                 {
  116.                                ucDigShow6=10;//数据显示空
  117.                                 }

  118.                 if(ulSource>=10000)
  119.                                 {
  120.                                    ucDigShow5=ulSource%100000/10000;
  121.                                 }
  122.                                 else
  123.                                 {
  124.                                ucDigShow5=10;//数据显示空
  125.                                 }

  126.                 if(ulSource>=1000)
  127.                                 {
  128.                                    ucDigShow4=ulSource%10000/1000;
  129.                                 }
  130.                                 else
  131.                                 {
  132.                                ucDigShow4=10;//数据显示空
  133.                                 }

  134.                 if(ulSource>=100)
  135.                                 {
  136.                                    ucDigShow3=ulSource%1000/100;
  137.                                 }
  138.                                 else
  139.                                 {
  140.                                ucDigShow3=10;//数据显示空
  141.                                 }

  142.                 if(ulSource>=10)
  143.                                 {
  144.                                    ucDigShow2=ulSource%100/10;
  145.                                 }
  146.                                 else
  147.                                 {
  148.                                ucDigShow2=10;//数据显示空
  149.                                 }

  150.                                 ucDigShow1=ulSource%10;

  151.                 break;
  152.            case 2:  //窗口2  第二个参与运算数据的窗口  比如加法运算中的加数
  153.                 if(ulOther>=10000000)
  154.                                 {
  155.                                    ucDigShow8=ulOther/10000000;
  156.                                 }
  157.                                 else
  158.                                 {
  159.                                ucDigShow8=10;//数据显示空
  160.                                 }


  161.                 if(ulOther>=1000000)
  162.                                 {
  163.                                    ucDigShow7=ulOther%10000000/1000000;
  164.                                 }
  165.                                 else
  166.                                 {
  167.                                ucDigShow7=10;//数据显示空
  168.                                 }


  169.                 if(ulOther>=100000)
  170.                                 {
  171.                                    ucDigShow6=ulOther%1000000/100000;
  172.                                 }
  173.                                 else
  174.                                 {
  175.                                ucDigShow6=10;//数据显示空
  176.                                 }

  177.                 if(ulOther>=10000)
  178.                                 {
  179.                                    ucDigShow5=ulOther%100000/10000;
  180.                                 }
  181.                                 else
  182.                                 {
  183.                                ucDigShow5=10;//数据显示空
  184.                                 }

  185.                 if(ulOther>=1000)
  186.                                 {
  187.                                    ucDigShow4=ulOther%10000/1000;
  188.                                 }
  189.                                 else
  190.                                 {
  191.                                ucDigShow4=10;//数据显示空
  192.                                 }

  193.                 if(ulOther>=100)
  194.                                 {
  195.                                    ucDigShow3=ulOther%1000/100;
  196.                                 }
  197.                                 else
  198.                                 {
  199.                                ucDigShow3=10;//数据显示空
  200.                                 }

  201.                 if(ulOther>=10)
  202.                                 {
  203.                                    ucDigShow2=ulOther%100/10;
  204.                                 }
  205.                                 else
  206.                                 {
  207.                                ucDigShow2=10;//数据显示空
  208.                                 }

  209.                                 ucDigShow1=ulOther%10;

  210.                 break;
  211.          }
  212.   }
  213. }

  214. void display_drive()  //放在定时中断里的数码管驱动函数
  215. {
  216.    //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
  217.    switch(ucDisplayDriveStep)
  218.    {
  219.       case 1:  //显示第1位
  220.            ucDigShowTemp=dig_table[ucDigShow1];
  221.            dig_hc595_drive(ucDigShowTemp,0xfe);
  222.                break;
  223.       case 2:  //显示第2位
  224.            ucDigShowTemp=dig_table[ucDigShow2];
  225.            dig_hc595_drive(ucDigShowTemp,0xfd);
  226.                break;
  227.       case 3:  //显示第3位
  228.            ucDigShowTemp=dig_table[ucDigShow3];
  229.            dig_hc595_drive(ucDigShowTemp,0xfb);
  230.                break;
  231.       case 4:  //显示第4位
  232.            ucDigShowTemp=dig_table[ucDigShow4];
  233.            dig_hc595_drive(ucDigShowTemp,0xf7);
  234.                break;
  235.       case 5:  //显示第5位
  236.            ucDigShowTemp=dig_table[ucDigShow5];
  237.            dig_hc595_drive(ucDigShowTemp,0xef);
  238.                break;
  239.       case 6:  //显示第6位
  240.            ucDigShowTemp=dig_table[ucDigShow6];
  241.            dig_hc595_drive(ucDigShowTemp,0xdf);
  242.                break;
  243.       case 7:  //显示第7位
  244.            ucDigShowTemp=dig_table[ucDigShow7];
  245.            dig_hc595_drive(ucDigShowTemp,0xbf);
  246.                break;
  247.       case 8:  //显示第8位
  248.            ucDigShowTemp=dig_table[ucDigShow8];
  249.            dig_hc595_drive(ucDigShowTemp,0x7f);
  250.                break;
  251.    }

  252.    ucDisplayDriveStep++;
  253.    if(ucDisplayDriveStep>8)  //扫描完8个数码管后,重新从第一个开始扫描
  254.    {
  255.      ucDisplayDriveStep=1;
  256.    }
  257. }


  258. //数码管的74HC595驱动函数
  259. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
  260. {
  261.    unsigned char i;
  262.    unsigned char ucTempData;
  263.    dig_hc595_sh_dr=0;
  264.    dig_hc595_st_dr=0;

  265.    ucTempData=ucDigStatusTemp16_09;  //先送高8位
  266.    for(i=0;i<8;i++)
  267.    {
  268.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  269.          else dig_hc595_ds_dr=0;

  270. /* 注释二:
  271. *  注意,此处的延时delay_short必须尽可能小,否则动态扫描数码管的速度就不够。
  272. */
  273.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  274.          delay_short(1);
  275.          dig_hc595_sh_dr=1;
  276.          delay_short(1);

  277.          ucTempData=ucTempData<<1;
  278.    }

  279.    ucTempData=ucDigStatusTemp08_01;  //再先送低8位
  280.    for(i=0;i<8;i++)
  281.    {
  282.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  283.          else dig_hc595_ds_dr=0;

  284.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  285.          delay_short(1);
  286.          dig_hc595_sh_dr=1;
  287.          delay_short(1);

  288.          ucTempData=ucTempData<<1;
  289.    }

  290.    dig_hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  291.    delay_short(1);
  292.    dig_hc595_st_dr=1;
  293.    delay_short(1);

  294.    dig_hc595_sh_dr=0;    //拉低,抗干扰就增强
  295.    dig_hc595_st_dr=0;
  296.    dig_hc595_ds_dr=0;

  297. }


  298. //LED灯的74HC595驱动函数
  299. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
  300. {
  301.    unsigned char i;
  302.    unsigned char ucTempData;
  303.    hc595_sh_dr=0;
  304.    hc595_st_dr=0;

  305.    ucTempData=ucLedStatusTemp16_09;  //先送高8位
  306.    for(i=0;i<8;i++)
  307.    {
  308.          if(ucTempData>=0x80)hc595_ds_dr=1;
  309.          else hc595_ds_dr=0;

  310.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  311.          delay_short(1);
  312.          hc595_sh_dr=1;
  313.          delay_short(1);

  314.          ucTempData=ucTempData<<1;
  315.    }

  316.    ucTempData=ucLedStatusTemp08_01;  //再先送低8位
  317.    for(i=0;i<8;i++)
  318.    {
  319.          if(ucTempData>=0x80)hc595_ds_dr=1;
  320.          else hc595_ds_dr=0;

  321.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  322.          delay_short(1);
  323.          hc595_sh_dr=1;
  324.          delay_short(1);

  325.          ucTempData=ucTempData<<1;
  326.    }

  327.    hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  328.    delay_short(1);
  329.    hc595_st_dr=1;
  330.    delay_short(1);

  331.    hc595_sh_dr=0;    //拉低,抗干扰就增强
  332.    hc595_st_dr=0;
  333.    hc595_ds_dr=0;

  334. }


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

  337.   switch(ucKeyStep)
  338.   {
  339.      case 1:   //按键扫描输出第ucRowRecord列低电平
  340.               if(ucRowRecord==1)  //第一列输出低电平
  341.                   {
  342.              key_dr1=0;      
  343.              key_dr2=1;
  344.              key_dr3=1;   
  345.              key_dr4=1;
  346.                   }
  347.               else if(ucRowRecord==2)  //第二列输出低电平
  348.                   {
  349.              key_dr1=1;      
  350.              key_dr2=0;
  351.              key_dr3=1;   
  352.              key_dr4=1;
  353.                   }
  354.               else if(ucRowRecord==3)  //第三列输出低电平
  355.                   {
  356.              key_dr1=1;      
  357.              key_dr2=1;
  358.              key_dr3=0;   
  359.              key_dr4=1;
  360.                   }
  361.               else   //第四列输出低电平
  362.                   {
  363.              key_dr1=1;      
  364.              key_dr2=1;
  365.              key_dr3=1;   
  366.              key_dr4=0;
  367.                   }

  368.           uiKeyTimeCnt=0;  //延时计数器清零
  369.           ucKeyStep++;     //切换到下一个运行步骤
  370.               break;

  371.      case 2:     //此处的小延时用来等待刚才列输出信号稳定,再判断输入信号。不是去抖动延时。
  372.           uiKeyTimeCnt++;
  373.                   if(uiKeyTimeCnt>1)
  374.                   {
  375.                      uiKeyTimeCnt=0;
  376.              ucKeyStep++;     //切换到下一个运行步骤
  377.                   }
  378.               break;

  379.      case 3:
  380.           if(key_sr1==1&&key_sr2==1&&key_sr3==1&&key_sr4==1)
  381.           {  
  382.              ucKeyStep=1;  //如果没有按键按下,返回到第一个运行步骤重新开始扫描
  383.              ucKeyLock=0;  //按键自锁标志清零
  384.              uiKeyTimeCnt=0; //按键去抖动延时计数器清零,此行非常巧妙     
  385.    
  386.                          ucRowRecord++;  //输出下一列
  387.                          if(ucRowRecord>4)  
  388.                          {
  389.                             ucRowRecord=1; //依次输出完四列之后,继续从第一列开始输出低电平
  390.                          }

  391.           }
  392.                   else if(ucKeyLock==0)  //有按键按下,且是第一次触发
  393.                   {
  394.                      if(key_sr1==0&&key_sr2==1&&key_sr3==1&&key_sr4==1)
  395.                          {
  396.                             uiKeyTimeCnt++;  //去抖动延时计数器
  397.                                 if(uiKeyTimeCnt>const_key_time)
  398.                                 {
  399.                                    uiKeyTimeCnt=0;
  400.                                    ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零

  401.                        if(ucRowRecord==1)  //第一列输出低电平
  402.                            {
  403.                                       ucKeySec=1;  //触发1号键 对应朱兆祺学习板的S1键
  404.                            }
  405.                        else if(ucRowRecord==2)  //第二列输出低电平
  406.                            {
  407.                                       ucKeySec=2;  //触发2号键 对应朱兆祺学习板的S2键
  408.                            }
  409.                        else if(ucRowRecord==3)  //第三列输出低电平
  410.                            {
  411.                                       ucKeySec=3;  //触发3号键 对应朱兆祺学习板的S3键
  412.                            }
  413.                        else   //第四列输出低电平
  414.                            {
  415.                                       ucKeySec=4;  //触发4号键 对应朱兆祺学习板的S4键
  416.                            }

  417.                                 }
  418.                         
  419.                          }
  420.                      else if(key_sr1==1&&key_sr2==0&&key_sr3==1&&key_sr4==1)
  421.                          {
  422.                             uiKeyTimeCnt++;  //去抖动延时计数器
  423.                                 if(uiKeyTimeCnt>const_key_time)
  424.                                 {
  425.                                    uiKeyTimeCnt=0;
  426.                                    ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
  427.                        if(ucRowRecord==1)  //第一列输出低电平
  428.                            {
  429.                                       ucKeySec=5;  //触发5号键 对应朱兆祺学习板的S5键
  430.                            }
  431.                        else if(ucRowRecord==2)  //第二列输出低电平
  432.                            {
  433.                                       ucKeySec=6;  //触发6号键 对应朱兆祺学习板的S6键
  434.                            }
  435.                        else if(ucRowRecord==3)  //第三列输出低电平
  436.                            {
  437.                                       ucKeySec=7;  //触发7号键 对应朱兆祺学习板的S7键
  438.                            }
  439.                        else   //第四列输出低电平
  440.                            {
  441.                                       ucKeySec=8;  //触发8号键 对应朱兆祺学习板的S8键
  442.                            }
  443.                                 }
  444.                         
  445.                          }
  446.                      else if(key_sr1==1&&key_sr2==1&&key_sr3==0&&key_sr4==1)
  447.                          {
  448.                             uiKeyTimeCnt++;  //去抖动延时计数器
  449.                                 if(uiKeyTimeCnt>const_key_time)
  450.                                 {
  451.                                    uiKeyTimeCnt=0;
  452.                                    ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
  453.                        if(ucRowRecord==1)  //第一列输出低电平
  454.                            {
  455.                                       ucKeySec=9;  //触发9号键 对应朱兆祺学习板的S9键
  456.                            }
  457.                        else if(ucRowRecord==2)  //第二列输出低电平
  458.                            {
  459.                                       ucKeySec=10;  //触发10号键 对应朱兆祺学习板的S10键
  460.                            }
  461.                        else if(ucRowRecord==3)  //第三列输出低电平
  462.                            {
  463.                                       ucKeySec=11;  //触发11号键 对应朱兆祺学习板的S11键
  464.                            }
  465.                        else   //第四列输出低电平
  466.                            {
  467.                                       ucKeySec=12;  //触发12号键 对应朱兆祺学习板的S12键
  468.                            }
  469.                                 }
  470.                         
  471.                          }
  472.                      else if(key_sr1==1&&key_sr2==1&&key_sr3==1&&key_sr4==0)
  473.                          {
  474.                             uiKeyTimeCnt++;  //去抖动延时计数器
  475.                                 if(uiKeyTimeCnt>const_key_time)
  476.                                 {
  477.                                    uiKeyTimeCnt=0;
  478.                                    ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
  479.                        if(ucRowRecord==1)  //第一列输出低电平
  480.                            {
  481.                                       ucKeySec=13;  //触发13号键 对应朱兆祺学习板的S13键
  482.                            }
  483.                        else if(ucRowRecord==2)  //第二列输出低电平
  484.                            {
  485.                                       ucKeySec=14;  //触发14号键 对应朱兆祺学习板的S14键
  486.                            }
  487.                        else if(ucRowRecord==3)  //第三列输出低电平
  488.                            {
  489.                                       ucKeySec=15;  //触发15号键 对应朱兆祺学习板的S15键
  490.                            }
  491.                        else   //第四列输出低电平
  492.                            {
  493.                                       ucKeySec=16;  //触发16号键 对应朱兆祺学习板的S16键
  494.                            }
  495.                                 }
  496.                         
  497.                          }
  498.                   
  499.                   }
  500.               break;

  501.   }


  502. }

  503. /* 注释三:
  504. *  按键服务程序操作的精髓在于根据当前系统处于什么窗口下,在此窗口下的运算符处于
  505. *  什么状态,然后紧紧围绕着不同的窗口ucWd,不同的ucOperator来执行不同的操作。
  506. */
  507. void key_service() //第三区 按键服务的应用程序
  508. {
  509.   switch(ucKeySec) //按键服务状态切换
  510.   {
  511.     case 1:// 1号键 对应朱兆祺学习板的S1键
  512.           number_key_input(1);  //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
  513.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  514.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  515.           break;        
  516.     case 2:// 2号键 对应朱兆祺学习板的S2键
  517.           number_key_input(2);  //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
  518.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  519.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  520.           break;     
  521.     case 3:// 3号键 对应朱兆祺学习板的S3键
  522.           number_key_input(3);  //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
  523.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  524.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  525.           break;         
  526.     case 4:// 4号键 对应朱兆祺学习板的S4键
  527.           number_key_input(4);  //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
  528.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  529.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  530.           break;   
  531.     case 5:// 5号键 对应朱兆祺学习板的S5键
  532.           number_key_input(5);  //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
  533.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  534.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  535.           break;   
  536.     case 6:// 6号键 对应朱兆祺学习板的S6键
  537.           number_key_input(6);  //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
  538.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  539.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  540.           break;   
  541.     case 7:// 7号键 对应朱兆祺学习板的S7键
  542.           number_key_input(7);  //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
  543.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  544.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  545.           break;   
  546.     case 8:// 8号键 对应朱兆祺学习板的S8键
  547.           number_key_input(8);  //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
  548.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  549.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  550.           break;   
  551.     case 9:// 9号键 对应朱兆祺学习板的S9键
  552.           number_key_input(9);  //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
  553.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  554.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  555.           break;   
  556.     case 10:// 把这个按键专门用来输入数字0    对应朱兆祺学习板的S10键
  557.           number_key_input(0);  //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
  558.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  559.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  560.           break;   
  561.     case 11:// 11号键 对应朱兆祺学习板的S11键

  562.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  563.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  564.           break;   
  565.     case 12:// 12号键 对应朱兆祺学习板的S12键

  566.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  567.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  568.           break;   
  569.     case 13:// 13号键 加号按键  对应朱兆祺学习板的S13键
  570.           switch(ucWd)
  571.                  {
  572.                   case 1:   //在原始数据和运算结果的窗口下
  573.                    ucOperator=1; //加法
  574.                    ulOther=ulSource;  //第二个运算数默认等于原始数
  575.                    ucDisplayUpdate=1;  //刷新显示窗口
  576.                                  break;
  577.                   case 2:   //在第二个参与运算数据的窗口下
  578.                    ulResult=ulSource+ulOther;//连加
  579.                    ulSource=ulResult; //下一次运算的原始数据默认为当前运算结果,方便连加功能
  580.                    ucWd=1;        //切换到第一个窗口
  581.                    ucDisplayUpdate=1;  //刷新显示窗口
  582.                            break;
  583.           }

  584.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  585.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  586.           break;   
  587.     case 14:// 14号键 等于号按键  对应朱兆祺学习板的S14键  
  588.           switch(ucWd)
  589.                  {
  590.                   case 1:   //在原始数据和运算结果的窗口下
  591.                            switch(ucOperator)  //根据不同的运算符号进行不同的操作
  592.                            {
  593.                                    case 0:  //无运算符号

  594.                                         break;
  595.                                    case 1:  //加法
  596.                             ulResult=ulSource+ulOther;//连加
  597.                             ulSource=ulResult; //下一次运算的原始数据默认为当前运算结果,方便连加功能
  598.                             ucDisplayUpdate=1;  //刷新显示窗口
  599.                                         break;
  600.                                    case 2:  //减法  本程序没有减法功能,如果读者想增加减法程序,可以按键这个框架添加下去

  601.                                         break;
  602.                        
  603.                            }
  604.                                  break;
  605.                   case 2:   //在第二个参与运算数据的窗口下
  606.                            switch(ucOperator)  //根据不同的运算符号进行不同的操作
  607.                            {
  608.                                    case 1:  //加法
  609.                             ulResult=ulSource+ulOther;//连加
  610.                             ulSource=ulResult; //下一次运算的原始数据默认为当前运算结果,方便连加功能
  611.                             ucWd=1;        //切换到第一个窗口
  612.                             ucDisplayUpdate=1;  //刷新显示窗口
  613.                                         break;
  614.                                    case 2:  //减法  本程序没有减法功能,如果读者想增加减法程序,可以按键这个框架添加下去

  615.                                         break;
  616.                        
  617.                            }
  618.                            break;
  619.           }

  620.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  621.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  622.           break;   
  623.     case 15:// 15号键 对应朱兆祺学习板的S15键

  624.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  625.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  626.           break;   
  627.     case 16:// 16号键 清除按键 相当于复位的功能。重新输入数据  对应朱兆祺学习板的S16键
  628.               ulSource=0;
  629.                   ulOther=0;
  630.           ulResult=0;
  631.                   ucOperator=0;
  632.           ucWd=1;        //切换到第一个窗口
  633.           ucDisplayUpdate=1;  //刷新显示窗口
  634.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  635.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  636.           break;   
  637.   }               
  638. }


  639. /* 注释四:
  640. * 此处参与运算的输入数字ucWhichKey记得用最大变量类型unsigned long,可以避免数据溢出等错误
  641. */
  642. void number_key_input(unsigned long ucWhichKey)  //由于数字按键的代码相似度高,因此封装在这个函数里
  643. {


  644.     switch(ucWd)
  645.            {
  646.            case 1:   //在原始数据和运算结果的窗口下
  647.             switch(ucOperator)  //根据不同的运算符号进行不同的操作
  648.                         {
  649.                            case 0:  //无运算符号  按键输入原始数据,比如被加输
  650.                                 if(ulSource<=9999999) //最大只能输入8位数
  651.                                         {
  652.                        ulSource=ulSource*10+ucWhichKey;  //十进制的数值移位方法。
  653.                                         }
  654.                                 break;
  655.                            default:  //在已经按下了运算符号的情况下
  656.                     ulOther=0;  //第二个运算数先清零,再输入新的数据,然后马上切换到第2个窗口下
  657.                     ulOther=ucWhichKey;
  658.                     ucWd=2; //马上切换到第二个窗口下
  659.                                 break;
  660.                        
  661.                         }

  662.             ucDisplayUpdate=1;  //刷新显示窗口
  663.                         break;
  664.            case 2:   //在第二个参与运算数据的窗口下   按键输入第二个参与运算的数据
  665.                         if(ulOther<=9999999) //最大只能输入8位数
  666.                         {
  667.                ulOther=ulOther*10+ucWhichKey;  //十进制的数值移位方法。
  668.                     }

  669.             ucDisplayUpdate=1;  //刷新显示窗口
  670.                     break;
  671.     }

  672. }


  673. void T0_time() interrupt 1
  674. {
  675.   TF0=0;  //清除中断标志
  676.   TR0=0; //关中断
  677.   key_scan(); //放在定时中断里的按键扫描函数
  678.   if(uiVoiceCnt!=0)
  679.   {
  680.      uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
  681.          beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  682.   }
  683.   else
  684.   {
  685.      ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
  686.            beep_dr=1;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  687.   }

  688.   display_drive();  //放在定时中断里的数码管驱动函数

  689. /* 注释五:
  690. *  注意,此处的重装初始值不能太大,否则动态扫描数码管的速度就不够。我把原来常用的2000改成了500。
  691. */
  692.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  693.   TL0=0x0b;
  694.   TR0=1;  //开中断
  695. }

  696. void delay_short(unsigned int uiDelayShort)
  697. {
  698.    unsigned int i;  
  699.    for(i=0;i<uiDelayShort;i++)
  700.    {
  701.      ;   //一个分号相当于执行一条空语句
  702.    }
  703. }


  704. void delay_long(unsigned int uiDelayLong)
  705. {
  706.    unsigned int i;
  707.    unsigned int j;
  708.    for(i=0;i<uiDelayLong;i++)
  709.    {
  710.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  711.           {
  712.              ; //一个分号相当于执行一条空语句
  713.           }
  714.    }
  715. }


  716. void initial_myself()  //第一区 初始化单片机
  717. {

  718.   led_dr=0;
  719.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

  720.   hc595_drive(0x00,0x00);
  721.   TMOD=0x01;  //设置定时器0为工作方式1

  722.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  723.   TL0=0x0b;
  724. }
  725. void initial_peripheral() //第二区 初始化外围
  726. {


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



  730. }
复制代码


总结陈词:
这节讲了加法简易计算器的程序项目。为了让读者理解运动,按键,显示是如何有规律关联起来的,下节会继续讲一个相关的小项目程序。欲知详情,请听下回分解-----数码管作为仪表盘显示跑马灯的方向,速度和运行状态。

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

作者: 东东    时间: 2014-3-28 18:59
不错,赞一个!
作者: jianhong_wu    时间: 2014-4-3 01:39
第三十七节:数码管作为仪表盘显示跑马灯的方向,速度和运行状态。

开场白:
    我在第24节中讲过按键控制跑马灯的方向,速度和运行状态的项目程序,只可惜那个程序不能直观地显示运行中的三种状态,这节我决定在24节的基础上,增加一个数码管显示作为类似汽车仪表盘的界面,实时显示跑马灯的方向,速度,和运行状态。
这一节要教会大家一个知识点:继续加深理解运动,按键与数码管三者之间的关联程序框架。

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

(1)硬件平台:
基于坚鸿51单片机学习板。用S1键作为控制跑马灯的方向按键,S5键作为控制跑马灯方向的加速度按键,S9键作为控制跑马灯方向的减速度按键,S13键作为控制跑马灯方向的启动或者暂停按键。记得把输出线P0.4一直输出低电平,模拟独立按键的触发地GND。

(2)实现功能:
跑马灯运行:第1个至第8个LED灯一直不亮。在第9个至第16个LED灯,依次逐个亮灯并且每次只能亮一个灯。每按一次独立按键S13键,原来运行的跑马灯会暂停,原来暂停的跑马灯会运行。用S1来改变方向。用S5和S9来改变速度,每按一次按键的递增或者递减以10为单位。
数码管显示:本程序只有1个窗口,这个窗口分成3个局部显示。8,7,6位数码管显示运行状态,启动时显示“on”,停止时显示“oFF”。5位数码管显示数码管方向,正向显示“n”,反向显示“U”。4,3,2,1位数码管显示速度。数值越大速度越慢,最慢的速度是550,最快的速度是50。

(3)源代码讲解如下:
  1. #include "REG52.H"


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


  3. #define const_key_time1  20    //按键去抖动延时的时间
  4. #define const_key_time2  20    //按键去抖动延时的时间
  5. #define const_key_time3  20    //按键去抖动延时的时间
  6. #define const_key_time4  20    //按键去抖动延时的时间


  7. void initial_myself();   
  8. void initial_peripheral();
  9. void delay_short(unsigned int uiDelayShort);
  10. void delay_long(unsigned int uiDelaylong);

  11. //驱动数码管的74HC595
  12. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);  
  13. void display_drive(); //显示数码管字模的驱动函数
  14. void display_service(); //显示的窗口菜单服务程序

  15. //驱动LED的74HC595
  16. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
  17. void led_flicker_09_16(); //第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
  18. void led_update();  //LED更新函数

  19. void T0_time();  //定时中断函数
  20. void key_service(); //按键服务的应用程序
  21. void key_scan();//按键扫描函数 放在定时中断里


  22. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
  23. sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
  24. sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键
  25. sbit key_sr3=P0^2; //对应朱兆祺学习板的S9键
  26. sbit key_sr4=P0^3; //对应朱兆祺学习板的S13键

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

  28. sbit led_dr=P3^5;  


  29. sbit dig_hc595_sh_dr=P2^0;     //数码管的74HC595程序
  30. sbit dig_hc595_st_dr=P2^1;  
  31. sbit dig_hc595_ds_dr=P2^2;  

  32. sbit hc595_sh_dr=P2^3;    //LED灯的74HC595程序
  33. sbit hc595_st_dr=P2^4;  
  34. sbit hc595_ds_dr=P2^5;  


  35. unsigned char ucKeySec=0;   //被触发的按键编号

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

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

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


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

  44. unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器

  45. unsigned char ucLed_dr1=0;   //代表16个灯的亮灭状态,0代表灭,1代表亮
  46. unsigned char ucLed_dr2=0;
  47. unsigned char ucLed_dr3=0;
  48. unsigned char ucLed_dr4=0;
  49. unsigned char ucLed_dr5=0;
  50. unsigned char ucLed_dr6=0;
  51. unsigned char ucLed_dr7=0;
  52. unsigned char ucLed_dr8=0;
  53. unsigned char ucLed_dr9=0;
  54. unsigned char ucLed_dr10=0;
  55. unsigned char ucLed_dr11=0;
  56. unsigned char ucLed_dr12=0;
  57. unsigned char ucLed_dr13=0;
  58. unsigned char ucLed_dr14=0;
  59. unsigned char ucLed_dr15=0;
  60. unsigned char ucLed_dr16=0;

  61. unsigned char ucLed_update=0;  //刷新变量。每次更改LED灯的状态都要更新一次。


  62. unsigned char ucLedStep_09_16=0; //第9个至第16个LED跑马灯的步骤变量
  63. unsigned int  uiTimeCnt_09_16=0; //第9个至第16个LED跑马灯的统计定时中断次数的延时计数器

  64. unsigned char ucLedStatus16_09=0;   //代表底层74HC595输出状态的中间变量
  65. unsigned char ucLedStatus08_01=0;   //代表底层74HC595输出状态的中间变量

  66. unsigned char ucLedDirFlag=0;   //方向变量,把按键与跑马灯关联起来的核心变量,0代表正方向,1代表反方向
  67. unsigned int  uiSetTimeLevel_09_16=300;  //速度变量,此数值越大速度越慢,此数值越小速度越快。
  68. unsigned char ucLedStartFlag=1;   //启动和暂停的变量,0代表暂停,1代表启动



  69. unsigned char ucDigShow8;  //第8位数码管要显示的内容
  70. unsigned char ucDigShow7;  //第7位数码管要显示的内容
  71. unsigned char ucDigShow6;  //第6位数码管要显示的内容
  72. unsigned char ucDigShow5;  //第5位数码管要显示的内容
  73. unsigned char ucDigShow4;  //第4位数码管要显示的内容
  74. unsigned char ucDigShow3;  //第3位数码管要显示的内容
  75. unsigned char ucDigShow2;  //第2位数码管要显示的内容
  76. unsigned char ucDigShow1;  //第1位数码管要显示的内容


  77. unsigned char ucDigDot8;  //数码管8的小数点是否显示的标志
  78. unsigned char ucDigDot7;  //数码管7的小数点是否显示的标志
  79. unsigned char ucDigDot6;  //数码管6的小数点是否显示的标志
  80. unsigned char ucDigDot5;  //数码管5的小数点是否显示的标志
  81. unsigned char ucDigDot4;  //数码管4的小数点是否显示的标志
  82. unsigned char ucDigDot3;  //数码管3的小数点是否显示的标志
  83. unsigned char ucDigDot2;  //数码管2的小数点是否显示的标志
  84. unsigned char ucDigDot1;  //数码管1的小数点是否显示的标志

  85. unsigned char ucDigShowTemp=0; //临时中间变量
  86. unsigned char ucDisplayDriveStep=1;  //动态扫描数码管的步骤变量

  87. unsigned char ucWd1Part1Update=1;  //窗口1的局部1更新显示变量
  88. unsigned char ucWd1Part2Update=1;  //窗口1的局部2更新显示变量
  89. unsigned char ucWd1Part3Update=1;  //窗口1的局部3更新显示变量


  90. //根据原理图得出的共阴数码管字模表
  91. code unsigned char dig_table[]=
  92. {
  93. 0x3f,  //0       序号0
  94. 0x06,  //1       序号1
  95. 0x5b,  //2       序号2
  96. 0x4f,  //3       序号3
  97. 0x66,  //4       序号4
  98. 0x6d,  //5       序号5
  99. 0x7d,  //6       序号6
  100. 0x07,  //7       序号7
  101. 0x7f,  //8       序号8
  102. 0x6f,  //9       序号9
  103. 0x00,  //无      序号10
  104. 0x40,  //-       序号11
  105. 0x73,  //P       序号12
  106. 0x5c,  //o       序号13
  107. 0x71,  //F       序号14
  108. 0x3e,  //U       序号15
  109. 0x37,  //n       序号16
  110. };

  111. void main()
  112.   {
  113.    initial_myself();  
  114.    delay_long(100);   
  115.    initial_peripheral();
  116.    while(1)  
  117.    {
  118.       key_service(); //按键服务的应用程序
  119.       display_service(); //显示的窗口菜单服务程序

  120.       led_flicker_09_16(); //第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
  121.           led_update();  //LED更新函数
  122.    }

  123. }



  124. /* 注释一:
  125. * 由于本程序只有1个窗口,而这个窗口又分成3个局部,因此可以省略去窗口变量uWd,
  126. * 只用三个局部变量ucWdxPartyUpdate就可以了。
  127. */

  128. void display_service() //显示的窗口菜单服务程序
  129. {


  130.     if(ucWd1Part1Update==1) //更新显示当前系统是处于运行还是暂停的状态
  131.         {
  132.        ucWd1Part1Update=0; //及时把更新变量清零,防止一直进来更新
  133.            if(ucLedStartFlag==1)  //启动,显示on
  134.            {
  135.                ucDigShow8=13;  //显示o
  136.            ucDigShow7=16;  //显示n
  137.            ucDigShow6=10;  //显示空
  138.            }
  139.            else  //暂停,显示oFF
  140.            {
  141.                       ucDigShow8=13;  //显示o
  142.            ucDigShow7=14;  //显示F
  143.            ucDigShow6=14;  //显示F
  144.            }
  145.         }

  146.     if(ucWd1Part2Update==1) //更新显示当前系统是处于正方向还是反方向
  147.         {
  148.        ucWd1Part2Update=0; //及时把更新变量清零,防止一直进来更新
  149.            if(ucLedDirFlag==0)  //正方向,向上,显示n
  150.            {
  151.                ucDigShow5=16;  //显示n
  152.            }
  153.            else  //反方向,向下,显示U
  154.            {
  155.                ucDigShow5=15;  //显示U
  156.            }
  157.         }

  158.     if(ucWd1Part3Update==1) //更新显示当前系统的速度,此数值越大速度越慢,此数值越小速度越快。
  159.         {
  160.        ucWd1Part3Update=0; //及时把更新变量清零,防止一直进来更新

  161.            ucDigShow4=10;  //显示空  这一位不用,作为空格

  162.            if(uiSetTimeLevel_09_16>=100)
  163.            {
  164.           ucDigShow3=uiSetTimeLevel_09_16/100;     //显示速度的百位
  165.            }
  166.            else
  167.            {
  168.           ucDigShow3=10;     //显示空
  169.            }

  170.            if(uiSetTimeLevel_09_16>=10)
  171.            {
  172.           ucDigShow2=uiSetTimeLevel_09_16%100/10;  //显示速度的十位
  173.            }
  174.            else
  175.            {
  176.           ucDigShow2=10;     //显示空
  177.            }

  178.        ucDigShow1=uiSetTimeLevel_09_16%10;      //显示速度的个位
  179.         }


  180.    


  181. }


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

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

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

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

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

  244. }


  245. void key_service() //按键服务的应用程序
  246. {
  247.   switch(ucKeySec) //按键服务状态切换
  248.   {
  249.     case 1:// 改变跑马灯方向的按键 对应朱兆祺学习板的S1键

  250.           if(ucLedDirFlag==0) //通过中间变量改变跑马灯的方向
  251.                   {
  252.                      ucLedDirFlag=1;
  253.                   }
  254.                   else
  255.                   {
  256.                            ucLedDirFlag=0;
  257.                   }

  258.           ucWd1Part2Update=1; //及时更新显示方向

  259.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  260.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  261.           break;   
  262.    
  263.     case 2:// 加速按键 对应朱兆祺学习板的S5键 uiSetTimeLevel_09_16越小速度越快
  264.           uiSetTimeLevel_09_16=uiSetTimeLevel_09_16-10;
  265.                   if(uiSetTimeLevel_09_16<50)  //最快限定在50
  266.                   {
  267.                       uiSetTimeLevel_09_16=50;
  268.                   }

  269.           ucWd1Part3Update=1; //及时更新显示速度

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

  273.     case 3:// 减速按键 对应朱兆祺学习板的S9键  uiSetTimeLevel_09_16越大速度越慢
  274.           uiSetTimeLevel_09_16=uiSetTimeLevel_09_16+10;
  275.                   if(uiSetTimeLevel_09_16>550)  //最慢限定在550
  276.                   {
  277.                       uiSetTimeLevel_09_16=550;
  278.                   }
  279.           ucWd1Part3Update=1; //及时更新显示速度
  280.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  281.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  282.           break;         
  283.          
  284.     case 4:// 启动和暂停按键 对应朱兆祺学习板的S13键  ucLedStartFlag为0时代表暂停,为1时代表启动

  285.               if(ucLedStartFlag==1)  //启动和暂停两种状态循环切换
  286.                   {
  287.                      ucLedStartFlag=0;
  288.                   }
  289.                   else                   //启动和暂停两种状态循环切换
  290.                   {
  291.                            ucLedStartFlag=1;
  292.                   }
  293.           ucWd1Part1Update=1; //及时更新显示系统的运行状态,是运行还是暂停.
  294.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  295.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  296.           break;   
  297.   }               
  298. }




  299. void led_update()  //LED更新函数
  300. {

  301.    if(ucLed_update==1)
  302.    {
  303.        ucLed_update=0;   //及时清零,让它产生只更新一次的效果,避免一直更新。

  304.        if(ucLed_dr1==1)
  305.            {
  306.               ucLedStatus08_01=ucLedStatus08_01|0x01;
  307.            }
  308.            else
  309.            {
  310.               ucLedStatus08_01=ucLedStatus08_01&0xfe;
  311.            }

  312.        if(ucLed_dr2==1)
  313.            {
  314.               ucLedStatus08_01=ucLedStatus08_01|0x02;
  315.            }
  316.            else
  317.            {
  318.               ucLedStatus08_01=ucLedStatus08_01&0xfd;
  319.            }

  320.        if(ucLed_dr3==1)
  321.            {
  322.               ucLedStatus08_01=ucLedStatus08_01|0x04;
  323.            }
  324.            else
  325.            {
  326.               ucLedStatus08_01=ucLedStatus08_01&0xfb;
  327.            }

  328.        if(ucLed_dr4==1)
  329.            {
  330.               ucLedStatus08_01=ucLedStatus08_01|0x08;
  331.            }
  332.            else
  333.            {
  334.               ucLedStatus08_01=ucLedStatus08_01&0xf7;
  335.            }


  336.        if(ucLed_dr5==1)
  337.            {
  338.               ucLedStatus08_01=ucLedStatus08_01|0x10;
  339.            }
  340.            else
  341.            {
  342.               ucLedStatus08_01=ucLedStatus08_01&0xef;
  343.            }


  344.        if(ucLed_dr6==1)
  345.            {
  346.               ucLedStatus08_01=ucLedStatus08_01|0x20;
  347.            }
  348.            else
  349.            {
  350.               ucLedStatus08_01=ucLedStatus08_01&0xdf;
  351.            }


  352.        if(ucLed_dr7==1)
  353.            {
  354.               ucLedStatus08_01=ucLedStatus08_01|0x40;
  355.            }
  356.            else
  357.            {
  358.               ucLedStatus08_01=ucLedStatus08_01&0xbf;
  359.            }


  360.        if(ucLed_dr8==1)
  361.            {
  362.               ucLedStatus08_01=ucLedStatus08_01|0x80;
  363.            }
  364.            else
  365.            {
  366.               ucLedStatus08_01=ucLedStatus08_01&0x7f;
  367.            }

  368.        if(ucLed_dr9==1)
  369.            {
  370.               ucLedStatus16_09=ucLedStatus16_09|0x01;
  371.            }
  372.            else
  373.            {
  374.               ucLedStatus16_09=ucLedStatus16_09&0xfe;
  375.            }

  376.        if(ucLed_dr10==1)
  377.            {
  378.               ucLedStatus16_09=ucLedStatus16_09|0x02;
  379.            }
  380.            else
  381.            {
  382.               ucLedStatus16_09=ucLedStatus16_09&0xfd;
  383.            }

  384.        if(ucLed_dr11==1)
  385.            {
  386.               ucLedStatus16_09=ucLedStatus16_09|0x04;
  387.            }
  388.            else
  389.            {
  390.               ucLedStatus16_09=ucLedStatus16_09&0xfb;
  391.            }

  392.        if(ucLed_dr12==1)
  393.            {
  394.               ucLedStatus16_09=ucLedStatus16_09|0x08;
  395.            }
  396.            else
  397.            {
  398.               ucLedStatus16_09=ucLedStatus16_09&0xf7;
  399.            }


  400.        if(ucLed_dr13==1)
  401.            {
  402.               ucLedStatus16_09=ucLedStatus16_09|0x10;
  403.            }
  404.            else
  405.            {
  406.               ucLedStatus16_09=ucLedStatus16_09&0xef;
  407.            }


  408.        if(ucLed_dr14==1)
  409.            {
  410.               ucLedStatus16_09=ucLedStatus16_09|0x20;
  411.            }
  412.            else
  413.            {
  414.               ucLedStatus16_09=ucLedStatus16_09&0xdf;
  415.            }


  416.        if(ucLed_dr15==1)
  417.            {
  418.               ucLedStatus16_09=ucLedStatus16_09|0x40;
  419.            }
  420.            else
  421.            {
  422.               ucLedStatus16_09=ucLedStatus16_09&0xbf;
  423.            }


  424.        if(ucLed_dr16==1)
  425.            {
  426.               ucLedStatus16_09=ucLedStatus16_09|0x80;
  427.            }
  428.            else
  429.            {
  430.               ucLedStatus16_09=ucLedStatus16_09&0x7f;
  431.            }

  432.        hc595_drive(ucLedStatus16_09,ucLedStatus08_01);  //74HC595底层驱动函数

  433.    }
  434. }


  435. void display_drive()  
  436. {
  437.    //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
  438.    switch(ucDisplayDriveStep)
  439.    {
  440.       case 1:  //显示第1位
  441.            ucDigShowTemp=dig_table[ucDigShow1];
  442.                    if(ucDigDot1==1)
  443.                    {
  444.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  445.                    }
  446.            dig_hc595_drive(ucDigShowTemp,0xfe);
  447.                break;
  448.       case 2:  //显示第2位
  449.            ucDigShowTemp=dig_table[ucDigShow2];
  450.                    if(ucDigDot2==1)
  451.                    {
  452.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  453.                    }
  454.            dig_hc595_drive(ucDigShowTemp,0xfd);
  455.                break;
  456.       case 3:  //显示第3位
  457.            ucDigShowTemp=dig_table[ucDigShow3];
  458.                    if(ucDigDot3==1)
  459.                    {
  460.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  461.                    }
  462.            dig_hc595_drive(ucDigShowTemp,0xfb);
  463.                break;
  464.       case 4:  //显示第4位
  465.            ucDigShowTemp=dig_table[ucDigShow4];
  466.                    if(ucDigDot4==1)
  467.                    {
  468.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  469.                    }
  470.            dig_hc595_drive(ucDigShowTemp,0xf7);
  471.                break;
  472.       case 5:  //显示第5位
  473.            ucDigShowTemp=dig_table[ucDigShow5];
  474.                    if(ucDigDot5==1)
  475.                    {
  476.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  477.                    }
  478.            dig_hc595_drive(ucDigShowTemp,0xef);
  479.                break;
  480.       case 6:  //显示第6位
  481.            ucDigShowTemp=dig_table[ucDigShow6];
  482.                    if(ucDigDot6==1)
  483.                    {
  484.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  485.                    }
  486.            dig_hc595_drive(ucDigShowTemp,0xdf);
  487.                break;
  488.       case 7:  //显示第7位
  489.            ucDigShowTemp=dig_table[ucDigShow7];
  490.                    if(ucDigDot7==1)
  491.                    {
  492.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  493.            }
  494.            dig_hc595_drive(ucDigShowTemp,0xbf);
  495.                break;
  496.       case 8:  //显示第8位
  497.            ucDigShowTemp=dig_table[ucDigShow8];
  498.                    if(ucDigDot8==1)
  499.                    {
  500.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  501.                    }
  502.            dig_hc595_drive(ucDigShowTemp,0x7f);
  503.                break;
  504.    }

  505.    ucDisplayDriveStep++;
  506.    if(ucDisplayDriveStep>8)  //扫描完8个数码管后,重新从第一个开始扫描
  507.    {
  508.      ucDisplayDriveStep=1;
  509.    }



  510. }


  511. //数码管的74HC595驱动函数
  512. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
  513. {
  514.    unsigned char i;
  515.    unsigned char ucTempData;
  516.    dig_hc595_sh_dr=0;
  517.    dig_hc595_st_dr=0;

  518.    ucTempData=ucDigStatusTemp16_09;  //先送高8位
  519.    for(i=0;i<8;i++)
  520.    {
  521.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  522.          else dig_hc595_ds_dr=0;

  523.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  524.          delay_short(1);
  525.          dig_hc595_sh_dr=1;
  526.          delay_short(1);

  527.          ucTempData=ucTempData<<1;
  528.    }

  529.    ucTempData=ucDigStatusTemp08_01;  //再先送低8位
  530.    for(i=0;i<8;i++)
  531.    {
  532.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  533.          else dig_hc595_ds_dr=0;

  534.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  535.          delay_short(1);
  536.          dig_hc595_sh_dr=1;
  537.          delay_short(1);

  538.          ucTempData=ucTempData<<1;
  539.    }

  540.    dig_hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  541.    delay_short(1);
  542.    dig_hc595_st_dr=1;
  543.    delay_short(1);

  544.    dig_hc595_sh_dr=0;    //拉低,抗干扰就增强
  545.    dig_hc595_st_dr=0;
  546.    dig_hc595_ds_dr=0;

  547. }


  548. //LED灯的74HC595驱动函数
  549. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
  550. {
  551.    unsigned char i;
  552.    unsigned char ucTempData;
  553.    hc595_sh_dr=0;
  554.    hc595_st_dr=0;

  555.    ucTempData=ucLedStatusTemp16_09;  //先送高8位
  556.    for(i=0;i<8;i++)
  557.    {
  558.          if(ucTempData>=0x80)hc595_ds_dr=1;
  559.          else hc595_ds_dr=0;

  560.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  561.          delay_short(1);
  562.          hc595_sh_dr=1;
  563.          delay_short(1);

  564.          ucTempData=ucTempData<<1;
  565.    }

  566.    ucTempData=ucLedStatusTemp08_01;  //再先送低8位
  567.    for(i=0;i<8;i++)
  568.    {
  569.          if(ucTempData>=0x80)hc595_ds_dr=1;
  570.          else hc595_ds_dr=0;

  571.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  572.          delay_short(1);
  573.          hc595_sh_dr=1;
  574.          delay_short(1);

  575.          ucTempData=ucTempData<<1;
  576.    }

  577.    hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  578.    delay_short(1);
  579.    hc595_st_dr=1;
  580.    delay_short(1);

  581.    hc595_sh_dr=0;    //拉低,抗干扰就增强
  582.    hc595_st_dr=0;
  583.    hc595_ds_dr=0;

  584. }


  585. void led_flicker_09_16() //第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
  586. {
  587.   if(ucLedStartFlag==1)  //此变量为1时代表启动
  588.   {
  589.      switch(ucLedStep_09_16)
  590.      {
  591.      case 0:
  592.            if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
  593.            {
  594.                uiTimeCnt_09_16=0; //时间计数器清零

  595.                            if(ucLedDirFlag==0)  //正方向
  596.                            {
  597.                   ucLed_dr16=0;  //第16个灭
  598.                   ucLed_dr9=1;  //第9个亮

  599.                   ucLed_update=1;  //更新显示
  600.                   ucLedStep_09_16=1; //切换到下一个步骤
  601.                            }
  602.                            else  //反方向
  603.                            {
  604.                   ucLed_dr15=1;  //第15个亮
  605.                   ucLed_dr16=0;  //第16个灭

  606.                   ucLed_update=1;  //更新显示
  607.                   ucLedStep_09_16=7; //返回上一个步骤
  608.                            }
  609.            }
  610.            break;
  611.      case 1:
  612.            if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
  613.            {
  614.                uiTimeCnt_09_16=0; //时间计数器清零

  615.                            if(ucLedDirFlag==0)  //正方向
  616.                            {
  617.                   ucLed_dr9=0;  //第9个灭
  618.                   ucLed_dr10=1;  //第10个亮

  619.                   ucLed_update=1;  //更新显示
  620.                   ucLedStep_09_16=2; //切换到下一个步骤
  621.                            }
  622.                            else  //反方向
  623.                            {
  624.                   ucLed_dr16=1;  //第16个亮
  625.                   ucLed_dr9=0;  //第9个灭

  626.                   ucLed_update=1;  //更新显示
  627.                   ucLedStep_09_16=0; //返回上一个步骤
  628.                            }
  629.            }
  630.            break;
  631.      case 2:
  632.            if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
  633.            {
  634.                uiTimeCnt_09_16=0; //时间计数器清零

  635.                            if(ucLedDirFlag==0)  //正方向
  636.                            {
  637.                   ucLed_dr10=0;  //第10个灭
  638.                   ucLed_dr11=1;  //第11个亮

  639.                   ucLed_update=1;  //更新显示
  640.                   ucLedStep_09_16=3; //切换到下一个步骤
  641.                            }
  642.                            else  //反方向
  643.                            {
  644.                   ucLed_dr9=1;  //第9个亮
  645.                   ucLed_dr10=0;  //第10个灭

  646.                   ucLed_update=1;  //更新显示
  647.                   ucLedStep_09_16=1; //返回上一个步骤
  648.                            }
  649.            }
  650.            break;
  651.      case 3:
  652.            if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
  653.            {
  654.                uiTimeCnt_09_16=0; //时间计数器清零

  655.                            if(ucLedDirFlag==0)  //正方向
  656.                            {
  657.                   ucLed_dr11=0;  //第11个灭
  658.                   ucLed_dr12=1;  //第12个亮

  659.                   ucLed_update=1;  //更新显示
  660.                   ucLedStep_09_16=4; //切换到下一个步骤
  661.                            }
  662.                            else  //反方向
  663.                            {
  664.                   ucLed_dr10=1;  //第10个亮
  665.                   ucLed_dr11=0;  //第11个灭

  666.                   ucLed_update=1;  //更新显示
  667.                   ucLedStep_09_16=2; //返回上一个步骤
  668.                            }
  669.            }
  670.            break;
  671.      case 4:
  672.            if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
  673.            {
  674.                uiTimeCnt_09_16=0; //时间计数器清零

  675.                            if(ucLedDirFlag==0)  //正方向
  676.                            {
  677.                   ucLed_dr12=0;  //第12个灭
  678.                   ucLed_dr13=1;  //第13个亮

  679.                   ucLed_update=1;  //更新显示
  680.                   ucLedStep_09_16=5; //切换到下一个步骤
  681.                            }
  682.                            else  //反方向
  683.                            {
  684.                   ucLed_dr11=1;  //第11个亮
  685.                   ucLed_dr12=0;  //第12个灭

  686.                   ucLed_update=1;  //更新显示
  687.                   ucLedStep_09_16=3; //返回上一个步骤
  688.                            }
  689.            }
  690.            break;
  691.      case 5:
  692.            if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
  693.            {
  694.                uiTimeCnt_09_16=0; //时间计数器清零

  695.                            if(ucLedDirFlag==0)  //正方向
  696.                            {
  697.                   ucLed_dr13=0;  //第13个灭
  698.                   ucLed_dr14=1;  //第14个亮

  699.                   ucLed_update=1;  //更新显示
  700.                   ucLedStep_09_16=6; //切换到下一个步骤
  701.                            }
  702.                            else  //反方向
  703.                            {
  704.                   ucLed_dr12=1;  //第12个亮
  705.                   ucLed_dr13=0;  //第13个灭

  706.                   ucLed_update=1;  //更新显示
  707.                   ucLedStep_09_16=4; //返回上一个步骤
  708.                            }
  709.            }
  710.            break;
  711.      case 6:
  712.            if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
  713.            {
  714.                uiTimeCnt_09_16=0; //时间计数器清零

  715.                            if(ucLedDirFlag==0)  //正方向
  716.                            {
  717.                   ucLed_dr14=0;  //第14个灭
  718.                   ucLed_dr15=1;  //第15个亮

  719.                   ucLed_update=1;  //更新显示
  720.                   ucLedStep_09_16=7; //切换到下一个步骤
  721.                            }
  722.                            else  //反方向
  723.                            {
  724.                   ucLed_dr13=1;  //第13个亮
  725.                   ucLed_dr14=0;  //第14个灭

  726.                   ucLed_update=1;  //更新显示
  727.                   ucLedStep_09_16=5; //返回上一个步骤
  728.                            }
  729.            }
  730.            break;
  731.      case 7:
  732.            if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
  733.            {
  734.                uiTimeCnt_09_16=0; //时间计数器清零

  735.                            if(ucLedDirFlag==0)  //正方向
  736.                            {
  737.                   ucLed_dr15=0;  //第15个灭
  738.                   ucLed_dr16=1;  //第16个亮

  739.                   ucLed_update=1;  //更新显示
  740.                   ucLedStep_09_16=0; //返回到开始处,重新开始新的一次循环
  741.                            }
  742.                            else  //反方向
  743.                            {
  744.                   ucLed_dr14=1;  //第14个亮
  745.                   ucLed_dr15=0;  //第15个灭

  746.                   ucLed_update=1;  //更新显示
  747.                   ucLedStep_09_16=6; //返回上一个步骤
  748.                            }
  749.            }
  750.            break;
  751.    
  752.       }
  753.    }

  754. }


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


  759.   if(uiTimeCnt_09_16<0xffff)  //设定这个条件,防止uiTimeCnt超范围。
  760.   {
  761.       if(ucLedStartFlag==1)  //此变量为1时代表启动
  762.           {
  763.          uiTimeCnt_09_16++;  //累加定时中断的次数,
  764.           }
  765.   }

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






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

  779.   display_drive();  //数码管字模的驱动函数


  780.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  781.   TL0=0x0b;
  782.   TR0=1;  //开中断
  783. }


  784. void delay_short(unsigned int uiDelayShort)
  785. {
  786.    unsigned int i;  
  787.    for(i=0;i<uiDelayShort;i++)
  788.    {
  789.      ;   //一个分号相当于执行一条空语句
  790.    }
  791. }


  792. void delay_long(unsigned int uiDelayLong)
  793. {
  794.    unsigned int i;
  795.    unsigned int j;
  796.    for(i=0;i<uiDelayLong;i++)
  797.    {
  798.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  799.           {
  800.              ; //一个分号相当于执行一条空语句
  801.           }
  802.    }
  803. }


  804. void initial_myself()  //第一区 初始化单片机
  805. {

  806. /* 注释二:
  807. * 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
  808. * 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
  809. * 坚鸿51学习板的S1就是本程序中用到的一个独立按键。
  810. */
  811.   key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平

  812.   led_dr=0;  //关闭独立LED灯
  813.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

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

  815.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  816.   TL0=0x0b;

  817. }

  818. void initial_peripheral() //第二区 初始化外围
  819. {


  820.    ucDigDot8=0;   //小数点全部不显示
  821.    ucDigDot7=0;  
  822.    ucDigDot6=0;
  823.    ucDigDot5=0;  
  824.    ucDigDot4=0;
  825.    ucDigDot3=0;  
  826.    ucDigDot2=0;
  827.    ucDigDot1=0;

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

  831. }
复制代码



总结陈词:
    前面花了大量的章节在讲数码管显示,按键,运动的关联程序框架,从下一节开始,我将会用八节内容来讲我常用的串口程序框架,内容非常精彩和震撼,思路非常简单而又实用
。欲知详情,请听下回分解-----判断数据尾来接收一串数据的串口通用程序框架。

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


作者: jianhong_wu    时间: 2014-4-5 11:19
本帖最后由 jianhong_wu 于 2014-4-6 22:29 编辑

第三十八节:判断数据尾来接收一串数据的串口通用程序框架。

开场白:
    在实际项目中,串口通讯不可能一次通讯只发送或接收一个字节,大部分的项目都是一次发送或者接受一串的数据。我们还要在这一串数据里解析数据协议,提取有用的数据。
这一节要教会大家三个知识点:
第一个:如何识别一串数据已经发送接收完毕。
第二个:如何在已经接收到的一串数据中解析数据尾协议并且提取有效数据。
第三个:接收一串数据的通用程序框架涉及到main循环里的串口服务程序,定时器的计时程序,串口接收中断程序的密切配合。大家要理解它们三者之间是如何关联起来的。

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

(1)硬件平台:
基于坚鸿51单片机学习板。

(2)实现功能:
波特率是:9600 。
通讯协议:XX YY  EB 00 55
           其中后三位 EB 00 55就是我所说的数据尾,它的有效数据XX YY在数据尾的前面。
        任意时刻,单片机从电脑“串口调试助手”上位机收到的一串数据中,只要此数据中包含关键字EB 00 55 ,并且此关键字前面两个字节的数据XX YY 分别为01 02,那么蜂鸣器鸣叫一声表示接收的数据尾和有效数据都是正确的。

(3)源代码讲解如下:
  1. #include "REG52.H"


  2. #define const_voice_short  40   //蜂鸣器短叫的持续时间
  3. #define const_rc_size  10  //接收串口中断数据的缓冲区数组大小

  4. #define const_receive_time  5  //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完,这个时间根据实际情况来调整大小

  5. void initial_myself(void);   
  6. void initial_peripheral(void);
  7. void delay_long(unsigned int uiDelaylong);



  8. void T0_time(void);  //定时中断函数
  9. void usart_receive(void); //串口接收中断函数
  10. void usart_service(void);  //串口服务程序,在main函数里

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

  12. unsigned int  uiSendCnt=0;     //用来识别串口是否接收完一串数据的计时器
  13. unsigned char ucSendLock=1;    //串口服务程序的自锁变量,每次接收完一串数据只处理一次
  14. unsigned int  uiRcregTotal=0;  //代表当前缓冲区已经接收了多少个数据
  15. unsigned char ucRcregBuf[const_rc_size]; //接收串口中断数据的缓冲区数组
  16. unsigned int  uiRcMoveIndex=0;  //用来解析数据协议的中间变量


  17. unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器



  18. void main()
  19.   {
  20.    initial_myself();  
  21.    delay_long(100);   
  22.    initial_peripheral();
  23.    while(1)  
  24.    {
  25.        usart_service();  //串口服务程序
  26.    }

  27. }


  28. void usart_service(void)  //串口服务程序,在main函数里
  29. {

  30.         
  31. /* 注释一:
  32. * 识别一串数据是否已经全部接收完了的原理:
  33. * 在规定的时间里,如果没有接收到任何一个字节数据,那么就认为一串数据被接收完了,然后就进入数据协议
  34. * 解析和处理的阶段。这个功能的实现要配合定时中断,串口中断的程序一起阅读,要理解他们之间的关系。
  35. */
  36.      if(uiSendCnt>=const_receive_time&&ucSendLock==1) //说明超过了一定的时间内,再也没有新数据从串口来
  37.      {

  38.             ucSendLock=0;    //处理一次就锁起来,不用每次都进来,除非有新接收的数据

  39.                     //下面的代码进入数据协议解析和数据处理的阶段

  40.                     uiRcMoveIndex=uiRcregTotal; //由于是判断数据尾,所以下标移动变量从数组的最尾端开始向0移动
  41.             while(uiRcMoveIndex>=5)   //如果处理的数据量大于等于5(2个有效数据,3个数据头)说明还没有把缓冲区的数据处理完
  42.             {
  43.                if(ucRcregBuf[uiRcMoveIndex-3]==0xeb&&ucRcregBuf[uiRcMoveIndex-2]==0x00&&ucRcregBuf[uiRcMoveIndex-1]==0x55)  //数据尾eb 00 55的判断
  44.                {
  45.                               if(ucRcregBuf[uiRcMoveIndex-5]==0x01&&ucRcregBuf[uiRcMoveIndex-4]==0x02)  //有效数据01 02的判断
  46.                                   {
  47.                                       uiVoiceCnt=const_voice_short; //蜂鸣器发出声音,说明数据尾和有效数据都接收正确
  48.                                   }
  49.                   break;   //退出循环
  50.                }
  51.                uiRcMoveIndex--; //因为是判断数据尾,下标向着0的方向移动
  52.            }
  53.                                          
  54.            uiRcregTotal=0;  //清空缓冲的下标,方便下次重新从0下标开始接受新数据
  55.   
  56.      }
  57.                         
  58. }


  59. void T0_time(void) interrupt 1    //定时中断
  60. {
  61.   TF0=0;  //清除中断标志
  62.   TR0=0; //关中断


  63.   if(uiSendCnt<const_receive_time)   //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完
  64.   {
  65.           uiSendCnt++;    //表面上这个数据不断累加,但是在串口中断里,每接收一个字节它都会被清零,除非这个中间没有串口数据过来
  66.       ucSendLock=1;     //开自锁标志
  67.   }

  68.   if(uiVoiceCnt!=0)
  69.   {
  70.      uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
  71.      beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。

  72.   }
  73.   else
  74.   {
  75.      ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
  76.      beep_dr=1;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  77.   }


  78.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  79.   TL0=0x0b;
  80.   TR0=1;  //开中断
  81. }


  82. void usart_receive(void) interrupt 4                 //串口接收数据中断        
  83. {        

  84.    if(RI==1)  
  85.    {
  86.         RI = 0;

  87.             ++uiRcregTotal;
  88.         if(uiRcregTotal>const_rc_size)  //超过缓冲区
  89.         {
  90.            uiRcregTotal=const_rc_size;
  91.         }
  92.         ucRcregBuf[uiRcregTotal-1]=SBUF;   //将串口接收到的数据缓存到接收缓冲区里
  93.         uiSendCnt=0;  //及时喂狗,虽然main函数那边不断在累加,但是只要串口的数据还没发送完毕,那么它永远也长不大,因为每个中断都被清零。
  94.    
  95.    }
  96.    else  //我在其它单片机上都不用else这段代码的,可能在51单片机上多增加" TI = 0;"稳定性会更好吧。
  97.    {
  98.         TI = 0;
  99.    }
  100.                                                          
  101. }                                


  102. void delay_long(unsigned int uiDelayLong)
  103. {
  104.    unsigned int i;
  105.    unsigned int j;
  106.    for(i=0;i<uiDelayLong;i++)
  107.    {
  108.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  109.           {
  110.              ; //一个分号相当于执行一条空语句
  111.           }
  112.    }
  113. }


  114. void initial_myself(void)  //第一区 初始化单片机
  115. {

  116.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

  117.   //配置定时器
  118.   TMOD=0x01;  //设置定时器0为工作方式1
  119.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  120.   TL0=0x0b;


  121.   //配置串口
  122.   SCON=0x50;
  123.   TMOD=0X21;
  124.   TH1=TL1=-(11059200L/12/32/9600);  //这段配置代码具体是什么意思,我也不太清楚,反正是跟串口波特率有关。
  125.   TR1=1;

  126. }

  127. void initial_peripheral(void) //第二区 初始化外围
  128. {

  129.    EA=1;     //开总中断
  130.    ES=1;     //允许串口中断
  131.    ET0=1;    //允许定时中断
  132.    TR0=1;    //启动定时中断

  133. }
复制代码

总结陈词:
     这一节讲了判断数据尾的程序框架,但是在大部分的项目中,都是通过判断数据头来接收数据的,这样的程序该怎么写?欲知详情,请听下回分解-----判断数据头来接收一串数据的串口通用程序框架。

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


作者: jianhong_wu    时间: 2014-4-6 12:27
本帖最后由 jianhong_wu 于 2014-4-7 08:58 编辑

第三十九节:判断数据头来接收一串数据的串口通用程序框架。

开场白:
上一节讲了判断数据尾的程序框架,但是在大部分的项目中,都是通过判断数据头来接收数据的,这一节要教会大家两个知识点:
第一个:如何在已经接收到的一串数据中解析数据头协议并且提取有效数据。
第二个:无论是判断数据头还是判断数据尾,无论是单片机还是上位机,最好在固定协议前多发送一个填充的无效字节0x00,因为硬件原因,第一个字节往往容易丢失。

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

(1)硬件平台:
基于坚鸿51单片机学习板。

(2)实现功能:
波特率是:9600 。
通讯协议:EB 00 55  XX YY  
加无效填充字节后,上位机实际上应该发送:00  EB 00 55  XX YY
其中第1位00是无效填充字节,防止由于硬件原因丢失第一个字节。
其中第2,3,4位EB 00 55就是数据头
           后2位XX YY就是有效数据
任意时刻,单片机从电脑“串口调试助手”上位机收到的一串数据中,只要此数据中包含关键字EB 00 55 ,并且此关键字后面两个字节的数据XX YY 分别为01 02,那么蜂鸣器鸣叫一声表示接收的数据头和有效数据都是正确的。

(3)源代码讲解如下:
  1. #include "REG52.H"


  2. #define const_voice_short  40   //蜂鸣器短叫的持续时间
  3. #define const_rc_size  10  //接收串口中断数据的缓冲区数组大小

  4. #define const_receive_time  5  //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完,这个时间根据实际情况来调整大小

  5. void initial_myself(void);   
  6. void initial_peripheral(void);
  7. void delay_long(unsigned int uiDelaylong);



  8. void T0_time(void);  //定时中断函数
  9. void usart_receive(void); //串口接收中断函数
  10. void usart_service(void);  //串口服务程序,在main函数里

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

  12. unsigned int  uiSendCnt=0;     //用来识别串口是否接收完一串数据的计时器
  13. unsigned char ucSendLock=1;    //串口服务程序的自锁变量,每次接收完一串数据只处理一次
  14. unsigned int  uiRcregTotal=0;  //代表当前缓冲区已经接收了多少个数据
  15. unsigned char ucRcregBuf[const_rc_size]; //接收串口中断数据的缓冲区数组
  16. unsigned int  uiRcMoveIndex=0;  //用来解析数据协议的中间变量


  17. unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器



  18. void main()
  19.   {
  20.    initial_myself();  
  21.    delay_long(100);   
  22.    initial_peripheral();
  23.    while(1)  
  24.    {
  25.        usart_service();  //串口服务程序
  26.    }

  27. }


  28. void usart_service(void)  //串口服务程序,在main函数里
  29. {

  30.         
  31. /* 注释一:
  32. * 识别一串数据是否已经全部接收完了的原理:
  33. * 在规定的时间里,如果没有接收到任何一个字节数据,那么就认为一串数据被接收完了,然后就进入数据协议
  34. * 解析和处理的阶段。这个功能的实现要配合定时中断,串口中断的程序一起阅读,要理解他们之间的关系。
  35. */
  36.      if(uiSendCnt>=const_receive_time&&ucSendLock==1) //说明超过了一定的时间内,再也没有新数据从串口来
  37.      {

  38.             ucSendLock=0;    //处理一次就锁起来,不用每次都进来,除非有新接收的数据



  39.                     //下面的代码进入数据协议解析和数据处理的阶段

  40.                     uiRcMoveIndex=0; //由于是判断数据头,所以下标移动变量从数组的0开始向最尾端移动

  41. /* 注释二:
  42. * 判断数据头,进入循环解析数据协议必须满足两个条件:
  43. * 第一:最大接收缓冲数据必须大于一串数据的长度(这里是5。包括2个有效数据,3个数据头)
  44. * 第二:游标uiRcMoveIndex必须小于等于最大接收缓冲数据减去一串数据的长度(这里是5。包括2个有效数据,3个数据头)
  45. */
  46.             while(uiRcregTotal>=5&&uiRcMoveIndex<=(uiRcregTotal-5))
  47.             {
  48.                if(ucRcregBuf[uiRcMoveIndex+0]==0xeb&&ucRcregBuf[uiRcMoveIndex+1]==0x00&&ucRcregBuf[uiRcMoveIndex+2]==0x55)  //数据头eb 00 55的判断
  49.                {
  50.                               if(ucRcregBuf[uiRcMoveIndex+3]==0x01&&ucRcregBuf[uiRcMoveIndex+4]==0x02)  //有效数据01 02的判断
  51.                                   {
  52.                                       uiVoiceCnt=const_voice_short; //蜂鸣器发出声音,说明数据头和有效数据都接收正确
  53.                                   }
  54.                   break;   //退出循环
  55.                }
  56.                uiRcMoveIndex++; //因为是判断数据头,游标向着数组最尾端的方向移动
  57.            }
  58.                                          
  59.            uiRcregTotal=0;  //清空缓冲的下标,方便下次重新从0下标开始接受新数据
  60.   
  61.      }
  62.                         
  63. }


  64. void T0_time(void) interrupt 1    //定时中断
  65. {
  66.   TF0=0;  //清除中断标志
  67.   TR0=0; //关中断


  68.   if(uiSendCnt<const_receive_time)   //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完
  69.   {
  70.           uiSendCnt++;    //表面上这个数据不断累加,但是在串口中断里,每接收一个字节它都会被清零,除非这个中间没有串口数据过来
  71.       ucSendLock=1;     //开自锁标志
  72.   }

  73.   if(uiVoiceCnt!=0)
  74.   {
  75.      uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
  76.      beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。

  77.   }
  78.   else
  79.   {
  80.      ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
  81.      beep_dr=1;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  82.   }


  83.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  84.   TL0=0x0b;
  85.   TR0=1;  //开中断
  86. }


  87. void usart_receive(void) interrupt 4                 //串口接收数据中断        
  88. {        

  89.    if(RI==1)  
  90.    {
  91.         RI = 0;

  92.             ++uiRcregTotal;
  93.         if(uiRcregTotal>const_rc_size)  //超过缓冲区
  94.         {
  95.            uiRcregTotal=const_rc_size;
  96.         }
  97.         ucRcregBuf[uiRcregTotal-1]=SBUF;   //将串口接收到的数据缓存到接收缓冲区里
  98.         uiSendCnt=0;  //及时喂狗,虽然main函数那边不断在累加,但是只要串口的数据还没发送完毕,那么它永远也长不大,因为每个中断都被清零。
  99.    
  100.    }
  101.    else  //我在其它单片机上都不用else这段代码的,可能在51单片机上多增加" TI = 0;"稳定性会更好吧。
  102.    {
  103.         TI = 0;
  104.    }
  105.                                                          
  106. }                                


  107. void delay_long(unsigned int uiDelayLong)
  108. {
  109.    unsigned int i;
  110.    unsigned int j;
  111.    for(i=0;i<uiDelayLong;i++)
  112.    {
  113.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  114.           {
  115.              ; //一个分号相当于执行一条空语句
  116.           }
  117.    }
  118. }


  119. void initial_myself(void)  //第一区 初始化单片机
  120. {

  121.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

  122.   //配置定时器
  123.   TMOD=0x01;  //设置定时器0为工作方式1
  124.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  125.   TL0=0x0b;


  126.   //配置串口
  127.   SCON=0x50;
  128.   TMOD=0X21;
  129.   TH1=TL1=-(11059200L/12/32/9600);  //这段配置代码具体是什么意思,我也不太清楚,反正是跟串口波特率有关。
  130.   TR1=1;

  131. }

  132. void initial_peripheral(void) //第二区 初始化外围
  133. {

  134.    EA=1;     //开总中断
  135.    ES=1;     //允许串口中断
  136.    ET0=1;    //允许定时中断
  137.    TR0=1;    //启动定时中断

  138. }
复制代码

总结陈词:
     这一节讲了常用的判断数据头来接收一串数据的程序框架,但是在很多项目中,仅仅靠判断数据头还是不够的,必须要有更加详细的通讯协议,比如可以包含数据类型,有效数据长度,有效数据,数据校验的通讯协议。这样的程序该怎么写?欲知详情,请听下回分解-----常用的自定义串口通讯协议。

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


作者: jianhong_wu    时间: 2014-4-7 13:14
本帖最后由 jianhong_wu 于 2014-7-21 00:17 编辑

第四十节:常用的自定义串口通讯协议。

开场白:
上一节讲了判断数据头的程序框架,但是在很多项目中,仅仅靠判断数据头还是不够的,必须要有更加详细的通讯协议,比如可以包含数据类型,数据地址,有效数据长度,有效数据,数据校验的通讯协议。这一节要教会大家三个知识点:
第一个:常用自定义串口通讯协议的程序框架。
第二个:累加校验和的校验方法。累加和的意思是前面所有字节的数据相加,超过一个字节的溢出部分会按照固定的规则自动丢弃,不用我们管。比如以下数据:
      eb 00 55 01 00 02 0028 6b  
      其中eb 00 55为数据头,01为数据类型,00 02为有效数据长度,00 28 分别为具体的有效数据,6b为前面所有字节的累加和。累加和可以用电脑系统自带的计算器来验证。打开电脑上的计算器,点击“查看”下拉的菜单,选“科学型”,然后选左边的“十六进制”,最后选右边的“字节”,然后把前面所有的字节相加,它们的和就是6b,没错吧。
第三个:原子锁的使用方法,实际上是借鉴了"红金龙吸味"关于原子锁的建议,专门用来保护中断与主函数的共享数据。

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

(1)硬件平台:
基于坚鸿51单片机学习板。

(2)实现功能:
  波特率是:9600.
通讯协议:EB 00 55  GG HH HH XX XX …YYYY CY
其中第1,2,3位EB 00 55就是数据头
其中第4位GG就是数据类型。01代表驱动奉命,02代表驱动Led灯。
其中第5,6位HH就是有效数据长度。高位在左,低位在右。
其中第5,6位HH就是有效数据长度。高位在左,低位在右。
其中从第7位开始,到最后一个字节Cy之前,XX..YY都是具体的有效数据。
在本程序中,当数据类型是01时,有效数据代表蜂鸣器鸣叫的时间长度。当数据类型是02时,有效数据代表Led灯点亮的时间长度。
最后一个字节CY是累加和,前面所有字节的累加。
发送以下测试数据,将会分别控制蜂鸣器和Led灯的驱动时间长度。
蜂鸣器短叫发送:eb 00 55 01 00 02 00 28 6b  
蜂鸣器长叫发送:eb 00 55 01 00 02 00 fa 3d  
Led灯短亮发送:eb 00 55 02 00 02 00 28 6c
Led灯长亮发送:eb 00 55 02 00 02 00 fa3e  

(3)源代码讲解如下:
  1. #include "REG52.H"

  2. /* 注释一:
  3. * 请评估实际项目中一串数据的最大长度是多少,并且留点余量,然后调整const_rc_size的大小。
  4. * 本节程序把上一节的缓冲区数组大小10改成了20
  5. */
  6. #define const_rc_size  20  //接收串口中断数据的缓冲区数组大小

  7. #define const_receive_time  5  //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完,这个时间根据实际情况来调整大小

  8. void initial_myself(void);   
  9. void initial_peripheral(void);
  10. void delay_long(unsigned int uiDelaylong);



  11. void T0_time(void);  //定时中断函数
  12. void usart_receive(void); //串口接收中断函数
  13. void usart_service(void);  //串口服务程序,在main函数里
  14. void led_service(void);  //Led灯的服务程序。

  15. sbit led_dr=P3^5;  //Led的驱动IO口
  16. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口

  17. unsigned int  uiSendCnt=0;     //用来识别串口是否接收完一串数据的计时器
  18. unsigned char ucSendLock=1;    //串口服务程序的自锁变量,每次接收完一串数据只处理一次
  19. unsigned int  uiRcregTotal=0;  //代表当前缓冲区已经接收了多少个数据
  20. unsigned char ucRcregBuf[const_rc_size]; //接收串口中断数据的缓冲区数组
  21. unsigned int  uiRcMoveIndex=0;  //用来解析数据协议的中间变量
  22. /* 注释二:
  23. * 为串口计时器多增加一个原子锁,作为中断与主函数共享数据的保护,实际上是借鉴了"红金龙吸味"关于原子锁的建议.
  24. */
  25. unsigned char  ucSendCntLock=0; //串口计时器的原子锁

  26. unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器

  27. unsigned char  ucVoiceLock=0;  //蜂鸣器鸣叫的原子锁

  28. unsigned char ucRcType=0;  //数据类型
  29. unsigned int  uiRcSize=0;  //数据长度
  30. unsigned char ucRcCy=0;  //校验累加和

  31. unsigned int  uiRcVoiceTime=0;  //蜂鸣器发出声音的持续时间

  32. unsigned int  uiRcLedTime=0; //在串口服务程序中,Led灯点亮时间长度的中间变量
  33. unsigned int  uiLedTime=0;  //Led灯点亮时间的长度
  34. unsigned int  uiLedCnt=0;   //Led灯点亮的计时器
  35. unsigned char ucLedLock=0;  //Led灯点亮时间的原子锁

  36. void main()
  37.   {
  38.    initial_myself();  
  39.    delay_long(100);   
  40.    initial_peripheral();
  41.    while(1)  
  42.    {
  43.        usart_service();  //串口服务程序
  44.        led_service(); //Led灯的服务程序
  45.    }

  46. }

  47. void led_service(void)
  48. {
  49.    if(uiLedCnt<uiLedTime)
  50.    {
  51.       led_dr=1; //开Led灯
  52.    }
  53.    else
  54.    {
  55.       led_dr=0; //关Led灯
  56.    }
  57. }

  58. void usart_service(void)  //串口服务程序,在main函数里
  59. {
  60. /* 注释三:
  61. * 我借鉴了朱兆祺的变量命名习惯,单个字母的变量比如i,j,k,h,这些变量只用作局部变量,直接在函数内部定义。
  62. */
  63.      unsigned int i;  
  64.         
  65.      if(uiSendCnt>=const_receive_time&&ucSendLock==1) //说明超过了一定的时间内,再也没有新数据从串口来
  66.      {

  67.             ucSendLock=0;    //处理一次就锁起来,不用每次都进来,除非有新接收的数据



  68.                     //下面的代码进入数据协议解析和数据处理的阶段

  69.                     uiRcMoveIndex=0; //由于是判断数据头,所以下标移动变量从数组的0开始向最尾端移动


  70.             while(uiRcregTotal>=5&&uiRcMoveIndex<=(uiRcregTotal-5))
  71.             {
  72.                if(ucRcregBuf[uiRcMoveIndex+0]==0xeb&&ucRcregBuf[uiRcMoveIndex+1]==0x00&&ucRcregBuf[uiRcMoveIndex+2]==0x55)  //数据头eb 00 55的判断
  73.                {
  74.                          ucRcType=ucRcregBuf[uiRcMoveIndex+3];   //数据类型  一个字节

  75.                                          uiRcSize=ucRcregBuf[uiRcMoveIndex+4];   //数据长度  两个字节
  76.                                          uiRcSize=uiRcSize<<8;
  77.                                          uiRcSize=uiRcSize+ucRcregBuf[uiRcMoveIndex+5];
  78.                                                                  
  79.                                          ucRcCy=ucRcregBuf[uiRcMoveIndex+6+uiRcSize];   //记录最后一个字节的校验
  80.                      ucRcregBuf[uiRcMoveIndex+6+uiRcSize]=0;  //清零最后一个字节的累加和变量
  81. /* 注释四:
  82. * 计算校验累加和的方法:除了最后一个字节,其它前面所有的字节累加起来,
  83. * 溢出的不用我们管,C语言编译器会按照固定的规则自动处理。
  84. * 以下for循环里的(3+1+2+uiRcSize),其中3代表3个字节数据头,1代表1个字节数据类型,
  85. * 2代表2个字节的数据长度变量,uiRcSize代表实际上一串数据中的有效数据个数。
  86. */
  87.                                           for(i=0;i<(3+1+2+uiRcSize);i++) //计算校验累加和
  88.                                          {
  89.                                                  ucRcregBuf[uiRcMoveIndex+6+uiRcSize]=ucRcregBuf[uiRcMoveIndex+6+uiRcSize]+ucRcregBuf[uiRcMoveIndex+i];
  90.                      }        


  91.                                          if(ucRcCy==ucRcregBuf[uiRcMoveIndex+6+uiRcSize])  //如果校验正确,则进入以下数据处理
  92.                                          {                                                  
  93.                          switch(ucRcType)   //根据不同的数据类型来做不同的数据处理
  94.                                              {

  95.                              case 0x01:   //驱动蜂鸣器发出声音,并且可以控制蜂鸣器持续发出声音的时间长度
  96.         
  97.                                   uiRcVoiceTime=ucRcregBuf[uiRcMoveIndex+6];  //把两个字节合并成一个int类型的数据
  98.                                   uiRcVoiceTime=uiRcVoiceTime<<8;  
  99.                                   uiRcVoiceTime=uiRcVoiceTime+ucRcregBuf[uiRcMoveIndex+7];

  100.                                   ucVoiceLock=1;  //共享数据的原子锁加锁
  101.                                   uiVoiceCnt=uiRcVoiceTime; //蜂鸣器发出声音
  102.                                   ucVoiceLock=0;  //共享数据的原子锁解锁

  103.                                                               break;        
  104.                                                                         
  105.                              case 0x02:   //点亮一个LED灯,并且可以控制LED灯持续亮的时间长度
  106.                                   uiRcLedTime=ucRcregBuf[uiRcMoveIndex+6];  //把两个字节合并成一个int类型的数据
  107.                                   uiRcLedTime=uiRcLedTime<<8;  
  108.                                   uiRcLedTime=uiRcLedTime+ucRcregBuf[uiRcMoveIndex+7];

  109.                                   ucLedLock=1;  //共享数据的原子锁加锁
  110.                                   uiLedTime=uiRcLedTime; //更改点亮Led灯的时间长度
  111.                                                                   uiLedCnt=0;  //在本程序中,清零计数器就等于自动点亮Led灯
  112.                                   ucLedLock=0;  //共享数据的原子锁解锁
  113.                                                               break;
  114.                                                                         
  115.                          }

  116.                                          }        

  117.                      break;   //退出循环
  118.                }
  119.                uiRcMoveIndex++; //因为是判断数据头,游标向着数组最尾端的方向移动
  120.            }
  121.                                          
  122.            uiRcregTotal=0;  //清空缓冲的下标,方便下次重新从0下标开始接受新数据
  123.   
  124.      }
  125.                         
  126. }


  127. void T0_time(void) interrupt 1    //定时中断
  128. {
  129.   TF0=0;  //清除中断标志
  130.   TR0=0; //关中断

  131. /* 注释五:
  132.   * 此处多增加一个原子锁,作为中断与主函数共享数据的保护,实际上是借鉴了"红金龙吸味"关于原子锁的建议.
  133.   */  
  134.   if(ucSendCntLock==0)  //原子锁判断
  135.   {
  136.      ucSendCntLock=1; //加锁
  137.      if(uiSendCnt<const_receive_time)   //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完
  138.      {
  139.         uiSendCnt++;    //表面上这个数据不断累加,但是在串口中断里,每接收一个字节它都会被清零,除非这个中间没有串口数据过来
  140.         ucSendLock=1;     //开自锁标志
  141.      }
  142.      ucSendCntLock=0; //解锁
  143.   }

  144.   if(ucVoiceLock==0) //原子锁判断
  145.   {
  146.      if(uiVoiceCnt!=0)
  147.      {

  148.         uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
  149.         beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  150.      
  151.      }
  152.      else
  153.      {

  154.         ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
  155.         beep_dr=1;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  156.         
  157.      }
  158.   }

  159.   if(ucLedLock==0)  //原子锁判断
  160.   {
  161.      if(uiLedCnt<uiLedTime)
  162.          {
  163.             uiLedCnt++;  //Led灯点亮的时间计时器
  164.          }
  165.   }

  166.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  167.   TL0=0x0b;
  168.   TR0=1;  //开中断
  169. }


  170. void usart_receive(void) interrupt 4                 //串口接收数据中断        
  171. {        

  172.    if(RI==1)  
  173.    {
  174.         RI = 0;

  175.             ++uiRcregTotal;
  176.         if(uiRcregTotal>const_rc_size)  //超过缓冲区
  177.         {
  178.            uiRcregTotal=const_rc_size;
  179.         }
  180.         ucRcregBuf[uiRcregTotal-1]=SBUF;   //将串口接收到的数据缓存到接收缓冲区里

  181.         if(ucSendCntLock==0)  //原子锁判断
  182.         {
  183.             ucSendCntLock=1; //加锁
  184.             uiSendCnt=0;  //及时喂狗,虽然在定时中断那边此变量会不断累加,但是只要串口的数据还没发送完毕,那么它永远也长不大,因为每个串口接收中断它都被清零。
  185.                     ucSendCntLock=0; //解锁
  186.                 }
  187.    
  188.    }
  189.    else  //我在其它单片机上都不用else这段代码的,可能在51单片机上多增加" TI = 0;"稳定性会更好吧。
  190.    {
  191.         TI = 0;
  192.    }
  193.                                                          
  194. }                                


  195. void delay_long(unsigned int uiDelayLong)
  196. {
  197.    unsigned int i;
  198.    unsigned int j;
  199.    for(i=0;i<uiDelayLong;i++)
  200.    {
  201.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  202.           {
  203.              ; //一个分号相当于执行一条空语句
  204.           }
  205.    }
  206. }


  207. void initial_myself(void)  //第一区 初始化单片机
  208. {
  209.   led_dr=0; //关Led灯
  210.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

  211.   //配置定时器
  212.   TMOD=0x01;  //设置定时器0为工作方式1
  213.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  214.   TL0=0x0b;


  215.   //配置串口
  216.   SCON=0x50;
  217.   TMOD=0X21;
  218.   TH1=TL1=-(11059200L/12/32/9600);  //这段配置代码具体是什么意思,我也不太清楚,反正是跟串口波特率有关。
  219.   TR1=1;

  220. }

  221. void initial_peripheral(void) //第二区 初始化外围
  222. {

  223.    EA=1;     //开总中断
  224.    ES=1;     //允许串口中断
  225.    ET0=1;    //允许定时中断
  226.    TR0=1;    //启动定时中断

  227. }
复制代码



总结陈词:
这一节讲了常用的自定义串口通讯协议的程序框架,这种框架在判断一串数据是否接收完毕的时候,都是靠“超过规定的时间内,没有发现串口数据”来判定的,这是我做绝大多数项目的串口程序框架,但是在少数要求实时反应非常快的项目中,我会用另外一种响应速度更快的串口程序框架,这种程序框架是什么样的?欲知详情,请听下回分解-----在串口接收中断里即时解析数据头的特殊程序框架。

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

作者: jianhong_wu    时间: 2014-4-19 11:51
第四十一节:在串口接收中断里即时解析数据头的特殊程序框架。

开场白:
上一节讲了常用的自定义串口通讯协议的程序框架,这种框架在判断一串数据是否接收完毕的时候,都是靠“超过规定的时间内,没有发现串口数据”来判定的,这是我做绝大多数项目的串口程序框架,但是在少数要求实时反应非常快的项目中,这样的程序框架可能会满足不了系统对速度的要求,这一节就是要介绍另外一种响应速度更加快的串口程序框架,要教会大家一个知识点:在串口接收中断里即时解析数据头的特殊程序框架。我在这种程序框架里,会尽量简化数据头和数据尾,同时也简化校验,目的都是为了提高响应速度。
具体内容,请看源代码讲解。

(1)硬件平台:
基于坚鸿51单片机学习板。

(2)实现功能:
  波特率是:9600.
通讯协议:EB  GG XX XX XX XX ED
其中第1位EB就是数据头.
其中第2位GG就是数据类型。01代表驱动蜂鸣器,02代表驱动Led灯。
其中第3,4,5,6位XX就是有效数据长度。高位在左,低位在右。
其中第7位ED就是数据尾,在这里也起一部分校验的作用,虽然不是累加和的方式。

在本程序中,
当数据类型是01时,4个有效数据代表一个long类型数据,如果这个数据等于十进制的123456789,那么蜂鸣器就鸣叫一声表示正确。
当数据类型是02时,4个有效数据代表一个long类型数据,如果这个数据等于十进制的123456789,那么LED灯就会闪烁一下表示正确。
十进制的123456789等于十六进制的75bcd15 。
发送以下测试数据,将会分别控制蜂鸣器Led灯。
控制蜂鸣器发送:eb 01 07 5b cd 15 ed
控制LED灯发送:eb 02 07 5b cd 15 ed

(3)源代码讲解如下:
  1. #include "REG52.H"


  2. #define const_rc_size  20  //接收串口中断数据的缓冲区数组大小
  3. #define const_receive_time  5  //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完,这个时间根据实际情况来调整大小

  4. #define const_voice_short  80   //蜂鸣器短叫的持续时间
  5. #define const_led_short  80    //LED灯亮的持续时间

  6. void initial_myself(void);   
  7. void initial_peripheral(void);
  8. void delay_long(unsigned int uiDelaylong);



  9. void T0_time(void);  //定时中断函数
  10. void usart_receive(void); //串口接收中断函数
  11. void led_service(void);  //Led灯的服务程序。

  12. sbit led_dr=P3^5;  //Led的驱动IO口
  13. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口


  14. unsigned int  uiRcregTotal=0;  //代表当前缓冲区已经接收了多少个数据
  15. unsigned char ucRcregBuf[const_rc_size]; //接收串口中断数据的缓冲区数组

  16. unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器
  17. unsigned char  ucVoiceLock=0;  //蜂鸣器鸣叫的原子锁



  18. unsigned int  uiRcVoiceTime=0;  //蜂鸣器发出声音的持续时间

  19. unsigned int  uiLedCnt=0;   //Led灯点亮的计时器
  20. unsigned char ucLedLock=0;  //Led灯点亮时间的原子锁

  21. unsigned long ulBeepData=0; //蜂鸣器的数据
  22. unsigned long ulLedData=0; //LED的数据

  23. unsigned char ucUsartStep=0; //串口接收字节的步骤变量

  24. void main()
  25.   {
  26.    initial_myself();  
  27.    delay_long(100);   
  28.    initial_peripheral();
  29.    while(1)  
  30.    {
  31.        led_service(); //Led灯的服务程序
  32.    }

  33. }

  34. void led_service(void)
  35. {
  36.    if(uiLedCnt<const_led_short)
  37.    {
  38.       led_dr=1; //开Led灯
  39.    }
  40.    else
  41.    {
  42.       led_dr=0; //关Led灯
  43.    }
  44. }



  45. void T0_time(void) interrupt 1    //定时中断
  46. {
  47.   TF0=0;  //清除中断标志
  48.   TR0=0; //关中断

  49. /* 注释一:
  50.   * 此处多增加一个原子锁,作为中断与主函数共享数据的保护,实际上是借鉴了"红金龙吸味"关于原子锁的建议.
  51.   */  

  52.   if(ucVoiceLock==0) //原子锁判断
  53.   {
  54.      if(uiVoiceCnt!=0)
  55.      {

  56.         uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
  57.         beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  58.      
  59.      }
  60.      else
  61.      {

  62.         ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
  63.         beep_dr=1;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  64.         
  65.      }
  66.   }

  67.   if(ucLedLock==0)  //原子锁判断
  68.   {
  69.      if(uiLedCnt<const_led_short)
  70.      {
  71.             uiLedCnt++;  //Led灯点亮的时间计时器
  72.      }

  73.   }

  74.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  75.   TL0=0x0b;
  76.   TR0=1;  //开中断
  77. }


  78. void usart_receive(void) interrupt 4                 //串口接收数据中断        
  79. {        
  80. /* 注释二:
  81.   * 以下就是吴坚鸿在串口接收中断里即时解析数据头的特殊程序框架,
  82.   * 它的特点是靠数据头来启动接受有效数据,靠数据尾来识别一串数据接受完毕,
  83.   * 这里的数据尾也起到一部分的校验作用,让数据更加可靠。这种程序结构适合应用
  84.   * 在传输的数据长度不是很长,而且要求响应速度非常高的实时场合。在这种实时要求
  85.   * 非常高的场合中,我就不像之前一样做数据累加和的复杂运算校验,只用数据尾来做简单的
  86.   * 校验确认,目的是尽可能提高处理速度。
  87.   */  

  88.    if(RI==1)  
  89.    {
  90.         RI = 0;

  91.         switch(ucUsartStep) //串口接收字节的步骤变量
  92.         {
  93.             case 0:
  94.                              ucRcregBuf[0]=SBUF;     
  95.                  if(ucRcregBuf[0]==0xeb)  //数据头判断
  96.                  {
  97.                                      ucRcregBuf[0]=0;  //数据头及时清零,为下一串数据的接受判断做准备
  98.                                      uiRcregTotal=1;  //缓存数组的下标初始化
  99.                      ucUsartStep=1;  //如果数据头正确,则切换到下一步,依次把上位机来的数据存入数组缓冲区
  100.                  }
  101.                  break;
  102.             case 1:
  103.                              ucRcregBuf[uiRcregTotal]=SBUF;  //依次把上位机来的数据存入数组缓冲区
  104.                                  uiRcregTotal++; //下标移动
  105.                                  if(uiRcregTotal>=7)  //已经接收了7个字节
  106.                                  {
  107.                    if(ucRcregBuf[6]==0xed)  //数据尾判断,也起到一部分校验的作用,让数据更加可靠,虽然没有用到累加和的检验方法
  108.                                    {
  109.                                        ucRcregBuf[6]=0;  //数据尾及时清零,为下一串数据的接受判断做准备                                       
  110.                                        switch(ucRcregBuf[1]) //根据不同的数据类型来做不同的数据处理
  111.                                            {
  112.                                                case 0x01:  //与蜂鸣器相关
  113.                                 ulBeepData=ucRcregBuf[2]; //把四个字节的数据合并成一个long型的数据
  114.                                                             ulBeepData=ulBeepData<<8;
  115.                                                             ulBeepData=ulBeepData+ucRcregBuf[3];
  116.                                                             ulBeepData=ulBeepData<<8;
  117.                                                             ulBeepData=ulBeepData+ucRcregBuf[4];
  118.                                                             ulBeepData=ulBeepData<<8;
  119.                                                             ulBeepData=ulBeepData+ucRcregBuf[5];
  120.                                                                 if(ulBeepData==123456789)  //如果此数据等于十进制的123456789,表示数据正确
  121.                                                                 {
  122.                                                                     ucVoiceLock=1;  //共享数据的原子锁加锁
  123.                                     uiVoiceCnt=const_voice_short; //蜂鸣器发出声音
  124.                                     ucVoiceLock=0;  //共享数据的原子锁解锁
  125.                                                                 }

  126.                                                         break;

  127.                                                case 0x02:  //与Led灯相关
  128.                                 ulLedData=ucRcregBuf[2]; //把四个字节的数据合并成一个long型的数据
  129.                                                             ulLedData=ulLedData<<8;
  130.                                                             ulLedData=ulLedData+ucRcregBuf[3];
  131.                                                             ulLedData=ulLedData<<8;
  132.                                                             ulLedData=ulLedData+ucRcregBuf[4];
  133.                                                             ulLedData=ulLedData<<8;
  134.                                                             ulLedData=ulLedData+ucRcregBuf[5];
  135.                                                                 if(ulLedData==123456789)  //如果此数据等于十进制的123456789,表示数据正确
  136.                                                                 {
  137.                                                                     ucLedLock=1;  //共享数据的原子锁加锁
  138.                                     uiLedCnt=0;  //在本程序中,清零计数器就等于自动点亮Led灯
  139.                                     ucLedLock=0;  //共享数据的原子锁解锁
  140.                                                                 }



  141.                                                         break;
  142.                                            }

  143.                                    }

  144.                    ucUsartStep=0;     //返回上一步数据头判断,为下一次的新数据接收做准备
  145.                                  }
  146.                  break;
  147.         }
  148.    
  149.    }
  150.    else  //我在其它单片机上都不用else这段代码的,可能在51单片机上多增加" TI = 0;"稳定性会更好吧。
  151.    {
  152.         TI = 0;
  153.    }
  154.                                                          
  155. }                                


  156. void delay_long(unsigned int uiDelayLong)
  157. {
  158.    unsigned int i;
  159.    unsigned int j;
  160.    for(i=0;i<uiDelayLong;i++)
  161.    {
  162.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  163.           {
  164.              ; //一个分号相当于执行一条空语句
  165.           }
  166.    }
  167. }


  168. void initial_myself(void)  //第一区 初始化单片机
  169. {
  170.   led_dr=0; //关Led灯
  171.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

  172.   //配置定时器
  173.   TMOD=0x01;  //设置定时器0为工作方式1
  174.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  175.   TL0=0x0b;


  176.   //配置串口
  177.   SCON=0x50;
  178.   TMOD=0X21;
  179.   TH1=TL1=-(11059200L/12/32/9600);  //这段配置代码具体是什么意思,我也不太清楚,反正是跟串口波特率有关。
  180.   TR1=1;

  181. }

  182. void initial_peripheral(void) //第二区 初始化外围
  183. {

  184.    EA=1;     //开总中断
  185.    ES=1;     //允许串口中断
  186.    ET0=1;    //允许定时中断
  187.    TR0=1;    //启动定时中断

  188. }
复制代码

总结陈词:
    前面花了4节内容仔细讲了各种串口接收数据的常用框架,从下一节开始,我开始讲串口发送数据的程序框架,这种程序框架是什么样的?欲知详情,请听下回分解-----通过串口用delay延时方式发送一串数据。

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

作者: jianhong_wu    时间: 2014-4-22 10:57
第四十二节:通过串口用delay延时方式发送一串数据。

开场白:
   上一节讲了在串口接收中断里即时解析数据头的特殊程序框架。这节开始讲串口发送数据需要特别注意的地方和程序框架,要教会大家一个知识点:根据我个人的经验,在发送一串数据中,每个字节之间必须添加一个延时,用来等待串口发送完成。当然,也有一些朋友可能不增加延时,直接靠单片机自带的发送完成标志位来判断,但是我以前在做项目中,感觉单单靠发送完成标志位来判断还是容易出错(当然也有可能是我自身程序的问题),所以后来在大部分的项目中我就干脆靠延时来等待它发送完成。我在51,PIC单片机中都是这么做的。但是,凭我的经验,在stm32单片机中,可以不增加延时,直接靠单片机自带的标志位来判断就很可靠。

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

(1)硬件平台:
基于坚鸿51单片机学习板。

(2)实现功能:
  波特率是:9600.
按一次按键S1,单片机就往上位机发送以下一串数据:
eb 00 55 01 00 00 00 00 41

(3)源代码讲解如下:
  1. #include "REG52.H"


  2. #define const_send_size  10  //串口发送数据的缓冲区数组大小

  3. #define const_key_time1  20    //按键去抖动延时的时间

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

  5. void initial_myself(void);   
  6. void initial_peripheral(void);
  7. void delay_short(unsigned int uiDelayshort);
  8. void delay_long(unsigned int uiDelaylong);

  9. void eusart_send(unsigned char ucSendData);  //发送一个字节,内部自带每个字节之间的延时

  10. void T0_time(void);  //定时中断函数
  11. void usart_receive(void); //串口接收中断函数

  12. void key_service(); //按键服务的应用程序
  13. void key_scan(); //按键扫描函数 放在定时中断里

  14. sbit led_dr=P3^5;  //Led的驱动IO口
  15. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口

  16. sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
  17. sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平



  18. unsigned char ucSendregBuf[const_send_size]; //接收串口中断数据的缓冲区数组


  19. unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器
  20. unsigned char  ucVoiceLock=0;  //蜂鸣器鸣叫的原子锁

  21. unsigned char ucKeySec=0;   //被触发的按键编号

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

  24. void main()
  25. {
  26.    initial_myself();  
  27.    delay_long(100);   
  28.    initial_peripheral();
  29.    while(1)  
  30.    {
  31.       key_service(); //按键服务的应用程序
  32.    }

  33. }



  34. void eusart_send(unsigned char ucSendData)
  35. {

  36.   ES = 0; //关串口中断
  37.   TI = 0; //清零串口发送完成中断请求标志
  38.   SBUF =ucSendData; //发送一个字节

  39. /* 注释一:
  40.   * 根据我个人的经验,在发送一串数据中,每个字节之间必须添加一个延时,用来等待串口发送完成。
  41.   * 当然,也有一些朋友可能不增加延时,直接靠单片机自带的发送完成标志位来判断,但是我以前
  42.   * 在做项目中,感觉单单靠发送完成标志位来判断还是容易出错(当然也有可能是我自身程序的问题),
  43.   * 所以后来在大部分的项目中我就干脆靠延时来等待它发送完成。我在51,PIC单片机中都是这么做的。
  44.   * 但是,凭我的经验,在stm32单片机中,可以不增加延时,直接靠单片机自带的标志位来判断就很可靠。
  45.   */  

  46.   delay_short(400);  //每个字节之间的延时,这里非常关键,也是最容易出错的地方。延时的大小请根据实际项目来调整

  47.   TI = 0; //清零串口发送完成中断请求标志
  48.   ES = 1; //允许串口中断

  49. }

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

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



  67. }


  68. void key_service() //第三区 按键服务的应用程序
  69. {
  70.   unsigned int i;

  71.   switch(ucKeySec) //按键服务状态切换
  72.   {
  73.     case 1:// 1号键 对应朱兆祺学习板的S1键
  74.           ucSendregBuf[0]=0xeb;    //把准备发送的数据放入发送缓冲区
  75.           ucSendregBuf[1]=0x00;
  76.           ucSendregBuf[2]=0x55;
  77.           ucSendregBuf[3]=0x01;
  78.           ucSendregBuf[4]=0x00;
  79.           ucSendregBuf[5]=0x00;
  80.           ucSendregBuf[6]=0x00;
  81.           ucSendregBuf[7]=0x00;
  82.           ucSendregBuf[8]=0x41;

  83.                   for(i=0;i<9;i++)
  84.                   {
  85.                      eusart_send(ucSendregBuf[i]);  //发送一串数据给上位机
  86.                   }

  87.           ucVoiceLock=1;  //原子锁加锁,保护中断与主函数的共享数据
  88.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  89.                   ucVoiceLock=0; //原子锁解锁

  90.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  91.           break;        
  92.   }        
  93. }



  94. void T0_time(void) interrupt 1    //定时中断
  95. {
  96.   TF0=0;  //清除中断标志
  97.   TR0=0; //关中断

  98. /* 注释二:
  99.   * 此处多增加一个原子锁,作为中断与主函数共享数据的保护,实际上是借鉴了"红金龙吸味"关于原子锁的建议.
  100.   */  

  101.   if(ucVoiceLock==0) //原子锁判断
  102.   {
  103.      if(uiVoiceCnt!=0)
  104.      {

  105.         uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
  106.         beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  107.      
  108.      }
  109.      else
  110.      {

  111.         ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
  112.         beep_dr=1;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  113.        
  114.      }
  115.   }

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


  117.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  118.   TL0=0x0b;
  119.   TR0=1;  //开中断
  120. }


  121. void usart_receive(void) interrupt 4                 //串口中断        
  122. {        

  123.    if(RI==1)  
  124.    {
  125.         RI = 0;   //接收中断,及时把接收中断标志位清零

  126.       
  127.    
  128.    }
  129.    else
  130.    {
  131.         TI = 0;    //发送中断,及时把发送中断标志位清零
  132.    }
  133.                                                          
  134. }                                

  135. void delay_short(unsigned int uiDelayShort)
  136. {
  137.    unsigned int i;  
  138.    for(i=0;i<uiDelayShort;i++)
  139.    {
  140.      ;   //一个分号相当于执行一条空语句
  141.    }
  142. }


  143. void delay_long(unsigned int uiDelayLong)
  144. {
  145.    unsigned int i;
  146.    unsigned int j;
  147.    for(i=0;i<uiDelayLong;i++)
  148.    {
  149.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  150.           {
  151.              ; //一个分号相当于执行一条空语句
  152.           }
  153.    }
  154. }


  155. void initial_myself(void)  //第一区 初始化单片机
  156. {
  157. /* 注释三:
  158. * 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
  159. * 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
  160. * 坚鸿51学习板的S1和S5两个按键就是本程序中用到的两个独立按键。
  161. */
  162.   key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平

  163.   led_dr=0; //关Led灯
  164.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

  165.   //配置定时器
  166.   TMOD=0x01;  //设置定时器0为工作方式1
  167.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  168.   TL0=0x0b;


  169.   //配置串口
  170.   SCON=0x50;
  171.   TMOD=0X21;
  172.   TH1=TL1=-(11059200L/12/32/9600);  //串口波特率9600。
  173.   TR1=1;

  174. }

  175. void initial_peripheral(void) //第二区 初始化外围
  176. {

  177.    EA=1;     //开总中断
  178.    ES=1;     //允许串口中断
  179.    ET0=1;    //允许定时中断
  180.    TR0=1;    //启动定时中断

  181. }
复制代码

总结陈词:
这节在每个字节之间都添加了delay延时来等待每个字节的发送完成,由于delay(400)这个时间还不算很长,所以可以应用在很多简单任务的系统中。但是在某些任务量很多的系统中,实时运行的主任务不允许被长时间和经常性地中断,这个时候就需要用计数延时来替代delay延时,这种程序框架是什么样的?欲知详情,请听下回分解-----通过串口用计数延时方式发送一串数据。

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

作者: jianhong_wu    时间: 2014-4-25 11:02
本帖最后由 jianhong_wu 于 2014-4-26 10:25 编辑

第四十三节:通过串口用计数延时方式发送一串数据。

开场白:
上一节讲了通过串口用delay延时方式发送一串数据,这种方式要求发送一串数据的时候一气呵成,期间不能执行其它任务,由于delay(400)这个时间还不算很长,所以可以应用在很多简单任务的系统中。但是在某些任务量很多的系统中,实时运行的主任务不允许被长时间和经常性地中断,这个时候就需要用计数延时来替代delay延时。本节要教会大家两个知识点:
第一个:用计数延时方式发送一串数据的程序框架。
第二个:环形消息队列的程序框架。

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

(1)硬件平台:
    基于坚鸿51单片机学习板。

(2)实现功能:
     波特率是:9600.
用坚鸿51单片机学习板中的S1,S5,S9,S13作为独立按键。
    按一次按键S1,发送EB 00 55 01 00 00 00 00 41
按一次按键S5,发送EB 00 55 02 00 00 00 00 42
按一次按键S9,发送EB 00 55 03 00 00 00 00 43
按一次按键S13,发送EB 00 55 04 00 00 00 00 44
(3)源代码讲解如下:
  1. #include "REG52.H"


  2. #define const_send_time  100  //累计主循环次数的计数延时 请根据项目实际情况来调整此数据大小

  3. #define const_send_size  10  //串口发送数据的缓冲区数组大小

  4. #define const_Message_size  10  //环形消息队列的缓冲区数组大小

  5. #define const_key_time1  20    //按键去抖动延时的时间
  6. #define const_key_time2  20    //按键去抖动延时的时间
  7. #define const_key_time3  20    //按键去抖动延时的时间
  8. #define const_key_time4  20    //按键去抖动延时的时间

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

  10. void initial_myself(void);   
  11. void initial_peripheral(void);
  12. //void delay_short(unsigned int uiDelayshort);
  13. void delay_long(unsigned int uiDelaylong);

  14. void eusart_send(unsigned char ucSendData);  //发送一个字节,内部没有每个字节之间的延时
  15. void send_service(void);  //利用累计主循环次数的计数延时方式来发送一串数据

  16. void T0_time(void);  //定时中断函数
  17. void usart_receive(void); //串口接收中断函数

  18. void key_service(void); //按键服务的应用程序
  19. void key_scan(void); //按键扫描函数 放在定时中断里


  20. void insert_message(unsigned char ucMessageTemp);  //插入新的消息到环形消息队列里
  21. unsigned char get_message(void);  //从环形消息队列里提取消息



  22. sbit led_dr=P3^5;  //Led的驱动IO口
  23. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口

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

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



  29. unsigned char ucSendregBuf[const_send_size]; //串口发送数据的缓冲区数组

  30. unsigned char ucMessageBuf[const_Message_size]; //环形消息队列的缓冲区数据
  31. unsigned int  uiMessageCurrent=0;  //环形消息队列的取数据当前位置
  32. unsigned int  uiMessageInsert=0;  //环形消息队列的插入新消息时候的位置
  33. unsigned int  uiMessageCnt=0;  //统计环形消息队列的消息数量  等于0时表示消息队列里没有消息

  34. unsigned char ucMessage=0; //当前获取到的消息

  35. unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器
  36. unsigned char  ucVoiceLock=0;  //蜂鸣器鸣叫的原子锁

  37. unsigned char ucKeySec=0;   //被触发的按键编号

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

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

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

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


  46. unsigned char ucSendStep=0;  //发送一串数据的运行步骤
  47. unsigned int  uiSendTimeCnt=0; //累计主循环次数的计数延时器

  48. unsigned int uiSendCnt=0; //发送数据时的中间变量

  49. void main()
  50. {
  51.    initial_myself();  
  52.    delay_long(100);   
  53.    initial_peripheral();
  54.    while(1)  
  55.    {
  56.       key_service(); //按键服务的应用程序
  57.           send_service();  //利用累计主循环次数的计数延时方式来发送一串数据
  58.    }

  59. }

  60. /* 注释一:
  61.   * 通过判断数组下标是否超范围的条件,把一个数组的首尾连接起来,就像一个环形,
  62.   * 因此命名为环形消息队列。环形消息队列有插入消息,获取消息两个核心函数,以及一个
  63.   * 统计消息总数的uiMessageCnt核心变量,通过此变量,我们可以知道消息队列里面是否有消息需要处理.
  64.   * 我在做项目中很少用消息队列的,印象中我只在两个项目中用过消息队列这种方法。大部分的单片机
  65.   * 项目其实直接用一两个中间变量就可以起到传递消息的作用,就能满足系统的要求。以下是各变量的含义:
  66.   * #define const_Message_size  10  //环形消息队列的缓冲区数组大小
  67.   * unsigned char ucMessageBuf[const_Message_size]; //环形消息队列的缓冲区数据
  68.   * unsigned int  uiMessageCurrent=0;  //环形消息队列的取数据当前位置
  69.   * unsigned int  uiMessageInsert=0;  //环形消息队列的插入新消息时候的位置
  70.   * unsigned int  uiMessageCnt=0;  //统计环形消息队列的消息数量  等于0时表示消息队列里没有消息
  71.   */  

  72. void insert_message(unsigned char ucMessageTemp)  //插入新的消息到环形消息队列里
  73. {
  74.    if(uiMessageCnt<const_Message_size)  //消息总数小于环形消息队列的缓冲区才允许插入新消息
  75.    {
  76.       ucMessageBuf[uiMessageInsert]=ucMessageTemp;

  77.           uiMessageInsert++;  //插入新消息时候的位置
  78.           if(uiMessageInsert>=const_Message_size) //到了缓冲区末尾,则从缓冲区的开头重新开始。数组的首尾连接,看起来就像环形
  79.           {
  80.              uiMessageInsert=0;
  81.           }
  82.       uiMessageCnt++; //消息数量累加  等于0时表示消息队列里没有消息
  83.    }
  84. }

  85. unsigned char get_message(void)  //从环形消息队列里提取消息
  86. {
  87.    unsigned char ucMessageTemp=0;  //返回的消息中间变量,默认为0

  88.    if(uiMessageCnt>0)  //只有消息数量大于0时才可以提取消息
  89.    {
  90.       ucMessageTemp=ucMessageBuf[uiMessageCurrent];
  91.           uiMessageCurrent++;  //环形消息队列的取数据当前位置
  92.           if(uiMessageCurrent>=const_Message_size) //到了缓冲区末尾,则从缓冲区的开头重新开始。数组的首尾连接,看起来就像环形
  93.           {
  94.              uiMessageCurrent=0;
  95.           }
  96.       uiMessageCnt--; //每提取一次,消息数量就减一  等于0时表示消息队列里没有消息
  97.    }

  98.    return ucMessageTemp;
  99. }


  100. void send_service(void)  //利用累计主循环次数的计数延时方式来发送一串数据
  101. {
  102.   switch(ucSendStep)  //发送一串数据的运行步骤
  103.   {
  104.     case 0:   //从环形消息队列里提取消息
  105.          if(uiMessageCnt>0)  //说明有消息需要处理
  106.                  {
  107.                     ucMessage=get_message();
  108.             switch(ucMessage)   //消息处理
  109.                         {
  110.                            case 1:
  111.                     ucSendregBuf[0]=0xeb;    //把准备发送的数据放入发送缓冲区
  112.                     ucSendregBuf[1]=0x00;
  113.                     ucSendregBuf[2]=0x55;
  114.                     ucSendregBuf[3]=0x01;    //01代表1号键
  115.                     ucSendregBuf[4]=0x00;
  116.                     ucSendregBuf[5]=0x00;
  117.                     ucSendregBuf[6]=0x00;
  118.                     ucSendregBuf[7]=0x00;
  119.                     ucSendregBuf[8]=0x41;

  120.                     uiSendCnt=0; //发送数据的中间变量清零
  121.                     uiSendTimeCnt=0; //累计主循环次数的计数延时器清零
  122.                     ucSendStep=1; //切换到下一步发送一串数据
  123.                                 break;
  124.                            case 2:
  125.                     ucSendregBuf[0]=0xeb;    //把准备发送的数据放入发送缓冲区
  126.                     ucSendregBuf[1]=0x00;
  127.                     ucSendregBuf[2]=0x55;
  128.                     ucSendregBuf[3]=0x02;    //02代表2号键
  129.                     ucSendregBuf[4]=0x00;
  130.                     ucSendregBuf[5]=0x00;
  131.                     ucSendregBuf[6]=0x00;
  132.                     ucSendregBuf[7]=0x00;
  133.                     ucSendregBuf[8]=0x42;

  134.                     uiSendCnt=0; //发送数据的中间变量清零
  135.                     uiSendTimeCnt=0; //累计主循环次数的计数延时器清零
  136.                     ucSendStep=1; //切换到下一步发送一串数据
  137.                                 break;
  138.                            case 3:
  139.                     ucSendregBuf[0]=0xeb;    //把准备发送的数据放入发送缓冲区
  140.                     ucSendregBuf[1]=0x00;
  141.                     ucSendregBuf[2]=0x55;
  142.                     ucSendregBuf[3]=0x03;    //03代表3号键
  143.                     ucSendregBuf[4]=0x00;
  144.                     ucSendregBuf[5]=0x00;
  145.                     ucSendregBuf[6]=0x00;
  146.                     ucSendregBuf[7]=0x00;
  147.                     ucSendregBuf[8]=0x43;

  148.                     uiSendCnt=0; //发送数据的中间变量清零
  149.                     uiSendTimeCnt=0; //累计主循环次数的计数延时器清零
  150.                     ucSendStep=1; //切换到下一步发送一串数据
  151.                                 break;
  152.                            case 4:
  153.                     ucSendregBuf[0]=0xeb;    //把准备发送的数据放入发送缓冲区
  154.                     ucSendregBuf[1]=0x00;
  155.                     ucSendregBuf[2]=0x55;
  156.                     ucSendregBuf[3]=0x04;    //04代表4号键
  157.                     ucSendregBuf[4]=0x00;
  158.                     ucSendregBuf[5]=0x00;
  159.                     ucSendregBuf[6]=0x00;
  160.                     ucSendregBuf[7]=0x00;
  161.                     ucSendregBuf[8]=0x44;

  162.                     uiSendCnt=0; //发送数据的中间变量清零
  163.                     uiSendTimeCnt=0; //累计主循环次数的计数延时器清零
  164.                     ucSendStep=1; //切换到下一步发送一串数据
  165.                                 break;

  166.                default:  //如果没有符合要求的消息,则不处理

  167.                                 ucSendStep=0; //维持现状,不切换
  168.                                 break;
  169.                         }
  170.                  }
  171.              break;

  172.     case 1:  //利用累加主循环次数的计数延时方式来发送一串数据

  173. /* 注释二:
  174.   * 这里的计数延时为什么不用累计定时中断次数的延时,而用累计主循环次数的计数延时?
  175.   * 因为本程序定时器中断一次需要500个指令时间,时间分辨率太低,不方便微调时间。因此我
  176.   * 就用累计主循环次数的计数延时方式,在做项目的时候,各位读者应该根据系统的实际情况
  177.   * 来调整const_send_time的大小。
  178.   */  
  179.          uiSendTimeCnt++;  //累计主循环次数的计数延时,为每个字节之间增加延时,
  180.                  if(uiSendTimeCnt>const_send_time)  //请根据实际系统的情况,调整const_send_time的大小
  181.                  {
  182.                     uiSendTimeCnt=0;

  183.                         eusart_send(ucSendregBuf[uiSendCnt]);  //发送一串数据给上位机
  184.             uiSendCnt++;
  185.                         if(uiSendCnt>=9) //说明数据已经发送完毕
  186.                         {
  187.                            uiSendCnt=0;
  188.                ucSendStep=0; //返回到上一步,处理其它未处理的消息
  189.                         }
  190.                  }

  191.              break;  
  192.   }

  193. }


  194. void eusart_send(unsigned char ucSendData)
  195. {

  196.   ES = 0; //关串口中断
  197.   TI = 0; //清零串口发送完成中断请求标志
  198.   SBUF =ucSendData; //发送一个字节

  199. /* 注释三:
  200.   * 根据我个人的经验,在发送一串数据中,每个字节之间必须添加一个延时,用来等待串口发送完成。
  201.   * 当然,也有一些朋友可能不增加延时,直接靠单片机自带的发送完成标志位来判断,但是我以前
  202.   * 在做项目中,感觉单单靠发送完成标志位来判断还是容易出错(当然也有可能是我自身程序的问题),
  203.   * 所以后来在大部分的项目中我就干脆靠延时来等待它发送完成。我在51,PIC单片机中都是这么做的。
  204.   * 但是,凭我的经验,在stm32单片机中,可以不增加延时,直接靠单片机自带的标志位来判断就很可靠。
  205.   */  

  206. //  delay_short(400);  //因为外部在每个发送字节之间用了累计主循环次数的计数延时,因此不要此行的delay延时

  207.   TI = 0; //清零串口发送完成中断请求标志
  208.   ES = 1; //允许串口中断

  209. }


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


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

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

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

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


  272. }


  273. void key_service(void) //第三区 按键服务的应用程序
  274. {


  275.   switch(ucKeySec) //按键服务状态切换
  276.   {
  277.     case 1:// 1号键 对应朱兆祺学习板的S1键

  278.           insert_message(0x01);  //把新消息插入到环形消息队列里等待处理

  279.           ucVoiceLock=1;  //原子锁加锁,保护中断与主函数的共享数据
  280.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  281.                   ucVoiceLock=0; //原子锁解锁

  282.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  283.           break;   
  284.     case 2:// 2号键 对应朱兆祺学习板的S5键

  285.           insert_message(0x02);  //把新消息插入到环形消息队列里等待处理

  286.           ucVoiceLock=1;  //原子锁加锁,保护中断与主函数的共享数据
  287.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  288.                   ucVoiceLock=0; //原子锁解锁

  289.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  290.           break;     
  291.     case 3:// 3号键 对应朱兆祺学习板的S9键

  292.           insert_message(0x03);  //把新消息插入到环形消息队列里等待处理

  293.           ucVoiceLock=1;  //原子锁加锁,保护中断与主函数的共享数据
  294.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  295.                   ucVoiceLock=0; //原子锁解锁

  296.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  297.           break;  
  298.     case 4:// 4号键 对应朱兆祺学习板的S13键

  299.           insert_message(0x04);  //把新消息插入到环形消息队列里等待处理

  300.           ucVoiceLock=1;  //原子锁加锁,保护中断与主函数的共享数据
  301.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  302.                   ucVoiceLock=0; //原子锁解锁

  303.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  304.           break;   

  305.   }        
  306. }



  307. void T0_time(void) interrupt 1    //定时中断
  308. {
  309.   TF0=0;  //清除中断标志
  310.   TR0=0; //关中断

  311. /* 注释四:
  312.   * 此处多增加一个原子锁,作为中断与主函数共享数据的保护,实际上是借鉴了"红金龙吸味"关于原子锁的建议.
  313.   */  

  314.   if(ucVoiceLock==0) //原子锁判断
  315.   {
  316.      if(uiVoiceCnt!=0)
  317.      {

  318.         uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
  319.         beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  320.      
  321.      }
  322.      else
  323.      {

  324.         ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
  325.         beep_dr=1;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  326.         
  327.      }
  328.   }

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


  330.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  331.   TL0=0x0b;
  332.   TR0=1;  //开中断
  333. }


  334. void usart_receive(void) interrupt 4                 //串口中断        
  335. {        

  336.    if(RI==1)  
  337.    {
  338.         RI = 0;   //接收中断,及时把接收中断标志位清零

  339.       
  340.    
  341.    }
  342.    else
  343.    {
  344.         TI = 0;    //发送中断,及时把发送中断标志位清零
  345.    }
  346.                                                          
  347. }                                



  348. //void delay_short(unsigned int uiDelayShort)
  349. //{
  350. //   unsigned int i;  
  351. //   for(i=0;i<uiDelayShort;i++)
  352. //   {
  353. //     ;   //一个分号相当于执行一条空语句
  354. //   }
  355. //}


  356. void delay_long(unsigned int uiDelayLong)
  357. {
  358.    unsigned int i;
  359.    unsigned int j;
  360.    for(i=0;i<uiDelayLong;i++)
  361.    {
  362.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  363.           {
  364.              ; //一个分号相当于执行一条空语句
  365.           }
  366.    }
  367. }


  368. void initial_myself(void)  //第一区 初始化单片机
  369. {
  370. /* 注释五:
  371. * 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
  372. * 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
  373. * 坚鸿51学习板的S1和S5两个按键就是本程序中用到的两个独立按键。
  374. */
  375.   key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平

  376.   led_dr=0; //关Led灯
  377.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

  378.   //配置定时器
  379.   TMOD=0x01;  //设置定时器0为工作方式1
  380.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  381.   TL0=0x0b;


  382.   //配置串口
  383.   SCON=0x50;
  384.   TMOD=0X21;
  385.   TH1=TL1=-(11059200L/12/32/9600);  //串口波特率9600。
  386.   TR1=1;

  387. }

  388. void initial_peripheral(void) //第二区 初始化外围
  389. {

  390.    EA=1;     //开总中断
  391.    ES=1;     //允许串口中断
  392.    ET0=1;    //允许定时中断
  393.    TR0=1;    //启动定时中断

  394. }
复制代码

总结陈词:
         前面几个章节中,每个章节要么独立地讲解串口收数据,要么独立地讲解发数据,实际上在大部分的项目中,串口都需要“一收一应答”的握手协议,上位机作为主机,单片机作为从机,主机先发一串数据,从机收到数据后进行校验判断,如果校验正确则返回正确应答指令,如果校验错误则返回错误应答指令,主机收到应答指令后,如果发现是正确应答指令则继续发送其它的新数据,如果发现是错误应答指令,或者超时没有接收到任何应答指令,则继续重发,如果连续重发三次都是错误应答或者无应答,主机就进行报错处理。读者只要把我的串口收发程序结合起来,就很容易实现这样的功能,我就不再详细讲解了。从下一节开始我讲解单片机掉电后数据保存的内容,欲知详情,请听下回分解-----利用AT24C02进行掉电后的数据保存。

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

作者: jianhong_wu    时间: 2014-5-3 08:45
本帖最后由 jianhong_wu 于 2014-7-21 00:20 编辑

第四十四节:从机的串口收发综合程序框架

开场白:
根据上一节的预告,本来这一节内容打算讲“利用AT24C02进行掉电后的数据保存”的,但是由于网友“261854681”强烈建议我讲一个完整的串口收发程序实例,因此我决定再花两节篇幅讲讲这方面的内容。
实际上在大部分的项目中,串口都需要“一收一应答”的握手协议,上位机作为主机,单片机作为从机,主机先发一串数据,从机收到数据后进行校验判断,如果校验正确则返回正确应答指令,如果校验错误则返回错误应答指令,主机收到应答指令后,如果发现是正确应答指令则继续发送其它的新数据,如果发现是错误应答指令,或者超时没有接收到任何应答指令,则继续重发,如果连续重发三次都是错误应答或者无应答,主机就进行报错处理。
这节先讲从机的收发端程序实例。要教会大家三个知识点:
第一个:为了保证串口中断接收的数据不丢失,在初始化时必须设置IP = 0x10,相当于把串口中断设置为最高优先级,这个时候,串口中断可以打断任何其他的中断服务函数,实现中断嵌套。
第二个:从机端的收发端程序框架。
第三个:从机的状态指示程序框架。可以指示待机,通讯中,超时出错三种状态。

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

(1)硬件平台:
    基于坚鸿51单片机学习板。

(2)实现功能:
显示和独立按键部分根据第29节的程序来改编,用坚鸿51单片机学习板中的S1,S5,S9,S13作为独立按键。
      一共有4个窗口。每个窗口显示一个参数。有两种更改参数的方式:
第一种:按键更改参数:
    第8,7,6,5位数码管显示当前窗口,P-1代表第1个窗口,P-2代表第2个窗口,P-3代表第3个窗口,P-4代表第1个窗口。
    第4,3,2,1位数码管显示当前窗口被设置的参数。范围是从0到9999。S1是加按键,按下此按键会依次增加当前窗口的参数。S5是减按键,按下此按键会依次减少当前窗口的参数。S9是切换窗口按键,按下此按键会依次循环切换不同的窗口。S13是复位按键,当通讯超时蜂鸣器报警时,可以按下此键清除报警。

第二种:通过串口来更改参数:
     波特率是:9600.
通讯协议:EB 00 55  GG 00 02 XX XX  CY
其中第1,2,3位EB 00 55就是数据头
其中第4位GG就是数据类型。01代表更改参数1,02代表更改参数2,03代表更改参数3,04代表更改参数4,
其中第5,6位00 02就是有效数据长度。高位在左,低位在右。
其中从第7,8位XX XX是被更改的参数。高位在左,低位在右。
第9位CY是累加和,前面所有字节的累加。
一个完整的通讯必须接收完4串数据,每串数据之间的间隔时间不能超过10秒钟,否则认为通讯超时出错引发蜂鸣器报警。如果接收到得数据校验正确,
则返回校验正确应答:eb 00        55 f5 00 00 35,
否则返回校验出错应答::eb 00        55 fa 00 00 3a。
   系统处于待机状态时,LED灯一直亮,
   系统处于非待机状态时,LED灯闪烁,
   系统处于通讯超时出错状态时,LED灯闪烁,并且蜂鸣器间歇鸣叫报警。


通过电脑的串口助手,依次发送以下测试数据,将会分别更改参数1,参数2,参数3,参数4。注意,每串数据之间的时间最大不能超过10秒,否则系统认为通讯超时报警。
把参数1更改为十进制的1:   eb 00 55 01 00 02 00 01 44
把参数2更改为十进制的12:  eb 00 55 02 00 02 00 0c 50
把参数3更改为十进制的123: eb 00 55 03 00 02 00 7b c0
把参数4更改为十进制的1234:eb 00 55 04 00 02 04 d2 1c

(3)源代码讲解如下:
  1. #include "REG52.H"

  2. #define const_voice_short  40   //蜂鸣器短叫的持续时间
  3. #define const_key_time1  20    //按键去抖动延时的时间
  4. #define const_key_time2  20    //按键去抖动延时的时间
  5. #define const_key_time3  20    //按键去抖动延时的时间
  6. #define const_key_time4  20    //按键去抖动延时的时间

  7. #define const_led_0_5s  200   //大概0.5秒的时间
  8. #define const_led_1s    400   //大概1秒的时间

  9. #define const_send_time_out   4000  //通讯超时出错的时间 大概10秒

  10. #define const_rc_size  20  //接收串口中断数据的缓冲区数组大小
  11. #define const_receive_time  5  //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完,这个时间根据实际情况来调整大小

  12. #define const_send_size  10  //串口发送数据的缓冲区数组大小

  13. void initial_myself(void);   
  14. void initial_peripheral(void);
  15. void delay_short(unsigned int uiDelayShort);
  16. void delay_long(unsigned int uiDelaylong);
  17. //驱动数码管的74HC595
  18. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);  
  19. void display_drive(void); //显示数码管字模的驱动函数
  20. void display_service(void); //显示的窗口菜单服务程序
  21. //驱动LED的74HC595
  22. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);

  23. void T0_time(void);  //定时中断函数
  24. void usart_receive(void); //串口接收中断函数
  25. void usart_service(void);  //串口服务程序,在main函数里
  26. void eusart_send(unsigned char ucSendData); //发送一个字节,内部自带每个字节之间的delay延时

  27. void key_service(void); //按键服务的应用程序
  28. void key_scan(void);//按键扫描函数 放在定时中断里

  29. void status_service(void);  //状态显示的应用程序


  30. sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
  31. sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键
  32. sbit key_sr3=P0^2; //对应朱兆祺学习板的S9键
  33. sbit key_sr4=P0^3; //对应朱兆祺学习板的S13键
  34. sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平
  35. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
  36. sbit led_dr=P3^5;  //作为状态指示灯 亮的时候表示待机状态.闪烁表示非待机状态,处于正在发送数据或者出错的状态

  37. sbit dig_hc595_sh_dr=P2^0;     //数码管的74HC595程序
  38. sbit dig_hc595_st_dr=P2^1;  
  39. sbit dig_hc595_ds_dr=P2^2;  
  40. sbit hc595_sh_dr=P2^3;    //LED灯的74HC595程序
  41. sbit hc595_st_dr=P2^4;  
  42. sbit hc595_ds_dr=P2^5;  

  43. unsigned char ucSendregBuf[const_send_size]; //发送的缓冲区数组

  44. unsigned int  uiSendCnt=0;     //用来识别串口是否接收完一串数据的计时器
  45. unsigned char ucSendLock=1;    //串口服务程序的自锁变量,每次接收完一串数据只处理一次
  46. unsigned int  uiRcregTotal=0;  //代表当前缓冲区已经接收了多少个数据
  47. unsigned char ucRcregBuf[const_rc_size]; //接收串口中断数据的缓冲区数组
  48. unsigned int  uiRcMoveIndex=0;  //用来解析数据协议的中间变量

  49. unsigned char  ucSendCntLock=0; //串口计时器的原子锁
  50. unsigned char ucRcType=0;  //数据类型
  51. unsigned int  uiRcSize=0;  //数据长度
  52. unsigned char ucRcCy=0;  //校验累加和

  53. unsigned int  uiLedCnt=0;  //控制Led闪烁的延时计时器
  54. unsigned int  uiSendTimeOutCnt=0; //用来识别接收数据超时的计时器
  55. unsigned char ucSendTimeOutLock=0; //原子锁


  56. unsigned char ucStatus=0; //当前状态变量 0代表待机 1代表正在通讯过程 2代表发送出错

  57. unsigned char ucKeySec=0;   //被触发的按键编号

  58. unsigned int  uiKeyTimeCnt1=0; //按键去抖动延时计数器
  59. unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志
  60. unsigned int  uiKeyTimeCnt2=0; //按键去抖动延时计数器
  61. unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志
  62. unsigned int  uiKeyTimeCnt3=0; //按键去抖动延时计数器
  63. unsigned char ucKeyLock3=0; //按键触发后自锁的变量标志
  64. unsigned int  uiKeyTimeCnt4=0; //按键去抖动延时计数器
  65. unsigned char ucKeyLock4=0; //按键触发后自锁的变量标志


  66. unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器
  67. unsigned char  ucVoiceLock=0;  //蜂鸣器鸣叫的原子锁

  68. unsigned char ucDigShow8;  //第8位数码管要显示的内容
  69. unsigned char ucDigShow7;  //第7位数码管要显示的内容
  70. unsigned char ucDigShow6;  //第6位数码管要显示的内容
  71. unsigned char ucDigShow5;  //第5位数码管要显示的内容
  72. unsigned char ucDigShow4;  //第4位数码管要显示的内容
  73. unsigned char ucDigShow3;  //第3位数码管要显示的内容
  74. unsigned char ucDigShow2;  //第2位数码管要显示的内容
  75. unsigned char ucDigShow1;  //第1位数码管要显示的内容

  76. unsigned char ucDigDot8;  //数码管8的小数点是否显示的标志
  77. unsigned char ucDigDot7;  //数码管7的小数点是否显示的标志
  78. unsigned char ucDigDot6;  //数码管6的小数点是否显示的标志
  79. unsigned char ucDigDot5;  //数码管5的小数点是否显示的标志
  80. unsigned char ucDigDot4;  //数码管4的小数点是否显示的标志
  81. unsigned char ucDigDot3;  //数码管3的小数点是否显示的标志
  82. unsigned char ucDigDot2;  //数码管2的小数点是否显示的标志
  83. unsigned char ucDigDot1;  //数码管1的小数点是否显示的标志
  84. unsigned char ucDigShowTemp=0; //临时中间变量
  85. unsigned char ucDisplayDriveStep=1;  //动态扫描数码管的步骤变量

  86. unsigned char ucWd1Update=1; //窗口1更新显示标志
  87. unsigned char ucWd2Update=0; //窗口2更新显示标志
  88. unsigned char ucWd3Update=0; //窗口3更新显示标志
  89. unsigned char ucWd4Update=0; //窗口4更新显示标志
  90. unsigned char ucWd=1;  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
  91. unsigned int  uiSetData1=0;  //本程序中需要被设置的参数1
  92. unsigned int  uiSetData2=0;  //本程序中需要被设置的参数2
  93. unsigned int  uiSetData3=0;  //本程序中需要被设置的参数3
  94. unsigned int  uiSetData4=0;  //本程序中需要被设置的参数4

  95. unsigned char ucTemp1=0;  //中间过渡变量
  96. unsigned char ucTemp2=0;  //中间过渡变量
  97. unsigned char ucTemp3=0;  //中间过渡变量
  98. unsigned char ucTemp4=0;  //中间过渡变量

  99. //根据原理图得出的共阴数码管字模表
  100. code unsigned char dig_table[]=
  101. {
  102. 0x3f,  //0       序号0
  103. 0x06,  //1       序号1
  104. 0x5b,  //2       序号2
  105. 0x4f,  //3       序号3
  106. 0x66,  //4       序号4
  107. 0x6d,  //5       序号5
  108. 0x7d,  //6       序号6
  109. 0x07,  //7       序号7
  110. 0x7f,  //8       序号8
  111. 0x6f,  //9       序号9
  112. 0x00,  //无      序号10
  113. 0x40,  //-       序号11
  114. 0x73,  //P       序号12
  115. };
  116. void main()
  117.   {
  118.    initial_myself();  
  119.    delay_long(100);   
  120.    initial_peripheral();
  121.    while(1)  
  122.    {
  123.       key_service(); //按键服务的应用程序
  124.           usart_service();  //串口服务程序
  125.       display_service(); //显示的窗口菜单服务程序
  126.           status_service();  //状态显示的应用程序
  127.    }
  128. }

  129. void status_service(void)  //状态显示的应用程序
  130. {
  131.    if(ucStatus!=0) //处于非待机的状态,Led闪烁
  132.    {
  133.       if(uiLedCnt<const_led_0_5s)  //大概0.5秒
  134.           {
  135.              led_dr=1;  //前半秒亮

  136.                  if(ucStatus==2)  //处于发送数据出错的状态,则蜂鸣器间歇鸣叫报警
  137.                  {
  138.              ucVoiceLock=1;  //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
  139.              uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  140.              ucVoiceLock=0;  //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt
  141.                  }
  142.           }
  143.           else if(uiLedCnt<const_led_1s)  //大概1秒
  144.           {
  145.              led_dr=0; //前半秒灭
  146.           }
  147.           else
  148.           {
  149.              uiLedCnt=0; //延时计时器清零,让Led灯处于闪烁的反复循环中
  150.           }
  151.    
  152.    }
  153.    else  //处于待机状态,Led一直亮
  154.    {
  155.       led_dr=1;
  156.    
  157.    }



  158. }



  159. void usart_service(void)  //串口服务程序,在main函数里
  160. {

  161.      unsigned int i;  
  162.         
  163.      if(uiSendCnt>=const_receive_time&&ucSendLock==1) //说明超过了一定的时间内,再也没有新数据从串口来
  164.      {

  165.             ucSendLock=0;    //处理一次就锁起来,不用每次都进来,除非有新接收的数据
  166.             //下面的代码进入数据协议解析和数据处理的阶段

  167.             uiRcMoveIndex=0; //由于是判断数据头,所以下标移动变量从数组的0开始向最尾端移动

  168.             while(uiRcregTotal>=5&&uiRcMoveIndex<=(uiRcregTotal-5))
  169.             {

  170.                if(ucRcregBuf[uiRcMoveIndex+0]==0xeb&&ucRcregBuf[uiRcMoveIndex+1]==0x00&&ucRcregBuf[uiRcMoveIndex+2]==0x55)  //数据头eb 00 55的判断
  171.                {

  172.                    ucRcType=ucRcregBuf[uiRcMoveIndex+3];   //数据类型  一个字节
  173.                    uiRcSize=ucRcregBuf[uiRcMoveIndex+4];   //数据长度  两个字节
  174.                    uiRcSize=uiRcSize<<8;
  175.                    uiRcSize=uiRcSize+ucRcregBuf[uiRcMoveIndex+5];
  176.                                                                  
  177.                    ucRcCy=ucRcregBuf[uiRcMoveIndex+6+uiRcSize];   //记录最后一个字节的校验
  178.                    ucRcregBuf[uiRcMoveIndex+6+uiRcSize]=0;  //清零最后一个字节的累加和变量

  179.                    for(i=0;i<(3+1+2+uiRcSize);i++) //计算校验累加和
  180.                    {
  181.                       ucRcregBuf[uiRcMoveIndex+6+uiRcSize]=ucRcregBuf[uiRcMoveIndex+6+uiRcSize]+ucRcregBuf[uiRcMoveIndex+i];
  182.                    }        


  183.                    if(ucRcCy==ucRcregBuf[uiRcMoveIndex+6+uiRcSize])  //如果校验正确,则进入以下数据处理
  184.                    {                                                  
  185.                        switch(ucRcType)   //根据不同的数据类型来做不同的数据处理
  186.                        {
  187.                              case 0x01:   //设置参数1

  188.                                                               ucStatus=1; //从设置参数1开始,表示当前处于正在发送数据的状态

  189.                                   uiSetData1=ucRcregBuf[uiRcMoveIndex+6];  //把两个字节合并成一个int类型的数据
  190.                                   uiSetData1=uiSetData1<<8;  
  191.                                   uiSetData1=uiSetData1+ucRcregBuf[uiRcMoveIndex+7];
  192.                                   ucWd1Update=1; //窗口1更新显示
  193.                                   break;        
  194.                                                                         
  195.                              case 0x02:   //设置参数2

  196.                                   uiSetData2=ucRcregBuf[uiRcMoveIndex+6];  //把两个字节合并成一个int类型的数据
  197.                                   uiSetData2=uiSetData2<<8;  
  198.                                   uiSetData2=uiSetData2+ucRcregBuf[uiRcMoveIndex+7];
  199.                                   ucWd2Update=1; //窗口2更新显示
  200.                                   break;   

  201.                              case 0x03:   //设置参数3

  202.                                   uiSetData3=ucRcregBuf[uiRcMoveIndex+6];  //把两个字节合并成一个int类型的数据
  203.                                   uiSetData3=uiSetData3<<8;  
  204.                                   uiSetData3=uiSetData3+ucRcregBuf[uiRcMoveIndex+7];
  205.                                   ucWd3Update=1; //窗口3更新显示
  206.                                   break;  

  207.                              case 0x04:   //设置参数4

  208.                                                               ucStatus=0; //从设置参数4结束发送数据的状态,表示发送数据的过程成功,切换回待机状态

  209.                                   uiSetData4=ucRcregBuf[uiRcMoveIndex+6];  //把两个字节合并成一个int类型的数据
  210.                                   uiSetData4=uiSetData4<<8;  
  211.                                   uiSetData4=uiSetData4+ucRcregBuf[uiRcMoveIndex+7];
  212.                                   ucWd4Update=1; //窗口4更新显示
  213.                                   break;  

  214.                                                                         
  215.                         }


  216.                                                 ucSendregBuf[0]=0xeb;    //把准备发送的数据放入发送缓冲区
  217.                         ucSendregBuf[1]=0x00;
  218.                         ucSendregBuf[2]=0x55;
  219.                         ucSendregBuf[3]=0xf5;  //代表校验正确
  220.                         ucSendregBuf[4]=0x00;
  221.                         ucSendregBuf[5]=0x00;
  222.                         ucSendregBuf[6]=0x35;

  223.                         for(i=0;i<7;i++)  //返回校验正确的应答指令
  224.                         {
  225.                            eusart_send(ucSendregBuf[i]);  //发送一串数据给上位机
  226.                         }

  227.                      }   
  228.                                       else
  229.                                          {
  230.                         ucSendTimeOutLock=1; //原子锁加锁
  231.                                             uiSendTimeOutCnt=0;  //超时计时器计时清零
  232.                         ucSendTimeOutLock=0; //原子锁解锁

  233.                                                 ucSendregBuf[0]=0xeb;    //把准备发送的数据放入发送缓冲区
  234.                         ucSendregBuf[1]=0x00;
  235.                         ucSendregBuf[2]=0x55;
  236.                         ucSendregBuf[3]=0xfa;   //代表校验错误
  237.                         ucSendregBuf[4]=0x00;
  238.                         ucSendregBuf[5]=0x00;
  239.                         ucSendregBuf[6]=0x3a;   

  240.                         for(i=0;i<7;i++)  //返回校验错误的应答指令
  241.                         {
  242.                            eusart_send(ucSendregBuf[i]);  //发送一串数据给上位机
  243.                         }                                         
  244.                                          
  245.                                          }

  246.                                          ucSendTimeOutLock=1; //原子锁加锁
  247.                                          uiSendTimeOutCnt=0;  //超时计时器计时清零
  248.                      ucSendTimeOutLock=0; //原子锁解锁

  249.                      break;   //退出循环
  250.                }
  251.                uiRcMoveIndex++; //因为是判断数据头,游标向着数组最尾端的方向移动
  252.            }
  253.                                          
  254.            uiRcregTotal=0;  //清空缓冲的下标,方便下次重新从0下标开始接受新数据
  255.   
  256.      }
  257.                         
  258. }


  259. void eusart_send(unsigned char ucSendData) //发送一个字节,内部自带每个字节之间的delay延时
  260. {

  261.   ES = 0; //关串口中断
  262.   TI = 0; //清零串口发送完成中断请求标志
  263.   SBUF =ucSendData; //发送一个字节

  264.   delay_short(400);  //每个字节之间的延时,这里非常关键,也是最容易出错的地方。延时的大小请根据实际项目来调整

  265.   TI = 0; //清零串口发送完成中断请求标志
  266.   ES = 1; //允许串口中断

  267. }


  268. void display_service(void) //显示的窗口菜单服务程序
  269. {

  270.    switch(ucWd)  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
  271.    {
  272.        case 1:   //显示P--1窗口的数据
  273.             if(ucWd1Update==1)  //窗口1要全部更新显示
  274.    {
  275.                ucWd1Update=0;  //及时清零标志,避免一直进来扫描
  276.                ucDigShow8=12;  //第8位数码管显示P
  277.                ucDigShow7=11;  //第7位数码管显示-
  278.                ucDigShow6=1;   //第6位数码管显示1
  279.                ucDigShow5=10;  //第5位数码管显示无

  280.               //先分解数据
  281.                        ucTemp4=uiSetData1/1000;     
  282.                        ucTemp3=uiSetData1%1000/100;
  283.                        ucTemp2=uiSetData1%100/10;
  284.                        ucTemp1=uiSetData1%10;
  285.   
  286.                           //再过渡需要显示的数据到缓冲变量里,让过渡的时间越短越好

  287.                if(uiSetData1<1000)   
  288.                            {
  289.                               ucDigShow4=10;  //如果小于1000,千位显示无
  290.                            }
  291.                else
  292.                            {
  293.                   ucDigShow4=ucTemp4;  //第4位数码管要显示的内容
  294.                            }
  295.                if(uiSetData1<100)
  296.                            {
  297.                   ucDigShow3=10;  //如果小于100,百位显示无
  298.                            }
  299.                            else
  300.                            {
  301.                   ucDigShow3=ucTemp3;  //第3位数码管要显示的内容
  302.                            }
  303.                if(uiSetData1<10)
  304.                            {
  305.                   ucDigShow2=10;  //如果小于10,十位显示无
  306.                            }
  307.                            else
  308.                            {
  309.                   ucDigShow2=ucTemp2;  //第2位数码管要显示的内容
  310.                }
  311.                ucDigShow1=ucTemp1;  //第1位数码管要显示的内容
  312.             }
  313.             break;
  314.         case 2:  //显示P--2窗口的数据
  315.             if(ucWd2Update==1)  //窗口2要全部更新显示
  316.    {
  317.                ucWd2Update=0;  //及时清零标志,避免一直进来扫描
  318.                ucDigShow8=12;  //第8位数码管显示P
  319.                ucDigShow7=11;  //第7位数码管显示-
  320.                ucDigShow6=2;  //第6位数码管显示2
  321.                ucDigShow5=10;   //第5位数码管显示无
  322.                        ucTemp4=uiSetData2/1000;     //分解数据
  323.                        ucTemp3=uiSetData2%1000/100;
  324.                        ucTemp2=uiSetData2%100/10;
  325.                        ucTemp1=uiSetData2%10;

  326.                if(uiSetData2<1000)   
  327.                            {
  328.                               ucDigShow4=10;  //如果小于1000,千位显示无
  329.                            }
  330.                else
  331.                            {
  332.                   ucDigShow4=ucTemp4;  //第4位数码管要显示的内容
  333.                            }
  334.                if(uiSetData2<100)
  335.                            {
  336.                   ucDigShow3=10;  //如果小于100,百位显示无
  337.                            }
  338.                            else
  339.                            {
  340.                   ucDigShow3=ucTemp3;  //第3位数码管要显示的内容
  341.                            }
  342.                if(uiSetData2<10)
  343.                            {
  344.                   ucDigShow2=10;  //如果小于10,十位显示无
  345.                            }
  346.                            else
  347.                            {
  348.                   ucDigShow2=ucTemp2;  //第2位数码管要显示的内容
  349.                }
  350.                ucDigShow1=ucTemp1;  //第1位数码管要显示的内容
  351.     }
  352.              break;
  353.         case 3:  //显示P--3窗口的数据
  354.             if(ucWd3Update==1)  //窗口3要全部更新显示
  355.    {
  356.                ucWd3Update=0;  //及时清零标志,避免一直进来扫描
  357.                ucDigShow8=12;  //第8位数码管显示P
  358.                ucDigShow7=11;  //第7位数码管显示-
  359.                ucDigShow6=3;  //第6位数码管显示3
  360.                ucDigShow5=10;   //第5位数码管显示无
  361.                        ucTemp4=uiSetData3/1000;     //分解数据
  362.                        ucTemp3=uiSetData3%1000/100;
  363.                        ucTemp2=uiSetData3%100/10;
  364.                        ucTemp1=uiSetData3%10;
  365.                if(uiSetData3<1000)   
  366.                            {
  367.                               ucDigShow4=10;  //如果小于1000,千位显示无
  368.                            }
  369.                else
  370.                            {
  371.                   ucDigShow4=ucTemp4;  //第4位数码管要显示的内容
  372.                            }
  373.                if(uiSetData3<100)
  374.                            {
  375.                   ucDigShow3=10;  //如果小于100,百位显示无
  376.                            }
  377.                            else
  378.                            {
  379.                   ucDigShow3=ucTemp3;  //第3位数码管要显示的内容
  380.                            }
  381.                if(uiSetData3<10)
  382.                            {
  383.                   ucDigShow2=10;  //如果小于10,十位显示无
  384.                            }
  385.                            else
  386.                            {
  387.                   ucDigShow2=ucTemp2;  //第2位数码管要显示的内容
  388.                }
  389.                ucDigShow1=ucTemp1;  //第1位数码管要显示的内容
  390.    }
  391.             break;
  392.         case 4:  //显示P--4窗口的数据
  393.             if(ucWd4Update==1)  //窗口4要全部更新显示
  394.    {
  395.                ucWd4Update=0;  //及时清零标志,避免一直进来扫描
  396.                ucDigShow8=12;  //第8位数码管显示P
  397.                ucDigShow7=11;  //第7位数码管显示-
  398.                ucDigShow6=4;  //第6位数码管显示4
  399.                ucDigShow5=10;   //第5位数码管显示无
  400.                        ucTemp4=uiSetData4/1000;     //分解数据
  401.                        ucTemp3=uiSetData4%1000/100;
  402.                        ucTemp2=uiSetData4%100/10;
  403.                        ucTemp1=uiSetData4%10;

  404.                if(uiSetData4<1000)   
  405.                            {
  406.                               ucDigShow4=10;  //如果小于1000,千位显示无
  407.                            }
  408.                else
  409.                            {
  410.                   ucDigShow4=ucTemp4;  //第4位数码管要显示的内容
  411.                            }
  412.                if(uiSetData4<100)
  413.                            {
  414.                   ucDigShow3=10;  //如果小于100,百位显示无
  415.                            }
  416.                            else
  417.                            {
  418.                   ucDigShow3=ucTemp3;  //第3位数码管要显示的内容
  419.                            }
  420.                if(uiSetData4<10)
  421.                            {
  422.                   ucDigShow2=10;  //如果小于10,十位显示无
  423.                            }
  424.                            else
  425.                            {
  426.                   ucDigShow2=ucTemp2;  //第2位数码管要显示的内容
  427.                }
  428.                ucDigShow1=ucTemp1;  //第1位数码管要显示的内容
  429.     }
  430.              break;
  431.            }
  432.    

  433. }

  434. void key_scan(void)//按键扫描函数 放在定时中断里
  435. {  
  436.   if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  437.   {
  438.      ucKeyLock1=0; //按键自锁标志清零
  439.      uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  440.   }
  441.   else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
  442.   {
  443.      uiKeyTimeCnt1++; //累加定时中断次数
  444.      if(uiKeyTimeCnt1>const_key_time1)
  445.      {
  446.         uiKeyTimeCnt1=0;
  447.         ucKeyLock1=1;  //自锁按键置位,避免一直触发
  448.         ucKeySec=1;    //触发1号键
  449.      }
  450.   }

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

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

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

  497. void key_service(void) //按键服务的应用程序
  498. {

  499.   switch(ucKeySec) //按键服务状态切换
  500.   {
  501.     case 1:// 加按键 对应朱兆祺学习板的S1键
  502.           switch(ucWd)  //在不同的窗口下,设置不同的参数
  503.                   {
  504.                      case 1:
  505.                   uiSetData1++;   
  506.                                   if(uiSetData1>9999) //最大值是9999
  507.                                   {
  508.                                      uiSetData1=9999;
  509.                                   }
  510.                            ucWd1Update=1;  //窗口1更新显示
  511.                               break;
  512.                      case 2:
  513.                   uiSetData2++;
  514.                                   if(uiSetData2>9999) //最大值是9999
  515.                                   {
  516.                                      uiSetData2=9999;
  517.                                   }
  518.                            ucWd2Update=1;  //窗口2更新显示
  519.                               break;
  520.                      case 3:
  521.                   uiSetData3++;
  522.                                   if(uiSetData3>9999) //最大值是9999
  523.                                   {
  524.                                      uiSetData3=9999;
  525.                                   }
  526.                            ucWd3Update=1;  //窗口3更新显示
  527.                               break;
  528.                      case 4:
  529.                   uiSetData4++;
  530.                                   if(uiSetData4>9999) //最大值是9999
  531.                                   {
  532.                                      uiSetData4=9999;
  533.                                   }
  534.                            ucWd4Update=1;  //窗口4更新显示
  535.                               break;
  536.                   }

  537.           ucVoiceLock=1;  //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
  538.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  539.           ucVoiceLock=0;  //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt

  540.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  541.           break;   
  542.    
  543.     case 2:// 减按键 对应朱兆祺学习板的S5键
  544.           switch(ucWd)  //在不同的窗口下,设置不同的参数
  545.                   {
  546.                      case 1:
  547.                   uiSetData1--;   

  548.                                   if(uiSetData1>9999)  
  549.                                   {
  550.                                      uiSetData1=0;  //最小值是0
  551.                                   }
  552.                            ucWd1Update=1;  //窗口1更新显示
  553.                               break;
  554.                      case 2:
  555.                   uiSetData2--;
  556.                                   if(uiSetData2>9999)
  557.                                   {
  558.                                      uiSetData2=0;  //最小值是0
  559.                                   }
  560.                            ucWd2Update=1;  //窗口2更新显示
  561.                               break;
  562.                      case 3:
  563.                   uiSetData3--;
  564.                                   if(uiSetData3>9999)
  565.                                   {
  566.                                      uiSetData3=0;  //最小值是0
  567.                                   }
  568.                            ucWd3Update=1;  //窗口3更新显示
  569.                               break;
  570.                      case 4:
  571.                   uiSetData4--;
  572.                                   if(uiSetData4>9999)
  573.                                   {
  574.                                      uiSetData4=0;  //最小值是0
  575.                                   }
  576.                            ucWd4Update=1;  //窗口4更新显示
  577.                               break;
  578.                   }

  579.           ucVoiceLock=1;  //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
  580.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  581.           ucVoiceLock=0;  //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt

  582.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  583.           break;  

  584.     case 3:// 切换窗口按键 对应朱兆祺学习板的S9键
  585.           ucWd++;  //切换窗口
  586.                   if(ucWd>4)
  587.                   {
  588.                     ucWd=1;
  589.                   }
  590.           switch(ucWd)  //在不同的窗口下,在不同的窗口下,更新显示不同的窗口
  591.                   {
  592.                      case 1:
  593.                            ucWd1Update=1;  //窗口1更新显示
  594.                               break;
  595.                      case 2:
  596.                            ucWd2Update=1;  //窗口2更新显示
  597.                               break;
  598.                      case 3:
  599.                            ucWd3Update=1;  //窗口3更新显示
  600.                               break;
  601.                      case 4:
  602.                            ucWd4Update=1;  //窗口4更新显示
  603.                               break;
  604.                   }
  605.           ucVoiceLock=1;  //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
  606.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  607.           ucVoiceLock=0;  //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt

  608.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  609.           break;         

  610.     case 4:// 复位按键 对应朱兆祺学习板的S13键
  611.           switch(ucStatus)  //在不同的状态下,进行不同的操作
  612.           {
  613.              case 0:  //处于待机状态
  614.                   break;

  615.              case 1:  //处于正在通讯的过程
  616.                   break;

  617.              case 2: //发送数据出错,比如中间超时没有接收到数据
  618.                   ucStatus=0; //切换回待机的状态
  619.                   break;
  620.           }
  621.           ucVoiceLock=1;  //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
  622.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  623.           ucVoiceLock=0;  //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt

  624.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发

  625.           break;   
  626.          
  627.   }               
  628. }

  629. void display_drive(void)  
  630. {
  631.    //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
  632.    switch(ucDisplayDriveStep)
  633.    {
  634.       case 1:  //显示第1位
  635.            ucDigShowTemp=dig_table[ucDigShow1];
  636.                    if(ucDigDot1==1)
  637.                    {
  638.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  639.                    }
  640.            dig_hc595_drive(ucDigShowTemp,0xfe);
  641.                break;
  642.       case 2:  //显示第2位
  643.            ucDigShowTemp=dig_table[ucDigShow2];
  644.                    if(ucDigDot2==1)
  645.                    {
  646.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  647.                    }
  648.            dig_hc595_drive(ucDigShowTemp,0xfd);
  649.                break;
  650.       case 3:  //显示第3位
  651.            ucDigShowTemp=dig_table[ucDigShow3];
  652.                    if(ucDigDot3==1)
  653.                    {
  654.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  655.                    }
  656.            dig_hc595_drive(ucDigShowTemp,0xfb);
  657.                break;
  658.       case 4:  //显示第4位
  659.            ucDigShowTemp=dig_table[ucDigShow4];
  660.                    if(ucDigDot4==1)
  661.                    {
  662.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  663.                    }
  664.            dig_hc595_drive(ucDigShowTemp,0xf7);
  665.                break;
  666.       case 5:  //显示第5位
  667.            ucDigShowTemp=dig_table[ucDigShow5];
  668.                    if(ucDigDot5==1)
  669.                    {
  670.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  671.                    }
  672.            dig_hc595_drive(ucDigShowTemp,0xef);
  673.                break;
  674.       case 6:  //显示第6位
  675.            ucDigShowTemp=dig_table[ucDigShow6];
  676.                    if(ucDigDot6==1)
  677.                    {
  678.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  679.                    }
  680.            dig_hc595_drive(ucDigShowTemp,0xdf);
  681.                break;
  682.       case 7:  //显示第7位
  683.            ucDigShowTemp=dig_table[ucDigShow7];
  684.                    if(ucDigDot7==1)
  685.                    {
  686.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  687.            }
  688.            dig_hc595_drive(ucDigShowTemp,0xbf);
  689.                break;
  690.       case 8:  //显示第8位
  691.            ucDigShowTemp=dig_table[ucDigShow8];
  692.                    if(ucDigDot8==1)
  693.                    {
  694.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  695.                    }
  696.            dig_hc595_drive(ucDigShowTemp,0x7f);
  697.                break;
  698.    }
  699.    ucDisplayDriveStep++;
  700.    if(ucDisplayDriveStep>8)  //扫描完8个数码管后,重新从第一个开始扫描
  701.    {
  702.      ucDisplayDriveStep=1;
  703.    }

  704. }

  705. //数码管的74HC595驱动函数
  706. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
  707. {
  708.    unsigned char i;
  709.    unsigned char ucTempData;
  710.    dig_hc595_sh_dr=0;
  711.    dig_hc595_st_dr=0;
  712.    ucTempData=ucDigStatusTemp16_09;  //先送高8位
  713.    for(i=0;i<8;i++)
  714.    {
  715.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  716.          else dig_hc595_ds_dr=0;
  717.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  718.          delay_short(1);
  719.          dig_hc595_sh_dr=1;
  720.          delay_short(1);
  721.          ucTempData=ucTempData<<1;
  722.    }
  723.    ucTempData=ucDigStatusTemp08_01;  //再先送低8位
  724.    for(i=0;i<8;i++)
  725.    {
  726.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  727.          else dig_hc595_ds_dr=0;
  728.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  729.          delay_short(1);
  730.          dig_hc595_sh_dr=1;
  731.          delay_short(1);
  732.          ucTempData=ucTempData<<1;
  733.    }
  734.    dig_hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  735.    delay_short(1);
  736.    dig_hc595_st_dr=1;
  737.    delay_short(1);
  738.    dig_hc595_sh_dr=0;    //拉低,抗干扰就增强
  739.    dig_hc595_st_dr=0;
  740.    dig_hc595_ds_dr=0;
  741. }

  742. //LED灯的74HC595驱动函数
  743. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
  744. {
  745.    unsigned char i;
  746.    unsigned char ucTempData;
  747.    hc595_sh_dr=0;
  748.    hc595_st_dr=0;
  749.    ucTempData=ucLedStatusTemp16_09;  //先送高8位
  750.    for(i=0;i<8;i++)
  751.    {
  752.          if(ucTempData>=0x80)hc595_ds_dr=1;
  753.          else hc595_ds_dr=0;
  754.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  755.          delay_short(1);
  756.          hc595_sh_dr=1;
  757.          delay_short(1);
  758.          ucTempData=ucTempData<<1;
  759.    }
  760.    ucTempData=ucLedStatusTemp08_01;  //再先送低8位
  761.    for(i=0;i<8;i++)
  762.    {
  763.          if(ucTempData>=0x80)hc595_ds_dr=1;
  764.          else hc595_ds_dr=0;
  765.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  766.          delay_short(1);
  767.          hc595_sh_dr=1;
  768.          delay_short(1);
  769.          ucTempData=ucTempData<<1;
  770.    }
  771.    hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  772.    delay_short(1);
  773.    hc595_st_dr=1;
  774.    delay_short(1);
  775.    hc595_sh_dr=0;    //拉低,抗干扰就增强
  776.    hc595_st_dr=0;
  777.    hc595_ds_dr=0;
  778. }


  779. void usart_receive(void) interrupt 4   //串口接收数据中断        
  780. {        

  781.    if(RI==1)  
  782.    {
  783.         RI = 0;

  784.          ++uiRcregTotal;
  785.         if(uiRcregTotal>const_rc_size)  //超过缓冲区
  786.         {
  787.            uiRcregTotal=const_rc_size;
  788.         }
  789.         ucRcregBuf[uiRcregTotal-1]=SBUF;   //将串口接收到的数据缓存到接收缓冲区里

  790.         if(ucSendCntLock==0)  //原子锁判断
  791.         {
  792.             ucSendCntLock=1; //加锁
  793.             uiSendCnt=0;  //及时喂狗,虽然在定时中断那边此变量会不断累加,但是只要串口的数据还没发送完毕,那么它永远也长不大,因为每个串口接收中断它都被清零。
  794.             ucSendCntLock=0; //解锁
  795.         }
  796.    
  797.    }
  798.    else  //我在其它单片机上都不用else这段代码的,可能在51单片机上多增加" TI = 0;"稳定性会更好吧。
  799.    {
  800.         TI = 0;  //如果不是串口接收中断,那么必然是串口发送中断,及时清除发送中断的标志,否则一直发送中断
  801.    }
  802.                                                          
  803. }  

  804. void T0_time(void) interrupt 1   //定时中断
  805. {
  806.   TF0=0;  //清除中断标志
  807.   TR0=0; //关中断


  808. /* 注释一:
  809.   * 此处多增加一个原子锁,作为中断与主函数共享数据的保护,实际上是借鉴了"红金龙吸味"关于原子锁的建议.
  810.   */  
  811.   if(ucSendCntLock==0)  //原子锁判断
  812.   {
  813.      ucSendCntLock=1; //加锁
  814.      if(uiSendCnt<const_receive_time)   //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完
  815.      {
  816.         uiSendCnt++;    //表面上这个数据不断累加,但是在串口中断里,每接收一个字节它都会被清零,除非这个中间没有串口数据过来
  817.         ucSendLock=1;     //开自锁标志
  818.      }
  819.      ucSendCntLock=0; //解锁
  820.   }

  821.   if(ucVoiceLock==0) //原子锁判断
  822.   {
  823.      if(uiVoiceCnt!=0)
  824.      {

  825.         uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
  826.         beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  827.      
  828.      }
  829.      else
  830.      {

  831.         ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
  832.         beep_dr=1;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  833.         
  834.      }
  835.   }

  836.   if(ucStatus!=0) //处于非待机的状态,Led闪烁
  837.   {
  838.      uiLedCnt++; //Led闪烁计时器不断累加
  839.   }

  840.   if(ucStatus==1) //处于正在通讯的状态,
  841.   {
  842.      if(ucSendTimeOutLock==0)  //原子锁判断
  843.          {
  844.         uiSendTimeOutCnt++;   //超时计时器累加
  845.             if(uiSendTimeOutCnt>const_send_time_out)  //超时出错
  846.             {
  847.                uiSendTimeOutCnt=0;
  848.                ucStatus=2;  //切换到出错报警状态
  849.              }
  850.          }
  851.   }



  852.   key_scan(); //按键扫描函数
  853.   display_drive();  //数码管字模的驱动函数

  854.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  855.   TL0=0x0b;
  856.   TR0=1;  //开中断
  857. }

  858. void delay_short(unsigned int uiDelayShort)
  859. {
  860.    unsigned int i;  
  861.    for(i=0;i<uiDelayShort;i++)
  862.    {
  863.      ;   //一个分号相当于执行一条空语句
  864.    }
  865. }

  866. void delay_long(unsigned int uiDelayLong)
  867. {
  868.    unsigned int i;
  869.    unsigned int j;
  870.    for(i=0;i<uiDelayLong;i++)
  871.    {
  872.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  873.           {
  874.              ; //一个分号相当于执行一条空语句
  875.           }
  876.    }
  877. }

  878. void initial_myself(void)  //第一区 初始化单片机
  879. {
  880. /* 注释二:
  881. * 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
  882. * 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
  883. * 坚鸿51学习板的S1就是本程序中用到的一个独立按键。
  884. */
  885.   key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平
  886.   led_dr=1;  //点亮独立LED灯
  887.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
  888.   hc595_drive(0x00,0x00);  //关闭所有经过另外两个74HC595驱动的LED灯
  889.   TMOD=0x01;  //设置定时器0为工作方式1
  890.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  891.   TL0=0x0b;

  892.   //配置串口
  893.   SCON=0x50;
  894.   TMOD=0X21;

  895. /* 注释三:
  896. * 为了保证串口中断接收的数据不丢失,必须设置IP = 0x10,相当于把串口中断设置为最高优先级,
  897. * 这个时候,串口中断可以打断任何其他的中断服务函数实现嵌套,
  898. */
  899.   IP =0x10;  //把串口中断设置为最高优先级,必须的。

  900.   TH1=TL1=-(11059200L/12/32/9600);  //串口波特率为9600。
  901.   TR1=1;
  902. }
  903. void initial_peripheral(void) //第二区 初始化外围
  904. {

  905.    ucDigDot8=0;   //小数点全部不显示
  906.    ucDigDot7=0;  
  907.    ucDigDot6=0;
  908.    ucDigDot5=0;  
  909.    ucDigDot4=0;
  910.    ucDigDot3=0;  
  911.    ucDigDot2=0;
  912.    ucDigDot1=0;

  913.    EA=1;     //开总中断
  914.    ES=1;     //允许串口中断
  915.    ET0=1;    //允许定时中断
  916.    TR0=1;    //启动定时中断
  917. }
复制代码



总结陈词:
   这节详细讲了从机收发端的程序框架,而主机端的程序则用电脑的串口助手来模拟。实际上,主机端的程序也有很多内容,它包括依次发送每一串数据,根据返回的应答来决定是否需要重发数据,重发三次如果没反应则进行报错,以及超时没接收到数据等等内容。主机收发端的程序框架是什么样的?欲知详情,请听下回分解-----主机的串口收发综合程序框架
  
(未完待续,下节更精彩,不要走开哦)


作者: jianhong_wu    时间: 2014-5-5 12:10
本帖最后由 jianhong_wu 于 2014-11-29 22:52 编辑

第四十五节:主机的串口收发综合程序框架

开场白:
在大部分的项目中,串口都需要“一收一应答”的握手协议,主机先发一串数据,从机收到数据后进行校验判断,如果校验正确则返回正确应答指令,如果校验错误则返回错误应答指令,主机收到应答指令后,如果发现是正确应答指令则继续发送其它的新数据,如果发现是错误应答指令,或者超时没有接收到任何应答指令,则继续重发,如果连续重发三次都是错误应答或者无应答,主机就进行报错处理。
     上一节已经讲了从机,这节就讲主机的收发端程序实例。要教会大家四个知识点:

第一个:为了保证串口中断接收的数据不丢失,在初始化时必须设置IP = 0x10,相当于把串口中断设置为最高优先级,这个时候,串口中断可以打断任何其他的中断服务函数,实现中断嵌套。
第二个:主机端的收发端程序框架。包括重发,超时检测等等。
第三个:主机的状态指示程序框架。可以指示待机,通讯中,超时出错三种状态。
第四个:其实上一节的LED灯闪烁的时间里,我忘了加原子锁,不加原子锁的后果是,闪烁的时间有时候会不一致,所以这节多增加一个原子锁变量ucLedLock,再次感谢“红金龙吸味”关于原子锁的建议,真的很好用。

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

(1)硬件平台:
    基于坚鸿51单片机学习板。

(2)实现功能:
显示和独立按键部分根据第29节的程序来改编,用坚鸿51单片机学习板中的S1,S5,S9,S13作为独立按键。
      一共有4个窗口。每个窗口显示一个参数。串口可以把当前设置的4个数据发送给从机。从机端可以用电脑的串口助手来模拟。
    按键更改参数:
    第8,7,6,5位数码管显示当前窗口,P-1代表第1个窗口,P-2代表第2个窗口,P-3代表第3个窗口,P-4代表第1个窗口。
    第4,3,2,1位数码管显示当前窗口被设置的参数。范围是从0到9999。S1是加按键,按下此按键会依次增加当前窗口的参数。S5是减按键,按下此按键会依次减少当前窗口的参数。S9是切换窗口按键,按下此按键会依次循环切换不同的窗口。S13是启动发送数据和复位按键,当系统处于待机状态时,按下此按键会启动发送数据,注意,单片机每发送一串数据,必须在电脑串口助手端发送应答信号,否则单片机重发3次后会引发超时报警;当通讯超时蜂鸣器报警时,可以按下此键清除报警,返回到待机的状态。
通过电脑的串口助手来模拟从机,返回不同的应答
从机返回校验正确应答:eb 00 55 f5 00 00 35
从机返回校验出错应答:eb 00 55 fa 00 00 3a

   系统处于待机状态时,LED灯一直亮,
   系统处于非待机状态时,LED灯闪烁,
   系统处于出错状态时,LED灯闪烁,并且蜂鸣器间歇鸣叫报警。


(3)源代码讲解如下:
  1. #include "REG52.H"

  2. #define const_voice_short  40   //蜂鸣器短叫的持续时间
  3. #define const_key_time1  20    //按键去抖动延时的时间
  4. #define const_key_time2  20    //按键去抖动延时的时间
  5. #define const_key_time3  20    //按键去抖动延时的时间
  6. #define const_key_time4  20    //按键去抖动延时的时间

  7. #define const_led_0_5s  200   //大概0.5秒的时间
  8. #define const_led_1s    400   //大概1秒的时间

  9. #define const_send_time_out   4000  //通讯超时出错的时间 大概10秒

  10. #define const_rc_size  20  //接收串口中断数据的缓冲区数组大小
  11. #define const_receive_time  5  //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完,这个时间根据实际情况来调整大小

  12. #define const_send_size  10  //串口发送数据的缓冲区数组大小

  13. void initial_myself(void);   
  14. void initial_peripheral(void);
  15. void delay_short(unsigned int uiDelayShort);
  16. void delay_long(unsigned int uiDelaylong);
  17. //驱动数码管的74HC595
  18. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);  
  19. void display_drive(void); //显示数码管字模的驱动函数
  20. void display_service(void); //显示的窗口菜单服务程序
  21. //驱动LED的74HC595
  22. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);

  23. void T0_time(void);  //定时中断函数
  24. void usart_receive(void); //串口接收中断函数
  25. void usart_service(void);  //串口接收服务程序,在main函数里
  26. void communication_service(void); //一发一收的通讯服务程序
  27. void eusart_send(unsigned char ucSendData); //发送一个字节,内部自带每个字节之间的delay延时

  28. void key_service(void); //按键服务的应用程序
  29. void key_scan(void);//按键扫描函数 放在定时中断里

  30. void status_service(void);  //状态显示的应用程序


  31. sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
  32. sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键
  33. sbit key_sr3=P0^2; //对应朱兆祺学习板的S9键
  34. sbit key_sr4=P0^3; //对应朱兆祺学习板的S13键
  35. sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平
  36. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
  37. sbit led_dr=P3^5;  //作为状态指示灯 亮的时候表示待机状态.闪烁表示非待机状态,处于正在发送数据或者出错的状态

  38. sbit dig_hc595_sh_dr=P2^0;     //数码管的74HC595程序
  39. sbit dig_hc595_st_dr=P2^1;  
  40. sbit dig_hc595_ds_dr=P2^2;  
  41. sbit hc595_sh_dr=P2^3;    //LED灯的74HC595程序
  42. sbit hc595_st_dr=P2^4;  
  43. sbit hc595_ds_dr=P2^5;  

  44. unsigned char ucSendregBuf[const_send_size]; //发送的缓冲区数组

  45. unsigned int  uiSendCnt=0;     //用来识别串口是否接收完一串数据的计时器
  46. unsigned char ucSendLock=1;    //串口服务程序的自锁变量,每次接收完一串数据只处理一次
  47. unsigned int  uiRcregTotal=0;  //代表当前缓冲区已经接收了多少个数据
  48. unsigned char ucRcregBuf[const_rc_size]; //接收串口中断数据的缓冲区数组
  49. unsigned int  uiRcMoveIndex=0;  //用来解析数据协议的中间变量

  50. unsigned char  ucSendCntLock=0; //串口计时器的原子锁
  51. unsigned char ucRcType=0;  //数据类型
  52. unsigned int  uiRcSize=0;  //数据长度
  53. unsigned char ucRcCy=0;  //校验累加和

  54. unsigned char ucLedLock=0; //原子锁
  55. unsigned int  uiLedCnt=0;  //控制Led闪烁的延时计时器
  56. unsigned int  uiSendTimeOutCnt=0; //用来识别接收数据超时的计时器
  57. unsigned char ucSendTimeOutLock=0; //原子锁


  58. unsigned char ucStatus=0; //当前状态变量 0代表待机 1代表正在通讯过程 2代表发送出错
  59. unsigned char ucSendStep=0; //发送数据的过程步骤
  60. unsigned char ucErrorCnt=0; //累计错误总数
  61. unsigned char ucSendTotal=0; //记录当前已经发送了多少串数据
  62. unsigned char ucReceiveStatus=0; //返回的数据状态 0代表待机 1代表校验正确 2代表校验出错

  63. unsigned char ucKeySec=0;   //被触发的按键编号

  64. unsigned int  uiKeyTimeCnt1=0; //按键去抖动延时计数器
  65. unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志
  66. unsigned int  uiKeyTimeCnt2=0; //按键去抖动延时计数器
  67. unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志
  68. unsigned int  uiKeyTimeCnt3=0; //按键去抖动延时计数器
  69. unsigned char ucKeyLock3=0; //按键触发后自锁的变量标志
  70. unsigned int  uiKeyTimeCnt4=0; //按键去抖动延时计数器
  71. unsigned char ucKeyLock4=0; //按键触发后自锁的变量标志


  72. unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器
  73. unsigned char  ucVoiceLock=0;  //蜂鸣器鸣叫的原子锁

  74. unsigned char ucDigShow8;  //第8位数码管要显示的内容
  75. unsigned char ucDigShow7;  //第7位数码管要显示的内容
  76. unsigned char ucDigShow6;  //第6位数码管要显示的内容
  77. unsigned char ucDigShow5;  //第5位数码管要显示的内容
  78. unsigned char ucDigShow4;  //第4位数码管要显示的内容
  79. unsigned char ucDigShow3;  //第3位数码管要显示的内容
  80. unsigned char ucDigShow2;  //第2位数码管要显示的内容
  81. unsigned char ucDigShow1;  //第1位数码管要显示的内容

  82. unsigned char ucDigDot8;  //数码管8的小数点是否显示的标志
  83. unsigned char ucDigDot7;  //数码管7的小数点是否显示的标志
  84. unsigned char ucDigDot6;  //数码管6的小数点是否显示的标志
  85. unsigned char ucDigDot5;  //数码管5的小数点是否显示的标志
  86. unsigned char ucDigDot4;  //数码管4的小数点是否显示的标志
  87. unsigned char ucDigDot3;  //数码管3的小数点是否显示的标志
  88. unsigned char ucDigDot2;  //数码管2的小数点是否显示的标志
  89. unsigned char ucDigDot1;  //数码管1的小数点是否显示的标志
  90. unsigned char ucDigShowTemp=0; //临时中间变量
  91. unsigned char ucDisplayDriveStep=1;  //动态扫描数码管的步骤变量

  92. unsigned char ucWd1Update=1; //窗口1更新显示标志
  93. unsigned char ucWd2Update=0; //窗口2更新显示标志
  94. unsigned char ucWd3Update=0; //窗口3更新显示标志
  95. unsigned char ucWd4Update=0; //窗口4更新显示标志
  96. unsigned char ucWd=1;  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
  97. unsigned int  uiSetData1=0;  //本程序中需要被设置的参数1
  98. unsigned int  uiSetData2=0;  //本程序中需要被设置的参数2
  99. unsigned int  uiSetData3=0;  //本程序中需要被设置的参数3
  100. unsigned int  uiSetData4=0;  //本程序中需要被设置的参数4

  101. unsigned char ucTemp1=0;  //中间过渡变量
  102. unsigned char ucTemp2=0;  //中间过渡变量
  103. unsigned char ucTemp3=0;  //中间过渡变量
  104. unsigned char ucTemp4=0;  //中间过渡变量

  105. //根据原理图得出的共阴数码管字模表
  106. code unsigned char dig_table[]=
  107. {
  108. 0x3f,  //0       序号0
  109. 0x06,  //1       序号1
  110. 0x5b,  //2       序号2
  111. 0x4f,  //3       序号3
  112. 0x66,  //4       序号4
  113. 0x6d,  //5       序号5
  114. 0x7d,  //6       序号6
  115. 0x07,  //7       序号7
  116. 0x7f,  //8       序号8
  117. 0x6f,  //9       序号9
  118. 0x00,  //无      序号10
  119. 0x40,  //-       序号11
  120. 0x73,  //P       序号12
  121. };
  122. void main()
  123.   {
  124.    initial_myself();  
  125.    delay_long(100);   
  126.    initial_peripheral();
  127.    while(1)  
  128.    {
  129.       key_service(); //按键服务的应用程序
  130.       usart_service();  //串口接收服务程序
  131.       communication_service(); //一发一收的通讯服务程序
  132.       display_service(); //显示的窗口菜单服务程序
  133.       status_service();  //状态显示的应用程序
  134.    }
  135. }


  136. void communication_service(void) //一发一收的通讯服务程序
  137. {
  138.    unsigned int i;

  139.    if(ucStatus==1)  //处于正在通讯的过程中
  140.    {
  141.        switch(ucSendStep)
  142.            {
  143.                case 0: //通讯过程0  发送一串数据
  144.                 switch(ucSendTotal)  //根据当前已经发送到第几条数据来决定发送哪些参数
  145.                                 {
  146.                                    case 0:   //发送参数1
  147.                         ucSendregBuf[0]=0xeb;    //把准备发送的数据放入发送缓冲区
  148.                         ucSendregBuf[1]=0x00;
  149.                         ucSendregBuf[2]=0x55;
  150.                         ucSendregBuf[3]=0x01;    //代表发送参数1
  151.                         ucSendregBuf[4]=0x00;
  152.                         ucSendregBuf[5]=0x02;    //代表发送2个字节的有效数据

  153.                                                 ucSendregBuf[6]=uiSetData1>>8;  //把int类型的参数分解成两个字节的数据
  154.                                                 ucSendregBuf[7]=uiSetData1;
  155.                                         break;

  156.                                    case 1:  //发送参数2
  157.                         ucSendregBuf[0]=0xeb;    //把准备发送的数据放入发送缓冲区
  158.                         ucSendregBuf[1]=0x00;
  159.                         ucSendregBuf[2]=0x55;
  160.                         ucSendregBuf[3]=0x02;    //代表发送参数2
  161.                         ucSendregBuf[4]=0x00;
  162.                         ucSendregBuf[5]=0x02;    //代表发送2个字节的有效数据

  163.                                                 ucSendregBuf[6]=uiSetData2>>8;  //把int类型的参数分解成两个字节的数据
  164.                                                 ucSendregBuf[7]=uiSetData2;
  165.                                         break;

  166.                                    case 2:  //发送参数3
  167.                         ucSendregBuf[0]=0xeb;    //把准备发送的数据放入发送缓冲区
  168.                         ucSendregBuf[1]=0x00;
  169.                         ucSendregBuf[2]=0x55;
  170.                         ucSendregBuf[3]=0x03;    //代表发送参数3
  171.                         ucSendregBuf[4]=0x00;
  172.                         ucSendregBuf[5]=0x02;    //代表发送2个字节的有效数据

  173.                                                 ucSendregBuf[6]=uiSetData3>>8;  //把int类型的参数分解成两个字节的数据
  174.                                                 ucSendregBuf[7]=uiSetData3;
  175.                                         break;

  176.                                    case 3:  //发送参数4
  177.                         ucSendregBuf[0]=0xeb;    //把准备发送的数据放入发送缓冲区
  178.                         ucSendregBuf[1]=0x00;
  179.                         ucSendregBuf[2]=0x55;
  180.                         ucSendregBuf[3]=0x04;    //代表发送参数4
  181.                         ucSendregBuf[4]=0x00;
  182.                         ucSendregBuf[5]=0x02;    //代表发送2个字节的有效数据

  183.                                                 ucSendregBuf[6]=uiSetData4>>8;  //把int类型的参数分解成两个字节的数据
  184.                                                 ucSendregBuf[7]=uiSetData4;
  185.                                         break;
  186.                                 }
  187.                                 

  188.                 ucSendregBuf[8]=0x00;  
  189.                 for(i=0;i<8;i++)  //最后一个字节是校验和,是前面所有字节累加,溢出部分不用我们管,系统会有规律的自动处理
  190.                 {
  191.                   ucSendregBuf[8]=ucSendregBuf[8]+ucSendregBuf[i];
  192.                 }

  193.                 for(i=0;i<9;i++)  
  194.                 {
  195.                     eusart_send(ucSendregBuf[i]);  //把一串完整的数据发送给下位机
  196.                 }

  197.                 ucSendTimeOutLock=1; //原子锁加锁
  198.                 uiSendTimeOutCnt=0;  //超时计时器计时清零
  199.                 ucSendTimeOutLock=0; //原子锁解锁

  200.                                 ucReceiveStatus=0;  //返回的数据状态清零
  201.                                 ucSendStep=1;  //切换到下一个步骤,等待返回的数据
  202.                         break;
  203.                case 1: //通讯过程1  判断返回的指令
  204.                         if(ucReceiveStatus==1)  //校验正确
  205.                                 {

  206.                                      ucErrorCnt=0; //累计校验错误总数清零

  207.                                    ucSendTotal++;  //累加当前发送了多少串数据

  208.                                    if(ucSendTotal>=4) //已经发送完全部4串数据,结束
  209.                                    {
  210.                       ucStatus=0;  //切换到结束时的待机状态
  211.                                    }
  212.                                    else  //还没发送完4串数据,则继续发送下一串新数据
  213.                                    {
  214.                                              ucSendStep=0;  //返回上一个步骤,继续发送新数据
  215.                                    }

  216.                                 }
  217.                         else if(ucReceiveStatus==2||uiSendTimeOutCnt>const_send_time_out)  //校验出错或者超时出错
  218.                                 {

  219.                                  ucErrorCnt++; //累计错误总数
  220.                    if(ucErrorCnt>=3)  //累加重发次数3次以上,则报错
  221.                                    {
  222.                       ucStatus=2;  //切换到出错报警状态
  223.                                    }
  224.                                    else  //重发还没超过3次,继续返回重发
  225.                                    {
  226.                                              ucSendStep=0;  //返回上一个步骤,重发一次数据
  227.                                    }
  228.                                 }
  229.                         break;           
  230.            
  231.            }
  232.    
  233.    }

  234. }

  235. void status_service(void)  //状态显示的应用程序
  236. {
  237.    if(ucStatus!=0) //处于非待机的状态,Led闪烁
  238.    {
  239.       if(uiLedCnt<const_led_0_5s)  //大概0.5秒
  240.           {
  241.              led_dr=1;  //前半秒亮

  242.                  if(ucStatus==2)  //处于发送数据出错的状态,则蜂鸣器间歇鸣叫报警
  243.                  {
  244.              ucVoiceLock=1;  //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
  245.              uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  246.              ucVoiceLock=0;  //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt
  247.                  }
  248.           }
  249.           else if(uiLedCnt<const_led_1s)  //大概1秒
  250.           {
  251.              led_dr=0; //前半秒灭
  252.           }
  253.           else
  254.           {
  255.                      ucLedLock=1; //原子锁加锁
  256.              uiLedCnt=0; //延时计时器清零,让Led灯处于闪烁的反复循环中
  257.                          ucLedLock=0; //原子锁解锁
  258.           }
  259.    
  260.    }
  261.    else  //处于待机状态,Led一直亮
  262.    {
  263.       led_dr=1;
  264.    
  265.    }



  266. }



  267. void usart_service(void)  //串口接收服务程序,在main函数里
  268. {

  269.      unsigned int i;  
  270.         
  271.      if(uiSendCnt>=const_receive_time&&ucSendLock==1) //说明超过了一定的时间内,再也没有新数据从串口来
  272.      {

  273.             ucSendLock=0;    //处理一次就锁起来,不用每次都进来,除非有新接收的数据
  274.             //下面的代码进入数据协议解析和数据处理的阶段

  275.             uiRcMoveIndex=0; //由于是判断数据头,所以下标移动变量从数组的0开始向最尾端移动

  276.             while(uiRcregTotal>=5&&uiRcMoveIndex<=(uiRcregTotal-5))
  277.             {

  278.                if(ucRcregBuf[uiRcMoveIndex+0]==0xeb&&ucRcregBuf[uiRcMoveIndex+1]==0x00&&ucRcregBuf[uiRcMoveIndex+2]==0x55)  //数据头eb 00 55的判断
  279.                {

  280.                    ucRcType=ucRcregBuf[uiRcMoveIndex+3];   //数据类型  一个字节
  281.                    uiRcSize=ucRcregBuf[uiRcMoveIndex+4];   //数据长度  两个字节
  282.                    uiRcSize=uiRcSize<<8;
  283.                    uiRcSize=uiRcSize+ucRcregBuf[uiRcMoveIndex+5];
  284.                                                                  
  285.                    ucRcCy=ucRcregBuf[uiRcMoveIndex+6+uiRcSize];   //记录最后一个字节的校验
  286.                    ucRcregBuf[uiRcMoveIndex+6+uiRcSize]=0;  //清零最后一个字节的累加和变量

  287.                    for(i=0;i<(3+1+2+uiRcSize);i++) //计算校验累加和
  288.                    {
  289.                       ucRcregBuf[uiRcMoveIndex+6+uiRcSize]=ucRcregBuf[uiRcMoveIndex+6+uiRcSize]+ucRcregBuf[uiRcMoveIndex+i];
  290.                    }        


  291.                     if(ucRcCy==ucRcregBuf[uiRcMoveIndex+6+uiRcSize])  //如果一串数据校验正确,则进入以下数据指令的判断
  292.                     {                                                  
  293.                        switch(ucRcType)   //根据不同的数据类型来做不同的数据处理
  294.                        {
  295.                              case 0xf5:   //返回的是正确的校验指令

  296.                                   ucReceiveStatus=1;//代表校验正确
  297.                                   break;        
  298.                                                                         
  299.                              case 0xfa:   //返回的是错误的校验指令

  300.                                   ucReceiveStatus=2;//代表校验错误
  301.                                   break;                                          
  302.                         }

  303.                      }   
  304.                      break;   //退出循环
  305.                }
  306.                uiRcMoveIndex++; //因为是判断数据头,游标向着数组最尾端的方向移动
  307.            }
  308.                                          
  309.            uiRcregTotal=0;  //清空缓冲的下标,方便下次重新从0下标开始接受新数据
  310.   
  311.      }
  312.                         
  313. }


  314. void eusart_send(unsigned char ucSendData) //发送一个字节,内部自带每个字节之间的delay延时
  315. {

  316.   ES = 0; //关串口中断
  317.   TI = 0; //清零串口发送完成中断请求标志
  318.   SBUF =ucSendData; //发送一个字节

  319.   delay_short(400);  //每个字节之间的延时,这里非常关键,也是最容易出错的地方。延时的大小请根据实际项目来调整

  320.   TI = 0; //清零串口发送完成中断请求标志
  321.   ES = 1; //允许串口中断

  322. }


  323. void display_service(void) //显示的窗口菜单服务程序
  324. {

  325.    switch(ucWd)  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
  326.    {
  327.        case 1:   //显示P--1窗口的数据
  328.             if(ucWd1Update==1)  //窗口1要全部更新显示
  329.    {
  330.                ucWd1Update=0;  //及时清零标志,避免一直进来扫描
  331.                ucDigShow8=12;  //第8位数码管显示P
  332.                ucDigShow7=11;  //第7位数码管显示-
  333.                ucDigShow6=1;   //第6位数码管显示1
  334.                ucDigShow5=10;  //第5位数码管显示无

  335.               //先分解数据
  336.                        ucTemp4=uiSetData1/1000;     
  337.                        ucTemp3=uiSetData1%1000/100;
  338.                        ucTemp2=uiSetData1%100/10;
  339.                        ucTemp1=uiSetData1%10;
  340.   
  341.                           //再过渡需要显示的数据到缓冲变量里,让过渡的时间越短越好

  342.                if(uiSetData1<1000)   
  343.                            {
  344.                               ucDigShow4=10;  //如果小于1000,千位显示无
  345.                            }
  346.                else
  347.                            {
  348.                   ucDigShow4=ucTemp4;  //第4位数码管要显示的内容
  349.                            }
  350.                if(uiSetData1<100)
  351.                            {
  352.                   ucDigShow3=10;  //如果小于100,百位显示无
  353.                            }
  354.                            else
  355.                            {
  356.                   ucDigShow3=ucTemp3;  //第3位数码管要显示的内容
  357.                            }
  358.                if(uiSetData1<10)
  359.                            {
  360.                   ucDigShow2=10;  //如果小于10,十位显示无
  361.                            }
  362.                            else
  363.                            {
  364.                   ucDigShow2=ucTemp2;  //第2位数码管要显示的内容
  365.                }
  366.                ucDigShow1=ucTemp1;  //第1位数码管要显示的内容
  367.             }
  368.             break;
  369.         case 2:  //显示P--2窗口的数据
  370.             if(ucWd2Update==1)  //窗口2要全部更新显示
  371.    {
  372.                ucWd2Update=0;  //及时清零标志,避免一直进来扫描
  373.                ucDigShow8=12;  //第8位数码管显示P
  374.                ucDigShow7=11;  //第7位数码管显示-
  375.                ucDigShow6=2;  //第6位数码管显示2
  376.                ucDigShow5=10;   //第5位数码管显示无
  377.                        ucTemp4=uiSetData2/1000;     //分解数据
  378.                        ucTemp3=uiSetData2%1000/100;
  379.                        ucTemp2=uiSetData2%100/10;
  380.                        ucTemp1=uiSetData2%10;

  381.                if(uiSetData2<1000)   
  382.                            {
  383.                               ucDigShow4=10;  //如果小于1000,千位显示无
  384.                            }
  385.                else
  386.                            {
  387.                   ucDigShow4=ucTemp4;  //第4位数码管要显示的内容
  388.                            }
  389.                if(uiSetData2<100)
  390.                            {
  391.                   ucDigShow3=10;  //如果小于100,百位显示无
  392.                            }
  393.                            else
  394.                            {
  395.                   ucDigShow3=ucTemp3;  //第3位数码管要显示的内容
  396.                            }
  397.                if(uiSetData2<10)
  398.                            {
  399.                   ucDigShow2=10;  //如果小于10,十位显示无
  400.                            }
  401.                            else
  402.                            {
  403.                   ucDigShow2=ucTemp2;  //第2位数码管要显示的内容
  404.                }
  405.                ucDigShow1=ucTemp1;  //第1位数码管要显示的内容
  406.     }
  407.              break;
  408.         case 3:  //显示P--3窗口的数据
  409.             if(ucWd3Update==1)  //窗口3要全部更新显示
  410.    {
  411.                ucWd3Update=0;  //及时清零标志,避免一直进来扫描
  412.                ucDigShow8=12;  //第8位数码管显示P
  413.                ucDigShow7=11;  //第7位数码管显示-
  414.                ucDigShow6=3;  //第6位数码管显示3
  415.                ucDigShow5=10;   //第5位数码管显示无
  416.                        ucTemp4=uiSetData3/1000;     //分解数据
  417.                        ucTemp3=uiSetData3%1000/100;
  418.                        ucTemp2=uiSetData3%100/10;
  419.                        ucTemp1=uiSetData3%10;
  420.                if(uiSetData3<1000)   
  421.                            {
  422.                               ucDigShow4=10;  //如果小于1000,千位显示无
  423.                            }
  424.                else
  425.                            {
  426.                   ucDigShow4=ucTemp4;  //第4位数码管要显示的内容
  427.                            }
  428.                if(uiSetData3<100)
  429.                            {
  430.                   ucDigShow3=10;  //如果小于100,百位显示无
  431.                            }
  432.                            else
  433.                            {
  434.                   ucDigShow3=ucTemp3;  //第3位数码管要显示的内容
  435.                            }
  436.                if(uiSetData3<10)
  437.                            {
  438.                   ucDigShow2=10;  //如果小于10,十位显示无
  439.                            }
  440.                            else
  441.                            {
  442.                   ucDigShow2=ucTemp2;  //第2位数码管要显示的内容
  443.                }
  444.                ucDigShow1=ucTemp1;  //第1位数码管要显示的内容
  445.    }
  446.             break;
  447.         case 4:  //显示P--4窗口的数据
  448.             if(ucWd4Update==1)  //窗口4要全部更新显示
  449.    {
  450.                ucWd4Update=0;  //及时清零标志,避免一直进来扫描
  451.                ucDigShow8=12;  //第8位数码管显示P
  452.                ucDigShow7=11;  //第7位数码管显示-
  453.                ucDigShow6=4;  //第6位数码管显示4
  454.                ucDigShow5=10;   //第5位数码管显示无
  455.                        ucTemp4=uiSetData4/1000;     //分解数据
  456.                        ucTemp3=uiSetData4%1000/100;
  457.                        ucTemp2=uiSetData4%100/10;
  458.                        ucTemp1=uiSetData4%10;

  459.                if(uiSetData4<1000)   
  460.                            {
  461.                               ucDigShow4=10;  //如果小于1000,千位显示无
  462.                            }
  463.                else
  464.                            {
  465.                   ucDigShow4=ucTemp4;  //第4位数码管要显示的内容
  466.                            }
  467.                if(uiSetData4<100)
  468.                            {
  469.                   ucDigShow3=10;  //如果小于100,百位显示无
  470.                            }
  471.                            else
  472.                            {
  473.                   ucDigShow3=ucTemp3;  //第3位数码管要显示的内容
  474.                            }
  475.                if(uiSetData4<10)
  476.                            {
  477.                   ucDigShow2=10;  //如果小于10,十位显示无
  478.                            }
  479.                            else
  480.                            {
  481.                   ucDigShow2=ucTemp2;  //第2位数码管要显示的内容
  482.                }
  483.                ucDigShow1=ucTemp1;  //第1位数码管要显示的内容
  484.     }
  485.              break;
  486.            }
  487.    

  488. }

  489. void key_scan(void)//按键扫描函数 放在定时中断里
  490. {  
  491.   if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  492.   {
  493.      ucKeyLock1=0; //按键自锁标志清零
  494.      uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  495.   }
  496.   else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
  497.   {
  498.      uiKeyTimeCnt1++; //累加定时中断次数
  499.      if(uiKeyTimeCnt1>const_key_time1)
  500.      {
  501.         uiKeyTimeCnt1=0;
  502.         ucKeyLock1=1;  //自锁按键置位,避免一直触发
  503.         ucKeySec=1;    //触发1号键
  504.      }
  505.   }

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

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

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

  552. void key_service(void) //按键服务的应用程序
  553. {

  554.   switch(ucKeySec) //按键服务状态切换
  555.   {
  556.     case 1:// 加按键 对应朱兆祺学习板的S1键
  557.           switch(ucWd)  //在不同的窗口下,设置不同的参数
  558.                   {
  559.                      case 1:
  560.                   uiSetData1++;   
  561.                                   if(uiSetData1>9999) //最大值是9999
  562.                                   {
  563.                                      uiSetData1=9999;
  564.                                   }
  565.                            ucWd1Update=1;  //窗口1更新显示
  566.                               break;
  567.                      case 2:
  568.                   uiSetData2++;
  569.                                   if(uiSetData2>9999) //最大值是9999
  570.                                   {
  571.                                      uiSetData2=9999;
  572.                                   }
  573.                            ucWd2Update=1;  //窗口2更新显示
  574.                               break;
  575.                      case 3:
  576.                   uiSetData3++;
  577.                                   if(uiSetData3>9999) //最大值是9999
  578.                                   {
  579.                                      uiSetData3=9999;
  580.                                   }
  581.                            ucWd3Update=1;  //窗口3更新显示
  582.                               break;
  583.                      case 4:
  584.                   uiSetData4++;
  585.                                   if(uiSetData4>9999) //最大值是9999
  586.                                   {
  587.                                      uiSetData4=9999;
  588.                                   }
  589.                            ucWd4Update=1;  //窗口4更新显示
  590.                               break;
  591.                   }

  592.           ucVoiceLock=1;  //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
  593.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  594.           ucVoiceLock=0;  //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt

  595.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  596.           break;   
  597.    
  598.     case 2:// 减按键 对应朱兆祺学习板的S5键
  599.           switch(ucWd)  //在不同的窗口下,设置不同的参数
  600.                   {
  601.                      case 1:
  602.                   uiSetData1--;   

  603.                                   if(uiSetData1>9999)  
  604.                                   {
  605.                                      uiSetData1=0;  //最小值是0
  606.                                   }
  607.                            ucWd1Update=1;  //窗口1更新显示
  608.                               break;
  609.                      case 2:
  610.                   uiSetData2--;
  611.                                   if(uiSetData2>9999)
  612.                                   {
  613.                                      uiSetData2=0;  //最小值是0
  614.                                   }
  615.                            ucWd2Update=1;  //窗口2更新显示
  616.                               break;
  617.                      case 3:
  618.                   uiSetData3--;
  619.                                   if(uiSetData3>9999)
  620.                                   {
  621.                                      uiSetData3=0;  //最小值是0
  622.                                   }
  623.                            ucWd3Update=1;  //窗口3更新显示
  624.                               break;
  625.                      case 4:
  626.                   uiSetData4--;
  627.                                   if(uiSetData4>9999)
  628.                                   {
  629.                                      uiSetData4=0;  //最小值是0
  630.                                   }
  631.                            ucWd4Update=1;  //窗口4更新显示
  632.                               break;
  633.                   }

  634.           ucVoiceLock=1;  //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
  635.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  636.           ucVoiceLock=0;  //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt

  637.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  638.           break;  

  639.     case 3:// 切换窗口按键 对应朱兆祺学习板的S9键
  640.           ucWd++;  //切换窗口
  641.                   if(ucWd>4)
  642.                   {
  643.                     ucWd=1;
  644.                   }
  645.           switch(ucWd)  //在不同的窗口下,在不同的窗口下,更新显示不同的窗口
  646.                   {
  647.                      case 1:
  648.                            ucWd1Update=1;  //窗口1更新显示
  649.                               break;
  650.                      case 2:
  651.                            ucWd2Update=1;  //窗口2更新显示
  652.                               break;
  653.                      case 3:
  654.                            ucWd3Update=1;  //窗口3更新显示
  655.                               break;
  656.                      case 4:
  657.                            ucWd4Update=1;  //窗口4更新显示
  658.                               break;
  659.                   }
  660.           ucVoiceLock=1;  //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
  661.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  662.           ucVoiceLock=0;  //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt

  663.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  664.           break;         

  665.     case 4:// 启动发送数据和复位按键 对应朱兆祺学习板的S13键
  666.           switch(ucStatus)  //在不同的状态下,进行不同的操作
  667.           {
  668.              case 0:  //处于待机状态,则启动发送数据


  669.                   ucErrorCnt=0; //累计错误总数清零
  670.                   ucSendTotal=0; //已经发送串数据总数清零

  671.                                   ucSendStep=0; //发送数据的过程步骤清零,返回开始的步骤待命
  672.                   ucStatus=1; //启动发送数据,1代表正在通讯过程
  673.                   break;

  674.              case 1:  //处于正在通讯的过程
  675.                   break;

  676.              case 2: //发送数据出错,比如中间超时没有接收到数据
  677.                   ucStatus=0; //切换回待机的状态
  678.                   break;
  679.           }
  680.           ucVoiceLock=1;  //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
  681.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  682.           ucVoiceLock=0;  //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt

  683.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发

  684.           break;   
  685.          
  686.   }               
  687. }

  688. void display_drive(void)  
  689. {
  690.    //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
  691.    switch(ucDisplayDriveStep)
  692.    {
  693.       case 1:  //显示第1位
  694.            ucDigShowTemp=dig_table[ucDigShow1];
  695.                    if(ucDigDot1==1)
  696.                    {
  697.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  698.                    }
  699.            dig_hc595_drive(ucDigShowTemp,0xfe);
  700.                break;
  701.       case 2:  //显示第2位
  702.            ucDigShowTemp=dig_table[ucDigShow2];
  703.                    if(ucDigDot2==1)
  704.                    {
  705.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  706.                    }
  707.            dig_hc595_drive(ucDigShowTemp,0xfd);
  708.                break;
  709.       case 3:  //显示第3位
  710.            ucDigShowTemp=dig_table[ucDigShow3];
  711.                    if(ucDigDot3==1)
  712.                    {
  713.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  714.                    }
  715.            dig_hc595_drive(ucDigShowTemp,0xfb);
  716.                break;
  717.       case 4:  //显示第4位
  718.            ucDigShowTemp=dig_table[ucDigShow4];
  719.                    if(ucDigDot4==1)
  720.                    {
  721.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  722.                    }
  723.            dig_hc595_drive(ucDigShowTemp,0xf7);
  724.                break;
  725.       case 5:  //显示第5位
  726.            ucDigShowTemp=dig_table[ucDigShow5];
  727.                    if(ucDigDot5==1)
  728.                    {
  729.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  730.                    }
  731.            dig_hc595_drive(ucDigShowTemp,0xef);
  732.                break;
  733.       case 6:  //显示第6位
  734.            ucDigShowTemp=dig_table[ucDigShow6];
  735.                    if(ucDigDot6==1)
  736.                    {
  737.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  738.                    }
  739.            dig_hc595_drive(ucDigShowTemp,0xdf);
  740.                break;
  741.       case 7:  //显示第7位
  742.            ucDigShowTemp=dig_table[ucDigShow7];
  743.                    if(ucDigDot7==1)
  744.                    {
  745.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  746.            }
  747.            dig_hc595_drive(ucDigShowTemp,0xbf);
  748.                break;
  749.       case 8:  //显示第8位
  750.            ucDigShowTemp=dig_table[ucDigShow8];
  751.                    if(ucDigDot8==1)
  752.                    {
  753.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  754.                    }
  755.            dig_hc595_drive(ucDigShowTemp,0x7f);
  756.                break;
  757.    }
  758.    ucDisplayDriveStep++;
  759.    if(ucDisplayDriveStep>8)  //扫描完8个数码管后,重新从第一个开始扫描
  760.    {
  761.      ucDisplayDriveStep=1;
  762.    }

  763. }

  764. //数码管的74HC595驱动函数
  765. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
  766. {
  767.    unsigned char i;
  768.    unsigned char ucTempData;
  769.    dig_hc595_sh_dr=0;
  770.    dig_hc595_st_dr=0;
  771.    ucTempData=ucDigStatusTemp16_09;  //先送高8位
  772.    for(i=0;i<8;i++)
  773.    {
  774.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  775.          else dig_hc595_ds_dr=0;
  776.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  777.          delay_short(1);
  778.          dig_hc595_sh_dr=1;
  779.          delay_short(1);
  780.          ucTempData=ucTempData<<1;
  781.    }
  782.    ucTempData=ucDigStatusTemp08_01;  //再先送低8位
  783.    for(i=0;i<8;i++)
  784.    {
  785.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  786.          else dig_hc595_ds_dr=0;
  787.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  788.          delay_short(1);
  789.          dig_hc595_sh_dr=1;
  790.          delay_short(1);
  791.          ucTempData=ucTempData<<1;
  792.    }
  793.    dig_hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  794.    delay_short(1);
  795.    dig_hc595_st_dr=1;
  796.    delay_short(1);
  797.    dig_hc595_sh_dr=0;    //拉低,抗干扰就增强
  798.    dig_hc595_st_dr=0;
  799.    dig_hc595_ds_dr=0;
  800. }

  801. //LED灯的74HC595驱动函数
  802. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
  803. {
  804.    unsigned char i;
  805.    unsigned char ucTempData;
  806.    hc595_sh_dr=0;
  807.    hc595_st_dr=0;
  808.    ucTempData=ucLedStatusTemp16_09;  //先送高8位
  809.    for(i=0;i<8;i++)
  810.    {
  811.          if(ucTempData>=0x80)hc595_ds_dr=1;
  812.          else hc595_ds_dr=0;
  813.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  814.          delay_short(1);
  815.          hc595_sh_dr=1;
  816.          delay_short(1);
  817.          ucTempData=ucTempData<<1;
  818.    }
  819.    ucTempData=ucLedStatusTemp08_01;  //再先送低8位
  820.    for(i=0;i<8;i++)
  821.    {
  822.          if(ucTempData>=0x80)hc595_ds_dr=1;
  823.          else hc595_ds_dr=0;
  824.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  825.          delay_short(1);
  826.          hc595_sh_dr=1;
  827.          delay_short(1);
  828.          ucTempData=ucTempData<<1;
  829.    }
  830.    hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  831.    delay_short(1);
  832.    hc595_st_dr=1;
  833.    delay_short(1);
  834.    hc595_sh_dr=0;    //拉低,抗干扰就增强
  835.    hc595_st_dr=0;
  836.    hc595_ds_dr=0;
  837. }


  838. void usart_receive(void) interrupt 4   //串口接收数据中断        
  839. {        

  840.    if(RI==1)  
  841.    {
  842.         RI = 0;

  843.          ++uiRcregTotal;
  844.         if(uiRcregTotal>const_rc_size)  //超过缓冲区
  845.         {
  846.            uiRcregTotal=const_rc_size;
  847.         }
  848.         ucRcregBuf[uiRcregTotal-1]=SBUF;   //将串口接收到的数据缓存到接收缓冲区里

  849.         if(ucSendCntLock==0)  //原子锁判断
  850.         {
  851.             ucSendCntLock=1; //加锁
  852.             uiSendCnt=0;  //及时喂狗,虽然在定时中断那边此变量会不断累加,但是只要串口的数据还没发送完毕,那么它永远也长不大,因为每个串口接收中断它都被清零。
  853.             ucSendCntLock=0; //解锁
  854.         }
  855.    
  856.    }
  857.    else  //我在其它单片机上都不用else这段代码的,可能在51单片机上多增加" TI = 0;"稳定性会更好吧。
  858.    {
  859.         TI = 0;  //如果不是串口接收中断,那么必然是串口发送中断,及时清除发送中断的标志,否则一直发送中断
  860.    }
  861.                                                          
  862. }  

  863. void T0_time(void) interrupt 1   //定时中断
  864. {
  865.   TF0=0;  //清除中断标志
  866.   TR0=0; //关中断


  867. /* 注释一:
  868.   * 此处多增加一个原子锁,作为中断与主函数共享数据的保护,实际上是借鉴了"红金龙吸味"关于原子锁的建议.
  869.   */  
  870.   if(ucSendCntLock==0)  //原子锁判断
  871.   {
  872.      ucSendCntLock=1; //加锁
  873.      if(uiSendCnt<const_receive_time)   //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完
  874.      {
  875.         uiSendCnt++;    //表面上这个数据不断累加,但是在串口中断里,每接收一个字节它都会被清零,除非这个中间没有串口数据过来
  876.         ucSendLock=1;     //开自锁标志
  877.      }
  878.      ucSendCntLock=0; //解锁
  879.   }

  880.   if(ucVoiceLock==0) //原子锁判断
  881.   {
  882.      if(uiVoiceCnt!=0)
  883.      {

  884.         uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
  885.         beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  886.      
  887.      }
  888.      else
  889.      {

  890.         ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
  891.         beep_dr=1;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  892.         
  893.      }
  894.   }

  895.   if(ucStatus!=0) //处于非待机的状态,Led闪烁
  896.   {
  897.      if(ucLedLock==0)//原子锁判断
  898.          {
  899.         uiLedCnt++; //Led闪烁计时器不断累加
  900.          }
  901.   }

  902.   if(ucStatus==1) //处于正在通讯的状态,
  903.   {
  904.      if(ucSendTimeOutLock==0)  //原子锁判断
  905.      {
  906.          uiSendTimeOutCnt++;   //超时计时器累加
  907.      }
  908.   }



  909.   key_scan(); //按键扫描函数
  910.   display_drive();  //数码管字模的驱动函数

  911.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  912.   TL0=0x0b;
  913.   TR0=1;  //开中断
  914. }

  915. void delay_short(unsigned int uiDelayShort)
  916. {
  917.    unsigned int i;  
  918.    for(i=0;i<uiDelayShort;i++)
  919.    {
  920.      ;   //一个分号相当于执行一条空语句
  921.    }
  922. }

  923. void delay_long(unsigned int uiDelayLong)
  924. {
  925.    unsigned int i;
  926.    unsigned int j;
  927.    for(i=0;i<uiDelayLong;i++)
  928.    {
  929.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  930.           {
  931.              ; //一个分号相当于执行一条空语句
  932.           }
  933.    }
  934. }

  935. void initial_myself(void)  //第一区 初始化单片机
  936. {
  937. /* 注释二:
  938. * 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
  939. * 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
  940. * 坚鸿51学习板的S1就是本程序中用到的一个独立按键。
  941. */
  942.   key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平
  943.   led_dr=1;  //点亮独立LED灯
  944.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
  945.   hc595_drive(0x00,0x00);  //关闭所有经过另外两个74HC595驱动的LED灯
  946.   TMOD=0x01;  //设置定时器0为工作方式1
  947.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  948.   TL0=0x0b;

  949.   //配置串口
  950.   SCON=0x50;
  951.   TMOD=0X21;

  952. /* 注释三:
  953. * 为了保证串口中断接收的数据不丢失,必须设置IP = 0x10,相当于把串口中断设置为最高优先级,
  954. * 这个时候,串口中断可以打断任何其他的中断服务函数实现嵌套,
  955. */
  956.   IP =0x10;  //把串口中断设置为最高优先级,必须的。

  957.   TH1=TL1=-(11059200L/12/32/9600);  //串口波特率为9600。
  958.   TR1=1;
  959. }
  960. void initial_peripheral(void) //第二区 初始化外围
  961. {

  962.    ucDigDot8=0;   //小数点全部不显示
  963.    ucDigDot7=0;  
  964.    ucDigDot6=0;
  965.    ucDigDot5=0;  
  966.    ucDigDot4=0;
  967.    ucDigDot3=0;  
  968.    ucDigDot2=0;
  969.    ucDigDot1=0;

  970.    EA=1;     //开总中断
  971.    ES=1;     //允许串口中断
  972.    ET0=1;    //允许定时中断
  973.    TR0=1;    //启动定时中断
  974. }
复制代码



总结陈词:
前面花了大量篇幅详细地讲解了串口收发数据的程序框架,从下一节开始我讲解单片机掉电后数据保存的内容,欲知详情,请听下回分解-----利用AT24C02进行掉电后的数据保存。
  
(未完待续,下节更精彩,不要走开哦)



作者: jianhong_wu    时间: 2014-5-12 13:33
第四十六节:利用AT24C02进行掉电后的数据保存。

开场白:
一个AT24C02可以存储256个字节,地址范围是(0至255)。利用AT24C02存储数据时,要教会大家六个知识点:
第一个:单片机操作AT24C02的通讯过程也就是IIC的通讯过程, IIC通讯过程是一个要求一气呵成的通讯过程,中间不能被其它中断影响时序出错,因此在整个通讯过程中应该先关闭总中断,完成之后再开中断。
第二个:在写入或者读取完一个字节之后,一定要加上一段延时时间。在11.0592M晶振的系统中,写入数据时经验值用delay_short(2000),读取数据时经验值用delay_short(800)。否则在连续写入或者读取一串数据时容易丢失数据。如果一旦发现丢失数据,应该适当继续把这个时间延长,尤其是在写入数据时。
第三个:如何初始化EEPROM数据的方法。系统第一次上电时,我们从EEPROM读取出来的数据有可能超出了范围,可能是ff。这个时候我们应该给它填入一个初始化的数据,这一步千万别漏了。
第四个:在时序中,发送ACK确认信号时,要记得把数据线eeprom_sda_dr_s设置为输入的状态。对于51单片机来说,只要把eeprom_sda_dr_s=1就可以。而对于PIC或者AVR单片机来说,它们都是带方向寄存器的,就不能直接eeprom_sda_dr_s=1,而要直接修改方向寄存器,把它设置为输入状态。在本驱动程序中,我没有对ACK信号进行出错判断,因为我这么多年一直都是这样用也没出现过什么问题。
第五个: 提醒各位读者在硬件上应该注意的问题,单片机跟AT24C02通讯的2根IO口都要加上一个4.7K左右的上拉电阻。凡是在IIC通讯场合,都要加上拉电阻。AT24C02的WP引脚一定要接地,否则存不进数据。
第六个:旧版的坚鸿51学习板在硬件上有一个bug,AT24C02的第8个引脚VCC悬空了!!!,读者记得把它飞线连接到5V电源处。新版的坚鸿51学习板已经改过来了。

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

(1)硬件平台:
    基于坚鸿51单片机学习板。

(2)实现功能:
    4个被更改后的参数断电后不丢失,数据可以保存,断电再上电后还是上一次最新被修改的数据。
    显示和独立按键部分根据第29节的程序来改编,用坚鸿51单片机学习板中的S1,S5,S9作为独立按键。
      一共有4个窗口。每个窗口显示一个参数。
     第8,7,6,5位数码管显示当前窗口,P-1代表第1个窗口,P-2代表第2个窗口,P-3代表第3个窗口,P-4代表第1个窗口。
     第4,3,2,1位数码管显示当前窗口被设置的参数。范围是从0到9999。S1是加按键,按下此按键会依次增加当前窗口的参数。S5是减按键,按下此按键会依次减少当前窗口的参数。S9是切换窗口按键,按下此按键会依次循环切换不同的窗口。


(3)源代码讲解如下:
  1. #include "REG52.H"

  2. #define const_voice_short  40   //蜂鸣器短叫的持续时间
  3. #define const_key_time1  20    //按键去抖动延时的时间
  4. #define const_key_time2  20    //按键去抖动延时的时间
  5. #define const_key_time3  20    //按键去抖动延时的时间


  6. void initial_myself(void);   
  7. void initial_peripheral(void);
  8. void delay_short(unsigned int uiDelayShort);
  9. void delay_long(unsigned int uiDelaylong);
  10. //驱动数码管的74HC595
  11. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);  
  12. void display_drive(void); //显示数码管字模的驱动函数
  13. void display_service(void); //显示的窗口菜单服务程序
  14. //驱动LED的74HC595
  15. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);


  16. void start24(void);  //开始位
  17. void ack24(void);  //确认位
  18. void stop24(void);  //停止位
  19. unsigned char read24(void);  //读取一个字节的时序
  20. void write24(unsigned char dd); //发送一个字节的时序
  21. unsigned char read_eeprom(unsigned int address);   //从一个地址读取出一个字节数据
  22. void write_eeprom(unsigned int address,unsigned char dd); //往一个地址存入一个字节数据
  23. unsigned int read_eeprom_int(unsigned int address);   //从一个地址读取出一个int类型的数据
  24. void write_eeprom_int(unsigned int address,unsigned int uiWriteData); //往一个地址存入一个int类型的数据

  25. void T0_time(void);  //定时中断函数

  26. void key_service(void); //按键服务的应用程序
  27. void key_scan(void);//按键扫描函数 放在定时中断里

  28. sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
  29. sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键
  30. sbit key_sr3=P0^2; //对应朱兆祺学习板的S9键

  31. sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平
  32. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口

  33. sbit eeprom_scl_dr=P3^7;    //时钟线
  34. sbit eeprom_sda_dr_sr=P3^6; //数据的输出线和输入线

  35. sbit dig_hc595_sh_dr=P2^0;     //数码管的74HC595程序
  36. sbit dig_hc595_st_dr=P2^1;  
  37. sbit dig_hc595_ds_dr=P2^2;  
  38. sbit hc595_sh_dr=P2^3;    //LED灯的74HC595程序
  39. sbit hc595_st_dr=P2^4;  
  40. sbit hc595_ds_dr=P2^5;  



  41. unsigned char ucKeySec=0;   //被触发的按键编号

  42. unsigned int  uiKeyTimeCnt1=0; //按键去抖动延时计数器
  43. unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志
  44. unsigned int  uiKeyTimeCnt2=0; //按键去抖动延时计数器
  45. unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志
  46. unsigned int  uiKeyTimeCnt3=0; //按键去抖动延时计数器
  47. unsigned char ucKeyLock3=0; //按键触发后自锁的变量标志



  48. unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器
  49. unsigned char  ucVoiceLock=0;  //蜂鸣器鸣叫的原子锁

  50. unsigned char ucDigShow8;  //第8位数码管要显示的内容
  51. unsigned char ucDigShow7;  //第7位数码管要显示的内容
  52. unsigned char ucDigShow6;  //第6位数码管要显示的内容
  53. unsigned char ucDigShow5;  //第5位数码管要显示的内容
  54. unsigned char ucDigShow4;  //第4位数码管要显示的内容
  55. unsigned char ucDigShow3;  //第3位数码管要显示的内容
  56. unsigned char ucDigShow2;  //第2位数码管要显示的内容
  57. unsigned char ucDigShow1;  //第1位数码管要显示的内容

  58. unsigned char ucDigDot8;  //数码管8的小数点是否显示的标志
  59. unsigned char ucDigDot7;  //数码管7的小数点是否显示的标志
  60. unsigned char ucDigDot6;  //数码管6的小数点是否显示的标志
  61. unsigned char ucDigDot5;  //数码管5的小数点是否显示的标志
  62. unsigned char ucDigDot4;  //数码管4的小数点是否显示的标志
  63. unsigned char ucDigDot3;  //数码管3的小数点是否显示的标志
  64. unsigned char ucDigDot2;  //数码管2的小数点是否显示的标志
  65. unsigned char ucDigDot1;  //数码管1的小数点是否显示的标志
  66. unsigned char ucDigShowTemp=0; //临时中间变量
  67. unsigned char ucDisplayDriveStep=1;  //动态扫描数码管的步骤变量

  68. unsigned char ucWd1Update=1; //窗口1更新显示标志
  69. unsigned char ucWd2Update=0; //窗口2更新显示标志
  70. unsigned char ucWd3Update=0; //窗口3更新显示标志
  71. unsigned char ucWd4Update=0; //窗口4更新显示标志
  72. unsigned char ucWd=1;  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
  73. unsigned int  uiSetData1=0;  //本程序中需要被设置的参数1
  74. unsigned int  uiSetData2=0;  //本程序中需要被设置的参数2
  75. unsigned int  uiSetData3=0;  //本程序中需要被设置的参数3
  76. unsigned int  uiSetData4=0;  //本程序中需要被设置的参数4

  77. unsigned char ucTemp1=0;  //中间过渡变量
  78. unsigned char ucTemp2=0;  //中间过渡变量
  79. unsigned char ucTemp3=0;  //中间过渡变量
  80. unsigned char ucTemp4=0;  //中间过渡变量


  81. //根据原理图得出的共阴数码管字模表
  82. code unsigned char dig_table[]=
  83. {
  84. 0x3f,  //0       序号0
  85. 0x06,  //1       序号1
  86. 0x5b,  //2       序号2
  87. 0x4f,  //3       序号3
  88. 0x66,  //4       序号4
  89. 0x6d,  //5       序号5
  90. 0x7d,  //6       序号6
  91. 0x07,  //7       序号7
  92. 0x7f,  //8       序号8
  93. 0x6f,  //9       序号9
  94. 0x00,  //无      序号10
  95. 0x40,  //-       序号11
  96. 0x73,  //P       序号12
  97. };
  98. void main()
  99.   {
  100.    initial_myself();  
  101.    delay_long(100);   
  102.    initial_peripheral();
  103.    while(1)  
  104.    {
  105.       key_service(); //按键服务的应用程序
  106.       display_service(); //显示的窗口菜单服务程序
  107.    }
  108. }



  109. //AT24C02驱动程序
  110. void start24(void)  //开始位
  111. {

  112.     eeprom_sda_dr_sr=1;
  113.     eeprom_scl_dr=1;
  114.         delay_short(15);
  115.     eeprom_sda_dr_sr=0;
  116.         delay_short(15);
  117.     eeprom_scl_dr=0;   
  118. }


  119. void ack24(void)  //确认位时序
  120. {
  121.     eeprom_sda_dr_sr=1; //51单片机在读取数据之前要先置一,表示数据输入

  122.     eeprom_scl_dr=1;
  123.         delay_short(15);
  124.     eeprom_scl_dr=0;
  125.         delay_short(15);

  126. //在本驱动程序中,我没有对ACK信号进行出错判断,因为我这么多年一直都是这样用也没出现过什么问题。
  127. //有兴趣的朋友可以自己增加出错判断,不一定非要按我的方式去做。
  128. }

  129. void stop24(void)  //停止位
  130. {
  131.     eeprom_sda_dr_sr=0;
  132.     eeprom_scl_dr=1;
  133.         delay_short(15);
  134.     eeprom_sda_dr_sr=1;
  135. }



  136. unsigned char read24(void)  //读取一个字节的时序
  137. {
  138.         unsigned char outdata,tempdata;


  139.         outdata=0;
  140.                 eeprom_sda_dr_sr=1; //51单片机的IO口在读取数据之前要先置一,表示数据输入
  141.         delay_short(2);
  142.         for(tempdata=0;tempdata<8;tempdata++)
  143.         {
  144.             eeprom_scl_dr=0;
  145.             delay_short(2);
  146.             eeprom_scl_dr=1;
  147.             delay_short(2);
  148.             outdata<<=1;
  149.             if(eeprom_sda_dr_sr==1)outdata++;      
  150.             eeprom_sda_dr_sr=1; //51单片机的IO口在读取数据之前要先置一,表示数据输入
  151.             delay_short(2);
  152.         }
  153.     return(outdata);
  154.      
  155. }

  156. void write24(unsigned char dd) //发送一个字节的时序
  157. {

  158.         unsigned char tempdata;
  159.         for(tempdata=0;tempdata<8;tempdata++)
  160.         {
  161.                 if(dd>=0x80)eeprom_sda_dr_sr=1;
  162.                 else eeprom_sda_dr_sr=0;
  163.                 dd<<=1;
  164.                 delay_short(2);
  165.                 eeprom_scl_dr=1;
  166.                 delay_short(4);
  167.                 eeprom_scl_dr=0;
  168.         }


  169. }



  170. unsigned char read_eeprom(unsigned int address)   //从一个地址读取出一个字节数据
  171. {

  172.    unsigned char dd,cAddress;  

  173.    cAddress=address; //把低字节地址传递给一个字节变量。

  174. /* 注释一:
  175.   * IIC通讯过程是一个要求一气呵成的通讯过程,中间不能被其它中断影响时序出错,因此
  176.   * 在整个通讯过程中应该先关闭总中断,完成之后再开中断。但是,这样就会引起另外一个新
  177.   * 问题,如果关闭总中断的时间太长,会导致动态数码管不能及时均匀的扫描,在操作EEPROM时,
  178.   * 数码管就会出现闪烁的现象,解决这个问题最好的办法就是在做项目中尽量不要用动态扫描数码管
  179.   * 的方案,应该用静态显示的方案。那么程序上还有没有改善的方法?有的,下一节我会讲这个问题
  180.   * 的改善方法。
  181.   */
  182.    EA=0; //禁止中断

  183.    start24(); //IIC通讯开始

  184.    write24(0xA0); //此字节包含读写指令和芯片地址两方面的内容。
  185.                   //指令为写指令。地址为"000"的信息,此信息由A0,A1,A2的引脚决定

  186.    ack24(); //发送应答信号
  187.    write24(cAddress); //发送读取的存储地址(范围是0至255)
  188.    ack24(); //发送应答信号

  189.    start24(); //开始
  190.    write24(0xA1); //此字节包含读写指令和芯片地址两方面的内容。
  191.                   //指令为读指令。地址为"000"的信息,此信息由A0,A1,A2的引脚决定
  192.    ack24(); //发送应答信号
  193.    dd=read24(); //读取一个字节
  194.    ack24(); //发送应答信号
  195.    stop24();  //停止

  196. /* 注释二:
  197.   * 在写入或者读取完一个字节之后,一定要加上一段延时时间。在11.0592M晶振的系统中,
  198.   * 写入数据时经验值用delay_short(2000),读取数据时经验值用delay_short(800)。
  199.   * 否则在连续写入或者读取一串数据时容易丢失数据。如果一旦发现丢失数据,
  200.   * 应该适当继续把这个时间延长,尤其是在写入数据时。
  201.   */
  202.    delay_short(800);  //此处最关键,此处的延时时间一定要,而且要足够长,此处也是导致动态数码管闪烁的根本原因
  203.    EA=1; //允许中断

  204.    return(dd);
  205. }

  206. void write_eeprom(unsigned int address,unsigned char dd) //往一个地址存入一个字节数据
  207. {
  208.    unsigned char cAddress;   

  209.    cAddress=address; //把低字节地址传递给一个字节变量。


  210.    EA=0; //禁止中断

  211.    start24(); //IIC通讯开始

  212.    write24(0xA0); //此字节包含读写指令和芯片地址两方面的内容。
  213.                   //指令为写指令。地址为"000"的信息,此信息由A0,A1,A2的引脚决定
  214.    ack24(); //发送应答信号
  215.    write24(cAddress);   //发送写入的存储地址(范围是0至255)
  216.    ack24(); //发送应答信号
  217.    write24(dd);  //写入存储的数据
  218.    ack24(); //发送应答信号
  219.    stop24();  //停止
  220.    delay_short(2000);  //此处最关键,此处的延时时间一定要,而且要足够长,此处也是导致动态数码管闪烁的根本原因
  221.    EA=1; //允许中断

  222. }


  223. unsigned int read_eeprom_int(unsigned int address)   //从一个地址读取出一个int类型的数据
  224. {
  225.    unsigned char ucReadDataH;
  226.    unsigned char ucReadDataL;
  227.    unsigned int  uiReadDate;

  228.    ucReadDataH=read_eeprom(address);    //读取高字节
  229.    ucReadDataL=read_eeprom(address+1);  //读取低字节

  230.    uiReadDate=ucReadDataH;  //把两个字节合并成一个int类型数据
  231.    uiReadDate=uiReadDate<<8;
  232.    uiReadDate=uiReadDate+ucReadDataL;

  233.    return uiReadDate;

  234. }

  235. void write_eeprom_int(unsigned int address,unsigned int uiWriteData) //往一个地址存入一个int类型的数据
  236. {
  237.    unsigned char ucWriteDataH;
  238.    unsigned char ucWriteDataL;

  239.    ucWriteDataH=uiWriteData>>8;
  240.    ucWriteDataL=uiWriteData;

  241.    write_eeprom(address,ucWriteDataH); //存入高字节
  242.    write_eeprom(address+1,ucWriteDataL); //存入低字节

  243. }


  244. void display_service(void) //显示的窗口菜单服务程序
  245. {

  246.    switch(ucWd)  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
  247.    {
  248.        case 1:   //显示P--1窗口的数据
  249.             if(ucWd1Update==1)  //窗口1要全部更新显示
  250.    {
  251.                ucWd1Update=0;  //及时清零标志,避免一直进来扫描
  252.                ucDigShow8=12;  //第8位数码管显示P
  253.                ucDigShow7=11;  //第7位数码管显示-
  254.                ucDigShow6=1;   //第6位数码管显示1
  255.                ucDigShow5=10;  //第5位数码管显示无

  256.               //先分解数据
  257.                        ucTemp4=uiSetData1/1000;     
  258.                        ucTemp3=uiSetData1%1000/100;
  259.                        ucTemp2=uiSetData1%100/10;
  260.                        ucTemp1=uiSetData1%10;
  261.   
  262.                           //再过渡需要显示的数据到缓冲变量里,让过渡的时间越短越好

  263.                if(uiSetData1<1000)   
  264.                            {
  265.                               ucDigShow4=10;  //如果小于1000,千位显示无
  266.                            }
  267.                else
  268.                            {
  269.                   ucDigShow4=ucTemp4;  //第4位数码管要显示的内容
  270.                            }
  271.                if(uiSetData1<100)
  272.                            {
  273.                   ucDigShow3=10;  //如果小于100,百位显示无
  274.                            }
  275.                            else
  276.                            {
  277.                   ucDigShow3=ucTemp3;  //第3位数码管要显示的内容
  278.                            }
  279.                if(uiSetData1<10)
  280.                            {
  281.                   ucDigShow2=10;  //如果小于10,十位显示无
  282.                            }
  283.                            else
  284.                            {
  285.                   ucDigShow2=ucTemp2;  //第2位数码管要显示的内容
  286.                }
  287.                ucDigShow1=ucTemp1;  //第1位数码管要显示的内容
  288.             }
  289.             break;
  290.         case 2:  //显示P--2窗口的数据
  291.             if(ucWd2Update==1)  //窗口2要全部更新显示
  292.    {
  293.                ucWd2Update=0;  //及时清零标志,避免一直进来扫描
  294.                ucDigShow8=12;  //第8位数码管显示P
  295.                ucDigShow7=11;  //第7位数码管显示-
  296.                ucDigShow6=2;  //第6位数码管显示2
  297.                ucDigShow5=10;   //第5位数码管显示无
  298.                        ucTemp4=uiSetData2/1000;     //分解数据
  299.                        ucTemp3=uiSetData2%1000/100;
  300.                        ucTemp2=uiSetData2%100/10;
  301.                        ucTemp1=uiSetData2%10;

  302.                if(uiSetData2<1000)   
  303.                            {
  304.                               ucDigShow4=10;  //如果小于1000,千位显示无
  305.                            }
  306.                else
  307.                            {
  308.                   ucDigShow4=ucTemp4;  //第4位数码管要显示的内容
  309.                            }
  310.                if(uiSetData2<100)
  311.                            {
  312.                   ucDigShow3=10;  //如果小于100,百位显示无
  313.                            }
  314.                            else
  315.                            {
  316.                   ucDigShow3=ucTemp3;  //第3位数码管要显示的内容
  317.                            }
  318.                if(uiSetData2<10)
  319.                            {
  320.                   ucDigShow2=10;  //如果小于10,十位显示无
  321.                            }
  322.                            else
  323.                            {
  324.                   ucDigShow2=ucTemp2;  //第2位数码管要显示的内容
  325.                }
  326.                ucDigShow1=ucTemp1;  //第1位数码管要显示的内容
  327.     }
  328.              break;
  329.         case 3:  //显示P--3窗口的数据
  330.             if(ucWd3Update==1)  //窗口3要全部更新显示
  331.    {
  332.                ucWd3Update=0;  //及时清零标志,避免一直进来扫描
  333.                ucDigShow8=12;  //第8位数码管显示P
  334.                ucDigShow7=11;  //第7位数码管显示-
  335.                ucDigShow6=3;  //第6位数码管显示3
  336.                ucDigShow5=10;   //第5位数码管显示无
  337.                        ucTemp4=uiSetData3/1000;     //分解数据
  338.                        ucTemp3=uiSetData3%1000/100;
  339.                        ucTemp2=uiSetData3%100/10;
  340.                        ucTemp1=uiSetData3%10;
  341.                if(uiSetData3<1000)   
  342.                            {
  343.                               ucDigShow4=10;  //如果小于1000,千位显示无
  344.                            }
  345.                else
  346.                            {
  347.                   ucDigShow4=ucTemp4;  //第4位数码管要显示的内容
  348.                            }
  349.                if(uiSetData3<100)
  350.                            {
  351.                   ucDigShow3=10;  //如果小于100,百位显示无
  352.                            }
  353.                            else
  354.                            {
  355.                   ucDigShow3=ucTemp3;  //第3位数码管要显示的内容
  356.                            }
  357.                if(uiSetData3<10)
  358.                            {
  359.                   ucDigShow2=10;  //如果小于10,十位显示无
  360.                            }
  361.                            else
  362.                            {
  363.                   ucDigShow2=ucTemp2;  //第2位数码管要显示的内容
  364.                }
  365.                ucDigShow1=ucTemp1;  //第1位数码管要显示的内容
  366.    }
  367.             break;
  368.         case 4:  //显示P--4窗口的数据
  369.             if(ucWd4Update==1)  //窗口4要全部更新显示
  370.    {
  371.                ucWd4Update=0;  //及时清零标志,避免一直进来扫描
  372.                ucDigShow8=12;  //第8位数码管显示P
  373.                ucDigShow7=11;  //第7位数码管显示-
  374.                ucDigShow6=4;  //第6位数码管显示4
  375.                ucDigShow5=10;   //第5位数码管显示无
  376.                        ucTemp4=uiSetData4/1000;     //分解数据
  377.                        ucTemp3=uiSetData4%1000/100;
  378.                        ucTemp2=uiSetData4%100/10;
  379.                        ucTemp1=uiSetData4%10;

  380.                if(uiSetData4<1000)   
  381.                            {
  382.                               ucDigShow4=10;  //如果小于1000,千位显示无
  383.                            }
  384.                else
  385.                            {
  386.                   ucDigShow4=ucTemp4;  //第4位数码管要显示的内容
  387.                            }
  388.                if(uiSetData4<100)
  389.                            {
  390.                   ucDigShow3=10;  //如果小于100,百位显示无
  391.                            }
  392.                            else
  393.                            {
  394.                   ucDigShow3=ucTemp3;  //第3位数码管要显示的内容
  395.                            }
  396.                if(uiSetData4<10)
  397.                            {
  398.                   ucDigShow2=10;  //如果小于10,十位显示无
  399.                            }
  400.                            else
  401.                            {
  402.                   ucDigShow2=ucTemp2;  //第2位数码管要显示的内容
  403.                }
  404.                ucDigShow1=ucTemp1;  //第1位数码管要显示的内容
  405.     }
  406.              break;
  407.            }
  408.    

  409. }

  410. void key_scan(void)//按键扫描函数 放在定时中断里
  411. {  
  412.   if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  413.   {
  414.      ucKeyLock1=0; //按键自锁标志清零
  415.      uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  416.   }
  417.   else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
  418.   {
  419.      uiKeyTimeCnt1++; //累加定时中断次数
  420.      if(uiKeyTimeCnt1>const_key_time1)
  421.      {
  422.         uiKeyTimeCnt1=0;
  423.         ucKeyLock1=1;  //自锁按键置位,避免一直触发
  424.         ucKeySec=1;    //触发1号键
  425.      }
  426.   }

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

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


  457. }

  458. void key_service(void) //按键服务的应用程序
  459. {

  460.   switch(ucKeySec) //按键服务状态切换
  461.   {
  462.     case 1:// 加按键 对应朱兆祺学习板的S1键
  463.           switch(ucWd)  //在不同的窗口下,设置不同的参数
  464.                   {
  465.                      case 1:
  466.                   uiSetData1++;   
  467.                                   if(uiSetData1>9999) //最大值是9999
  468.                                   {
  469.                                      uiSetData1=9999;
  470.                                   }

  471.                            write_eeprom_int(0,uiSetData1); //存入EEPROM 由于内部有延时函数,所以此处会引起数码管闪烁

  472.                            ucWd1Update=1;  //窗口1更新显示
  473.                               break;
  474.                      case 2:
  475.                   uiSetData2++;
  476.                                   if(uiSetData2>9999) //最大值是9999
  477.                                   {
  478.                                      uiSetData2=9999;
  479.                                   }


  480.                            write_eeprom_int(2,uiSetData2); //存入EEPROM,由于内部有延时函数,所以此处会引起数码管闪烁

  481.                            ucWd2Update=1;  //窗口2更新显示
  482.                               break;
  483.                      case 3:
  484.                   uiSetData3++;
  485.                                   if(uiSetData3>9999) //最大值是9999
  486.                                   {
  487.                                      uiSetData3=9999;
  488.                                   }
  489.                            write_eeprom_int(4,uiSetData3); //存入EEPROM,由于内部有延时函数,所以此处会引起数码管闪烁
  490.                            ucWd3Update=1;  //窗口3更新显示
  491.                               break;
  492.                      case 4:
  493.                   uiSetData4++;
  494.                                   if(uiSetData4>9999) //最大值是9999
  495.                                   {
  496.                                      uiSetData4=9999;
  497.                                   }
  498.                            write_eeprom_int(6,uiSetData4); //存入EEPROM,由于内部有延时函数,所以此处会引起数码管闪烁
  499.                            ucWd4Update=1;  //窗口4更新显示
  500.                               break;
  501.                   }

  502.           ucVoiceLock=1;  //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
  503.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  504.           ucVoiceLock=0;  //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt

  505.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  506.           break;   
  507.    
  508.     case 2:// 减按键 对应朱兆祺学习板的S5键
  509.           switch(ucWd)  //在不同的窗口下,设置不同的参数
  510.                   {
  511.                      case 1:
  512.                   uiSetData1--;   

  513.                                   if(uiSetData1>9999)  
  514.                                   {
  515.                                      uiSetData1=0;  //最小值是0
  516.                                   }

  517.                            write_eeprom_int(0,uiSetData1); //存入EEPROM,由于内部有延时函数,所以此处会引起数码管闪烁

  518.                            ucWd1Update=1;  //窗口1更新显示
  519.                               break;
  520.                      case 2:
  521.                   uiSetData2--;
  522.                                   if(uiSetData2>9999)
  523.                                   {
  524.                                      uiSetData2=0;  //最小值是0
  525.                                   }
  526.                            write_eeprom_int(2,uiSetData2); //存入EEPROM,由于内部有延时函数,所以此处会引起数码管闪烁
  527.                            ucWd2Update=1;  //窗口2更新显示
  528.                               break;
  529.                      case 3:
  530.                   uiSetData3--;
  531.                                   if(uiSetData3>9999)
  532.                                   {
  533.                                      uiSetData3=0;  //最小值是0
  534.                                   }

  535.                            write_eeprom_int(4,uiSetData3); //存入EEPROM,由于内部有延时函数,所以此处会引起数码管闪烁
  536.                            ucWd3Update=1;  //窗口3更新显示
  537.                               break;
  538.                      case 4:
  539.                   uiSetData4--;
  540.                                   if(uiSetData4>9999)
  541.                                   {
  542.                                      uiSetData4=0;  //最小值是0
  543.                                   }
  544.                            write_eeprom_int(6,uiSetData4); //存入EEPROM,由于内部有延时函数,所以此处会引起数码管闪烁
  545.                            ucWd4Update=1;  //窗口4更新显示
  546.                               break;
  547.                   }

  548.           ucVoiceLock=1;  //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
  549.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  550.           ucVoiceLock=0;  //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt

  551.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  552.           break;  

  553.     case 3:// 切换窗口按键 对应朱兆祺学习板的S9键
  554.           ucWd++;  //切换窗口
  555.                   if(ucWd>4)
  556.                   {
  557.                     ucWd=1;
  558.                   }
  559.           switch(ucWd)  //在不同的窗口下,在不同的窗口下,更新显示不同的窗口
  560.                   {
  561.                      case 1:
  562.                            ucWd1Update=1;  //窗口1更新显示
  563.                               break;
  564.                      case 2:
  565.                            ucWd2Update=1;  //窗口2更新显示
  566.                               break;
  567.                      case 3:
  568.                            ucWd3Update=1;  //窗口3更新显示
  569.                               break;
  570.                      case 4:
  571.                            ucWd4Update=1;  //窗口4更新显示
  572.                               break;
  573.                   }
  574.           ucVoiceLock=1;  //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
  575.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  576.           ucVoiceLock=0;  //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt

  577.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  578.           break;         

  579.          
  580.   }               
  581. }

  582. void display_drive(void)  
  583. {
  584.    //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
  585.    switch(ucDisplayDriveStep)
  586.    {
  587.       case 1:  //显示第1位
  588.            ucDigShowTemp=dig_table[ucDigShow1];
  589.                    if(ucDigDot1==1)
  590.                    {
  591.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  592.                    }
  593.            dig_hc595_drive(ucDigShowTemp,0xfe);
  594.                break;
  595.       case 2:  //显示第2位
  596.            ucDigShowTemp=dig_table[ucDigShow2];
  597.                    if(ucDigDot2==1)
  598.                    {
  599.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  600.                    }
  601.            dig_hc595_drive(ucDigShowTemp,0xfd);
  602.                break;
  603.       case 3:  //显示第3位
  604.            ucDigShowTemp=dig_table[ucDigShow3];
  605.                    if(ucDigDot3==1)
  606.                    {
  607.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  608.                    }
  609.            dig_hc595_drive(ucDigShowTemp,0xfb);
  610.                break;
  611.       case 4:  //显示第4位
  612.            ucDigShowTemp=dig_table[ucDigShow4];
  613.                    if(ucDigDot4==1)
  614.                    {
  615.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  616.                    }
  617.            dig_hc595_drive(ucDigShowTemp,0xf7);
  618.                break;
  619.       case 5:  //显示第5位
  620.            ucDigShowTemp=dig_table[ucDigShow5];
  621.                    if(ucDigDot5==1)
  622.                    {
  623.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  624.                    }
  625.            dig_hc595_drive(ucDigShowTemp,0xef);
  626.                break;
  627.       case 6:  //显示第6位
  628.            ucDigShowTemp=dig_table[ucDigShow6];
  629.                    if(ucDigDot6==1)
  630.                    {
  631.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  632.                    }
  633.            dig_hc595_drive(ucDigShowTemp,0xdf);
  634.                break;
  635.       case 7:  //显示第7位
  636.            ucDigShowTemp=dig_table[ucDigShow7];
  637.                    if(ucDigDot7==1)
  638.                    {
  639.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  640.            }
  641.            dig_hc595_drive(ucDigShowTemp,0xbf);
  642.                break;
  643.       case 8:  //显示第8位
  644.            ucDigShowTemp=dig_table[ucDigShow8];
  645.                    if(ucDigDot8==1)
  646.                    {
  647.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  648.                    }
  649.            dig_hc595_drive(ucDigShowTemp,0x7f);
  650.                break;
  651.    }
  652.    ucDisplayDriveStep++;
  653.    if(ucDisplayDriveStep>8)  //扫描完8个数码管后,重新从第一个开始扫描
  654.    {
  655.      ucDisplayDriveStep=1;
  656.    }

  657. }

  658. //数码管的74HC595驱动函数
  659. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
  660. {
  661.    unsigned char i;
  662.    unsigned char ucTempData;
  663.    dig_hc595_sh_dr=0;
  664.    dig_hc595_st_dr=0;
  665.    ucTempData=ucDigStatusTemp16_09;  //先送高8位
  666.    for(i=0;i<8;i++)
  667.    {
  668.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  669.          else dig_hc595_ds_dr=0;
  670.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  671.          delay_short(1);
  672.          dig_hc595_sh_dr=1;
  673.          delay_short(1);
  674.          ucTempData=ucTempData<<1;
  675.    }
  676.    ucTempData=ucDigStatusTemp08_01;  //再先送低8位
  677.    for(i=0;i<8;i++)
  678.    {
  679.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  680.          else dig_hc595_ds_dr=0;
  681.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  682.          delay_short(1);
  683.          dig_hc595_sh_dr=1;
  684.          delay_short(1);
  685.          ucTempData=ucTempData<<1;
  686.    }
  687.    dig_hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  688.    delay_short(1);
  689.    dig_hc595_st_dr=1;
  690.    delay_short(1);
  691.    dig_hc595_sh_dr=0;    //拉低,抗干扰就增强
  692.    dig_hc595_st_dr=0;
  693.    dig_hc595_ds_dr=0;
  694. }

  695. //LED灯的74HC595驱动函数
  696. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
  697. {
  698.    unsigned char i;
  699.    unsigned char ucTempData;
  700.    hc595_sh_dr=0;
  701.    hc595_st_dr=0;
  702.    ucTempData=ucLedStatusTemp16_09;  //先送高8位
  703.    for(i=0;i<8;i++)
  704.    {
  705.          if(ucTempData>=0x80)hc595_ds_dr=1;
  706.          else hc595_ds_dr=0;
  707.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  708.          delay_short(1);
  709.          hc595_sh_dr=1;
  710.          delay_short(1);
  711.          ucTempData=ucTempData<<1;
  712.    }
  713.    ucTempData=ucLedStatusTemp08_01;  //再先送低8位
  714.    for(i=0;i<8;i++)
  715.    {
  716.          if(ucTempData>=0x80)hc595_ds_dr=1;
  717.          else hc595_ds_dr=0;
  718.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  719.          delay_short(1);
  720.          hc595_sh_dr=1;
  721.          delay_short(1);
  722.          ucTempData=ucTempData<<1;
  723.    }
  724.    hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  725.    delay_short(1);
  726.    hc595_st_dr=1;
  727.    delay_short(1);
  728.    hc595_sh_dr=0;    //拉低,抗干扰就增强
  729.    hc595_st_dr=0;
  730.    hc595_ds_dr=0;
  731. }


  732. void T0_time(void) interrupt 1   //定时中断
  733. {
  734.   TF0=0;  //清除中断标志
  735.   TR0=0; //关中断


  736. /* 注释三:
  737.   * 此处多增加一个原子锁,作为中断与主函数共享数据的保护,实际上是借鉴了"红金龙吸味"关于原子锁的建议.
  738.   */  

  739.   if(ucVoiceLock==0) //原子锁判断
  740.   {
  741.      if(uiVoiceCnt!=0)
  742.      {

  743.         uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
  744.         beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  745.      
  746.      }
  747.      else
  748.      {

  749.         ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
  750.         beep_dr=1;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  751.         
  752.      }
  753.   }




  754.   key_scan(); //按键扫描函数
  755.   display_drive();  //数码管字模的驱动函数

  756.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  757.   TL0=0x0b;
  758.   TR0=1;  //开中断
  759. }

  760. void delay_short(unsigned int uiDelayShort)
  761. {
  762.    unsigned int i;  
  763.    for(i=0;i<uiDelayShort;i++)
  764.    {
  765.      ;   //一个分号相当于执行一条空语句
  766.    }
  767. }

  768. void delay_long(unsigned int uiDelayLong)
  769. {
  770.    unsigned int i;
  771.    unsigned int j;
  772.    for(i=0;i<uiDelayLong;i++)
  773.    {
  774.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  775.           {
  776.              ; //一个分号相当于执行一条空语句
  777.           }
  778.    }
  779. }

  780. void initial_myself(void)  //第一区 初始化单片机
  781. {
  782. /* 注释四:
  783. * 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
  784. * 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
  785. * 坚鸿51学习板的S1就是本程序中用到的一个独立按键。
  786. */
  787.   key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平
  788.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
  789.   hc595_drive(0x00,0x00);  //关闭所有经过另外两个74HC595驱动的LED灯
  790.   TMOD=0x01;  //设置定时器0为工作方式1
  791.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  792.   TL0=0x0b;

  793. }
  794. void initial_peripheral(void) //第二区 初始化外围
  795. {

  796.    ucDigDot8=0;   //小数点全部不显示
  797.    ucDigDot7=0;  
  798.    ucDigDot6=0;
  799.    ucDigDot5=0;  
  800.    ucDigDot4=0;
  801.    ucDigDot3=0;  
  802.    ucDigDot2=0;
  803.    ucDigDot1=0;

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

  807. /* 注释五:
  808. * 如何初始化EEPROM数据的方法。在使用EEPROM时,这一步初始化很关键!
  809. * 第一次上电时,我们从EEPROM读取出来的数据有可能超出了范围,可能是ff。
  810. * 这个时候我们应该给它填入一个初始化的数据,这一步千万别漏了。另外,
  811. * 由于int类型数据占用2个字节,所以以下4个数据挨着的地址是0,2,4,6.
  812. */

  813.    uiSetData1=read_eeprom_int(0);  //读取uiSetData1,内部占用2个字节地址
  814.    if(uiSetData1>9999)   //不在范围内
  815.    {
  816.        uiSetData1=0;   //填入一个初始化数据
  817.        write_eeprom_int(0,uiSetData1); //存入uiSetData1,内部占用2个字节地址
  818.    }

  819.    uiSetData2=read_eeprom_int(2);  //读取uiSetData2,内部占用2个字节地址
  820.    if(uiSetData2>9999)//不在范围内
  821.    {
  822.        uiSetData2=0;  //填入一个初始化数据
  823.        write_eeprom_int(2,uiSetData2); //存入uiSetData2,内部占用2个字节地址
  824.    }

  825.    uiSetData3=read_eeprom_int(4);  //读取uiSetData3,内部占用2个字节地址
  826.    if(uiSetData3>9999)//不在范围内
  827.    {
  828.        uiSetData3=0;  //填入一个初始化数据
  829.        write_eeprom_int(4,uiSetData3); //存入uiSetData3,内部占用2个字节地址
  830.    }

  831.    uiSetData4=read_eeprom_int(6);  //读取uiSetData4,内部占用2个字节地址
  832.    if(uiSetData4>9999)//不在范围内
  833.    {
  834.        uiSetData4=0;  //填入一个初始化数据
  835.        write_eeprom_int(6,uiSetData4); //存入uiSetData4,内部占用2个字节地址
  836.    }



  837. }
复制代码

总结陈词:
   IIC通讯过程是一个要求一气呵成的通讯过程,中间不能被其它中断影响时序出错,因此,在整个通讯过程中应该先关闭总中断,完成之后再开中断。但是,这样就会引起另外一个新问题,如果关闭总中断的时间太长,会导致动态数码管不能及时均匀的扫描,在按键更改参数,内部操作EEPROM时,数码管就会出现短暂明显的闪烁现象,解决这个问题最好的办法就是在做项目中尽量不要用动态扫描数码管的方案,应该用静态显示的方案。那么在程序上还有没有改善这种现象的方法?当然有。欲知详情,请听下回分解-----操作AT24C02时,利用“一气呵成的定时器方式”改善数码管的闪烁现象。
  
(未完待续,下节更精彩,不要走开哦)

作者: jianhong_wu    时间: 2014-5-15 12:55
第四十七节:操作AT24C02时,利用“一气呵成的定时器延时”改善数码管的闪烁现象。

开场白:
上一节在按键更改参数时,会出现短暂明显的数码管闪烁现象。这节通过教大家使用新型延时函数可以有效的改善闪烁现象。要教会大家三个知识点:
第一个:如何编写一气呵成的定时器延时函数。
第二个:如何编写检查EEPROM芯片是否存在短路,虚焊或者芯片坏了的监控程序。
第三个:经过网友“cjseng”的提醒,我建议大家以后在用EEPROM芯片时,如果单片机IO口足够多,WP引脚应该专门接一个IO口,并且加一个上拉电阻,需要更改EEPROM存储数据时置低,其他任何一个时刻都置高,这样可以更加有效地保护EEPROM内部数据不会被意外更改。

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

(1)硬件平台:
    基于坚鸿51单片机学习板。旧版的坚鸿51学习板在硬件上有一个bug,AT24C02的第8个引脚VCC悬空了!!!,读者记得把它飞线连接到5V电源处。新版的坚鸿51学习板已经改过来了。


(2)实现功能:
    4个被更改后的参数断电后不丢失,数据可以保存,断电再上电后还是上一次最新被修改的数据。如果AT24C02短路,虚焊,或者坏了,系统可以检查出来,并且蜂鸣器会间歇性鸣叫报警。按更改参数按键时,数码管比上一节大大降低了闪烁现象。
    显示和独立按键部分根据第29节的程序来改编,用坚鸿51单片机学习板中的S1,S5,S9作为独立按键。
      一共有4个窗口。每个窗口显示一个参数。
     第8,7,6,5位数码管显示当前窗口,P-1代表第1个窗口,P-2代表第2个窗口,P-3代表第3个窗口,P-4代表第1个窗口。
     第4,3,2,1位数码管显示当前窗口被设置的参数。范围是从0到9999。S1是加按键,按下此按键会依次增加当前窗口的参数。S5是减按键,按下此按键会依次减少当前窗口的参数。S9是切换窗口按键,按下此按键会依次循环切换不同的窗口。


(3)源代码讲解如下:
  1. #include "REG52.H"

  2. #define const_voice_short  40   //蜂鸣器短叫的持续时间
  3. #define const_key_time1  20    //按键去抖动延时的时间
  4. #define const_key_time2  20    //按键去抖动延时的时间
  5. #define const_key_time3  20    //按键去抖动延时的时间


  6. #define const_eeprom_1s    400   //大概1秒的时间

  7. void initial_myself(void);   
  8. void initial_peripheral(void);
  9. void delay_short(unsigned int uiDelayShort);
  10. void delay_long(unsigned int uiDelaylong);
  11. void delay_timer(unsigned int uiDelayTimerTemp); //一气呵成的定时器延时方式

  12. //驱动数码管的74HC595
  13. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);  
  14. void display_drive(void); //显示数码管字模的驱动函数
  15. void display_service(void); //显示的窗口菜单服务程序
  16. //驱动LED的74HC595
  17. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);


  18. void start24(void);  //开始位
  19. void ack24(void);  //确认位
  20. void stop24(void);  //停止位
  21. unsigned char read24(void);  //读取一个字节的时序
  22. void write24(unsigned char dd); //发送一个字节的时序
  23. unsigned char read_eeprom(unsigned int address);   //从一个地址读取出一个字节数据
  24. void write_eeprom(unsigned int address,unsigned char dd); //往一个地址存入一个字节数据
  25. unsigned int read_eeprom_int(unsigned int address);   //从一个地址读取出一个int类型的数据
  26. void write_eeprom_int(unsigned int address,unsigned int uiWriteData); //往一个地址存入一个int类型的数据

  27. void T0_time(void);  //定时中断函数

  28. void key_service(void); //按键服务的应用程序
  29. void key_scan(void);//按键扫描函数 放在定时中断里

  30. void eeprom_alarm_service(void); //EEPROM出错报警


  31. sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
  32. sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键
  33. sbit key_sr3=P0^2; //对应朱兆祺学习板的S9键

  34. sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平
  35. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口

  36. sbit eeprom_scl_dr=P3^7;    //时钟线
  37. sbit eeprom_sda_dr_sr=P3^6; //数据的输出线和输入线

  38. sbit dig_hc595_sh_dr=P2^0;     //数码管的74HC595程序
  39. sbit dig_hc595_st_dr=P2^1;  
  40. sbit dig_hc595_ds_dr=P2^2;  
  41. sbit hc595_sh_dr=P2^3;    //LED灯的74HC595程序
  42. sbit hc595_st_dr=P2^4;  
  43. sbit hc595_ds_dr=P2^5;  



  44. unsigned char ucKeySec=0;   //被触发的按键编号

  45. unsigned int  uiKeyTimeCnt1=0; //按键去抖动延时计数器
  46. unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志
  47. unsigned int  uiKeyTimeCnt2=0; //按键去抖动延时计数器
  48. unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志
  49. unsigned int  uiKeyTimeCnt3=0; //按键去抖动延时计数器
  50. unsigned char ucKeyLock3=0; //按键触发后自锁的变量标志



  51. unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器
  52. unsigned char  ucVoiceLock=0;  //蜂鸣器鸣叫的原子锁

  53. unsigned char ucDigShow8;  //第8位数码管要显示的内容
  54. unsigned char ucDigShow7;  //第7位数码管要显示的内容
  55. unsigned char ucDigShow6;  //第6位数码管要显示的内容
  56. unsigned char ucDigShow5;  //第5位数码管要显示的内容
  57. unsigned char ucDigShow4;  //第4位数码管要显示的内容
  58. unsigned char ucDigShow3;  //第3位数码管要显示的内容
  59. unsigned char ucDigShow2;  //第2位数码管要显示的内容
  60. unsigned char ucDigShow1;  //第1位数码管要显示的内容

  61. unsigned char ucDigDot8;  //数码管8的小数点是否显示的标志
  62. unsigned char ucDigDot7;  //数码管7的小数点是否显示的标志
  63. unsigned char ucDigDot6;  //数码管6的小数点是否显示的标志
  64. unsigned char ucDigDot5;  //数码管5的小数点是否显示的标志
  65. unsigned char ucDigDot4;  //数码管4的小数点是否显示的标志
  66. unsigned char ucDigDot3;  //数码管3的小数点是否显示的标志
  67. unsigned char ucDigDot2;  //数码管2的小数点是否显示的标志
  68. unsigned char ucDigDot1;  //数码管1的小数点是否显示的标志
  69. unsigned char ucDigShowTemp=0; //临时中间变量
  70. unsigned char ucDisplayDriveStep=1;  //动态扫描数码管的步骤变量

  71. unsigned char ucWd1Update=1; //窗口1更新显示标志
  72. unsigned char ucWd2Update=0; //窗口2更新显示标志
  73. unsigned char ucWd3Update=0; //窗口3更新显示标志
  74. unsigned char ucWd4Update=0; //窗口4更新显示标志
  75. unsigned char ucWd=1;  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
  76. unsigned int  uiSetData1=0;  //本程序中需要被设置的参数1
  77. unsigned int  uiSetData2=0;  //本程序中需要被设置的参数2
  78. unsigned int  uiSetData3=0;  //本程序中需要被设置的参数3
  79. unsigned int  uiSetData4=0;  //本程序中需要被设置的参数4

  80. unsigned char ucTemp1=0;  //中间过渡变量
  81. unsigned char ucTemp2=0;  //中间过渡变量
  82. unsigned char ucTemp3=0;  //中间过渡变量
  83. unsigned char ucTemp4=0;  //中间过渡变量

  84. unsigned char ucDelayTimerLock=0; //原子锁
  85. unsigned int  uiDelayTimer=0;

  86. unsigned char ucCheckEeprom=0;  //检查EEPROM芯片是否正常
  87. unsigned char ucEepromError=0; //EEPROM芯片是否正常的标志

  88. unsigned char ucEepromLock=0;//原子锁
  89. unsigned int  uiEepromCnt=0; //间歇性蜂鸣器报警的计时器

  90. //根据原理图得出的共阴数码管字模表
  91. code unsigned char dig_table[]=
  92. {
  93. 0x3f,  //0       序号0
  94. 0x06,  //1       序号1
  95. 0x5b,  //2       序号2
  96. 0x4f,  //3       序号3
  97. 0x66,  //4       序号4
  98. 0x6d,  //5       序号5
  99. 0x7d,  //6       序号6
  100. 0x07,  //7       序号7
  101. 0x7f,  //8       序号8
  102. 0x6f,  //9       序号9
  103. 0x00,  //无      序号10
  104. 0x40,  //-       序号11
  105. 0x73,  //P       序号12
  106. };
  107. void main()
  108.   {
  109.    initial_myself();  
  110.    delay_long(100);   
  111.    initial_peripheral();
  112.    while(1)  
  113.    {
  114.       key_service(); //按键服务的应用程序
  115.       display_service(); //显示的窗口菜单服务程序
  116.           eeprom_alarm_service(); //EEPROM出错报警
  117.    }
  118. }


  119. void eeprom_alarm_service(void) //EEPROM出错报警
  120. {

  121.   if(ucEepromError==1) //EEPROM出错
  122.   {
  123.       if(uiEepromCnt<const_eeprom_1s)  //大概1秒钟蜂鸣器响一次
  124.       {
  125.              ucEepromLock=1;  //原子锁加锁
  126.          uiEepromCnt=0; //计时器清零
  127.              ucEepromLock=0;  //原子锁解锁


  128.          ucVoiceLock=1;  //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
  129.          uiVoiceCnt=const_voice_short; //蜂鸣器声音触发,滴一声就停。
  130.          ucVoiceLock=0;  //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt

  131.       }
  132.   }

  133. }


  134. //AT24C02驱动程序
  135. void start24(void)  //开始位
  136. {

  137.     eeprom_sda_dr_sr=1;
  138.     eeprom_scl_dr=1;
  139.         delay_short(15);
  140.     eeprom_sda_dr_sr=0;
  141.         delay_short(15);
  142.     eeprom_scl_dr=0;   
  143. }


  144. void ack24(void)  //确认位时序
  145. {
  146.     eeprom_sda_dr_sr=1; //51单片机在读取数据之前要先置一,表示数据输入

  147.     eeprom_scl_dr=1;
  148.         delay_short(15);
  149.     eeprom_scl_dr=0;
  150.         delay_short(15);

  151. //在本驱动程序中,我没有对ACK信号进行出错判断,因为我这么多年一直都是这样用也没出现过什么问题。
  152. //有兴趣的朋友可以自己增加出错判断,不一定非要按我的方式去做。
  153. }

  154. void stop24(void)  //停止位
  155. {
  156.     eeprom_sda_dr_sr=0;
  157.     eeprom_scl_dr=1;
  158.         delay_short(15);
  159.     eeprom_sda_dr_sr=1;
  160. }



  161. unsigned char read24(void)  //读取一个字节的时序
  162. {
  163.         unsigned char outdata,tempdata;


  164.         outdata=0;
  165.                 eeprom_sda_dr_sr=1; //51单片机的IO口在读取数据之前要先置一,表示数据输入
  166.         delay_short(2);
  167.         for(tempdata=0;tempdata<8;tempdata++)
  168.         {
  169.             eeprom_scl_dr=0;
  170.             delay_short(2);
  171.             eeprom_scl_dr=1;
  172.             delay_short(2);
  173.             outdata<<=1;
  174.             if(eeprom_sda_dr_sr==1)outdata++;      
  175.             eeprom_sda_dr_sr=1; //51单片机的IO口在读取数据之前要先置一,表示数据输入
  176.             delay_short(2);
  177.         }
  178.     return(outdata);
  179.      
  180. }

  181. void write24(unsigned char dd) //发送一个字节的时序
  182. {

  183.         unsigned char tempdata;
  184.         for(tempdata=0;tempdata<8;tempdata++)
  185.         {
  186.                 if(dd>=0x80)eeprom_sda_dr_sr=1;
  187.                 else eeprom_sda_dr_sr=0;
  188.                 dd<<=1;
  189.                 delay_short(2);
  190.                 eeprom_scl_dr=1;
  191.                 delay_short(4);
  192.                 eeprom_scl_dr=0;
  193.         }


  194. }



  195. unsigned char read_eeprom(unsigned int address)   //从一个地址读取出一个字节数据
  196. {

  197.    unsigned char dd,cAddress;  

  198.    cAddress=address; //把低字节地址传递给一个字节变量。

  199.    EA=0; //禁止中断

  200.    start24(); //IIC通讯开始

  201.    write24(0xA0); //此字节包含读写指令和芯片地址两方面的内容。
  202.                   //指令为写指令。地址为"000"的信息,此信息由A0,A1,A2的引脚决定

  203.    ack24(); //发送应答信号
  204.    write24(cAddress); //发送读取的存储地址(范围是0至255)
  205.    ack24(); //发送应答信号

  206.    start24(); //开始
  207.    write24(0xA1); //此字节包含读写指令和芯片地址两方面的内容。
  208.                   //指令为读指令。地址为"000"的信息,此信息由A0,A1,A2的引脚决定
  209.    ack24(); //发送应答信号
  210.    dd=read24(); //读取一个字节
  211.    ack24(); //发送应答信号
  212.    stop24();  //停止
  213.    EA=1; //允许中断
  214.    delay_timer(2); //一气呵成的定时器延时方式,在延时的时候还可以动态扫描数码管

  215.    return(dd);
  216. }

  217. void write_eeprom(unsigned int address,unsigned char dd) //往一个地址存入一个字节数据
  218. {
  219.    unsigned char cAddress;   

  220.    cAddress=address; //把低字节地址传递给一个字节变量。


  221.    EA=0; //禁止中断

  222.    start24(); //IIC通讯开始

  223.    write24(0xA0); //此字节包含读写指令和芯片地址两方面的内容。
  224.                   //指令为写指令。地址为"000"的信息,此信息由A0,A1,A2的引脚决定
  225.    ack24(); //发送应答信号
  226.    write24(cAddress);   //发送写入的存储地址(范围是0至255)
  227.    ack24(); //发送应答信号
  228.    write24(dd);  //写入存储的数据
  229.    ack24(); //发送应答信号
  230.    stop24();  //停止
  231.    EA=1; //允许中断
  232.    delay_timer(4); //一气呵成的定时器延时方式,在延时的时候还可以动态扫描数码管

  233. }


  234. unsigned int read_eeprom_int(unsigned int address)   //从一个地址读取出一个int类型的数据
  235. {
  236.    unsigned char ucReadDataH;
  237.    unsigned char ucReadDataL;
  238.    unsigned int  uiReadDate;

  239.    ucReadDataH=read_eeprom(address);    //读取高字节
  240.    ucReadDataL=read_eeprom(address+1);  //读取低字节

  241.    uiReadDate=ucReadDataH;  //把两个字节合并成一个int类型数据
  242.    uiReadDate=uiReadDate<<8;
  243.    uiReadDate=uiReadDate+ucReadDataL;

  244.    return uiReadDate;

  245. }

  246. void write_eeprom_int(unsigned int address,unsigned int uiWriteData) //往一个地址存入一个int类型的数据
  247. {
  248.    unsigned char ucWriteDataH;
  249.    unsigned char ucWriteDataL;

  250.    ucWriteDataH=uiWriteData>>8;
  251.    ucWriteDataL=uiWriteData;

  252.    write_eeprom(address,ucWriteDataH); //存入高字节
  253.    write_eeprom(address+1,ucWriteDataL); //存入低字节

  254. }


  255. void display_service(void) //显示的窗口菜单服务程序
  256. {

  257.    switch(ucWd)  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
  258.    {
  259.        case 1:   //显示P--1窗口的数据
  260.             if(ucWd1Update==1)  //窗口1要全部更新显示
  261.    {
  262.                ucWd1Update=0;  //及时清零标志,避免一直进来扫描
  263.                ucDigShow8=12;  //第8位数码管显示P
  264.                ucDigShow7=11;  //第7位数码管显示-
  265.                ucDigShow6=1;   //第6位数码管显示1
  266.                ucDigShow5=10;  //第5位数码管显示无

  267.               //先分解数据
  268.                        ucTemp4=uiSetData1/1000;     
  269.                        ucTemp3=uiSetData1%1000/100;
  270.                        ucTemp2=uiSetData1%100/10;
  271.                        ucTemp1=uiSetData1%10;
  272.   
  273.                           //再过渡需要显示的数据到缓冲变量里,让过渡的时间越短越好

  274.                if(uiSetData1<1000)   
  275.                            {
  276.                               ucDigShow4=10;  //如果小于1000,千位显示无
  277.                            }
  278.                else
  279.                            {
  280.                   ucDigShow4=ucTemp4;  //第4位数码管要显示的内容
  281.                            }
  282.                if(uiSetData1<100)
  283.                            {
  284.                   ucDigShow3=10;  //如果小于100,百位显示无
  285.                            }
  286.                            else
  287.                            {
  288.                   ucDigShow3=ucTemp3;  //第3位数码管要显示的内容
  289.                            }
  290.                if(uiSetData1<10)
  291.                            {
  292.                   ucDigShow2=10;  //如果小于10,十位显示无
  293.                            }
  294.                            else
  295.                            {
  296.                   ucDigShow2=ucTemp2;  //第2位数码管要显示的内容
  297.                }
  298.                ucDigShow1=ucTemp1;  //第1位数码管要显示的内容
  299.             }
  300.             break;
  301.         case 2:  //显示P--2窗口的数据
  302.             if(ucWd2Update==1)  //窗口2要全部更新显示
  303.    {
  304.                ucWd2Update=0;  //及时清零标志,避免一直进来扫描
  305.                ucDigShow8=12;  //第8位数码管显示P
  306.                ucDigShow7=11;  //第7位数码管显示-
  307.                ucDigShow6=2;  //第6位数码管显示2
  308.                ucDigShow5=10;   //第5位数码管显示无
  309.                        ucTemp4=uiSetData2/1000;     //分解数据
  310.                        ucTemp3=uiSetData2%1000/100;
  311.                        ucTemp2=uiSetData2%100/10;
  312.                        ucTemp1=uiSetData2%10;

  313.                if(uiSetData2<1000)   
  314.                            {
  315.                               ucDigShow4=10;  //如果小于1000,千位显示无
  316.                            }
  317.                else
  318.                            {
  319.                   ucDigShow4=ucTemp4;  //第4位数码管要显示的内容
  320.                            }
  321.                if(uiSetData2<100)
  322.                            {
  323.                   ucDigShow3=10;  //如果小于100,百位显示无
  324.                            }
  325.                            else
  326.                            {
  327.                   ucDigShow3=ucTemp3;  //第3位数码管要显示的内容
  328.                            }
  329.                if(uiSetData2<10)
  330.                            {
  331.                   ucDigShow2=10;  //如果小于10,十位显示无
  332.                            }
  333.                            else
  334.                            {
  335.                   ucDigShow2=ucTemp2;  //第2位数码管要显示的内容
  336.                }
  337.                ucDigShow1=ucTemp1;  //第1位数码管要显示的内容
  338.     }
  339.              break;
  340.         case 3:  //显示P--3窗口的数据
  341.             if(ucWd3Update==1)  //窗口3要全部更新显示
  342.    {
  343.                ucWd3Update=0;  //及时清零标志,避免一直进来扫描
  344.                ucDigShow8=12;  //第8位数码管显示P
  345.                ucDigShow7=11;  //第7位数码管显示-
  346.                ucDigShow6=3;  //第6位数码管显示3
  347.                ucDigShow5=10;   //第5位数码管显示无
  348.                        ucTemp4=uiSetData3/1000;     //分解数据
  349.                        ucTemp3=uiSetData3%1000/100;
  350.                        ucTemp2=uiSetData3%100/10;
  351.                        ucTemp1=uiSetData3%10;
  352.                if(uiSetData3<1000)   
  353.                            {
  354.                               ucDigShow4=10;  //如果小于1000,千位显示无
  355.                            }
  356.                else
  357.                            {
  358.                   ucDigShow4=ucTemp4;  //第4位数码管要显示的内容
  359.                            }
  360.                if(uiSetData3<100)
  361.                            {
  362.                   ucDigShow3=10;  //如果小于100,百位显示无
  363.                            }
  364.                            else
  365.                            {
  366.                   ucDigShow3=ucTemp3;  //第3位数码管要显示的内容
  367.                            }
  368.                if(uiSetData3<10)
  369.                            {
  370.                   ucDigShow2=10;  //如果小于10,十位显示无
  371.                            }
  372.                            else
  373.                            {
  374.                   ucDigShow2=ucTemp2;  //第2位数码管要显示的内容
  375.                }
  376.                ucDigShow1=ucTemp1;  //第1位数码管要显示的内容
  377.    }
  378.             break;
  379.         case 4:  //显示P--4窗口的数据
  380.             if(ucWd4Update==1)  //窗口4要全部更新显示
  381.    {
  382.                ucWd4Update=0;  //及时清零标志,避免一直进来扫描
  383.                ucDigShow8=12;  //第8位数码管显示P
  384.                ucDigShow7=11;  //第7位数码管显示-
  385.                ucDigShow6=4;  //第6位数码管显示4
  386.                ucDigShow5=10;   //第5位数码管显示无
  387.                        ucTemp4=uiSetData4/1000;     //分解数据
  388.                        ucTemp3=uiSetData4%1000/100;
  389.                        ucTemp2=uiSetData4%100/10;
  390.                        ucTemp1=uiSetData4%10;

  391.                if(uiSetData4<1000)   
  392.                            {
  393.                               ucDigShow4=10;  //如果小于1000,千位显示无
  394.                            }
  395.                else
  396.                            {
  397.                   ucDigShow4=ucTemp4;  //第4位数码管要显示的内容
  398.                            }
  399.                if(uiSetData4<100)
  400.                            {
  401.                   ucDigShow3=10;  //如果小于100,百位显示无
  402.                            }
  403.                            else
  404.                            {
  405.                   ucDigShow3=ucTemp3;  //第3位数码管要显示的内容
  406.                            }
  407.                if(uiSetData4<10)
  408.                            {
  409.                   ucDigShow2=10;  //如果小于10,十位显示无
  410.                            }
  411.                            else
  412.                            {
  413.                   ucDigShow2=ucTemp2;  //第2位数码管要显示的内容
  414.                }
  415.                ucDigShow1=ucTemp1;  //第1位数码管要显示的内容
  416.     }
  417.              break;
  418.            }
  419.    

  420. }

  421. void key_scan(void)//按键扫描函数 放在定时中断里
  422. {  
  423.   if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  424.   {
  425.      ucKeyLock1=0; //按键自锁标志清零
  426.      uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  427.   }
  428.   else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
  429.   {
  430.      uiKeyTimeCnt1++; //累加定时中断次数
  431.      if(uiKeyTimeCnt1>const_key_time1)
  432.      {
  433.         uiKeyTimeCnt1=0;
  434.         ucKeyLock1=1;  //自锁按键置位,避免一直触发
  435.         ucKeySec=1;    //触发1号键
  436.      }
  437.   }

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

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


  468. }

  469. void key_service(void) //按键服务的应用程序
  470. {

  471.   switch(ucKeySec) //按键服务状态切换
  472.   {
  473.     case 1:// 加按键 对应朱兆祺学习板的S1键
  474.           switch(ucWd)  //在不同的窗口下,设置不同的参数
  475.                   {
  476.                      case 1:
  477.                   uiSetData1++;   
  478.                                   if(uiSetData1>9999) //最大值是9999
  479.                                   {
  480.                                      uiSetData1=9999;
  481.                                   }

  482.                            write_eeprom_int(0,uiSetData1); //存入EEPROM 由于内部有延时函数,所以此处会引起数码管闪烁

  483.                            ucWd1Update=1;  //窗口1更新显示
  484.                               break;
  485.                      case 2:
  486.                   uiSetData2++;
  487.                                   if(uiSetData2>9999) //最大值是9999
  488.                                   {
  489.                                      uiSetData2=9999;
  490.                                   }


  491.                            write_eeprom_int(2,uiSetData2); //存入EEPROM,由于内部有延时函数,所以此处会引起数码管闪烁

  492.                            ucWd2Update=1;  //窗口2更新显示
  493.                               break;
  494.                      case 3:
  495.                   uiSetData3++;
  496.                                   if(uiSetData3>9999) //最大值是9999
  497.                                   {
  498.                                      uiSetData3=9999;
  499.                                   }
  500.                            write_eeprom_int(4,uiSetData3); //存入EEPROM,由于内部有延时函数,所以此处会引起数码管闪烁
  501.                            ucWd3Update=1;  //窗口3更新显示
  502.                               break;
  503.                      case 4:
  504.                   uiSetData4++;
  505.                                   if(uiSetData4>9999) //最大值是9999
  506.                                   {
  507.                                      uiSetData4=9999;
  508.                                   }
  509.                            write_eeprom_int(6,uiSetData4); //存入EEPROM,由于内部有延时函数,所以此处会引起数码管闪烁
  510.                            ucWd4Update=1;  //窗口4更新显示
  511.                               break;
  512.                   }

  513.           ucVoiceLock=1;  //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
  514.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  515.           ucVoiceLock=0;  //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt

  516.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  517.           break;   
  518.    
  519.     case 2:// 减按键 对应朱兆祺学习板的S5键
  520.           switch(ucWd)  //在不同的窗口下,设置不同的参数
  521.                   {
  522.                      case 1:
  523.                   uiSetData1--;   

  524.                                   if(uiSetData1>9999)  
  525.                                   {
  526.                                      uiSetData1=0;  //最小值是0
  527.                                   }

  528.                            write_eeprom_int(0,uiSetData1); //存入EEPROM,由于内部有延时函数,所以此处会引起数码管闪烁

  529.                            ucWd1Update=1;  //窗口1更新显示
  530.                               break;
  531.                      case 2:
  532.                   uiSetData2--;
  533.                                   if(uiSetData2>9999)
  534.                                   {
  535.                                      uiSetData2=0;  //最小值是0
  536.                                   }
  537.                            write_eeprom_int(2,uiSetData2); //存入EEPROM,由于内部有延时函数,所以此处会引起数码管闪烁
  538.                            ucWd2Update=1;  //窗口2更新显示
  539.                               break;
  540.                      case 3:
  541.                   uiSetData3--;
  542.                                   if(uiSetData3>9999)
  543.                                   {
  544.                                      uiSetData3=0;  //最小值是0
  545.                                   }

  546.                            write_eeprom_int(4,uiSetData3); //存入EEPROM,由于内部有延时函数,所以此处会引起数码管闪烁
  547.                            ucWd3Update=1;  //窗口3更新显示
  548.                               break;
  549.                      case 4:
  550.                   uiSetData4--;
  551.                                   if(uiSetData4>9999)
  552.                                   {
  553.                                      uiSetData4=0;  //最小值是0
  554.                                   }
  555.                            write_eeprom_int(6,uiSetData4); //存入EEPROM,由于内部有延时函数,所以此处会引起数码管闪烁
  556.                            ucWd4Update=1;  //窗口4更新显示
  557.                               break;
  558.                   }

  559.           ucVoiceLock=1;  //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
  560.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  561.           ucVoiceLock=0;  //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt

  562.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  563.           break;  

  564.     case 3:// 切换窗口按键 对应朱兆祺学习板的S9键
  565.           ucWd++;  //切换窗口
  566.                   if(ucWd>4)
  567.                   {
  568.                     ucWd=1;
  569.                   }
  570.           switch(ucWd)  //在不同的窗口下,在不同的窗口下,更新显示不同的窗口
  571.                   {
  572.                      case 1:
  573.                            ucWd1Update=1;  //窗口1更新显示
  574.                               break;
  575.                      case 2:
  576.                            ucWd2Update=1;  //窗口2更新显示
  577.                               break;
  578.                      case 3:
  579.                            ucWd3Update=1;  //窗口3更新显示
  580.                               break;
  581.                      case 4:
  582.                            ucWd4Update=1;  //窗口4更新显示
  583.                               break;
  584.                   }
  585.           ucVoiceLock=1;  //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
  586.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  587.           ucVoiceLock=0;  //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt

  588.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  589.           break;         

  590.          
  591.   }               
  592. }

  593. void display_drive(void)  
  594. {
  595.    //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
  596.    switch(ucDisplayDriveStep)
  597.    {
  598.       case 1:  //显示第1位
  599.            ucDigShowTemp=dig_table[ucDigShow1];
  600.                    if(ucDigDot1==1)
  601.                    {
  602.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  603.                    }
  604.            dig_hc595_drive(ucDigShowTemp,0xfe);
  605.                break;
  606.       case 2:  //显示第2位
  607.            ucDigShowTemp=dig_table[ucDigShow2];
  608.                    if(ucDigDot2==1)
  609.                    {
  610.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  611.                    }
  612.            dig_hc595_drive(ucDigShowTemp,0xfd);
  613.                break;
  614.       case 3:  //显示第3位
  615.            ucDigShowTemp=dig_table[ucDigShow3];
  616.                    if(ucDigDot3==1)
  617.                    {
  618.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  619.                    }
  620.            dig_hc595_drive(ucDigShowTemp,0xfb);
  621.                break;
  622.       case 4:  //显示第4位
  623.            ucDigShowTemp=dig_table[ucDigShow4];
  624.                    if(ucDigDot4==1)
  625.                    {
  626.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  627.                    }
  628.            dig_hc595_drive(ucDigShowTemp,0xf7);
  629.                break;
  630.       case 5:  //显示第5位
  631.            ucDigShowTemp=dig_table[ucDigShow5];
  632.                    if(ucDigDot5==1)
  633.                    {
  634.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  635.                    }
  636.            dig_hc595_drive(ucDigShowTemp,0xef);
  637.                break;
  638.       case 6:  //显示第6位
  639.            ucDigShowTemp=dig_table[ucDigShow6];
  640.                    if(ucDigDot6==1)
  641.                    {
  642.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  643.                    }
  644.            dig_hc595_drive(ucDigShowTemp,0xdf);
  645.                break;
  646.       case 7:  //显示第7位
  647.            ucDigShowTemp=dig_table[ucDigShow7];
  648.                    if(ucDigDot7==1)
  649.                    {
  650.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  651.            }
  652.            dig_hc595_drive(ucDigShowTemp,0xbf);
  653.                break;
  654.       case 8:  //显示第8位
  655.            ucDigShowTemp=dig_table[ucDigShow8];
  656.                    if(ucDigDot8==1)
  657.                    {
  658.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  659.                    }
  660.            dig_hc595_drive(ucDigShowTemp,0x7f);
  661.                break;
  662.    }
  663.    ucDisplayDriveStep++;
  664.    if(ucDisplayDriveStep>8)  //扫描完8个数码管后,重新从第一个开始扫描
  665.    {
  666.      ucDisplayDriveStep=1;
  667.    }

  668. }

  669. //数码管的74HC595驱动函数
  670. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
  671. {
  672.    unsigned char i;
  673.    unsigned char ucTempData;
  674.    dig_hc595_sh_dr=0;
  675.    dig_hc595_st_dr=0;
  676.    ucTempData=ucDigStatusTemp16_09;  //先送高8位
  677.    for(i=0;i<8;i++)
  678.    {
  679.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  680.          else dig_hc595_ds_dr=0;
  681.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  682.          delay_short(1);
  683.          dig_hc595_sh_dr=1;
  684.          delay_short(1);
  685.          ucTempData=ucTempData<<1;
  686.    }
  687.    ucTempData=ucDigStatusTemp08_01;  //再先送低8位
  688.    for(i=0;i<8;i++)
  689.    {
  690.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  691.          else dig_hc595_ds_dr=0;
  692.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  693.          delay_short(1);
  694.          dig_hc595_sh_dr=1;
  695.          delay_short(1);
  696.          ucTempData=ucTempData<<1;
  697.    }
  698.    dig_hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  699.    delay_short(1);
  700.    dig_hc595_st_dr=1;
  701.    delay_short(1);
  702.    dig_hc595_sh_dr=0;    //拉低,抗干扰就增强
  703.    dig_hc595_st_dr=0;
  704.    dig_hc595_ds_dr=0;
  705. }

  706. //LED灯的74HC595驱动函数
  707. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
  708. {
  709.    unsigned char i;
  710.    unsigned char ucTempData;
  711.    hc595_sh_dr=0;
  712.    hc595_st_dr=0;
  713.    ucTempData=ucLedStatusTemp16_09;  //先送高8位
  714.    for(i=0;i<8;i++)
  715.    {
  716.          if(ucTempData>=0x80)hc595_ds_dr=1;
  717.          else hc595_ds_dr=0;
  718.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  719.          delay_short(1);
  720.          hc595_sh_dr=1;
  721.          delay_short(1);
  722.          ucTempData=ucTempData<<1;
  723.    }
  724.    ucTempData=ucLedStatusTemp08_01;  //再先送低8位
  725.    for(i=0;i<8;i++)
  726.    {
  727.          if(ucTempData>=0x80)hc595_ds_dr=1;
  728.          else hc595_ds_dr=0;
  729.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  730.          delay_short(1);
  731.          hc595_sh_dr=1;
  732.          delay_short(1);
  733.          ucTempData=ucTempData<<1;
  734.    }
  735.    hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  736.    delay_short(1);
  737.    hc595_st_dr=1;
  738.    delay_short(1);
  739.    hc595_sh_dr=0;    //拉低,抗干扰就增强
  740.    hc595_st_dr=0;
  741.    hc595_ds_dr=0;
  742. }


  743. void T0_time(void) interrupt 1   //定时中断
  744. {
  745.   TF0=0;  //清除中断标志
  746.   TR0=0; //关中断


  747.   if(ucVoiceLock==0) //原子锁判断
  748.   {
  749.      if(uiVoiceCnt!=0)
  750.      {

  751.         uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
  752.         beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  753.      
  754.      }
  755.      else
  756.      {

  757.         ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
  758.         beep_dr=1;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  759.         
  760.      }
  761.   }

  762.   if(ucDelayTimerLock==0) //原子锁判断
  763.   {
  764.      if(uiDelayTimer>0)
  765.          {
  766.            uiDelayTimer--;   //一气呵成的定时器延时方式的计时器
  767.          }
  768.   
  769.   }


  770.   if(ucEepromError==1) //EEPROM出错
  771.   {
  772.       if(ucEepromLock==0)//原子锁判断
  773.           {
  774.              uiEepromCnt++;  //间歇性蜂鸣器报警的计时器
  775.           }
  776.   }




  777.   key_scan(); //按键扫描函数
  778.   display_drive();  //数码管字模的驱动函数

  779.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  780.   TL0=0x0b;
  781.   TR0=1;  //开中断
  782. }

  783. void delay_short(unsigned int uiDelayShort)
  784. {
  785.    unsigned int i;  
  786.    for(i=0;i<uiDelayShort;i++)
  787.    {
  788.      ;   //一个分号相当于执行一条空语句
  789.    }
  790. }

  791. void delay_long(unsigned int uiDelayLong)
  792. {
  793.    unsigned int i;
  794.    unsigned int j;
  795.    for(i=0;i<uiDelayLong;i++)
  796.    {
  797.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  798.           {
  799.              ; //一个分号相当于执行一条空语句
  800.           }
  801.    }
  802. }

  803. void delay_timer(unsigned int uiDelayTimerTemp)
  804. {
  805.     ucDelayTimerLock=1; //原子锁加锁
  806.     uiDelayTimer=uiDelayTimerTemp;
  807.     ucDelayTimerLock=0; //原子锁解锁   

  808. /* 注释一:
  809.   *延时等待,一直等到定时中断把它减到0为止.这种一气呵成的定时器方式,
  810.   *可以在延时的时候动态扫描数码管,改善数码管的闪烁现象
  811.   */
  812.     while(uiDelayTimer!=0);  //一气呵成的定时器方式延时等待

  813. }


  814. void initial_myself(void)  //第一区 初始化单片机
  815. {

  816.   key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平
  817.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
  818.   hc595_drive(0x00,0x00);  //关闭所有经过另外两个74HC595驱动的LED灯
  819.   TMOD=0x01;  //设置定时器0为工作方式1
  820.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  821.   TL0=0x0b;

  822. }
  823. void initial_peripheral(void) //第二区 初始化外围
  824. {

  825.    ucDigDot8=0;   //小数点全部不显示
  826.    ucDigDot7=0;  
  827.    ucDigDot6=0;
  828.    ucDigDot5=0;  
  829.    ucDigDot4=0;
  830.    ucDigDot3=0;  
  831.    ucDigDot2=0;
  832.    ucDigDot1=0;

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


  836. /* 注释二:
  837.   * 检查AT24C02芯片是否存在短路,虚焊,芯片坏了等不工作现象。
  838.   * 在一个特定的地址里把数据读出来,如果发现不等于0x5a,则重新写入0x5a,再读出来
  839.   * 判断是不是等于0x5a,如果不相等,则芯片有问题,出错报警提示。
  840.   */
  841.    ucCheckEeprom=read_eeprom(254); //判断AT24C02是否正常
  842.    if(ucCheckEeprom!=0x5a)  //如果不等于特定内容。则重新写入数据再判断一次
  843.    {
  844.      write_eeprom(254,0x5a);  //重新写入标志数据
  845.      ucCheckEeprom=read_eeprom(254); //判断AT24C02是否正常
  846.          if(ucCheckEeprom!=0x5a)  //如果还是不等于特定数字,则芯片不正常
  847.          {
  848.             ucEepromError=1;  //表示AT24C02芯片出错报警
  849.          }
  850.    }

  851.    uiSetData1=read_eeprom_int(0);  //读取uiSetData1,内部占用2个字节地址
  852.    if(uiSetData1>9999)   //不在范围内
  853.    {
  854.        uiSetData1=0;   //填入一个初始化数据
  855.        write_eeprom_int(0,uiSetData1); //存入uiSetData1,内部占用2个字节地址
  856.    }

  857.    uiSetData2=read_eeprom_int(2);  //读取uiSetData2,内部占用2个字节地址
  858.    if(uiSetData2>9999)//不在范围内
  859.    {
  860.        uiSetData2=0;  //填入一个初始化数据
  861.        write_eeprom_int(2,uiSetData2); //存入uiSetData2,内部占用2个字节地址
  862.    }

  863.    uiSetData3=read_eeprom_int(4);  //读取uiSetData3,内部占用2个字节地址
  864.    if(uiSetData3>9999)//不在范围内
  865.    {
  866.        uiSetData3=0;  //填入一个初始化数据
  867.        write_eeprom_int(4,uiSetData3); //存入uiSetData3,内部占用2个字节地址
  868.    }

  869.    uiSetData4=read_eeprom_int(6);  //读取uiSetData4,内部占用2个字节地址
  870.    if(uiSetData4>9999)//不在范围内
  871.    {
  872.        uiSetData4=0;  //填入一个初始化数据
  873.        write_eeprom_int(6,uiSetData4); //存入uiSetData4,内部占用2个字节地址
  874.    }



  875. }
复制代码

总结陈词:
下一节开始讲关于单片机驱动实时时钟芯片的内容,欲知详情,请听下回分解-----利用DS1302做一个实时时钟  。

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

作者: jianhong_wu    时间: 2014-5-21 11:16
第四十八节:利用DS1302做一个实时时钟  。

开场白:
DS1302有两路独立电源输入,我们只要在其中一路电源上挂一个纽扣电池就可以实现掉电时钟继续跑的功能,纽扣电池作为备用电源必须比主电源的电压低一点。DS1302还给我们预留了一片RAM区,我们可以把一些数据存入到DS1302,只要DS1302的电池有电,那么它就相当于一个EEPROM。这个RAM区有什么用呢?因为RAM区的数据只要一掉电,所有的数据都会变成0x00或者0xff,也就是数据掉电会丢失,我们可以利用这个特点,可以在里面存入标志位数据,一旦发现这个数据改变了,就知道时钟的数据需要重新设置过,或者说明电池没电了。
       在移植DS1302驱动程序中,有一个地方最容易出错,就是DS1302芯片的数据线DIO。我们编程时要特别留意这个IO口什么时候作为数据输入,什么时候作为数据输出,以便及时更改方向寄存器。对于51单片机,IO口在读取数据之前,要先置1。
这一节要教会大家六个知识点:
第一个:DS1302做实时时钟时,修改时间和读取时间的常见程序框架。
第二个:如何编写监控备用电池电量耗尽的监控程序。
第三个:在往DS1302写入数据修改时间之前,必须注意调整一下“日”的变量,因为每个月的最大天数是不一样的,有的一个月28天,有的一个月29天,有的一个月30天,有的一个月31天。
第四个:本程序第一次出现了电平按键,跟之前讲的下降沿按键不一样,请留意我是何如用软件滤波的,以及它具体的实现代码。
第五个:本程序第一次出现了一个按键按下去后,如果不松手就会触发两次事件,第一次是短按,第二次是长按3秒。请留意我是如何在之前的按键上略做修改就实现此功能的具体代码。
第六个:继续加深了解按键与显示是如何紧密关联起来的程序框架。

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

(1)        硬件平台.
基于坚鸿51单片机学习板。
旧版的坚鸿51学习板在硬件上有一个bug,DS1302芯片附近的R43,R42两个电阻应该去掉,并且把R41的电阻换成0欧姆的电阻,或者直接短接起来。新版的坚鸿51学习板已经改过来了。

(2)实现功能:
     本程序有2两个窗口。
     第1个窗口显示日期。显示格式“年-月-日”。注意中间有“-”分开。
     第2个窗口显示时间。显示格式“时 分 秒”。注意中间没“-”,只有空格分开。
     系统上电后,默认显示第2个窗口,实时显示动态的“时 分 秒”时间。此时按下S13按键不松手就会切换到显示日期的第1个窗口。松手后自动切换回第2个显示动态时间的窗口。
     需要更改时间的时候,长按S9按键不松手超过3秒后,系统将进入修改时间的状态,切换到第1个日期窗口,并且显示“年”的两位数码管会闪烁,此时可以按S1或者S5加减按键修改年的参数,修改完年后,继续短按S9按键,会切换到“月”的参数闪烁状态,只要依次不断按下S9按键,就会依次切换年,月,日,时,分,秒的参数闪烁状态,最后修改完秒的参数后,系统会自动把我们修改设置的日期时间一次性写入DS1302芯片内部,达到修改日期时间的目的。
S13是电平变化按键,用来切换窗口的,专门用来查看当前日期。按下S13按键时显示日期窗口,松手后返回到显示实时时间的窗口。

本程序在使用过程中的注意事项:
(a)第一次上电时,蜂鸣器会报警,只要DS1302芯片的备用电池电量充足,这个时候断电再重启一次,就不会报警了。
(b)第一次上电时,时间没有走动,需要重新设置一下日期时间才可以。长按S9按键可以进入修改日期时间的状态。

(3)源代码讲解如下:
  1. #include "REG52.H"

  2. #define const_dpy_time_half  200  //数码管闪烁时间的半值
  3. #define const_dpy_time_all   400  //数码管闪烁时间的全值 一定要比const_dpy_time_half 大

  4. #define const_voice_short  40   //蜂鸣器短叫的持续时间
  5. #define const_key_time1  20    //按键去抖动延时的时间
  6. #define const_key_time2  20    //按键去抖动延时的时间
  7. #define const_key_time3  20    //按键去抖动延时的时间
  8. #define const_key_time4  20    //按键去抖动延时的时间

  9. #define const_key_time17  1200  //长按超过3秒的时间
  10. #define const_ds1302_0_5s  200   //大概0.5秒的时间

  11. #define const_ds1302_sampling_time    360   //累计主循环次数的时间,每次刷新采样时钟芯片的时间

  12. #define WRITE_SECOND    0x80    //DS1302内部的相关地址
  13. #define WRITE_MINUTE    0x82
  14. #define WRITE_HOUR      0x84
  15. #define WRITE_DATE      0x86
  16. #define WRITE_MONTH     0x88
  17. #define WRITE_YEAR      0x8C

  18. #define WRITE_CHECK     0xC2  //用来检查芯片的备用电池是否用完了的地址
  19. #define READ_CHECK      0xC3  //用来检查芯片的备用电池是否用完了的地址

  20. #define READ_SECOND     0x81
  21. #define READ_MINUTE     0x83
  22. #define READ_HOUR       0x85
  23. #define READ_DATE       0x87
  24. #define READ_MONTH      0x89
  25. #define READ_YEAR       0x8D

  26. #define WRITE_PROTECT   0x8E

  27. void initial_myself(void);   
  28. void initial_peripheral(void);
  29. void delay_short(unsigned int uiDelayShort);
  30. void delay_long(unsigned int uiDelaylong);


  31. //驱动数码管的74HC595
  32. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);  
  33. void display_drive(void); //显示数码管字模的驱动函数
  34. void display_service(void); //显示的窗口菜单服务程序
  35. //驱动LED的74HC595
  36. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);

  37. void T0_time(void);  //定时中断函数

  38. void key_service(void); //按键服务的应用程序
  39. void key_scan(void);//按键扫描函数 放在定时中断里

  40. void ds1302_alarm_service(void); //ds1302出错报警
  41. void ds1302_sampling(void); //ds1302采样程序,内部每秒钟采集更新一次
  42. void Write1302 ( unsigned char addr, unsigned char dat );//修改时间的驱动
  43. unsigned char Read1302 ( unsigned char addr );//读取时间的驱动

  44. unsigned char bcd_to_number(unsigned char ucBcdTemp);  //BCD转原始数值
  45. unsigned char number_to_bcd(unsigned char ucNumberTemp); //原始数值转BCD

  46. //日调整 每个月份的日最大取值不同,有的最大28日,有的最大29日,有的最大30,有的最大31
  47. unsigned char date_adjust(unsigned char ucYearTemp,unsigned char ucMonthTemp,unsigned char ucDateTemp); //日调整

  48. sbit SCLK_dr      =P1^3;  
  49. sbit DIO_dr_sr    =P1^4;  
  50. sbit DS1302_CE_dr =P1^5;  

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

  55. sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平
  56. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口

  57. sbit eeprom_scl_dr=P3^7;    //时钟线
  58. sbit eeprom_sda_dr_sr=P3^6; //数据的输出线和输入线

  59. sbit dig_hc595_sh_dr=P2^0;     //数码管的74HC595程序
  60. sbit dig_hc595_st_dr=P2^1;  
  61. sbit dig_hc595_ds_dr=P2^2;  
  62. sbit hc595_sh_dr=P2^3;    //LED灯的74HC595程序
  63. sbit hc595_st_dr=P2^4;  
  64. sbit hc595_ds_dr=P2^5;  


  65. unsigned int uiSampingCnt=0;   //采集Ds1302的计时器,每秒钟更新采集一次

  66. unsigned char ucKeySec=0;   //被触发的按键编号

  67. unsigned int  uiKeyTimeCnt1=0; //按键去抖动延时计数器
  68. unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志
  69. unsigned int  uiKeyTimeCnt2=0; //按键去抖动延时计数器
  70. unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志
  71. unsigned int  uiKeyTimeCnt3=0; //按键去抖动延时计数器
  72. unsigned char ucKeyLock3=0; //按键触发后自锁的变量标志

  73. unsigned int uiKey4Cnt1=0;  //在软件滤波中,用到的变量
  74. unsigned int uiKey4Cnt2=0;
  75. unsigned char ucKey4Sr=1;  //实时反映按键的电平状态
  76. unsigned char ucKey4SrRecord=0; //记录上一次按键的电平状态

  77. unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器
  78. unsigned char  ucVoiceLock=0;  //蜂鸣器鸣叫的原子锁

  79. unsigned char ucDigShow8;  //第8位数码管要显示的内容
  80. unsigned char ucDigShow7;  //第7位数码管要显示的内容
  81. unsigned char ucDigShow6;  //第6位数码管要显示的内容
  82. unsigned char ucDigShow5;  //第5位数码管要显示的内容
  83. unsigned char ucDigShow4;  //第4位数码管要显示的内容
  84. unsigned char ucDigShow3;  //第3位数码管要显示的内容
  85. unsigned char ucDigShow2;  //第2位数码管要显示的内容
  86. unsigned char ucDigShow1;  //第1位数码管要显示的内容

  87. unsigned char ucDigDot8;  //数码管8的小数点是否显示的标志
  88. unsigned char ucDigDot7;  //数码管7的小数点是否显示的标志
  89. unsigned char ucDigDot6;  //数码管6的小数点是否显示的标志
  90. unsigned char ucDigDot5;  //数码管5的小数点是否显示的标志
  91. unsigned char ucDigDot4;  //数码管4的小数点是否显示的标志
  92. unsigned char ucDigDot3;  //数码管3的小数点是否显示的标志
  93. unsigned char ucDigDot2;  //数码管2的小数点是否显示的标志
  94. unsigned char ucDigDot1;  //数码管1的小数点是否显示的标志
  95. unsigned char ucDigShowTemp=0; //临时中间变量
  96. unsigned char ucDisplayDriveStep=1;  //动态扫描数码管的步骤变量


  97. unsigned char ucWd=2;  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
  98. unsigned char ucPart=0;//本程序的核心变量,局部显示变量。类似于二级菜单的变量。代表显示不同的局部。

  99. unsigned char ucWd1Update=0; //窗口1更新显示标志
  100. unsigned char ucWd2Update=1; //窗口2更新显示标志

  101. unsigned char ucWd1Part1Update=0;  //在窗口1中,局部1的更新显示标志
  102. unsigned char ucWd1Part2Update=0; //在窗口1中,局部2的更新显示标志
  103. unsigned char ucWd1Part3Update=0; //在窗口1中,局部3的更新显示标志

  104. unsigned char ucWd2Part1Update=0;  //在窗口2中,局部1的更新显示标志
  105. unsigned char ucWd2Part2Update=0; //在窗口2中,局部2的更新显示标志
  106. unsigned char ucWd2Part3Update=0; //在窗口2中,局部3的更新显示标志

  107. unsigned char  ucYear=0;    //原始数据
  108. unsigned char  ucMonth=0;  
  109. unsigned char  ucDate=0;  
  110. unsigned char  ucHour=0;  
  111. unsigned char  ucMinute=0;  
  112. unsigned char  ucSecond=0;  

  113. unsigned char  ucYearBCD=0;   //BCD码的数据
  114. unsigned char  ucMonthBCD=0;  
  115. unsigned char  ucDateBCD=0;  
  116. unsigned char  ucHourBCD=0;  
  117. unsigned char  ucMinuteBCD=0;  
  118. unsigned char  ucSecondBCD=0;  

  119. unsigned char ucTemp1=0;  //中间过渡变量
  120. unsigned char ucTemp2=0;  //中间过渡变量

  121. unsigned char ucTemp4=0;  //中间过渡变量
  122. unsigned char ucTemp5=0;  //中间过渡变量

  123. unsigned char ucTemp7=0;  //中间过渡变量
  124. unsigned char ucTemp8=0;  //中间过渡变量

  125. unsigned char ucDelayTimerLock=0; //原子锁
  126. unsigned int  uiDelayTimer=0;

  127. unsigned char ucCheckDs1302=0;  //检查Ds1302芯片是否正常
  128. unsigned char ucDs1302Error=0; //Ds1302芯片的备用电池是否用完了的报警标志

  129. unsigned char ucDs1302Lock=0;//原子锁
  130. unsigned int  uiDs1302Cnt=0; //间歇性蜂鸣器报警的计时器

  131. unsigned char ucDpyTimeLock=0; //原子锁
  132. unsigned int  uiDpyTimeCnt=0;  //数码管的闪烁计时器,放在定时中断里不断累加

  133. //根据原理图得出的共阴数码管字模表
  134. code unsigned char dig_table[]=
  135. {
  136. 0x3f,  //0       序号0
  137. 0x06,  //1       序号1
  138. 0x5b,  //2       序号2
  139. 0x4f,  //3       序号3
  140. 0x66,  //4       序号4
  141. 0x6d,  //5       序号5
  142. 0x7d,  //6       序号6
  143. 0x07,  //7       序号7
  144. 0x7f,  //8       序号8
  145. 0x6f,  //9       序号9
  146. 0x00,  //无      序号10
  147. 0x40,  //-       序号11
  148. 0x73,  //P       序号12
  149. };
  150. void main()
  151.   {
  152.    initial_myself();  
  153.    delay_long(100);   
  154.    initial_peripheral();
  155.    while(1)  
  156.    {
  157.       key_service(); //按键服务的应用程序
  158.           ds1302_sampling(); //ds1302采样程序,内部每秒钟采集更新一次
  159.       display_service(); //显示的窗口菜单服务程序
  160.           ds1302_alarm_service(); //ds1302出错报警
  161.    }
  162. }


  163. /* 注释一:
  164.   * 系统不用时时刻刻采集ds1302的内部数据,每隔一段时间更新采集一次就可以了。
  165.   * 这个间隔时间应该小于或者等于1秒钟的时间,否则在数码管上看不到每秒钟的时间变化。
  166.   */
  167. void ds1302_sampling(void) //ds1302采样程序,内部每秒钟采集更新一次
  168. {
  169.    if(ucPart==0)  //当系统不是处于设置日期和时间的情况下
  170.    {
  171.       ++uiSampingCnt;  //累计主循环次数的时间
  172.       if(uiSampingCnt>const_ds1302_sampling_time)  //每隔一段时间就更新采集一次Ds1302数据
  173.           {

  174.           uiSampingCnt=0;

  175.   
  176.           ucYearBCD=Read1302(READ_YEAR); //读取年
  177.           ucMonthBCD=Read1302(READ_MONTH); //读取月
  178.           ucDateBCD=Read1302(READ_DATE); //读取日
  179.           ucHourBCD=Read1302(READ_HOUR); //读取时
  180.           ucMinuteBCD=Read1302(READ_MINUTE); //读取分
  181.           ucSecondBCD=Read1302(READ_SECOND); //读取秒


  182.                   ucYear=bcd_to_number(ucYearBCD);  //BCD转原始数值
  183.                   ucMonth=bcd_to_number(ucMonthBCD);  //BCD转原始数值
  184.                   ucDate=bcd_to_number(ucDateBCD);  //BCD转原始数值
  185.                   ucHour=bcd_to_number(ucHourBCD);  //BCD转原始数值
  186.                   ucMinute=bcd_to_number(ucMinuteBCD);  //BCD转原始数值
  187.                   ucSecond=bcd_to_number(ucSecondBCD);  //BCD转原始数值

  188.           ucWd2Update=1; //窗口2更新显示时间
  189.           }

  190.    }
  191. }

  192. //修改ds1302时间的驱动 ,注意,此处写入的是BCD码,
  193. void Write1302 ( unsigned char addr, unsigned char dat )
  194. {
  195.      unsigned char i,temp;         //单片机驱动DS1302属于SPI通讯方式,根据我的经验,不用关闭中断
  196.      DS1302_CE_dr=0;                                            //CE引脚为低,数据传送中止
  197.      delay_short(1);
  198.      SCLK_dr=0;                                                 //清零时钟总线
  199.      delay_short(1);
  200.      DS1302_CE_dr = 1;                                          //CE引脚为高,逻辑控制有效
  201.      delay_short(1);
  202.                                                              //发送地址
  203.      for ( i=0; i<8; i++ )                                   //循环8次移位
  204.      {
  205.         DIO_dr_sr = 0;
  206.         temp = addr;
  207.         if(temp&0x01)
  208.         {
  209.             DIO_dr_sr =1;
  210.         }
  211.         else
  212.         {
  213.             DIO_dr_sr =0;
  214.         }
  215.         delay_short(1);
  216.         addr >>= 1;                                           //右移一位

  217.         SCLK_dr = 1;
  218.         delay_short(1);
  219.         SCLK_dr = 0;
  220.         delay_short(1);
  221.      }

  222.                                                               //发送数据
  223.      for ( i=0; i<8; i++ )                                    //循环8次移位
  224.      {
  225.         DIO_dr_sr = 0;
  226.         temp = dat;
  227.         if(temp&0x01)
  228.         {
  229.             DIO_dr_sr =1;
  230.         }
  231.         else
  232.         {
  233.            DIO_dr_sr =0;
  234.         }
  235.         delay_short(1);
  236.         dat >>= 1;                                             //右移一位

  237.         SCLK_dr = 1;
  238.         delay_short(1);
  239.         SCLK_dr = 0;
  240.         delay_short(1);
  241.      }
  242.      DS1302_CE_dr = 0;
  243.      delay_short(1);
  244. }


  245. //读取Ds1302时间的驱动 ,注意,此处读取的是BCD码,
  246. unsigned char Read1302 ( unsigned char addr )
  247. {
  248.     unsigned char i,temp,dat1;
  249.     DS1302_CE_dr=0;      //单片机驱动DS1302属于SPI通讯方式,根据我的经验,不用关闭中断
  250.     delay_short(1);
  251.     SCLK_dr=0;
  252.     delay_short(1);
  253.     DS1302_CE_dr = 1;
  254.     delay_short(1);

  255.                                                                //发送地址
  256.     for ( i=0; i<8; i++ )                                      //循环8次移位
  257.     {
  258.        DIO_dr_sr = 0;

  259.        temp = addr;
  260.        if(temp&0x01)
  261.        {
  262.           DIO_dr_sr =1;
  263.        }
  264.        else
  265.        {
  266.           DIO_dr_sr =0;
  267.        }
  268.        delay_short(1);
  269.        addr >>= 1;                                             //右移一位

  270.        SCLK_dr = 1;
  271.        delay_short(1);
  272.        SCLK_dr = 0;
  273.        delay_short(1);
  274.     }
  275.                                                                
  276. /* 注释二:
  277.   * 51单片机IO口的特点,在读取数据之前必须先输出高电平,
  278.   * 如果是PIC,AVR等单片机,这里应该把IO方向寄存器设置为输入
  279.   */
  280.    DIO_dr_sr =1;   //51单片机IO口的特点,在读取数据之前必须先输出高电平,
  281.    temp=0;
  282.    for ( i=0; i<8; i++ )
  283.    {
  284.       temp>>=1;

  285.       if(DIO_dr_sr==1)
  286.       {
  287.          temp=temp+0x80;
  288.       }
  289.           DIO_dr_sr =1;  //51单片机IO口的特点,在读取数据之前必须先输出高电平

  290.       delay_short(1);
  291.       SCLK_dr = 1;
  292.       delay_short(1);
  293.       SCLK_dr = 0;
  294.       delay_short(1);
  295.     }
  296.     DS1302_CE_dr=0;
  297.     delay_short(1);
  298.     dat1=temp;

  299.     return (dat1);
  300. }

  301. unsigned char bcd_to_number(unsigned char ucBcdTemp)  //BCD转原始数值
  302. {
  303.    unsigned char ucNumberResult=0;
  304.    unsigned char ucBcdTemp10;
  305.    unsigned char ucBcdTemp1;

  306.    ucBcdTemp10=ucBcdTemp;
  307.    ucBcdTemp10=ucBcdTemp10>>4;

  308.    ucBcdTemp1=ucBcdTemp;
  309.    ucBcdTemp1=ucBcdTemp1&0x0f;


  310.    ucNumberResult=ucBcdTemp10*10+ucBcdTemp1;

  311.    return ucNumberResult;


  312. }

  313. unsigned char number_to_bcd(unsigned char ucNumberTemp) //原始数值转BCD
  314. {
  315.    unsigned char ucBcdResult=0;
  316.    unsigned char ucNumberTemp10;
  317.    unsigned char ucNumberTemp1;

  318.    ucNumberTemp10=ucNumberTemp;
  319.    ucNumberTemp10=ucNumberTemp10/10;
  320.    ucNumberTemp10=ucNumberTemp10<<4;
  321.    ucNumberTemp10=ucNumberTemp10&0xf0;

  322.    ucNumberTemp1=ucNumberTemp;
  323.    ucNumberTemp1=ucNumberTemp1%10;

  324.    ucBcdResult=ucNumberTemp10|ucNumberTemp1;

  325.    return ucBcdResult;

  326. }


  327. //日调整 每个月份的日最大取值不同,有的最大28日,有的最大29日,有的最大30,有的最大31
  328. unsigned char date_adjust(unsigned char ucYearTemp,unsigned char ucMonthTemp,unsigned char ucDateTemp) //日调整
  329. {


  330.    unsigned char ucDayResult;
  331.    unsigned int uiYearTemp;
  332.    unsigned int uiYearYu;
  333.    

  334.    ucDayResult=ucDateTemp;

  335.    switch(ucMonthTemp)  //根据不同的月份来修正不同的日最大值
  336.    {
  337.       case 2:  //二月份要计算是否是闰年
  338.            uiYearTemp=2000+ucYearTemp;  
  339.            uiYearYu=uiYearTemp%4;
  340.            if(uiYearYu==0) //闰年
  341.            {
  342.                if(ucDayResult>29)
  343.                {
  344.                   ucDayResult=29;
  345.                }
  346.            }
  347.            else
  348.            {
  349.                if(ucDayResult>28)
  350.                {
  351.                   ucDayResult=28;
  352.                }
  353.            }
  354.            break;
  355.       case 4:
  356.       case 6:
  357.       case 9:
  358.       case 11:
  359.            if(ucDayResult>30)
  360.            {
  361.               ucDayResult=30;
  362.            }
  363.            break;

  364.    }

  365.    return ucDayResult;

  366. }


  367. void ds1302_alarm_service(void) //ds1302出错报警
  368. {
  369.     if(ucDs1302Error==1)  //备用电池的电量用完了报警提示
  370.         {
  371.            if(uiDs1302Cnt>const_ds1302_0_5s)  //大概0.5秒钟蜂鸣器响一次
  372.            {
  373.                    ucDs1302Lock=1;  //原子锁加锁
  374.                uiDs1302Cnt=0; //计时器清零
  375.                    ucDs1302Lock=0;  //原子锁解锁

  376.                ucVoiceLock=1;  //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
  377.                uiVoiceCnt=const_voice_short; //蜂鸣器声音触发,滴一声就停。
  378.                ucVoiceLock=0;  //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt
  379.            
  380.            }
  381.    }
  382. }



  383. void display_service(void) //显示的窗口菜单服务程序
  384. {

  385.    switch(ucWd)  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
  386.    {
  387.        case 1:   //显示日期窗口的数据  数据格式 NN-YY-RR 年-月-日
  388.             if(ucWd1Update==1)  //窗口1要全部更新显示
  389.             {
  390.                ucWd1Update=0;  //及时清零标志,避免一直进来扫描

  391.                ucDigShow6=11;  //显示一杠"-"
  392.                ucDigShow3=11;  //显示一杠"-"

  393.                ucWd1Part1Update=1;  //局部年更新显示
  394.                ucWd1Part2Update=1;  //局部月更新显示
  395.                ucWd1Part3Update=1;  //局部日更新显示
  396.             }

  397.                         if(ucWd1Part1Update==1)//局部年更新显示
  398.                         {
  399.                            ucWd1Part1Update=0;
  400.                ucTemp8=ucYear/10;  //年
  401.                ucTemp7=ucYear%10;

  402.                ucDigShow8=ucTemp8; //数码管显示实际内容
  403.                ucDigShow7=ucTemp7;
  404.                         }


  405.                         if(ucWd1Part2Update==1)//局部月更新显示
  406.                         {
  407.                            ucWd1Part2Update=0;
  408.                ucTemp5=ucMonth/10;  //月
  409.                ucTemp4=ucMonth%10;

  410.                ucDigShow5=ucTemp5; //数码管显示实际内容
  411.                ucDigShow4=ucTemp4;
  412.                         }


  413.                         if(ucWd1Part3Update==1) //局部日更新显示
  414.                         {
  415.                            ucWd1Part3Update=0;
  416.                ucTemp2=ucDate/10;  //日
  417.                ucTemp1=ucDate%10;
  418.                        
  419.                ucDigShow2=ucTemp2; //数码管显示实际内容
  420.                ucDigShow1=ucTemp1;
  421.                         }
  422.               //数码管闪烁
  423.             switch(ucPart)  //相当于二级菜单,根据局部变量的值,使对应的参数产生闪烁的动态效果。
  424.             {
  425.                 case 0:  //都不闪烁
  426.                      break;
  427.                 case 1:  //年参数闪烁
  428.                      if(uiDpyTimeCnt==const_dpy_time_half)
  429.                      {
  430.                            ucDigShow8=ucTemp8; //数码管显示实际内容
  431.                            ucDigShow7=ucTemp7;
  432.                       }
  433.                      else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
  434.                      {
  435.                                            ucDpyTimeLock=1; //原子锁加锁
  436.                            uiDpyTimeCnt=0;   //及时把闪烁记时器清零
  437.                                                    ucDpyTimeLock=0;  //原子锁解锁

  438.                            ucDigShow8=10;   //数码管显示空,什么都不显示
  439.                            ucDigShow7=10;

  440.                      }
  441.                      break;
  442.                 case 2:   //月参数闪烁
  443.                      if(uiDpyTimeCnt==const_dpy_time_half)
  444.                      {
  445.                            ucDigShow5=ucTemp5; //数码管显示实际内容
  446.                            ucDigShow4=ucTemp4;
  447.                       }
  448.                      else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
  449.                      {
  450.                                            ucDpyTimeLock=1; //原子锁加锁
  451.                            uiDpyTimeCnt=0;   //及时把闪烁记时器清零
  452.                                                    ucDpyTimeLock=0;  //原子锁解锁

  453.                            ucDigShow5=10;   //数码管显示空,什么都不显示
  454.                            ucDigShow4=10;

  455.                      }
  456.                     break;
  457.                 case 3:   //日参数闪烁
  458.                      if(uiDpyTimeCnt==const_dpy_time_half)
  459.                      {
  460.                            ucDigShow2=ucTemp2; //数码管显示实际内容
  461.                            ucDigShow1=ucTemp1;
  462.                       }
  463.                      else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
  464.                      {
  465.                                            ucDpyTimeLock=1; //原子锁加锁
  466.                            uiDpyTimeCnt=0;   //及时把闪烁记时器清零
  467.                                                    ucDpyTimeLock=0;  //原子锁解锁

  468.                            ucDigShow2=10;   //数码管显示空,什么都不显示
  469.                            ucDigShow1=10;

  470.                      }
  471.                     break;      
  472.             }

  473.             break;
  474.        case 2:   //显示时间窗口的数据  数据格式 SS FF MM 时 分 秒
  475.             if(ucWd2Update==1)  //窗口2要全部更新显示
  476.             {
  477.                ucWd2Update=0;  //及时清零标志,避免一直进来扫描

  478.                ucDigShow6=10;  //显示空
  479.                ucDigShow3=10;  //显示空

  480.                ucWd2Part3Update=1;  //局部时更新显示
  481.                ucWd2Part2Update=1;  //局部分更新显示
  482.                ucWd2Part1Update=1;  //局部秒更新显示
  483.             }

  484.                         if(ucWd2Part1Update==1)//局部时更新显示
  485.                         {
  486.                            ucWd2Part1Update=0;
  487.                ucTemp8=ucHour/10;  //时
  488.                ucTemp7=ucHour%10;

  489.                ucDigShow8=ucTemp8; //数码管显示实际内容
  490.                ucDigShow7=ucTemp7;
  491.                         }


  492.                         if(ucWd2Part2Update==1)//局部分更新显示
  493.                         {
  494.                            ucWd2Part2Update=0;
  495.                ucTemp5=ucMinute/10;  //分
  496.                ucTemp4=ucMinute%10;

  497.                ucDigShow5=ucTemp5; //数码管显示实际内容
  498.                ucDigShow4=ucTemp4;
  499.                         }


  500.                         if(ucWd2Part3Update==1) //局部秒更新显示
  501.                         {
  502.                            ucWd2Part3Update=0;
  503.                ucTemp2=ucSecond/10;  //秒
  504.                ucTemp1=ucSecond%10;               
  505.        
  506.                ucDigShow2=ucTemp2; //数码管显示实际内容
  507.                ucDigShow1=ucTemp1;
  508.                         }
  509.               //数码管闪烁
  510.             switch(ucPart)  //相当于二级菜单,根据局部变量的值,使对应的参数产生闪烁的动态效果。
  511.             {
  512.                 case 0:  //都不闪烁
  513.                      break;
  514.                 case 1:  //时参数闪烁
  515.                      if(uiDpyTimeCnt==const_dpy_time_half)
  516.                      {
  517.                            ucDigShow8=ucTemp8; //数码管显示实际内容
  518.                            ucDigShow7=ucTemp7;
  519.                       }
  520.                      else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
  521.                      {
  522.                                            ucDpyTimeLock=1; //原子锁加锁
  523.                            uiDpyTimeCnt=0;   //及时把闪烁记时器清零
  524.                                                    ucDpyTimeLock=0;  //原子锁解锁

  525.                            ucDigShow8=10;   //数码管显示空,什么都不显示
  526.                            ucDigShow7=10;

  527.                      }
  528.                      break;
  529.                 case 2:   //分参数闪烁
  530.                      if(uiDpyTimeCnt==const_dpy_time_half)
  531.                      {
  532.                            ucDigShow5=ucTemp5; //数码管显示实际内容
  533.                            ucDigShow4=ucTemp4;
  534.                       }
  535.                      else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
  536.                      {
  537.                                            ucDpyTimeLock=1; //原子锁加锁
  538.                            uiDpyTimeCnt=0;   //及时把闪烁记时器清零
  539.                                                    ucDpyTimeLock=0;  //原子锁解锁

  540.                            ucDigShow5=10;   //数码管显示空,什么都不显示
  541.                            ucDigShow4=10;

  542.                      }
  543.                     break;
  544.                 case 3:   //秒参数闪烁
  545.                      if(uiDpyTimeCnt==const_dpy_time_half)
  546.                      {
  547.                            ucDigShow2=ucTemp2; //数码管显示实际内容
  548.                            ucDigShow1=ucTemp1;
  549.                       }
  550.                      else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
  551.                      {
  552.                                            ucDpyTimeLock=1; //原子锁加锁
  553.                            uiDpyTimeCnt=0;   //及时把闪烁记时器清零
  554.                                                    ucDpyTimeLock=0;  //原子锁解锁

  555.                            ucDigShow2=10;   //数码管显示空,什么都不显示
  556.                            ucDigShow1=10;

  557.                      }
  558.                     break;      
  559.             }


  560.             break;
  561.       }
  562.    

  563. }

  564. void key_scan(void)//按键扫描函数 放在定时中断里
  565. {  
  566.   if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  567.   {
  568.      ucKeyLock1=0; //按键自锁标志清零
  569.      uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  570.   }
  571.   else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
  572.   {
  573.      uiKeyTimeCnt1++; //累加定时中断次数
  574.      if(uiKeyTimeCnt1>const_key_time1)
  575.      {
  576.         uiKeyTimeCnt1=0;
  577.         ucKeyLock1=1;  //自锁按键置位,避免一直触发
  578.         ucKeySec=1;    //触发1号键
  579.      }
  580.   }

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



  596. /* 注释三:
  597.   * 注意,此处把一个按键的短按和长按的功能都实现了。
  598.   */

  599.   if(key_sr3==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  600.   {
  601.      ucKeyLock3=0; //按键自锁标志清零
  602.      uiKeyTimeCnt3=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  603.   }
  604.   else if(ucKeyLock3==0)//有按键按下,且是第一次被按下
  605.   {
  606.      uiKeyTimeCnt3++; //累加定时中断次数
  607.      if(uiKeyTimeCnt3>const_key_time3)
  608.      {
  609.         uiKeyTimeCnt3=0;
  610.         ucKeyLock3=1;  //自锁按键置位,避免一直触发
  611.         ucKeySec=3;    //短按触发3号键
  612.      }
  613.   }
  614.   else if(uiKeyTimeCnt3<const_key_time17)   //长按3秒
  615.   {
  616.      uiKeyTimeCnt3++; //累加定时中断次数
  617.          if(uiKeyTimeCnt3==const_key_time17)  //等于3秒钟,触发17号长按按键
  618.          {
  619.             ucKeySec=17;    //长按3秒触发17号键  
  620.          }
  621.   }


  622. /* 注释四:
  623.   * 注意,此处是电平按键的滤波抗干扰处理
  624.   */
  625.    if(key_sr4==1)  //对应朱兆祺学习板的S13键  
  626.    {
  627.        uiKey4Cnt1=0; //在软件滤波中,非常关键的语句!!!类似按键去抖动程序的及时清零
  628.        uiKey4Cnt2++; //类似独立按键去抖动的软件抗干扰处理
  629.        if(uiKey4Cnt2>const_key_time4)
  630.        {
  631.            uiKey4Cnt2=0;
  632.            ucKey4Sr=1;  //实时反映按键松手时的电平状态
  633.        }
  634.    }
  635.    else   
  636.    {
  637.        uiKey4Cnt2=0; //在软件滤波中,非常关键的语句!!!类似按键去抖动程序的及时清零
  638.        uiKey4Cnt1++;
  639.        if(uiKey4Cnt1>const_key_time4)
  640.        {
  641.           uiKey4Cnt1=0;
  642.           ucKey4Sr=0;  //实时反映按键按下时的电平状态
  643.        }
  644.    }


  645. }

  646. void key_service(void) //按键服务的应用程序
  647. {

  648.   switch(ucKeySec) //按键服务状态切换
  649.   {
  650.     case 1:// 加按键 对应朱兆祺学习板的S1键
  651.           switch(ucWd)  //在不同的窗口下,设置不同的参数
  652.           {
  653.                case 1:
  654.                     switch(ucPart) //在不同的局部变量下,相当于二级菜单
  655.                                         {
  656.                                            case 1:  //年
  657.                                                 ucYear++;
  658.                                                         if(ucYear>99)
  659.                                                         {
  660.                                                            ucYear=99;
  661.                                                         }
  662.                                         ucWd1Part1Update=1;  //更新显示
  663.                                                 break;
  664.                                            case 2: //月
  665.                                                 ucMonth++;
  666.                                                         if(ucMonth>12)
  667.                                                         {
  668.                                                            ucMonth=12;
  669.                                                         }
  670.                                         ucWd1Part2Update=1;  //更新显示                                               
  671.                                                 break;
  672.                                            case 3: //日
  673.                                                 ucDate++;
  674.                                                         if(ucDate>31)
  675.                                                         {
  676.                                                            ucDate=31;
  677.                                                         }
  678.                                         ucWd1Part3Update=1;  //更新显示               
  679.                                                 break;                                       

  680.                                         }


  681.                     break;
  682.                case 2:
  683.                     switch(ucPart) //在不同的局部变量下,相当于二级菜单
  684.                                         {
  685.                                            case 1:  //时
  686.                                                 ucHour++;
  687.                                                         if(ucHour>23)
  688.                                                         {
  689.                                                            ucHour=23;
  690.                                                         }
  691.                                         ucWd2Part1Update=1;  //更新显示                                               
  692.                                                 break;
  693.                                            case 2: //分
  694.                                                 ucMinute++;
  695.                                                         if(ucMinute>59)
  696.                                                         {
  697.                                                            ucMinute=59;
  698.                                                         }
  699.                                         ucWd2Part2Update=1;  //更新显示                                                       
  700.                                                 break;
  701.                                            case 3: //秒
  702.                                                 ucSecond++;
  703.                                                         if(ucSecond>59)
  704.                                                         {
  705.                                                            ucSecond=59;
  706.                                                         }
  707.                                         ucWd2Part3Update=1;  //更新显示       
  708.                                                 break;                                       

  709.                                         }
  710.                     break;
  711.          
  712.           }

  713.           ucVoiceLock=1;  //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
  714.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  715.           ucVoiceLock=0;  //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt

  716.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  717.           break;   
  718.    
  719.     case 2:// 减按键 对应朱兆祺学习板的S5键
  720.           switch(ucWd)  //在不同的窗口下,设置不同的参数
  721.           {
  722.                case 1:
  723.                     switch(ucPart) //在不同的局部变量下,相当于二级菜单
  724.                                         {
  725.                                            case 1:  //年
  726.                                                 ucYear--;
  727.                                                         if(ucYear>99)
  728.                                                         {
  729.                                                            ucYear=0;
  730.                                                         }
  731.                                         ucWd1Part1Update=1;  //更新显示
  732.                                                 break;
  733.                                            case 2: //月
  734.                                                 ucMonth--;
  735.                                                         if(ucMonth<1)
  736.                                                         {
  737.                                                            ucMonth=1;
  738.                                                         }
  739.                                         ucWd1Part2Update=1;  //更新显示                                               
  740.                                                 break;
  741.                                            case 3: //日
  742.                                                 ucDate--;
  743.                                                         if(ucDate<1)
  744.                                                         {
  745.                                                            ucDate=1;
  746.                                                         }
  747.                                         ucWd1Part3Update=1;  //更新显示               
  748.                                                 break;                                       

  749.                                         }


  750.                     break;
  751.                case 2:
  752.                     switch(ucPart) //在不同的局部变量下,相当于二级菜单
  753.                                         {
  754.                                            case 1:  //时
  755.                                                 ucHour--;
  756.                                                         if(ucHour>23)
  757.                                                         {
  758.                                                            ucHour=0;
  759.                                                         }
  760.                                         ucWd2Part1Update=1;  //更新显示                                               
  761.                                                 break;
  762.                                            case 2: //分
  763.                                                 ucMinute--;
  764.                                                         if(ucMinute>59)
  765.                                                         {
  766.                                                            ucMinute=0;
  767.                                                         }
  768.                                         ucWd2Part2Update=1;  //更新显示                                                       
  769.                                                 break;
  770.                                            case 3: //秒
  771.                                                 ucSecond--;
  772.                                                         if(ucSecond>59)
  773.                                                         {
  774.                                                            ucSecond=0;
  775.                                                         }
  776.                                         ucWd2Part3Update=1;  //更新显示       
  777.                                                 break;                                       

  778.                                         }
  779.                     break;
  780.          
  781.           }

  782.           ucVoiceLock=1;  //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
  783.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  784.           ucVoiceLock=0;  //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt

  785.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  786.           break;  

  787.     case 3://短按设置按键 对应朱兆祺学习板的S9键
  788.           switch(ucWd)  //在不同的窗口下,设置不同的参数
  789.           {
  790.                case 1:
  791.                     ucPart++;
  792.                                         if(ucPart>3)
  793.                                         {
  794.                                            ucPart=1;
  795.                                            ucWd=2; //切换到第二个窗口,设置时分秒
  796.                                            ucWd2Update=1;  //窗口2更新显示
  797.                                         }
  798.                                     ucWd1Update=1;  //窗口1更新显示
  799.                     break;
  800.                case 2:
  801.                                 if(ucPart>0) //在窗口2的时候,要第一次激活设置时间,必须是长按3秒才可以,这里短按激活不了第一次
  802.                                         {
  803.                        ucPart++;
  804.                                               if(ucPart>3)  //设置时间结束
  805.                                            {
  806.                                                ucPart=0;



  807. /* 注释五:
  808.   * 每个月份的天数最大值是不一样的,在写入ds1302时钟芯片内部数据前,应该做一次调整。
  809.   * 有的月份最大28天,有的月份最大29天,有的月份最大30天,有的月份最大31天,
  810.   */                                                  
  811.                            ucDate=date_adjust(ucYear,ucMonth,ucDate); //日调整 避免日的数值在某个月份超范围

  812.                                    ucYearBCD=number_to_bcd(ucYear);  //原始数值转BCD
  813.                                    ucMonthBCD=number_to_bcd(ucMonth); //原始数值转BCD
  814.                                      ucDateBCD=number_to_bcd(ucDate);  //原始数值转BCD
  815.                                    ucHourBCD=number_to_bcd(ucHour);  //原始数值转BCD
  816.                                    ucMinuteBCD=number_to_bcd(ucMinute);  //原始数值转BCD
  817.                                    ucSecondBCD=number_to_bcd(ucSecond);  //原始数值转BCD

  818.                                                    Write1302 (WRITE_PROTECT,0X00);          //禁止写保护
  819.                            Write1302 (WRITE_YEAR,ucYearBCD);        //年修改
  820.                            Write1302 (WRITE_MONTH,ucMonthBCD);      //月修改
  821.                            Write1302 (WRITE_DATE,ucDateBCD);        //日修改
  822.                            Write1302 (WRITE_HOUR,ucHourBCD);        //小时修改
  823.                            Write1302 (WRITE_MINUTE,ucMinuteBCD);    //分钟修改
  824.                            Write1302 (WRITE_SECOND,ucSecondBCD);    //秒位修改
  825.                            Write1302 (WRITE_PROTECT,0x80);          //允许写保护
  826.                                              }
  827.                                               ucWd2Update=1;  //窗口2更新显示
  828.                                         }

  829.                     break;
  830.          
  831.           }

  832.          
  833.           ucVoiceLock=1;  //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
  834.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  835.           ucVoiceLock=0;  //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt

  836.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  837.           break;         
  838.     case 17://长按3秒设置按键 对应朱兆祺学习板的S9键
  839.           switch(ucWd)  //在不同的窗口下,设置不同的参数
  840.           {
  841.                case 2:
  842.                                 if(ucPart==0) //处于非设置时间的状态下,要第一次激活设置时间,必须是长按3秒才可以
  843.                                         {
  844.                                             ucWd=1;
  845.                        ucPart=1;  //进入到设置日期的状态下
  846.                                             ucWd1Update=1;  //窗口1更新显示
  847.                                         }
  848.                     break;
  849.          
  850.           }
  851.           ucVoiceLock=1;  //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
  852.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  853.           ucVoiceLock=0;  //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt

  854.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  855.           break;   
  856.          
  857.   }         
  858.   

  859. /* 注释六:
  860.   * 注意,此处就是第一次出现的电平按键程序,跟以往的下降沿按键不一样。
  861.   * ucKey4Sr是经过软件滤波处理后,直接反应IO口电平状态的变量.当电平发生
  862.   * 变化时,就会切换到不同的显示界面,这里多用了一个ucKey4SrRecord变量
  863.   * 记录上一次的电平状态,是为了避免一直刷新显示。
  864.   */
  865.   if(ucKey4Sr!=ucKey4SrRecord)  //说明S13的切换按键电平状态发生变化
  866.   {
  867.      ucKey4SrRecord=ucKey4Sr;//及时记录当前最新的按键电平状态  避免一直进来触发

  868.          if(ucKey4Sr==1) //松手后切换到显示时间的窗口
  869.          {
  870.             ucWd=2;    //显示时分秒的窗口
  871.                 ucPart=0;  //进入到非设置时间的状态下
  872.             ucWd2Update=1;  //窗口2更新显示
  873.          }
  874.          else  //按下去切换到显示日期的窗口
  875.          {
  876.             ucWd=1;   //显示年月日的窗口
  877.                 ucPart=0;  //进入到非设置时间的状态下
  878.             ucWd1Update=1;  //窗口1更新显示
  879.          }
  880.   
  881.   }
  882. }

  883. void display_drive(void)  
  884. {
  885.    //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
  886.    switch(ucDisplayDriveStep)
  887.    {
  888.       case 1:  //显示第1位
  889.            ucDigShowTemp=dig_table[ucDigShow1];
  890.                    if(ucDigDot1==1)
  891.                    {
  892.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  893.                    }
  894.            dig_hc595_drive(ucDigShowTemp,0xfe);
  895.                break;
  896.       case 2:  //显示第2位
  897.            ucDigShowTemp=dig_table[ucDigShow2];
  898.                    if(ucDigDot2==1)
  899.                    {
  900.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  901.                    }
  902.            dig_hc595_drive(ucDigShowTemp,0xfd);
  903.                break;
  904.       case 3:  //显示第3位
  905.            ucDigShowTemp=dig_table[ucDigShow3];
  906.                    if(ucDigDot3==1)
  907.                    {
  908.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  909.                    }
  910.            dig_hc595_drive(ucDigShowTemp,0xfb);
  911.                break;
  912.       case 4:  //显示第4位
  913.            ucDigShowTemp=dig_table[ucDigShow4];
  914.                    if(ucDigDot4==1)
  915.                    {
  916.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  917.                    }
  918.            dig_hc595_drive(ucDigShowTemp,0xf7);
  919.                break;
  920.       case 5:  //显示第5位
  921.            ucDigShowTemp=dig_table[ucDigShow5];
  922.                    if(ucDigDot5==1)
  923.                    {
  924.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  925.                    }
  926.            dig_hc595_drive(ucDigShowTemp,0xef);
  927.                break;
  928.       case 6:  //显示第6位
  929.            ucDigShowTemp=dig_table[ucDigShow6];
  930.                    if(ucDigDot6==1)
  931.                    {
  932.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  933.                    }
  934.            dig_hc595_drive(ucDigShowTemp,0xdf);
  935.                break;
  936.       case 7:  //显示第7位
  937.            ucDigShowTemp=dig_table[ucDigShow7];
  938.                    if(ucDigDot7==1)
  939.                    {
  940.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  941.            }
  942.            dig_hc595_drive(ucDigShowTemp,0xbf);
  943.                break;
  944.       case 8:  //显示第8位
  945.            ucDigShowTemp=dig_table[ucDigShow8];
  946.                    if(ucDigDot8==1)
  947.                    {
  948.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  949.                    }
  950.            dig_hc595_drive(ucDigShowTemp,0x7f);
  951.                break;
  952.    }
  953.    ucDisplayDriveStep++;
  954.    if(ucDisplayDriveStep>8)  //扫描完8个数码管后,重新从第一个开始扫描
  955.    {
  956.      ucDisplayDriveStep=1;
  957.    }

  958. }

  959. //数码管的74HC595驱动函数
  960. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
  961. {
  962.    unsigned char i;
  963.    unsigned char ucTempData;
  964.    dig_hc595_sh_dr=0;
  965.    dig_hc595_st_dr=0;
  966.    ucTempData=ucDigStatusTemp16_09;  //先送高8位
  967.    for(i=0;i<8;i++)
  968.    {
  969.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  970.          else dig_hc595_ds_dr=0;
  971.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  972.          delay_short(1);
  973.          dig_hc595_sh_dr=1;
  974.          delay_short(1);
  975.          ucTempData=ucTempData<<1;
  976.    }
  977.    ucTempData=ucDigStatusTemp08_01;  //再先送低8位
  978.    for(i=0;i<8;i++)
  979.    {
  980.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  981.          else dig_hc595_ds_dr=0;
  982.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  983.          delay_short(1);
  984.          dig_hc595_sh_dr=1;
  985.          delay_short(1);
  986.          ucTempData=ucTempData<<1;
  987.    }
  988.    dig_hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  989.    delay_short(1);
  990.    dig_hc595_st_dr=1;
  991.    delay_short(1);
  992.    dig_hc595_sh_dr=0;    //拉低,抗干扰就增强
  993.    dig_hc595_st_dr=0;
  994.    dig_hc595_ds_dr=0;
  995. }

  996. //LED灯的74HC595驱动函数
  997. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
  998. {
  999.    unsigned char i;
  1000.    unsigned char ucTempData;
  1001.    hc595_sh_dr=0;
  1002.    hc595_st_dr=0;
  1003.    ucTempData=ucLedStatusTemp16_09;  //先送高8位
  1004.    for(i=0;i<8;i++)
  1005.    {
  1006.          if(ucTempData>=0x80)hc595_ds_dr=1;
  1007.          else hc595_ds_dr=0;
  1008.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  1009.          delay_short(1);
  1010.          hc595_sh_dr=1;
  1011.          delay_short(1);
  1012.          ucTempData=ucTempData<<1;
  1013.    }
  1014.    ucTempData=ucLedStatusTemp08_01;  //再先送低8位
  1015.    for(i=0;i<8;i++)
  1016.    {
  1017.          if(ucTempData>=0x80)hc595_ds_dr=1;
  1018.          else hc595_ds_dr=0;
  1019.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  1020.          delay_short(1);
  1021.          hc595_sh_dr=1;
  1022.          delay_short(1);
  1023.          ucTempData=ucTempData<<1;
  1024.    }
  1025.    hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  1026.    delay_short(1);
  1027.    hc595_st_dr=1;
  1028.    delay_short(1);
  1029.    hc595_sh_dr=0;    //拉低,抗干扰就增强
  1030.    hc595_st_dr=0;
  1031.    hc595_ds_dr=0;
  1032. }


  1033. void T0_time(void) interrupt 1   //定时中断
  1034. {
  1035.   TF0=0;  //清除中断标志
  1036.   TR0=0; //关中断


  1037.   if(ucVoiceLock==0) //原子锁判断
  1038.   {
  1039.      if(uiVoiceCnt!=0)
  1040.      {

  1041.         uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
  1042.         beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  1043.      
  1044.      }
  1045.      else
  1046.      {

  1047.         ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
  1048.         beep_dr=1;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  1049.         
  1050.      }
  1051.   }




  1052.   if(ucDs1302Error>0) //EEPROM出错
  1053.   {
  1054.       if(ucDs1302Lock==0)//原子锁判断
  1055.           {
  1056.              uiDs1302Cnt++;  //间歇性蜂鸣器报警的计时器
  1057.           }
  1058.   }


  1059.   if(ucDpyTimeLock==0) //原子锁判断
  1060.   {
  1061.      uiDpyTimeCnt++;  //数码管的闪烁计时器
  1062.   }



  1063.   key_scan(); //按键扫描函数
  1064.   display_drive();  //数码管字模的驱动函数

  1065.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  1066.   TL0=0x0b;
  1067.   TR0=1;  //开中断
  1068. }

  1069. void delay_short(unsigned int uiDelayShort)
  1070. {
  1071.    unsigned int i;  
  1072.    for(i=0;i<uiDelayShort;i++)
  1073.    {
  1074.      ;   //一个分号相当于执行一条空语句
  1075.    }
  1076. }

  1077. void delay_long(unsigned int uiDelayLong)
  1078. {
  1079.    unsigned int i;
  1080.    unsigned int j;
  1081.    for(i=0;i<uiDelayLong;i++)
  1082.    {
  1083.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  1084.           {
  1085.              ; //一个分号相当于执行一条空语句
  1086.           }
  1087.    }
  1088. }

  1089. void initial_myself(void)  //第一区 初始化单片机
  1090. {

  1091.   key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平
  1092.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
  1093.   hc595_drive(0x00,0x00);  //关闭所有经过另外两个74HC595驱动的LED灯
  1094.   TMOD=0x01;  //设置定时器0为工作方式1
  1095.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  1096.   TL0=0x0b;

  1097. }
  1098. void initial_peripheral(void) //第二区 初始化外围
  1099. {

  1100.    ucDigDot8=0;   //小数点全部不显示
  1101.    ucDigDot7=0;  
  1102.    ucDigDot6=0;
  1103.    ucDigDot5=0;  
  1104.    ucDigDot4=0;
  1105.    ucDigDot3=0;  
  1106.    ucDigDot2=0;
  1107.    ucDigDot1=0;

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


  1111. /* 注释七:
  1112.   * 检查ds1302芯片的备用电池电量是否用完了。
  1113.   * 在上电的时候,在一个特定的地址里把数据读出来,如果发现不等于0x5a,
  1114.   * 则是因为备用电池电量用完了,导致保存在ds1302内部RAM数据区的数据被更改,
  1115.   * 与此同时,应该重新写入一次0x5a,为下一次通电判断做准备
  1116.   */
  1117.    ucCheckDs1302=Read1302(READ_CHECK); //判断ds1302内部的数据是否被更改
  1118.    if(ucCheckDs1302!=0x5a)  
  1119.    {
  1120.           Write1302 (WRITE_PROTECT,0X00);          //禁止写保护
  1121.       Write1302 (WRITE_CHECK,0x5a);            //重新写入标志数据,方便下一次更换新电池后的判断
  1122.       Write1302 (WRITE_PROTECT,0x80);          //允许写保护

  1123.           ucDs1302Error=1;  //表示ds1302备用电池没电了,报警提示更换新电池
  1124.    }


  1125. }
复制代码

总结陈词:
下一节开始讲单片机驱动温度传感器芯片的内容,欲知详情,请听下回分解-----利用DS18B20做一个温控器  。

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

作者: jianhong_wu    时间: 2014-5-28 12:31
第四十九节:利用DS18B20做一个温控器  。

开场白:
      DS18B20是一款常用的温度传感器芯片,它只占用单片机一根IO口,使用起来也特别方便。需要特别注意的是,正因为它只用一根IO口跟单片机通讯,因此读取一次温度值的通讯时间比较长,而且时序要求严格,在通讯期间不允许被单片机其它的中断干扰,因此在实际项目中,系统一旦选用了这款传感器芯片,就千万不要选用动态扫描数码管的显示方式。否则在关闭中断读取温度的时候,数码管的显示会有略微的“闪烁”现象。
      DS18B20的测温范围是-55度至125度。在-10度至85度的温度范围内误差是+-0.5度,能满足大部分常用的测温要求。
这一节要教会大家三个知识点:
第一个:大概了解一下DS18B20的驱动程序。
第二个:做温控设备的时候,为了避免继电器在临界温度附近频繁跳动切换,应该设置一个缓冲温差。本程序的缓冲温差是2度。
第三个:继续加深了解按键,显示,传感器它们三者是如何紧密关联起来的程序框架。

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

(1)        硬件平台.
基于坚鸿51单片机学习板。

(2)实现功能:
     本程序只有1个窗口。这个窗口有2个局部显示。
第1个局部是第7,6,5位数码管,显示设定的温度。
第2个局部是第4,3,2,1位数码管,显示实际环境温度。其中第4位数码管显示正负符号位。
S1按键是加键,S5按键是减键。通过它们可以直接设置“设定温度”。
一个LED灯用来模拟工控的继电器。
当实际温度低于或者等于设定温度2度以下时,模拟继电器的LED灯亮。
当实际温度等于或者大于设定温度时,模拟继电器的LED灯灭。
当实际温度处于设定温度和设定温度减去2度的范围内,模拟继电器的LED维持现状,这个2度范围用来做缓冲温差,避免继电器在临界温度附近频繁跳动切换。

(3)源代码讲解如下:
  1. #include "REG52.H"


  2. #define const_voice_short  40   //蜂鸣器短叫的持续时间
  3. #define const_key_time1  20    //按键去抖动延时的时间
  4. #define const_key_time2  20    //按键去抖动延时的时间

  5. #define const_ds18b20_sampling_time    180   //累计主循环次数的时间,每次刷新采样时钟芯片的时间


  6. void initial_myself(void);   
  7. void initial_peripheral(void);
  8. void delay_short(unsigned int uiDelayShort);
  9. void delay_long(unsigned int uiDelaylong);


  10. //驱动数码管的74HC595
  11. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);  
  12. void display_drive(void); //显示数码管字模的驱动函数
  13. void display_service(void); //显示的窗口菜单服务程序
  14. //驱动LED的74HC595
  15. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);

  16. void T0_time(void);  //定时中断函数

  17. void key_service(void); //按键服务的应用程序
  18. void key_scan(void);//按键扫描函数 放在定时中断里

  19. void temper_control_service(void); //温控程序
  20. void ds18b20_sampling(void); //ds18b20采样程序

  21. void ds18b20_reset(); //复位ds18b20的时序
  22. unsigned char ds_read_byte(void ); //读一字节
  23. void ds_write_byte(unsigned char dat); //写一个字节
  24. unsigned int get_temper();  //读取一次没有经过换算的温度数值

  25. sbit dq_dr_sr=P2^6; //ds18b20的数据驱动线

  26. sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
  27. sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键

  28. sbit led_dr=P3^5;  //LED灯,模拟工控中的继电器

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

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



  31. sbit dig_hc595_sh_dr=P2^0;     //数码管的74HC595程序
  32. sbit dig_hc595_st_dr=P2^1;  
  33. sbit dig_hc595_ds_dr=P2^2;  
  34. sbit hc595_sh_dr=P2^3;    //LED灯的74HC595程序
  35. sbit hc595_st_dr=P2^4;  
  36. sbit hc595_ds_dr=P2^5;  


  37. unsigned int uiSampingCnt=0;   //采集Ds1302的计时器,每秒钟更新采集一次
  38. unsigned char ucSignFlag=0; //正负符号。0代表正数,1代表负数,表示零下多少度。
  39. unsigned long ulCurrentTemper=33; //实际温度
  40. unsigned long ulSetTemper=26; //设定温度

  41. unsigned int uiTemperTemp=0; //中间变量

  42. unsigned char ucKeySec=0;   //被触发的按键编号

  43. unsigned int  uiKeyTimeCnt1=0; //按键去抖动延时计数器
  44. unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志
  45. unsigned int  uiKeyTimeCnt2=0; //按键去抖动延时计数器
  46. unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志

  47. unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器
  48. unsigned char  ucVoiceLock=0;  //蜂鸣器鸣叫的原子锁

  49. unsigned char ucDigShow8;  //第8位数码管要显示的内容
  50. unsigned char ucDigShow7;  //第7位数码管要显示的内容
  51. unsigned char ucDigShow6;  //第6位数码管要显示的内容
  52. unsigned char ucDigShow5;  //第5位数码管要显示的内容
  53. unsigned char ucDigShow4;  //第4位数码管要显示的内容
  54. unsigned char ucDigShow3;  //第3位数码管要显示的内容
  55. unsigned char ucDigShow2;  //第2位数码管要显示的内容
  56. unsigned char ucDigShow1;  //第1位数码管要显示的内容

  57. unsigned char ucDigDot8;  //数码管8的小数点是否显示的标志
  58. unsigned char ucDigDot7;  //数码管7的小数点是否显示的标志
  59. unsigned char ucDigDot6;  //数码管6的小数点是否显示的标志
  60. unsigned char ucDigDot5;  //数码管5的小数点是否显示的标志
  61. unsigned char ucDigDot4;  //数码管4的小数点是否显示的标志
  62. unsigned char ucDigDot3;  //数码管3的小数点是否显示的标志
  63. unsigned char ucDigDot2;  //数码管2的小数点是否显示的标志
  64. unsigned char ucDigDot1;  //数码管1的小数点是否显示的标志
  65. unsigned char ucDigShowTemp=0; //临时中间变量
  66. unsigned char ucDisplayDriveStep=1;  //动态扫描数码管的步骤变量


  67. unsigned char ucWd=1;  //因为本程序只有1个窗口,在实际项目中,此处的ucWd也可以省略不要

  68. unsigned char ucWd1Part1Update=1;  //在窗口1中,局部1的更新显示标志
  69. unsigned char ucWd1Part2Update=1; //在窗口1中,局部2的更新显示标志


  70. unsigned char ucTemp1=0;  //中间过渡变量
  71. unsigned char ucTemp2=0;  //中间过渡变量
  72. unsigned char ucTemp3=0;  //中间过渡变量
  73. unsigned char ucTemp4=0;  //中间过渡变量
  74. unsigned char ucTemp5=0;  //中间过渡变量
  75. unsigned char ucTemp6=0;  //中间过渡变量
  76. unsigned char ucTemp7=0;  //中间过渡变量
  77. unsigned char ucTemp8=0;  //中间过渡变量


  78. //根据原理图得出的共阴数码管字模表
  79. code unsigned char dig_table[]=
  80. {
  81. 0x3f,  //0       序号0
  82. 0x06,  //1       序号1
  83. 0x5b,  //2       序号2
  84. 0x4f,  //3       序号3
  85. 0x66,  //4       序号4
  86. 0x6d,  //5       序号5
  87. 0x7d,  //6       序号6
  88. 0x07,  //7       序号7
  89. 0x7f,  //8       序号8
  90. 0x6f,  //9       序号9
  91. 0x00,  //无      序号10
  92. 0x40,  //-       序号11
  93. 0x73,  //P       序号12
  94. };
  95. void main()
  96.   {
  97.    initial_myself();  
  98.    delay_long(100);   
  99.    initial_peripheral();
  100.    while(1)  
  101.    {
  102.       key_service(); //按键服务的应用程序
  103.       ds18b20_sampling(); //ds18b20采样程序
  104.       temper_control_service(); //温控程序
  105.       display_service(); //显示的窗口菜单服务程序
  106.    }
  107. }

  108. /* 注释一:
  109.   * 做温控设备的时候,为了避免继电器在临界温度附近频繁跳动切换,应该设置一个
  110.   * 缓冲温差。本程序的缓冲温差是2度。
  111.   */
  112. void temper_control_service(void) //温控程序
  113. {
  114.    if(ucSignFlag==0) //是正数的前提下
  115.    {
  116.       if(ulCurrentTemper>=ulSetTemper)  //当实际温度大于等于设定温度时
  117.       {
  118.         led_dr=0; //模拟继电器的LED灯熄灭
  119.       }
  120.       else if(ulCurrentTemper<=(ulSetTemper-2))  //当实际温度小于等于设定温度2读以下时,这里的2是缓冲温差2度
  121.       {
  122.         led_dr=1; //模拟继电器的LED灯点亮
  123.       }
  124.    }
  125.    else  //是负数,说明是零下多少度的情况下
  126.    {
  127.       led_dr=1; //模拟继电器的LED灯点亮
  128.    }

  129. }


  130. void ds18b20_sampling(void) //ds18b20采样程序
  131. {

  132.       ++uiSampingCnt;  //累计主循环次数的时间
  133.       if(uiSampingCnt>const_ds18b20_sampling_time)  //每隔一段时间就更新采集一次Ds18b20数据
  134.           {
  135.           uiSampingCnt=0;

  136.           ET0=0;  //禁止定时中断
  137.           uiTemperTemp=get_temper();  //读取一次没有经过换算的温度数值
  138.           ET0=1; //开启定时中断

  139.           if((uiTemperTemp&0xf800)==0xf800) //是负号
  140.           {
  141.                          ucSignFlag=1;

  142.              uiTemperTemp=~uiTemperTemp;  //求补码
  143.              uiTemperTemp=uiTemperTemp+1;

  144.           }
  145.           else //是正号
  146.           {
  147.                          ucSignFlag=0;

  148.           }



  149.           ulCurrentTemper=0; //把int数据类型赋给long类型之前要先清零
  150.           ulCurrentTemper=uiTemperTemp;

  151.           ulCurrentTemper=ulCurrentTemper*10; //为了先保留一位小数点,所以放大10倍,
  152.           ulCurrentTemper=ulCurrentTemper>>4;  //往右边移动4位,相当于乘以0.0625. 此时保留了1位小数点,

  153.           ulCurrentTemper=ulCurrentTemper+5;  //四舍五入
  154.           ulCurrentTemper=ulCurrentTemper/10; //四舍五入后,去掉小数点

  155.           ucWd1Part2Update=1; //局部2更新显示实时温度
  156.           }
  157. }


  158. //ds18b20驱动程序
  159. unsigned int get_temper()  //读取一次没有经过换算的温度数值
  160. {
  161. unsigned char temper_H;
  162. unsigned char temper_L;
  163. unsigned int ds18b20_data=0;

  164. ds18b20_reset(); //复位ds18b20的时序
  165. ds_write_byte(0xCC);
  166. ds_write_byte(0x44);

  167. ds18b20_reset(); //复位ds18b20的时序
  168. ds_write_byte(0xCC);
  169. ds_write_byte(0xBE);
  170. temper_L=ds_read_byte();
  171. temper_H=ds_read_byte();

  172. ds18b20_data=temper_H;     //把两个字节合并成一个int数据类型
  173. ds18b20_data=ds18b20_data<<8;
  174. ds18b20_data=ds18b20_data|temper_L;
  175. return ds18b20_data;
  176. }



  177. void ds18b20_reset() //复位ds18b20的时序
  178. {
  179.   unsigned char x;
  180.   dq_dr_sr=1;
  181.   delay_short(8);
  182.   dq_dr_sr=0;
  183.   delay_short(80);
  184.   dq_dr_sr=1;
  185.   delay_short(14);
  186.   x=dq_dr_sr;
  187.   delay_short(20);

  188. }

  189. void ds_write_byte(unsigned char date) //写一个字节
  190. {
  191. unsigned char  i;

  192. for(i=0;i<8;i++)
  193. {
  194.   dq_dr_sr=0;
  195.   dq_dr_sr=date&0x01;
  196.   delay_short(5);
  197.   dq_dr_sr=1;
  198.   date=date>>1;
  199. }
  200. }

  201. unsigned char ds_read_byte(void ) //读一字节
  202. {
  203. unsigned char i;
  204. unsigned char date=0;
  205. for(i=0;i<8;i++)
  206. {
  207.   dq_dr_sr=0;
  208.   date=date>>1;
  209.   dq_dr_sr=1;
  210.   if(dq_dr_sr)
  211.   {
  212.      date=date|0x80;
  213.   }
  214.   delay_short(5);
  215. }
  216. return (date);
  217. }



  218. void display_service(void) //显示的窗口菜单服务程序
  219. {

  220.    switch(ucWd)  //因为本程序只有1个窗口,在实际项目中,此处的ucWd也可以省略不要
  221.    {
  222.        case 1:  

  223.                         if(ucWd1Part1Update==1)//局部设定温度更新显示
  224.                         {
  225.                            ucWd1Part1Update=0;

  226.                ucTemp8=10; //显示空

  227.                            if(ulSetTemper>=100)
  228.                            {
  229.                   ucTemp7=ulSetTemper%1000/100; //显示设定温度的百位
  230.                            }
  231.                            else
  232.                            {
  233.                               ucTemp7=10; //显示空
  234.                            }

  235.                            if(ulSetTemper>=10)
  236.                            {
  237.                   ucTemp6=ulSetTemper%100/10; //显示设定温度的十位
  238.                            }
  239.                            else
  240.                            {
  241.                               ucTemp6=10; //显示空
  242.                            }

  243.                ucTemp5=ulSetTemper%10; //显示设定温度的个位


  244.                ucDigShow8=ucTemp8; //数码管显示实际内容
  245.                ucDigShow7=ucTemp7;
  246.                ucDigShow6=ucTemp6;
  247.                ucDigShow5=ucTemp5;
  248.                         }


  249.                         if(ucWd1Part2Update==1)//局部实际温度更新显示
  250.                         {
  251.                            if(ucSignFlag==0)  //正数
  252.                            {
  253.                   ucTemp4=10; //显示空
  254.                            }
  255.                            else  //负数,说明是零下多少度的情况下
  256.                            {
  257.                   ucTemp4=11; //显示负号-
  258.                            }

  259.                            if(ulCurrentTemper>=100)
  260.                            {
  261.                   ucTemp3=ulCurrentTemper%100/100; //显示实际温度的百位
  262.                            }
  263.                            else
  264.                            {
  265.                           ucTemp3=10; //显示空
  266.                            }


  267.                            if(ulCurrentTemper>=10)
  268.                            {
  269.                   ucTemp2=ulCurrentTemper%100/10;  //显示实际温度的十位
  270.                            }
  271.                            else
  272.                            {
  273.                   ucTemp2=10;  //显示空
  274.                            }

  275.                ucTemp1=ulCurrentTemper%10; //显示实际温度的个数位

  276.                ucDigShow4=ucTemp4; //数码管显示实际内容
  277.                ucDigShow3=ucTemp3;
  278.                ucDigShow2=ucTemp2;
  279.                ucDigShow1=ucTemp1;
  280.                         }

  281.             break;

  282.       }
  283.    

  284. }

  285. void key_scan(void)//按键扫描函数 放在定时中断里
  286. {  
  287.   if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  288.   {
  289.      ucKeyLock1=0; //按键自锁标志清零
  290.      uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  291.   }
  292.   else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
  293.   {
  294.      uiKeyTimeCnt1++; //累加定时中断次数
  295.      if(uiKeyTimeCnt1>const_key_time1)
  296.      {
  297.         uiKeyTimeCnt1=0;
  298.         ucKeyLock1=1;  //自锁按键置位,避免一直触发
  299.         ucKeySec=1;    //触发1号键
  300.      }
  301.   }

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





  317. }

  318. void key_service(void) //按键服务的应用程序
  319. {

  320.   switch(ucKeySec) //按键服务状态切换
  321.   {
  322.     case 1:// 加按键 对应朱兆祺学习板的S1键
  323.           switch(ucWd) //因为本程序只有1个窗口,在实际项目中,此处的ucWd也可以省略不要
  324.           {
  325.               case 1: //在窗口1下设置设定温度
  326.                    ulSetTemper++;
  327.                                    if(ulSetTemper>125)
  328.                                    {
  329.                                      ulSetTemper=125;
  330.                                    }

  331.                                ucWd1Part1Update=1; //更新显示设定温度
  332.                    break;
  333.           }

  334.           ucVoiceLock=1;  //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
  335.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  336.           ucVoiceLock=0;  //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt

  337.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  338.           break;   
  339.    
  340.     case 2:// 减按键 对应朱兆祺学习板的S5键
  341.           switch(ucWd) //因为本程序只有1个窗口,在实际项目中,此处的ucWd也可以省略不要
  342.           {
  343.                case 1: //在窗口1下设置设定温度
  344.                     if(ulSetTemper>2)  //由于缓冲温差是2度,所以我人为规定最小允许设定的温度不能低于2度
  345.                                         {
  346.                                            ulSetTemper--;
  347.                                         }

  348.                           ucWd1Part1Update=1; //更新显示设定温度
  349.                     break;
  350.          
  351.           }

  352.           ucVoiceLock=1;  //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
  353.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  354.           ucVoiceLock=0;  //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt

  355.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  356.           break;  


  357.          
  358.   }         
  359.   

  360. }

  361. void display_drive(void)  
  362. {
  363.    //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
  364.    switch(ucDisplayDriveStep)
  365.    {
  366.       case 1:  //显示第1位
  367.            ucDigShowTemp=dig_table[ucDigShow1];
  368.                    if(ucDigDot1==1)
  369.                    {
  370.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  371.                    }
  372.            dig_hc595_drive(ucDigShowTemp,0xfe);
  373.                break;
  374.       case 2:  //显示第2位
  375.            ucDigShowTemp=dig_table[ucDigShow2];
  376.                    if(ucDigDot2==1)
  377.                    {
  378.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  379.                    }
  380.            dig_hc595_drive(ucDigShowTemp,0xfd);
  381.                break;
  382.       case 3:  //显示第3位
  383.            ucDigShowTemp=dig_table[ucDigShow3];
  384.                    if(ucDigDot3==1)
  385.                    {
  386.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  387.                    }
  388.            dig_hc595_drive(ucDigShowTemp,0xfb);
  389.                break;
  390.       case 4:  //显示第4位
  391.            ucDigShowTemp=dig_table[ucDigShow4];
  392.                    if(ucDigDot4==1)
  393.                    {
  394.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  395.                    }
  396.            dig_hc595_drive(ucDigShowTemp,0xf7);
  397.                break;
  398.       case 5:  //显示第5位
  399.            ucDigShowTemp=dig_table[ucDigShow5];
  400.                    if(ucDigDot5==1)
  401.                    {
  402.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  403.                    }
  404.            dig_hc595_drive(ucDigShowTemp,0xef);
  405.                break;
  406.       case 6:  //显示第6位
  407.            ucDigShowTemp=dig_table[ucDigShow6];
  408.                    if(ucDigDot6==1)
  409.                    {
  410.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  411.                    }
  412.            dig_hc595_drive(ucDigShowTemp,0xdf);
  413.                break;
  414.       case 7:  //显示第7位
  415.            ucDigShowTemp=dig_table[ucDigShow7];
  416.                    if(ucDigDot7==1)
  417.                    {
  418.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  419.            }
  420.            dig_hc595_drive(ucDigShowTemp,0xbf);
  421.                break;
  422.       case 8:  //显示第8位
  423.            ucDigShowTemp=dig_table[ucDigShow8];
  424.                    if(ucDigDot8==1)
  425.                    {
  426.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  427.                    }
  428.            dig_hc595_drive(ucDigShowTemp,0x7f);
  429.                break;
  430.    }
  431.    ucDisplayDriveStep++;
  432.    if(ucDisplayDriveStep>8)  //扫描完8个数码管后,重新从第一个开始扫描
  433.    {
  434.      ucDisplayDriveStep=1;
  435.    }

  436. }

  437. //数码管的74HC595驱动函数
  438. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
  439. {
  440.    unsigned char i;
  441.    unsigned char ucTempData;
  442.    dig_hc595_sh_dr=0;
  443.    dig_hc595_st_dr=0;
  444.    ucTempData=ucDigStatusTemp16_09;  //先送高8位
  445.    for(i=0;i<8;i++)
  446.    {
  447.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  448.          else dig_hc595_ds_dr=0;
  449.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  450.          delay_short(1);
  451.          dig_hc595_sh_dr=1;
  452.          delay_short(1);
  453.          ucTempData=ucTempData<<1;
  454.    }
  455.    ucTempData=ucDigStatusTemp08_01;  //再先送低8位
  456.    for(i=0;i<8;i++)
  457.    {
  458.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  459.          else dig_hc595_ds_dr=0;
  460.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  461.          delay_short(1);
  462.          dig_hc595_sh_dr=1;
  463.          delay_short(1);
  464.          ucTempData=ucTempData<<1;
  465.    }
  466.    dig_hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  467.    delay_short(1);
  468.    dig_hc595_st_dr=1;
  469.    delay_short(1);
  470.    dig_hc595_sh_dr=0;    //拉低,抗干扰就增强
  471.    dig_hc595_st_dr=0;
  472.    dig_hc595_ds_dr=0;
  473. }

  474. //LED灯的74HC595驱动函数
  475. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
  476. {
  477.    unsigned char i;
  478.    unsigned char ucTempData;
  479.    hc595_sh_dr=0;
  480.    hc595_st_dr=0;
  481.    ucTempData=ucLedStatusTemp16_09;  //先送高8位
  482.    for(i=0;i<8;i++)
  483.    {
  484.          if(ucTempData>=0x80)hc595_ds_dr=1;
  485.          else hc595_ds_dr=0;
  486.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  487.          delay_short(1);
  488.          hc595_sh_dr=1;
  489.          delay_short(1);
  490.          ucTempData=ucTempData<<1;
  491.    }
  492.    ucTempData=ucLedStatusTemp08_01;  //再先送低8位
  493.    for(i=0;i<8;i++)
  494.    {
  495.          if(ucTempData>=0x80)hc595_ds_dr=1;
  496.          else hc595_ds_dr=0;
  497.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  498.          delay_short(1);
  499.          hc595_sh_dr=1;
  500.          delay_short(1);
  501.          ucTempData=ucTempData<<1;
  502.    }
  503.    hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  504.    delay_short(1);
  505.    hc595_st_dr=1;
  506.    delay_short(1);
  507.    hc595_sh_dr=0;    //拉低,抗干扰就增强
  508.    hc595_st_dr=0;
  509.    hc595_ds_dr=0;
  510. }


  511. void T0_time(void) interrupt 1   //定时中断
  512. {
  513.   TF0=0;  //清除中断标志
  514.   TR0=0; //关中断


  515.   if(ucVoiceLock==0) //原子锁判断
  516.   {
  517.      if(uiVoiceCnt!=0)
  518.      {

  519.         uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
  520.         beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  521.      
  522.      }
  523.      else
  524.      {

  525.         ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
  526.         beep_dr=1;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  527.         
  528.      }
  529.   }


  530.   key_scan(); //按键扫描函数
  531.   display_drive();  //数码管字模的驱动函数

  532.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  533.   TL0=0x0b;
  534.   TR0=1;  //开中断
  535. }

  536. void delay_short(unsigned int uiDelayShort)
  537. {
  538.    unsigned int i;  
  539.    for(i=0;i<uiDelayShort;i++)
  540.    {
  541.      ;   //一个分号相当于执行一条空语句
  542.    }
  543. }

  544. void delay_long(unsigned int uiDelayLong)
  545. {
  546.    unsigned int i;
  547.    unsigned int j;
  548.    for(i=0;i<uiDelayLong;i++)
  549.    {
  550.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  551.           {
  552.              ; //一个分号相当于执行一条空语句
  553.           }
  554.    }
  555. }


  556. void initial_myself(void)  //第一区 初始化单片机
  557. {
  558.   led_dr=0;//此处的LED灯模拟工控中的继电器
  559.   key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平
  560.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
  561.   hc595_drive(0x00,0x00);  //关闭所有经过另外两个74HC595驱动的LED灯
  562.   TMOD=0x01;  //设置定时器0为工作方式1
  563.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  564.   TL0=0x0b;

  565. }
  566. void initial_peripheral(void) //第二区 初始化外围
  567. {

  568.    ucDigDot8=0;   //小数点全部不显示
  569.    ucDigDot7=0;  
  570.    ucDigDot6=0;
  571.    ucDigDot5=0;  
  572.    ucDigDot4=0;
  573.    ucDigDot3=0;  
  574.    ucDigDot2=0;
  575.    ucDigDot1=0;

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

  579. }
复制代码

总结陈词:
下一节开始讲单片机采集模拟信号的内容,欲知详情,请听下回分解-----利用ADC0832采集电压的模拟信号。

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

作者: jianhong_wu    时间: 2014-6-5 15:56
第五十节:利用ADC0832采集电压信号,用平均法和区间法进行软件滤波处理。

开场白:
ADC0832是一款常用的8位AD采样芯片,通过它可以把外部的模拟电压信号转换成数字信号,然后给单片机进行换算,显示等处理。
这一节要教会大家五个知识点:
第一个:分辨率的算法。有些书上说8位AD最高分辩可达到256级(0xff+1),当输入电压是0---5V时,电压精度为19.53mV(5000mV除以256),我认为这种说法是错误的。8位AD的最高分辨率应该是255级(0xff),当输入电压是0---5V时,电压精度为19.61mV(5000mV除以255)。
第二个:用求平均值的滤波法,可以使AD采样的数据更加圆滑,去除小毛刺。
第三个:用区间滤波法,在一些干扰很大的场合,可以避免末尾小数点的数据频繁跳动。
第四个:如何使系统可以采集到更高的电压。由于ADC0832直接采集的电压最大不能超过5V,如果要采集的最大电压是25V该怎么办?我们只要在外部多增加1个10K的电阻和1个40K的电阻组成分压电路,把25V分压成5V,然后再让ADC0832采样,这时采样到的数据只要乘以5的系数,就可以得到超过5V的实际电压。选择分压电阻时,阻值尽量不要太小,一般要10K级别以上,阻值大一点,对被采样的系统干扰影响就越小。
第五个:如何有效保护AD通道口。我在一些电压不稳定的工控场合,一般是在AD通道口对负极反接一个瞬变二极管SA5.0A。当电压超过5V时,瞬变二极管会导通吸收掉多余的能量,把电压降下来,避免AD通道口烧坏。

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

(1)        硬件平台.
基于坚鸿51单片机学习板。

(2)实现功能:
     本程序有2个局部显示。
第1个局部是第8,7,6,5位数码管,显示没有经过滤波处理的实际电压值。此时能观察到未经滤波的数据不太稳定,末尾小数点数据会有跳动的现象
第2个局部是第4,3,2,1位数码管,显示经过平均法,区间法滤波的实际电压值。此时能观察到经过滤波后的数据很稳定,没有跳动的现象

系统保留3位小数点。手动调节可调电阻时,可以看到显示的数据在变化。

(3)源代码讲解如下:
  1. #include "REG52.H"

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

  3. void initial_myself(void);   
  4. void initial_peripheral(void);
  5. void delay_short(unsigned int uiDelayShort);
  6. void delay_long(unsigned int uiDelaylong);


  7. //驱动数码管的74HC595
  8. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);  
  9. void display_drive(void); //显示数码管字模的驱动函数
  10. void display_service(void); //显示的窗口菜单服务程序
  11. //驱动LED的74HC595
  12. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);

  13. void T0_time(void);  //定时中断函数

  14. void ad_sampling_service(void); //AD采样与处理的服务程序


  15. sbit led_dr=P3^5;  //LED灯
  16. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口



  17. sbit dig_hc595_sh_dr=P2^0;     //数码管的74HC595程序
  18. sbit dig_hc595_st_dr=P2^1;  
  19. sbit dig_hc595_ds_dr=P2^2;  
  20. sbit hc595_sh_dr=P2^3;    //LED灯的74HC595程序
  21. sbit hc595_st_dr=P2^4;  
  22. sbit hc595_ds_dr=P2^5;  


  23. sbit adc0832_clk_dr     = P1^2;  // 定义adc0832的引脚
  24. sbit adc0832_cs_dr      = P1^0;
  25. sbit adc0832_data_sr_dr = P1^1;


  26. unsigned char ucDigShow8;  //第8位数码管要显示的内容
  27. unsigned char ucDigShow7;  //第7位数码管要显示的内容
  28. unsigned char ucDigShow6;  //第6位数码管要显示的内容
  29. unsigned char ucDigShow5;  //第5位数码管要显示的内容
  30. unsigned char ucDigShow4;  //第4位数码管要显示的内容
  31. unsigned char ucDigShow3;  //第3位数码管要显示的内容
  32. unsigned char ucDigShow2;  //第2位数码管要显示的内容
  33. unsigned char ucDigShow1;  //第1位数码管要显示的内容

  34. unsigned char ucDigDot8;  //数码管8的小数点是否显示的标志
  35. unsigned char ucDigDot7;  //数码管7的小数点是否显示的标志
  36. unsigned char ucDigDot6;  //数码管6的小数点是否显示的标志
  37. unsigned char ucDigDot5;  //数码管5的小数点是否显示的标志
  38. unsigned char ucDigDot4;  //数码管4的小数点是否显示的标志
  39. unsigned char ucDigDot3;  //数码管3的小数点是否显示的标志
  40. unsigned char ucDigDot2;  //数码管2的小数点是否显示的标志
  41. unsigned char ucDigDot1;  //数码管1的小数点是否显示的标志
  42. unsigned char ucDigShowTemp=0; //临时中间变量
  43. unsigned char ucDisplayDriveStep=1;  //动态扫描数码管的步骤变量


  44. unsigned char ucWd1Part1Update=1;  //在窗口1中,局部1的更新显示标志
  45. unsigned char ucWd1Part2Update=1; //在窗口1中,局部2的更新显示标志


  46. unsigned char ucTemp1=0;  //中间过渡变量
  47. unsigned char ucTemp2=0;  //中间过渡变量
  48. unsigned char ucTemp3=0;  //中间过渡变量
  49. unsigned char ucTemp4=0;  //中间过渡变量
  50. unsigned char ucTemp5=0;  //中间过渡变量
  51. unsigned char ucTemp6=0;  //中间过渡变量
  52. unsigned char ucTemp7=0;  //中间过渡变量
  53. unsigned char ucTemp8=0;  //中间过渡变量

  54. unsigned char ucAD=0;   //AD值
  55. unsigned char ucCheckAD=0; //用来做校验对比的AD值


  56. unsigned long ulTemp=0;  //参与换算的中间变量
  57. unsigned long ulTempFilterV=0; //参与换算的中间变量
  58. unsigned long ulBackupFilterV=5000;  //备份最新采样数据的中间变量
  59. unsigned char ucSamplingCnt=0; //统计采样的次数  本程序采样8次后求平均值

  60. unsigned long ulV=0; //未经滤波处理的实时电压值
  61. unsigned long ulFilterV=0; //经过滤波后的实时电压值


  62. //根据原理图得出的共阴数码管字模表
  63. code unsigned char dig_table[]=
  64. {
  65. 0x3f,  //0       序号0
  66. 0x06,  //1       序号1
  67. 0x5b,  //2       序号2
  68. 0x4f,  //3       序号3
  69. 0x66,  //4       序号4
  70. 0x6d,  //5       序号5
  71. 0x7d,  //6       序号6
  72. 0x07,  //7       序号7
  73. 0x7f,  //8       序号8
  74. 0x6f,  //9       序号9
  75. 0x00,  //无      序号10
  76. 0x40,  //-       序号11
  77. 0x73,  //P       序号12
  78. };
  79. void main()
  80.   {
  81.    initial_myself();  
  82.    delay_long(100);   
  83.    initial_peripheral();
  84.    while(1)  
  85.    {
  86.       ad_sampling_service(); //AD采样与处理的服务程序
  87.       display_service(); //显示的窗口菜单服务程序
  88.    }
  89. }

  90. void ad_sampling_service(void) //AD采样与处理的服务程序
  91. {
  92.     unsigned char i;

  93.     ucAD=0;   //AD值
  94.     ucCheckAD=0; //用来做校验对比的AD值


  95.     /* 片选信号置为低电平 */
  96.     adc0832_cs_dr = 0;

  97.         /* 第一个脉冲,开始位 */
  98.         adc0832_data_sr_dr = 1;
  99.         adc0832_clk_dr  = 0;
  100.     delay_short(1);
  101.         adc0832_clk_dr  = 1;

  102.         /* 第二个脉冲,选择通道 */
  103.         adc0832_data_sr_dr = 1;
  104.         adc0832_clk_dr  = 0;
  105.         adc0832_clk_dr  = 1;

  106.         /* 第三个脉冲,选择通道 */
  107.         adc0832_data_sr_dr = 0;
  108.         adc0832_clk_dr  = 0;
  109.         adc0832_clk_dr  = 1;

  110.     /* 数据线输出高电平 */
  111.         adc0832_data_sr_dr = 1;
  112.     delay_short(2);

  113.         /* 第一个下降沿 */
  114.         adc0832_clk_dr  = 1;
  115.         adc0832_clk_dr  = 0;
  116.     delay_short(1);


  117.         /* AD值开始送出 */
  118.         for (i = 0; i < 8; i++)
  119.         {
  120.         ucAD <<= 1;
  121.                 adc0832_clk_dr = 1;
  122.                 adc0832_clk_dr = 0;
  123.                 if (adc0832_data_sr_dr==1)
  124.                 {
  125.             ucAD |= 0x01;
  126.                 }
  127.         }

  128.         /* 用于校验的AD值开始送出 */
  129.         for (i = 0; i < 8; i++)
  130.         {
  131.         ucCheckAD >>= 1;
  132.                 if (adc0832_data_sr_dr==1)
  133.                 {
  134.            ucCheckAD |= 0x80;
  135.                 }
  136.                 adc0832_clk_dr = 1;
  137.                 adc0832_clk_dr = 0;
  138.         }
  139.        
  140.         /* 片选信号置为高电平 */
  141.         adc0832_cs_dr = 1;


  142.         if(ucCheckAD==ucAD)  //检验相等
  143.         {
  144.        
  145.             ulTemp=0;  //把char类型数据赋值给long类型数据之前,必须先清零
  146.         ulTemp=ucAD; //把char类型数据赋值给long类型数据,参与乘除法运算的数据,为了避免运算结果溢出,我都用long类型

  147. /* 注释一:
  148. * 因为保留3为小数点,这里的5000代表5.000V。ulTemp/255代表分辨率.
  149. * 有些书上说8位AD最高分辩可达到256级(0xff+1),我认为这种说法是错误的。
  150. * 8位AD最高分辩应该是255级(0xff),所以这里除以255,而不是256.
  151. */
  152.         ulTemp=5000*ulTemp/255;  //进行电压换算

  153.         ulV=ulTemp; //得到未经滤波处理的实时电压值
  154.         ucWd1Part1Update=1; //局部更新显示未经滤波处理的电压


  155.                 ulTempFilterV=ulTempFilterV+ulTemp;  //累加8次后求平均值
  156.         ucSamplingCnt++;  //统计已经采样累计的次数
  157.                 if(ucSamplingCnt>=8)
  158.                 {

  159. /* 注释二:
  160. * 求平均值滤波法,为了得到的数据更加圆滑,去除小毛刺。
  161. * 向右边移动3位相当于除以8。
  162. */

  163.                      ulTempFilterV=ulTempFilterV>>3; //求平均值滤波法


  164. /* 注释三:
  165. * 以下区间滤波法,为了避免末尾小数点的数据频繁跳动。
  166. * 这里的20用于区间滤波法的正负偏差,这里的20代表0.020V。
  167. * 意思是只要最近采集到的数据在正负0.020V偏差范围内,就不更新。
  168. */
  169.                     if(ulBackupFilterV>=20)  //最近备份的上一次数据大于等于0.02V的情况下
  170.                     {
  171.                        if(ulTempFilterV<(ulBackupFilterV-20)||ulTempFilterV>(ulBackupFilterV+20)) //在正负0.020V偏差范围外,更新
  172.                        {
  173.                            ulBackupFilterV=ulTempFilterV;  //备份最新采样的数据,方便下一次对比判断

  174.                    ulFilterV=ulTempFilterV; //得到经过滤波处理的实时电压值
  175.                    ucWd1Part2Update=1; //局部更新显示经过滤波处理的电压
  176.                               }
  177.                     }
  178.                     else   //最近备份的上一次数据小于0.02V的情况下
  179.                     {
  180.                        if(ulTempFilterV>(ulBackupFilterV+20))  //在正0.020V偏差范围外,更新
  181.                        {
  182.                            ulBackupFilterV=ulTempFilterV;  //备份最新采样的数据,方便下一次对比判断

  183.                    ulFilterV=ulTempFilterV; //得到经过滤波处理的实时电压值
  184.                    ucWd1Part2Update=1; //局部更新显示经过滤波处理的电压
  185.                            }
  186.                   
  187.                     }


  188.                     ucSamplingCnt=0;  //清零,为下一轮采样滤波作准备。
  189.                     ulTempFilterV=0;
  190.                 }
  191.        
  192.         }

  193. }

  194. void display_service(void) //显示的窗口菜单服务程序
  195. {

  196.                         if(ucWd1Part1Update==1)//未经滤波处理的实时电压更新显示
  197.                         {
  198.                            ucWd1Part1Update=0;

  199.                ucTemp8=ulV%10000/1000;  //显示电压值个位
  200.                ucTemp7=ulV%1000/100;    //显示电压值小数点后第1位
  201.                ucTemp6=ulV%100/10;      //显示电压值小数点后第2位
  202.                ucTemp5=ulV%10;          //显示电压值小数点后第3位


  203.                ucDigShow8=ucTemp8; //数码管显示实际内容
  204.                ucDigShow7=ucTemp7;
  205.                ucDigShow6=ucTemp6;
  206.                ucDigShow5=ucTemp5;
  207.                         }


  208.                         if(ucWd1Part2Update==1)//经过滤波处理后的实时电压更新显示
  209.                         {
  210.                              ucWd1Part2Update=0;

  211.                ucTemp4=ulFilterV%10000/1000;  //显示电压值个位
  212.                ucTemp3=ulFilterV%1000/100;    //显示电压值小数点后第1位
  213.                ucTemp2=ulFilterV%100/10;      //显示电压值小数点后第2位
  214.                ucTemp1=ulFilterV%10;          //显示电压值小数点后第3位


  215.                ucDigShow4=ucTemp4; //数码管显示实际内容
  216.                ucDigShow3=ucTemp3;
  217.                ucDigShow2=ucTemp2;
  218.                ucDigShow1=ucTemp1;
  219.                         }


  220. }



  221. void display_drive(void)  
  222. {
  223.    //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
  224.    switch(ucDisplayDriveStep)
  225.    {
  226.       case 1:  //显示第1位
  227.            ucDigShowTemp=dig_table[ucDigShow1];
  228.                    if(ucDigDot1==1)
  229.                    {
  230.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  231.                    }
  232.            dig_hc595_drive(ucDigShowTemp,0xfe);
  233.                break;
  234.       case 2:  //显示第2位
  235.            ucDigShowTemp=dig_table[ucDigShow2];
  236.                    if(ucDigDot2==1)
  237.                    {
  238.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  239.                    }
  240.            dig_hc595_drive(ucDigShowTemp,0xfd);
  241.                break;
  242.       case 3:  //显示第3位
  243.            ucDigShowTemp=dig_table[ucDigShow3];
  244.                    if(ucDigDot3==1)
  245.                    {
  246.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  247.                    }
  248.            dig_hc595_drive(ucDigShowTemp,0xfb);
  249.                break;
  250.       case 4:  //显示第4位
  251.            ucDigShowTemp=dig_table[ucDigShow4];
  252.                    if(ucDigDot4==1)
  253.                    {
  254.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  255.                    }
  256.            dig_hc595_drive(ucDigShowTemp,0xf7);
  257.                break;
  258.       case 5:  //显示第5位
  259.            ucDigShowTemp=dig_table[ucDigShow5];
  260.                    if(ucDigDot5==1)
  261.                    {
  262.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  263.                    }
  264.            dig_hc595_drive(ucDigShowTemp,0xef);
  265.                break;
  266.       case 6:  //显示第6位
  267.            ucDigShowTemp=dig_table[ucDigShow6];
  268.                    if(ucDigDot6==1)
  269.                    {
  270.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  271.                    }
  272.            dig_hc595_drive(ucDigShowTemp,0xdf);
  273.                break;
  274.       case 7:  //显示第7位
  275.            ucDigShowTemp=dig_table[ucDigShow7];
  276.                    if(ucDigDot7==1)
  277.                    {
  278.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  279.            }
  280.            dig_hc595_drive(ucDigShowTemp,0xbf);
  281.                break;
  282.       case 8:  //显示第8位
  283.            ucDigShowTemp=dig_table[ucDigShow8];
  284.                    if(ucDigDot8==1)
  285.                    {
  286.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  287.                    }
  288.            dig_hc595_drive(ucDigShowTemp,0x7f);
  289.                break;
  290.    }
  291.    ucDisplayDriveStep++;
  292.    if(ucDisplayDriveStep>8)  //扫描完8个数码管后,重新从第一个开始扫描
  293.    {
  294.      ucDisplayDriveStep=1;
  295.    }

  296. }

  297. //数码管的74HC595驱动函数
  298. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
  299. {
  300.    unsigned char i;
  301.    unsigned char ucTempData;
  302.    dig_hc595_sh_dr=0;
  303.    dig_hc595_st_dr=0;
  304.    ucTempData=ucDigStatusTemp16_09;  //先送高8位
  305.    for(i=0;i<8;i++)
  306.    {
  307.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  308.          else dig_hc595_ds_dr=0;
  309.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  310.          delay_short(1);
  311.          dig_hc595_sh_dr=1;
  312.          delay_short(1);
  313.          ucTempData=ucTempData<<1;
  314.    }
  315.    ucTempData=ucDigStatusTemp08_01;  //再先送低8位
  316.    for(i=0;i<8;i++)
  317.    {
  318.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  319.          else dig_hc595_ds_dr=0;
  320.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  321.          delay_short(1);
  322.          dig_hc595_sh_dr=1;
  323.          delay_short(1);
  324.          ucTempData=ucTempData<<1;
  325.    }
  326.    dig_hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  327.    delay_short(1);
  328.    dig_hc595_st_dr=1;
  329.    delay_short(1);
  330.    dig_hc595_sh_dr=0;    //拉低,抗干扰就增强
  331.    dig_hc595_st_dr=0;
  332.    dig_hc595_ds_dr=0;
  333. }

  334. //LED灯的74HC595驱动函数
  335. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
  336. {
  337.    unsigned char i;
  338.    unsigned char ucTempData;
  339.    hc595_sh_dr=0;
  340.    hc595_st_dr=0;
  341.    ucTempData=ucLedStatusTemp16_09;  //先送高8位
  342.    for(i=0;i<8;i++)
  343.    {
  344.          if(ucTempData>=0x80)hc595_ds_dr=1;
  345.          else hc595_ds_dr=0;
  346.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  347.          delay_short(1);
  348.          hc595_sh_dr=1;
  349.          delay_short(1);
  350.          ucTempData=ucTempData<<1;
  351.    }
  352.    ucTempData=ucLedStatusTemp08_01;  //再先送低8位
  353.    for(i=0;i<8;i++)
  354.    {
  355.          if(ucTempData>=0x80)hc595_ds_dr=1;
  356.          else hc595_ds_dr=0;
  357.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  358.          delay_short(1);
  359.          hc595_sh_dr=1;
  360.          delay_short(1);
  361.          ucTempData=ucTempData<<1;
  362.    }
  363.    hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  364.    delay_short(1);
  365.    hc595_st_dr=1;
  366.    delay_short(1);
  367.    hc595_sh_dr=0;    //拉低,抗干扰就增强
  368.    hc595_st_dr=0;
  369.    hc595_ds_dr=0;
  370. }


  371. void T0_time(void) interrupt 1   //定时中断
  372. {
  373.   TF0=0;  //清除中断标志
  374.   TR0=0; //关中断


  375.   display_drive();  //数码管字模的驱动函数

  376.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  377.   TL0=0x0b;
  378.   TR0=1;  //开中断
  379. }

  380. void delay_short(unsigned int uiDelayShort)
  381. {
  382.    unsigned int i;  
  383.    for(i=0;i<uiDelayShort;i++)
  384.    {
  385.      ;   //一个分号相当于执行一条空语句
  386.    }
  387. }

  388. void delay_long(unsigned int uiDelayLong)
  389. {
  390.    unsigned int i;
  391.    unsigned int j;
  392.    for(i=0;i<uiDelayLong;i++)
  393.    {
  394.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  395.           {
  396.              ; //一个分号相当于执行一条空语句
  397.           }
  398.    }
  399. }


  400. void initial_myself(void)  //第一区 初始化单片机
  401. {
  402.   led_dr=0;//LED灯默认关闭
  403.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
  404.   hc595_drive(0x00,0x00);  //关闭所有经过另外两个74HC595驱动的LED灯
  405.   TMOD=0x01;  //设置定时器0为工作方式1
  406.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  407.   TL0=0x0b;

  408. }
  409. void initial_peripheral(void) //第二区 初始化外围
  410. {

  411.    ucDigDot8=1;   //显示未经过滤波电压的小数点
  412.    ucDigDot7=0;  
  413.    ucDigDot6=0;
  414.    ucDigDot5=0;  
  415.    ucDigDot4=1;  //显示经过滤波后电压的小数点
  416.    ucDigDot3=0;  
  417.    ucDigDot2=0;
  418.    ucDigDot1=0;

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

  422. }
复制代码

总结陈词:
这节用区间滤波法虽然可以解决小数点后面的数据出现频繁跳动的现象,但是也存在一个小问题,就是精度受到了影响,比如我们设置的正负偏差是0.02V,那就意味着系统存在0.02V的误差。有没有更好的办法解决这个问题?如果系统的末尾数据一直不断处于频繁跳动中,那么只能牺牲一点精度,我认为用区间法已经是最好的解决办法了,但是经过本次实验,我观察到未经过滤波处理的数据只是偶尔跳动,并非频繁跳动,所以下一节我会给大家介绍一种不用牺牲精度,又可以很好滤波的方法。欲知详情,请听下回分解-----利用ADC0832采集电压信号,用连续N次一致性的方法进行滤波处理。

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

作者: jianhong_wu    时间: 2014-6-15 12:21
第五十一节:利用ADC0832采集电压信号,用连续N次一致性的方法进行滤波处理。

开场白:
连续判断N次一致性的滤波法,是为了避免末尾小数点的数据偶尔跳动。这种滤波方法的原理跟我在按键扫描中去抖动的原理是一模一样的,被我频繁地应用在大量的工控项目中。
这一节要教会大家一个知识点:连续判断N次一致性的滤波法。
具体原理:当某个采样变量发生变化时,有两种可能,一种可能是外界的一个瞬间干扰。另一种可能是变量确实发生变化。为了有效去除干扰,当发现变量有变化时,我会连续采集N次,如果连续N次都是一致的结果,我才认为不是干扰。如果中间只要出现一次不一致,我会马上把计数器清零,这一步是精华,很关键。

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

(1)硬件平台.
基于坚鸿51单片机学习板。

(2)实现功能:
本程序有2个局部显示。
第1个局部是第8,7,6,5位数码管,显示没有经过滤波处理的实际电压值。此时能观察到未经滤波的数据不太稳定,末尾小数点数据会有跳动的现象
第2个局部是第4,3,2,1位数码管,显示经过特定算法滤波后的实际电压值。此时能观察到经过滤波后的数据很稳定,没有跳动的现象。而且显示的电压值跟未经过滤波的电压值几乎是完全一致,不会出现上一节用区间滤波法所留下的0.02V误差问题。
系统保留3位小数点。手动调节可调电阻时,可以看到显示的数据在变化。

(3)源代码讲解如下:
  1. #include "REG52.H"

  2. #define const_N   8  //连续判断N次一致性滤波方法中,N的取值
  3. #define const_voice_short  40   //蜂鸣器短叫的持续时间

  4. void initial_myself(void);   
  5. void initial_peripheral(void);
  6. void delay_short(unsigned int uiDelayShort);
  7. void delay_long(unsigned int uiDelaylong);


  8. //驱动数码管的74HC595
  9. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);  
  10. void display_drive(void); //显示数码管字模的驱动函数
  11. void display_service(void); //显示的窗口菜单服务程序
  12. //驱动LED的74HC595
  13. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);

  14. void T0_time(void);  //定时中断函数

  15. void ad_sampling_service(void); //AD采样与处理的服务程序


  16. sbit led_dr=P3^5;  //LED灯
  17. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口



  18. sbit dig_hc595_sh_dr=P2^0;     //数码管的74HC595程序
  19. sbit dig_hc595_st_dr=P2^1;  
  20. sbit dig_hc595_ds_dr=P2^2;  
  21. sbit hc595_sh_dr=P2^3;    //LED灯的74HC595程序
  22. sbit hc595_st_dr=P2^4;  
  23. sbit hc595_ds_dr=P2^5;  


  24. sbit adc0832_clk_dr     = P1^2;  // 定义adc0832的引脚
  25. sbit adc0832_cs_dr      = P1^0;
  26. sbit adc0832_data_sr_dr = P1^1;


  27. unsigned char ucDigShow8;  //第8位数码管要显示的内容
  28. unsigned char ucDigShow7;  //第7位数码管要显示的内容
  29. unsigned char ucDigShow6;  //第6位数码管要显示的内容
  30. unsigned char ucDigShow5;  //第5位数码管要显示的内容
  31. unsigned char ucDigShow4;  //第4位数码管要显示的内容
  32. unsigned char ucDigShow3;  //第3位数码管要显示的内容
  33. unsigned char ucDigShow2;  //第2位数码管要显示的内容
  34. unsigned char ucDigShow1;  //第1位数码管要显示的内容

  35. unsigned char ucDigDot8;  //数码管8的小数点是否显示的标志
  36. unsigned char ucDigDot7;  //数码管7的小数点是否显示的标志
  37. unsigned char ucDigDot6;  //数码管6的小数点是否显示的标志
  38. unsigned char ucDigDot5;  //数码管5的小数点是否显示的标志
  39. unsigned char ucDigDot4;  //数码管4的小数点是否显示的标志
  40. unsigned char ucDigDot3;  //数码管3的小数点是否显示的标志
  41. unsigned char ucDigDot2;  //数码管2的小数点是否显示的标志
  42. unsigned char ucDigDot1;  //数码管1的小数点是否显示的标志
  43. unsigned char ucDigShowTemp=0; //临时中间变量
  44. unsigned char ucDisplayDriveStep=1;  //动态扫描数码管的步骤变量


  45. unsigned char ucWd1Part1Update=1;  //在窗口1中,局部1的更新显示标志
  46. unsigned char ucWd1Part2Update=1; //在窗口1中,局部2的更新显示标志


  47. unsigned char ucTemp1=0;  //中间过渡变量
  48. unsigned char ucTemp2=0;  //中间过渡变量
  49. unsigned char ucTemp3=0;  //中间过渡变量
  50. unsigned char ucTemp4=0;  //中间过渡变量
  51. unsigned char ucTemp5=0;  //中间过渡变量
  52. unsigned char ucTemp6=0;  //中间过渡变量
  53. unsigned char ucTemp7=0;  //中间过渡变量
  54. unsigned char ucTemp8=0;  //中间过渡变量

  55. unsigned char ucAD=0;   //AD值
  56. unsigned char ucCheckAD=0; //用来做校验对比的AD值


  57. unsigned long ulTemp=0;  //参与换算的中间变量
  58. unsigned long ulTempFilterV=0; //参与换算的中间变量
  59. unsigned long ulBackupFilterV=5000;  //备份最新采样数据的中间变量
  60. unsigned char ucSamplingCnt=0; //记录连续N次采样的计数器

  61. unsigned long ulV=0; //未经滤波处理的实时电压值
  62. unsigned long ulFilterV=0; //经过滤波后的实时电压值


  63. //根据原理图得出的共阴数码管字模表
  64. code unsigned char dig_table[]=
  65. {
  66. 0x3f,  //0       序号0
  67. 0x06,  //1       序号1
  68. 0x5b,  //2       序号2
  69. 0x4f,  //3       序号3
  70. 0x66,  //4       序号4
  71. 0x6d,  //5       序号5
  72. 0x7d,  //6       序号6
  73. 0x07,  //7       序号7
  74. 0x7f,  //8       序号8
  75. 0x6f,  //9       序号9
  76. 0x00,  //无      序号10
  77. 0x40,  //-       序号11
  78. 0x73,  //P       序号12
  79. };
  80. void main()
  81.   {
  82.    initial_myself();  
  83.    delay_long(100);   
  84.    initial_peripheral();
  85.    while(1)  
  86.    {
  87.       ad_sampling_service(); //AD采样与处理的服务程序
  88.       display_service(); //显示的窗口菜单服务程序
  89.    }
  90. }

  91. void ad_sampling_service(void) //AD采样与处理的服务程序
  92. {
  93.     unsigned char i;

  94.     ucAD=0;   //AD值
  95.     ucCheckAD=0; //用来做校验对比的AD值


  96.     /* 片选信号置为低电平 */
  97.     adc0832_cs_dr = 0;

  98.         /* 第一个脉冲,开始位 */
  99.         adc0832_data_sr_dr = 1;
  100.         adc0832_clk_dr  = 0;
  101.     delay_short(1);
  102.         adc0832_clk_dr  = 1;

  103.         /* 第二个脉冲,选择通道 */
  104.         adc0832_data_sr_dr = 1;
  105.         adc0832_clk_dr  = 0;
  106.         adc0832_clk_dr  = 1;

  107.         /* 第三个脉冲,选择通道 */
  108.         adc0832_data_sr_dr = 0;
  109.         adc0832_clk_dr  = 0;
  110.         adc0832_clk_dr  = 1;

  111.     /* 数据线输出高电平 */
  112.         adc0832_data_sr_dr = 1;
  113.     delay_short(2);

  114.         /* 第一个下降沿 */
  115.         adc0832_clk_dr  = 1;
  116.         adc0832_clk_dr  = 0;
  117.     delay_short(1);


  118.         /* AD值开始送出 */
  119.         for (i = 0; i < 8; i++)
  120.         {
  121.         ucAD <<= 1;
  122.                 adc0832_clk_dr = 1;
  123.                 adc0832_clk_dr = 0;
  124.                 if (adc0832_data_sr_dr==1)
  125.                 {
  126.             ucAD |= 0x01;
  127.                 }
  128.         }

  129.         /* 用于校验的AD值开始送出 */
  130.         for (i = 0; i < 8; i++)
  131.         {
  132.         ucCheckAD >>= 1;
  133.                 if (adc0832_data_sr_dr==1)
  134.                 {
  135.            ucCheckAD |= 0x80;
  136.                 }
  137.                 adc0832_clk_dr = 1;
  138.                 adc0832_clk_dr = 0;
  139.         }
  140.         
  141.         /* 片选信号置为高电平 */
  142.         adc0832_cs_dr = 1;


  143.         if(ucCheckAD==ucAD)  //检验相等
  144.         {
  145.         
  146.             ulTemp=0;  //把char类型数据赋值给long类型数据之前,必须先清零
  147.             ulTemp=ucAD; //把char类型数据赋值给long类型数据,参与乘除法运算的数据,为了避免运算结果溢出,我都用long类型

  148. /* 注释一:
  149. * 因为保留3为小数点,这里的5000代表5.000V。ulTemp/255代表分辨率.
  150. * 有些书上说8位AD最高分辩可达到256级(0xff+1),我认为这种说法是错误的。
  151. * 8位AD最高分辩应该是255级(0xff),所以这里除以255,而不是256.
  152. */
  153.             ulTemp=5000*ulTemp/255;  //进行电压换算
  154.             ulV=ulTemp; //得到未经滤波处理的实时电压值
  155.             ucWd1Part1Update=1; //局部更新显示未经滤波处理的电压


  156. /* 注释二:
  157. * 以下连续判断N次一致性的滤波法,为了避免末尾小数点的数据偶尔跳动。
  158. * 这种滤波方法的原理跟我在按键扫描中的去抖动原理是一模一样的,被我频繁
  159. * 地应用在大量的工控项目中。
  160. * 具体原理:当某个采样变量发生变化时,有两种可能,一种可能是外界的一个瞬间干扰。
  161. * 另一种可能是变量确实发生变化。为了有效去除干扰,当发现变量有变化时,
  162. * 我会连续采集N次,如果连续N次都是一致的结果,我才认为不是干扰。如果中间
  163. * 只要出现一次不一致,我会马上把计数器清零,这一步是精华,很关键。
  164. *
  165. */
  166.                       if(ulTempFilterV!=ulTemp) //发现变量有变化
  167.                      {
  168.                         ucSamplingCnt++;    //计数器累加
  169.                               if(ucSamplingCnt>const_N)  //如果连续N次都是一致的,则认为不是干扰。确实有数据需要更新显示。这里的const_N取值是8
  170.                             {
  171.                                 ucSamplingCnt=0;

  172.                                 ulTempFilterV=ulTemp;   //及时保存更新了的数据,方便下一次有新数据对比做准备

  173.                     ulFilterV=ulTempFilterV; //得到经过滤波处理的实时电压值
  174.                     ucWd1Part2Update=1; //局部更新显示经过滤波处理的电压                         
  175.                             }
  176.                        }
  177.                     else
  178.                     {
  179.                          ucSamplingCnt=0;  //只要出现一次不一致,我会马上把计数器清零,这一步是精华,很关键。
  180.                     }



  181.         
  182.         }

  183. }

  184. void display_service(void) //显示的窗口菜单服务程序
  185. {

  186.                         if(ucWd1Part1Update==1)//未经滤波处理的实时电压更新显示
  187.                         {
  188.                            ucWd1Part1Update=0;

  189.                ucTemp8=ulV%10000/1000;  //显示电压值个位
  190.                ucTemp7=ulV%1000/100;    //显示电压值小数点后第1位
  191.                ucTemp6=ulV%100/10;      //显示电压值小数点后第2位
  192.                ucTemp5=ulV%10;          //显示电压值小数点后第3位


  193.                ucDigShow8=ucTemp8; //数码管显示实际内容
  194.                ucDigShow7=ucTemp7;
  195.                ucDigShow6=ucTemp6;
  196.                ucDigShow5=ucTemp5;
  197.                         }


  198.                         if(ucWd1Part2Update==1)//经过滤波处理后的实时电压更新显示
  199.                         {
  200.                              ucWd1Part2Update=0;

  201.                ucTemp4=ulFilterV%10000/1000;  //显示电压值个位
  202.                ucTemp3=ulFilterV%1000/100;    //显示电压值小数点后第1位
  203.                ucTemp2=ulFilterV%100/10;      //显示电压值小数点后第2位
  204.                ucTemp1=ulFilterV%10;          //显示电压值小数点后第3位


  205.                ucDigShow4=ucTemp4; //数码管显示实际内容
  206.                ucDigShow3=ucTemp3;
  207.                ucDigShow2=ucTemp2;
  208.                ucDigShow1=ucTemp1;
  209.                         }


  210. }



  211. void display_drive(void)  
  212. {
  213.    //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
  214.    switch(ucDisplayDriveStep)
  215.    {
  216.       case 1:  //显示第1位
  217.            ucDigShowTemp=dig_table[ucDigShow1];
  218.                    if(ucDigDot1==1)
  219.                    {
  220.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  221.                    }
  222.            dig_hc595_drive(ucDigShowTemp,0xfe);
  223.                break;
  224.       case 2:  //显示第2位
  225.            ucDigShowTemp=dig_table[ucDigShow2];
  226.                    if(ucDigDot2==1)
  227.                    {
  228.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  229.                    }
  230.            dig_hc595_drive(ucDigShowTemp,0xfd);
  231.                break;
  232.       case 3:  //显示第3位
  233.            ucDigShowTemp=dig_table[ucDigShow3];
  234.                    if(ucDigDot3==1)
  235.                    {
  236.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  237.                    }
  238.            dig_hc595_drive(ucDigShowTemp,0xfb);
  239.                break;
  240.       case 4:  //显示第4位
  241.            ucDigShowTemp=dig_table[ucDigShow4];
  242.                    if(ucDigDot4==1)
  243.                    {
  244.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  245.                    }
  246.            dig_hc595_drive(ucDigShowTemp,0xf7);
  247.                break;
  248.       case 5:  //显示第5位
  249.            ucDigShowTemp=dig_table[ucDigShow5];
  250.                    if(ucDigDot5==1)
  251.                    {
  252.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  253.                    }
  254.            dig_hc595_drive(ucDigShowTemp,0xef);
  255.                break;
  256.       case 6:  //显示第6位
  257.            ucDigShowTemp=dig_table[ucDigShow6];
  258.                    if(ucDigDot6==1)
  259.                    {
  260.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  261.                    }
  262.            dig_hc595_drive(ucDigShowTemp,0xdf);
  263.                break;
  264.       case 7:  //显示第7位
  265.            ucDigShowTemp=dig_table[ucDigShow7];
  266.                    if(ucDigDot7==1)
  267.                    {
  268.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  269.            }
  270.            dig_hc595_drive(ucDigShowTemp,0xbf);
  271.                break;
  272.       case 8:  //显示第8位
  273.            ucDigShowTemp=dig_table[ucDigShow8];
  274.                    if(ucDigDot8==1)
  275.                    {
  276.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  277.                    }
  278.            dig_hc595_drive(ucDigShowTemp,0x7f);
  279.                break;
  280.    }
  281.    ucDisplayDriveStep++;
  282.    if(ucDisplayDriveStep>8)  //扫描完8个数码管后,重新从第一个开始扫描
  283.    {
  284.      ucDisplayDriveStep=1;
  285.    }

  286. }

  287. //数码管的74HC595驱动函数
  288. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
  289. {
  290.    unsigned char i;
  291.    unsigned char ucTempData;
  292.    dig_hc595_sh_dr=0;
  293.    dig_hc595_st_dr=0;
  294.    ucTempData=ucDigStatusTemp16_09;  //先送高8位
  295.    for(i=0;i<8;i++)
  296.    {
  297.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  298.          else dig_hc595_ds_dr=0;
  299.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  300.          delay_short(1);
  301.          dig_hc595_sh_dr=1;
  302.          delay_short(1);
  303.          ucTempData=ucTempData<<1;
  304.    }
  305.    ucTempData=ucDigStatusTemp08_01;  //再先送低8位
  306.    for(i=0;i<8;i++)
  307.    {
  308.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  309.          else dig_hc595_ds_dr=0;
  310.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  311.          delay_short(1);
  312.          dig_hc595_sh_dr=1;
  313.          delay_short(1);
  314.          ucTempData=ucTempData<<1;
  315.    }
  316.    dig_hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  317.    delay_short(1);
  318.    dig_hc595_st_dr=1;
  319.    delay_short(1);
  320.    dig_hc595_sh_dr=0;    //拉低,抗干扰就增强
  321.    dig_hc595_st_dr=0;
  322.    dig_hc595_ds_dr=0;
  323. }

  324. //LED灯的74HC595驱动函数
  325. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
  326. {
  327.    unsigned char i;
  328.    unsigned char ucTempData;
  329.    hc595_sh_dr=0;
  330.    hc595_st_dr=0;
  331.    ucTempData=ucLedStatusTemp16_09;  //先送高8位
  332.    for(i=0;i<8;i++)
  333.    {
  334.          if(ucTempData>=0x80)hc595_ds_dr=1;
  335.          else hc595_ds_dr=0;
  336.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  337.          delay_short(1);
  338.          hc595_sh_dr=1;
  339.          delay_short(1);
  340.          ucTempData=ucTempData<<1;
  341.    }
  342.    ucTempData=ucLedStatusTemp08_01;  //再先送低8位
  343.    for(i=0;i<8;i++)
  344.    {
  345.          if(ucTempData>=0x80)hc595_ds_dr=1;
  346.          else hc595_ds_dr=0;
  347.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  348.          delay_short(1);
  349.          hc595_sh_dr=1;
  350.          delay_short(1);
  351.          ucTempData=ucTempData<<1;
  352.    }
  353.    hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  354.    delay_short(1);
  355.    hc595_st_dr=1;
  356.    delay_short(1);
  357.    hc595_sh_dr=0;    //拉低,抗干扰就增强
  358.    hc595_st_dr=0;
  359.    hc595_ds_dr=0;
  360. }


  361. void T0_time(void) interrupt 1   //定时中断
  362. {
  363.   TF0=0;  //清除中断标志
  364.   TR0=0; //关中断


  365.   display_drive();  //数码管字模的驱动函数

  366.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  367.   TL0=0x0b;
  368.   TR0=1;  //开中断
  369. }

  370. void delay_short(unsigned int uiDelayShort)
  371. {
  372.    unsigned int i;  
  373.    for(i=0;i<uiDelayShort;i++)
  374.    {
  375.      ;   //一个分号相当于执行一条空语句
  376.    }
  377. }

  378. void delay_long(unsigned int uiDelayLong)
  379. {
  380.    unsigned int i;
  381.    unsigned int j;
  382.    for(i=0;i<uiDelayLong;i++)
  383.    {
  384.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  385.           {
  386.              ; //一个分号相当于执行一条空语句
  387.           }
  388.    }
  389. }


  390. void initial_myself(void)  //第一区 初始化单片机
  391. {
  392.   led_dr=0;//LED灯默认关闭
  393.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
  394.   hc595_drive(0x00,0x00);  //关闭所有经过另外两个74HC595驱动的LED灯
  395.   TMOD=0x01;  //设置定时器0为工作方式1
  396.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  397.   TL0=0x0b;

  398. }
  399. void initial_peripheral(void) //第二区 初始化外围
  400. {

  401.    ucDigDot8=1;   //显示未经过滤波电压的小数点
  402.    ucDigDot7=0;  
  403.    ucDigDot6=0;
  404.    ucDigDot5=0;  
  405.    ucDigDot4=1;  //显示经过滤波后电压的小数点
  406.    ucDigDot3=0;  
  407.    ucDigDot2=0;
  408.    ucDigDot1=0;

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

  412. }
复制代码

总结陈词:
在单片机AD采样的系统中,我常用的滤波方法有求平均值法,区间法,连续判断N次一致性这三种方法。读者可以根据不同的系统特点选择对应的滤波方法,有一些要求高的系统还可以把三种滤波方法混合在一起用。关于AD采样的知识到本节已经讲完,下一节会讲什么新内容呢?欲知详情,请听下回分解-----return语句鲜为人知的用法。

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

作者: jianhong_wu    时间: 2014-6-22 02:05
第五十二节:程序后续升级修改的利器,return语句鲜为人知的用法。

开场白:
return语句经常用在带参数返回的函数中,字面上理解就是返回的意思,因此很多单片机初学者很容易忽略了return语句还有中断强行退出的功能。利用这个强行退出的功能,在项目后续程序的升级修改上很方便,还可以有效减少if语句的嵌套层数,使程序阅读起来很简洁。这一节要教大家return语句三个鲜为人知的用法:
第一个鲜为人知的用法:在空函数里,可以插入很多个return语句,不仅仅是一个。
第二个鲜为人知的用法:return语句可以有效较少程序里条件判断语句的嵌套层数。
第三个鲜为人知的用法:return语句本身已经包含了类似break语句的功能,不管当前处于几层的内部循环嵌套,只要遇到return语句都可以强行退出全部循环,并且直接退出当前子程序,不执行当前子程序后面的任何语句,这个功能实在是太强大,太铁腕了。

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

(1)硬件平台:
基于坚鸿51单片机学习板。

(2)实现功能:
本程序实现的功能跟第三十九节是一摸一样的,唯一的差别就是在第三十九节的基础上,插入了几个return语句,用新的return语句替代原来的条件和循环判断语句。

波特率是:9600 。
通讯协议:EB 00 55  XX YY  
加无效填充字节后,上位机实际上应该发送:00  EB 00 55  XX YY
其中第1位00是无效填充字节,防止由于硬件原因丢失第一个字节。
其中第2,3,4位EB 00 55就是数据头
           后2位XX YY就是有效数据
任意时刻,单片机从电脑“串口调试助手”上位机收到的一串数据中,只要此数据中包含关键字EB 00 55 ,并且此关键字后面两个字节的数据XX YY 分别为01 02,那么蜂鸣器鸣叫一声表示接收的数据头和有效数据都是正确的。

也就是说,当在 串口助手往单片机发送十六进制数据串:  eb 00 55 01 02  时,会听到蜂鸣器”滴”的一声。

(3)源代码讲解如下:
  1. #include "REG52.H"


  2. #define const_voice_short  40   //蜂鸣器短叫的持续时间
  3. #define const_rc_size  10  //接收串口中断数据的缓冲区数组大小

  4. #define const_receive_time  5  //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完,这个时间根据实际情况来调整大小

  5. void initial_myself(void);   
  6. void initial_peripheral(void);
  7. void delay_long(unsigned int uiDelaylong);



  8. void T0_time(void);  //定时中断函数
  9. void usart_receive(void); //串口接收中断函数
  10. void usart_service(void);  //串口服务程序,在main函数里

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

  12. unsigned int  uiSendCnt=0;     //用来识别串口是否接收完一串数据的计时器
  13. unsigned char ucSendLock=1;    //串口服务程序的自锁变量,每次接收完一串数据只处理一次
  14. unsigned int  uiRcregTotal=0;  //代表当前缓冲区已经接收了多少个数据
  15. unsigned char ucRcregBuf[const_rc_size]; //接收串口中断数据的缓冲区数组
  16. unsigned int  uiRcMoveIndex=0;  //用来解析数据协议的中间变量


  17. unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器



  18. void main()
  19.   {
  20.    initial_myself();  
  21.    delay_long(100);   
  22.    initial_peripheral();
  23.    while(1)  
  24.    {
  25.        usart_service();  //串口服务程序
  26.    }

  27. }

  28. /* 注释一:
  29. * 以下函数说明了,在空函数里,可以插入很多个return语句。
  30. * 用return语句非常便于后续程序的升级修改。
  31. */
  32. void usart_service(void)  //串口服务程序,在main函数里
  33. {

  34.         

  35. //     if(uiSendCnt>=const_receive_time&&ucSendLock==1) //原来的语句,现在被两个return语句替代了
  36. //     {

  37.        if(uiSendCnt<const_receive_time)  //延时还没超过规定时间,直接退出本程序,不执行return后的任何语句。
  38.            {
  39.               return;  //强行退出本子程序,不执行以下任何语句
  40.            }

  41.            if(ucSendLock==0)  //不是最新一次接收到串口数据,直接退出本程序,不执行return后的任何语句。
  42.            {
  43.               return;  //强行退出本子程序,不执行以下任何语句
  44.            }
  45. /* 注释二:
  46. * 以上两条return语句就相当于原来的一条if(uiSendCnt>=const_receive_time&&ucSendLock==1)语句。
  47. * 用了return语句后,就明显减少了一个if嵌套。
  48. */


  49.             ucSendLock=0;    //处理一次就锁起来,不用每次都进来,除非有新接收的数据

  50.                     //下面的代码进入数据协议解析和数据处理的阶段

  51.             uiRcMoveIndex=0; //由于是判断数据头,所以下标移动变量从数组的0开始向最尾端移动


  52. //           while(uiRcregTotal>=5&&uiRcMoveIndex<=(uiRcregTotal-5)) //原来的语句,现在被两个return语句替代了
  53.             while(1) //死循环可以被以下return或者break语句中断,return本身已经包含了break语句功能。
  54.             {
  55.                if(uiRcregTotal<5)  //串口接收到的数据太少
  56.                            {
  57.                               uiRcregTotal=0;  //清空缓冲的下标,方便下次重新从0下标开始接受新数据
  58.                                   return;  //强行退出while(1)循环嵌套,直接退出本程序,不执行以下任何语句
  59.                            }

  60.                            if(uiRcMoveIndex>(uiRcregTotal-5)) //数组缓冲区的数据已经处理完
  61.                            {
  62.                               uiRcregTotal=0;  //清空缓冲的下标,方便下次重新从0下标开始接受新数据
  63.                                   return;  //强行退出while(1)循环嵌套,直接退出本程序,不执行以下任何语句
  64.                            }
  65. /* 注释三:
  66. * 以上两条return语句就相当于原来的一条while(uiRcregTotal>=5&&uiRcMoveIndex<=(uiRcregTotal-5))语句。
  67. * 以上两个return语句的用法,同时说明了return本身已经包含了break语句功能,不管当前处于几层的内部循环嵌套,
  68. * 都可以强行退出循环,并且直接退出本程序。
  69. */


  70.                if(ucRcregBuf[uiRcMoveIndex+0]==0xeb&&ucRcregBuf[uiRcMoveIndex+1]==0x00&&ucRcregBuf[uiRcMoveIndex+2]==0x55)  //数据头eb 00 55的判断
  71.                {
  72.                   if(ucRcregBuf[uiRcMoveIndex+3]==0x01&&ucRcregBuf[uiRcMoveIndex+4]==0x02)  //有效数据01 02的判断
  73.                   {
  74.                        uiVoiceCnt=const_voice_short; //蜂鸣器发出声音,说明数据头和有效数据都接收正确
  75.                   }
  76.                   break;   //退出while(1)循环
  77.                }
  78.                uiRcMoveIndex++; //因为是判断数据头,游标向着数组最尾端的方向移动
  79.            }
  80.                                          
  81.            uiRcregTotal=0;  //清空缓冲的下标,方便下次重新从0下标开始接受新数据
  82.   
  83. //     }
  84.                         
  85. }


  86. void T0_time(void) interrupt 1    //定时中断
  87. {
  88.   TF0=0;  //清除中断标志
  89.   TR0=0; //关中断


  90.   if(uiSendCnt<const_receive_time)   //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完
  91.   {
  92.           uiSendCnt++;    //表面上这个数据不断累加,但是在串口中断里,每接收一个字节它都会被清零,除非这个中间没有串口数据过来
  93.       ucSendLock=1;     //开自锁标志
  94.   }

  95.   if(uiVoiceCnt!=0)
  96.   {
  97.      uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
  98.      beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。

  99.   }
  100.   else
  101.   {
  102.      ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
  103.      beep_dr=1;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  104.   }


  105.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  106.   TL0=0x0b;
  107.   TR0=1;  //开中断
  108. }


  109. void usart_receive(void) interrupt 4                 //串口接收数据中断        
  110. {        

  111.    if(RI==1)  
  112.    {
  113.         RI = 0;

  114.             ++uiRcregTotal;
  115.         if(uiRcregTotal>const_rc_size)  //超过缓冲区
  116.         {
  117.            uiRcregTotal=const_rc_size;
  118.         }
  119.         ucRcregBuf[uiRcregTotal-1]=SBUF;   //将串口接收到的数据缓存到接收缓冲区里
  120.         uiSendCnt=0;  //及时喂狗,虽然main函数那边不断在累加,但是只要串口的数据还没发送完毕,那么它永远也长不大,因为每个中断都被清零。
  121.    
  122.    }
  123.    else  //我在其它单片机上都不用else这段代码的,可能在51单片机上多增加" TI = 0;"稳定性会更好吧。
  124.    {
  125.         TI = 0;
  126.    }
  127.                                                          
  128. }                                


  129. void delay_long(unsigned int uiDelayLong)
  130. {
  131.    unsigned int i;
  132.    unsigned int j;
  133.    for(i=0;i<uiDelayLong;i++)
  134.    {
  135.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  136.           {
  137.              ; //一个分号相当于执行一条空语句
  138.           }
  139.    }
  140. }


  141. void initial_myself(void)  //第一区 初始化单片机
  142. {

  143.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

  144.   //配置定时器
  145.   TMOD=0x01;  //设置定时器0为工作方式1
  146.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  147.   TL0=0x0b;


  148.   //配置串口
  149.   SCON=0x50;
  150.   TMOD=0X21;
  151.   TH1=TL1=-(11059200L/12/32/9600);  //这段配置代码具体是什么意思,我也不太清楚,反正是跟串口波特率有关。
  152.   TR1=1;

  153. }

  154. void initial_peripheral(void) //第二区 初始化外围
  155. {

  156.    EA=1;     //开总中断
  157.    ES=1;     //允许串口中断
  158.    ET0=1;    //允许定时中断
  159.    TR0=1;    //启动定时中断

  160. }
复制代码

总结陈词:
我在第一节就告诉读者了,搞单片机开发如果不会C语言的指针也没关系,不会影响做项目。我本人平时做项目时,也很少用指针,只有在三种场合下我才会用指针,因为在这三种场合下,用了指针感觉程序阅读起来更加清爽了。所以,指针还是有它独到的好处,有哪三种好处?欲知详情,请听下回分解-----指针的第一大好处,让一个函数可以封装多个相当于return语句返回的参数。

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

作者: jianhong_wu    时间: 2014-6-29 07:32
第五十三节:指针的第一大好处,让一个函数可以封装多个相当于return语句返回的参数。

开场白:
当我们想把某种算法通过一个函数来实现的时候,如果不会指针,那么只有两种方法。
第1种:用不带参数返回的空函数。这是最原始的做法,也是我当年刚毕业就开始做项目的时候经常用的方法。它完全依靠全局变量作为函数的输入和输出口。我们要用到这个函数,就要把参与运算的变量直接赋给对应的输入全局变量,调用一次函数之后,再找到对应的输出变量,这些输出变量就是我们要的结果。这种方法的缺点是阅读不直观,封装性不强,没有面对用户的输入输出接口。
第2种:用return返回参数和带输入形参的函数,这种方法已经具备了完整的输入和输出性能,比第1种方法直观多了。但是这种方法有它的局限性,因为return只能返回一个变量,如果要用在返回多个输出结果的函数中,就无能为力了,这时候该怎么办?就必须用指针了,也就是我下面讲到的第3种方法。
这一节要教大家一个知识点:通过指针,让函数可以返回多个变量。

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

(1)硬件平台:
基于坚鸿51单片机学习板。

(2)实现功能:
通过电脑串口调试助手,往单片机发送EB 00 55 XX YY  指令,其中EB 00 55是数据头, XX是被除数,YY是除数。单片机收到指令后就会返回6个数据,最前面两个数据是第1种运算方式的商和余数,中间两个数据是第2种运算方式的商和余数,最后两个数据是第3种运算方式的商和余数。
比如电脑发送:EB 00 55 08 02
单片机就返回:04 00 04 00 04 00  (04是商,00是余数)

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

波特率是:9600 。

(3)源代码讲解如下:
  1. #include "REG52.H"


  2. #define const_voice_short  40   //蜂鸣器短叫的持续时间
  3. #define const_rc_size  10  //接收串口中断数据的缓冲区数组大小

  4. #define const_receive_time  5  //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完,这个时间根据实际情况来调整大小

  5. void initial_myself(void);   
  6. void initial_peripheral(void);
  7. void delay_long(unsigned int uiDelaylong);
  8. void delay_short(unsigned int uiDelayShort);


  9. void T0_time(void);  //定时中断函数
  10. void usart_receive(void); //串口接收中断函数
  11. void usart_service(void);  //串口服务程序,在main函数里


  12. void eusart_send(unsigned char ucSendData);
  13. void chu_fa_yun_suan_1(void);//第1种方法 求商和余数
  14. unsigned char get_shang_2(unsigned char ucBeiChuShuTemp,unsigned char ucChuShuTemp); //第2种方法 求商
  15. unsigned char get_yu_2(unsigned char ucBeiChuShuTemp,unsigned char ucChuShuTemp); //第2种方法 求余数
  16. void chu_fa_yun_suan_3(unsigned char ucBeiChuShuTemp,unsigned char ucChuShuTemp,unsigned char *p_ucShangTemp,unsigned char *p_ucYuTemp);//第3种方法 求商和余数

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

  18. unsigned int  uiSendCnt=0;     //用来识别串口是否接收完一串数据的计时器
  19. unsigned char ucSendLock=1;    //串口服务程序的自锁变量,每次接收完一串数据只处理一次
  20. unsigned int  uiRcregTotal=0;  //代表当前缓冲区已经接收了多少个数据
  21. unsigned char ucRcregBuf[const_rc_size]; //接收串口中断数据的缓冲区数组
  22. unsigned int  uiRcMoveIndex=0;  //用来解析数据协议的中间变量


  23. unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器


  24. unsigned char ucBeiChuShu_1=0;  //第1种方法中的被除数
  25. unsigned char ucChuShu_1=1;     //第1种方法中的除数
  26. unsigned char ucShang_1=0;      //第1种方法中的商
  27. unsigned char ucYu_1=0;         //第1种方法中的余数

  28. unsigned char ucBeiChuShu_2=0;  //第2种方法中的被除数
  29. unsigned char ucChuShu_2=1;     //第2种方法中的除数
  30. unsigned char ucShang_2=0;      //第2种方法中的商
  31. unsigned char ucYu_2=0;         //第2种方法中的余数

  32. unsigned char ucBeiChuShu_3=0;  //第3种方法中的被除数
  33. unsigned char ucChuShu_3=1;     //第3种方法中的除数
  34. unsigned char ucShang_3=0;      //第3种方法中的商
  35. unsigned char ucYu_3=0;         //第3种方法中的余数

  36. void main()
  37.   {
  38.    initial_myself();  
  39.    delay_long(100);   
  40.    initial_peripheral();
  41.    while(1)  
  42.    {
  43.        usart_service();  //串口服务程序
  44.    }

  45. }


  46. /* 注释一:
  47. * 第1种方法,用不带参数返回的空函数,这是最原始的做法,也是我当年刚毕业
  48. * 就开始做项目的时候经常用的方法。它完全依靠全局变量作为函数的输入和输出口。
  49. * 我们要用到这个函数,就要把参与运算的变量直接赋给对应的输入全局变量,
  50. * 调用一次函数之后,再找到对应的输出变量,这些输出变量就是我们要的结果。
  51. * 在本函数中,被除数ucBeiChuShu_1和除数ucChuShu_1就是输入全局变量,
  52. * 商ucShang_1和余数ucYu_1就是输出全局变量。这种方法的缺点是阅读不直观,
  53. * 封装性不强,没有面对用户的输入输出接口,
  54. */
  55. void chu_fa_yun_suan_1(void)//第1种方法 求商和余数
  56. {
  57.    if(ucChuShu_1==0) //如果除数为0,则商和余数都为0
  58.    {
  59.       ucShang_1=0;
  60.           ucYu_1=0;
  61.    }
  62.    else
  63.    {
  64.       ucShang_1=ucBeiChuShu_1/ucChuShu_1;  //求商
  65.       ucYu_1=ucBeiChuShu_1%ucChuShu_1;  //求余数
  66.    }

  67. }


  68. /* 注释二:
  69. * 第2种方法,用return返回参数和带输入形参的函数,这种方法已经具备了完整的输入和输出性能,
  70. * 比第1种方法直观多了。但是这种方法有它的局限性,因为return只能返回一个变量,
  71. * 如果要用在返回多个输出结果的函数中,就无能为力了。比如本程序,就不能同时输出
  72. * 商和余数,只能分两个函数来做。如果要在一个函数中同时输出商和余数,该怎么办?
  73. * 这个时候就必须用指针了,也就是我下面讲到的第3种方法。
  74. */
  75. unsigned char get_shang_2(unsigned char ucBeiChuShuTemp,unsigned char ucChuShuTemp) //第2种方法 求商
  76. {
  77.    unsigned char ucShangTemp;
  78.    if(ucChuShuTemp==0) //如果除数为0,则商为0
  79.    {
  80.       ucShangTemp=0;
  81.    }
  82.    else
  83.    {
  84.       ucShangTemp=ucBeiChuShuTemp/ucChuShuTemp;  //求商
  85.    }

  86.    return ucShangTemp; //返回运算后的结果 商
  87. }

  88. unsigned char get_yu_2(unsigned char ucBeiChuShuTemp,unsigned char ucChuShuTemp) //第2种方法 求余数
  89. {
  90.    unsigned char ucYuTemp;
  91.    if(ucChuShuTemp==0) //如果除数为0,则余数为0
  92.    {
  93.       ucYuTemp=0;
  94.    }
  95.    else
  96.    {
  97.       ucYuTemp=ucBeiChuShuTemp%ucChuShuTemp;   //求余数
  98.    }

  99.    return ucYuTemp; //返回运算后的结果 余数
  100. }

  101. /* 注释三:
  102. * 第3种方法,用带指针的函数,就可以顺心所欲,不受return的局限,想输出多少个
  103. * 运算结果都可以,赞一个!在本函数中,ucBeiChuShuTemp和ucChuShuTemp是输入变量,
  104. * 它们不是指针,所以不具备输出接口属性。*p_ucShangTemp和*p_ucYuTemp是输出变量,
  105. * 因为它们是指针,所以具备输出接口属性。
  106. */
  107. void chu_fa_yun_suan_3(unsigned char ucBeiChuShuTemp,unsigned char ucChuShuTemp,unsigned char *p_ucShangTemp,unsigned char *p_ucYuTemp)//第3种方法 求商和余数
  108. {
  109.    if(ucChuShuTemp==0) //如果除数为0,则商和余数都为0
  110.    {
  111.       *p_ucShangTemp=0;
  112.           *p_ucYuTemp=0;
  113.    }
  114.    else
  115.    {
  116.       *p_ucShangTemp=ucBeiChuShuTemp/ucChuShuTemp;  //求商
  117.       *p_ucYuTemp=ucBeiChuShuTemp%ucChuShuTemp;  //求余数
  118.    }

  119. }

  120. void usart_service(void)  //串口服务程序,在main函数里
  121. {

  122.         

  123.      if(uiSendCnt>=const_receive_time&&ucSendLock==1) //说明超过了一定的时间内,再也没有新数据从串口来
  124.      {

  125.             ucSendLock=0;    //处理一次就锁起来,不用每次都进来,除非有新接收的数据

  126.             //下面的代码进入数据协议解析和数据处理的阶段

  127.             uiRcMoveIndex=0; //由于是判断数据头,所以下标移动变量从数组的0开始向最尾端移动

  128.             while(uiRcregTotal>=5&&uiRcMoveIndex<=(uiRcregTotal-5))
  129.             {
  130.                if(ucRcregBuf[uiRcMoveIndex+0]==0xeb&&ucRcregBuf[uiRcMoveIndex+1]==0x00&&ucRcregBuf[uiRcMoveIndex+2]==0x55)  //数据头eb 00 55的判断
  131.                {

  132.                   //第1种运算方法,依靠全局变量
  133.                   ucBeiChuShu_1=ucRcregBuf[uiRcMoveIndex+3]; //被除数
  134.                   ucChuShu_1=ucRcregBuf[uiRcMoveIndex+4];  //除数
  135.                                   chu_fa_yun_suan_1(); //调用一次空函数就出结果了,结果保存在ucShang_1和ucYu_1全局变量中
  136.                                   eusart_send(ucShang_1); //把运算结果返回给上位机观察
  137.                                   eusart_send(ucYu_1);//把运算结果返回给上位机观察

  138.                   //第2种运算方法,依靠两个带return语句的返回函数
  139.                   ucBeiChuShu_2=ucRcregBuf[uiRcMoveIndex+3]; //被除数
  140.                   ucChuShu_2=ucRcregBuf[uiRcMoveIndex+4];  //除数
  141.                   ucShang_2=get_shang_2(ucBeiChuShu_2,ucChuShu_2); //第2种方法 求商
  142.                   ucYu_2=get_yu_2(ucBeiChuShu_2,ucChuShu_2); //第2种方法 求余数
  143.                                   eusart_send(ucShang_2); //把运算结果返回给上位机观察
  144.                                   eusart_send(ucYu_2);//把运算结果返回给上位机观察

  145.                   //第3种运算方法,依靠指针
  146.                   ucBeiChuShu_3=ucRcregBuf[uiRcMoveIndex+3]; //被除数
  147.                   ucChuShu_3=ucRcregBuf[uiRcMoveIndex+4];  //除数
  148. /* 注释四:
  149. * 注意,由于商和余数是指针形参,我们代入的变量必须带地址符号& 。比如&ucShang_3和&ucYu_3。
  150. * 因为我们是把变量的地址传递进去的。
  151. */
  152.                                   chu_fa_yun_suan_3(ucBeiChuShu_3,ucChuShu_3,&ucShang_3,&ucYu_3);//第3种方法 求商和余数
  153.                                   eusart_send(ucShang_3); //把运算结果返回给上位机观察
  154.                                   eusart_send(ucYu_3);//把运算结果返回给上位机观察


  155.                   break;   //退出循环
  156.                }
  157.                uiRcMoveIndex++; //因为是判断数据头,游标向着数组最尾端的方向移动
  158.            }
  159.                                          
  160.            uiRcregTotal=0;  //清空缓冲的下标,方便下次重新从0下标开始接受新数据
  161.   
  162.      }
  163.                         
  164. }

  165. void eusart_send(unsigned char ucSendData) //往上位机发送一个字节的函数
  166. {

  167.   ES = 0; //关串口中断
  168.   TI = 0; //清零串口发送完成中断请求标志
  169.   SBUF =ucSendData; //发送一个字节

  170.   delay_short(400);  //每个字节之间的延时,这里非常关键,也是最容易出错的地方。延时的大小请根据实际项目来调整

  171.   TI = 0; //清零串口发送完成中断请求标志
  172.   ES = 1; //允许串口中断

  173. }



  174. void T0_time(void) interrupt 1    //定时中断
  175. {
  176.   TF0=0;  //清除中断标志
  177.   TR0=0; //关中断


  178.   if(uiSendCnt<const_receive_time)   //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完
  179.   {
  180.           uiSendCnt++;    //表面上这个数据不断累加,但是在串口中断里,每接收一个字节它都会被清零,除非这个中间没有串口数据过来
  181.       ucSendLock=1;     //开自锁标志
  182.   }

  183.   if(uiVoiceCnt!=0)
  184.   {
  185.      uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
  186.      beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。

  187.   }
  188.   else
  189.   {
  190.      ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
  191.      beep_dr=1;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  192.   }


  193.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  194.   TL0=0x0b;
  195.   TR0=1;  //开中断
  196. }


  197. void usart_receive(void) interrupt 4                 //串口接收数据中断        
  198. {        

  199.    if(RI==1)  
  200.    {
  201.         RI = 0;

  202.             ++uiRcregTotal;
  203.         if(uiRcregTotal>const_rc_size)  //超过缓冲区
  204.         {
  205.            uiRcregTotal=const_rc_size;
  206.         }
  207.         ucRcregBuf[uiRcregTotal-1]=SBUF;   //将串口接收到的数据缓存到接收缓冲区里
  208.         uiSendCnt=0;  //及时喂狗,虽然main函数那边不断在累加,但是只要串口的数据还没发送完毕,那么它永远也长不大,因为每个中断都被清零。
  209.    
  210.    }
  211.    else  //发送中断,及时把发送中断标志位清零
  212.    {
  213.         TI = 0;
  214.    }
  215.                                                          
  216. }                                


  217. void delay_long(unsigned int uiDelayLong)
  218. {
  219.    unsigned int i;
  220.    unsigned int j;
  221.    for(i=0;i<uiDelayLong;i++)
  222.    {
  223.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  224.           {
  225.              ; //一个分号相当于执行一条空语句
  226.           }
  227.    }
  228. }

  229. void delay_short(unsigned int uiDelayShort)
  230. {
  231.    unsigned int i;  
  232.    for(i=0;i<uiDelayShort;i++)
  233.    {
  234.      ;   //一个分号相当于执行一条空语句
  235.    }
  236. }


  237. void initial_myself(void)  //第一区 初始化单片机
  238. {

  239.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

  240.   //配置定时器
  241.   TMOD=0x01;  //设置定时器0为工作方式1
  242.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  243.   TL0=0x0b;


  244.   //配置串口
  245.   SCON=0x50;
  246.   TMOD=0X21;
  247.   TH1=TL1=-(11059200L/12/32/9600);  //这段配置代码具体是什么意思,我也不太清楚,反正是跟串口波特率有关。
  248.   TR1=1;

  249. }

  250. void initial_peripheral(void) //第二区 初始化外围
  251. {

  252.    EA=1;     //开总中断
  253.    ES=1;     //允许串口中断
  254.    ET0=1;    //允许定时中断
  255.    TR0=1;    //启动定时中断

  256. }
复制代码

总结陈词:
这节讲了指针的第一大好处,它的第二大好处是什么?欲知详情,请听下回分解-----指针的第二大好处,指针作为数组在函数内部的化身。

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

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

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

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

(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)源代码讲解如下:
  1. #include "REG52.H"


  2. #define const_array_size  5  //参与排序的数组大小

  3. #define const_voice_short  40   //蜂鸣器短叫的持续时间
  4. #define const_rc_size  10  //接收串口中断数据的缓冲区数组大小

  5. #define const_receive_time  5  //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完,这个时间根据实际情况来调整大小

  6. void initial_myself(void);   
  7. void initial_peripheral(void);
  8. void delay_long(unsigned int uiDelaylong);
  9. void delay_short(unsigned int uiDelayShort);


  10. void T0_time(void);  //定时中断函数
  11. void usart_receive(void); //串口接收中断函数
  12. void usart_service(void);  //串口服务程序,在main函数里


  13. void eusart_send(unsigned char ucSendData);

  14. void big_to_small_sort_1(void);//第1种方法 把一个数组从大小小排序
  15. void big_to_small_sort_2(unsigned char *p_ucInputBuffer);//第2种方法 把一个数组从大小小排序

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

  17. unsigned int  uiSendCnt=0;     //用来识别串口是否接收完一串数据的计时器
  18. unsigned char ucSendLock=1;    //串口服务程序的自锁变量,每次接收完一串数据只处理一次
  19. unsigned int  uiRcregTotal=0;  //代表当前缓冲区已经接收了多少个数据
  20. unsigned char ucRcregBuf[const_rc_size]; //接收串口中断数据的缓冲区数组
  21. unsigned int  uiRcMoveIndex=0;  //用来解析数据协议的中间变量

  22. unsigned char ucUsartBuffer[const_array_size];  //从串口接收到的需要排序的原始数据
  23. unsigned char ucGlobalBuffer_1[const_array_size]; //第1种方法,参与具体排序算法的全局变量数组
  24. unsigned char ucGlobalBuffer_2[const_array_size]; //第2种方法,参与具体排序算法的全局变量数组
  25. void main()
  26.   {
  27.    initial_myself();  
  28.    delay_long(100);   
  29.    initial_peripheral();
  30.    while(1)  
  31.    {
  32.        usart_service();  //串口服务程序
  33.    }

  34. }


  35. /* 注释一:
  36. * 第1种方法,用不带输入输出接口的空函数,这是最原始的做法,它完全依靠
  37. * 全局变量作为函数的输入和输出口。我们要用到这个函数,就要把参与运算
  38. * 的变量直接赋给对应的输入全局变量,调用一次函数之后,再找到对应的
  39. * 输出全局变量,这些输出全局变量就是我们要的结果。
  40. * 在本函数中,ucGlobalBuffer_1[const_array_size]既是输入全局变量,也是输出全局变量,
  41. * 这种方法的缺点是阅读不直观,封装性不强,没有面对用户的输入输出接口,
  42. */
  43. void big_to_small_sort_1(void)//第1种方法 把一个数组从大小小排序
  44. {
  45.    unsigned char i;
  46.    unsigned char k;
  47.    unsigned char ucTemp; //在两两交换数据的过程中,用于临时存放交换的某个变量

  48. /* 注释二:
  49. * 以下就是著名的 冒泡法排序。这个方法几乎所有的C语言大学教材都讲过了。大家在百度上可以直接
  50. * 搜索到它的工作原理和详细的讲解步骤,我就不再详细讲解了。
  51. */
  52.    for(i=0;i<(const_array_size-1);i++)  //冒泡的次数是(const_array_size-1)次
  53.    {
  54.       for(k=0;k<(const_array_size-1-i);k++) //每次冒泡的过程中,需要两两比较的次数是(const_array_size-1-i)
  55.           {
  56.              if(ucGlobalBuffer_1[const_array_size-1-k]>ucGlobalBuffer_1[const_array_size-1-1-k])  //后一个与前一个数据两两比较
  57.                  {
  58.                      ucTemp=ucGlobalBuffer_1[const_array_size-1-1-k];     //通过一个中间变量实现两个数据交换
  59.              ucGlobalBuffer_1[const_array_size-1-1-k]=ucGlobalBuffer_1[const_array_size-1-k];
  60.              ucGlobalBuffer_1[const_array_size-1-k]=ucTemp;
  61.                  }
  62.           
  63.           }
  64.    }

  65. }

  66. /* 注释三:
  67. * 第2种方法,为了改进第1种方法的用户体验,用指针为函数增加一个输入接口。
  68. * 为什么要用指针?因为C语言的函数中,数组不能直接用来做函数的形参,只能用指针作为数组的形参。
  69. * 比如,你不能这样写一个函数void big_to_small_sort_2(unsigned char a[5]),否则编译就会出错不通过。
  70. * 在本函数中,*p_ucInputBuffer指针就是输入接口,而输出接口仍然是全局变量数组ucGlobalBuffer_2。
  71. * 这种方法由于为函数多增加了一个数组输入接口,已经比第1种方法更加直观了。
  72. */
  73. void big_to_small_sort_2(unsigned char *p_ucInputBuffer)//第2种方法 把一个数组从大小小排序
  74. {
  75.    unsigned char i;
  76.    unsigned char k;
  77.    unsigned char ucTemp; //在两两交换数据的过程中,用于临时存放交换的某个变量


  78.    for(i=0;i<const_array_size;i++)  
  79.    {
  80.       ucGlobalBuffer_2[i]=p_ucInputBuffer[i];  //参与排序算法之前,先把输入接口的数据全部搬移到全局变量数组中。
  81.    }


  82.    //以下就是著名的 冒泡法排序。详细讲解请找百度。
  83.    for(i=0;i<(const_array_size-1);i++)  //冒泡的次数是(const_array_size-1)次
  84.    {
  85.       for(k=0;k<(const_array_size-1-i);k++) //每次冒泡的过程中,需要两两比较的次数是(const_array_size-1-i)
  86.           {
  87.              if(ucGlobalBuffer_2[const_array_size-1-k]>ucGlobalBuffer_2[const_array_size-1-1-k])  //后一个与前一个数据两两比较
  88.                  {
  89.                      ucTemp=ucGlobalBuffer_2[const_array_size-1-1-k];     //通过一个中间变量实现两个数据交换
  90.              ucGlobalBuffer_2[const_array_size-1-1-k]=ucGlobalBuffer_2[const_array_size-1-k];
  91.              ucGlobalBuffer_2[const_array_size-1-k]=ucTemp;
  92.                  }
  93.           
  94.           }
  95.    }

  96. }



  97. void usart_service(void)  //串口服务程序,在main函数里
  98. {

  99.      unsigned char i=0;   

  100.      if(uiSendCnt>=const_receive_time&&ucSendLock==1) //说明超过了一定的时间内,再也没有新数据从串口来
  101.      {

  102.             ucSendLock=0;    //处理一次就锁起来,不用每次都进来,除非有新接收的数据

  103.             //下面的代码进入数据协议解析和数据处理的阶段

  104.             uiRcMoveIndex=0; //由于是判断数据头,所以下标移动变量从数组的0开始向最尾端移动

  105.             while(uiRcregTotal>=5&&uiRcMoveIndex<=(uiRcregTotal-5))
  106.             {
  107.                if(ucRcregBuf[uiRcMoveIndex+0]==0xeb&&ucRcregBuf[uiRcMoveIndex+1]==0x00&&ucRcregBuf[uiRcMoveIndex+2]==0x55)  //数据头eb 00 55的判断
  108.                {


  109.                                   for(i=0;i<const_array_size;i++)
  110.                                   {
  111.                      ucUsartBuffer[i]=ucRcregBuf[uiRcMoveIndex+3+i]; //从串口接收到的需要被排序的原始数据
  112.                                   }


  113.                   //第1种运算方法,依靠全局变量
  114.                                   for(i=0;i<const_array_size;i++)
  115.                                   {
  116.                                      ucGlobalBuffer_1[i]=ucUsartBuffer[i];  //把需要被排列的数据放进输入全局变量数组
  117.                                   }
  118.                   big_to_small_sort_1(); //调用一次空函数就出结果了,结果还是保存在ucGlobalBuffer_1全局变量数组中
  119.                   for(i=0;i<const_array_size;i++)
  120.                                   {
  121.                                     eusart_send(ucGlobalBuffer_1[i]);  ////把用第1种方法排序后的结果返回给上位机观察
  122.                                   }


  123.                                   eusart_send(0xee);  //为了方便上位机观察,多发送3个字节ee ee ee作为第1种方法与第2种方法的分割线
  124.                                   eusart_send(0xee);
  125.                                   eusart_send(0xee);

  126.                   //第2种运算方法,依靠指针为函数增加一个数组的输入接口
  127.                                   //通过指针输入接口,直接把ucUsartBuffer数组的首地址传址进去,排序后输出的结果还是保存在ucGlobalBuffer_2全局变量数组中
  128.                   big_to_small_sort_2(ucUsartBuffer);
  129.                   for(i=0;i<const_array_size;i++)
  130.                                   {
  131.                                     eusart_send(ucGlobalBuffer_2[i]);  //把用第2种方法排序后的结果返回给上位机观察
  132.                                   }





  133.                   break;   //退出循环
  134.                }
  135.                uiRcMoveIndex++; //因为是判断数据头,游标向着数组最尾端的方向移动
  136.            }
  137.                                          
  138.            uiRcregTotal=0;  //清空缓冲的下标,方便下次重新从0下标开始接受新数据
  139.   
  140.      }
  141.                         
  142. }

  143. void eusart_send(unsigned char ucSendData) //往上位机发送一个字节的函数
  144. {

  145.   ES = 0; //关串口中断
  146.   TI = 0; //清零串口发送完成中断请求标志
  147.   SBUF =ucSendData; //发送一个字节

  148.   delay_short(400);  //每个字节之间的延时,这里非常关键,也是最容易出错的地方。延时的大小请根据实际项目来调整

  149.   TI = 0; //清零串口发送完成中断请求标志
  150.   ES = 1; //允许串口中断

  151. }



  152. void T0_time(void) interrupt 1    //定时中断
  153. {
  154.   TF0=0;  //清除中断标志
  155.   TR0=0; //关中断


  156.   if(uiSendCnt<const_receive_time)   //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完
  157.   {
  158.           uiSendCnt++;    //表面上这个数据不断累加,但是在串口中断里,每接收一个字节它都会被清零,除非这个中间没有串口数据过来
  159.       ucSendLock=1;     //开自锁标志
  160.   }



  161.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  162.   TL0=0x0b;
  163.   TR0=1;  //开中断
  164. }


  165. void usart_receive(void) interrupt 4                 //串口接收数据中断        
  166. {        

  167.    if(RI==1)  
  168.    {
  169.         RI = 0;

  170.             ++uiRcregTotal;
  171.         if(uiRcregTotal>const_rc_size)  //超过缓冲区
  172.         {
  173.            uiRcregTotal=const_rc_size;
  174.         }
  175.         ucRcregBuf[uiRcregTotal-1]=SBUF;   //将串口接收到的数据缓存到接收缓冲区里
  176.         uiSendCnt=0;  //及时喂狗,虽然main函数那边不断在累加,但是只要串口的数据还没发送完毕,那么它永远也长不大,因为每个中断都被清零。
  177.    
  178.    }
  179.    else  //发送中断,及时把发送中断标志位清零
  180.    {
  181.         TI = 0;
  182.    }
  183.                                                          
  184. }                                


  185. void delay_long(unsigned int uiDelayLong)
  186. {
  187.    unsigned int i;
  188.    unsigned int j;
  189.    for(i=0;i<uiDelayLong;i++)
  190.    {
  191.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  192.           {
  193.              ; //一个分号相当于执行一条空语句
  194.           }
  195.    }
  196. }

  197. void delay_short(unsigned int uiDelayShort)
  198. {
  199.    unsigned int i;  
  200.    for(i=0;i<uiDelayShort;i++)
  201.    {
  202.      ;   //一个分号相当于执行一条空语句
  203.    }
  204. }


  205. void initial_myself(void)  //第一区 初始化单片机
  206. {

  207.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

  208.   //配置定时器
  209.   TMOD=0x01;  //设置定时器0为工作方式1
  210.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  211.   TL0=0x0b;


  212.   //配置串口
  213.   SCON=0x50;
  214.   TMOD=0X21;
  215.   TH1=TL1=-(11059200L/12/32/9600);  //这段配置代码具体是什么意思,我也不太清楚,反正是跟串口波特率有关。
  216.   TR1=1;

  217. }

  218. void initial_peripheral(void) //第二区 初始化外围
  219. {

  220.    EA=1;     //开总中断
  221.    ES=1;     //允许串口中断
  222.    ET0=1;    //允许定时中断
  223.    TR0=1;    //启动定时中断

  224. }
复制代码

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

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

作者: jianhong_wu    时间: 2014-7-10 15:42
第五十五节:指针的第三大好处,指针作为数组在函数中的输出接口。

开场白:
上一节介绍的第2种方法,由于为函数多增加了一个数组输入接口,已经比第1种方法更加直观了,但是由于只有输入接口,没有输出接口,输出接口仍然要靠全局变量数组,所以还是有一个小小的遗憾,这节介绍的第3种方法就是为了改变这个遗憾,为数组在函数中多增加一个输出接口,这样,函数既有输入接口,又有输出接口,这样的函数才算完美直观。这一节要教大家一个知识点:通过指针,为函数增加一个数组输出接口。

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

(1)硬件平台:
基于坚鸿51单片机学习板。

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

比如电脑发送: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)源代码讲解如下:
  1. #include "REG52.H"


  2. #define const_array_size  5  //参与排序的数组大小

  3. #define const_voice_short  40   //蜂鸣器短叫的持续时间
  4. #define const_rc_size  10  //接收串口中断数据的缓冲区数组大小

  5. #define const_receive_time  5  //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完,这个时间根据实际情况来调整大小

  6. void initial_myself(void);   
  7. void initial_peripheral(void);
  8. void delay_long(unsigned int uiDelaylong);
  9. void delay_short(unsigned int uiDelayShort);


  10. void T0_time(void);  //定时中断函数
  11. void usart_receive(void); //串口接收中断函数
  12. void usart_service(void);  //串口服务程序,在main函数里


  13. void eusart_send(unsigned char ucSendData);


  14. void big_to_small_sort_2(unsigned char *p_ucInputBuffer);//第2种方法 把一个数组从大到小排序
  15. void big_to_small_sort_3(unsigned char *p_ucInputBuffer,unsigned char *p_ucOutputBuffer);//第3种方法 把一个数组从大到小排序
  16. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口

  17. unsigned int  uiSendCnt=0;     //用来识别串口是否接收完一串数据的计时器
  18. unsigned char ucSendLock=1;    //串口服务程序的自锁变量,每次接收完一串数据只处理一次
  19. unsigned int  uiRcregTotal=0;  //代表当前缓冲区已经接收了多少个数据
  20. unsigned char ucRcregBuf[const_rc_size]; //接收串口中断数据的缓冲区数组
  21. unsigned int  uiRcMoveIndex=0;  //用来解析数据协议的中间变量

  22. unsigned char ucUsartBuffer[const_array_size];  //从串口接收到的需要排序的原始数据

  23. unsigned char ucGlobalBuffer_2[const_array_size]; //第2种方法,参与具体排序算法的全局变量数组
  24. unsigned char ucGlobalBuffer_3[const_array_size]; //第3种方法,用来接收输出接口数据的全局变量数组

  25. void main()
  26.   {
  27.    initial_myself();  
  28.    delay_long(100);   
  29.    initial_peripheral();
  30.    while(1)  
  31.    {
  32.        usart_service();  //串口服务程序
  33.    }

  34. }



  35. /* 注释一:
  36. * 第2种方法,为了改进第1种方法的用户体验,用指针为函数增加一个输入接口。
  37. * 为什么要用指针?因为C语言的函数中,数组不能直接用来做函数的形参,只能用指针作为数组的形参。
  38. * 比如,你不能这样写一个函数void big_to_small_sort_2(unsigned char a[5]),否则编译就会出错不通过。
  39. * 在本函数中,*p_ucInputBuffer指针就是输入接口,而输出接口仍然是全局变量数组ucGlobalBuffer_2。
  40. * 这种方法由于为函数多增加了一个数组输入接口,已经比第1种方法更加直观了,但是由于只有输入接口,
  41. * 没有输出接口,输出接口仍然要靠全局变量,所以还是有点小遗憾,以下第3种方法就是为了改变这个遗憾。
  42. */
  43. void big_to_small_sort_2(unsigned char *p_ucInputBuffer)//第2种方法 把一个数组从大到小排序
  44. {
  45.    unsigned char i;
  46.    unsigned char k;
  47.    unsigned char ucTemp; //在两两交换数据的过程中,用于临时存放交换的某个变量


  48.    for(i=0;i<const_array_size;i++)  
  49.    {
  50.       ucGlobalBuffer_2[i]=p_ucInputBuffer[i];  //参与排序算法之前,先把输入接口的数据全部搬移到全局变量数组中。
  51.    }


  52.    //以下就是著名的 冒泡法排序。详细讲解请找百度。
  53.    for(i=0;i<(const_array_size-1);i++)  //冒泡的次数是(const_array_size-1)次
  54.    {
  55.       for(k=0;k<(const_array_size-1-i);k++) //每次冒泡的过程中,需要两两比较的次数是(const_array_size-1-i)
  56.           {
  57.              if(ucGlobalBuffer_2[const_array_size-1-k]>ucGlobalBuffer_2[const_array_size-1-1-k])  //后一个与前一个数据两两比较
  58.                  {
  59.                      ucTemp=ucGlobalBuffer_2[const_array_size-1-1-k];     //通过一个中间变量实现两个数据交换
  60.              ucGlobalBuffer_2[const_array_size-1-1-k]=ucGlobalBuffer_2[const_array_size-1-k];
  61.              ucGlobalBuffer_2[const_array_size-1-k]=ucTemp;
  62.                  }
  63.           
  64.           }
  65.    }

  66. }



  67. /* 注释二:
  68. * 第3种方法,为了改进第2种方法的用户体验,用指针为函数多增加一个数组输出接口。
  69. * 这样,函数的数组既有输入接口,又有输出接口,已经堪称完美了。
  70. * 本程序中*p_ucInputBuffer输入接口,*p_ucOutputBuffer是输出接口。
  71. */
  72. void big_to_small_sort_3(unsigned char *p_ucInputBuffer,unsigned char *p_ucOutputBuffer)//第3种方法 把一个数组从大到小排序
  73. {
  74.    unsigned char i;
  75.    unsigned char k;
  76.    unsigned char ucTemp; //在两两交换数据的过程中,用于临时存放交换的某个变量
  77.    unsigned char ucBuffer_3[const_array_size]; //第3种方法,参与具体排序算法的局部变量数组

  78.    for(i=0;i<const_array_size;i++)  
  79.    {
  80.       ucBuffer_3[i]=p_ucInputBuffer[i];  //参与排序算法之前,先把输入接口的数据全部搬移到局部变量数组中。
  81.    }


  82.    //以下就是著名的 冒泡法排序。详细讲解请找百度。
  83.    for(i=0;i<(const_array_size-1);i++)  //冒泡的次数是(const_array_size-1)次
  84.    {
  85.       for(k=0;k<(const_array_size-1-i);k++) //每次冒泡的过程中,需要两两比较的次数是(const_array_size-1-i)
  86.           {
  87.              if(ucBuffer_3[const_array_size-1-k]>ucBuffer_3[const_array_size-1-1-k])  //后一个与前一个数据两两比较
  88.                  {
  89.                      ucTemp=ucBuffer_3[const_array_size-1-1-k];     //通过一个中间变量实现两个数据交换
  90.              ucBuffer_3[const_array_size-1-1-k]=ucBuffer_3[const_array_size-1-k];
  91.              ucBuffer_3[const_array_size-1-k]=ucTemp;
  92.                  }
  93.           
  94.           }
  95.    }


  96.    for(i=0;i<const_array_size;i++)  
  97.    {
  98.       p_ucOutputBuffer[i]=ucBuffer_3[i];  //参与排序算法之后,把运算结果的数据全部搬移到输出接口中,方便外面程序调用
  99.    }
  100. }




  101. void usart_service(void)  //串口服务程序,在main函数里
  102. {

  103.      unsigned char i=0;   

  104.      if(uiSendCnt>=const_receive_time&&ucSendLock==1) //说明超过了一定的时间内,再也没有新数据从串口来
  105.      {

  106.             ucSendLock=0;    //处理一次就锁起来,不用每次都进来,除非有新接收的数据

  107.             //下面的代码进入数据协议解析和数据处理的阶段

  108.             uiRcMoveIndex=0; //由于是判断数据头,所以下标移动变量从数组的0开始向最尾端移动

  109.             while(uiRcregTotal>=5&&uiRcMoveIndex<=(uiRcregTotal-5))
  110.             {
  111.                if(ucRcregBuf[uiRcMoveIndex+0]==0xeb&&ucRcregBuf[uiRcMoveIndex+1]==0x00&&ucRcregBuf[uiRcMoveIndex+2]==0x55)  //数据头eb 00 55的判断
  112.                {


  113.                                   for(i=0;i<const_array_size;i++)
  114.                                   {
  115.                      ucUsartBuffer[i]=ucRcregBuf[uiRcMoveIndex+3+i]; //从串口接收到的需要被排序的原始数据
  116.                                   }


  117.                   //第2种运算方法,依靠指针为函数增加一个数组的输入接口
  118.                                   //通过指针输入接口,直接把ucUsartBuffer数组的首地址传址进去,排序后输出的结果还是保存在ucGlobalBuffer_2全局变量数组中
  119.                   big_to_small_sort_2(ucUsartBuffer);
  120.                   for(i=0;i<const_array_size;i++)
  121.                                   {
  122.                                     eusart_send(ucGlobalBuffer_2[i]);  //把用第2种方法排序后的结果返回给上位机观察
  123.                                   }


  124.                                   eusart_send(0xee);  //为了方便上位机观察,多发送3个字节ee ee ee作为第2种方法与第3种方法的分割线
  125.                                   eusart_send(0xee);
  126.                                   eusart_send(0xee);

  127.                   //第3种运算方法,依靠指针为函数增加一个数组的输出接口
  128.                                   //通过指针输出接口,排序运算后的结果直接从这个输出口中导出到ucGlobalBuffer_3数组中
  129.                   big_to_small_sort_3(ucUsartBuffer,ucGlobalBuffer_3);   //ucUsartBuffer是输入的数组,ucGlobalBuffer_3是接收排序结果的数组
  130.                   for(i=0;i<const_array_size;i++)
  131.                                   {
  132.                                     eusart_send(ucGlobalBuffer_3[i]);  //把用第3种方法排序后的结果返回给上位机观察
  133.                                   }



  134.                   break;   //退出循环
  135.                }
  136.                uiRcMoveIndex++; //因为是判断数据头,游标向着数组最尾端的方向移动
  137.            }
  138.                                          
  139.            uiRcregTotal=0;  //清空缓冲的下标,方便下次重新从0下标开始接受新数据
  140.   
  141.      }
  142.                         
  143. }

  144. void eusart_send(unsigned char ucSendData) //往上位机发送一个字节的函数
  145. {

  146.   ES = 0; //关串口中断
  147.   TI = 0; //清零串口发送完成中断请求标志
  148.   SBUF =ucSendData; //发送一个字节

  149.   delay_short(400);  //每个字节之间的延时,这里非常关键,也是最容易出错的地方。延时的大小请根据实际项目来调整

  150.   TI = 0; //清零串口发送完成中断请求标志
  151.   ES = 1; //允许串口中断

  152. }



  153. void T0_time(void) interrupt 1    //定时中断
  154. {
  155.   TF0=0;  //清除中断标志
  156.   TR0=0; //关中断


  157.   if(uiSendCnt<const_receive_time)   //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完
  158.   {
  159.           uiSendCnt++;    //表面上这个数据不断累加,但是在串口中断里,每接收一个字节它都会被清零,除非这个中间没有串口数据过来
  160.       ucSendLock=1;     //开自锁标志
  161.   }



  162.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  163.   TL0=0x0b;
  164.   TR0=1;  //开中断
  165. }


  166. void usart_receive(void) interrupt 4                 //串口接收数据中断        
  167. {        

  168.    if(RI==1)  
  169.    {
  170.         RI = 0;

  171.             ++uiRcregTotal;
  172.         if(uiRcregTotal>const_rc_size)  //超过缓冲区
  173.         {
  174.            uiRcregTotal=const_rc_size;
  175.         }
  176.         ucRcregBuf[uiRcregTotal-1]=SBUF;   //将串口接收到的数据缓存到接收缓冲区里
  177.         uiSendCnt=0;  //及时喂狗,虽然main函数那边不断在累加,但是只要串口的数据还没发送完毕,那么它永远也长不大,因为每个中断都被清零。
  178.    
  179.    }
  180.    else  //发送中断,及时把发送中断标志位清零
  181.    {
  182.         TI = 0;
  183.    }
  184.                                                          
  185. }                                


  186. void delay_long(unsigned int uiDelayLong)
  187. {
  188.    unsigned int i;
  189.    unsigned int j;
  190.    for(i=0;i<uiDelayLong;i++)
  191.    {
  192.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  193.           {
  194.              ; //一个分号相当于执行一条空语句
  195.           }
  196.    }
  197. }

  198. void delay_short(unsigned int uiDelayShort)
  199. {
  200.    unsigned int i;  
  201.    for(i=0;i<uiDelayShort;i++)
  202.    {
  203.      ;   //一个分号相当于执行一条空语句
  204.    }
  205. }


  206. void initial_myself(void)  //第一区 初始化单片机
  207. {

  208.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

  209.   //配置定时器
  210.   TMOD=0x01;  //设置定时器0为工作方式1
  211.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  212.   TL0=0x0b;


  213.   //配置串口
  214.   SCON=0x50;
  215.   TMOD=0X21;
  216.   TH1=TL1=-(11059200L/12/32/9600);  //这段配置代码具体是什么意思,我也不太清楚,反正是跟串口波特率有关。
  217.   TR1=1;

  218. }

  219. void initial_peripheral(void) //第二区 初始化外围
  220. {

  221.    EA=1;     //开总中断
  222.    ES=1;     //允许串口中断
  223.    ET0=1;    //允许定时中断
  224.    TR0=1;    //启动定时中断

  225. }
复制代码

总结陈词:
通过本节程序的讲解,一部分细心的读者可能会发现一个规律,其实所谓指针作为数组在函数中的输入接口和输出接口,输入接口的指针跟输出接口的指针在语法上没有任何区别,我没有用到C语言中专门的关键词去限定某个指针是输入,某个指针是输出,因此,这个告诉我们什么道理?指针在函数的接口中,天生就是既可以做输入,也可以是做输出,它是双向性的,不像普通的函数变量形参只能做输入。发现了这个秘密,我们可不可以把本节程序中的输入接口和输出接口合并成一个输入输出接口呢?当然可以。欲知详情,请听下回分解-----指针的第四大好处,指针作为数组在函数中的输入输出接口。

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

作者: jianhong_wu    时间: 2014-7-10 17:52
第五十六节:指针的第四大好处,指针作为数组在函数中的输入输出接口。

开场白:
通过前面几个章节的学习,我们知道指针在函数的接口中,天生就是既可以做输入,也可以是做输出,它是双向性的,类似全局变量的特点。我们根据实际项目的情况,在必要的时候可以直接把输入接口和输出接口合并在一起,这种方法的缺点是没有把输入和输出分开,没有那么直观。但是优点也是很明显的,就是比较省程序ROM容量和数据RAM容量,而且运行效率也比较快。这一节要教大家一个知识点:指针作为数组在函数中输入输出接口的特点。

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

(1)硬件平台:
基于坚鸿51单片机学习板。

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

比如电脑发送: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)源代码讲解如下:
  1. #include "REG52.H"


  2. #define const_array_size  5  //参与排序的数组大小

  3. #define const_voice_short  40   //蜂鸣器短叫的持续时间
  4. #define const_rc_size  10  //接收串口中断数据的缓冲区数组大小

  5. #define const_receive_time  5  //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完,这个时间根据实际情况来调整大小

  6. void initial_myself(void);   
  7. void initial_peripheral(void);
  8. void delay_long(unsigned int uiDelaylong);
  9. void delay_short(unsigned int uiDelayShort);


  10. void T0_time(void);  //定时中断函数
  11. void usart_receive(void); //串口接收中断函数
  12. void usart_service(void);  //串口服务程序,在main函数里


  13. void eusart_send(unsigned char ucSendData);

  14. void big_to_small_sort_3(unsigned char *p_ucInputBuffer,unsigned char *p_ucOutputBuffer);//第3种方法 把一个数组从大到小排序
  15. void big_to_small_sort_4(unsigned char *p_ucInputAndOutputBuffer);//第4种方法 把一个数组从大到小排序
  16. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口

  17. unsigned int  uiSendCnt=0;     //用来识别串口是否接收完一串数据的计时器
  18. unsigned char ucSendLock=1;    //串口服务程序的自锁变量,每次接收完一串数据只处理一次
  19. unsigned int  uiRcregTotal=0;  //代表当前缓冲区已经接收了多少个数据
  20. unsigned char ucRcregBuf[const_rc_size]; //接收串口中断数据的缓冲区数组
  21. unsigned int  uiRcMoveIndex=0;  //用来解析数据协议的中间变量

  22. unsigned char ucUsartBuffer[const_array_size];  //从串口接收到的需要排序的原始数据

  23. unsigned char ucGlobalBuffer_3[const_array_size]; //第3种方法,用来接收输出接口数据的全局变量数组
  24. unsigned char ucGlobalBuffer_4[const_array_size]; //第4种方法,用来输入和输出接口数据的全局变量数组
  25. void main()
  26.   {
  27.    initial_myself();  
  28.    delay_long(100);   
  29.    initial_peripheral();
  30.    while(1)  
  31.    {
  32.        usart_service();  //串口服务程序
  33.    }

  34. }




  35. /* 注释一:
  36. * 第3种方法,为了改进第2种方法的用户体验,用指针为函数多增加一个数组输出接口。
  37. * 这样,函数的数组既有输入接口,又有输出接口,已经堪称完美了。
  38. * 本程序中*p_ucInputBuffer输入接口,*p_ucOutputBuffer是输出接口。
  39. */
  40. void big_to_small_sort_3(unsigned char *p_ucInputBuffer,unsigned char *p_ucOutputBuffer)//第3种方法 把一个数组从大到小排序
  41. {
  42.    unsigned char i;
  43.    unsigned char k;
  44.    unsigned char ucTemp; //在两两交换数据的过程中,用于临时存放交换的某个变量
  45.    unsigned char ucBuffer_3[const_array_size]; //第3种方法,参与具体排序算法的局部变量数组

  46.    for(i=0;i<const_array_size;i++)  
  47.    {
  48.       ucBuffer_3[i]=p_ucInputBuffer[i];  //参与排序算法之前,先把输入接口的数据全部搬移到局部变量数组中。
  49.    }


  50.    //以下就是著名的 冒泡法排序。详细讲解请找百度。
  51.    for(i=0;i<(const_array_size-1);i++)  //冒泡的次数是(const_array_size-1)次
  52.    {
  53.       for(k=0;k<(const_array_size-1-i);k++) //每次冒泡的过程中,需要两两比较的次数是(const_array_size-1-i)
  54.           {
  55.              if(ucBuffer_3[const_array_size-1-k]>ucBuffer_3[const_array_size-1-1-k])  //后一个与前一个数据两两比较
  56.                  {
  57.                      ucTemp=ucBuffer_3[const_array_size-1-1-k];     //通过一个中间变量实现两个数据交换
  58.              ucBuffer_3[const_array_size-1-1-k]=ucBuffer_3[const_array_size-1-k];
  59.              ucBuffer_3[const_array_size-1-k]=ucTemp;
  60.                  }
  61.           
  62.           }
  63.    }


  64.    for(i=0;i<const_array_size;i++)  
  65.    {
  66.       p_ucOutputBuffer[i]=ucBuffer_3[i];  //参与排序算法之后,把运算结果的数据全部搬移到输出接口中,方便外面程序调用
  67.    }
  68. }


  69. /* 注释二:
  70. * 第4种方法.指针在函数的接口中,天生就是既可以做输入,也可以是做输出,它是双向性的,类似全局变量的特点。
  71. * 我们可以根据实际项目的情况,在必要的时候可以直接把输入接口和输出接口合并在一起,
  72. * 这种方法的缺点是没有把输入和输出分开,没有那么直观。但是优点也是很明显的,就是比较
  73. * 省程序ROM容量和数据RAM容量,而且运行效率也比较快。现在介绍给大家。
  74. * 本程序的*p_ucInputAndOutputBuffer是输入输出接口。
  75. */
  76. void big_to_small_sort_4(unsigned char *p_ucInputAndOutputBuffer)//第4种方法 把一个数组从大到小排序
  77. {
  78.    unsigned char i;
  79.    unsigned char k;
  80.    unsigned char ucTemp; //在两两交换数据的过程中,用于临时存放交换的某个变量

  81.    //以下就是著名的 冒泡法排序。详细讲解请找百度。
  82.    for(i=0;i<(const_array_size-1);i++)  //冒泡的次数是(const_array_size-1)次
  83.    {
  84.       for(k=0;k<(const_array_size-1-i);k++) //每次冒泡的过程中,需要两两比较的次数是(const_array_size-1-i)
  85.           {
  86.              if(p_ucInputAndOutputBuffer[const_array_size-1-k]>p_ucInputAndOutputBuffer[const_array_size-1-1-k])  //后一个与前一个数据两两比较
  87.                  {
  88.                      ucTemp=p_ucInputAndOutputBuffer[const_array_size-1-1-k];     //通过一个中间变量实现两个数据交换
  89.              p_ucInputAndOutputBuffer[const_array_size-1-1-k]=p_ucInputAndOutputBuffer[const_array_size-1-k];
  90.              p_ucInputAndOutputBuffer[const_array_size-1-k]=ucTemp;
  91.                  }
  92.           
  93.           }
  94.    }


  95. }


  96. void usart_service(void)  //串口服务程序,在main函数里
  97. {

  98.      unsigned char i=0;   

  99.      if(uiSendCnt>=const_receive_time&&ucSendLock==1) //说明超过了一定的时间内,再也没有新数据从串口来
  100.      {

  101.             ucSendLock=0;    //处理一次就锁起来,不用每次都进来,除非有新接收的数据

  102.             //下面的代码进入数据协议解析和数据处理的阶段

  103.             uiRcMoveIndex=0; //由于是判断数据头,所以下标移动变量从数组的0开始向最尾端移动

  104.             while(uiRcregTotal>=5&&uiRcMoveIndex<=(uiRcregTotal-5))
  105.             {
  106.                if(ucRcregBuf[uiRcMoveIndex+0]==0xeb&&ucRcregBuf[uiRcMoveIndex+1]==0x00&&ucRcregBuf[uiRcMoveIndex+2]==0x55)  //数据头eb 00 55的判断
  107.                {


  108.                                   for(i=0;i<const_array_size;i++)
  109.                                   {
  110.                      ucUsartBuffer[i]=ucRcregBuf[uiRcMoveIndex+3+i]; //从串口接收到的需要被排序的原始数据
  111.                                   }


  112.                   //第3种运算方法,依靠指针为函数增加一个数组的输出接口
  113.                                   //通过指针输出接口,排序运算后的结果直接从这个输出口中导出到ucGlobalBuffer_3数组中
  114.                   big_to_small_sort_3(ucUsartBuffer,ucGlobalBuffer_3);   //ucUsartBuffer是输入的数组,ucGlobalBuffer_3是接收排序结果的数组
  115.                   for(i=0;i<const_array_size;i++)
  116.                                   {
  117.                                     eusart_send(ucGlobalBuffer_3[i]);  //把用第3种方法排序后的结果返回给上位机观察
  118.                                   }

  119.                                   eusart_send(0xee);  //为了方便上位机观察,多发送3个字节ee ee ee作为第2种方法与第3种方法的分割线
  120.                                   eusart_send(0xee);
  121.                                   eusart_send(0xee);

  122.                   //第4种运算方法,依靠一个指针作为函数的输入输出接口。
  123.                                   //通过这个指针输入输出接口,ucGlobalBuffer_4数组既是输入数组,也是输出数组,排序运算后的结果直接存放在它本身,类似于全局变量的特点。
  124.                                   for(i=0;i<const_array_size;i++)
  125.                                   {
  126.                      ucGlobalBuffer_4[i]=ucUsartBuffer[i]; //把需要被排序的原始数据传递给接收输入输出数组ucGlobalBuffer_4,
  127.                                   }
  128.                   big_to_small_sort_4(ucGlobalBuffer_4);  
  129.                   for(i=0;i<const_array_size;i++)
  130.                                   {
  131.                                     eusart_send(ucGlobalBuffer_4[i]);  //把用第4种方法排序后的结果返回给上位机观察
  132.                                   }


  133.                   break;   //退出循环
  134.                }
  135.                uiRcMoveIndex++; //因为是判断数据头,游标向着数组最尾端的方向移动
  136.            }
  137.                                          
  138.            uiRcregTotal=0;  //清空缓冲的下标,方便下次重新从0下标开始接受新数据
  139.   
  140.      }
  141.                         
  142. }

  143. void eusart_send(unsigned char ucSendData) //往上位机发送一个字节的函数
  144. {

  145.   ES = 0; //关串口中断
  146.   TI = 0; //清零串口发送完成中断请求标志
  147.   SBUF =ucSendData; //发送一个字节

  148.   delay_short(400);  //每个字节之间的延时,这里非常关键,也是最容易出错的地方。延时的大小请根据实际项目来调整

  149.   TI = 0; //清零串口发送完成中断请求标志
  150.   ES = 1; //允许串口中断

  151. }



  152. void T0_time(void) interrupt 1    //定时中断
  153. {
  154.   TF0=0;  //清除中断标志
  155.   TR0=0; //关中断


  156.   if(uiSendCnt<const_receive_time)   //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完
  157.   {
  158.           uiSendCnt++;    //表面上这个数据不断累加,但是在串口中断里,每接收一个字节它都会被清零,除非这个中间没有串口数据过来
  159.       ucSendLock=1;     //开自锁标志
  160.   }



  161.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  162.   TL0=0x0b;
  163.   TR0=1;  //开中断
  164. }


  165. void usart_receive(void) interrupt 4                 //串口接收数据中断        
  166. {        

  167.    if(RI==1)  
  168.    {
  169.         RI = 0;

  170.             ++uiRcregTotal;
  171.         if(uiRcregTotal>const_rc_size)  //超过缓冲区
  172.         {
  173.            uiRcregTotal=const_rc_size;
  174.         }
  175.         ucRcregBuf[uiRcregTotal-1]=SBUF;   //将串口接收到的数据缓存到接收缓冲区里
  176.         uiSendCnt=0;  //及时喂狗,虽然main函数那边不断在累加,但是只要串口的数据还没发送完毕,那么它永远也长不大,因为每个中断都被清零。
  177.    
  178.    }
  179.    else  //发送中断,及时把发送中断标志位清零
  180.    {
  181.         TI = 0;
  182.    }
  183.                                                          
  184. }                                


  185. void delay_long(unsigned int uiDelayLong)
  186. {
  187.    unsigned int i;
  188.    unsigned int j;
  189.    for(i=0;i<uiDelayLong;i++)
  190.    {
  191.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  192.           {
  193.              ; //一个分号相当于执行一条空语句
  194.           }
  195.    }
  196. }

  197. void delay_short(unsigned int uiDelayShort)
  198. {
  199.    unsigned int i;  
  200.    for(i=0;i<uiDelayShort;i++)
  201.    {
  202.      ;   //一个分号相当于执行一条空语句
  203.    }
  204. }


  205. void initial_myself(void)  //第一区 初始化单片机
  206. {

  207.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

  208.   //配置定时器
  209.   TMOD=0x01;  //设置定时器0为工作方式1
  210.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  211.   TL0=0x0b;


  212.   //配置串口
  213.   SCON=0x50;
  214.   TMOD=0X21;
  215.   TH1=TL1=-(11059200L/12/32/9600);  //这段配置代码具体是什么意思,我也不太清楚,反正是跟串口波特率有关。
  216.   TR1=1;

  217. }

  218. void initial_peripheral(void) //第二区 初始化外围
  219. {

  220.    EA=1;     //开总中断
  221.    ES=1;     //允许串口中断
  222.    ET0=1;    //允许定时中断
  223.    TR0=1;    //启动定时中断

  224. }
复制代码

总结陈词:
通过本章的学习,我们知道指针在函数接口中的双向性,这个双向性是一把双刃剑,既给我们带来便捷,也给我们在以下两个场合中带来隐患。
第一个场合:当需要把输入接口和输出接口分开时,我们希望输入接口的参数不要被意外改变,改变的仅仅只能是输出接口的数据。但是指针的双向性,就有可能导致我们在写函数内部代码的时候一不小心改变而没有发觉。
第二个场合:如果是一个现成封装好的函数直接给我们调用,当我们发现是指针作为接口的时候,我们就不敢确定这个接口是输入接口,还是输出接口,或者是输入输出接口,我们传递进去的参数可能会更改,除非用之前进行数据备份,否则是没有安全感可言的。
有没有办法巧妙的解决以上两个问题?当然有。欲知详情,请听下回分解-----为指针加上紧箍咒const,避免意外修改了只做输入接口的数据。

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

作者: jianhong_wu    时间: 2014-7-13 08:31
第五十七节:为指针加上紧箍咒const,避免意外修改了只做输入接口的数据。

开场白:
通过上一节的学习,我们知道指针在函数接口中具有双向性,这个双向性是一把双刃剑,既给我们带来便捷,也给我们带来隐患。这一节要教大家以下知识点:
凡是做输入接口的指针,都应该加上const标签来标识,它可以让原来双向性的接口变成了单向性接口,它有两个好处:
第一个:如果你是用别人已经封装好的函数,你发现接口指针带了const标签,就足以说明这个指针只能做输入接口,你用了它,不用担心输入数据被修改。
第二个:如果是你自己写的函数,你在输入接口处的指针加了const标签,它可以预防你在写函数内部代码时不小心修改了输入接口的数据。比如,你试着在函数内部更改带const标签的输入接口数据,当你点击编译时,会编译不过,出现错误提示:error C183: unmodifiable lvalue。这就是一道防火墙啊!

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

(1)硬件平台:
基于坚鸿51单片机学习板。

(2)实现功能:
我只是把第55节中凡是输入接口数据的指针都加了const关键字标签,其它代码内容没变。
把5个随机数据按从大到小排序,用冒泡法来排序。
通过电脑串口调试助手,往单片机发送EB 00 55 08 06 09 05 07  指令,其中EB 00 55是数据头,08 06 09 05 07 是参与排序的5个随机原始数据。单片机收到指令后就会返回13个数据,最前面5个数据是第3种方法的排序结果,中间3个数据EE EE EE是第3种和第4种的分割线,为了方便观察,没实际意义。最后5个数据是第4种方法的排序结果.

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

波特率是:9600 。

(3)源代码讲解如下:
  1. #include "REG52.H"


  2. #define const_array_size  5  //参与排序的数组大小

  3. #define const_voice_short  40   //蜂鸣器短叫的持续时间
  4. #define const_rc_size  10  //接收串口中断数据的缓冲区数组大小

  5. #define const_receive_time  5  //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完,这个时间根据实际情况来调整大小

  6. void initial_myself(void);   
  7. void initial_peripheral(void);
  8. void delay_long(unsigned int uiDelaylong);
  9. void delay_short(unsigned int uiDelayShort);


  10. void T0_time(void);  //定时中断函数
  11. void usart_receive(void); //串口接收中断函数
  12. void usart_service(void);  //串口服务程序,在main函数里


  13. void eusart_send(unsigned char ucSendData);


  14. void big_to_small_sort_2(const unsigned char *p_ucInputBuffer);//第2种方法 把一个数组从大到小排序
  15. void big_to_small_sort_3(const unsigned char *p_ucInputBuffer,unsigned char *p_ucOutputBuffer);//第3种方法 把一个数组从大到小排序
  16. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口

  17. unsigned int  uiSendCnt=0;     //用来识别串口是否接收完一串数据的计时器
  18. unsigned char ucSendLock=1;    //串口服务程序的自锁变量,每次接收完一串数据只处理一次
  19. unsigned int  uiRcregTotal=0;  //代表当前缓冲区已经接收了多少个数据
  20. unsigned char ucRcregBuf[const_rc_size]; //接收串口中断数据的缓冲区数组
  21. unsigned int  uiRcMoveIndex=0;  //用来解析数据协议的中间变量

  22. unsigned char ucUsartBuffer[const_array_size];  //从串口接收到的需要排序的原始数据

  23. unsigned char ucGlobalBuffer_2[const_array_size]; //第2种方法,参与具体排序算法的全局变量数组
  24. unsigned char ucGlobalBuffer_3[const_array_size]; //第3种方法,用来接收输出接口数据的全局变量数组

  25. void main()
  26.   {
  27.    initial_myself();  
  28.    delay_long(100);   
  29.    initial_peripheral();
  30.    while(1)  
  31.    {
  32.        usart_service();  //串口服务程序
  33.    }

  34. }


  35. void big_to_small_sort_2(const unsigned char *p_ucInputBuffer)//第2种方法 把一个数组从大到小排序
  36. {
  37.    unsigned char i;
  38.    unsigned char k;
  39.    unsigned char ucTemp; //在两两交换数据的过程中,用于临时存放交换的某个变量


  40.    for(i=0;i<const_array_size;i++)  
  41.    {
  42.       ucGlobalBuffer_2[i]=p_ucInputBuffer[i];  //参与排序算法之前,先把输入接口的数据全部搬移到全局变量数组中。
  43.    }


  44.    //以下就是著名的 冒泡法排序。详细讲解请找百度。
  45.    for(i=0;i<(const_array_size-1);i++)  //冒泡的次数是(const_array_size-1)次
  46.    {
  47.       for(k=0;k<(const_array_size-1-i);k++) //每次冒泡的过程中,需要两两比较的次数是(const_array_size-1-i)
  48.           {
  49.              if(ucGlobalBuffer_2[const_array_size-1-k]>ucGlobalBuffer_2[const_array_size-1-1-k])  //后一个与前一个数据两两比较
  50.                  {
  51.                      ucTemp=ucGlobalBuffer_2[const_array_size-1-1-k];     //通过一个中间变量实现两个数据交换
  52.              ucGlobalBuffer_2[const_array_size-1-1-k]=ucGlobalBuffer_2[const_array_size-1-k];
  53.              ucGlobalBuffer_2[const_array_size-1-k]=ucTemp;
  54.                  }
  55.          
  56.           }
  57.    }


  58. }



  59. /* 注释一:
  60. * 凡是做输入接口的指针,都应该加上const标签来标识,它可以让原来双向性的接口变成了单向性接口,它有两个好处:
  61. * 第一个:如果你是用别人已经封装好的函数,你发现接口指针带了const标签,就足以说明
  62. * 这个指针只能做输入接口,你用了它,不用担心输入数据被修改。
  63. * 第二个:如果是你自己写的函数,你在输入接口处的指针加了const标签,它可以预防你在写函数内部代码时
  64. * 不小心修改了输入接口的数据。比如,你试着在以下函数最后的地方加一条更改输入接口数据的指令,
  65. * 当你点击编译时,会编译不过,出现错误提示:error C183: unmodifiable lvalue。
  66. */
  67. void big_to_small_sort_3(const unsigned char *p_ucInputBuffer,unsigned char *p_ucOutputBuffer)//第3种方法 把一个数组从大到小排序
  68. {
  69.    unsigned char i;
  70.    unsigned char k;
  71.    unsigned char ucTemp; //在两两交换数据的过程中,用于临时存放交换的某个变量
  72.    unsigned char ucBuffer_3[const_array_size]; //第3种方法,参与具体排序算法的局部变量数组

  73.    for(i=0;i<const_array_size;i++)  
  74.    {
  75.       ucBuffer_3[i]=p_ucInputBuffer[i];  //参与排序算法之前,先把输入接口的数据全部搬移到局部变量数组中。
  76.    }


  77.    //以下就是著名的 冒泡法排序。详细讲解请找百度。
  78.    for(i=0;i<(const_array_size-1);i++)  //冒泡的次数是(const_array_size-1)次
  79.    {
  80.       for(k=0;k<(const_array_size-1-i);k++) //每次冒泡的过程中,需要两两比较的次数是(const_array_size-1-i)
  81.           {
  82.              if(ucBuffer_3[const_array_size-1-k]>ucBuffer_3[const_array_size-1-1-k])  //后一个与前一个数据两两比较
  83.                  {
  84.                      ucTemp=ucBuffer_3[const_array_size-1-1-k];     //通过一个中间变量实现两个数据交换
  85.              ucBuffer_3[const_array_size-1-1-k]=ucBuffer_3[const_array_size-1-k];
  86.              ucBuffer_3[const_array_size-1-k]=ucTemp;
  87.                  }
  88.          
  89.           }
  90.    }


  91.    for(i=0;i<const_array_size;i++)  
  92.    {
  93.       p_ucOutputBuffer[i]=ucBuffer_3[i];  //参与排序算法之后,把运算结果的数据全部搬移到输出接口中,方便外面程序调用
  94.    }


  95. /* 注释二:
  96. * 以下这条是企图修改输入接口数据的指令,如果不屏蔽,编译的时候就会出错提醒:error C183: unmodifiable lvalue?
  97. */

  98.    //p_ucInputBuffer[0]=0;  //修改输入接口数据的指令
  99. }




  100. void usart_service(void)  //串口服务程序,在main函数里
  101. {

  102.      unsigned char i=0;   

  103.      if(uiSendCnt>=const_receive_time&&ucSendLock==1) //说明超过了一定的时间内,再也没有新数据从串口来
  104.      {

  105.             ucSendLock=0;    //处理一次就锁起来,不用每次都进来,除非有新接收的数据

  106.             //下面的代码进入数据协议解析和数据处理的阶段

  107.             uiRcMoveIndex=0; //由于是判断数据头,所以下标移动变量从数组的0开始向最尾端移动

  108.             while(uiRcregTotal>=5&&uiRcMoveIndex<=(uiRcregTotal-5))
  109.             {
  110.                if(ucRcregBuf[uiRcMoveIndex+0]==0xeb&&ucRcregBuf[uiRcMoveIndex+1]==0x00&&ucRcregBuf[uiRcMoveIndex+2]==0x55)  //数据头eb 00 55的判断
  111.                {


  112.                                   for(i=0;i<const_array_size;i++)
  113.                                   {
  114.                      ucUsartBuffer[i]=ucRcregBuf[uiRcMoveIndex+3+i]; //从串口接收到的需要被排序的原始数据
  115.                                   }


  116.                   //第2种运算方法,依靠指针为函数增加一个数组的输入接口
  117.                                   //通过指针输入接口,直接把ucUsartBuffer数组的首地址传址进去,排序后输出的结果还是保存在ucGlobalBuffer_2全局变量数组中
  118.                   big_to_small_sort_2(ucUsartBuffer);
  119.                   for(i=0;i<const_array_size;i++)
  120.                                   {
  121.                                     eusart_send(ucGlobalBuffer_2[i]);  //把用第2种方法排序后的结果返回给上位机观察
  122.                                   }


  123.                                   eusart_send(0xee);  //为了方便上位机观察,多发送3个字节ee ee ee作为第2种方法与第3种方法的分割线
  124.                                   eusart_send(0xee);
  125.                                   eusart_send(0xee);

  126.                   //第3种运算方法,依靠指针为函数增加一个数组的输出接口
  127.                                   //通过指针输出接口,排序运算后的结果直接从这个输出口中导出到ucGlobalBuffer_3数组中
  128.                   big_to_small_sort_3(ucUsartBuffer,ucGlobalBuffer_3);   //ucUsartBuffer是输入的数组,ucGlobalBuffer_3是接收排序结果的数组
  129.                   for(i=0;i<const_array_size;i++)
  130.                                   {
  131.                                     eusart_send(ucGlobalBuffer_3[i]);  //把用第3种方法排序后的结果返回给上位机观察
  132.                                   }



  133.                   break;   //退出循环
  134.                }
  135.                uiRcMoveIndex++; //因为是判断数据头,游标向着数组最尾端的方向移动
  136.            }
  137.                                          
  138.            uiRcregTotal=0;  //清空缓冲的下标,方便下次重新从0下标开始接受新数据
  139.   
  140.      }
  141.                         
  142. }

  143. void eusart_send(unsigned char ucSendData) //往上位机发送一个字节的函数
  144. {

  145.   ES = 0; //关串口中断
  146.   TI = 0; //清零串口发送完成中断请求标志
  147.   SBUF =ucSendData; //发送一个字节

  148.   delay_short(400);  //每个字节之间的延时,这里非常关键,也是最容易出错的地方。延时的大小请根据实际项目来调整

  149.   TI = 0; //清零串口发送完成中断请求标志
  150.   ES = 1; //允许串口中断

  151. }



  152. void T0_time(void) interrupt 1    //定时中断
  153. {
  154.   TF0=0;  //清除中断标志
  155.   TR0=0; //关中断


  156.   if(uiSendCnt<const_receive_time)   //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完
  157.   {
  158.           uiSendCnt++;    //表面上这个数据不断累加,但是在串口中断里,每接收一个字节它都会被清零,除非这个中间没有串口数据过来
  159.       ucSendLock=1;     //开自锁标志
  160.   }



  161.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  162.   TL0=0x0b;
  163.   TR0=1;  //开中断
  164. }


  165. void usart_receive(void) interrupt 4                 //串口接收数据中断        
  166. {        

  167.    if(RI==1)  
  168.    {
  169.         RI = 0;

  170.             ++uiRcregTotal;
  171.         if(uiRcregTotal>const_rc_size)  //超过缓冲区
  172.         {
  173.            uiRcregTotal=const_rc_size;
  174.         }
  175.         ucRcregBuf[uiRcregTotal-1]=SBUF;   //将串口接收到的数据缓存到接收缓冲区里
  176.         uiSendCnt=0;  //及时喂狗,虽然main函数那边不断在累加,但是只要串口的数据还没发送完毕,那么它永远也长不大,因为每个中断都被清零。
  177.    
  178.    }
  179.    else  //发送中断,及时把发送中断标志位清零
  180.    {
  181.         TI = 0;
  182.    }
  183.                                                          
  184. }                                


  185. void delay_long(unsigned int uiDelayLong)
  186. {
  187.    unsigned int i;
  188.    unsigned int j;
  189.    for(i=0;i<uiDelayLong;i++)
  190.    {
  191.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  192.           {
  193.              ; //一个分号相当于执行一条空语句
  194.           }
  195.    }
  196. }

  197. void delay_short(unsigned int uiDelayShort)
  198. {
  199.    unsigned int i;  
  200.    for(i=0;i<uiDelayShort;i++)
  201.    {
  202.      ;   //一个分号相当于执行一条空语句
  203.    }
  204. }


  205. void initial_myself(void)  //第一区 初始化单片机
  206. {

  207.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

  208.   //配置定时器
  209.   TMOD=0x01;  //设置定时器0为工作方式1
  210.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  211.   TL0=0x0b;


  212.   //配置串口
  213.   SCON=0x50;
  214.   TMOD=0X21;
  215.   TH1=TL1=-(11059200L/12/32/9600);  //这段配置代码具体是什么意思,我也不太清楚,反正是跟串口波特率有关。
  216.   TR1=1;

  217. }

  218. void initial_peripheral(void) //第二区 初始化外围
  219. {

  220.    EA=1;     //开总中断
  221.    ES=1;     //允许串口中断
  222.    ET0=1;    //允许定时中断
  223.    TR0=1;    //启动定时中断

  224. }
复制代码

总结陈词:
    通过前面几节的学习,我们知道了指针在函数接口中的输入输出用途,以及const关键字的作用。下一节将要讲指针的第五大好处。欲知详情,请听下回分解-----指针的第五大好处,指针在众多数组中的中转站作用。

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

作者: primer    时间: 2014-7-17 14:11
很不错。继续努力。
作者: primer    时间: 2014-7-17 14:19
本帖最后由 primer 于 2014-7-17 14:21 编辑

12345678910
作者: ceshi    时间: 2014-7-17 14:42
12123123123
作者: 黑夜之狼    时间: 2014-7-17 14:50
持续关注                              
作者: tiantan153    时间: 2014-7-17 16:12
鸿哥好样的!
作者: jianhong_wu    时间: 2014-7-17 16:21
tiantan153 发表于 2014-7-17 16:12
鸿哥好样的!

感谢大家支持。
作者: Mr.Caiii    时间: 2014-7-19 10:53
特地过来顶下鸿哥,我坚信,坚持看下去,肯定会有很大的收获,谢谢鸿哥~!!!
作者: jianhong_wu    时间: 2014-7-19 14:17
Mr.Caiii 发表于 2014-7-19 10:53
特地过来顶下鸿哥,我坚信,坚持看下去,肯定会有很大的收获,谢谢鸿哥~!!!

非常感谢。我后续会继续更新。
作者: jianhong_wu    时间: 2014-7-20 06:45
本帖最后由 jianhong_wu 于 2014-7-20 12:53 编辑

第五十八节:指针的第五大好处,指针在众多数组中的中转站作用。
开场白:
    单个变量数据之间可以通过一条指令任意自由赋值转移,但是数组之间不能通过一条指令直接赋值转移,必须用for等循环指令挨个把数组的数据一个一个来赋值转移,如果一个 函数中,有很多数组需要赋值转移,那就非常麻烦了,要用很多for语句,耗时。还好C语言里有个指针,它可以非常高效地来切换我们所需要的数组,起到很好的中转站作用。这一节要教大家一个知识点:指针在众多数组中的中转站作用。
具体内容,请看源代码讲解。
1)硬件平台:
基于朱兆祺51单片机学习板。
2)实现功能:
在第57节的串口收发程序基础上修改。在串口接收函数中,以下代码有略微修改:
while(uiRcregTotal>=4&&uiRcMoveIndex<=(uiRcregTotal-4))//注意,这里是4,不是上一节的5,因为只有eb 00 55 xx4个数据
通过上位机来调用下位机对应的数组数据。
通过电脑串口调试助手,往单片机发送EB 00 55 XX 指令,其中EB 00 55是数据头,XX的取值范围是0x01 0x05,每个不同的值代表调用下位机不同的数组数据。0x01调用第1组数据,0x02调用第2组数据,0x05调用第5组数据。
1组:11 12 13 14 15
2组:21 22 23 24 25
3组:31 32 33 34 35
4组:41 42 43 44 45
5组:51 52 53 54 55
下位机返回21个数据,前面5个是第1种不带指针函数返回的数据。中间5个是第2种不带指针函数返回的数据。最后5个是第3种带指针函数返回的数据。期间2EE EE EE是各函数返回的数据分割线,为了方便观察,没实际意义。
比如电脑发送:EB 0055 02
单片机就返回:21 2223 24 25 EE EE EE 21 22 23 24 25 EE EE EE 21 22 23 24 25
波特率是:9600
3)源代码讲解如下:
  1. #include "REG52.H"


  2. #define const_array_size  5  //参与排序的数组大小

  3. #define const_voice_short  40   //蜂鸣器短叫的持续时间
  4. #define const_rc_size  10  //接收串口中断数据的缓冲区数组大小

  5. #define const_receive_time  5  //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完,这个时间根据实际情况来调整大小

  6. void initial_myself(void);   
  7. void initial_peripheral(void);
  8. void delay_long(unsigned int uiDelaylong);
  9. void delay_short(unsigned int uiDelayShort);


  10. void T0_time(void);  //定时中断函数
  11. void usart_receive(void); //串口接收中断函数
  12. void usart_service(void);  //串口服务程序,在main函数里
  13. void send_array_1(unsigned char ucArraySec);  //第1种函数,不带指针
  14. void send_array_2(unsigned char ucArraySec);  //第2种函数,不带指针
  15. void send_array_3(unsigned char ucArraySec);  //第3种函数,带指针
  16. void eusart_send(unsigned char ucSendData);


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

  18. unsigned int  uiSendCnt=0;     //用来识别串口是否接收完一串数据的计时器
  19. unsigned char ucSendLock=1;    //串口服务程序的自锁变量,每次接收完一串数据只处理一次
  20. unsigned int  uiRcregTotal=0;  //代表当前缓冲区已经接收了多少个数据
  21. unsigned char ucRcregBuf[const_rc_size]; //接收串口中断数据的缓冲区数组
  22. unsigned int  uiRcMoveIndex=0;  //用来解析数据协议的中间变量

  23. const unsigned char array_0x01[]={0x11,0x12,0x13,0x14,0x15}; //第1个常量数组
  24. const unsigned char array_0x02[]={0x21,0x22,0x23,0x24,0x25}; //第2个常量数组
  25. const unsigned char array_0x03[]={0x31,0x32,0x33,0x34,0x35}; //第3个常量数组
  26. const unsigned char array_0x04[]={0x41,0x42,0x43,0x44,0x45}; //第4个常量数组
  27. const unsigned char array_0x05[]={0x51,0x52,0x53,0x54,0x55}; //第5个常量数组

  28. void main()
  29.   {
  30.    initial_myself();  
  31.    delay_long(100);   
  32.    initial_peripheral();
  33.    while(1)  
  34.    {
  35.        usart_service();  //串口服务程序
  36.    }

  37. }

  38. /* 注释一:
  39. * 第1种函数,内部不带指针,根据上位机相关的指令,
  40. * 直接返回对应的数组。由于不带指针,因此多用了5个for循环来搬运数组。
  41. * 比较耗程序ROM容量,也不够简洁清晰。
  42. */
  43. void send_array_1(unsigned char ucArraySec)
  44. {
  45.    unsigned int i;
  46.    switch(ucArraySec)
  47.    {
  48.       case 1:  //直接返回第1个常量数组
  49.                for(i=0;i<5;i++)
  50.                    {
  51.                       eusart_send(array_0x01[i]);
  52.                    }
  53.                break;
  54.       case 2:  //直接返回第2个常量数组
  55.                for(i=0;i<5;i++)
  56.                    {
  57.                       eusart_send(array_0x02[i]);
  58.                    }
  59.                break;
  60.       case 3:  //直接返回第3个常量数组
  61.                for(i=0;i<5;i++)
  62.                    {
  63.                       eusart_send(array_0x03[i]);
  64.                    }
  65.                break;
  66.       case 4:  //直接返回第4个常量数组
  67.                for(i=0;i<5;i++)
  68.                    {
  69.                       eusart_send(array_0x04[i]);
  70.                    }
  71.                break;   
  72.       case 5:  //直接返回第5个常量数组
  73.                for(i=0;i<5;i++)
  74.                    {
  75.                       eusart_send(array_0x05[i]);
  76.                    }
  77.                break;

  78.    }
  79.   
  80. }


  81. /* 注释二:
  82. * 第2种函数,内部不带指针,根据上位机相关的指令,
  83. * 先转移对应的数组放到一个中间变量数组,然后发送数组。
  84. * 由于不带指针,因此多用了6个for循环来搬运数组。
  85. * 跟第1种函数一样,比较耗程序ROM容量,也不够简洁清晰。
  86. */
  87. void send_array_2(unsigned char ucArraySec)  //第2种函数,不带指针
  88. {
  89.    unsigned int i;
  90.    unsigned char array_temp[5]; //临时中间数组
  91.    switch(ucArraySec)
  92.    {
  93.       case 1:  //直接返回第1个常量数组
  94.                for(i=0;i<5;i++)
  95.                    {
  96.                           array_temp[i]=array_0x01[i]; //先挨个把对应的数组数据转移到中间数组里
  97.                    }
  98.                break;
  99.       case 2:  //直接返回第2个常量数组
  100.                for(i=0;i<5;i++)
  101.                    {
  102.                           array_temp[i]=array_0x02[i]; //先挨个把对应的数组数据转移到中间数组里
  103.                    }
  104.                break;
  105.       case 3:  //直接返回第3个常量数组
  106.                for(i=0;i<5;i++)
  107.                    {
  108.                           array_temp[i]=array_0x03[i]; //先挨个把对应的数组数据转移到中间数组里
  109.                    }
  110.                break;
  111.       case 4:  //直接返回第4个常量数组
  112.                for(i=0;i<5;i++)
  113.                    {
  114.                           array_temp[i]=array_0x04[i]; //先挨个把对应的数组数据转移到中间数组里
  115.                    }
  116.                break;   
  117.       case 5:  //直接返回第5个常量数组
  118.                for(i=0;i<5;i++)
  119.                    {
  120.                           array_temp[i]=array_0x05[i]; //先挨个把对应的数组数据转移到中间数组里
  121.                    }
  122.                break;

  123.    }

  124.    for(i=0;i<5;i++)
  125.    {
  126.            eusart_send(array_temp[i]);  //把临时存放在中间数组的数据全部发送出去
  127.    }

  128. }

  129. /* 注释三:
  130. * 第3种函数,内部带指针,根据上位机相关的指令,
  131. * 先把对应的数组首地址传递给一个中间指针,然后再通过
  132. * 指针把整个数组的数据发送出去,由于带指针,切换转移数组的数据非常快,
  133. * 只需传递一下首地址给指针就可以,非常高效,整个函数只用了1个for循环。
  134. * 跟前面第1,2种函数相比,更加节省程序容量,处理速度更加快,更加简洁。
  135. */
  136. void send_array_3(unsigned char ucArraySec)  //第3种函数,带指针
  137. {
  138.    unsigned int i;
  139.    unsigned char *p_array; //临时中间指针,作为数组的中转站,非常高效
  140.    switch(ucArraySec)
  141.    {
  142.       case 1:  //直接返回第1个常量数组
  143.                    p_array=array_0x01;  //把数组的首地址传递给指针,一个指令就可以,不用for来挨个搬移数据,高效!
  144.                break;
  145.       case 2:  //直接返回第2个常量数组
  146.                    p_array=array_0x02;  //把数组的首地址传递给指针,一个指令就可以,不用for来挨个搬移数据,高效!
  147.                break;
  148.       case 3:  //直接返回第3个常量数组
  149.                    p_array=array_0x03;  //把数组的首地址传递给指针,一个指令就可以,不用for来挨个搬移数据,高效!
  150.                break;
  151.       case 4:  //直接返回第4个常量数组
  152.                    p_array=array_0x04;  //把数组的首地址传递给指针,一个指令就可以,不用for来挨个搬移数据,高效!
  153.                break;   
  154.       case 5:  //直接返回第5个常量数组
  155.                    p_array=array_0x05;  //把数组的首地址传递给指针,一个指令就可以,不用for来挨个搬移数据,高效!
  156.                break;

  157.    }

  158.    for(i=0;i<5;i++)
  159.    {
  160.            eusart_send(p_array[i]);  //通过指针把数组的数据全部发送出去
  161.    }

  162. }

  163. void usart_service(void)  //串口服务程序,在main函数里
  164. {

  165.      unsigned char i=0;   
  166.      unsigned char ucWhichArray;
  167.      if(uiSendCnt>=const_receive_time&&ucSendLock==1) //说明超过了一定的时间内,再也没有新数据从串口来
  168.      {

  169.             ucSendLock=0;    //处理一次就锁起来,不用每次都进来,除非有新接收的数据

  170.             //下面的代码进入数据协议解析和数据处理的阶段

  171.             uiRcMoveIndex=0; //由于是判断数据头,所以下标移动变量从数组的0开始向最尾端移动

  172.             while(uiRcregTotal>=4&&uiRcMoveIndex<=(uiRcregTotal-4)) //注意,这里是4,不是上一节的5,因为只有eb 00 55 xx这4个数据
  173.             {
  174.                if(ucRcregBuf[uiRcMoveIndex+0]==0xeb&&ucRcregBuf[uiRcMoveIndex+1]==0x00&&ucRcregBuf[uiRcMoveIndex+2]==0x55)  //数据头eb 00 55的判断
  175.                {

  176.                    ucWhichArray=ucRcregBuf[uiRcMoveIndex+3]; //上位机需要返回的某个数组

  177.                    send_array_1(ucWhichArray); //第1种函数返回数组的5个数据,不带指针

  178.                    eusart_send(0xee);  //为了方便上位机观察,多发送3个字节ee ee ee作为分割线
  179.                    eusart_send(0xee);
  180.                    eusart_send(0xee);

  181.                    send_array_2(ucWhichArray); //第2种函数返回数组的5个数据,不带指针

  182.                    eusart_send(0xee);  //为了方便上位机观察,多发送3个字节ee ee ee作为分割线
  183.                    eusart_send(0xee);
  184.                    eusart_send(0xee);

  185.                    send_array_3(ucWhichArray); //第3种函数返回数组的5个数据,带指针


  186.                    break;   //退出循环
  187.                }
  188.                uiRcMoveIndex++; //因为是判断数据头,游标向着数组最尾端的方向移动
  189.            }
  190.                                          
  191.            uiRcregTotal=0;  //清空缓冲的下标,方便下次重新从0下标开始接受新数据
  192.   
  193.      }
  194.                         
  195. }

  196. void eusart_send(unsigned char ucSendData) //往上位机发送一个字节的函数
  197. {

  198.   ES = 0; //关串口中断
  199.   TI = 0; //清零串口发送完成中断请求标志
  200.   SBUF =ucSendData; //发送一个字节

  201.   delay_short(400);  //每个字节之间的延时,这里非常关键,也是最容易出错的地方。延时的大小请根据实际项目来调整

  202.   TI = 0; //清零串口发送完成中断请求标志
  203.   ES = 1; //允许串口中断

  204. }



  205. void T0_time(void) interrupt 1    //定时中断
  206. {
  207.   TF0=0;  //清除中断标志
  208.   TR0=0; //关中断


  209.   if(uiSendCnt<const_receive_time)   //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完
  210.   {
  211.       uiSendCnt++;    //表面上这个数据不断累加,但是在串口中断里,每接收一个字节它都会被清零,除非这个中间没有串口数据过来
  212.       ucSendLock=1;     //开自锁标志
  213.   }



  214.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  215.   TL0=0x0b;
  216.   TR0=1;  //开中断
  217. }


  218. void usart_receive(void) interrupt 4                 //串口接收数据中断        
  219. {        

  220.    if(RI==1)  
  221.    {
  222.         RI = 0;

  223.         ++uiRcregTotal;
  224.         if(uiRcregTotal>const_rc_size)  //超过缓冲区
  225.         {
  226.            uiRcregTotal=const_rc_size;
  227.         }
  228.         ucRcregBuf[uiRcregTotal-1]=SBUF;   //将串口接收到的数据缓存到接收缓冲区里
  229.         uiSendCnt=0;  //及时喂狗,虽然main函数那边不断在累加,但是只要串口的数据还没发送完毕,那么它永远也长不大,因为每个中断都被清零。
  230.    
  231.    }
  232.    else  //发送中断,及时把发送中断标志位清零
  233.    {
  234.         TI = 0;
  235.    }
  236.                                                          
  237. }                                


  238. void delay_long(unsigned int uiDelayLong)
  239. {
  240.    unsigned int i;
  241.    unsigned int j;
  242.    for(i=0;i<uiDelayLong;i++)
  243.    {
  244.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  245.           {
  246.              ; //一个分号相当于执行一条空语句
  247.           }
  248.    }
  249. }

  250. void delay_short(unsigned int uiDelayShort)
  251. {
  252.    unsigned int i;  
  253.    for(i=0;i<uiDelayShort;i++)
  254.    {
  255.      ;   //一个分号相当于执行一条空语句
  256.    }
  257. }


  258. void initial_myself(void)  //第一区 初始化单片机
  259. {

  260.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

  261.   //配置定时器
  262.   TMOD=0x01;  //设置定时器0为工作方式1
  263.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  264.   TL0=0x0b;


  265.   //配置串口
  266.   SCON=0x50;
  267.   TMOD=0X21;
  268.   TH1=TL1=-(11059200L/12/32/9600);  //这段配置代码具体是什么意思,我也不太清楚,反正是跟串口波特率有关。
  269.   TR1=1;

  270. }

  271. void initial_peripheral(void) //第二区 初始化外围
  272. {

  273.    EA=1;     //开总中断
  274.    ES=1;     //允许串口中断
  275.    ET0=1;    //允许定时中断
  276.    TR0=1;    //启动定时中断

  277. }
复制代码

总结陈词:
通过前面几节的学习,基本上讲完了我平时用指针的所有心得体会。
下一节开始讲新内容。在前面一些章节中,我提到为了防止中断函数把某些共享数据破坏,在主函数中更改某个数据变量时,应该先关闭中断,修改完后再打开中断;我也提到了网友“红金龙吸味”关于原子锁的建议。经过这段时间的思考和总结,我发现不管是关中断开中断,还是原子锁,其实本质上都是程序在多进程中临界点的数据处理,原子锁在程序员中有个专用名词叫互斥量,而我引以为豪的状态机程序框架,主函数的switch语句,外加一个定时中断,本质上就是2个独立进程在不断切换并行运行。我觉得这个临界点处理的知识很重要,也很容易忽略,所以我决定专门用两节内容来讲讲这方面的知识应用。欲知详情,请听下回分解-----关中断和开中断在多进程临界点的应用。
(未完待续,下节更精彩,不要走开哦)

作者: jianhong_wu    时间: 2014-7-21 00:40
第五十九节:串口程序第40,44,45节中存在一个bug,特此紧急公告。
                                 
   经过网友“intech2008”的提醒,在我之前发表的第40,44,45节串口接收程序中,在计算检验和的地方,存在一个不容易发觉的bug。
   原来的是:

  for(i=0;i<(3+1+2+uiRcSize);i++) //计算校验累加和
  {
      ucRcregBuf[uiRcMoveIndex+6+uiRcSize]=ucRcregBuf[uiRcMoveIndex+6+uiRcSize]+ucRcregBuf[i];
  }   

  应该改成:
  for(i=0;i<(3+1+2+uiRcSize);i++) //计算校验累加和
  {
      ucRcregBuf[uiRcMoveIndex+6+uiRcSize]=ucRcregBuf[uiRcMoveIndex+6+uiRcSize]+ucRcregBuf[uiRcMoveIndex+i];
  }   

  由于本连载技术文章在各大论坛发布和被转载,我没法做到处处提醒,不得不专门用一节内容来告知各位读者。

  下节预告-----关中断和开中断在多进程临界点的应用。

(未完待续,下节更精彩,不要走开哦)
作者: ggg    时间: 2014-7-23 13:24
支持。非常好的文章。
作者: jianhong_wu    时间: 2014-7-26 10:26
第六十节:用关中断和互斥量来保护多线程共享的全局变量。

开场白:
在前面一些章节中,我提到为了防止中断函数把某些共享数据破坏,在主函数中更改某个数据变量时,应该先关闭中断,修改完后再打开中断;我也提到了网友“红金龙吸味”关于原子锁的建议。经过这段时间的思考和总结,我发现不管是关中断开中断,还是原子锁,其实本质上都是程序在多进程中临界点的数据处理,原子锁有个专用名词叫互斥量,而我引以为豪的状态机程序框架,主函数的switch语句,外加一个定时中断,本质上就是2个独立进程在不断切换并行运行。
为什么要保护多线程共享的全局变量?因为,多个线程同时访问同一个全局变量,如果都是读取操作,则不会出现问题。如果一个线程负责改变此变量的值,而其他线程负责同时读取变量内容,则不能保证读取到的数据是经过写线程修改后的。
这一节要教大家一个知识点:如何用关中断和互斥量来保护多线程共享的全局变量。

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

(1)硬件平台:
基于坚鸿51单片机学习板。

(2)实现功能:
在第5节的基础上略作修改,让蜂鸣器在前面3秒发生一次短叫报警,在后面6秒发生一次长叫报警,如此反复循环。

(3)源代码讲解如下:
  1. #include "REG52.H"


  2. #define const_time_3s 1332   //3秒钟的时间需要的定时中断次数
  3. #define const_time_6s 2664   //6秒钟的时间需要的定时中断次数

  4. #define const_voice_short  40   //蜂鸣器短叫的持续时间
  5. #define const_voice_long   200  //蜂鸣器长叫的持续时间

  6. void initial_myself();   
  7. void initial_peripheral();
  8. void delay_long(unsigned int uiDelaylong);
  9. void led_flicker();
  10. void alarm_run();   
  11. void T0_time();  //定时中断函数

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

  13. unsigned char ucAlarmStep=0; //报警的步骤变量
  14. unsigned int  uiTimeAlarmCnt=0; //报警统计定时中断次数的延时计数器

  15. unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器

  16. unsigned char ucLock=0;     //互斥量,俗称原子锁
  17. void main()
  18.   {
  19.    initial_myself();  
  20.    delay_long(100);   
  21.    initial_peripheral();
  22.    while(1)  
  23.    {
  24.       alarm_run();   //报警器定时报警
  25.    }

  26. }


  27. /* 注释一:
  28. * 保护多线程共享全局变量的原理:
  29. * 多个线程同时访问同一个全局变量,如果都是读取操作,则不会出现问题。如果一个线程负责改变此变量的值,
  30. * 而其他线程负责同时读取变量内容,则不能保证读取到的数据是经过写线程修改后的。
  31. * 鸿哥的基本程序框架都是两线程为主,一个是main函数线程,一个是定时函数线程。
  32. */

  33. void alarm_run() //报警器的应用程序
  34. {
  35.   
  36.   switch(ucAlarmStep)
  37.   {
  38.      case 0:

  39.            if(uiTimeAlarmCnt>=const_time_3s) //时间到
  40.            {
  41. /* 注释二:
  42. * 用关中断来保护多线程共享的全局变量:
  43. * 因为uiTimeAlarmCnt和uiVoiceCnt都是unsigned int类型,本质上是由两个字节组成。
  44. * 在C语言中uiTimeAlarmCnt=0和uiVoiceCnt=const_voice_short看似一条指令,
  45. * 实际上经过编译之后它不只一条汇编指令。由于另外一个定时中断线程里也会对这个变量
  46. * 进行判断和操作,如果不禁止定时中断或者采取其它措施,定时函数往往会在主函数还没有
  47. * 结束操作共享变量前就去访问或处理这个共享变量,这就会引起冲突,导致系统运行异常。
  48. */
  49.               ET0=0;  //禁止定时中断
  50.               uiTimeAlarmCnt=0; //时间计数器清零
  51.               uiVoiceCnt=const_voice_short;  //蜂鸣器短叫
  52.                           ET0=1; //开启允许定时中断
  53.               ucAlarmStep=1; //切换到下一个步骤
  54.            }
  55.            break;
  56.      case 1:
  57.            if(uiTimeAlarmCnt>=const_time_6s) //时间到
  58.            {
  59. /* 注释三:
  60. * 用互斥量来保护多线程共享的全局变量:
  61. * 我觉得,在这种场合,用互斥量比前面用关中断的方法更加好。
  62. * 因为一旦关闭了定时中断,整个中断函数就会在那一刻停止运行了,
  63. * 而加一个互斥量,既能保护全局变量,又能让定时中断函数正常运行,
  64. * 真是一举两得。
  65. */
  66.                       ucLock=1;  //互斥量加锁。 俗称原子锁
  67.               uiTimeAlarmCnt=0; //时间计数器清零
  68.               uiVoiceCnt=const_voice_long;  //蜂鸣器长叫
  69.                       ucLock=0; //互斥量解锁。  俗称原子锁

  70.               ucAlarmStep=0; //返回到上一个步骤
  71.            }
  72.            break;
  73.   }

  74. }

  75. void T0_time() interrupt 1
  76. {
  77.   TF0=0;  //清除中断标志
  78.   TR0=0; //关中断
  79.   
  80.   if(ucLock==0) //互斥量判断
  81.   {
  82.      if(uiTimeAlarmCnt<0xffff)  //设定这个条件,防止uiTimeAlarmCnt超范围。
  83.      {
  84.          uiTimeAlarmCnt++;  //报警的时间计数器,累加定时中断的次数,
  85.      }

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

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


  101. void delay_long(unsigned int uiDelayLong)
  102. {
  103.    unsigned int i;
  104.    unsigned int j;
  105.    for(i=0;i<uiDelayLong;i++)
  106.    {
  107.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  108.           {
  109.              ; //一个分号相当于执行一条空语句
  110.           }
  111.    }
  112. }


  113. void initial_myself()  //第一区 初始化单片机
  114. {
  115.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

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


  117.   TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
  118.   TL0=0x2f;

  119. }
  120. void initial_peripheral() //第二区 初始化外围
  121. {
  122.   EA=1;     //开总中断
  123.   ET0=1;    //允许定时中断
  124.   TR0=1;    //启动定时中断

  125. }
复制代码

总结陈词:
从下一节开始我准备用几章节的内容来讲常用的数学运算程序。这些程序经常要用在计算器,工控,以及高精度的仪器仪表等领域。C语言的语法中不是已经提供了+,-,*,/这些运算符号吗?为什么还要专门写算法程序?因为那些运算符只能进行简单的运算,一旦数据超过了unsigned long(4个字节)的范围就会出错。而这种大数据算法的程序是什么样的?欲知详情,请听下回分解----大数据的加法运算。

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

作者: 又一个暑假    时间: 2014-7-28 21:16
jianhong_wu 发表于 2014-4-5 11:19
第三十八节:判断数据尾来接收一串数据的串口通用程序框架。

开场白:

同志们:
          如果你们在看鸿哥的程序串口不懂的配置,可以参照我的,我刚开始没调出来是QQ群里的@小傅-惠州告诉我要在中间的  单字符串   发送区就可以的
使用说明书如下

说明书.jpg (90.29 KB, 下载次数: 341)

说明书.jpg

作者: jianhong_wu    时间: 2014-7-28 21:24
又一个暑假 发表于 2014-7-28 21:16
同志们:
          如果你们在看鸿哥的程序串口不懂的配置,可以参照我的,我刚开始没调出来是QQ群里的 ...

很不错的分享精神。
作者: 又一个暑假    时间: 2014-7-28 21:40
jianhong_wu 发表于 2014-7-28 21:24
很不错的分享精神。

很不幸的是我之前没用世界上最好用的串口调试助手
很幸运的是碰到这个问题后让我知道了世界上最好用的串口调试助手http://www.dumenmen.com/forum.ph ... &extra=page%3D2
作者: jianhong_wu    时间: 2014-7-28 23:30
{:soso_e113:}{:soso_e113:}现在发现这个软件还不迟。
作者: kaisadadi33    时间: 2014-7-30 22:57
很好很强大,找工作就指望这个帖子了
作者: xiaomage    时间: 2014-7-31 08:20
连载,这个讲的好,谢谢!
作者: jianhong_wu    时间: 2014-8-4 13:27
第六十一节:组合BCD码,非组合BCD码,以及数值三者之间的相互转换和关系。
开场白:
本来这一节打算讲大数据的加法运算的,但是考虑大数据运算的基础是非组合BCD码,所以多增加一节讲BCD码的内容。
计算机中的BCD码,经常使用的有两种格式,即组合BCD码,非组合BCD码。
组合BCD码,是将两位十进制数,存放在一个字节中,例如:十进制数51的存放格式是0101 0001。
非组合BCD码,是将一个字节的低四位编码表示十进制数的一位,而高4位都为0。例如:十进制数51的占用了两个字节的空间,存放格式为:00000101 00000001。
    这一节要教大家两个知识点:
第一个:如何编写组合BCD码,非组合BCD码,以及数值三者之间的相互转换函数。
第二个:通过转换函数的编写,重温前面几节所讲到的指针用法。

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

(1)硬件平台:
    基于坚鸿51单片机学习板。

(2)实现功能:
波特率是:9600 。
通过电脑串口调试助手模拟上位机,往单片机发送EB 00 55 XX YY YY … YY YY  指令,其中EB 00 55是数据头,XX 是指令类型。YY是具体的数据。
指令类型01代表发送的是数值,需要转成组合BCD码和非组合BCD码,并且返回上位机显示。
指令类型02代表发送的是组合BCD码,需要转成数值和非组合BCD码,并且返回上位机显示。
指令类型03代表发送的是非组合BCD码,需要转成数值和组合BCD码,并且返回上位机显示。

返回上位机的数据中,中间3个数据EE EE EE是分割线,为了方便观察,没实际意义。

例如:十进制的数据52013140,它的十六进制数据是03 19 A8 54。
(a)上位机发送数据:eb 00 55 01 03 19 a8 54
单片机返回:52 01 31 40 EE EE EE 05 02 00 01 03 01 04 00
(b)上位机发送组合BCD码:eb 00 55 02 52 01 31 40
单片机返回:03 19 A8 54 EE EE EE 05 02 00 01 03 01 04 00
(c)发送非组合BCD码:eb 00 55 03 05 02 00 01 03 01 04 00
单片机返回:03 19 A8 54 EE EE EE 52 01 31 40

(3)源代码讲解如下:
  1. #include "REG52.H"

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


  3. /* 注释一:
  4. * 注意,此处的const_rc_size是20,比之前章节的缓冲区稍微改大了一点。
  5. */
  6. #define const_rc_size  20  //接收串口中断数据的缓冲区数组大小

  7. #define const_receive_time  5  //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完,这个时间根据实际情况来调整大小

  8. void initial_myself(void);   
  9. void initial_peripheral(void);
  10. void delay_long(unsigned int uiDelaylong);
  11. void delay_short(unsigned int uiDelayShort);


  12. void T0_time(void);  //定时中断函数
  13. void usart_receive(void); //串口接收中断函数
  14. void usart_service(void);  //串口服务程序,在main函数里


  15. void eusart_send(unsigned char ucSendData);

  16. void number_to_BCD4(const unsigned char *p_ucNumber,unsigned char *p_ucBCD_bit4);//把数值转换成组合BCD码
  17. void number_to_BCD8(const unsigned char *p_ucNumber,unsigned char *p_ucBCD_bit8);//把数值转换成非组合BCD码
  18. void BCD4_to_number(const unsigned char *p_ucBCD_bit4,unsigned char *p_ucNumber); //组合BCD码转成数值
  19. void BCD4_to_BCD8(const unsigned char *p_ucBCD_bit4,unsigned char *p_ucBCD_bit8); //组合BCD码转成非组合BCD码
  20. void BCD8_to_number(const unsigned char *p_ucBCD_bit8,unsigned char *p_ucNumber); //非组合BCD码转成数值
  21. void BCD8_to_BCD4(const unsigned char *p_ucBCD_bit8,unsigned char *p_ucBCD_bit4); //非组合BCD码转成组合BCD码


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

  23. unsigned int  uiSendCnt=0;     //用来识别串口是否接收完一串数据的计时器
  24. unsigned char ucSendLock=1;    //串口服务程序的自锁变量,每次接收完一串数据只处理一次
  25. unsigned int  uiRcregTotal=0;  //代表当前缓冲区已经接收了多少个数据
  26. unsigned char ucRcregBuf[const_rc_size]; //接收串口中断数据的缓冲区数组
  27. unsigned int  uiRcMoveIndex=0;  //用来解析数据协议的中间变量

  28. /* 注释二:
  29. * 注意,本程序规定数值的最大范围是0至99999999
  30. * 数组中的数据。高位在数组下标大的方向,低位在数组下标小的方向。
  31. */
  32. unsigned char ucBufferNumber[4]; //数值,用4个字节表示long类型的数值
  33. unsigned char ucBufferBCB_bit4[4]; //组合BCD码
  34. unsigned char ucBufferBCB_bit8[8]; //非组合BCD码

  35. void main()
  36.   {
  37.    initial_myself();  
  38.    delay_long(100);   
  39.    initial_peripheral();
  40.    while(1)  
  41.    {
  42.        usart_service();  //串口服务程序
  43.    }

  44. }

  45. void number_to_BCD4(const unsigned char *p_ucNumber,unsigned char *p_ucBCD_bit4)//把数值转换成组合BCD码
  46. {
  47.    unsigned long ulNumberTemp=0;
  48.    unsigned char ucTemp=0;
  49.    ulNumberTemp=p_ucNumber[3];  //把4个字节的数值合并成一个long类型数据
  50.    ulNumberTemp=ulNumberTemp<<8;
  51.    ulNumberTemp=ulNumberTemp+p_ucNumber[2];
  52.    ulNumberTemp=ulNumberTemp<<8;
  53.    ulNumberTemp=ulNumberTemp+p_ucNumber[1];
  54.    ulNumberTemp=ulNumberTemp<<8;
  55.    ulNumberTemp=ulNumberTemp+p_ucNumber[0];


  56.    p_ucBCD_bit4[3]=ulNumberTemp%100000000/10000000;
  57.    p_ucBCD_bit4[3]=p_ucBCD_bit4[3]<<4; //前半4位存第8位组合BCD码
  58.    ucTemp=ulNumberTemp%10000000/1000000;
  59.    p_ucBCD_bit4[3]=p_ucBCD_bit4[3]+ucTemp; //后半4位存第7位组合BCD码

  60.    p_ucBCD_bit4[2]=ulNumberTemp%1000000/100000;
  61.    p_ucBCD_bit4[2]=p_ucBCD_bit4[2]<<4; //前半4位存第6位组合BCD码
  62.    ucTemp=ulNumberTemp%100000/10000;
  63.    p_ucBCD_bit4[2]=p_ucBCD_bit4[2]+ucTemp;//后半4位存第5位组合BCD码

  64.    p_ucBCD_bit4[1]=ulNumberTemp%10000/1000;
  65.    p_ucBCD_bit4[1]=p_ucBCD_bit4[1]<<4; //前半4位存第4位组合BCD码
  66.    ucTemp=ulNumberTemp%1000/100;
  67.    p_ucBCD_bit4[1]=p_ucBCD_bit4[1]+ucTemp;//后半4位存第3位组合BCD码

  68.    p_ucBCD_bit4[0]=ulNumberTemp%100/10;
  69.    p_ucBCD_bit4[0]=p_ucBCD_bit4[0]<<4; //前半4位存第2位组合BCD码
  70.    ucTemp=ulNumberTemp%10;
  71.    p_ucBCD_bit4[0]=p_ucBCD_bit4[0]+ucTemp;//后半4位存第1位组合BCD码

  72. }


  73. void number_to_BCD8(const unsigned char *p_ucNumber,unsigned char *p_ucBCD_bit8)//把数值转换成非组合BCD码
  74. {
  75.    unsigned long ulNumberTemp=0;
  76.    ulNumberTemp=p_ucNumber[3];  //把4个字节的数值合并成一个long类型数据
  77.    ulNumberTemp=ulNumberTemp<<8;
  78.    ulNumberTemp=ulNumberTemp+p_ucNumber[2];
  79.    ulNumberTemp=ulNumberTemp<<8;
  80.    ulNumberTemp=ulNumberTemp+p_ucNumber[1];
  81.    ulNumberTemp=ulNumberTemp<<8;
  82.    ulNumberTemp=ulNumberTemp+p_ucNumber[0];

  83.    p_ucBCD_bit8[7]=ulNumberTemp%100000000/10000000;//一个字节8位存储第8位非组合BCD码
  84.    p_ucBCD_bit8[6]=ulNumberTemp%10000000/1000000;//一个字节8位存储第7位非组合BCD码
  85.    p_ucBCD_bit8[5]=ulNumberTemp%1000000/100000;//一个字节8位存储第6位非组合BCD码
  86.    p_ucBCD_bit8[4]=ulNumberTemp%100000/10000;//一个字节8位存储第5位非组合BCD码
  87.    p_ucBCD_bit8[3]=ulNumberTemp%10000/1000;//一个字节8位存储第4位非组合BCD码
  88.    p_ucBCD_bit8[2]=ulNumberTemp%1000/100;//一个字节8位存储第3位非组合BCD码
  89.    p_ucBCD_bit8[1]=ulNumberTemp%100/10;//一个字节8位存储第2位非组合BCD码
  90.    p_ucBCD_bit8[0]=ulNumberTemp%10;//一个字节8位存储第1位非组合BCD码

  91. }


  92. void BCD4_to_number(const unsigned char *p_ucBCD_bit4,unsigned char *p_ucNumber) //组合BCD码转成数值
  93. {
  94.    unsigned long ulTmep;
  95.    unsigned long ulSum;

  96.    ulSum=0;  //累加和数值清零

  97.    ulTmep=0;
  98.    ulTmep=p_ucBCD_bit4[3];
  99.    ulTmep=ulTmep>>4;  //把组合BCD码第8位分解出来
  100.    ulTmep=ulTmep*10000000;
  101.    ulSum=ulSum+ulTmep; //累加各位数值

  102.    ulTmep=0;
  103.    ulTmep=p_ucBCD_bit4[3];
  104.    ulTmep=ulTmep&0x0000000f;  //把组合BCD码第7位分解出来
  105.    ulTmep=ulTmep*1000000;
  106.    ulSum=ulSum+ulTmep; //累加各位数值

  107.    ulTmep=0;
  108.    ulTmep=p_ucBCD_bit4[2];
  109.    ulTmep=ulTmep>>4;  //把组合BCD码第6位分解出来
  110.    ulTmep=ulTmep*100000;
  111.    ulSum=ulSum+ulTmep; //累加各位数值

  112.    ulTmep=0;
  113.    ulTmep=p_ucBCD_bit4[2];
  114.    ulTmep=ulTmep&0x0000000f;  //把组合BCD码第5位分解出来
  115.    ulTmep=ulTmep*10000;
  116.    ulSum=ulSum+ulTmep; //累加各位数值

  117.    ulTmep=0;
  118.    ulTmep=p_ucBCD_bit4[1];
  119.    ulTmep=ulTmep>>4;  //把组合BCD码第4位分解出来
  120.    ulTmep=ulTmep*1000;
  121.    ulSum=ulSum+ulTmep; //累加各位数值

  122.    ulTmep=0;
  123.    ulTmep=p_ucBCD_bit4[1];
  124.    ulTmep=ulTmep&0x0000000f;  //把组合BCD码第3位分解出来
  125.    ulTmep=ulTmep*100;
  126.    ulSum=ulSum+ulTmep; //累加各位数值

  127.    ulTmep=0;
  128.    ulTmep=p_ucBCD_bit4[0];
  129.    ulTmep=ulTmep>>4;  //把组合BCD码第2位分解出来
  130.    ulTmep=ulTmep*10;
  131.    ulSum=ulSum+ulTmep; //累加各位数值

  132.    ulTmep=0;
  133.    ulTmep=p_ucBCD_bit4[0];
  134.    ulTmep=ulTmep&0x0000000f;  //把组合BCD码第1位分解出来
  135.    ulTmep=ulTmep*1;
  136.    ulSum=ulSum+ulTmep; //累加各位数值

  137.    //以上代码非常有规律,有兴趣的读者也可以自己想办法把它压缩成一个for循环的函数,可以极大节省容量。

  138.    p_ucNumber[3]=ulSum>>24;  //把long类型数据分解成4个字节
  139.    p_ucNumber[2]=ulSum>>16;
  140.    p_ucNumber[1]=ulSum>>8;
  141.    p_ucNumber[0]=ulSum;
  142. }



  143. void BCD4_to_BCD8(const unsigned char *p_ucBCD_bit4,unsigned char *p_ucBCD_bit8) //组合BCD码转成非组合BCD码
  144. {
  145.    unsigned char ucTmep;

  146.    ucTmep=p_ucBCD_bit4[3];
  147.    p_ucBCD_bit8[7]=ucTmep>>4;    //把组合BCD码第8位分解出来
  148.    p_ucBCD_bit8[6]=ucTmep&0x0f;  //把组合BCD码第7位分解出来

  149.    ucTmep=p_ucBCD_bit4[2];
  150.    p_ucBCD_bit8[5]=ucTmep>>4;    //把组合BCD码第6位分解出来
  151.    p_ucBCD_bit8[4]=ucTmep&0x0f;  //把组合BCD码第5位分解出来

  152.    ucTmep=p_ucBCD_bit4[1];
  153.    p_ucBCD_bit8[3]=ucTmep>>4;    //把组合BCD码第4位分解出来
  154.    p_ucBCD_bit8[2]=ucTmep&0x0f;  //把组合BCD码第3位分解出来

  155.    ucTmep=p_ucBCD_bit4[0];
  156.    p_ucBCD_bit8[1]=ucTmep>>4;    //把组合BCD码第2位分解出来
  157.    p_ucBCD_bit8[0]=ucTmep&0x0f;  //把组合BCD码第1位分解出来

  158. }



  159. void BCD8_to_number(const unsigned char *p_ucBCD_bit8,unsigned char *p_ucNumber) //非组合BCD码转成数值
  160. {
  161.    unsigned long ulTmep;
  162.    unsigned long ulSum;

  163.    ulSum=0;  //累加和数值清零

  164.    ulTmep=0;
  165.    ulTmep=p_ucBCD_bit8[7];
  166.    ulTmep=ulTmep*10000000;
  167.    ulSum=ulSum+ulTmep; //累加各位数值

  168.    ulTmep=0;
  169.    ulTmep=p_ucBCD_bit8[6];
  170.    ulTmep=ulTmep*1000000;
  171.    ulSum=ulSum+ulTmep; //累加各位数值

  172.    ulTmep=0;
  173.    ulTmep=p_ucBCD_bit8[5];
  174.    ulTmep=ulTmep*100000;
  175.    ulSum=ulSum+ulTmep; //累加各位数值

  176.    ulTmep=0;
  177.    ulTmep=p_ucBCD_bit8[4];
  178.    ulTmep=ulTmep*10000;
  179.    ulSum=ulSum+ulTmep; //累加各位数值

  180.    ulTmep=0;
  181.    ulTmep=p_ucBCD_bit8[3];
  182.    ulTmep=ulTmep*1000;
  183.    ulSum=ulSum+ulTmep; //累加各位数值

  184.    ulTmep=0;
  185.    ulTmep=p_ucBCD_bit8[2];
  186.    ulTmep=ulTmep*100;
  187.    ulSum=ulSum+ulTmep; //累加各位数值

  188.    ulTmep=0;
  189.    ulTmep=p_ucBCD_bit8[1];
  190.    ulTmep=ulTmep*10;
  191.    ulSum=ulSum+ulTmep; //累加各位数值

  192.    ulTmep=0;
  193.    ulTmep=p_ucBCD_bit8[0];
  194.    ulTmep=ulTmep*1;
  195.    ulSum=ulSum+ulTmep; //累加各位数值

  196.    //以上代码非常有规律,有兴趣的读者也可以自己想办法把它压缩成一个for循环的函数,可以极大节省容量。

  197.    p_ucNumber[3]=ulSum>>24;  //把long类型数据分解成4个字节
  198.    p_ucNumber[2]=ulSum>>16;
  199.    p_ucNumber[1]=ulSum>>8;
  200.    p_ucNumber[0]=ulSum;
  201. }



  202. void BCD8_to_BCD4(const unsigned char *p_ucBCD_bit8,unsigned char *p_ucBCD_bit4) //非组合BCD码转成组合BCD码
  203. {
  204.    unsigned char ucTmep;

  205.    ucTmep=p_ucBCD_bit8[7];    //把非组合BCD码第8位分解出来
  206.    p_ucBCD_bit4[3]=ucTmep<<4;
  207.    p_ucBCD_bit4[3]=p_ucBCD_bit4[3]+p_ucBCD_bit8[6];    //把非组合BCD码第7位分解出来

  208.    ucTmep=p_ucBCD_bit8[5];    //把非组合BCD码第6位分解出来
  209.    p_ucBCD_bit4[2]=ucTmep<<4;
  210.    p_ucBCD_bit4[2]=p_ucBCD_bit4[2]+p_ucBCD_bit8[4];    //把非组合BCD码第5位分解出来

  211.    ucTmep=p_ucBCD_bit8[3];    //把非组合BCD码第4位分解出来
  212.    p_ucBCD_bit4[1]=ucTmep<<4;
  213.    p_ucBCD_bit4[1]=p_ucBCD_bit4[1]+p_ucBCD_bit8[2];    //把非组合BCD码第3位分解出来

  214.    ucTmep=p_ucBCD_bit8[1];    //把非组合BCD码第2位分解出来
  215.    p_ucBCD_bit4[0]=ucTmep<<4;
  216.    p_ucBCD_bit4[0]=p_ucBCD_bit4[0]+p_ucBCD_bit8[0];    //把非组合BCD码第1位分解出来
  217.   
  218. }

  219. void usart_service(void)  //串口服务程序,在main函数里
  220. {

  221.      unsigned char i=0;   

  222.      if(uiSendCnt>=const_receive_time&&ucSendLock==1) //说明超过了一定的时间内,再也没有新数据从串口来
  223.      {

  224.             ucSendLock=0;    //处理一次就锁起来,不用每次都进来,除非有新接收的数据

  225.             //下面的代码进入数据协议解析和数据处理的阶段

  226.             uiRcMoveIndex=0; //由于是判断数据头,所以下标移动变量从数组的0开始向最尾端移动

  227.             while(uiRcregTotal>=5&&uiRcMoveIndex<=(uiRcregTotal-5))
  228.             {
  229.                if(ucRcregBuf[uiRcMoveIndex+0]==0xeb&&ucRcregBuf[uiRcMoveIndex+1]==0x00&&ucRcregBuf[uiRcMoveIndex+2]==0x55)  //数据头eb 00 55的判断
  230.                {
  231.                     switch(ucRcregBuf[uiRcMoveIndex+3])  //根据命令类型来进行不同的处理
  232.                                         {
  233.                                            case 1:  //接收到的是数值,需要转成组合BCD码和非组合BCD码
  234.                             for(i=0;i<4;i++)
  235.                             {
  236.                                 ucBufferNumber[3-i]=ucRcregBuf[uiRcMoveIndex+4+i]; //从串口接收到的数据,注意,高位在数组下标大的方向
  237.                             }
  238.                             number_to_BCD4(ucBufferNumber,ucBufferBCB_bit4);//把数值转换成组合BCD码
  239.                             number_to_BCD8(ucBufferNumber,ucBufferBCB_bit8);//把数值转换成非组合BCD码
  240.                             for(i=0;i<4;i++)
  241.                             {
  242.                                eusart_send(ucBufferBCB_bit4[3-i]);  ////把组合BCD码返回给上位机观察,注意,高位在数组下标大的方向
  243.                             }
  244.                             eusart_send(0xee);  //为了方便上位机观察,多发送3个字节ee ee ee作为分割线
  245.                             eusart_send(0xee);
  246.                             eusart_send(0xee);
  247.                             for(i=0;i<8;i++)
  248.                             {
  249.                                eusart_send(ucBufferBCB_bit8[7-i]);  ////把非组合BCD码返回给上位机观察,注意,高位在数组下标大的方向
  250.                             }

  251.                                                 break;
  252.                                            case 2:  //接收到的是组合BCD码,需要转成数值和非组合BCD码
  253.                             for(i=0;i<4;i++)
  254.                             {
  255.                                 ucBufferBCB_bit4[3-i]=ucRcregBuf[uiRcMoveIndex+4+i]; //从串口接收到的组合BCD码,注意,高位在数组下标大的方向
  256.                             }
  257.                             BCD4_to_number(ucBufferBCB_bit4,ucBufferNumber); //组合BCD码转成数值
  258.                             BCD4_to_BCD8(ucBufferBCB_bit4,ucBufferBCB_bit8); //组合BCD码转成非组合BCD码
  259.                             for(i=0;i<4;i++)
  260.                             {
  261.                                eusart_send(ucBufferNumber[3-i]);  ////把数值返回给上位机观察,注意,高位在数组下标大的方向
  262.                             }
  263.                             eusart_send(0xee);  //为了方便上位机观察,多发送3个字节ee ee ee作为分割线
  264.                             eusart_send(0xee);
  265.                             eusart_send(0xee);
  266.                             for(i=0;i<8;i++)
  267.                             {
  268.                                eusart_send(ucBufferBCB_bit8[7-i]);  ////把非组合BCD码返回给上位机观察,注意,高位在数组下标大的方向
  269.                             }

  270.                                                 break;
  271.                                            case 3:  //接收到的是非组合BCD码,需要转成数值和组合BCD码
  272.                             for(i=0;i<8;i++)
  273.                             {
  274.                                 ucBufferBCB_bit8[7-i]=ucRcregBuf[uiRcMoveIndex+4+i]; //从串口接收到的非组合BCD码,注意,高位在数组下标大的方向
  275.                             }

  276.                             BCD8_to_number(ucBufferBCB_bit8,ucBufferNumber); //非组合BCD码转成数值
  277.                             BCD8_to_BCD4(ucBufferBCB_bit8,ucBufferBCB_bit4); //非组合BCD码转成组合BCD码
  278.                             for(i=0;i<4;i++)
  279.                             {
  280.                                eusart_send(ucBufferNumber[3-i]);  ////把数值返回给上位机观察
  281.                             }
  282.                             eusart_send(0xee);  //为了方便上位机观察,多发送3个字节ee ee ee作为分割线,注意,高位在数组下标大的方向
  283.                             eusart_send(0xee);
  284.                             eusart_send(0xee);
  285.                             for(i=0;i<4;i++)
  286.                             {
  287.                                eusart_send(ucBufferBCB_bit4[3-i]);  ////把组合BCD码返回给上位机观察,注意,高位在数组下标大的方向
  288.                             }

  289.                                                 break;
  290.                                         }

  291.                     break;   //退出循环
  292.                }
  293.                uiRcMoveIndex++; //因为是判断数据头,游标向着数组最尾端的方向移动
  294.            }
  295.                                          
  296.            uiRcregTotal=0;  //清空缓冲的下标,方便下次重新从0下标开始接受新数据
  297.   
  298.      }
  299.                         
  300. }

  301. void eusart_send(unsigned char ucSendData) //往上位机发送一个字节的函数
  302. {

  303.   ES = 0; //关串口中断
  304.   TI = 0; //清零串口发送完成中断请求标志
  305.   SBUF =ucSendData; //发送一个字节

  306.   delay_short(400);  //每个字节之间的延时,这里非常关键,也是最容易出错的地方。延时的大小请根据实际项目来调整

  307.   TI = 0; //清零串口发送完成中断请求标志
  308.   ES = 1; //允许串口中断

  309. }



  310. void T0_time(void) interrupt 1    //定时中断
  311. {
  312.   TF0=0;  //清除中断标志
  313.   TR0=0; //关中断


  314.   if(uiSendCnt<const_receive_time)   //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完
  315.   {
  316.           uiSendCnt++;    //表面上这个数据不断累加,但是在串口中断里,每接收一个字节它都会被清零,除非这个中间没有串口数据过来
  317.       ucSendLock=1;     //开自锁标志
  318.   }



  319.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  320.   TL0=0x0b;
  321.   TR0=1;  //开中断
  322. }


  323. void usart_receive(void) interrupt 4                 //串口接收数据中断        
  324. {        

  325.    if(RI==1)  
  326.    {
  327.         RI = 0;

  328.             ++uiRcregTotal;
  329.         if(uiRcregTotal>const_rc_size)  //超过缓冲区
  330.         {
  331.            uiRcregTotal=const_rc_size;
  332.         }
  333.         ucRcregBuf[uiRcregTotal-1]=SBUF;   //将串口接收到的数据缓存到接收缓冲区里
  334.         uiSendCnt=0;  //及时喂狗,虽然main函数那边不断在累加,但是只要串口的数据还没发送完毕,那么它永远也长不大,因为每个中断都被清零。
  335.    
  336.    }
  337.    else  //发送中断,及时把发送中断标志位清零
  338.    {
  339.         TI = 0;
  340.    }
  341.                                                          
  342. }                                


  343. void delay_long(unsigned int uiDelayLong)
  344. {
  345.    unsigned int i;
  346.    unsigned int j;
  347.    for(i=0;i<uiDelayLong;i++)
  348.    {
  349.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  350.           {
  351.              ; //一个分号相当于执行一条空语句
  352.           }
  353.    }
  354. }

  355. void delay_short(unsigned int uiDelayShort)
  356. {
  357.    unsigned int i;  
  358.    for(i=0;i<uiDelayShort;i++)
  359.    {
  360.      ;   //一个分号相当于执行一条空语句
  361.    }
  362. }


  363. void initial_myself(void)  //第一区 初始化单片机
  364. {

  365.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

  366.   //配置定时器
  367.   TMOD=0x01;  //设置定时器0为工作方式1
  368.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  369.   TL0=0x0b;


  370.   //配置串口
  371.   SCON=0x50;
  372.   TMOD=0X21;
  373.   TH1=TL1=-(11059200L/12/32/9600);  //这段配置代码具体是什么意思,我也不太清楚,反正是跟串口波特率有关。
  374.   TR1=1;

  375. }

  376. void initial_peripheral(void) //第二区 初始化外围
  377. {

  378.    EA=1;     //开总中断
  379.    ES=1;     //允许串口中断
  380.    ET0=1;    //允许定时中断
  381.    TR0=1;    //启动定时中断

  382. }
复制代码

总结陈词:
有了这一节非组合BCD的基础知识,下一节就开始讲大数据的算法程序。这些算法程序经常要用在计算器,工控,以及高精度的仪器仪表等领域。C语言的语法中不是已经提供了+,-,*,/这些运算符号吗?为什么还要专门写算法程序?因为那些运算符只能进行简单的运算,一旦数据超过了unsigned long(4个字节)的范围就会出错。而这种大数据算法的程序是什么样的?欲知详情,请听下回分解----大数据的加法运算。

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

作者: jianhong_wu    时间: 2014-8-13 00:46
第六十二节:大数据的加法运算。

开场白:
直接用C语言的“+”运算符进行加法运算时,“被加数”,“加数”,“和”,这三个数据的最大范围是unsigned long 类型,也就是数据最大范围是4个字节,十进制的范围是0至4294967295。一旦超过了这个范围,则运算会出错。因此,当进行大数据加法运算时,我们要额外编程序,实现大数据的算法。其实这种算法并不难,就是我们在小学里学的四则运算算法。
      我们先要弄清楚一个新的概念。不考虑小数点的情况下,数据有两种表现形式。一种是常用的变量形式,另外一种是上一节讲到的BCD码数组形式。变量的最大范围有限,而BCD码数组的形式是无限的,正因为这个特点,所以我们可以进行大数据运算。
    这一节要教大家两个知识点:
第一个:如何通过用for循环语句改写上一节的组合BCD码跟非组合BCD码的转换函数。
第二个:如何编写涉及到大数据加法运算的算法程序函数,同时也复习了指针的用途。
第三个:如何在串口程序中通过关键字来截取所需要的数据。

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

(1)硬件平台:
    基于坚鸿51单片机学习板。

(2)实现功能:
波特率是:9600 。
通过电脑串口调试助手模拟上位机,往单片机发送组合BCD码的被加数和加数。单片机把组合BCD码的运算结果返回到上位机。最大范围4位,从0到9999,如果超范围则返回EE EE EE报错。往单片机发送的数据格式:EB 00 55 XX XX 0d 0a  YY YY  0d 0a指令,其中EB 00 55是数据头,XX 是被加数,可以是1个字节,也可以是2个字节。YY是加数,可以是1个字节,也可以是2个字节。0d 0a是固定的结束标志。
例如:
(a)1234+5678=6912
上位机发送数据:eb 00 55 12 34 0d 0a 56 78 0d 0a
单片机返回:69 12

(b)9999+56=10055  超过4位的9999,所以报错
上位机发送数据:eb 00 55 99 99  0d 0a 56 0d 0a
单片机返回:EE EE EE  表示出错了

(3)源代码讲解如下:
  1. #include "REG52.H"


  2. /* 注释一:
  3. * 本系统中,规定最大运算位数是4位。
  4. * 由于STC89C52单片机的RAM只有256个,也就是说系统的变量数最大
  5. * 不能超过256个,如果超过了这个极限,编译器就会报错。如果这个算法
  6. * 移植到stm32或者PIC等RAM比较大的单片机上,那么就可以把这个运算位数
  7. * 设置得更加大一点。
  8. */

  9. #define  BCD4_MAX     2  //本系统中,规定的组合BCD码最大字节数,一个字节包含2位,因此4位有效运算数
  10. #define  BCD8_MAX    (BCD4_MAX*2)  //本系统中,规定的非组合BCD码最大字节数,一个字节包含1位,因此4位有效运算数

  11. #define const_rc_size  30  //接收串口中断数据的缓冲区数组大小

  12. #define const_receive_time  5  //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完,这个时间根据实际情况来调整大小

  13. #define uchar unsigned char    //方便移植平台
  14. #define ulong unsigned long   //方便移植平台

  15. //如果在VC的平台模拟此算法,则都定义成int类型,如下:
  16. //#define uchar int  
  17. //#define ulong int

  18. void initial_myself(void);   
  19. void initial_peripheral(void);
  20. void delay_long(unsigned int uiDelaylong);
  21. void delay_short(unsigned int uiDelayShort);


  22. void T0_time(void);  //定时中断函数
  23. void usart_receive(void); //串口接收中断函数
  24. void usart_service(void);  //串口服务程序,在main函数里


  25. void eusart_send(unsigned char ucSendData);

  26. void BCD4_to_BCD8(const unsigned char *p_ucBCD_bit4,unsigned char ucBCD4_cnt,unsigned char *p_ucBCD_bit8,unsigned char *p_ucBCD8_cnt);
  27. void BCD8_to_BCD4(const unsigned char *p_ucBCD_bit8,unsigned char ucBCD8_cnt,unsigned char *p_ucBCD_bit4,unsigned char *p_ucBCD4_cnt);

  28. void ClearAllData(uchar ucARRAY_MAX,uchar *destData);
  29. uchar GetDataLength(const uchar *destData,uchar ucARRAY_MAX);
  30. uchar AddData(const uchar *destData,const uchar *sourceData,uchar *resultData);

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

  32. unsigned int  uiSendCnt=0;     //用来识别串口是否接收完一串数据的计时器
  33. unsigned char ucSendLock=1;    //串口服务程序的自锁变量,每次接收完一串数据只处理一次
  34. unsigned int  uiRcregTotal=0;  //代表当前缓冲区已经接收了多少个数据
  35. unsigned char ucRcregBuf[const_rc_size]; //接收串口中断数据的缓冲区数组
  36. unsigned int  uiRcMoveIndex=0;  //用来解析数据协议的中间变量


  37. unsigned char ucDataBCD4_1[BCD4_MAX]; //接收到的第1个数组合BCD码数组形式  这里是指被加数
  38. unsigned char ucDataBCD4_cnt_1=0;  //接收到的第1个数组合BCD码数组的有效数据长度

  39. unsigned char ucDataBCD4_2[BCD4_MAX]; //接收到的第2个数组合BCD码数组形式  这里是指加数
  40. unsigned char ucDataBCD4_cnt_2=0;  //接收到的第2个数组合BCD码数组的有效数据长度

  41. unsigned char ucDataBCD4_3[BCD4_MAX]; //接收到的第3个数组合BCD码数组形式  这里是指和
  42. unsigned char ucDataBCD4_cnt_3=0;  //接收到的第3个数组合BCD码数组的有效数据长度


  43. unsigned char ucDataBCD8_1[BCD8_MAX]; //接收到的第1个数非组合BCD码数组形式   这里是指被加数
  44. unsigned char ucDataBCD8_cnt_1=0;  //接收到的第1个数非组合BCD码数组的有效数据长度

  45. unsigned char ucDataBCD8_2[BCD8_MAX]; //接收到的第2个数非组合BCD码数组形式   这里是指加数
  46. unsigned char ucDataBCD8_cnt_2=0;  //接收到的第2个数非组合BCD码数组的有效数据长度

  47. unsigned char ucDataBCD8_3[BCD8_MAX]; //接收到的第3个数非组合BCD码数组形式   这里是指和
  48. unsigned char ucDataBCD8_cnt_3=0;  //接收到的第3个数非组合BCD码数组的有效数据长度

  49. unsigned char ucResultFlag=11; //运算结果标志,10代表计算结果超出范围出错,11代表正常。

  50. void main()
  51.   {
  52.    initial_myself();  
  53.    delay_long(100);   
  54.    initial_peripheral();
  55.    while(1)  
  56.    {
  57.        usart_service();  //串口服务程序
  58.    }

  59. }

  60. /* 注释二:
  61. * 组合BCD码转成非组合BCD码。
  62. * 这里的变量ucBCD4_cnt代表组合BCD码的有效字节数.
  63. * 这里的变量*p_ucBCD8_cnt代表经过转换后,非组合BCD码的有效字节数,记得加地址符号&传址进去
  64. * 本程序在上一节的基础上,略作修改,用循环for语句压缩了代码,
  65. * 同时引进了组合BCD码的有效字节数变量。这样就不限定了数据的长度,
  66. * 可以让我们根据数据的实际大小灵活运用。
  67. */
  68. void BCD4_to_BCD8(const unsigned char *p_ucBCD_bit4,unsigned char ucBCD4_cnt,unsigned char *p_ucBCD_bit8,unsigned char *p_ucBCD8_cnt)
  69. {
  70.    unsigned char ucTmep;
  71.    unsigned char i;

  72.    for(i=0;i<BCD8_MAX;i++)   //先把即将保存转换结果的缓冲区清零
  73.    {
  74.       p_ucBCD_bit8[i]=0;
  75.    }


  76.    *p_ucBCD8_cnt=ucBCD4_cnt*2; //转换成非组合BCD码后的有效数据长度  

  77.    for(i=0;i<ucBCD4_cnt;i++)
  78.    {
  79.       ucTmep=p_ucBCD_bit4[ucBCD4_cnt-1-i];
  80.       p_ucBCD_bit8[ucBCD4_cnt*2-i*2-1]=ucTmep>>4;   
  81.       p_ucBCD_bit8[ucBCD4_cnt*2-i*2-2]=ucTmep&0x0f;  
  82.    }

  83. }


  84. /* 注释三:
  85. * 非组合BCD码转成组合BCD码。
  86. * 这里的变量ucBCD8_cnt代表非组合BCD码的有效字节数.
  87. * 这里的变量*p_ucBCD4_cnt代表经过转换后,组合BCD码的有效字节数,记得加地址符号&传址进去
  88. * 本程序在上一节的基础上,略作修改,用循环for语句压缩了代码,
  89. * 同时引进了非组合BCD码的有效字节数变量。这样就不限定了数据的长度,
  90. * 可以让我们根据数据的实际大小灵活运用。
  91. */
  92. void BCD8_to_BCD4(const unsigned char *p_ucBCD_bit8,unsigned char ucBCD8_cnt,unsigned char *p_ucBCD_bit4,unsigned char *p_ucBCD4_cnt)
  93. {
  94.    unsigned char ucTmep;
  95.    unsigned char i;
  96.    unsigned char ucBCD4_cnt;

  97.    for(i=0;i<BCD4_MAX;i++)   //先把即将保存转换结果的缓冲区清零
  98.    {
  99.       p_ucBCD_bit4[i]=0;
  100.    }

  101.    ucBCD4_cnt=(ucBCD8_cnt+1)/2; //非组合BCD码转化成组合BCD码的有效数,这里+1避免非组合数据长度是奇数位
  102.    *p_ucBCD4_cnt=ucBCD4_cnt; //把转换后的结果付给接口指针的数据,可以对外输出结果

  103.    for(i=0;i<ucBCD4_cnt;i++)
  104.    {
  105.       ucTmep=p_ucBCD_bit8[ucBCD4_cnt*2-1-i*2];    //把非组合BCD码第8位分解出来
  106.       p_ucBCD_bit4[ucBCD4_cnt-1-i]=ucTmep<<4;
  107.       p_ucBCD_bit4[ucBCD4_cnt-1-i]=p_ucBCD_bit4[ucBCD4_cnt-1-i]+p_ucBCD_bit8[ucBCD4_cnt*2-2-i*2];    //把非组合BCD码第7位分解出来
  108.    }
  109.   
  110. }

  111. /* 注释四:
  112. *函数介绍:清零数组的全部数组数据
  113. *输入参数:ucARRAY_MAX代表数组定义的最大长度
  114. *输入输出参数:*destData--被清零的数组。
  115. */

  116. void ClearAllData(uchar ucARRAY_MAX,uchar *destData)
  117. {
  118.   uchar i;

  119.   for(i=0;i<ucARRAY_MAX;i++)
  120.   {
  121.      destData[i]=0;
  122.   }

  123. }


  124. /* 注释五:
  125. *函数介绍:获取数组的有效长度
  126. *输入参数:*destData--被获取的数组。
  127. *输入参数:ucARRAY_MAX代表数组定义的最大长度
  128. *返回值  :返回数组的有效长度。比如58786这个数据的有效长度是5
  129. *电子开发者作者:吴坚鸿
  130. */
  131. uchar GetDataLength(const uchar *destData,uchar ucARRAY_MAX)
  132. {
  133.   uchar i;
  134.   uchar DataLength=ucARRAY_MAX;
  135.   for(i=0;i<ucARRAY_MAX;i++)
  136.   {
  137.       if(0!=destData[ucARRAY_MAX-1-i])
  138.           {
  139.              break;
  140.           }
  141.           else
  142.           {
  143.              DataLength--;
  144.           }

  145.   }

  146.   return DataLength;

  147. }



  148. /* 注释六:
  149. *函数介绍:两个数相加
  150. *输入参数:
  151. *(1)*destData--被加数的数组。
  152. *(2)*sourceData--加数的数组。
  153. *(3)*resultData--和的数组。注意,调用本函数前,必须先把这个数组清零
  154. *返回值  :10代表计算结果超出范围出错,11代表正常。
  155. */
  156. uchar AddData(const uchar *destData,const uchar *sourceData,uchar *resultData)
  157. {
  158. uchar addResult=11; //开始默认返回的运算结果是正常
  159. uchar destCnt=0;
  160. uchar sourceCnt=0;
  161. uchar i;
  162. uchar carryData=0;  //进位
  163. uchar maxCnt=0; //最大位数
  164. uchar resultTemp=0; //存放临时运算结果的中间变量

  165. //为什么不在本函数内先把resultData数组清零?因为后面章节中的乘法运算中要用到此函数实现连加功能。
  166. //因此如果纯粹实现加法运算时,在调用本函数之前,必须先在外面把和的数组清零,否则会计算出错。

  167. destCnt=GetDataLength(destData,BCD8_MAX);   //获取被加数的有效位数
  168. sourceCnt=GetDataLength(sourceData,BCD8_MAX);  //获取加数的有效位数

  169. if(destCnt>=sourceCnt)  //找出两个运算数据中最大的有效位数
  170. {
  171.    maxCnt=destCnt;
  172. }
  173. else
  174. {
  175.    maxCnt=sourceCnt;
  176. }

  177. for(i=0;i<maxCnt;i++)
  178. {
  179.    resultTemp=destData[i]+sourceData[i]+carryData; //按位相加
  180.    resultData[i]=resultTemp%10;   //截取最低位存放进保存结果的数组
  181.    carryData=resultTemp/10;    //存放进位
  182. }

  183. resultData[i]=carryData;

  184. if((maxCnt==BCD8_MAX)&&(carryData==1))  //如果数组的有效位是最大值并且最后的进位是1,则计算溢出报错
  185. {

  186.   ClearAllData(BCD8_MAX,resultData);

  187.   addResult=10;  //报错
  188. }


  189. return addResult;
  190. }




  191. void usart_service(void)  //串口服务程序,在main函数里
  192. {

  193.      unsigned char i=0;   
  194.      unsigned char k=0;   
  195.          unsigned char ucGetDataStep=0;

  196.      if(uiSendCnt>=const_receive_time&&ucSendLock==1) //说明超过了一定的时间内,再也没有新数据从串口来
  197.      {

  198.             ucSendLock=0;    //处理一次就锁起来,不用每次都进来,除非有新接收的数据

  199.             //下面的代码进入数据协议解析和数据处理的阶段

  200.             uiRcMoveIndex=0; //由于是判断数据头,所以下标移动变量从数组的0开始向最尾端移动
  201.             while(uiRcMoveIndex<uiRcregTotal)  //说明还没有把缓冲区的数据读取完
  202.             {
  203.                if(ucRcregBuf[uiRcMoveIndex+0]==0xeb&&ucRcregBuf[uiRcMoveIndex+1]==0x00&&ucRcregBuf[uiRcMoveIndex+2]==0x55)  //数据头eb 00 55的判断
  204.                {
  205.                     
  206.                                    i=0;
  207.                                    ucGetDataStep=0;
  208.                    ucDataBCD4_cnt_1=0;  //第1个数组合BCD码数组的有效数据长度
  209.                    ucDataBCD4_cnt_2=0;  //第2个数组合BCD码数组的有效数据长度

  210.                                    ClearAllData(BCD4_MAX,ucDataBCD4_1);  //清零第1个参与运算的数据
  211.                                    ClearAllData(BCD4_MAX,ucDataBCD4_2);  //清零第2个参与运算的数据

  212.                    //以下while循环是通过关键字0x0d 0x0a来截取第1个和第2个参与运算的数据。
  213.                                    while(i<(BCD8_MAX+4))//这里+4是因为有2对0x0d 0x0a结尾特殊符号,一个共4个字节
  214.                                    {
  215.                                            if(ucGetDataStep==0)//步骤0,相当于我平时用的case 0,获取第1个数,在这里是指被加数
  216.                                            {
  217.                                                          if(ucRcregBuf[uiRcMoveIndex+3+i]==0x0d&&ucRcregBuf[uiRcMoveIndex+4+i]==0x0a) //结束标志
  218.                                                          {
  219.                                 for(k=0;k<ucDataBCD4_cnt_1;k++) //提取第1个参与运算的数组数据
  220.                                                                 {
  221.                                                                    ucDataBCD4_1[k]=ucRcregBuf[uiRcMoveIndex+3+i-1-k]; //注意,接收到的数组数据与实际存储的数组数据的下标方向是相反的
  222.                                                                 }
  223.                                                                                                                                                        
  224.                                                                 i=i+2; //跳过 0x0d 0x0a 这两个字节,进行下一轮的关键字提取
  225.                                                             ucGetDataStep=1;  //切换到下一个关键字提取的步骤

  226.                              }
  227.                                                          else
  228.                                                          {
  229.                                                                 i++;
  230.                                                                 ucDataBCD4_cnt_1++;  //统计第1个有效数据的长度
  231.                              }
  232.                                                                                                                          
  233.                                              }
  234.                                                  else if(ucGetDataStep==1) //步骤1,相当于我平时用的case 1,获取第2个参与运行的数,在这里是加数
  235.                                                  {
  236.                                                           if(ucRcregBuf[uiRcMoveIndex+3+i]==0x0d&&ucRcregBuf[uiRcMoveIndex+4+i]==0x0a) //结束标志
  237.                                                          {
  238.                                 for(k=0;k<ucDataBCD4_cnt_2;k++) //提取第2个参与运算的数组数据
  239.                                                                 {
  240.                                                                    ucDataBCD4_2[k]=ucRcregBuf[uiRcMoveIndex+3+i-1-k]; //注意,接收到的数组数据与实际存储的数组数据的下标方向是相反的
  241.                                                                 }
  242.                                                                                                                                                        
  243.                                 break; //截取数据完成。直接跳出截取数据的while(i<(BCD8_MAX+4))循环

  244.                              }
  245.                                                          else
  246.                                                          {
  247.                                                                 i++;
  248.                                                                 ucDataBCD4_cnt_2++;  //统计第2个有效数据的长度
  249.                              }
  250.                          }
  251.                     }


  252.                     //注意ucDataBCD8_cnt_1和ucDataBCD8_cnt_2要带地址符号&传址进去
  253.                     BCD4_to_BCD8(ucDataBCD4_1,ucDataBCD4_cnt_1,ucDataBCD8_1,&ucDataBCD8_cnt_1); //把接收到的组合BCD码转换成非组合BCD码  第1个数
  254.                     BCD4_to_BCD8(ucDataBCD4_2,ucDataBCD4_cnt_2,ucDataBCD8_2,&ucDataBCD8_cnt_2); //把接收到的组合BCD码转换成非组合BCD码  第2个数


  255.                                     ClearAllData(BCD8_MAX,ucDataBCD8_3);  //清零第3个参与运算的数据,用来接收运行的结果
  256.                                         ucResultFlag=AddData(ucDataBCD8_1,ucDataBCD8_2,ucDataBCD8_3); //相加运算,结果放在ucDataBCD8_3数组里

  257.                                         if(ucResultFlag==11) //表示运算结果没有超范围
  258.                                         {
  259.                        ucDataBCD8_cnt_3=GetDataLength(ucDataBCD8_3,BCD8_MAX);  //获取和的有效字节数
  260.                                            BCD8_to_BCD4(ucDataBCD8_3,ucDataBCD8_cnt_3,ucDataBCD4_3,&ucDataBCD4_cnt_3); //把非组合BCD码转成组合BCD码。注意,&ucDataBCD4_cnt_3带地址符号&
  261.                        for(k=0;k<ucDataBCD4_cnt_3;k++) //返回运算结果到上位机上观察。看到的是组合BCD码形式。返回的时候注意数组下标的顺序要反过来发送,先发高位的下标数组
  262.                                             {
  263.                                                 eusart_send(ucDataBCD4_3[ucDataBCD4_cnt_3-1-k]); //往上位机发送一个字节的函数
  264.                                            }
  265.                                         }
  266.                                         else //运算结果超范围,返回EE EE EE
  267.                                         {
  268.                                              eusart_send(0xee); //往上位机发送一个字节的函数
  269.                                              eusart_send(0xee); //往上位机发送一个字节的函数
  270.                                              eusart_send(0xee); //往上位机发送一个字节的函数
  271.                                         }

  272.                     break;   //退出循环
  273.                }
  274.                uiRcMoveIndex++; //因为是判断数据头,游标向着数组最尾端的方向移动
  275.            }

  276.            ucRcregBuf[0]=0; //把数据头清零,方便下次接收判断新数据
  277.                    ucRcregBuf[1]=0;
  278.                    ucRcregBuf[2]=0;         
  279.                   
  280.            uiRcregTotal=0;  //清空缓冲的下标,方便下次重新从0下标开始接受新数据
  281.   
  282.      }
  283.                         
  284. }

  285. void eusart_send(unsigned char ucSendData) //往上位机发送一个字节的函数
  286. {

  287.   ES = 0; //关串口中断
  288.   TI = 0; //清零串口发送完成中断请求标志
  289.   SBUF =ucSendData; //发送一个字节

  290.   delay_short(400);  //每个字节之间的延时,这里非常关键,也是最容易出错的地方。延时的大小请根据实际项目来调整

  291.   TI = 0; //清零串口发送完成中断请求标志
  292.   ES = 1; //允许串口中断

  293. }



  294. void T0_time(void) interrupt 1    //定时中断
  295. {
  296.   TF0=0;  //清除中断标志
  297.   TR0=0; //关中断


  298.   if(uiSendCnt<const_receive_time)   //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完
  299.   {
  300.           uiSendCnt++;    //表面上这个数据不断累加,但是在串口中断里,每接收一个字节它都会被清零,除非这个中间没有串口数据过来
  301.       ucSendLock=1;     //开自锁标志
  302.   }



  303.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  304.   TL0=0x0b;
  305.   TR0=1;  //开中断
  306. }


  307. void usart_receive(void) interrupt 4                 //串口接收数据中断        
  308. {        

  309.    if(RI==1)  
  310.    {
  311.         RI = 0;

  312.             ++uiRcregTotal;
  313.         if(uiRcregTotal>const_rc_size)  //超过缓冲区
  314.         {
  315.            uiRcregTotal=const_rc_size;
  316.         }
  317.         ucRcregBuf[uiRcregTotal-1]=SBUF;   //将串口接收到的数据缓存到接收缓冲区里
  318.         uiSendCnt=0;  //及时喂狗,虽然main函数那边不断在累加,但是只要串口的数据还没发送完毕,那么它永远也长不大,因为每个中断都被清零。
  319.    
  320.    }
  321.    else  //发送中断,及时把发送中断标志位清零
  322.    {
  323.         TI = 0;
  324.    }
  325.                                                          
  326. }                                


  327. void delay_long(unsigned int uiDelayLong)
  328. {
  329.    unsigned int i;
  330.    unsigned int j;
  331.    for(i=0;i<uiDelayLong;i++)
  332.    {
  333.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  334.           {
  335.              ; //一个分号相当于执行一条空语句
  336.           }
  337.    }
  338. }

  339. void delay_short(unsigned int uiDelayShort)
  340. {
  341.    unsigned int i;  
  342.    for(i=0;i<uiDelayShort;i++)
  343.    {
  344.      ;   //一个分号相当于执行一条空语句
  345.    }
  346. }


  347. void initial_myself(void)  //第一区 初始化单片机
  348. {

  349.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

  350.   //配置定时器
  351.   TMOD=0x01;  //设置定时器0为工作方式1
  352.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  353.   TL0=0x0b;


  354.   //配置串口
  355.   SCON=0x50;
  356.   TMOD=0X21;
  357.   TH1=TL1=-(11059200L/12/32/9600);  //这段配置代码具体是什么意思,我也不太清楚,反正是跟串口波特率有关。
  358.   TR1=1;

  359. }

  360. void initial_peripheral(void) //第二区 初始化外围
  361. {

  362.    EA=1;     //开总中断
  363.    ES=1;     //允许串口中断
  364.    ET0=1;    //允许定时中断
  365.    TR0=1;    //启动定时中断

  366. }
复制代码

总结陈词:
既然这节讲了加法程序,那么下一节接着讲常用的减法程序,这种大数据的减法程序是什么样的?欲知详情,请听下回分解----大数据的减法运算。

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

作者: jianhong_wu    时间: 2014-8-19 16:19
第六十三节:大数据的减法运算。

开场白:
直接用C语言的“-”运算符进行加法运算时,“被减数”,“ 减数”,“差”,这三个数据的最大范围是unsigned long 类型,也就是数据最大范围是4个字节,十进制的范围是0至4294967295。一旦超过了这个范围,则运算会出错。因此,当进行大数据减法运算时,我们要额外编程序,实现大数据的算法。其实这种算法并不难,就是我们在小学里学的四则运算算法。
      我们先要弄清楚一个新的概念。不考虑小数点的情况下,数据有两种表现形式。一种是常用的变量形式,另外一种是BCD码数组形式。变量的最大范围有限,而BCD码数组的形式是无限的,正因为这个特点,所以我们可以进行大数据运算。
    这一节要教大家两个知识点:
第一个:如何编写比较两个非组合BCD码数据的大小。
第二个:如何编写涉及到大数据减法运算的算法程序函数,同时也复习了指针的用途。

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

(1)硬件平台:
    基于坚鸿51单片机学习板。

(2)实现功能:
波特率是:9600 。
通过电脑串口调试助手模拟上位机,往单片机发送组合BCD码的被减数和减数。单片机把组合BCD码的运算结果返回到上位机。最大范围4位,从0到9999,如果被减数小于减数则返回EE EE EE报错。往单片机发送的数据格式:EB 00 55 XX XX 0d 0a  YY YY  0d 0a指令,其中EB 00 55是数据头,XX 是被减数,可以是1个字节,也可以是2个字节。YY是减数,可以是1个字节,也可以是2个字节。0d 0a是固定的结束标志。
例如:
(a)8259 – 5267 = 2992
上位机发送数据:eb 00 55 82 59 0d 0a  52 67 0d 0a
单片机返回:29 92

(b)5267 - 8259=小于0  所以报错
上位机发送数据:eb 00 55  52 67 0d 0a  82 59 0d 0a
单片机返回:EE EE EE  表示出错了

(3)源代码讲解如下:
  1. #include "REG52.H"


  2. /* 注释一:
  3. * 本系统中,规定最大运算位数是4位。
  4. * 由于STC89C52单片机的RAM只有256个,也就是说系统的变量数最大
  5. * 不能超过256个,如果超过了这个极限,编译器就会报错。如果这个算法
  6. * 移植到stm32或者PIC等RAM比较大的单片机上,那么就可以把这个运算位数
  7. * 设置得更加大一点。
  8. */

  9. #define  BCD4_MAX     2  //本系统中,规定的组合BCD码最大字节数,一个字节包含2位,因此4位有效运算数
  10. #define  BCD8_MAX    (BCD4_MAX*2)  //本系统中,规定的非组合BCD码最大字节数,一个字节包含1位,因此4位有效运算数

  11. #define const_rc_size  30  //接收串口中断数据的缓冲区数组大小

  12. #define const_receive_time  5  //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完,这个时间根据实际情况来调整大小

  13. #define uchar unsigned char    //方便移植平台
  14. #define ulong unsigned long   //方便移植平台

  15. //如果在VC的平台模拟此算法,则都定义成int类型,如下:
  16. //#define uchar int  
  17. //#define ulong int

  18. void initial_myself(void);   
  19. void initial_peripheral(void);
  20. void delay_long(unsigned int uiDelaylong);
  21. void delay_short(unsigned int uiDelayShort);


  22. void T0_time(void);  //定时中断函数
  23. void usart_receive(void); //串口接收中断函数
  24. void usart_service(void);  //串口服务程序,在main函数里


  25. void eusart_send(unsigned char ucSendData);

  26. void BCD4_to_BCD8(const unsigned char *p_ucBCD_bit4,unsigned char ucBCD4_cnt,unsigned char *p_ucBCD_bit8,unsigned char *p_ucBCD8_cnt);
  27. void BCD8_to_BCD4(const unsigned char *p_ucBCD_bit8,unsigned char ucBCD8_cnt,unsigned char *p_ucBCD_bit4,unsigned char *p_ucBCD4_cnt);

  28. void ClearAllData(uchar ucARRAY_MAX,uchar *destData);
  29. uchar GetDataLength(const uchar *destData,uchar ucARRAY_MAX);
  30. uchar CmpData(const uchar *destData,const uchar *sourceData); //比较两个数的大小
  31. uchar SubData(const uchar *destData,const uchar *sourceData,uchar *resultData);//两个数相减

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

  33. unsigned int  uiSendCnt=0;     //用来识别串口是否接收完一串数据的计时器
  34. unsigned char ucSendLock=1;    //串口服务程序的自锁变量,每次接收完一串数据只处理一次
  35. unsigned int  uiRcregTotal=0;  //代表当前缓冲区已经接收了多少个数据
  36. unsigned char ucRcregBuf[const_rc_size]; //接收串口中断数据的缓冲区数组
  37. unsigned int  uiRcMoveIndex=0;  //用来解析数据协议的中间变量


  38. unsigned char ucDataBCD4_1[BCD4_MAX]; //接收到的第1个数组合BCD码数组形式  这里是指被减数
  39. unsigned char ucDataBCD4_cnt_1=0;  //接收到的第1个数组合BCD码数组的有效数据长度

  40. unsigned char ucDataBCD4_2[BCD4_MAX]; //接收到的第2个数组合BCD码数组形式  这里是指减数
  41. unsigned char ucDataBCD4_cnt_2=0;  //接收到的第2个数组合BCD码数组的有效数据长度

  42. unsigned char ucDataBCD4_3[BCD4_MAX]; //接收到的第3个数组合BCD码数组形式  这里是指差
  43. unsigned char ucDataBCD4_cnt_3=0;  //接收到的第3个数组合BCD码数组的有效数据长度


  44. unsigned char ucDataBCD8_1[BCD8_MAX]; //接收到的第1个数非组合BCD码数组形式   这里是指被减数
  45. unsigned char ucDataBCD8_cnt_1=0;  //接收到的第1个数非组合BCD码数组的有效数据长度

  46. unsigned char ucDataBCD8_2[BCD8_MAX]; //接收到的第2个数非组合BCD码数组形式   这里是指减数
  47. unsigned char ucDataBCD8_cnt_2=0;  //接收到的第2个数非组合BCD码数组的有效数据长度

  48. unsigned char ucDataBCD8_3[BCD8_MAX]; //接收到的第3个数非组合BCD码数组形式   这里是指差
  49. unsigned char ucDataBCD8_cnt_3=0;  //接收到的第3个数非组合BCD码数组的有效数据长度

  50. unsigned char ucResultFlag=11; //运算结果标志,10代表计算结果超出范围出错,11代表正常。

  51. void main()
  52.   {
  53.    initial_myself();  
  54.    delay_long(100);   
  55.    initial_peripheral();
  56.    while(1)  
  57.    {
  58.        usart_service();  //串口服务程序
  59.    }

  60. }

  61. /* 注释二:
  62. * 组合BCD码转成非组合BCD码。
  63. * 这里的变量ucBCD4_cnt代表组合BCD码的有效字节数.
  64. * 这里的变量*p_ucBCD8_cnt代表经过转换后,非组合BCD码的有效字节数,记得加地址符号&传址进去
  65. * 本程序在上一节的基础上,略作修改,用循环for语句压缩了代码,
  66. * 同时引进了组合BCD码的有效字节数变量。这样就不限定了数据的长度,
  67. * 可以让我们根据数据的实际大小灵活运用。
  68. */
  69. void BCD4_to_BCD8(const unsigned char *p_ucBCD_bit4,unsigned char ucBCD4_cnt,unsigned char *p_ucBCD_bit8,unsigned char *p_ucBCD8_cnt)
  70. {
  71.    unsigned char ucTmep;
  72.    unsigned char i;

  73.    for(i=0;i<BCD8_MAX;i++)   //先把即将保存转换结果的缓冲区清零
  74.    {
  75.       p_ucBCD_bit8[i]=0;
  76.    }


  77.    *p_ucBCD8_cnt=ucBCD4_cnt*2; //转换成非组合BCD码后的有效数据长度  

  78.    for(i=0;i<ucBCD4_cnt;i++)
  79.    {
  80.       ucTmep=p_ucBCD_bit4[ucBCD4_cnt-1-i];
  81.       p_ucBCD_bit8[ucBCD4_cnt*2-i*2-1]=ucTmep>>4;   
  82.       p_ucBCD_bit8[ucBCD4_cnt*2-i*2-2]=ucTmep&0x0f;  
  83.    }

  84. }


  85. /* 注释三:
  86. * 非组合BCD码转成组合BCD码。
  87. * 这里的变量ucBCD8_cnt代表非组合BCD码的有效字节数.
  88. * 这里的变量*p_ucBCD4_cnt代表经过转换后,组合BCD码的有效字节数,记得加地址符号&传址进去
  89. * 本程序在上一节的基础上,略作修改,用循环for语句压缩了代码,
  90. * 同时引进了非组合BCD码的有效字节数变量。这样就不限定了数据的长度,
  91. * 可以让我们根据数据的实际大小灵活运用。
  92. */
  93. void BCD8_to_BCD4(const unsigned char *p_ucBCD_bit8,unsigned char ucBCD8_cnt,unsigned char *p_ucBCD_bit4,unsigned char *p_ucBCD4_cnt)
  94. {
  95.    unsigned char ucTmep;
  96.    unsigned char i;
  97.    unsigned char ucBCD4_cnt;

  98.    for(i=0;i<BCD4_MAX;i++)   //先把即将保存转换结果的缓冲区清零
  99.    {
  100.       p_ucBCD_bit4[i]=0;
  101.    }

  102.    ucBCD4_cnt=(ucBCD8_cnt+1)/2; //非组合BCD码转化成组合BCD码的有效数,这里+1避免非组合数据长度是奇数位
  103.    *p_ucBCD4_cnt=ucBCD4_cnt; //把转换后的结果付给接口指针的数据,可以对外输出结果

  104.    for(i=0;i<ucBCD4_cnt;i++)
  105.    {
  106.       ucTmep=p_ucBCD_bit8[ucBCD4_cnt*2-1-i*2];    //把非组合BCD码第8位分解出来
  107.       p_ucBCD_bit4[ucBCD4_cnt-1-i]=ucTmep<<4;
  108.       p_ucBCD_bit4[ucBCD4_cnt-1-i]=p_ucBCD_bit4[ucBCD4_cnt-1-i]+p_ucBCD_bit8[ucBCD4_cnt*2-2-i*2];    //把非组合BCD码第7位分解出来
  109.    }
  110.   
  111. }

  112. /* 注释四:
  113. *函数介绍:清零数组的全部数组数据
  114. *输入参数:ucARRAY_MAX代表数组定义的最大长度
  115. *输入输出参数:*destData--被清零的数组。
  116. */

  117. void ClearAllData(uchar ucARRAY_MAX,uchar *destData)
  118. {
  119.   uchar i;

  120.   for(i=0;i<ucARRAY_MAX;i++)
  121.   {
  122.      destData[i]=0;
  123.   }

  124. }


  125. /* 注释五:
  126. *函数介绍:获取数组的有效长度
  127. *输入参数:*destData--被获取的数组。
  128. *输入参数:ucARRAY_MAX代表数组定义的最大长度
  129. *返回值  :返回数组的有效长度。比如58786这个数据的有效长度是5
  130. *电子开发者作者:吴坚鸿
  131. */
  132. uchar GetDataLength(const uchar *destData,uchar ucARRAY_MAX)
  133. {
  134.   uchar i;
  135.   uchar DataLength=ucARRAY_MAX;
  136.   for(i=0;i<ucARRAY_MAX;i++)
  137.   {
  138.       if(0!=destData[ucARRAY_MAX-1-i])
  139.           {
  140.              break;
  141.           }
  142.           else
  143.           {
  144.              DataLength--;
  145.           }

  146.   }

  147.   return DataLength;

  148. }



  149. /* 注释六:
  150. *函数介绍:比较两个数的大小
  151. *输入参数:
  152. *(1)*destData--被比较数的数组。
  153. *(2)*sourceData--比较数的数组。
  154. *返回值  :9代表小于,10代表相等,11代表大于。
  155. */
  156. uchar CmpData(const uchar *destData,const uchar *sourceData)
  157. {
  158. uchar cmpResult=10; //开始默认相等
  159. uchar destCnt=0;
  160. uchar sourceCnt=0;
  161. uchar i;

  162. destCnt=GetDataLength(destData,BCD8_MAX);
  163. sourceCnt=GetDataLength(sourceData,BCD8_MAX);

  164. if(destCnt>sourceCnt)  //大于
  165. {
  166.   cmpResult=11;
  167. }
  168. else if(destCnt<sourceCnt) //小于
  169. {
  170.   cmpResult=9;
  171. }
  172. else if((destCnt==0)&&(sourceCnt==0))  //如果都是等于0则等于
  173. {
  174.   cmpResult=10;
  175. }
  176. else  //否则就要继续判断
  177. {
  178.   for(i=0;i<destCnt;i++)
  179.   {
  180.      if(destData[destCnt-1-i]>sourceData[destCnt-1-i])   //从最高位开始判断,如果最高位大于则大于
  181.          {
  182.            cmpResult=11;
  183.            break;
  184.          }
  185.      else if(destData[destCnt-1-i]<sourceData[destCnt-1-i])  //从最高位开始判断,如果最高位小于则小于
  186.          {
  187.            cmpResult=9;
  188.            break;
  189.          }

  190.      //否则继续判断下一位
  191.   }
  192. }


  193. return cmpResult;
  194. }


  195. /* 注释七:
  196. *函数介绍:两个数相减
  197. *输入参数:
  198. *(1)*destData--被减数的数组。
  199. *(2)*sourceData--减数的数组。
  200. *(3)*resultData--差的数组。注意,调用本函数前,必须先把这个数组清零
  201. *返回值  :10代表计算结果是负数或者超出范围出错,11代表正常。
  202. */
  203. uchar SubData(const uchar *destData,const uchar *sourceData,uchar *resultData)
  204. {
  205. uchar subResult=11; //开始默认正常
  206. uchar destCnt=0;

  207. uchar i;
  208. uchar carryData=0;  //进位
  209. uchar maxCnt=0; //最大位数
  210. uchar resultTemp=0; //存放临时运算结果的中间变量

  211. //为什么不在本函数内先把resultData数组清零?因为后面章节中的除法运算中要用到此函数实现连减功能。
  212. //因此如果纯粹实现减法运算时,在调用本函数之前,必须先在外面把差的数组清零,否则会计算出错。

  213. if(CmpData(destData,sourceData)==9)  //被减数小于减数,报错
  214. {
  215.    subResult=10;
  216.    return subResult;  //返回判断结果,并且退出本程序,不往下执行本程序余下代码
  217. }

  218. destCnt=GetDataLength(destData,BCD8_MAX);  //获取被减数的有效数据长度
  219. maxCnt=destCnt;


  220. for(i=0;i<maxCnt;i++)
  221. {

  222.    resultTemp=sourceData[i]+carryData; //按位相加
  223.    if(resultTemp>destData[i])
  224.    {
  225.       resultData[i]=destData[i]+10-sourceData[i]-carryData;    //借位
  226.           carryData=1;
  227.    }
  228.    else
  229.    {
  230.       resultData[i]=destData[i]-sourceData[i]-carryData;    //不用借位
  231.           carryData=0;
  232.    }

  233. }


  234. return subResult;
  235. }



  236. void usart_service(void)  //串口服务程序,在main函数里
  237. {

  238.      unsigned char i=0;   
  239.      unsigned char k=0;   
  240.          unsigned char ucGetDataStep=0;

  241.      if(uiSendCnt>=const_receive_time&&ucSendLock==1) //说明超过了一定的时间内,再也没有新数据从串口来
  242.      {

  243.             ucSendLock=0;    //处理一次就锁起来,不用每次都进来,除非有新接收的数据

  244.             //下面的代码进入数据协议解析和数据处理的阶段

  245.             uiRcMoveIndex=0; //由于是判断数据头,所以下标移动变量从数组的0开始向最尾端移动
  246.             while(uiRcMoveIndex<uiRcregTotal)  //说明还没有把缓冲区的数据读取完
  247.             {
  248.                if(ucRcregBuf[uiRcMoveIndex+0]==0xeb&&ucRcregBuf[uiRcMoveIndex+1]==0x00&&ucRcregBuf[uiRcMoveIndex+2]==0x55)  //数据头eb 00 55的判断
  249.                {
  250.                     
  251.                    i=0;
  252.                    ucGetDataStep=0;
  253.                    ucDataBCD4_cnt_1=0;  //第1个数组合BCD码数组的有效数据长度
  254.                    ucDataBCD4_cnt_2=0;  //第2个数组合BCD码数组的有效数据长度

  255.                    ClearAllData(BCD4_MAX,ucDataBCD4_1);  //清零第1个参与运算的数据
  256.                    ClearAllData(BCD4_MAX,ucDataBCD4_2);  //清零第2个参与运算的数据

  257.                    //以下while循环是通过关键字0x0d 0x0a来截取第1个和第2个参与运算的数据。
  258.                    while(i<(BCD8_MAX+4))//这里+4是因为有2对0x0d 0x0a结尾特殊符号,一个共4个字节
  259.                    {
  260.                       if(ucGetDataStep==0)//步骤0,相当于我平时用的case 0,获取第1个数,在这里是指被加数
  261.                       {
  262.                            if(ucRcregBuf[uiRcMoveIndex+3+i]==0x0d&&ucRcregBuf[uiRcMoveIndex+4+i]==0x0a) //结束标志
  263.                            {
  264.                                 for(k=0;k<ucDataBCD4_cnt_1;k++) //提取第1个参与运算的数组数据
  265.                                 {
  266.                                     ucDataBCD4_1[k]=ucRcregBuf[uiRcMoveIndex+3+i-1-k]; //注意,接收到的数组数据与实际存储的数组数据的下标方向是相反的
  267.                                 }                                                                                                               
  268.                                 i=i+2; //跳过 0x0d 0x0a 这两个字节,进行下一轮的关键字提取
  269.                                 ucGetDataStep=1;  //切换到下一个关键字提取的步骤

  270.                            }
  271.                            else
  272.                            {
  273.                                 i++;
  274.                                 ucDataBCD4_cnt_1++;  //统计第1个有效数据的长度
  275.                            }
  276.                                                                                                                         
  277.                        }
  278.                        else if(ucGetDataStep==1) //步骤1,相当于我平时用的case 1,获取第2个参与运行的数,在这里是加数
  279.                        {
  280.                            if(ucRcregBuf[uiRcMoveIndex+3+i]==0x0d&&ucRcregBuf[uiRcMoveIndex+4+i]==0x0a) //结束标志
  281.                            {
  282.                                 for(k=0;k<ucDataBCD4_cnt_2;k++) //提取第2个参与运算的数组数据
  283.                                 {
  284.                                     ucDataBCD4_2[k]=ucRcregBuf[uiRcMoveIndex+3+i-1-k]; //注意,接收到的数组数据与实际存储的数组数据的下标方向是相反的
  285.                                 }
  286.                                                                                                                                                         
  287.                                 break; //截取数据完成。直接跳出截取数据的while(i<(BCD8_MAX+4))循环

  288.                             }
  289.                             else
  290.                             {
  291.                                 i++;
  292.                                 ucDataBCD4_cnt_2++;  //统计第2个有效数据的长度
  293.                             }
  294.                        }
  295.                     }


  296.                     //注意ucDataBCD8_cnt_1和ucDataBCD8_cnt_2要带地址符号&传址进去
  297.                     BCD4_to_BCD8(ucDataBCD4_1,ucDataBCD4_cnt_1,ucDataBCD8_1,&ucDataBCD8_cnt_1); //把接收到的组合BCD码转换成非组合BCD码  第1个数
  298.                     BCD4_to_BCD8(ucDataBCD4_2,ucDataBCD4_cnt_2,ucDataBCD8_2,&ucDataBCD8_cnt_2); //把接收到的组合BCD码转换成非组合BCD码  第2个数


  299.                     ClearAllData(BCD8_MAX,ucDataBCD8_3);  //清零第3个参与运算的数据,用来接收运行的结果
  300.                     ucResultFlag=SubData(ucDataBCD8_1,ucDataBCD8_2,ucDataBCD8_3); //相减运算,结果放在ucDataBCD8_3数组里
  301.                     if(ucResultFlag==11) //表示运算结果没有超范围
  302.                     {
  303.                        ucDataBCD8_cnt_3=GetDataLength(ucDataBCD8_3,BCD8_MAX);  //获取运算结果的有效字节数
  304.                        BCD8_to_BCD4(ucDataBCD8_3,ucDataBCD8_cnt_3,ucDataBCD4_3,&ucDataBCD4_cnt_3); //把非组合BCD码转成组合BCD码。注意,&ucDataBCD4_cnt_3带地址符号&
  305.                        for(k=0;k<ucDataBCD4_cnt_3;k++) //返回运算结果到上位机上观察。看到的是组合BCD码形式。返回的时候注意数组下标的顺序要反过来发送,先发高位的下标数组
  306.                        {
  307.                           eusart_send(ucDataBCD4_3[ucDataBCD4_cnt_3-1-k]); //往上位机发送一个字节的函数
  308.                        }
  309.                     }
  310.                     else //运算结果超范围,返回EE EE EE
  311.                     {
  312.                        eusart_send(0xee); //往上位机发送一个字节的函数
  313.                        eusart_send(0xee); //往上位机发送一个字节的函数
  314.                        eusart_send(0xee); //往上位机发送一个字节的函数
  315.                     }

  316.                     break;   //退出循环
  317.                }
  318.                uiRcMoveIndex++; //因为是判断数据头,游标向着数组最尾端的方向移动
  319.            }

  320.            ucRcregBuf[0]=0; //把数据头清零,方便下次接收判断新数据
  321.            ucRcregBuf[1]=0;
  322.            ucRcregBuf[2]=0;         
  323.                   
  324.            uiRcregTotal=0;  //清空缓冲的下标,方便下次重新从0下标开始接受新数据
  325.   
  326.      }
  327.                         
  328. }

  329. void eusart_send(unsigned char ucSendData) //往上位机发送一个字节的函数
  330. {

  331.   ES = 0; //关串口中断
  332.   TI = 0; //清零串口发送完成中断请求标志
  333.   SBUF =ucSendData; //发送一个字节

  334.   delay_short(400);  //每个字节之间的延时,这里非常关键,也是最容易出错的地方。延时的大小请根据实际项目来调整

  335.   TI = 0; //清零串口发送完成中断请求标志
  336.   ES = 1; //允许串口中断

  337. }



  338. void T0_time(void) interrupt 1    //定时中断
  339. {
  340.   TF0=0;  //清除中断标志
  341.   TR0=0; //关中断


  342.   if(uiSendCnt<const_receive_time)   //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完
  343.   {
  344.           uiSendCnt++;    //表面上这个数据不断累加,但是在串口中断里,每接收一个字节它都会被清零,除非这个中间没有串口数据过来
  345.       ucSendLock=1;     //开自锁标志
  346.   }



  347.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  348.   TL0=0x0b;
  349.   TR0=1;  //开中断
  350. }


  351. void usart_receive(void) interrupt 4                 //串口接收数据中断        
  352. {        

  353.    if(RI==1)  
  354.    {
  355.         RI = 0;

  356.             ++uiRcregTotal;
  357.         if(uiRcregTotal>const_rc_size)  //超过缓冲区
  358.         {
  359.            uiRcregTotal=const_rc_size;
  360.         }
  361.         ucRcregBuf[uiRcregTotal-1]=SBUF;   //将串口接收到的数据缓存到接收缓冲区里
  362.         uiSendCnt=0;  //及时喂狗,虽然main函数那边不断在累加,但是只要串口的数据还没发送完毕,那么它永远也长不大,因为每个中断都被清零。
  363.    
  364.    }
  365.    else  //发送中断,及时把发送中断标志位清零
  366.    {
  367.         TI = 0;
  368.    }
  369.                                                          
  370. }                                


  371. void delay_long(unsigned int uiDelayLong)
  372. {
  373.    unsigned int i;
  374.    unsigned int j;
  375.    for(i=0;i<uiDelayLong;i++)
  376.    {
  377.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  378.           {
  379.              ; //一个分号相当于执行一条空语句
  380.           }
  381.    }
  382. }

  383. void delay_short(unsigned int uiDelayShort)
  384. {
  385.    unsigned int i;  
  386.    for(i=0;i<uiDelayShort;i++)
  387.    {
  388.      ;   //一个分号相当于执行一条空语句
  389.    }
  390. }


  391. void initial_myself(void)  //第一区 初始化单片机
  392. {

  393.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

  394.   //配置定时器
  395.   TMOD=0x01;  //设置定时器0为工作方式1
  396.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  397.   TL0=0x0b;


  398.   //配置串口
  399.   SCON=0x50;
  400.   TMOD=0X21;
  401.   TH1=TL1=-(11059200L/12/32/9600);  //这段配置代码具体是什么意思,我也不太清楚,反正是跟串口波特率有关。
  402.   TR1=1;

  403. }

  404. void initial_peripheral(void) //第二区 初始化外围
  405. {

  406.    EA=1;     //开总中断
  407.    ES=1;     //允许串口中断
  408.    ET0=1;    //允许定时中断
  409.    TR0=1;    //启动定时中断

  410. }
复制代码

总结陈词:
既然这节讲了减法程序,那么下一节接着讲常用的乘法程序,这种大数据的乘法程序是什么样的?欲知详情,请听下回分解----大数据的乘法运算。

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

作者: 又一个暑假    时间: 2014-8-21 10:29
本帖最后由 又一个暑假 于 2014-8-21 10:37 编辑
jianhong_wu 发表于 2014-4-7 13:14
第四十节:常用的自定义串口通讯协议。
开场白:上一节讲了判断数据头的程序框架,但是在很多项目中,仅仅 ...

“其中第5,6位HH就是有效数据长度。高位在左,低位在右。“这句话没理解,鸿哥麻烦解释一下。“Led灯短亮发送:eb 00 55 02 00 02 00 28 6c”这里的“00 02”表示由“00 28”组成一个数据“0x0028”这样理解对吗
作者: jianhong_wu    时间: 2014-8-21 10:39
又一个暑假 发表于 2014-8-21 10:29
“其中第5,6位HH就是有效数据长度。高位在左,低位在右。“这句话没理解,鸿哥麻烦解释一下

表示有两个字节合成的int型数据,左边的是高位,右边的是低位。比如十六进制的两个字节 A2  9C ,相当于十六进制的int型数据0xA29C,十进制的41628
作者: 又一个暑假    时间: 2014-8-21 10:59
jianhong_wu 发表于 2014-8-21 10:39
表示有两个字节合成的int型数据,左边的是高位,右边的是低位。比如十六进制的两个字节 A2  9C ,相当于 ...

鸿哥用了2个字节来表示数据长度,那么有效数据“XXXXX。。。。YYYYY”可以达到65535个字节对吧
作者: 又一个暑假    时间: 2014-8-21 11:55
本帖最后由 又一个暑假 于 2014-8-21 11:57 编辑
jianhong_wu 发表于 2014-4-7 13:14
第四十节:常用的自定义串口通讯协议。
开场白:上一节讲了判断数据头的程序框架,但是在很多项目中,仅仅 ...

ucRcregBuf[uiRcMoveIndex+6+uiRcSize]=0;  //清零最后一个字节的累加和变量 这里都清零了

        for(i=0;i<(3+1+2+uiRcSize);i++) //计算校验累加和
                                                         {
                                                                 ucRcregBuf[uiRcMoveIndex+6+uiRcSize]=ucRcregBuf[uiRcMoveIndex+6+uiRcSize]+ucRcregBuf[uiRcMoveIndex+i];//这里为什么ucRcregBuf[uiRcMoveIndex+6+uiRcSize]不加为什么不行
                                                         }   


1.jpg (59.1 KB, 下载次数: 330)

1.jpg

作者: jianhong_wu    时间: 2014-8-21 13:38
又一个暑假 发表于 2014-8-21 10:59
鸿哥用了2个字节来表示数据长度,那么有效数据“XXXXX。。。。YYYYY”可以达到65535个字节对吧

是的,理论上是可以达到65535个字节,实际上我们的接收缓冲区没有定时那么大的接收数组。
作者: jianhong_wu    时间: 2014-8-21 13:40
本帖最后由 jianhong_wu 于 2014-8-21 13:42 编辑
又一个暑假 发表于 2014-8-21 11:55
ucRcregBuf=0;  //清零最后一个字节的累加和变量 这里都清零了

        for(i=0;i

它是一个累加和变量,前面已经清零了一次,后面当然不用清零了。比如:

  1. sum=0; //清零一次
  2. for(i=0;i<50;i++)
  3. {
  4.    sum=sum+a[i];
  5. }
复制代码

这里的sum就是累加和。
作者: 又一个暑假    时间: 2014-8-21 17:47
jianhong_wu 发表于 2014-8-21 13:40
它是一个累加和变量,前面已经清零了一次,后面当然不用清零了。比如:

噢,看花眼了。问了如此低级的问题
作者: 看花开花落    时间: 2014-8-21 20:53
鸿哥什么时候发一次鸿哥版的模块化编程呀
作者: jianhong_wu    时间: 2014-8-21 21:39
看花开花落 发表于 2014-8-21 20:53
鸿哥什么时候发一次鸿哥版的模块化编程呀

再过五节左右就会讲到多文件编程的。
作者: waphaoyun    时间: 2014-8-22 19:26
jianhong_wu 发表于 2014-8-19 16:19
第六十三节:大数据的减法运算。

开场白:

鸿哥辛苦了,又更新了  我刚看到三十三节   不知啥时候才能赶上来!
作者: jianhong_wu    时间: 2014-8-26 00:44
第六十四节:大数据的乘法运算。

开场白:
直接用C语言的“*”运算符进行乘法运算时,“被乘数”,“ 乘数”,“积”,这三个数据的最大范围是unsigned long 类型,也就是数据最大范围是4个字节,十进制的范围是0至4294967295。一旦超过了这个范围,则运算会出错。因此,当进行大数据乘法运算时,我们要额外编程序,实现大数据的算法。其实这种算法并不难,就是我们在小学里学的四则运算算法。
      我们先要弄清楚一个新的概念。不考虑小数点的情况下,数据有两种表现形式。一种是常用的变量形式,另外一种是BCD码数组形式。变量的最大范围有限,而BCD码数组的形式是无限的,正因为这个特点,所以我们可以进行大数据运算。
    这一节要教大家一个知识点:
   第一个:如何编写涉及到大数据乘法运算的算法程序函数,同时也复习了指针的用途。

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

(1)硬件平台:
    基于坚鸿51单片机学习板。

(2)实现功能:
波特率是:9600 。
通过电脑串口调试助手模拟上位机,往单片机发送组合BCD码的被乘数和乘数,单片机把组合BCD码的运算结果返回到上位机。被乘数与乘数的最大范围都是从0到99,如果运算的乘积超过允许保存的最大位数范围则返回EE EE EE报错。
往单片机发送的数据格式:EB 00 55 XX  0d  0a  YY  0d  0a指令,其中EB 00 55是数据头,XX 是被乘数,是1个字节的组合BCD码。YY是乘数,可以是1个字节的组合BCD码。0d 0a是固定的结束标志。
例如:
(a)83 x 98 = 8134
上位机发送数据:eb 00 55 83 0d 0a 98 0d 0a
单片机返回:81 34

(3)源代码讲解如下:
  1. #include "REG52.H"


  2. /* 注释一:
  3. * 本系统中的乘法运算,规定两个乘数的最大范围是0至99.
  4. * 由于STC89C52单片机的RAM只有256个,也就是说系统的变量数最大
  5. * 不能超过256个,如果超过了这个极限,编译器就会报错。由于51单片机RAM资源有限,
  6. * 因此规定乘数的最大范围不能超过99,如果这个算法移植到stm32或者PIC等RAM比较大
  7. * 的单片机上,那么就可以把这个运算位数设置得更加大一点。调整下面 BCD4_MAX的大小,
  8. * 可以调整运算的数据范围。
  9. */

  10. #define  BCD4_MAX     3  //为了让乘法的结果不超过范围,因此把组合BCD码最大字节数从上一节的2改成3,一个字节包含2位,因此可以保存6位有效数
  11. #define  BCD8_MAX    (BCD4_MAX*2)  //本系统中,规定的非组合BCD码能保存的最大字节数,一个字节包含1位,因此能保存6位有效运算数

  12. #define const_rc_size  30  //接收串口中断数据的缓冲区数组大小

  13. #define const_receive_time  5  //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完,这个时间根据实际情况来调整大小

  14. #define uchar unsigned char    //方便移植平台
  15. #define ulong unsigned long   //方便移植平台

  16. //如果在VC的平台模拟此算法,则都定义成int类型,如下:
  17. //#define uchar int  
  18. //#define ulong int

  19. void initial_myself(void);   
  20. void initial_peripheral(void);
  21. void delay_long(unsigned int uiDelaylong);
  22. void delay_short(unsigned int uiDelayShort);


  23. void T0_time(void);  //定时中断函数
  24. void usart_receive(void); //串口接收中断函数
  25. void usart_service(void);  //串口服务程序,在main函数里


  26. void eusart_send(unsigned char ucSendData);

  27. void BCD4_to_BCD8(const unsigned char *p_ucBCD_bit4,unsigned char ucBCD4_cnt,unsigned char *p_ucBCD_bit8,unsigned char *p_ucBCD8_cnt);
  28. void BCD8_to_BCD4(const unsigned char *p_ucBCD_bit8,unsigned char ucBCD8_cnt,unsigned char *p_ucBCD_bit4,unsigned char *p_ucBCD4_cnt);

  29. void ClearAllData(uchar ucARRAY_MAX,uchar *destData);
  30. uchar GetDataLength(const uchar *destData,uchar ucARRAY_MAX);
  31. uchar AddData(const uchar *destData,const uchar *sourceData,uchar *resultData);  //两个数相加
  32. void EnlargeData(uchar *destData,uchar enlarge_cnt); //数组向大索引值移位,移一位相当于放大10倍
  33. uchar MultData(const uchar *destData,const uchar *sourceData,uchar *resultData); //两个数相乘

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

  35. unsigned int  uiSendCnt=0;     //用来识别串口是否接收完一串数据的计时器
  36. unsigned char ucSendLock=1;    //串口服务程序的自锁变量,每次接收完一串数据只处理一次
  37. unsigned int  uiRcregTotal=0;  //代表当前缓冲区已经接收了多少个数据
  38. unsigned char ucRcregBuf[const_rc_size]; //接收串口中断数据的缓冲区数组
  39. unsigned int  uiRcMoveIndex=0;  //用来解析数据协议的中间变量


  40. unsigned char ucDataBCD4_1[BCD4_MAX]; //接收到的第1个数组合BCD码数组形式  这里是指被乘数
  41. unsigned char ucDataBCD4_cnt_1=0;  //接收到的第1个数组合BCD码数组的有效数据长度

  42. unsigned char ucDataBCD4_2[BCD4_MAX]; //接收到的第2个数组合BCD码数组形式  这里是指乘数
  43. unsigned char ucDataBCD4_cnt_2=0;  //接收到的第2个数组合BCD码数组的有效数据长度

  44. unsigned char ucDataBCD4_3[BCD4_MAX]; //接收到的第3个数组合BCD码数组形式  这里是指积
  45. unsigned char ucDataBCD4_cnt_3=0;  //接收到的第3个数组合BCD码数组的有效数据长度


  46. unsigned char ucDataBCD8_1[BCD8_MAX]; //接收到的第1个数非组合BCD码数组形式   这里是指被乘数
  47. unsigned char ucDataBCD8_cnt_1=0;  //接收到的第1个数非组合BCD码数组的有效数据长度

  48. unsigned char ucDataBCD8_2[BCD8_MAX]; //接收到的第2个数非组合BCD码数组形式   这里是指乘数
  49. unsigned char ucDataBCD8_cnt_2=0;  //接收到的第2个数非组合BCD码数组的有效数据长度

  50. unsigned char ucDataBCD8_3[BCD8_MAX]; //接收到的第3个数非组合BCD码数组形式   这里是指积
  51. unsigned char ucDataBCD8_cnt_3=0;  //接收到的第3个数非组合BCD码数组的有效数据长度

  52. unsigned char ucResultFlag=11; //运算结果标志,10代表计算结果超出范围出错,11代表正常。

  53. void main()
  54.   {
  55.    initial_myself();  
  56.    delay_long(100);   
  57.    initial_peripheral();
  58.    while(1)  
  59.    {
  60.        usart_service();  //串口服务程序
  61.    }

  62. }

  63. /* 注释二:
  64. * 组合BCD码转成非组合BCD码。
  65. * 这里的变量ucBCD4_cnt代表组合BCD码的有效字节数.
  66. * 这里的变量*p_ucBCD8_cnt代表经过转换后,非组合BCD码的有效字节数,记得加地址符号&传址进去
  67. * 本程序在上一节的基础上,略作修改,用循环for语句压缩了代码,
  68. * 同时引进了组合BCD码的有效字节数变量。这样就不限定了数据的长度,
  69. * 可以让我们根据数据的实际大小灵活运用。
  70. */
  71. void BCD4_to_BCD8(const unsigned char *p_ucBCD_bit4,unsigned char ucBCD4_cnt,unsigned char *p_ucBCD_bit8,unsigned char *p_ucBCD8_cnt)
  72. {
  73.    unsigned char ucTmep;
  74.    unsigned char i;

  75.    for(i=0;i<BCD8_MAX;i++)   //先把即将保存转换结果的缓冲区清零
  76.    {
  77.       p_ucBCD_bit8[i]=0;
  78.    }


  79.    *p_ucBCD8_cnt=ucBCD4_cnt*2; //转换成非组合BCD码后的有效数据长度  

  80.    for(i=0;i<ucBCD4_cnt;i++)
  81.    {
  82.       ucTmep=p_ucBCD_bit4[ucBCD4_cnt-1-i];
  83.       p_ucBCD_bit8[ucBCD4_cnt*2-i*2-1]=ucTmep>>4;   
  84.       p_ucBCD_bit8[ucBCD4_cnt*2-i*2-2]=ucTmep&0x0f;  
  85.    }

  86. }


  87. /* 注释三:
  88. * 非组合BCD码转成组合BCD码。
  89. * 这里的变量ucBCD8_cnt代表非组合BCD码的有效字节数.
  90. * 这里的变量*p_ucBCD4_cnt代表经过转换后,组合BCD码的有效字节数,记得加地址符号&传址进去
  91. * 本程序在上一节的基础上,略作修改,用循环for语句压缩了代码,
  92. * 同时引进了非组合BCD码的有效字节数变量。这样就不限定了数据的长度,
  93. * 可以让我们根据数据的实际大小灵活运用。
  94. */
  95. void BCD8_to_BCD4(const unsigned char *p_ucBCD_bit8,unsigned char ucBCD8_cnt,unsigned char *p_ucBCD_bit4,unsigned char *p_ucBCD4_cnt)
  96. {
  97.    unsigned char ucTmep;
  98.    unsigned char i;
  99.    unsigned char ucBCD4_cnt;

  100.    for(i=0;i<BCD4_MAX;i++)   //先把即将保存转换结果的缓冲区清零
  101.    {
  102.       p_ucBCD_bit4[i]=0;
  103.    }

  104.    ucBCD4_cnt=(ucBCD8_cnt+1)/2; //非组合BCD码转化成组合BCD码的有效数,这里+1避免非组合数据长度是奇数位
  105.    *p_ucBCD4_cnt=ucBCD4_cnt; //把转换后的结果付给接口指针的数据,可以对外输出结果

  106.    for(i=0;i<ucBCD4_cnt;i++)
  107.    {
  108.       ucTmep=p_ucBCD_bit8[ucBCD4_cnt*2-1-i*2];    //把非组合BCD码第8位分解出来
  109.       p_ucBCD_bit4[ucBCD4_cnt-1-i]=ucTmep<<4;
  110.       p_ucBCD_bit4[ucBCD4_cnt-1-i]=p_ucBCD_bit4[ucBCD4_cnt-1-i]+p_ucBCD_bit8[ucBCD4_cnt*2-2-i*2];    //把非组合BCD码第7位分解出来
  111.    }
  112.   
  113. }

  114. /* 注释四:
  115. *函数介绍:清零数组的全部数组数据
  116. *输入参数:ucARRAY_MAX代表数组定义的最大长度
  117. *输入输出参数:*destData--被清零的数组。
  118. */

  119. void ClearAllData(uchar ucARRAY_MAX,uchar *destData)
  120. {
  121.   uchar i;

  122.   for(i=0;i<ucARRAY_MAX;i++)
  123.   {
  124.      destData[i]=0;
  125.   }

  126. }


  127. /* 注释五:
  128. *函数介绍:获取数组的有效长度
  129. *输入参数:*destData--被获取的数组。
  130. *输入参数:ucARRAY_MAX代表数组定义的最大长度
  131. *返回值  :返回数组的有效长度。比如58786这个数据的有效长度是5
  132. *电子开发者作者:吴坚鸿
  133. */
  134. uchar GetDataLength(const uchar *destData,uchar ucARRAY_MAX)
  135. {
  136.   uchar i;
  137.   uchar DataLength=ucARRAY_MAX;
  138.   for(i=0;i<ucARRAY_MAX;i++)
  139.   {
  140.       if(0!=destData[ucARRAY_MAX-1-i])
  141.           {
  142.              break;
  143.           }
  144.           else
  145.           {
  146.              DataLength--;
  147.           }

  148.   }

  149.   return DataLength;

  150. }



  151. /* 注释六:
  152. *函数介绍:两个数相加
  153. *输入参数:
  154. *(1)*destData--被加数的数组。
  155. *(2)*sourceData--加数的数组。
  156. *(3)*resultData--和的数组。注意,调用本函数前,必须先把这个数组清零
  157. *返回值  :10代表计算结果超出范围出错,11代表正常。
  158. */
  159. uchar AddData(const uchar *destData,const uchar *sourceData,uchar *resultData)
  160. {
  161. uchar addResult=11; //开始默认返回的运算结果是正常
  162. uchar destCnt=0;
  163. uchar sourceCnt=0;
  164. uchar i;
  165. uchar carryData=0;  //进位
  166. uchar maxCnt=0; //最大位数
  167. uchar resultTemp=0; //存放临时运算结果的中间变量

  168. //为什么不在本函数内先把resultData数组清零?因为后面章节中的乘法运算中要用到此函数实现连加功能。
  169. //因此如果纯粹实现加法运算时,在调用本函数之前,必须先在外面把和的数组清零,否则会计算出错。

  170. destCnt=GetDataLength(destData,BCD8_MAX);   //获取被加数的有效位数
  171. sourceCnt=GetDataLength(sourceData,BCD8_MAX);  //获取加数的有效位数

  172. if(destCnt>=sourceCnt)  //找出两个运算数据中最大的有效位数
  173. {
  174.    maxCnt=destCnt;
  175. }
  176. else
  177. {
  178.    maxCnt=sourceCnt;
  179. }

  180. for(i=0;i<maxCnt;i++)
  181. {
  182.    resultTemp=destData[i]+sourceData[i]+carryData; //按位相加
  183.    resultData[i]=resultTemp%10;   //截取最低位存放进保存结果的数组
  184.    carryData=resultTemp/10;    //存放进位
  185. }

  186. resultData[i]=carryData;

  187. if((maxCnt==BCD8_MAX)&&(carryData==1))  //如果数组的有效位是最大值并且最后的进位是1,则计算溢出报错
  188. {

  189.   ClearAllData(BCD8_MAX,resultData);

  190.   addResult=10;  //报错
  191. }


  192. return addResult;
  193. }



  194. /* 注释七:
  195. *函数介绍:数组向大索引值移位,移一位相当于放大10倍
  196. *输入参数:*destData--被移位的数组。
  197. *输入参数:enlarge_cnt--被移位的个数。
  198. */
  199. void EnlargeData(uchar *destData,uchar enlarge_cnt)
  200. {
  201.   uchar i;

  202.   if(enlarge_cnt!=0)
  203.   {
  204.     for(i=0;i<(BCD8_MAX-enlarge_cnt);i++)
  205.         {
  206.        destData[BCD8_MAX-1-i]=destData[BCD8_MAX-1-enlarge_cnt-i];
  207.         }

  208.     for(i=0;i<enlarge_cnt;i++) //最低位被移空的补上0
  209.         {
  210.       destData[i]=0;
  211.         }
  212.   }

  213. }




  214. /* 注释八:
  215. *函数介绍:两个数相乘
  216. *输入参数:
  217. *(1)*destData--被乘数的数组。
  218. *(2)*sourceData--乘数的数组。
  219. *(3)*resultData--积的数组。
  220. *返回值  :10代表计算结果超出范围出错,11代表正常。
  221. */
  222. uchar MultData(const uchar *destData,const uchar *sourceData,uchar *resultData)
  223. {
  224. uchar multResult=11; //开始默认正常
  225. uchar destCnt=0;
  226. uchar sourceCnt=0;
  227. uchar i;
  228. uchar j;
  229. uchar carryData=0;  //进位

  230. uchar resultTemp=0; //存放临时运算结果的中间变量

  231. uchar nc_add_result;  //接收相加的运算是否超出范围,这里不用判断,因为不会溢出

  232. uchar multArrayTemp[BCD8_MAX]; //存放临时运算结果的数组中间变量



  233. destCnt=GetDataLength(destData,BCD8_MAX);      //获取被乘数的长度
  234. sourceCnt=GetDataLength(sourceData,BCD8_MAX);   //获取乘数的长度

  235. ClearAllData(BCD8_MAX,resultData);   //清零存储的结果
  236. if((0==destCnt)||(0==sourceCnt)) //被乘数或者乘数为0,则结果为0
  237. {
  238.    return multResult;
  239. }


  240. if((destCnt+sourceCnt+2)>BCD8_MAX)
  241. {
  242.    multResult=10; //运算结果有可能超范围报错
  243.    return multResult;
  244. }

  245. for(i=0;i<sourceCnt;i++)  //乘数
  246. {
  247.    carryData=0; //清零进位
  248.    ClearAllData(BCD8_MAX,multArrayTemp); //清零一位乘数相乘的结果中间变量数组
  249.    for(j=0;j<destCnt;j++) //被乘数
  250.    {
  251.       resultTemp=destData[j]*sourceData[i]+carryData;  //乘数的一位依次与被乘数各位相乘,并且加进位
  252.       multArrayTemp[j]=resultTemp%10;  //存储一位乘数相乘的结果
  253.           carryData=resultTemp/10; //保存进位
  254.    }
  255.    multArrayTemp[j]=carryData; //存储最后的进位
  256.    EnlargeData(multArrayTemp,i); //移位。移一次相当于放大10倍。
  257.    nc_add_result=AddData(resultData,multArrayTemp,resultData); //把一位乘数相乘的结果存储进总结果

  258. }


  259. return multResult;
  260. }



  261. void usart_service(void)  //串口服务程序,在main函数里
  262. {

  263.      unsigned char i=0;   
  264.      unsigned char k=0;   
  265.          unsigned char ucGetDataStep=0;

  266.      if(uiSendCnt>=const_receive_time&&ucSendLock==1) //说明超过了一定的时间内,再也没有新数据从串口来
  267.      {

  268.             ucSendLock=0;    //处理一次就锁起来,不用每次都进来,除非有新接收的数据

  269.             //下面的代码进入数据协议解析和数据处理的阶段

  270.             uiRcMoveIndex=0; //由于是判断数据头,所以下标移动变量从数组的0开始向最尾端移动
  271.             while(uiRcMoveIndex<uiRcregTotal)  //说明还没有把缓冲区的数据读取完
  272.             {
  273.                if(ucRcregBuf[uiRcMoveIndex+0]==0xeb&&ucRcregBuf[uiRcMoveIndex+1]==0x00&&ucRcregBuf[uiRcMoveIndex+2]==0x55)  //数据头eb 00 55的判断
  274.                {
  275.                     
  276.                    i=0;
  277.                    ucGetDataStep=0;
  278.                    ucDataBCD4_cnt_1=0;  //第1个数组合BCD码数组的有效数据长度
  279.                    ucDataBCD4_cnt_2=0;  //第2个数组合BCD码数组的有效数据长度

  280.                    ClearAllData(BCD4_MAX,ucDataBCD4_1);  //清零第1个参与运算的数据
  281.                    ClearAllData(BCD4_MAX,ucDataBCD4_2);  //清零第2个参与运算的数据

  282.                    //以下while循环是通过关键字0x0d 0x0a来截取第1个和第2个参与运算的数据。
  283.                    while(i<(BCD8_MAX+4))//这里+4是因为有2对0x0d 0x0a结尾特殊符号,一个共4个字节
  284.                    {
  285.                       if(ucGetDataStep==0)//步骤0,相当于我平时用的case 0,获取第1个数,在这里是指被乘数
  286.                       {
  287.                            if(ucRcregBuf[uiRcMoveIndex+3+i]==0x0d&&ucRcregBuf[uiRcMoveIndex+4+i]==0x0a) //结束标志
  288.                            {
  289.                                 for(k=0;k<ucDataBCD4_cnt_1;k++) //提取第1个参与运算的数组数据
  290.                                 {
  291.                                     ucDataBCD4_1[k]=ucRcregBuf[uiRcMoveIndex+3+i-1-k]; //注意,接收到的数组数据与实际存储的数组数据的下标方向是相反的
  292.                                 }                                                                                                               
  293.                                 i=i+2; //跳过 0x0d 0x0a 这两个字节,进行下一轮的关键字提取
  294.                                 ucGetDataStep=1;  //切换到下一个关键字提取的步骤

  295.                            }
  296.                            else
  297.                            {
  298.                                 i++;
  299.                                 ucDataBCD4_cnt_1++;  //统计第1个有效数据的长度
  300.                            }
  301.                                                                                                                         
  302.                        }
  303.                        else if(ucGetDataStep==1) //步骤1,相当于我平时用的case 1,获取第2个参与运行的数,在这里是乘数
  304.                        {
  305.                            if(ucRcregBuf[uiRcMoveIndex+3+i]==0x0d&&ucRcregBuf[uiRcMoveIndex+4+i]==0x0a) //结束标志
  306.                            {
  307.                                 for(k=0;k<ucDataBCD4_cnt_2;k++) //提取第2个参与运算的数组数据
  308.                                 {
  309.                                     ucDataBCD4_2[k]=ucRcregBuf[uiRcMoveIndex+3+i-1-k]; //注意,接收到的数组数据与实际存储的数组数据的下标方向是相反的
  310.                                 }
  311.                                                                                                                                                         
  312.                                 break; //截取数据完成。直接跳出截取数据的while(i<(BCD8_MAX+4))循环

  313.                             }
  314.                             else
  315.                             {
  316.                                 i++;
  317.                                 ucDataBCD4_cnt_2++;  //统计第2个有效数据的长度
  318.                             }
  319.                        }
  320.                     }


  321.                     //注意ucDataBCD8_cnt_1和ucDataBCD8_cnt_2要带地址符号&传址进去
  322.                     BCD4_to_BCD8(ucDataBCD4_1,ucDataBCD4_cnt_1,ucDataBCD8_1,&ucDataBCD8_cnt_1); //把接收到的组合BCD码转换成非组合BCD码  第1个数
  323.                     BCD4_to_BCD8(ucDataBCD4_2,ucDataBCD4_cnt_2,ucDataBCD8_2,&ucDataBCD8_cnt_2); //把接收到的组合BCD码转换成非组合BCD码  第2个数


  324.                     ClearAllData(BCD8_MAX,ucDataBCD8_3);  //清零第3个参与运算的数据,用来接收运行的结果
  325.                     ucResultFlag=MultData(ucDataBCD8_1,ucDataBCD8_2,ucDataBCD8_3); //相乘运算,结果放在ucDataBCD8_3数组里
  326.                     if(ucResultFlag==11) //表示运算结果没有超范围
  327.                     {
  328.                        ucDataBCD8_cnt_3=GetDataLength(ucDataBCD8_3,BCD8_MAX);  //获取运算结果的有效字节数
  329.                        if(ucDataBCD8_cnt_3==0) //如果1个有效位数都没有,表示数组所有的数据都是0,这个时候的有效位数应该人为的默认是1,表示一个0
  330.                                            {
  331.                                                ucDataBCD8_cnt_3=1;
  332.                                            }

  333.                        BCD8_to_BCD4(ucDataBCD8_3,ucDataBCD8_cnt_3,ucDataBCD4_3,&ucDataBCD4_cnt_3); //把非组合BCD码转成组合BCD码。注意,&ucDataBCD4_cnt_3带地址符号&
  334.                        for(k=0;k<ucDataBCD4_cnt_3;k++) //返回运算结果到上位机上观察。看到的是组合BCD码形式。返回的时候注意数组下标的顺序要反过来发送,先发高位的下标数组
  335.                        {
  336.                           eusart_send(ucDataBCD4_3[ucDataBCD4_cnt_3-1-k]); //往上位机发送一个字节的函数
  337.                        }
  338.                     }
  339.                     else //运算结果超范围,返回EE EE EE
  340.                     {
  341.                        eusart_send(0xee); //往上位机发送一个字节的函数
  342.                        eusart_send(0xee); //往上位机发送一个字节的函数
  343.                        eusart_send(0xee); //往上位机发送一个字节的函数
  344.                     }

  345.                     break;   //退出循环
  346.                }
  347.                uiRcMoveIndex++; //因为是判断数据头,游标向着数组最尾端的方向移动
  348.            }

  349.            ucRcregBuf[0]=0; //把数据头清零,方便下次接收判断新数据
  350.            ucRcregBuf[1]=0;
  351.            ucRcregBuf[2]=0;         
  352.                   
  353.            uiRcregTotal=0;  //清空缓冲的下标,方便下次重新从0下标开始接受新数据
  354.   
  355.      }
  356.                         
  357. }

  358. void eusart_send(unsigned char ucSendData) //往上位机发送一个字节的函数
  359. {

  360.   ES = 0; //关串口中断
  361.   TI = 0; //清零串口发送完成中断请求标志
  362.   SBUF =ucSendData; //发送一个字节

  363.   delay_short(400);  //每个字节之间的延时,这里非常关键,也是最容易出错的地方。延时的大小请根据实际项目来调整

  364.   TI = 0; //清零串口发送完成中断请求标志
  365.   ES = 1; //允许串口中断

  366. }



  367. void T0_time(void) interrupt 1    //定时中断
  368. {
  369.   TF0=0;  //清除中断标志
  370.   TR0=0; //关中断


  371.   if(uiSendCnt<const_receive_time)   //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完
  372.   {
  373.           uiSendCnt++;    //表面上这个数据不断累加,但是在串口中断里,每接收一个字节它都会被清零,除非这个中间没有串口数据过来
  374.       ucSendLock=1;     //开自锁标志
  375.   }



  376.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  377.   TL0=0x0b;
  378.   TR0=1;  //开中断
  379. }


  380. void usart_receive(void) interrupt 4                 //串口接收数据中断        
  381. {        

  382.    if(RI==1)  
  383.    {
  384.         RI = 0;

  385.             ++uiRcregTotal;
  386.         if(uiRcregTotal>const_rc_size)  //超过缓冲区
  387.         {
  388.            uiRcregTotal=const_rc_size;
  389.         }
  390.         ucRcregBuf[uiRcregTotal-1]=SBUF;   //将串口接收到的数据缓存到接收缓冲区里
  391.         uiSendCnt=0;  //及时喂狗,虽然main函数那边不断在累加,但是只要串口的数据还没发送完毕,那么它永远也长不大,因为每个中断都被清零。
  392.    
  393.    }
  394.    else  //发送中断,及时把发送中断标志位清零
  395.    {
  396.         TI = 0;
  397.    }
  398.                                                          
  399. }                                


  400. void delay_long(unsigned int uiDelayLong)
  401. {
  402.    unsigned int i;
  403.    unsigned int j;
  404.    for(i=0;i<uiDelayLong;i++)
  405.    {
  406.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  407.           {
  408.              ; //一个分号相当于执行一条空语句
  409.           }
  410.    }
  411. }

  412. void delay_short(unsigned int uiDelayShort)
  413. {
  414.    unsigned int i;  
  415.    for(i=0;i<uiDelayShort;i++)
  416.    {
  417.      ;   //一个分号相当于执行一条空语句
  418.    }
  419. }


  420. void initial_myself(void)  //第一区 初始化单片机
  421. {

  422.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

  423.   //配置定时器
  424.   TMOD=0x01;  //设置定时器0为工作方式1
  425.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  426.   TL0=0x0b;


  427.   //配置串口
  428.   SCON=0x50;
  429.   TMOD=0X21;
  430.   TH1=TL1=-(11059200L/12/32/9600);  //这段配置代码具体是什么意思,我也不太清楚,反正是跟串口波特率有关。
  431.   TR1=1;

  432. }

  433. void initial_peripheral(void) //第二区 初始化外围
  434. {

  435.    EA=1;     //开总中断
  436.    ES=1;     //允许串口中断
  437.    ET0=1;    //允许定时中断
  438.    TR0=1;    //启动定时中断

  439. }
复制代码

总结陈词:
既然这节讲了乘法程序,那么下一节接着讲常用的除法程序,这种大数据的除法程序是什么样的?欲知详情,请听下回分解----大数据的除法运算。

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






欢迎光临 独闷闷网 (http://www.dumenmen.com/) Powered by Discuz! X3.2