基于定时器的倒计时程序
题目如下所示:
实现过程中遇到的一些问题
01 如何改变Seg_Buf数组的值数码管总是一致地显示0 1 2 3 4 5
首先这个问题不是在main.c中关于数码管显示部分的逻辑错误,就是发生在数码管的底层错误。
检查了逻辑部分,没有发现问题
转而查找底层上面的错误。
底层的Seg.c是这样写的:
#include <Seg.h>
unsigned char code Seg_Dula[] = {0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f ,0x00};
unsigned char code Seg_Wela[] = {0xfe ,0xfd ,0xfb,0xf7,0xef,0xdf};
void Seg_Disp(unsigned char wela,dala)
{
//消影
P0 = 0x00;
P2_6 = 1;
P2_6 = 0;
P0 = Seg_Wela[wela];
P2_7 = 1;
P2_7 = 0;
P0 = Seg_Dula[wela];//问题出现在这里!
P2_6 = 1;
P2_6 = 0;
}
果然被我发现了,段选数组的索引写错了,写成了wela,这样无论如何,数码管的每一位都会按照传入的wela来显示(wela在main函数中即Seg_Pos,这个变量在0-5范围内循环)
if(++Seg_Pos == 6) Seg_Pos = 0;
Seg_Disp(Seg_Pos,Seg_Buf[Seg_Pos]);
02 按下设置按键KEY4设置好倒计时参数后,该参数应该在什么时机传输给Time_Count倒计时变量用于倒计时呢?
(1)设置完就对Time_Count赋值
//按键处理函数Key_Proc()
void Key_Proc()
{
if(Key_Slow_Down)return;
Key_Slow_Down = 1;//按键减速程序
//这三行要背下来
Key_Val = Key_Read();//读取按下的键码值
Key_Down = Key_Val &(Key_Val ^ Key_Old);//捕获下降沿
Key_Old = Key_Val;//辅助扫描
switch(Key_Down)
{
//框架先搭好,内容先不写
case 1:
if(Seg_Mode == 0) System_Flag = 1;
break;
case 3:
Seg_Mode ^= 1;
break;
case 4:
if(Seg_Mode == 1)
{
if(++Set_Dat_Index == 3) Set_Dat_Index = 0;
Time_Count = Set_Dat[Set_Dat_Index];//设置完就对Time_Count赋值!!!!!!
}
break;
case 2:
if(Seg_Mode == 0) Time_Count = Set_Dat[Set_Dat_Index];
break;
}
}
如果这样的话,会导致切换回显示模式后已经倒计时了一段时间了,不是从设置的值开始倒计时的。想要切换回显示模式从设置的值开始倒计时,需要在切换回显示模式后,再对Time_Count赋值。
(2)在切换成显示模式后对Time_Count赋值
//按键处理函数Key_Proc()
void Key_Proc()
{
if(Key_Slow_Down)return;
Key_Slow_Down = 1;//按键减速程序
//这三行要背下来
Key_Val = Key_Read();//读取按下的键码值
Key_Down = Key_Val &(Key_Val ^ Key_Old);//捕获下降沿
Key_Old = Key_Val;//辅助扫描
switch(Key_Down)
{
//框架先搭好,内容先不写
case 1:
if(Seg_Mode == 0) System_Flag = 1;
break;
case 3:
Seg_Mode ^= 1;
if(Seg_Mode == 0)
Time_Count = Set_Dat[Set_Dat_Index] ;//切换到显示界面再对Time_Count赋值!!!!!!
break;
case 4:
if(Seg_Mode == 1)
{
if(++Set_Dat_Index == 3) Set_Dat_Index = 0;
//Time_Count = Set_Dat[Set_Dat_Index];
}
break;
case 2:
if(Seg_Mode == 0) Time_Count = Set_Dat[Set_Dat_Index];
break;
}
}
03 倒计时到0之后为什么数码管又变成55再倒计时呢?
这是因为Time_Count–,没有对其作任何限制的情况下,Time_Count减为0之后会再从255开始减递减(Time_Count是一个unsigned char类型的变量,该变量8bit,变量可以表示的范围为0-255
)。而我们只让数码管显示到十位数,因此我们看到的就是数码管从55开始倒计时。
对中断服务函数代码作如下修改:
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(System_Flag == 1)
//倒计时开始
{
if(++Timer_1000ms == 1000)
{
Timer_1000ms = 0;
Time_Count--;//相当于1s中减少一次(倒计时一次)
if(Time_Count==255) Time_Count=0;//修改代码!!!!!!!!!!!!!!!!!!
}
}
}
修改代码之后数码管会停在00的位置。
至此,所有代码如下所示(不包含设置参数以1s为周期闪烁):
//头文件声明
#include <REGX52.H>
#include <Key.h>
#include <Seg.h>//动态数码管会用到数码管的底层?
//变量声明
unsigned char Key_Slow_Down;//按键减速专用变量 10ms
unsigned char Key_Val,Key_Down,Key_Old;//按键扫描专用变量
unsigned int Seg_Slow_Down;//数码管减速专用变量 500mschar:0-255.char不够用
//动态数码管
unsigned char Seg_Pos;//数码管扫描变量
unsigned char Seg_Buf[6] = {10,10,10,10,10,10};//数码管显示数据存放数组
unsigned char Seg_Mode;//数码管显示页面 0-显示 1-设置,默认为0
unsigned int Timer_1000ms;//1000ms标志位
unsigned char Time_Count = 30;//倒计时变量
//按键
bit System_Flag;//0-暂停 1-开始倒计时
unsigned char Set_Dat[3] = {15,30,60};//设置参数储存数组
unsigned char Set_Dat_Index = 1;
//LED点亮标志位
bit Timer0Flag;//0-倒计时没有到0;1-倒计时到0
//按键处理函数Key_Proc()
void Key_Proc()
{
if(Key_Slow_Down)return;
Key_Slow_Down = 1;//按键减速程序
//这三行要背下来
Key_Val = Key_Read();//读取按下的键码值
Key_Down = Key_Val &(Key_Val ^ Key_Old);//捕获下降沿
Key_Old = Key_Val;//辅助扫描
switch(Key_Down)
{
//框架先搭好,内容先不写
case 1:
if(Seg_Mode == 0) System_Flag = 1;
break;
case 3:
Seg_Mode ^= 1;
//if(Seg_Mode == 0)
//Time_Count = Set_Dat[Set_Dat_Index];
break;
case 4:
if(Seg_Mode == 1)
{
if(++Set_Dat_Index == 3) Set_Dat_Index = 0;
Time_Count = Set_Dat[Set_Dat_Index];
}
break;
case 2:
if(Seg_Mode == 0) Time_Count = Set_Dat[Set_Dat_Index];
break;
}
}
//信息显示函数Seg_Proc()
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;//可以写成Time_Count/10嘛
Seg_Buf[5] = Time_Count%10;
}
else//处于设置模式
{
Seg_Buf[4] = Set_Dat[Set_Dat_Index]/10%10;
Seg_Buf[5] = Set_Dat[Set_Dat_Index]%10;
}
}
/* 其他显示函数 */
//Led_Proc()
void Led_Proc()
{
if(Time_Count == 0)
{
P1 = 0x00;//LED全亮
P2_3 = 0;//蜂鸣器使能
}
else
{
P1 = 0xff;//LED全灭
P2_3 = 1;//蜂鸣器关闭
}
}
//Timer0Init()定时器0的中断初始化函数
void Timer0Init(void) //1毫秒@12.000MHz
{
//AUXR &= 0x7F; //定时器时钟12T模式
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x01; //设置定时器模式
TL0 = 0x18; //设置定时初值
TH0 = 0xFC; //设置定时初值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
//复制过来之后不要忘记加上两句话
ET0 = 1;
EA = 1;
}
//Server定时器0的中断服务函数
//a++是先进行取值,后进行自增。++a是先进行自增,后进行取值
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(System_Flag == 1)
//倒计时开始
{
if(++Timer_1000ms == 1000)
{
Timer_1000ms = 0;
Time_Count--;//相当于1s中减少一次(倒计时一次)
if(Time_Count==255) Time_Count=0;
}
}
}
//主函数
void main()
{
Timer0Init();//上电时立即调用定时器初始化函数
while(1)
{
//三大处理单元:按键、数码管、LED
Key_Proc();
Seg_Proc();
Led_Proc();
}
}
04 如何实现设置参数以1s为周期闪烁?
(1)在变量声明区新增两个变量:
注:以1s为周期闪烁,即500ms亮,500ms灭。
unsigned int Timer_500ms;//500ms标志位
bit Seg_Flag;//数码管标志位,即控制数码管的亮灭
(2)在中断服务函数中
if(++Timer_500ms == 500)
{
Timer_500ms = 0;
Seg_Flag ^= 1;
//因为默认值为0,只需要对其取反即可,不需要赋值,可以用^=1来对一个变量取反
}
(3)在数码管显示函数中
//信息显示函数Seg_Proc()
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;//可以写成Time_Count/10嘛
Seg_Buf[5] = Time_Count%10;
}
else//处于设置模式
{
// Seg_Buf[4] = Set_Dat[Set_Dat_Index]/10%10;
// Seg_Buf[5] = Set_Dat[Set_Dat_Index]%10;
//修改如下:
if(Seg_Flag == 1)
{
Seg_Buf[4] = 10;
Seg_Buf[5] = 10;
}
else
{
Seg_Buf[4] = Set_Dat[Set_Dat_Index]/10%10;
Seg_Buf[5] = Set_Dat[Set_Dat_Index]%10;
}
}
}
05 最终版代码
//头文件声明
#include <REGX52.H>
#include <Key.h>
#include <Seg.h>//动态数码管会用到数码管的底层?
//变量声明
unsigned char Key_Slow_Down;//按键减速专用变量 10ms
unsigned char Key_Val,Key_Down,Key_Old;//按键扫描专用变量
unsigned int Seg_Slow_Down;//数码管减速专用变量 500mschar:0-255.char不够用
//动态数码管
unsigned char Seg_Pos;//数码管扫描变量
unsigned char Seg_Buf[6] = {10,10,10,10,10,10};//数码管显示数据存放数组
unsigned char Seg_Mode;//数码管显示页面 0-显示 1-设置,默认为0
unsigned int Timer_1000ms;//1000ms标志位
unsigned char Time_Count = 30;//倒计时变量
//按键
bit System_Flag;//0-暂停 1-开始倒计时
unsigned char Set_Dat[3] = {15,30,60};//设置参数储存数组
unsigned char Set_Dat_Index = 1;
//设置参数闪烁
unsigned int Timer_500ms;//500ms标志位
bit Seg_Flag;//数码管标志位
//LED点亮标志位
bit Timer0Flag;//0-倒计时没有到0;1-倒计时到0
//按键处理函数Key_Proc()
void Key_Proc()
{
if(Key_Slow_Down)return;
Key_Slow_Down = 1;//按键减速程序
//这三行要背下来
Key_Val = Key_Read();//读取按下的键码值
Key_Down = Key_Val &(Key_Val ^ Key_Old);//捕获下降沿
Key_Old = Key_Val;//辅助扫描
switch(Key_Down)
{
//框架先搭好,内容先不写
case 1:
if(Seg_Mode == 0) System_Flag = 1;
break;
case 3:
Seg_Mode ^= 1;
//if(Seg_Mode == 0)
//Time_Count = Set_Dat[Set_Dat_Index];
break;
case 4:
if(Seg_Mode == 1)
{
if(++Set_Dat_Index == 3) Set_Dat_Index = 0;
Time_Count = Set_Dat[Set_Dat_Index];
}
break;
case 2:
if(Seg_Mode == 0) Time_Count = Set_Dat[Set_Dat_Index];
break;
}
}
//信息显示函数Seg_Proc()
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;//可以写成Time_Count/10嘛
Seg_Buf[5] = Time_Count%10;
}
else//处于设置模式
{
// Seg_Buf[4] = Set_Dat[Set_Dat_Index]/10%10;
// Seg_Buf[5] = Set_Dat[Set_Dat_Index]%10;
if(Seg_Flag == 1)
{
Seg_Buf[4] = 10;
Seg_Buf[5] = 10;
}
else
{
Seg_Buf[4] = Set_Dat[Set_Dat_Index]/10%10;
Seg_Buf[5] = Set_Dat[Set_Dat_Index]%10;
}
}
}
/* 其他显示函数 */
//Led_Proc()
void Led_Proc()
{
if(Time_Count == 0)
{
P1 = 0x00;//LED全亮
P2_3 = 0;//蜂鸣器使能
}
else
{
P1 = 0xff;//LED全灭
P2_3 = 1;//蜂鸣器关闭
}
}
//Timer0Init()定时器0的中断初始化函数
void Timer0Init(void) //1毫秒@12.000MHz
{
//AUXR &= 0x7F; //定时器时钟12T模式
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x01; //设置定时器模式
TL0 = 0x18; //设置定时初值
TH0 = 0xFC; //设置定时初值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
//复制过来之后不要忘记加上两句话
ET0 = 1;
EA = 1;
}
//Server定时器0的中断服务函数
//a++是先进行取值,后进行自增。++a是先进行自增,后进行取值
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(System_Flag == 1)
//倒计时开始
{
if(++Timer_1000ms == 1000)
{
Timer_1000ms = 0;
Time_Count--;//相当于1s中减少一次(倒计时一次)
if(Time_Count==255) Time_Count=0;
}
}
if(++Timer_500ms == 500)
{
Timer_500ms = 0;
Seg_Flag ^= 1;
//因为默认值为0,只需要对其取反即可,不需要赋值,可以用^=1来对一个变量取反
}
}
//主函数
void main()
{
Timer0Init();//上电时立即调用定时器初始化函数
while(1)
{
//三大处理单元:按键、数码管、LED
Key_Proc();
Seg_Proc();
Led_Proc();
}
}
参考视频:https://www.bilibili.com/video/BV1TR4y1k7iz?p=6&vd_source=5af7b905774c79f1754cd4ab83975115