您好, 登录| 注册|
论坛导航
您好, 登录| 注册|
子站:
商城:
论坛首页    单片机MCU/嵌入式
  •  发帖
  • 收藏

从业将近十年!手把手教你单片机程序框架(连载)
阅读: 29195 |  回复: 307 楼层直达

2014/03/04 23:19:53
1
jianhong_wu
电源币:20 | 积分:0 主题帖:1 | 回复帖:0
LV4
连长

QQ截图20160321155901  【有奖DIY】工程师本色上演 设计征集活动

QQ截图20160321155901 日本之旅(一)回忆感超强的历史,充满期待的未来 你要不要也来看看




      大家好,我叫吴坚鸿,从事单片机项目开发已经有快十年了,目前跟小伙伴们在深圳开了个方案公司,专门承接项目开发。这些年我做的项目不计其数,现在借电子信息网这个平台把我认为最有价值的东西分享给大家,我这个技术贴每个星期更新一两篇,直到我江郎才尽为止。

       

      

      

LV10
司令

第一节:吴坚鸿谈初学单片机的误区。 (1)很难记住繁杂的寄存器?寄存器不用死记硬背,鸿哥我行走江湖多年,连一个寄存器.. 查看全部

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

(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灯的闪烁。(未完待续,下节更精彩,不要走开哦)

回复 ↑收起
LV10
司令

第二节:delay()延时实现LED灯的闪烁。 开场白:            上一节鸿哥列出了初.. 查看全部

第二节: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灯闪烁。

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

回复 ↑收起
电流模式PWM控制器8-SOIC 0至70 IO-Link数字输入集线器参考设计 面向 1000mA 空间受限类应用的同步降压稳压器
0至70的双路低功耗轨到轨输入/输出运算放大器 USB Type-C及PD多端口适配器参考设计 具有40μA静态电流的 2A SIMPLE SWITCHER®、降压稳压器
四线双线至1线数据选择器/多路复用器 支持音频和充电功能的USB Type-C和供电迷你坞 高速、4A、600V 高侧/低侧栅极驱动器
2014/03/04 23:22:43
2
jianhong_wu
电源币:20 | 积分:0 主题帖:1 | 回复帖:0
LV4
连长

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

(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灯的闪烁。(未完待续,下节更精彩,不要走开哦)

2014/03/04 23:26:52
3
jianhong_wu
电源币:20 | 积分:0 主题帖:1 | 回复帖:0
LV4
连长

1

2014/03/05 13:47:30
4
roc19850
电源币:224 | 积分:0 主题帖:15 | 回复帖:204
LV5
营长
楼主:怎么我看TXT附件打开为乱码啊?
2014/03/05 14:18:15
5
jianhong_wu
电源币:20 | 积分:0 主题帖:1 | 回复帖:0
LV4
连长
论坛系统的问题。我直接发代码也会乱在一起,没有段落,所有的段落首位都会连在一起。望管理员能解决这个问题。还有一种方法,你点击右键“目标另存为”下载了之后再打开看,就不会乱。
2014/03/05 17:25:46
9
tangchao123
电源币:63 | 积分:0 主题帖:29 | 回复帖:152
LV5
营长
顶起!!!!!
2014/12/27 08:03:05
258
liht1634
电源币:42 | 积分:0 主题帖:6 | 回复帖:21
LV3
排长
仔细看原代码,会有收获,不妨买一块开发板对着练更好。

http://bbs.elecfans.com/jishu_406259_1_1.html

http://bbs.21ic.com/icview-691804-1-1.html

书的内容丰富,做个目录,也方便大家查看:


【序言】 

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


第一章、【关于时间】 

第二节:delay()延时实现LED灯的闪烁。11    引入状态机

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

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

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


第二章、【人机交互之输入按键】 

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

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

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

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

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

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

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

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

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

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

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


第三章、【人机交互之输出LED及数码管显示】 

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

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

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

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

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

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

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

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

第二十五节:用LED灯和按键来模拟工业自动化设备的运动控制。55    开关型输入传感器软件抗干扰处理

第二十六节:在主函数while循环中驱动数码管的动态扫描程序。56   数码管显示原理图(254楼)

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

第二十八节:数码管通过切换窗口来设置参数。58   1级/2级菜单显示理论

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

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

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

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

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

第三十四节:在数码管中实现iphone4S开机密码锁的程序。103  数组接收数字按键输入void number_key_input(unsigned long ucWhichKey) 加深熟悉一二级菜单显示理论

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

第三十六节:带数码管显示的加法简易计算器。 114  数字按键输入void number_key_input(unsigned long ucWhichKey)和十进制数值移位ulSource=ulSource*10+ucWhichKey

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


第四章、【单片机外设之串口】 

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

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

第四十节:   常用的自定义串口通讯协议。 131    原子锁,防止中断把某个共享数据破坏

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

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

第四十三节:通过串口用计数延时方式发送一串数据。144    环形消息队列 (uiMessageCnt核心变量)

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

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


第六章、【单片机其它外设】

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

第四十七节:操作AT24C02时,利用一气呵成的定时器延时改善数码管的闪烁现象。158   延时等待中加入显示因素避免闪烁

第四十八节:利用DS1302做一个实时时钟。 160   软件滤波之电平按键 BCD码与原始码转换

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

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

第五十一节:利用ADC0832采集电压信号,用连续N次一致性的方法进行滤波处理。 173   连续判断N次一致性(变量一直在变化)


第七章、【编程技巧一之return、指针与互斥

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

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

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

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

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

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

第五十八节:指针的第五大好处,指针在众多数组中的中转站作用。 197  数组之间通过指针赋值转移

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


第八章、【大数据的处理】

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

第六十二节:大数据的加法运算。202  大数据用BCD码数组形式进行运算

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

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

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


第九章、【单片机的外部中断】

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

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


第十章、【编程技巧二】

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

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


第十一章、【人机交互之输出液晶显示】 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

 

2014/12/29 09:17:20
267
aj2zwx
电源币:2 | 积分:0 主题帖:0 | 回复帖:3
LV1
士兵
不错,方便快速跳转学习!
2014/03/05 16:07:16
8
huangyi59
电源币:65 | 积分:0 主题帖:6 | 回复帖:49
LV4
连长
不错,值得学习。占个坐先。
2014/12/31 13:56:38
280
阿哩雷
电源币:152 | 积分:0 主题帖:1 | 回复帖:1
LV1
士兵
hehe
2014/03/05 17:28:40
10
shenx123
电源币:6 | 积分:0 主题帖:136 | 回复帖:768
LV10
司令

第一节的前面没基础的吗? 比如看什么书,之类

从入门开始行吗

2014/03/05 21:14:06
14
jianhong_wu
电源币:20 | 积分:0 主题帖:1 | 回复帖:0
LV4
连长
我已经从很基本的开始讲起的,主要是教大家怎么做项目的程序框架和思路,其他更加基础的东西要靠你们自学,比如C语言的语法,十六进制的转换等等。
2014/03/08 17:23:35
66
SKY丶辉煌
集齐5个最佳回复
电源币:992 | 积分:19 主题帖:91 | 回复帖:646
LV10
司令
十六进制转换啥的,计算机也可以搞定。电脑都带计算器
2014/03/06 13:35:18
20
liuqiwei85
电源币:567 | 积分:0 主题帖:41 | 回复帖:196
LV6
团长

呵呵,从FSY一路追到这里,走到哪里都是焦点,崇拜中

2014/03/06 14:48:16
22
jianhong_wu
电源币:20 | 积分:0 主题帖:1 | 回复帖:0
LV4
连长
谢谢,听你这么说我很开心。希望我分享的东西能对你有帮助。
2015/05/30 17:18:32
285
twh888
电源币:0 | 积分:0 主题帖:14 | 回复帖:47
LV4
连长
感谢鸿哥,才学到这一贴,收获很多,思路很清析,   我会用心学的
2015/06/03 12:25:58
286
mingxu0203
电源币:0 | 积分:3 主题帖:1 | 回复帖:9
LV2
班长
喜欢这样的帖子
2014/03/08 17:22:15
65
SKY丶辉煌
集齐5个最佳回复
电源币:992 | 积分:19 主题帖:91 | 回复帖:646
LV10
司令
真详细,赞一个,鸿哥犀利
2014/03/12 14:02:03
96
shenx123
电源币:6 | 积分:0 主题帖:136 | 回复帖:768
LV10
司令
果然是高手
2014/04/04 16:52:30
122
aaronliang
电源币:0 | 积分:0 主题帖:2 | 回复帖:8
LV2
班长
鸿哥,喜欢你说话的语气,今天开始跟你学单片机。
2014/06/17 15:11:56
176
皓天图1688
电源币:3 | 积分:0 主题帖:7 | 回复帖:12
LV3
排长

鸿哥太牛了,

问题是我这个一点都不懂的,看了你的东西头晕,有没有得治啊鸿哥哥???

2014/06/25 16:25:04
183
zj19841027
电源币:0 | 积分:0 主题帖:1 | 回复帖:15
LV2
班长
顶起顶起顶起顶起顶起
2014/07/09 08:57:44
190
西华杨林
电源币:1508 | 积分:0 主题帖:4 | 回复帖:53
LV4
连长
值得学习,学习总结
2014/12/26 11:38:25
256
wholesome
电源币:0 | 积分:0 主题帖:0 | 回复帖:41
LV3
排长
带个板凳,好好听
2014/12/27 07:59:55
257
liht1634
电源币:42 | 积分:0 主题帖:6 | 回复帖:21
LV3
排长

 ding

 

2016/01/27 13:52:57
292
空军通信兵
电源币:0 | 积分:10 主题帖:6 | 回复帖:251
LV7
旅长
不错,做个记号方便下次上论坛很快就能找到
2016/02/21 15:29:52
297
chen08046792
电源币:63 | 积分:0 主题帖:1 | 回复帖:51
LV3
排长

学习

2016/07/13 10:21:12
304
wyl_66
电源币:1 | 积分:3 主题帖:1 | 回复帖:11
LV2
班长
楼主的一番话给想学单片机的人鼓足了勇气。
2014/03/05 15:05:03
6
电源网-俪俪
电源币:263 | 积分:0 主题帖:58 | 回复帖:84
LV5
营长

代码与word 粘贴,可以选择以上按钮操作下~~

2014/03/05 20:59:02
12
jianhong_wu
电源币:20 | 积分:0 主题帖:1 | 回复帖:0
LV4
连长
我试过了,都不行。你们用的论坛系统不是discuz的模板吧,这个问题不解决,真的很打击发帖的积极性。
2014/03/08 17:25:16
67
SKY丶辉煌
集齐5个最佳回复
电源币:992 | 积分:19 主题帖:91 | 回复帖:646
LV10
司令
加油!不要停,雅蠛蝶
2014/03/10 12:43:30
78
sherlocked
电源币:4 | 积分:0 主题帖:8 | 回复帖:33
LV4
连长
应该是编码不一样,,,但是别忘了微软有个牛逼的东西,,记事本,,这样先在记事本中粘一下,,就行了
2014/03/21 22:41:25
109
FBY188
电源币:10 | 积分:0 主题帖:41 | 回复帖:67
LV5
营长
 请问这个菜单是在WORD 的吗?? 在  WORD 的那个菜单 是插入菜单还是工具????
2014/03/05 16:05:22
7
风之崖
电源币:72 | 积分:0 主题帖:2 | 回复帖:73
LV4
连长
顶起!
2014/03/05 20:53:09
11
jianhong_wu
电源币:20 | 积分:0 主题帖:1 | 回复帖:0
LV4
连长

第二节: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灯闪烁。

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

2014/03/07 14:50:07
39
myth
电源币:23 | 积分:0 主题帖:1 | 回复帖:25
LV2
班长
mark!!
2014/03/08 17:32:03
68
SKY丶辉煌
集齐5个最佳回复
电源币:992 | 积分:19 主题帖:91 | 回复帖:646
LV10
司令

精彩,很详细

2014/04/19 18:27:14
139
sherlocked
电源币:4 | 积分:0 主题帖:8 | 回复帖:33
LV4
连长
第一节,很好,,已经亲身验证,最喜欢的是您的模块化程序,,学习
2017/04/27 09:06:57
307
1179300092
电源币:27 | 积分:0 主题帖:45 | 回复帖:702
LV8
师长
不错
2014/03/05 21:07:53
13
jianhong_wu
电源币:20 | 积分:0 主题帖:1 | 回复帖:0
LV4
连长

第三节:累计主循环次数使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灯闪烁。

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

2014/04/19 18:57:23
140
sherlocked
电源币:4 | 积分:0 主题帖:8 | 回复帖:33
LV4
连长
鸿哥,我在这里有一个问题,在case下的第一个语句,是
uiTimeCnt++;问题是,这个变量会老老实实的一直累加到上限在去执行if吗?还是这里有一个else作为默认的第二分支让他一直在做累加?我在编译时倒是没有问题,却有3个警告,
2014/12/27 09:55:56
260
zhan2012
电源币:36 | 积分:0 主题帖:9 | 回复帖:44
LV4
连长
  在这里是这个变量是全局变量
2014/12/27 09:08:09
259
zhan2012
电源币:36 | 积分:0 主题帖:9 | 回复帖:44
LV4
连长
     思路的确不错,三区一线,这里主要使用for循环,应该是开头吧
2014/03/05 21:18:02
15
jianhong_wu
电源币:20 | 积分:0 主题帖:1 | 回复帖:0
LV4
连长

第四节:累计定时中断次数使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灯闪烁的单任务,如果要多增加一个任务来并行处理,该怎么办?欲知详情,请听下回分解-----蜂鸣器的驱动程序。

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

2014/03/06 08:36:53
16
pangjihao
电源币:198 | 积分:25 主题帖:140 | 回复帖:1228
LV10
司令
向老师学习!
2014/03/06 10:37:20
17
开kai
电源币:4 | 积分:0 主题帖:0 | 回复帖:11
LV2
班长
学习.我顶
2014/03/06 11:12:11
18
wheelzhou
电源币:1081 | 积分:9 主题帖:232 | 回复帖:806
LV9
军长
学习ing,复制, 保存了
2014/03/06 12:55:19
19
hzlqz
电源币:21 | 积分:0 主题帖:0 | 回复帖:20
LV2
班长
很好,终于找到老师了。
2014/03/08 17:36:52
69
SKY丶辉煌
集齐5个最佳回复
电源币:992 | 积分:19 主题帖:91 | 回复帖:646
LV10
司令
真的很棒!
2014/03/10 12:49:03
80
sherlocked
电源币:4 | 积分:0 主题帖:8 | 回复帖:33
LV4
连长
顶起,,,,,
2014/12/27 10:02:26
261
zhan2012
电源币:36 | 积分:0 主题帖:9 | 回复帖:44
LV4
连长
   
void main() 
  {
   initial_myself();  
   delay_long(100);   
   initial_peripheral(); 
   while(1)   
   {
      led_flicker();   
   }

}
这个架构我要学习
2014/12/27 10:19:16
262
zhan2012
电源币:36 | 积分:0 主题帖:9 | 回复帖:44
LV4
连长
  
void initial_peripheral() //第二区 初始化外围
{
  EA=1;     //开总中断
  ET0=1;    //允许定时中断
  TR0=1;    //启动定时中断
}

void initial_myself()  //第一区 初始化单片机
{
TMOD=0x01;  //设置定时器0为工作方式1
TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
TL0=0x2f;
led_dr=0;  //LED灭
}
这两个函数这样子分开写?我一直理解成
void initial_myself() 用于单片机本身初始化(内部寄存器,包括需要特别处理I/O口)
void initial_peripheral()而这个函数用于和一些外围通信器件初始化
2014/03/06 14:45:57
21
jianhong_wu
电源币:20 | 积分:0 主题帖:1 | 回复帖:0
LV4
连长

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

开场白:

      上一节讲了利用累计定时中断次数实现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;    //启动定时中断

}

 

总结陈词:

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

 

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

 

2014/06/16 15:12:27
174
不进则退
电源币:3 | 积分:0 主题帖:5 | 回复帖:97
LV5
营长
为什么闪烁不放到中断里面?不跟蜂鸣器一样?中断每多一条指令不是越不准么 ?
2014/12/27 10:28:01
263
zhan2012
电源币:36 | 积分:0 主题帖:9 | 回复帖:44
LV4
连长
* 第三步:把程序烧录进单片机后,上电开始测试,手上同步打开手机里的秒表。
*         如果单片机仅仅跑了27秒。
* 第四步:那么最终得出1秒钟需要的定时中断次数是:const_time_1s=(200*60)/27=444

27秒是怎样出来?什么条件?
2014/12/31 15:45:33
281
zoumengjun123
电源币:0 | 积分:3 主题帖:0 | 回复帖:2
LV1
士兵

那个200是假设的!   27是实测的时间!

2014/03/06 14:54:23
23
jianhong_wu
电源币:20 | 积分:0 主题帖:1 | 回复帖:0
LV4
连长

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

开场白:

     上一节讲了多任务中蜂鸣器驱动程序的框架,这节继续利用多任务处理的方式,在主函数中利用累计主循环次数来实现独立按键的检测。要教会大家四个知识点:

     第一点:独立按键的驱动程序框架。

     第二点:用累计主循环次数来实现去抖动的延时。

     第三点:灵活运用防止按键不松手后一直触发的按键自锁标志。

     第四点:在按键去抖动延时计时中,添加一个抗干扰的软件监控判断。一旦发现瞬间杂波干扰,马上把延时计数器清零。这种方法是我在复杂的工控项目中总结出来的。以后凡是用到开关感应器的地方,都可以用类似的方法实现软件上的抗干扰处理。具体内容,请看源代码讲解。

(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。如何解决这个问题呢?欲知详情,请听下回分解-----在主函数中利用累计定时中断的次数来实现独立按键的检测。

 

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

 

2014/11/19 06:56:49
239
elec12345678
电源币:2 | 积分:3 主题帖:0 | 回复帖:1
LV1
士兵

吴老师好!跟着您的教程,我进行了系统的学习。很佩服您的编程思路,看似简单,但里面有很多我难以想象的智慧,我现在只能停留在对您程序执行过程的分析,我就是不明白您是怎么做到既简单明了,又不出漏洞的呢?我没有系统学过编程,现在需要看些什么书呢?

我分析您第六节按键扫描程序

else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
  {
     ++uiKeyTimeCnt1;  //延时计数器
     if(uiKeyTimeCnt1>const_key_time1)
     {
        uiKeyTimeCnt1=0; //这一行不加对程序执行没有影响呢
        ucKeyLock1=1;  //自锁按键置位,避免一直触发
        ucKeySec=1;    //触发1号键

我觉得
       uiKeyTimeCnt1=0; //这一行不加对程序执行没有影响呢
不知道不加会不会出现什么漏洞,望多指教!

2014/12/27 10:43:57
264
zhan2012
电源币:36 | 积分:0 主题帖:9 | 回复帖:44
LV4
连长
  快速连续多按几次,你看看会发生什么情况??
2014/03/06 15:02:55
24
jianhong_wu
电源币:20 | 积分:0 主题帖:1 | 回复帖:0
LV4
连长

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

开场白:

      上一节讲了在主函数中利用累计主循环次数来实现独立按键的检测,但是它也有一个小小的不足,随着在主函数中任务量的增加,为了保证去抖动延时的时间一致性,要适当调整一下去抖动的时间阀值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;    //启动定时中断

}

 

总结陈词:

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

 

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

 

2014/03/06 15:07:18
25
jianhong_wu
电源币:20 | 积分:0 主题帖:1 | 回复帖:0
LV4
连长

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

开场白:

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

(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;    //启动定时中断

}

 

总结陈词:

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

 

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

 

2014/03/06 15:12:31
26
jianhong_wu
电源币:20 | 积分:0 主题帖:1 | 回复帖:0
LV4
连长

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

开场白:

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

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

(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;    //启动定时中断

}

 

总结陈词:

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

 

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

 

2014/12/27 15:08:09
265
zhan2012
电源币:36 | 积分:0 主题帖:9 | 回复帖:44
LV4
连长
       
  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号键 } }
  }
