数码管定时器

  1. 视频演示

  2. 任务要求

  3. 任务实现

1、视频演示

数码管计时器 

希望大家也能关注一下新人up,以后会有更有意思的视频,也希望各位大佬提出些改进的建议。

2、任务要求

正片开始,这是我们协会的作业,要求如下:

数码管定时器_第1张图片

 这个作业我个人觉得还是比较简单的,不过因为是个小白,所以有些知识点不太熟悉,所以也花了些时间,期间也在不断的试错,代码写得一般,希望有大佬能够帮忙指正。

2、任务实现

1、按键消抖

我个人觉得这一部分是比较重要的,因为我用的是晋中科技的51单片机,所以它板子的按键比较容易误触,一开是我用的是比较简单的消抖算法如下:

void key_scan()
{
    if (key == 0)//按键按下
    {
        Delay(10);//等待10ms
        if(key == 0)//二次检测,防止误触
        {
            //执行代码
            while(!key)//松手检测
            {

            }
        }
    }
}

这个算法计较简单,我看很多教程用的也是这种算法,但是我认为它有一点不太好,就是它需要等待松手,如果不松手,就会一直在循环里面,导致无法执行其他的程序。

如果是数码管显示,会使得每次按键后的刷新显示都会先熄灭再显示,就很不优雅,这种事情我肯定无法接受,所以就上网找有没有新的算法,让它的显示如吃了德芙一样丝滑(德芙打钱),还真让我找到了,B站UP主金善愚的视频,链接如下:独立按键进阶——非阻塞延时实现独立按键的消抖与按键识别方法

作为优雅的Ctrl c/v工程师,我好好“借鉴”了一下,以KEY1为例

#define Key_Delay_Time 100			//宏定义按键延迟的时间
unsigned char key1_lock_float;		//定义KEY1自锁标志
unsigned char key1_cnt;				//定义KEY1积累变量

unsigned char Key_Scan(void)    	//独立按键扫描函数,读取键值
{
	unsigned char Key_Num = 0;
	if(key1)                        //按键未按下
	{
		key1_lock_float = 0;        //清除自锁标志
		key1_cnt = 0;               //清除计数标志
	}
	else if(!key1_lock_float)        //!key1_lock_float && key1 ==0
	{
		key1_cnt++;//累计按键消抖延迟次数
		if(key1_cnt > Key_Delay_Time)
		{
			key1_cnt = 0;//清零计数变量
			key1_lock_float = 1;//自锁标志置1,防止按键多次触发
			Key_Num = 1;//赋键值编码
		}
	}
	return Key_Num;
}

我用我浅薄的理解来讲一下,

