STC8H8K64U开发箱V9.62学习笔记
首先感谢STC平台,通过对陈教授的视频学习,赠送的开发箱。在网上看见吴坚鸿 的《手把手教你单片机程序框架》感觉不错,就移植到STC8H8K64U开发箱V9.62。同时感谢吴坚鸿老师!下面是我是移植笔记:
第一课:LED闪烁
/*******************************************
本学习程序来源于吴坚鸿老师《手把手教你单片机程序框架》,所有
权属于吴坚鸿老师,本人是用来学习,移植STC8H8K64U开发箱9.62版。
到吴坚鸿老师若有异议,我立即删除!
开场白:
初学者关注的核心:
那就是裸机奔跑的程序结构。一个好的程序结构,本身就是一个微型的
多任务操作系统。鸿哥教给大家的就是如何编写这个简单的操作系统。
在 main函数循环中用 switch 语句实现多任务并行处理的任务切换,再
外加一个定时器中断,这两者的结合就是鸿哥多年来所有实战项目的核心。
鸿哥的程序结构看似简单,实际上就是那么简单。大家不用着急,本篇连
载文章现在才正式开始,这一节我要教会大家两个知识点:
第一点:鸿哥首次提出的“三区一线”理论。此理论把程序代码分成三个区,一个延时分割线。
第二点:delay()延时的用途。
(1)硬件平台:STC8H8K64U开发箱9.62版。
(2)实现功能:让一个 LED 闪烁。
(3)源代码讲解如下:
**********************************************/
#include "STC8H.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 = P6^0;
sbit ledonoff_dr = P4^0;
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 = 0; //LED 亮
delay_short(50000); //延时 50000 个空指令的时间
delay_short(50000); //延时 50000 个空指令的时间
delay_short(50000); //延时 50000 个空指令的时间
/* 注释六:
* delay_long(100)延时 50000 个空指令的时间,因为内嵌了一个 500 次的 for 循环
*/
led_dr = 1; //LED 灭
delay_long(1000); //延时 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() //初始化单片机
{
P4M0 = 0x01;
P4M1 = 0xFF; //P40设置为开漏输出,其他高
P6M0 = 0x01; //P60设置为开漏输出,其他口设置为准双向口输出
P6M1 = 0x01; //P6其他口设置为开漏输出,其他口会微亮
led_dr = 1; //LED 灭
ledonoff_dr = 0; //LED 控制开关打开
}
void initial_peripheral() //初始化外围
{
; //本例为空
}
//总结陈词:
// 鸿哥首次提出的“三区一线”理论概况了各种项目程序的基本分区。我后续的程序就按此分区编写。
//Delay()函数的长延时适用在上电初始化。
//Delay()函数的短延时适用在驱动时序的脉冲延时,此时的时间不能太长,本例中暂时没有列出这方面的例子,在
//后面的章节中会提到。
//在本例源代码中,在 led_flicker()闪烁应用程序里用到的两个延时 delay,它们的延时时间都太长了,在实战项目
//中肯定不能用这种延时,因为消耗的时间太长了,其它任务根本没有机会执行。那怎么办呢?我们应该如何改善?
//欲知详情,请听下回分解-----累计主循环次数使 LED 灯闪烁。
第二课:
/*******************************************
本学习程序来源于吴坚鸿老师《手把手教你单片机程序框架》,所有
权属于吴坚鸿老师,本人是用来学习,移植STC8H8K64U开发箱9.62版。
到吴坚鸿老师若有异议,我立即删除!
开场白:
上一节鸿哥提到 delay()延时函数消耗的时间太长了,
其它任务根本没有机会执行,我们该怎么改善?本节教
大家利用累计主循环次数的方法来解决这个问题。这一
节要教会大家两个知识点:
第一点:利用累计主循环次数的方法实现时间延时
第二点:switch 核心语句之初体验。 鸿哥所有的
实战项目都是基于 switch 语句实现多任务并行处理。
(1)硬件平台:STC8H8K64U开发箱9.62版。
(2)实现功能:让一个 LED 闪烁。
(3)源代码讲解如下:
**********************************************/
#include "stc8h.h"
/* 注释一:
* const_time_level 是统计循环次数的设定上限,数值越大,LED 延时的时间越久
*/
#define const_time_level 50000
void initial_myself();
void initial_peripheral();
void delay_long(unsigned int uiDelaylong);
void led_flicker();
sbit led_dr=P6^0;
sbit ledonoff_dr=P4^0;
/* 注释二:
* 吴坚鸿个人的命名风格:凡是 switch 语句里面的步骤变量后缀都是 Step.
* 前缀带 uc,ui,ul 分别表示此变量是 unsigned char,unsigned int,unsigned long.
*/
unsigned char ucLedStep=0; //步骤变量
unsigned intuiTimeCnt=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()任务,继续快速扫描其他的任务,
* 这样的程序结构就可以达到多任务并行处理的目的。
* 本程序基于STC8H8K64U开发箱9.62版。
*/
uiTimeCnt++; //累加循环次数,
if(uiTimeCnt>=const_time_level) //时间到
{
uiTimeCnt=0; //时间计数器清零
led_dr=0; //让 LED 亮
ucLedStep=1; //切换到下一个步骤
}
break;
case 1:
uiTimeCnt++; //累加循环次数,
if(uiTimeCnt>=const_time_level) //时间到
{
uiTimeCnt=0; //时间计数器清零
led_dr=1; //让 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() //第一区 初始化单片机
{
P4M0 = 0x01;
P4M1 = 0x01; //P40设置为开漏输出,其他为准双向口
P6M0 = 0x00; //P60口设置为开漏输出,其他为高阻输入,这样设置其他灯会微亮
P6M1 = 0x00; //P6设置为准双向口,
ledonoff_dr = 0; //LED总开关打开
P6 = 0XFF; //LED 灭
}
void initial_peripheral() //第二区 初始化外围
{
; //本例为空
}
//总结陈词:
//在实际项目中,用累计主循环次数实现时间延时是一个不错的选择。
//这种方法能胜任多任务处理的程序框架,但是它本身也有一个小小的
//不足。随着主函数里任务量的增加,我们为了保证延时时间的准确性,
//要不断修正设定上限 const_time_level 。我们该怎么解决这个问题
//呢?欲知详情,请听下回分解-----累计定时中断次数使 LED灯闪烁
lclbf 发表于 2024-1-3 10:32
第二课:
/*******************************************
本学习程序来源于吴坚鸿老师《手把手教你单片 ...
第三课:
/*******************************************
本学习程序来源于吴坚鸿老师《手把手教你单片机程序框架》,所有
权属于吴坚鸿老师,本人是用来学习,移植STC8H8K64U开发箱9.62版。
到吴坚鸿老师若有异议,我立即删除!
开场白:
上一节提到在累计主循环次数来实现计时,随着主函数里任务量的增加,
为了保证延时时间的准确性,要不断修正设定上限阀值 const_time_level 。
我们该怎么解决这个问题呢?本节教大家利用累计定时中断次数的方法来解
决这个问题。这一节要教会大家四个知识点:
第一点:利用累计定时中断次数的方法实现时间延时
第二点:展现鸿哥最完整的实战程序框架。在主函数循环里用 switch 语句
实现状态机的切换,在定时中断里累计中断次数,这两个的结合就是我
写代码最本质的框架思想。
第三点:提醒大家 C 语言中的 int ,long 变量是由几个字节构成的数据,
凡是在 main 函数和中断函数里有可能同时改变的变量,这个变量应
该在主函数中被更改之前,先关闭相应的中断,更改完了此变量,再
打开中断,否则会留下不宜察觉的漏洞。当然在大部分的项目中可以
不用这么操作,但是在一些要求非常高的项目中,有一些核心变量必须这么做。
第四点:定时中断的初始值该怎么设置。不用严格按公式来计算时间,一般取
个经验值是最大初始值减去 1000 就可以了。
具体内容,请看源代码讲解。
(1)硬件平台:STC8H8K64U开发箱9.62版。
(2)实现功能:让一个 LED 闪烁。
(3)源代码讲解如下:
**********************************************/
#include "stc8h.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 ledonoff_dr = P4^0;
sbit led_dr = P6^0;
unsigned char ucLedStep=0;//步骤变量
unsigned intuiTimeCnt=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 = 0; //让 LED 亮
ucLedStep = 1;//切换到下一个步骤
}
break;
case 1:
if(uiTimeCnt>=const_time_level) //时间到
{
ET0 = 0; //禁止定时中断
uiTimeCnt = 0; //时间计数器清零
ET0 = 1; //开启定时中断
led_dr = 1; //让 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;
P4M0 = 0x01;
P4M1 = 0x01; //P40设置为开漏输出,其他为准双向口
P6M0 = 0x00; //P6口设置为准双向口
P6M1 = 0x00;
ledonoff_dr = 0; //LED总开关打开
led_dr=1; //LED 灭
}
void initial_peripheral() //第二区 初始化外围
{
EA=1; //开总中断
ET0=1; //允许定时中断
TR0=1; //启动定时中断
}
//总结陈词:
//本节程序麻雀虽小五脏俱全。在本节中已经展示了最完整的实战程序框架。
//本节程序只有一个 LED 灯闪烁的单任务,如果要多增加一个任务来并行处理,该怎么办?
//欲知详情,请听下回分解-----蜂鸣器的驱动程序。
lclbf 发表于 2024-1-3 10:33
第三课:
/*******************************************
本学习程序来源于吴坚鸿老师《手把手教你单片机 ...
第四课:
/*******************************************
本学习程序来源于吴坚鸿老师《手把手教你单片机程序框架》,所有
权属于吴坚鸿老师,本人是用来学习,移植STC8H8K64U开发箱9.62版。
到吴坚鸿老师若有异议,我立即删除!
开场白:
上一节讲了利用累计定时中断次数实现 LED 灯闪烁,这个例子同时也第一
次展示了我最完整的实战程序框架:用 switch 语句实现状态机,外加定时
中断。这个框架看似简单,实际上就是那么简单。我做的所有开发项目都是
基于这个简单框架,但是非常好用。上一节只有一个单任务的 LED 灯在闪烁,
这节开始,我们多增加一个蜂鸣器报警的任务,要教会大家四个知识点:
第一点:蜂鸣器的驱动程序框架编写。
第二点:多任务处理的程序框架。
第三点:如何控制蜂鸣器声音的长叫和短叫。
第四点:如何知道 1 秒钟需要多少个定时中断,也就是如何按比例修正时间精度。
具体内容,请看源代码讲解。
(1)硬件平台:STC8H8K64U开发箱9.62版。
(2)实现功能:同时跑两个任务,第一个任务让一个 LED 灯 1 秒钟闪烁一次。
第二个任务让蜂鸣器在前面 3 秒发生一次短叫报警,在后面 6 秒发生一
次长叫报警,反复循环。
(3)源代码讲解如下:
**********************************************/
#include "STC8H.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_1s444 //1 秒钟的时间需要的定时中断次数
#define const_time_3s1332 //3 秒钟的时间需要的定时中断次数
#define const_time_6s2664 //6 秒钟的时间需要的定时中断次数
#define const_voice_short 40 //蜂鸣器短叫的持续时间
#define const_voice_long200 //蜂鸣器长叫的持续时间
void initial_myself();
void initial_peripheral();
void delay_long(unsigned int uiDelaylong);
void led_flicker();
void alarm_run();
void T0_time(); //定时中断函数
sbit beep_dr = P5^4; //蜂鸣器的驱动 IO 口,低电平开
sbit ledonoff_dr = P4^0; //LED 灯总开关 IO 口,低电平开
sbit led_dr = P6^0; //LED 灯的驱动 IO 口,低电平开
unsigned char ucLedStep=0; //LED 灯的步骤变量
unsigned intuiTimeLedCnt=0; //LED 灯统计定时中断次数的延时计数器
unsigned char ucAlarmStep=0; //报警的步骤变量
unsigned intuiTimeAlarmCnt=0; //报警统计定时中断次数的延时计数器
unsigned intuiVoiceCnt=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=0; //让 LED 亮
ucLedStep=1; //切换到下一个步骤
}
break;
case 1:
if(uiTimeLedCnt>=const_time_05s) //时间到
{
uiTimeLedCnt=0; //时间计数器清零
led_dr=1; //让 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() //第一区 初始化单片机
{
P4M0 = 0X00; //P4口设置为准双向口
P4M1 = 0X00;
P5M0 = 0X00; //P5口设置为准双向口
P5M1 = 0X00;
P6M0 = 0X00; //P6口设置为准双向口
P6M1 = 0X00;
beep_dr=1; //用 PNP 三极管控制蜂鸣器,输出高电平时不叫。
ledonoff_dr=0; //LED总开关打开
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; //启动定时中断
}
//总结陈词:
//本节程序已经展示了一个多任务处理的基本思路,假如要实现一个独立按键检测,
//能不能也按照这种思路来处理呢?欲知详情,请听下回分解-----在主函数中利用
//累计主循环次数来实现独立按键的检测。 lclbf 发表于 2024-1-3 10:35
第四课:
/*******************************************
第五课:
/*******************************************
本学习程序来源于吴坚鸿老师《手把手教你单片机程序框架》,所有
权属于吴坚鸿老师,本人是用来学习,移植STC8H8K64U开发箱9.62版。
到吴坚鸿老师若有异议,我立即删除!
开场白:
上一节讲了多任务中蜂鸣器驱动程序的框架,这节继续利用多任务处理的
方式,在主函数中利用累计主循环次数来实现独立按键的检测。要教会大
家四个知识点:
第一点:独立按键的驱动程序框架。
第二点:用累计主循环次数来实现去抖动的延时。
第三点:灵活运用防止按键不松手后一直触发的按键自锁标志。
第四点:在按键去抖动延时计时中,添加一个抗干扰的软件监控判断。一
旦发现瞬间杂波干扰,马上把延时计数器清零。这种方法是我在复杂的工
控项目中总结出来的。以后凡是用到开关感应器的地方,都可以用类似的
方法实现软件上的抗干扰处理。
具体内容,请看源代码讲解。
(1)硬件平台:STC8H8K64U开发箱9.62版。用矩阵键盘中的 SW32 和 SW36
号键作为独立按键,记得把输出线 P0.0一直输出低电平,模拟独立按
键的触发地 GND。
(2)实现功能:有两个独立按键,每按一个独立按键,蜂鸣器发出“滴”的一声后就停。
(3)源代码讲解如下:
**********************************************/
#include "STC8H.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(); //定时0中断函数
void key_service(); //按键服务的应用程序
void key_scan(); //按键扫描函数
sbit key_sr1=P0^6; //STC8H8K64U开发箱9.62版SW32键
sbit key_sr2=P0^7; //STC8H8K64U开发箱9.62版SW36键
sbit key_gnd_dr=P0^0; //模拟独立按键的地 GND,因此必须一直输出低电平
sbit beep_dr=P5^4; //蜂鸣器的驱动 IO 口
unsigned char ucKeySec=0; //被触发的按键编号
unsigned intuiKeyTimeCnt1=0; //按键SW32去抖动延时计数器
unsigned char ucKeyLock1=0; //按键SW32触发后自锁的变量标志
unsigned intuiKeyTimeCnt2=0; //按键SW36去抖动延时计数器
unsigned char ucKeyLock2=0; //按键SW36触发后自锁的变量标志
unsigned intuiVoiceCnt=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; //触发SW32号键
}
}
if(key_sr2==1) //IO 是高电平,说明按键没有被按下,这时要及时清零一些标志位
{
ucKeyLock2=0; //按键自锁标志清零
uiKeyTimeCnt2=0; //按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
}
else if(ucKeyLock2==0) //有按键按下,且是第一次被按下
{
++uiKeyTimeCnt2; //延时计数器
if(uiKeyTimeCnt2>const_key_time2)
{
uiKeyTimeCnt2=0; //自锁按键置位,避免一直触发
ucKeyLock2=1;
ucKeySec=2; //触发 2 号键
}
}
}
void key_service() //第三区 按键服务的应用程序
{
switch(ucKeySec) //按键服务状态切换
{
case 1: // 1号键STC8H8K64U开发箱9.62版SW32键
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
break;
case 2: // 2号键STC8H8K64U开发箱9.62版SW36键
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 输出低电平。
* STC8H8K64U开发箱9.62版SW32键SW36键两个按键就是本程序中用到的两个独立按键。
*/
P0M0 = 0XFE;//P06,P07设置为开漏输出,P00设置为准双向口,
P0M1 = 0XC0;//其他设置为高阻输入(P06,P07有外接10K上拉)
P5M0 = 0X10;//P54开漏输出其他高阻输入(P54外接10K上拉)
P5M1 = 0XFF;
key_gnd_dr=0; //模拟独立按键的地 GND,因此必须一直输出低电平
beep_dr=1; //用 PNP 三极管控制蜂鸣器,输出高电平时不叫。
TMOD=0x01; //设置定时器 0 为工作方式 1
TH0=0xf8; //重装初始值(65535-2000)=63535=0xf82f12Mhz定时为20ms
TL0=0x2f;
}
void initial_peripheral() //第二区 初始化外围
{
EA=1; //开总中断
ET0=1; //允许定时中断
TR0=1; //启动定时中断
}
//总结陈词:
//本节程序已经展示了在主函数中,利用累计主循环次数来实现独立按键的检测。这种方法我经常在实战用应
//用,但是它也有一个小小的不足,随着在主函数循环中任务量的增加,为了保证去抖动延时的时间一致性,要适
//当调整一下去抖动的阀值 const_key_time1。如何解决这个问题呢?欲知详情,请听下回分解-----在主函数中利用
//累计定时中断的次数来实现独立按键的检测。
lclbf 发表于 2024-1-3 14:13
第五课:
/*******************************************
第六课:
/*******************************************
本学习程序来源于吴坚鸿老师《手把手教你单片机程序框架》,所有
权属于吴坚鸿老师,本人是用来学习,移植STC8H8K64U开发箱9.62版。
到吴坚鸿老师若有异议,我立即删除!
开场白:
上一节讲了多任务中蜂鸣器驱动程序的框架,这节继续利用多任务处理的
方式,在主函数中利用累计主循环次数来实现独立按键的检测。要教会大
家四个知识点:
第一点:独立按键的驱动程序框架。
第二点:用累计主循环次数来实现去抖动的延时。
第三点:灵活运用防止按键不松手后一直触发的按键自锁标志。
第四点:在按键去抖动延时计时中,添加一个抗干扰的软件监控判断。一
旦发现瞬间杂波干扰,马上把延时计数器清零。这种方法是我在复杂的工
控项目中总结出来的。以后凡是用到开关感应器的地方,都可以用类似的
方法实现软件上的抗干扰处理。
具体内容,请看源代码讲解。
(1)硬件平台:STC8H8K64U开发箱9.62版。用矩阵键盘中的 SW32 和 SW36
号键作为独立按键,记得把输出线 P0.0一直输出低电平,模拟独立按
键的触发地 GND。
(2)实现功能:有两个独立按键,每按一个独立按键,蜂鸣器发出“滴”的一声后就停。
(3)源代码讲解如下:
**********************************************/
#include "STC8H.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(); //定时0中断函数
void key_service(); //按键服务的应用程序
void key_scan(); //按键扫描函数
sbit key_sr1 = P0^6; //STC8H8K64U开发箱9.62版SW32键
sbit key_sr2 = P0^7; //STC8H8K64U开发箱9.62版SW36键
sbit key_gnd_dr = P0^0; //模拟独立按键的地 GND,因此必须一直输出低电平
sbit beep_dr = P5^4; //蜂鸣器的驱动 IO 口
sbit LEDonoff_dr = P4^0; //LED控制开关
sbit LED_dr = P6^0; //LED灯
unsigned char ucKeySec=0; //被触发的按键编号
unsigned intuiKeyTimeCnt1=0; //按键SW32去抖动延时计数器
unsigned char ucKeyLock1=0; //按键SW32触发后自锁的变量标志
unsigned intuiKeyTimeCnt2=0; //按键SW36去抖动延时计数器
unsigned char ucKeyLock2=0; //按键SW36触发后自锁的变量标志
unsigned intuiVoiceCnt=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; //触发SW32号键
}
}
if(key_sr2==1) //IO 是高电平,说明按键没有被按下,这时要及时清零一些标志位
{
ucKeyLock2=0; //按键自锁标志清零
uiKeyTimeCnt2=0; //按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
}
else if(ucKeyLock2==0) //有按键按下,且是第一次被按下
{
++uiKeyTimeCnt2; //延时计数器
if(uiKeyTimeCnt2>const_key_time2)
{
uiKeyTimeCnt2=0; //自锁按键置位,避免一直触发
ucKeyLock2=1;
ucKeySec=2; //触发 2 号键
}
}
}
void key_service() //第三区 按键服务的应用程序
{
switch(ucKeySec) //按键服务状态切换
{
case 1: // 1号键STC8H8K64U开发箱9.62版SW32键
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
LED_dr = 0; //点亮P60口的LED
ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
break;
case 2: // 2号键STC8H8K64U开发箱9.62版SW36键
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
LED_dr = 1; //关闭P60口的LED
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 输出低电平。
* STC8H8K64U开发箱9.62版SW32键SW36键两个按键就是本程序中用到的两个独立按键。
*/
P0M0 = 0XFE; //P06,P07设置为开漏输出,P00设置为准双向口,
P0M1 = 0XC0; //其他设置为高阻输入(P06,P07有外接10K上拉)
P5M0 = 0X10; //P54开漏输出其他高阻输入(P54外接10K上拉)
P5M1 = 0XFF;
P4M0 = 0X00; //P40开准双向口输出其他高阻输入
P4M1 = 0XFE;
P6M0 = 0X00; //P60开漏输出其他高阻输入(P6口外接10K+33R上拉)
P6M1 = 0X00; //P6口要设置为准双向口或者推挽输出,不然会出现不点亮的点灯微亮
key_gnd_dr=0; //模拟独立按键的地 GND,因此必须一直输出低电平
beep_dr=1; //用 PNP 三极管控制蜂鸣器,输出高电平时不叫。
LEDonoff_dr = 0; //打开LED开关
TMOD=0x01; //设置定时器 0 为工作方式 1
TH0=0xf8; //重装初始值(65535-2000)=63535=0xf82f12Mhz定时为20ms
TL0=0x2f;
}
void initial_peripheral() //第二区 初始化外围
{
EA=1; //开总中断
ET0=1; //允许定时中断
TR0=1; //启动定时中断
}
//总结陈词:
//本节程序已经展示了在主函数中,利用累计主循环次数来实现独立按键的检测。这种方法我经常在实战用应
//用,但是它也有一个小小的不足,随着在主函数循环中任务量的增加,为了保证去抖动延时的时间一致性,要适
//当调整一下去抖动的阀值 const_key_time1。如何解决这个问题呢?欲知详情,请听下回分解-----在主函数中利用
//累计定时中断的次数来实现独立按键的检测。
lclbf 发表于 2024-1-3 14:14
第六课:
/*******************************************
第七课:
/*******************************************
本学习程序来源于吴坚鸿老师《手把手教你单片机程序框架》,所有
权属于吴坚鸿老师,本人是用来学习,移植STC8H8K64U开发箱9.62版。
到吴坚鸿老师若有异议,我立即删除!
开场白:
上一节讲了多任务中蜂鸣器驱动程序的框架,这节继续利用多任务处理的
方式,在主函数中利用累计主循环次数来实现独立按键的检测。要教会大
家四个知识点:
第一点:独立按键的驱动程序框架。
第二点:用累计主循环次数来实现去抖动的延时。
第三点:灵活运用防止按键不松手后一直触发的按键自锁标志。
第四点:在按键去抖动延时计时中,添加一个抗干扰的软件监控判断。一
旦发现瞬间杂波干扰,马上把延时计数器清零。这种方法是我在复杂的工
控项目中总结出来的。以后凡是用到开关感应器的地方,都可以用类似的
方法实现软件上的抗干扰处理。
具体内容,请看源代码讲解。
(1)硬件平台:STC8H8K64U开发箱9.62版。用矩阵键盘中的 SW32 和 SW36
号键作为独立按键,记得把输出线 P0.0一直输出低电平,模拟独立按
键的触发地 GND。
(2)实现功能:有两个独立按键,每按一个独立按键,蜂鸣器发出“滴”的一声后就停。
(3)源代码讲解如下:
**********************************************/
#include "STC15.H"
/* 注释一:
* 调整抖动时间阀值的大小,可以更改按键的触发灵敏度。
* 去抖动的时间本质上等于累计主循环次数的时间。
*/
#define const_key_time1 500 //按键去抖动延时的时间
#define const_key_time2 500 //按键去抖动延时的时间
#define u8 unsigned char
#define u16 unsigned int
void initial_myself();
void initial_peripheral();
void delay_long(unsigned int uiDelaylong);
void T0_time(); //定时0中断函数
void key_service(); //按键服务的应用程序
void key_scan(); //按键扫描函数
void delay(u16 x);
void huxideng();
//sbit key_sr1 = P0^6; //STC8H8K64U开发箱9.62版SW32键
//sbit key_sr2 = P0^7; //STC8H8K64U开发箱9.62版SW36键
//sbit key_gnd_dr = P0^0; //模拟独立按键的地 GND,因此必须一直输出低电平
//sbit LEDonoff_dr = P4^0; //LED控制开关
//sbit LED_dr = P6^0; //LED灯
sbit key_sr1 = P1^0;//红外接口标志
sbit LEDB_dr = P2^2;
sbit LEDR_dr = P2^3;
sbit LEDG_dr = P2^4;
bit flag= 1; //开关灯标志 0开灯1上电默认关
unsigned char ID = 0;
u16 PWM_Low=0,Clock=1200; //改变呼吸效果
unsigned char ucKeySec=0; //被触发的按键编号
unsigned intuiKeyTimeCnt1=0; //按键SW32去抖动延时计数器
unsigned char ucKeyLock1=0; //按键SW32触发后自锁的变量标志
//unsigned intuiKeyTimeCnt2=0; //按键SW36去抖动延时计数器
//unsigned char ucKeyLock2=0; //按键SW36触发后自锁的变量标志
unsigned intuiVoiceCnt=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; //触发SW32号键
}
}
// if(key_sr2==1) //IO 是高电平,说明按键没有被按下,这时要及时清零一些标志位
// {
// ucKeyLock2=0; //按键自锁标志清零
// uiKeyTimeCnt2=0; //按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。
// }
// else if(ucKeyLock2==0) //有按键按下,且是第一次被按下
// {
// ++uiKeyTimeCnt2; //延时计数器
// if(uiKeyTimeCnt2>const_key_time2)
// {
// uiKeyTimeCnt2=0; //自锁按键置位,避免一直触发
// ucKeyLock2=1;
// ucKeySec=2; //触发 2 号键
// }
// }
}
void key_service() //第三区 按键服务的应用程序
{
switch(ucKeySec) //按键服务状态切换
{
case 1: // 1号键STC8H8K64U开发箱9.62版SW32键
// uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
// LED_dr = 0; //点亮P60口的LED
huxideng();
ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
break;
// case 2: // 2号键STC8H8K64U开发箱9.62版SW36键
// uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
// LED_dr = 1; //关闭P60口的LED
// 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 输出低电平。
* STC8H8K64U开发箱9.62版SW32键SW36键两个按键就是本程序中用到的两个独立按键。
*/
// P0M0 = 0XFE; //P06,P07设置为开漏输出,P00设置为准双向口,
// P0M1 = 0XC0; //其他设置为高阻输入(P06,P07有外接10K上拉)
//
// P5M0 = 0X10; //P54开漏输出其他高阻输入(P54外接10K上拉)
// P5M1 = 0XFF;
//
// P4M0 = 0X00; //P40开准双向口输出其他高阻输入
// P4M1 = 0XFE;
//
// P6M0 = 0X00; //P60开漏输出其他高阻输入(P6口外接10K+33R上拉)
// P6M1 = 0X00; //P6口要设置为准双向口或者推挽输出,不然会出现不点亮的点灯微亮
//
// key_gnd_dr=0; //模拟独立按键的地 GND,因此必须一直输出低电平
// beep_dr=1; //用 PNP 三极管控制蜂鸣器,输出高电平时不叫。
// LEDonoff_dr = 0; //打开LED开关
P2M0 = 0XFF;//P2口为准双向口
P2M1 = 0X00;
P1M0 = 0X00;//P1口为准高阻输入
P1M1 = 0XFF;
LEDR_dr = 0;
LEDG_dr = 0;
LEDB_dr = 0;
TMOD=0x01; //设置定时器 0 为工作方式 1
TH0=0xf8; //重装初始值(65535-2000)=63535=0xf82f12Mhz定时为20ms
TL0=0x2f;
}
void delay(u16 x) //延迟函数
{
u16 i;
for(i=0;i<x;i++);
}
void initial_peripheral() //第二区 初始化外围
{
EA=1; //开总中断
ET0=1; //允许定时中断
TR0=1; //启动定时中断
}
void huxideng()
{
while(1)
{
if(key_sr1 == 1)
break;
for(PWM_Low=0;PWM_Low<Clock;PWM_Low++ )//红逐渐变亮
{
if(key_sr1 == 1)
break;
LEDR_dr=1;LEDG_dr=0;LEDB_dr=0;
delay(PWM_Low);
LEDR_dr=0;LEDG_dr=0;LEDB_dr=0;
delay(Clock-PWM_Low);
}
for(PWM_Low=Clock;PWM_Low>0;PWM_Low--) //红逐渐变暗
{
if(key_sr1 == 1)
break;
LEDR_dr=1;LEDG_dr=0;LEDB_dr=0;
delay(PWM_Low);
LEDR_dr=0;LEDG_dr=0;LEDB_dr=0;
delay(Clock-PWM_Low);
}
for(PWM_Low=0;PWM_Low<Clock;PWM_Low++ ) //黄逐渐变亮
{
if(key_sr1 == 1)
break;
LEDR_dr=1;LEDG_dr=1;LEDB_dr=0;
delay(PWM_Low);
LEDR_dr=0;LEDG_dr=0;LEDB_dr=0;
delay(Clock-PWM_Low);
}
for(PWM_Low=Clock;PWM_Low>0;PWM_Low--) //黄逐渐变暗
{
if(key_sr1 == 1)
break;
LEDR_dr=1;LEDG_dr=1;LEDB_dr=0;
delay(PWM_Low);
LEDR_dr=0;LEDG_dr=0;LEDB_dr=0;
delay(Clock-PWM_Low);
}
for(PWM_Low=0;PWM_Low<Clock;PWM_Low++ )//绿逐渐变亮
{
if(key_sr1 == 1)
break;
LEDR_dr=0;LEDG_dr=1;LEDB_dr=0;
delay(PWM_Low);
LEDR_dr=0;LEDG_dr=0;LEDB_dr=0;
delay(Clock-PWM_Low);
}
for(PWM_Low=Clock;PWM_Low>0;PWM_Low--) //绿逐渐变暗
{
if(key_sr1 == 1)
break;
LEDR_dr=0;LEDG_dr=1;LEDB_dr=0;
delay(PWM_Low);
LEDR_dr=0;LEDG_dr=0;LEDB_dr=0;
delay(Clock-PWM_Low);
}
for(PWM_Low=0;PWM_Low<Clock;PWM_Low++ )//青逐渐变亮
{
if(key_sr1 == 1)
break;
LEDR_dr=0;LEDG_dr=1;LEDB_dr=1;
delay(PWM_Low);
LEDR_dr=0;LEDG_dr=0;LEDB_dr=0;
delay(Clock-PWM_Low);
}
for(PWM_Low=Clock;PWM_Low>0;PWM_Low--) //青逐渐变暗
{
if(key_sr1 == 1)
break;
LEDR_dr=0;LEDG_dr=1;LEDB_dr=1;
delay(PWM_Low);
LEDR_dr=0;LEDG_dr=0;LEDB_dr=0;
delay(Clock-PWM_Low);
}
for(PWM_Low=0;PWM_Low<Clock;PWM_Low++ )//蓝逐渐变亮
{
if(key_sr1 == 1)
break;
LEDR_dr=0;LEDG_dr=0;LEDB_dr=1;
delay(PWM_Low);
LEDR_dr=0;LEDG_dr=0;LEDB_dr=0;
delay(Clock-PWM_Low);
}
for(PWM_Low=Clock;PWM_Low>0;PWM_Low--) //蓝逐渐变暗
{
if(key_sr1 == 1)
break;
LEDR_dr=0;LEDG_dr=0;LEDB_dr=1;
delay(PWM_Low);
LEDR_dr=0;LEDG_dr=0;LEDB_dr=0;
delay(Clock-PWM_Low);
}
for(PWM_Low=0;PWM_Low<Clock;PWM_Low++ )//紫逐渐变亮
{
if(key_sr1 == 1)
break;
LEDR_dr=1;LEDG_dr=0;LEDB_dr=1;
delay(PWM_Low);
LEDR_dr=0;LEDG_dr=0;LEDB_dr=0;
delay(Clock-PWM_Low);
}
for(PWM_Low=Clock;PWM_Low>0;PWM_Low--) //紫逐渐变暗
{
if(key_sr1 == 1)
break;
LEDR_dr=1;LEDG_dr=0;LEDB_dr=1;
delay(PWM_Low);
LEDR_dr=0;LEDG_dr=0;LEDB_dr=0;
delay(Clock-PWM_Low);
}
for(PWM_Low=0;PWM_Low<Clock;PWM_Low++ )//白逐渐变亮
{
if(key_sr1 == 1)
break;
LEDR_dr=1;LEDG_dr=1;LEDB_dr=1;
delay(PWM_Low);
LEDR_dr=0;LEDG_dr=0;LEDB_dr=0;
delay(Clock-PWM_Low);
}
for(PWM_Low=Clock;PWM_Low>0;PWM_Low--) //白逐渐变暗
{
if(key_sr1 == 1)
break;
LEDR_dr=1;LEDG_dr=1;LEDB_dr=1;
delay(PWM_Low);
LEDR_dr=0;LEDG_dr=0;LEDB_dr=0;
delay(Clock-PWM_Low);
}
}
}
//总结陈词:
//本节程序已经展示了在主函数中,利用累计主循环次数来实现独立按键的检测。这种方法我经常在实战用应
//用,但是它也有一个小小的不足,随着在主函数循环中任务量的增加,为了保证去抖动延时的时间一致性,要适
//当调整一下去抖动的阀值 const_key_time1。如何解决这个问题呢?欲知详情,请听下回分解-----在主函数中利用
//累计定时中断的次数来实现独立按键的检测。
lclbf 发表于 2024-1-3 14:15
第七课:
/*******************************************
第八课:
/*******************************************
本学习程序来源于吴坚鸿老师《手把手教你单片机程序框架》,所有
权属于吴坚鸿老师,本人是用来学习,移植STC8H8K64U开发箱9.62版。
到吴坚鸿老师若有异议,我立即删除!
开场白:
上一节讲了在主函数中利用累计定时中断的次数来实现独立按键的检测,但是如果在某些项目中,需要在主
函数里间歇性地执行一些一气呵成的耗时任务,当主函数正在处理一气呵成的耗时任务时(前提是没有关闭定时
器中断),这个时候如果有按键按下来,就有可能没有及时被响应到而遗漏了。在定时中断函数里处理独立按键
的扫描程序,可以避免这个问题。要教会大家一个知识点:如何在上一节的基础上,略作修改,就可以在定时中
断函数里处理独立按键的扫描程序。
具体内容,请看源代码讲解。
(1)硬件平台:STC8H8K64U开发箱9.62版。用矩阵键盘中的SW32和SW36号键作为独立按键,记得把输出线 P0.0
一直输出低电平,模拟独立按键的触发地 GND。
(2)实现功能:有两个独立按键,每按一个独立按键,蜂鸣器发出“滴”的一声后就停。SW32开灯,SW36关灯
(3)源代码讲解如下:
**********************************************/
#include "STC8H.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^6; //STC8H8K64U开发箱9.62版 SW32键
sbit key_sr2=P0^7; //STC8H8K64U开发箱9.62版 SW36键
sbit key_gnd_dr=P0^0; //模拟独立按键的地 GND,因此必须一直输出低电平
sbit beep_dr=P5^4; //蜂鸣器的驱动 IO 口
sbit LEDonoff_dr=P4^0;//LED灯开关
sbit LED_dr=P6^0; //LED灯
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 号键 STC8H8K64U开发箱9.62版 SW32键
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
LED_dr = 0; //点亮LED
ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
break;
case 2: // 2 号键 STC8H8K64U开发箱9.62版 SW36键
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
LED_dr = 1; //关闭LED
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 输出低电平。
* STC8H8K64U开发箱9.62版 SW32键 SW36键两个按键就是本程序中用到的两个独立按键。
*/
P0M0 = 0X00; //P0口设置为准双向口输出
P0M1 = 0X00;
P4M0 = 0X00; //P0口设置为准双向口输出
P4M1 = 0X00;
P5M0 = 0X10; //P54口设置为开漏输出(外接有10K上拉电阻),其余口设置为高阻输入
P5M1 = 0XFF;
P6M0 = 0X00; //P0口设置为准双向口输出
P6M1 = 0X00;
key_gnd_dr=0; //模拟独立按键的地 GND,因此必须一直输出低电平
beep_dr=1; //用 PNP 三极管控制蜂鸣器,输出高电平时不叫。
LEDonoff_dr=0;//打开LED控制开关
TMOD=0x01; //设置定时器 0 为工作方式 1
TH0=0xf8; //重装初始值(65535-2000)=63535=0xf82f
TL0=0x2f;
}
void initial_peripheral() //第二区 初始化外围
{
EA=1; //开总中断
ET0=1; //允许定时中断
TR0=1; //启动定时中断
}
//总结陈词:
// 本节程序已经展示了在定时中断函数里执行独立按键的扫描程序。这节和前面两节所讲的扫描方式,我都在
//项目上用过,具体跟项目的侧重点不同来选择不同的方式,我本人用得最多的就是当前这种方式。假如要独立按
//键实现类似鼠标的双击功能,我们改怎么写程序?欲知详情,请听下回分解-----独立按键的双击按键触发。
lclbf 发表于 2024-1-4 08:09
第八课:
/*******************************************
第九课:
/*******************************************
本学习程序来源于吴坚鸿老师《手把手教你单片机程序框架》,所有
权属于吴坚鸿老师,本人是用来学习,移植STC8H8K64U开发箱9.62版。
到吴坚鸿老师若有异议,我立即删除!
开场白:
上一节讲了在定时中断函数里处理独立按键的扫描程序,这种结构的程序我用在了很多项目上。这一节教大
家如何实现按键双击触发的功能,这种功能类似鼠标的双击。要教会大家一个知识点:如何在上一节的基础上,
略作修改,就可以实现按键的双击功能。
具体内容,请看源代码讲解。
(1)硬件平台:STC8H8K64U开发箱9.62版。用矩阵键盘中的SW32和SW36号键作为独立按键,记得把输出线 P0.0
一直输出低电平,模拟独立按键的触发地 GND。
(2)实现功能:有两个独立按键,每双击一个独立按键,蜂鸣器发出“滴”的一声后就停。
(3)源代码讲解如下:
**********************************************/
#include "STC8H.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^6; //STC8H8K64U开发箱9.62版 SW32键
sbit key_sr2=P0^7; //STC8H8K64U开发箱9.62版 SW36键
sbit key_gnd_dr=P0^0; //模拟独立按键的地 GND,因此必须一直输出低电平
sbit beep_dr=P5^4; //蜂鸣器的驱动 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++; //统计按键次数加1
if(ucKeyTouchCnt1>1) //连续被按了两次以上
{
ucKeyTouchCnt1=0; //统计按键次数清零
ucKeySec=1; //触发 1 号键
}
}
}
if(key_sr2==1) //IO 是高电平,说明按键没有被按下,这时要及时清零一些标志位
{
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++; //统计按键次数加1
if(ucKeyTouchCnt2>1) //连续被按了两次以上
{
ucKeyTouchCnt2=0; //统计按键次数清零
ucKeySec=2; //触发 2 号键
}
}
}
}
void key_service() //第三区 按键服务的应用程序
{
switch(ucKeySec) //按键服务状态切换
{
case 1: // 1 号键 双击 STC8H8K64U开发箱9.62版 SW32键
uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发
break;
case 2: // 2 号键 双击 对应STC8H8K64U开发箱9.62版 SW32键
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 输出低电平。
* STC8H8K64U开发箱9.62版 SW32键SW36键 两个按键就是本程序中用到的两个独立按键。
*/
P0M0 = 0X00; //P0口设置为准双向口输出
P0M1 = 0X00;
P5M0 = 0X10; //P54口设置为开漏输出(外接有10K上拉电阻),其余口设置为高阻输入
P5M1 = 0XFF;
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; //启动定时中断
}
//总结陈词:
// 假如要两个独立按键实现组合按键的功能,我们该怎么写程序?欲知详情,请听下回分解-----独立按键的组
//合按键触发。 第十课:
/*******************************************
本学习程序来源于吴坚鸿老师《手把手教你单片机程序框架》,所有
权属于吴坚鸿老师,本人是用来学习,移植STC8H8K64U开发箱9.62版。
到吴坚鸿老师若有异议,我立即删除!
开场白:
上一节讲了按键双击触发功能的程序,这一节讲类似电脑键盘组合按键触发的功能,要教会大家一个知识点:
如何在上一节的基础上,略作修改,就可以实现两个独立按键的组合按键触发功能。
具体内容,请看源代码讲解。
(1)硬件平台:STC8H8K64U开发箱9.62版。用矩阵键盘中的SW32和SW36号键作为独立按键,记得把输出线 P0.0
一直输出低电平,模拟独立按键的触发地 GND。
(2)实现功能:有两个独立按键,当把两个独立按键都按下后,蜂鸣器发出“滴”的一声后就停。直到松开任一
个按键后,才能重新进行下一次的组合按键触发。
(3)源代码讲解如下:
**********************************************/
#include "STC8H.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^6; //STC8H8K64U开发箱9.62版 SW32键
sbit key_sr2=P0^7; //STC8H8K64U开发箱9.62版 SW36键
sbit key_gnd_dr=P0^0; //模拟独立按键的地 GND,因此必须一直输出低电平
sbit beep_dr=P5^4; //蜂鸣器的驱动 IO 口
unsigned char ucKeySec=0; //被触发的按键编号
unsigned intuiKeyTimeCnt12=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 号键 组合按键 STC8H8K64U开发箱9.62版 SW32键SW36键
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 输出低电平。
* STC8H8K64U开发箱9.62版 SW32键SW36键两个按键就是本程序中用到的两个独立按键。
*/
P0M0 = 0X00; //P0口设置为准双向口
P0M1 = 0X00;
P5M0 = 0X10; //P54设置为开漏输出,其他为高阻输入
P5M1 = 0XFF;
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; //启动定时中断
}
//总结陈词:
//以前寻呼机流行的时候,寻呼机往往只有一个设置按键,它要求用一个按键来设置不同的参数,这个时候就
//要用到同一个按键来实现短按和长按的区别触发功能。要现实这种功能,我们该怎么写程序?欲知详情,请听下
//回分解-----同一个按键短按与长按的区别触发