你这个双击,要是我多次连续敲击,不知什么效果?
2015/05/04 20:38:18
284
yangfeng139
电源币:0 | 积分:0 主题帖:1 | 回复帖:4
LV1
士兵
效果很好,已经试过了。连续按多次,只要ucKeyTouchCnt1>1,就清零,认为一次双击。
ucKeyTouchCnt1又重0开始。
又从
2014/03/06 15:17:40
27
jianhong_wu
电源币:20 | 积分:0 主题帖:1 | 回复帖:0
LV4
连长

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

开场白:

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

(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;    //启动定时中断

}

 

总结陈词:

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

 

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

 

2014/03/06 16:34:57
28
yzl1128
电源币:1662 | 积分:0 主题帖:0 | 回复帖:24
LV2
班长
不错,值得学习.
2014/03/06 17:09:49
29
maluhan
电源币:0 | 积分:0 主题帖:0 | 回复帖:6
LV1
士兵
相当不错,楼主辛苦
2014/03/07 08:52:16
33
roc19850
电源币:224 | 积分:0 主题帖:15 | 回复帖:204
LV5
营长
太给力了!
2014/03/07 10:28:35
34
开kai
电源币:4 | 积分:0 主题帖:0 | 回复帖:11
LV2
班长
顶!
2014/12/27 15:25:32
266
zhan2012
电源币:36 | 积分:0 主题帖:9 | 回复帖:44
LV4
连长
  
  if(key_sr1==1||key_sr2==1)//IO是高电平,说明两个按键没有全部被按下,这时要及时清零一些标志位---难道不是使用“&&”??
  {/*使用“||”这里其中任何一个不按下,也会执行ucKeyLock12=0;*/ ucKeyLock12=0; //按键自锁标志清零
         uiKeyTimeCnt12=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  }
  else if(ucKeyLock12==0)//有按键按下,且是第一次被按下
  {
     uiKeyTimeCnt12++; //累加定时中断次数
     if(uiKeyTimeCnt12>const_key_time12)
     {
        uiKeyTimeCnt12=0; 
        ucKeyLock12=1;  //自锁按键置位,避免一直触发
        ucKeySec=1;    //触发1号键
              
     }
  }