if(key1)                        //按键未按下或者有抖动
	{
		key1_lock_float = 0;        //清除自锁标志
		key1_cnt = 0;               //清除计数标志

KEY1未按下时为高电平,或者当我们按下时,因为按键中间薄膜的抖动会有高低电平的转换,这一部分代码就是为了防止因为抖动造成电平变换导致误触

else if(!key1_lock_float)        //!key1_lock_float && key1 ==0
	{
		key1_cnt++;//累计按键消抖延迟次数
		if(key1_cnt > Key_Delay_Time)
		{
			key1_cnt = 0;//清零计数变量
			key1_lock_float = 1;//自锁标志置1,防止按键多次触发
			Key_Num = 1;//赋键值编码
		}

原来KEY1未按下为高电平,else就是当它按下低电平的时候,之前我们未按下KEY1时,将key1_lock_float置零,!key1_lock_float后它就为1成立,接着执行下面的代码。

因为我们这个函数是放在while(1)里不断循环的,所以每调用函数,key1_cnt都会+1,当key1_cnt这个变量>Key_Delay_Time这个变量时,将我们的计数变量清零,自锁标志置1,防止多次触发,同给Key_Num赋值,方便后续switch()函数的使用。

Key_Delay_Time这个变量需要通过我们去预估我们程序需要多少时间去宏定义,同时这个值越小灵敏度就越高。

2、数码管显示

数码管显示的原理网上有很多,这里就不在这卖弄了,大家可以去B站大学找找看看,我这个代码也是参考 

数码管动态显示编程之基于底层显示模块的应用层功能程序 

这个视频写的,这里以一位数码管为例,八位数码管是一样的。

#define GPIO_DIC P0			//段码
#define GPIO_PLACE P2		//位码

/*编码表*/
unsigned char code leddate[] ={	0x3f,//0
								0x06,//1
								0x5b,//2
								0x4f,//3
								0x66,//4
								0x6d,//5
								0x7d,//6
								0x07,//7
								0x7f,//8
								0x6f,//9
								0x77,//A
								0x7c,//B
								0x39,//C
								0x5e,//D
								0x79,//E
								0x71,//F
								0x40,//-
								0x00//清空
							    };
unsigned char LEDBuf[] = {17,17,17,17,17,17,17,17};//数据显示缓冲区
unsigned char code PLACE_CODE[] = {0x00,0xff,0xfb,0xf7,0xf3,0xef,0xeb,0xe7,0xe3};//位选信号 0x00占位无具体位号

/*数码管动态显示显示*/
void Display()
{
	unsigned char i;
	switch(i)
	{
		case 0:GPIO_DIC =leddate[LEDBuf[0]];    //段选
               GPIO_PLACE = PLACE_CODE[1];      //位选
               Delay(1);                        //延迟
               GPIO_DIC = 0x00;                 //消影
               i++;                     
                break;
	}
}

编码表没什么好说的就给对应的管脚电平,然后二进制转十六进制就行了。

数据显示缓冲区就是方便我们以后如果需要显示不同的数据,直接去这里更改,方便后期维护与修改。

位选信号就是给锁存器对应的电平,让它显示第几位数码管,这里为了方便显示对应数码管,我在第一个位置,也就是数组的零号位,加了一个占位的数据,这样数组里数据的下标就和数码管的位数对上。

下面就是普通的调用,先进行段选,选择自己要选择的数据,我这里用的是数组嵌套

从里往外讲因为我将LEDBuf[0]赋17,所以

leddate[LEDBuf[0]] = leddate[17]

对应编码表里的第18个“清空”

然后进行位选,选择要第几位数码管显示,因为视觉暂留,我们延迟1ms,让它感觉是常亮的,其实是在不停的闪烁,最后消影,否则会有残影在数码管上,就很不优雅。

而这个i++,其实是为了动态显示用的,将这个程序放到while(1)里,它会不停循环,我们多加几个case ,每个case 都放入i++,这样当本次case 执行完因为i+1,所以它会进入到下一个case ,我们再最后一个case 将i初始化,使switch语句不断循环,实现动态显示。

4定时器

由于本人技术一般,所以不不知道怎么使用for()循环进行流水灯的滚动显示,所以这里使用了定时器。

#include "reg52.h"
#include "bsp_key.h"
#include "bsp_timer.h"

void Timer0Init(void)
{
	TMOD &= 0xF0;			//设置定时器模式
	TMOD |= 0x01;			//设置定时器模式
	TL0 = 0x18;				//设置定时初始值
	TH0 = 0xFC;				//设置定时初始值
	TF0 = 0;				//清除TF0标志
	ET0 = 1;				//使能定时器0中断
	EA = 1;					//使能中断总开关
	TR0 = 1;				//定时器0开始计时
}

/*中断函数*/
void Timer0_Routine() interrupt 1
{
	static unsigned int c = 0;
	
	TL0 = 0x18;				
	TH0 = 0xFC;				
	c++;
	
	/*延迟300ms*/
	if(c>=300)
	{
		c=0;
		Dis_service3();
	}
}

这里以晶振为12M的51单片机为例,TMOD |= 0X01 是将定时器设置为工作模式一。

然后设置TL0(低8位),TH0(高八位)的值,具体值计算如下:

晶体频率为12M,那么单片机的机械频率就为

12*(1/12M) = 1μs,

因为TH0和TL0总共能存储2^16 == 65536的数据,我们需要使定时器定时1ms,由前面的条件知道,要到1ms需要晶体发出1000次脉冲(1ms/1μs),但是1ms过去后,我们只往存储空间中存入1000,要到达65536,就需要先往存储空间里放入数据,当晶体发出1000次脉冲后,能够将存储空间填满,使TF0置1,所以

预先放入的值(后称预值)  =(65536-1000)

同时我们需要将这个值分别放入TH0和TL0里,所以

TH0 = 预值 /256

TL0 = 预值 %256

256刚好为2^8,那么 预值 /256 就可以得到高八位,而 预值 %256 可以得到低八位。

接着就是将TF0置零,打开中断ET0和中断总开关EA0,开启定时器TR0

下面的函数实现就是当中断触发,将定时器初始化,当300ms后,使数码管显示。

接下来就是具体的实现

void main()
{	
	while(1)
	{
		key_service();
		if(Num1 == 1)//进入数字设置模式
		{
			Dis_service2();
		}
		else if(Num1 == 0 &&Num3 == 1)//退出数字模式且按下KEY4,进入自检
		{
		Timer0Init();
		}
		else
		{
			Dis_service1();
		}
		Display();
	}		
}

void key_service()
{
	switch(Key_Scan())
	{
		case 1:Num1++;if(Num1>1){Num1 = 0;}break;//Num1 == 1显示,Num1 ==0不显示
		case 2:if(Num1 == 1){Num2++;}if(Num2>10){Num2 = 0;}break;//数码管显示Num2对应数字
		case 3:if(Num1 == 1){Num2--;};if(Num2<1){Num2 = 10;}break;//数码管显示Num3对应数字
		case 4:if(Num1 == 0){Num3++;};if(Num3>1){Num3 = 0;}break;	//启动自检程序
	}
}
/*KEY1程序 Num1 == 0不显示 Num2 == 1显示*/
void Dis_service1()
{
	if(Num1 == 0)
	{	
		LEDBuf[0] = 17;
		LEDBuf[1]	=	17;
		LEDBuf[2]	= 17;
		LEDBuf[3]	= 17;
		LEDBuf[4]	= 17;
		LEDBuf[5]	= 17;
		LEDBuf[6]	= 17;
		LEDBuf[7]	= 17;
	}
	else
	{
		LEDBuf[0] = 0;
		LEDBuf[1]	=	0;
		LEDBuf[2]	= 16;
		LEDBuf[3]	= 0;
		LEDBuf[4]	= 0;
		LEDBuf[5]	= 16;
		LEDBuf[6]	= 0;
		LEDBuf[7]	= 0;
	}
}

/*KEY2 KEY3程序 显示Num2数字 按KEY2++ 按KEY3--*/
void Dis_service2()
{
	LEDBuf[0] = Num2;
	LEDBuf[1]	= Num2;
	LEDBuf[2]	= 16;
	LEDBuf[3]	= Num2;
	LEDBuf[4]	= Num2;
	LEDBuf[5]	= 16;
	LEDBuf[6]	= Num2;
	LEDBuf[7]	= Num2;
}
	
/*KEY4程序 自检*/
void Dis_service3()
{
	j++;
	LEDBuf[0] = j;
	LEDBuf[1]	= j;
	LEDBuf[2]	= j;
	LEDBuf[3]	= j;
	LEDBuf[4]	= j;
	LEDBuf[5]	= j;
	LEDBuf[6]	= j;
	LEDBuf[7]	= j;
	if(j>14)
	{
		j=17;
	}
}

任务一

设置变量Num1 当KEY1按下,key_scan()函数返回1,进入switch函数,使Num1++

当Num>1时置零,当Num1 == 0时数码管不显示(退出数字显示模式)

当Num1 == 1时数码管显示00-00-00(进入数字显示模式)。

任务二

设置变量Num2 当KEY2按下,key_scan()函数返回2,进入switch函数

当KEY1 == 1(进入数字显示模式),显示数值,同时使Num2++

当Num2 >10时,将Num2置零(只显示0~A),实现0-A的循环。

当KEY1 == 0(退出数字显示模式),Num2不变化,实现只有在数字显示模式里才能对数码管显示的数值进行改变。

任务三

和任务二一样,将Num2++ 变为 Num2--,按键改为KEY3,key_scan()函数返回3,当Num2 <1时,将Num2置10。

任务四

设置变量Num3 当KEY4按下,key_scan()函数返回4,进入switch函数

当KEY1 == 0(退出数字显示模式),使Num3++

当KEY1 == 1(进入数字显示模式),Num3不变化

当 KEY1 == 0且 KEY3 == 1时,进入自检,够实现只有在退出数字显示模式里才能进行自检。

你可能感兴趣的:(c++,51单片机,算法)