目录
1.前言:
对于任何一次的省赛或国赛题,我们都要依次构建出一个大模板出来,这有利于后面程序的书写和错误的查找,然后我们相应地来做一道基于AT89C52单片机模拟题,具体完整操作和题目在主页资源共享中查看:
- 构建模版操作步骤.pdf
- 第二届 创意智造·奠基未来 单片机设计与开发大赛 模拟题(2).pdf
注:不同的单片机类型其所创建的Driver的底层函数不同。
B站配套视频:【【蓝桥杯-单片机】零基础入门省国赛冲刺培训】https://www.bilibili.com/video/BV1TR4y1k7iz?p=2&vd_source=2abf575de11fcb91c755ee01bdf65bd3
Key.c
//编写底层函数
#include "Key.h" //每一个.c文件需要引用自身的.h文件
unsigned char Key_Read()
{
unsigned char temp=0;
if(P3_4==0) temp=1;
if(P3_5==0) temp=2;
if(P3_6==0) temp=3;
if(P3_7==0) temp=4;
return temp;
}
Key.h
#include <REGX52.H> //引用.c引脚文件
unsigned char Key_Read();
Seg.c
#include "Seg.h"
unsigned char Seg_Dula[] = {0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x00};
unsigned char Seg_Wela[] = {0xfe,0xfd,0xfb,0xf7,0xef,0xdf};
void Seg_Disp(unsigned char wela,dula)
{
P0 = 0x00;
P2_6 = 1;
P2_6 = 0;
P0 = Seg_Wela[wela];
P2_7 = 1;
P2_7 = 0;
P0 = Seg_Dula[dula];
P2_6 = 1;
P2_6 = 0;
}
seg.h
#include <REGX52.H>
void Seg_Disp(unsigned char dula,wela);
注意:在Key.c底层代码中,我们需要值得注意的是按键的引脚是接VCC或者GRD,若错误则会造成按键无法读取键码值造成按键失灵;
我就不一一去介绍其不同底层函数的含义了,完整代码如下:
/* 头文件声明区 */
#include <REGX52.H>//单片机寄存器专用头文件
#include "Key.h"//按键底层驱动专用头文件
#include "Seg.h"//数码管底层驱动专用头文件
/* 变量声明区 */
unsigned char Key_Val,Key_Down,Key_Old;//按键专用变量
unsigned char Key_Slow_Down;//按键减速专用变量 10ms unsigned char范围0—255;
unsigned char Seg_Buf[6] = {1,2,3,4,5,6,};//数码管显示数据存放数组
unsigned char Seg_Pos;//数码管扫描专用变量
unsigned int Seg_Slow_Down;//数码管减速专用变量 500ms
/* 键盘处理函数 */
void Key_Proc()
{
if(Key_Slow_Down) return;
Key_Slow_Down = 1;//键盘减速程序
Key_Val = Key_Read();//实时读取键码值
Key_Down = Key_Val & (Key_Old ^ Key_Val);//捕捉按键下降沿
Key_Old = Key_Val;//辅助扫描变量
switch(Key_Down)
{
}
}
/* 信息处理函数 */
void Seg_Proc()
{
}
/* 其他显示函数 */
void Led_Proc()
{
}
/* 定时器0中断初始化函数 */
void Timer0Init(void) //1毫秒@12.000MHz
{
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x01; //设置定时器模式
TL0 = 0x18; //设置定时初始值
TH0 = 0xFC; //设置定时初始值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
ET0 = 1; //定时器0中断打开
EA = 1; //总中断打开
}
/* 定时器0中断服务函数 */
void Timer0Server() interrupt 1
{
TL0 = 0x18; //设置定时初始值
TH0 = 0xFC; //设置定时初始值
if(++Key_Slow_Down == 10) Key_Slow_Down = 0;//键盘减速专用
if(++Seg_Slow_Down == 500) Seg_Slow_Down = 0;//数码管减速专用
if(++Seg_Pos == 6) Seg_Pos = 0;//数码管显示专用
Seg_Disp(Seg_Pos,Seg_Buf[Seg_Pos]);
}
/* Main */
void main()
{
Timer0Init();
while (1)
{
Key_Proc();
Seg_Proc();
Led_Proc();
}
}
我们将通过Protues仿真来判断底层代码和main函数是否正确,运行结果如下所示:
在其中/*main*/主函数中,我们分别定义了一个Key_Slow_Down'和Seg_Slow_Down 减速专用变量,其解释如下:
在 Keil 开发环境中,按键减速专用变量主要用于按键去抖动以及防止按键长时间按下时重复触发过快。这在嵌入式开发中是非常常见的做法,因为物理按键存在抖动现象,可能会导致在短时间内多次触发按键中断或事件。
具体来说,按键减速专用变量的作用包括以下几个方面:
1. 按键去抖动
当按键被按下或释放时,按键的机械结构可能会在接触瞬间产生抖动,即按键的开关在非常短的时间内(通常在 5-50 毫秒范围内)多次开合,导致按键输入信号不稳定。这种抖动如果不加处理,会被系统误判为多次按键按下。
按键减速变量可以通过延时的方式来忽略按键抖动期间的重复信号。通过每次检测按键后,等待一段固定的时间(如 10ms、100ms 等),避免系统在抖动期间频繁响应按键信号。
2. 防止按键长按时重复触发过快
当按键被按住时,系统如果没有减速机制,可能会每次都快速读取按键的状态,从而导致按键功能被快速重复执行。这会导致用户的长按操作触发多次按键事件。
按键减速专用变量通过在按键触发之后设置一定的时间间隔(例如 100ms),在这段时间内忽略后续的按键触发,直到减速时间结束,按键才允许再次触发。这样可以防止按键长按时重复触发太快,达到更好的用户体验。
3. 实现按键的定时扫描
在很多嵌入式系统中,按键的状态是通过定时器周期性扫描获取的。按键减速变量可以在定时器中断里进行递增,当达到设定值时(如 100ms 内部计数),才进行一次按键扫描,从而实现按键的周期性扫描和响应。
代码中的用法示例:
在代码中,Key_Slow_Down
是按键减速专用变量,其作用是控制按键检测的间隔时间。在 Timer0Server
定时器中断函数中,这个变量会递增,当它达到某个设定值(如 10)时,系统才允许再次检测按键。该部分代码如下:
if(++Key_Slow_Down == 10)
Key_Slow_Down = 0; // 每10次中断,重置减速变量
当 Key_Slow_Down
达到 100 时,说明已经过了 100 次中断(例如,假设定时器中断是每 1 毫秒触发一次,这意味着已经过了 100 毫秒),此时系统会重新允许按键处理程序 Key_Proc()
执行。
在 Key_Proc()
函数中,首先会检查 Key_Slow_Down
是否为 0,只有当 Key_Slow_Down
归零时,才允许进行按键处理:
void Key_Proc()
{
if(Key_Slow_Down) return; // 如果Key_Slow_Down不为0,跳过按键处理
Key_Slow_Down = 1; // 按键减速变量重新置1,等待下一次允许检测
}
总结
按键减速专用变量的作用就是防止按键在短时间内被多次触发(去抖和防止长按多次触发),从而保证按键操作的稳定性和响应的可靠性。
完整代码如下所示:
/* 头文件声明区 */
#include <REGX52.H>//单片机寄存器专用头文件
#include "Key.h"//按键底层驱动专用头文件
#include "Seg.h"//数码管底层驱动专用头文件
/* 变量声明区 */
unsigned char Key_Val,Key_Down,Key_Old;//按键专用变量
unsigned char Key_Slow_Down;//按键减速专用变量 10ms unsigned char范围0—225;
unsigned char Seg_Buf[6] = {10,10,10,10,10,10};//数码管显示数据存放数组
unsigned char Seg_Pos; //数码管扫描专用变量
unsigned int Seg_Slow_Down;//数码管减速专用变量 500ms
unsigned int Timer_1000ms; //1000ms标志位
unsigned char Time_Count=30; //系统倒计时标志位;
unsigned char Seg_Mode; //默认初始值为0-显示界面,1-设置界面;
bit start_Flag=0; //系统开始标志位 0为暂停,1为开始;
unsigned char set_date [3]={15,30,60}; //系统设置参数数组;
unsigned char set_date_scan=1; //系统设置参数扫描变量;
unsigned int Timer_500ms; //500ms标志位;
bit Seg_Flag;
/* 键盘处理函数 */
void Key_Proc()
{
if(Key_Slow_Down) return;
Key_Slow_Down = 1;//键盘减速程序
Key_Val = Key_Read();//实时读取键码值
Key_Down = Key_Val & (Key_Old ^ Key_Val);//捕捉按键下降沿
Key_Old = Key_Val;//辅助扫描变量
switch(Key_Down)
{
case 1: //显示界面
if(Seg_Mode==0)
start_Flag=1;
break;
case 2: //复位界面;
if(Seg_Mode==0) //处于显示界面;
Time_Count=set_date[set_date_scan];
break;
case 3: // 切换界面
if(Seg_Mode==1) //处于设置界面:
Time_Count=set_date[set_date_scan];
Seg_Mode=!Seg_Mode; //若处于显示界面则切换成设置界面;
break;
case 4:
if(Seg_Mode==1)
{
if(++set_date_scan==3) set_date_scan=0; //0-1-2之间循环:
}
break;
}
}
/* 信息处理函数 */
void Seg_Proc()
{
if(Seg_Slow_Down) return;
Seg_Slow_Down = 1;//数码管减速程序
Seg_Buf[0]=Seg_Mode+1; //显示界面与设置界面切换;
if(Seg_Mode==0) //系统处于显示界面;
{
Seg_Buf[4]=Time_Count/10%10; //十位
Seg_Buf[5]=Time_Count%10; //个位
}
else //系统处于设置界面;
{
Seg_Buf[4]=set_date[set_date_scan]/10%10;
Seg_Buf[5]=set_date[set_date_scan]%10;
}
}
/* 其他显示函数 */
void Led_Proc()
{
if(Time_Count==0)
{
P1=0x00;
P2_3=0;
}
else
{
P1=0xff;
P2_3=1;
}
}
/* 定时器0中断初始化函数 */
void Timer0Init(void) //1毫秒@12.000MHz
{
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x01; //设置定时器模式
TL0 = 0x18; //设置定时初始值
TH0 = 0xFC; //设置定时初始值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
ET0 = 1; //定时器0中断打开
EA = 1; //总中断打开
}
/* 定时器0中断服务函数 */
void Timer0Server() interrupt 1
{
TL0 = 0x18; //设置定时初始值
TH0 = 0xFC; //设置定时初始值
if(++Key_Slow_Down == 10) Key_Slow_Down = 0;//键盘减速专用
if(++Seg_Slow_Down == 500) Seg_Slow_Down = 0;//数码管减速专用
if(++Seg_Pos == 6) Seg_Pos = 0;//数码管显示专用
Seg_Disp(Seg_Pos,Seg_Buf[Seg_Pos]);
if(start_Flag==1)
{
if(++Timer_1000ms==1000) //定时器中断:单片机可以配置定时器中断,使得在1000ms-1m时触发中断请求。这可以用于周期性任务调度或实现时间控制。
{
Timer_1000ms=0;
Time_Count--;
if(Time_Count==255) //0减1的值为255,char类型函数变量;
Time_Count=0;
}
}
}
/* Main */
void main()
{
Timer0Init();
while (1)
{
Key_Proc();
Seg_Proc();
Led_Proc();
}
}
我们可以在其中发现我们定义了一个Timer_1000ms 标志位,其和按键减速专用变量相似
在单片机中,1000ms标志位通常用于实现定时功能。它可以用来:
-
时间延迟:通过设置一个1000ms(即1秒)的标志位,单片机可以在1秒后执行特定的任务或操作。这对于需要周期性执行的任务,比如定时更新显示或轮询传感器等非常有用。
-
定时器中断:单片机可以配置定时器中断,使得在1000ms时触发中断请求。这可以用于周期性任务调度或实现时间控制。
-
任务调度:在一些实时操作系统中,1000ms标志位可以用于调度任务,以保证不同任务之间的时间分配和管理。
-
计时器:可以用于精确计时,例如计时器功能来测量时间间隔或执行计时操作。
实现这种功能通常涉及配置单片机的定时器或计时器模块,并设置适当的计时值和中断处理程序。这样,单片机在计时器达到指定时间(例如1000ms)时会产生一个标志位或中断信号。
我们可以观察到所给的题目限制了蜂鸣器的功能:
其中蜂鸣器的I/O口接到我们的P2_3口共阴极当:
P2_3=0; //蜂鸣器打开
P2_3=0; //蜂鸣器关闭
更值得注意的一点是我们前期一定要写完一个功能去proteus进行测试防止后续程序报错。