2014/03/06 17:17:15
30
tanb006
电源币:627 | 积分:29 主题帖:199 | 回复帖:919
LV10
司令

如此精彩,不得不顶.

2014/03/06 17:30:48
31
luxha
电源币:25 | 积分:0 主题帖:99 | 回复帖:131
LV6
团长
谢谢!好经典!很久没看到这么好的文章了!
2014/03/06 19:18:55
32
wp7521
电源币:20 | 积分:0 主题帖:0 | 回复帖:4
LV1
士兵
向大师致敬!!!
2014/03/07 11:54:30
35
qinzutaim
电源币:5074 | 积分:134 主题帖:36 | 回复帖:3517
LV11
统帅
这么好的帖子,当然得做个标记啦
2014/03/07 12:29:56
36
chrisma2005
电源币:2 | 积分:0 主题帖:5 | 回复帖:27
LV3
排长
不错,值得学习。占个坐先。
2014/03/07 12:33:44
37
帅男
电源币:2 | 积分:0 主题帖:7 | 回复帖:41
LV4
连长

我也想学!但什么也不懂!

2014/03/07 12:59:20
38
电源网-俪俪
电源币:263 | 积分:0 主题帖:58 | 回复帖:84
LV5
营长
别急,慢慢来~~~
2014/03/07 16:05:10
40
眼高手低怎么行
电源币:2 | 积分:0 主题帖:0 | 回复帖:1
LV1
士兵
 鸿哥我们在等你哦
