Bootstrap

【蓝桥杯-单片机】基于定时器的倒计时程序设计

基于定时器的倒计时程序

题目如下所示:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

实现过程中遇到的一些问题

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

;