2014/03/07 16:22:24
41
jianhong_wu
电源币:20 | 积分:0 主题帖:1 | 回复帖:0
LV4
连长
第十一节:同一个按键短按与长按的区别触发。

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

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

(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;    //启动定时中断

}

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

(未完待续,下节更精彩,不要走开哦)
2014/03/07 17:07:31
42
jianhong_wu
电源币:20 | 积分:0 主题帖:1 | 回复帖:0
LV4
连长
第十二节:按住一个独立按键不松手的连续步进触发。

开场白:
上一节讲了同一个按键短按与长按的区别触发功能,这节要教会大家两个知识点:
第一个知识点:如何在上一节的基础上,略作修改,就可以实现按住一个独立按键不松手的连续步进触发。
第二个知识点:在单片机的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   //1秒钟的时间需要的定时中断次数



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  //按住累加到1秒后仍然不放手,这个时候进入有节奏的连续触发
  {
     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的时候,还来不及松手就很容易超过头,不好微调。有没有完整的方案解决这个问题?当然有。欲知详情,请听下回分解-----按住一个独立按键不松手的加速匀速触发。

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

 

2014/03/07 17:09:09
43
jianhong_wu
电源币:20 | 积分:0 主题帖:1 | 回复帖:0
LV4
连长
第十三节:按住一个独立按键不松手的加速匀速触发。

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

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

(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/12/13 09:57:54
244
hyping29
电源币:0 | 积分:0 主题帖:3 | 回复帖:7
LV2
班长

吴老师你好,这个标志位为什么不放在按键释放的时候清掉呢?

2014/03/07 17:11:24
44
jianhong_wu
电源币:20 | 积分:0 主题帖:1 | 回复帖:0
LV4
连长
第十四节:矩阵键盘的单个触发。

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

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

(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;    //启动定时中断

}

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

(未完待续,下节更精彩,不要走开哦)
2014/03/07 17:12:33
45
jianhong_wu
电源币:20 | 积分:0 主题帖:1 | 回复帖:0
LV4
连长
第十五节:矩阵键盘单个触发的压缩代码编程。

开场白:
上一节讲了矩阵键盘的单个触发。这节要教会大家在不改变其它任何性能的情况下,把上一节的按键扫描程序压缩一下容量。经过压缩后,把原来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;    //启动定时中断

}

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

(未完待续,下节更精彩,不要走开哦)
2014/03/07 17:29:54
46
jianhong_wu
电源币:20 | 积分:0 主题帖:1 | 回复帖:0
LV4
连长
第十六节:矩阵键盘的组合按键触发。

开场白:
上一节讲了矩阵键盘单个触发的压缩代码编程。这节讲矩阵键盘的组合按键触发。要教会大家三个知识点:
第一点:如何把矩阵键盘翻译成独立按盘的处理方式。然后按独立按键的方式来实现组合按键的功能。
第二点:要提醒大家在设计矩阵键盘时,很容易犯的一个错误。任意两个组合按键不能处于同一行,否则触发性能大打折扣。在做产品的时候,硬件电路设计中,除了四路行输入的要加上拉电阻,四路列输出也应该串入一个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灯的基本驱动程序。

(未完待续,下节更精彩,不要走开哦)
2014/03/07 17:41:03
47
jianhong_wu
电源币:20 | 积分:0 主题帖:1 | 回复帖:0
LV4
连长
第十七节:两片联级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口直接驱动的方式。
(未完待续,下节更精彩,不要走开哦)
2014/03/07 17:42:05
48
jianhong_wu
电源币:20 | 积分:0 主题帖:1 | 回复帖:0
LV4
连长
第十八节:把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的跑马灯程序。

(未完待续,下节更精彩,不要走开哦)
2014/03/07 17:43:19
49
jianhong_wu
电源币:20 | 积分:0 主题帖:1 | 回复帖:0
LV4
连长
第十九节:依次逐个点亮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灯中,依次逐个亮灯并且每次只能亮一个灯(其它的都灭),依次循环,我们该如何编写程序?欲知详情,请听下回分解-----依次逐个亮灯并且每次只能亮一个灯的跑马灯程序。

(未完待续,下节更精彩,不要走开哦)
2014/03/07 17:44:15
50
jianhong_wu
电源币:20 | 积分:0 主题帖:1 | 回复帖:0
LV4
连长
第二十节:依次逐个亮灯并且每次只能亮一个灯的跑马灯程序。

开场白:
上一节讲了先依次逐个亮再依次逐个灭的跑马灯程序。这一节在上一节的基础上,略作修改,继续讲跑马灯程序。我的跑马灯程序看似简单而且重复,其实蕴含着鸿哥的大智慧。它是基于鸿哥的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;    //启动定时中断

}

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

(未完待续,下节更精彩,不要走开哦)
2014/03/07 17:45:22
51
jianhong_wu
电源币:20 | 积分:0 主题帖:1 | 回复帖:0
LV4
连长
第二十一节:多任务并行处理两路跑马灯。

开场白:
上一节讲了依次逐个亮灯并且每次只能亮一个灯的跑马灯程序。这一节要结合前面两节的内容,实现多任务并行处理两路跑马灯。要教会大家一个知识点:利用鸿哥的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;    //启动定时中断

}

总结陈词:
这一节讲了多任务并行处理两路跑马灯的程序,从下一节开始,将会在跑马灯的基础上,新加入按键这个元素。如何把按键跟跑马灯的任务有效的关联起来,欲知详情,请听下回分解-----独立按键控制跑马灯的方向。

(未完待续,下节更精彩,不要走开哦)
2014/03/07 17:46:23
52
jianhong_wu
电源币:20 | 积分:0 主题帖:1 | 回复帖:0
LV4
连长
第二十二节:独立按键控制跑马灯的方向。

开场白:
上一节讲了多任务并行处理两路跑马灯的程序。这一节要教会大家一个知识点:如何通过一个中间变量把按键跟跑马灯的任务有效的关联起来。
具体内容,请看源代码讲解。

(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;    //启动定时中断

}

总结陈词:
这一节讲了独立按键控制跑马灯的方向。如果按键要控制跑马灯的速度,我们该怎么编写程序呢?欲知详情,请听下回分解-----独立按键控制跑马灯的速度。

(未完待续,下节更精彩,不要走开哦)
2014/03/07 17:49:03
53
jianhong_wu
电源币:20 | 积分:0 主题帖:1 | 回复帖:0
LV4
连长
第二十三节:独立按键控制跑马灯的速度。

开场白:
上一节讲了独立按键控制跑马灯的方向。这一节继续要教会大家一个知识点:如何通过一个中间变量把按键跟跑马灯的速度有效关联起来。
具体内容,请看源代码讲解。

(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;    //启动定时中断

}

总结陈词:
这一节讲了独立按键控制跑马灯的速度。如果按键要控制跑马灯的启动和暂停,我们该怎么编写程序呢?欲知详情,请听下回分解-----独立按键控制跑马灯的启动和暂停。

(未完待续,下节更精彩,不要走开哦)
2014/03/07 17:50:50
54
jianhong_wu
电源币:20 | 积分:0 主题帖:1 | 回复帖:0
LV4
连长
第二十四节:独立按键控制跑马灯的启动和暂停。

开场白:
上一节讲了独立按键控制跑马灯的速度。这一节继续要教会大家一个知识点:如何通过一个中间变量把按键跟跑马灯的启动和暂停有效关联起来。
具体内容,请看源代码讲解。

(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灯和按键来模拟工业自动化设备的运动控制。

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

 

2014/03/07 17:51:53
55
jianhong_wu
电源币:20 | 积分:0 主题帖:1 | 回复帖:0
LV4
连长
第二十五节:用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循环中驱动数码管的动态扫描程序。

(未完待续,下节更精彩,不要走开哦)
2014/03/07 17:53:03
56
jianhong_wu
电源币:20 | 积分:0 主题帖:1 | 回复帖:0
LV4
连长
第二十六节:在主函数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学习板上,发现显示的效果还是挺不错的。但是,本程序也有一个弱点,在一些项目中 ,主函数循环中的任务越多,就意味着在某一瞬间,每显示一位数码管停留的时间就会越久,一旦超过某个值,会严重影响显示的效果,有没有办法改善它?当然有。欲知详情,请听下回分解-----在定时中断里动态扫描数码管的程序。

(未完待续,下节更精彩,不要走开哦)
2014/12/25 16:22:57
254
liht1634
电源币:42 | 积分:0 主题帖:6 | 回复帖:21
LV3
排长

数码管显示要看原理图,不然不好看懂dig_hc595_drive(xx,yy)函数,其中第一个形参xx是驱动数码管段seg的引脚,第二个形参yy是驱动数码管公共位com的引脚。

unsigned char ucDigStatusTemp16_09是每个数码管的段码unsigned char ucDigStatusTemp08_01放的是位码

高位在前,先把段码移在位码的595(U3)中;再把位码移进去,段码就在高8位(U2)上,位码在低8位(U3)上。

 

2014/03/07 17:54:29
57
jianhong_wu
电源币:20 | 积分:0 主题帖:1 | 回复帖:0
LV4
连长
第二十七节:在定时中断里动态扫描数码管的程序。

开场白:
上一节讲了在主函数循环中动态扫描数码管的程序,但是该程序有一个隐患,在一些项目中 ,主函数循环中的任务越多,就意味着在某一瞬间,每显示一位数码管停留的时间就会越久,一旦超过某个值,会严重影响显示的效果。这一节要教会大家两个知识点:
第一个:如何把动态扫描数码管的程序放在定时中断里,彻底解决上节的显示隐患。
第二个:在定时中断里的重装初始值不能太大,否则动态扫描数码管的速度就不够。我把原来常用的初始值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;    //启动定时中断

}

总结陈词:
     有的朋友会质疑,很多教科书上说,定时中断函数里面的内容应该越少越好,你把动态驱动数码管的函数放在中断里面,难道不会影响其它任务的执行吗?我的回答是,大部分的小项目都不会影响,只有少数实时性要求非常高的项目会影响,而对于这类项目,我的做法是从一开始设计硬件电路板的时候,就应该放弃用动态扫描数码管的方案,而是应该选数码管专用驱动芯片来实现静态驱动。因为动态扫描数码管本来就不适合应用在实时性非常高的项目。
      前面这两节都讲了数码管的驱动程序,要在此基础上,做一些项目中经常遇到的界面应用,我们该怎么写程序?欲知详情,请听下回分解-----数码管通过切换窗口来设置参数。

(未完待续,下节更精彩,不要走开哦)
2014/03/07 17:55:33
58
jianhong_wu
电源币:20 | 积分:0 主题帖:1 | 回复帖:0
LV4
连长
第二十八节:数码管通过切换窗口来设置参数。

开场白:
上一节讲了数码管的驱动程序,这节在上节的基础上,通过按键切换不同的窗口来设置不同的参数。
这一节要教会大家三个知识点:
第一个:鸿哥首次提出的“一二级菜单显示理论”:凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用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位数码管显示当前窗口被设置的参数。范围是从0到9999。
有三个按键。一个是加按键,按下此按键会依次增加当前窗口的参数。一个是减按键,按下此按键会依次减少当前窗口的参数。一个是切换窗口按键,按下此按键会依次循环切换不同的窗口。

(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的高位。

(未完待续,下节更精彩,不要走开哦)
2014/03/07 17:56:52
59
jianhong_wu
电源币:20 | 积分:0 主题帖:1 | 回复帖:0
LV4
连长
第二十九节:数码管通过切换窗口来设置参数,并且不显示为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位数码管显示当前窗口被设置的参数。范围是从0到9999。
有三个按键。一个是加按键,按下此按键会依次增加当前窗口的参数。一个是减按键,按下此按键会依次减少当前窗口的参数。一个是切换窗口按键,按下此按键会依次循环切换不同的窗口。
并且要求被设置的数据不显示为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;    //启动定时中断
}


总结陈词:
数码管通过切换窗口来设置参数,这里的窗口类似于一级菜单,在一级菜单下,还可以分解出二级菜单。一级菜单的特点是整屏数码管的显示内容全部都改变,而二级菜单的特点是只改变其中一部分数码管的内容。二级菜单的程序怎么编写?欲知详情,请听下回分解-----数码管通过闪烁来设置数据。

(未完待续,下节更精彩,不要走开哦)
2014/03/07 17:58:12
60
jianhong_wu
电源币:20 | 积分:0 主题帖:1 | 回复帖:0
LV4
连长
第三十节:数码管通过闪烁来设置数据。

开场白:
   上一节讲了一级菜单,这一节要教会大家两个知识点:
第一个:二级菜单的程序的程序框架。
第二个:继续加深熟悉鸿哥首次提出的“一二级菜单显示理论”:凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用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;    //启动定时中断

}


总结陈词:
这节讲了数码管通过闪烁来设置数据的基本程序,但是该程序只有一个窗口。实际应用中,有些项目会有几个窗口,而且每个窗口都要设置几个参数,这样的程序该怎么写?欲知详情,请听下回分解-----数码管通过一二级菜单来设置数据的综合程序。

(未完待续,下节更精彩,不要走开哦)
2014/03/07 17:59:24
61
jianhong_wu
电源币:20 | 积分:0 主题帖:1 | 回复帖:0
LV4
连长
第三十一节:数码管通过一二级菜单来设置数据的综合程序。

开场白:
   上一节讲了二级菜单,这一节要教会大家两个知识点:
第一个:数码管通过一二级菜单来设置数据的综合程序框架。
第二个:继续加深熟悉鸿哥首次提出的“一二级菜单显示理论”:凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用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下,根据不同的局部闪烁位置来设置不同的参数