蓝桥杯单片机设计点1:按键长按触发,数码管被选中闪烁动画

目录

附上工程下载地址:

附上最新更改后的工程下载地址:

蓝桥杯设计点1介绍:

实践上的实现与解析:

1.add分位的处理、按键短按:

2.矩阵按键长按触发的写法:

3.数码管被选中的亮灭闪烁动画:

4.实验效果视频:

5.事后问题发现与改进:


附上工程下载地址:

https://download.csdn.net/download/qq_64257614/87779267?spm=1001.2014.3001.5503

附上最新更改后的工程下载地址:

https://download.csdn.net/download/qq_64257614/87805683?spm=1001.2014.3001.5503

蓝桥杯设计点1介绍:

按键长按触发的功能在往年国赛才考察,但近年来蓝桥杯难度加大,省赛也开始考察长按触发了。

数码管选中闪烁的考察比较基础简单,在第九届省赛、第八届省赛等就开始出现了。

按键的长按触发,是要按键扫描函数和定时器结合起来一起实现的。

数码管选中闪烁的实现也与定时器密切相关。

实践上的实现与解析:

此处我写了一个小工程实验来演示分享这俩个功能如何实现:

需要实现的操作如下:

/*
	本次实验主要演示学习
	来实现矩阵按键长按触发的功能
	用到的是S4 S5 S8 S9
	
	add 是被加的数,有四位,没用到的位显示0;
	S5 按键按下取消所有选择设置
	S9 按下是开始从个位更改数据
	
	S4 S8分长短按情况处理:
	S4 短按就是当前位的 加功能
	S8 短按就是当前位的 减功能
	S4 长按会左移一位选中的位
	S8 长按会右移一位选中的位
	
	数码管被选中的位会以0.66s 为周期进行亮灭
*/

1.add分位的处理、按键短按:

首先我们发现,add作为被加的数,而且有最大四位,如何对每个不同的数位进行加减呢?

我们在这里可以用计算机的空间来换取自己为数不多的脑容量

我采用了分位加减,再合并计算的思想:

u16 add;    //打印被加的数
u8 add_1;   //加数个位
u8 add_2;   //加数十位
u8 add_3;   //加数百位
u8 add_4;   //加数千位

这样,我们就只需在要求的地方对add_1~add_4四个位分别进行加减

然后合并方面的计算扔给计算机就能得到add的值了:

add=add_4*1000+add_3*100+add_2*10+add_1;

然而我们在设计时的思想应活跃起来.

在实际的实践过程中我们发现:

在语句结构的限制下,我们对每个位其实应该有个“选中”的操作,

如何对位进行选中?是用很多if()?还是用Switch()看起来更美观些?

我认为数组其实是解决这个“选中”问题的一种比较好的解法——>

看下图按键扫描处理的代码:

(该代码存在于主函数while(1)之中)

		if(key_flag==1)    //判断按键扫描标志
		{
			key_flag=0;
			key_value=key_return();
			switch(key_value)
			{
				case 5:           //取消选中任何位
								wei=0;
								break;
				case 9:	          //开始从个位更改数据
                                wei=1;	
								break;
				case 4:						//当前位的 加功能
								
								break;
				case 8:						//当前位的 减功能
								
								break;
			}

在此处先说明:

1.此处贴出的按键操作处理代码,存在于主函数while(1)之中

    能且只能实现短按松手检测功能,真正的长按还在文章后面(写在了定时器中)

2.case 4与case 8 空白处就是我们需要对选中位进行 短按加减操作的地方;

3.代码中的变量 wei 定义在 工程文件 #include “Timer.c”中,表示当前被选中的,变量add的数位

4.实验要求中 S4 S5 的长按和 S5 S9都会影响 wei的值

从代码中可以发现:

wei=0表示不选择任何位,wei=1表示选择个位,

wei=2表示选择十位,wei=3表示选择百位,wei=4表示选择千位;

因此我们可以定义数组,用数组的四个下标位来对应add的四个数位

然后进行前面说到的数学计算即可:

u16 add;    //打印被加的数
u8 add_w[5]={0,0,0,0,0};   //加数其余各个位
u8 wei;     //对位的选择记录

然后像我一样填入按键操作函数就是这样比较简洁了:

		if(key_flag==1)    //判断按键扫描标志
		{
			key_flag=0;
			key_value=key_return();
			switch(key_value)
			{
				case 5:           //取消选中任何位
								wei=0;
								break;
				case 9:	wei=1;	  //开始从个位更改数据
								break;
				case 4:						//当前位的 加功能
								add_w[wei]++;
								add=add_w[4]*1000+add_w[3]*100+add_w[2]*10+add_w[1];
								break;
				case 8:						//当前位的 减功能
								add_w[wei]--;
								if(add_w[wei]<=0) //对位减的时候注意不要小于0
								{add_w[wei]=0;}
								add=add_w[4]*1000+add_w[3]*100+add_w[2]*10+add_w[1];								
								break;
			}
		}

对数据的加减操作,我们要时刻考虑是否越界,有很多约定俗成的,不能小于0的,不能大于10的情况我们都要考虑到。

因为最终在数码管打印的变量是add

所以每次加减操作过后都要及时更新计算add的值。

2.矩阵按键长按触发的写法:

我们看矩阵按键函数:

该函数在文件#include  "key_4589.h"中

#ifndef _key_4589_h_
#define _key_4589_h_

#include "stc15f2k60s2.h"
#include "public.h"

extern u8 key4_flag;    //S4长安标志位
extern u8 key8_flag;		//S8长安标志位

sbit X1=P3^3;
sbit X2=P3^2;
sbit Y1=P4^4;
sbit Y2=P4^2;

u8 key_return();

#endif

u8 key4_flag;   //S4长安标志位
u8 key8_flag;		//S8长安标志位


void Delay12ms()		//@12.000MHz
{
	unsigned char i, j;

	i = 141;
	j = 16;
	do
	{
		while (--j);
	} while (--i);
}



void key_scan_inint(u8 n)
{
	switch(n)
	{
		case 1:X1=0;X2=1;Y1=1;Y2=1;break;
		case 2:X1=1;X2=0;Y1=1;Y2=1;break;
	}
}

u8 key_return()
{
	u8 key_value;
	key_value=0;
	
	Delay12ms();     //消抖
	
	key_scan_inint(1);
	if(Y1==0)
	{
		while(Y1==0)		//长按保持key4_flag=1
		{key4_flag=1;}
		key_value=4;
		key4_flag=0;		//松手key4_flag=0
	}
	if(Y2==0)
	{
		while(Y2==0)		//长按保持key8_flag=1
		{key8_flag=1;}	
		key_value=8;
		key8_flag=0;		//松手key8_flag=0
	}	
	
	key_scan_inint(2);
	if(Y1==0)
	{while(Y1==0);key_value=5;}
	if(Y2==0)
	{while(Y2==0);key_value=9;}	

	return key_value;
}

发现在S4 S8俩个按键的 等待松开的whie()循环中

多了一句让它们对应的keyx_flag=1;

这个标志在松手时又会置0;

	if(Y1==0)
	{
		while(Y1==0)		//长按保持key4_flag=1
		{key4_flag=1;}
		key_value=4;
		key4_flag=0;		//松手key4_flag=0
	}

这个标志就界定了一个时间区间——>按下开始—保持—松手结束

而我们该如何对这个时间区间计时呢?

只需在定时器中定义一个变量,在定时器中计数计时

u16 key4_cnttime;//定时器中为S4计数计时
u16 key8_cnttime;//定时器中为S8计数计时

当定时器服务函数中  if(keyx_flag==1)条件满足时,就开始自加

	//处理矩阵按键长按
	if(key4_flag==1)   		  //当标志位置1时,即按键按住时
	{ 
		key4_cnttime++;       //开始为按住这个过程计数计时
		if(key4_cnttime==800)	//当按住800ms时
		{
			key4_cnttime=0;			//计数清0,执行位加操作,实现向左移位
			wei++;
			if(wei>=4) {wei=4;}  //注意边界
		}
	}
	//当标志位置0时,即按键松开时,计数保持置0
	if(key4_flag==0) {key4_cnttime=0;}
	

3.数码管被选中的亮灭闪烁动画:

我写的数码管打印代码是一次性打印八个位选的,在其他文件有介绍可提取:

蓝桥杯单片机第十一届省赛练习回顾_NULL指向我的博客-CSDN博客

 蓝桥杯单片机数码管和LED冲突,数码管动态刷新鬼影_NULL指向我的博客-CSDN博客

 要实现闪烁很简单,让对应位的段码在要求的时间内=0xff 即可。

数码管字库如下:

//数码管字库
/*
数组下标对应段码速查:
	0_0  1_1  2_2  3_3  4_4  5_5 
       6_6  7_7  8_8  9_9  10_a 
       11_b 12_c	13_d 14_e 15_f 
       16_空 17_根线 18_H 19_P 
	20_0. 21_1. 22_2. 23_3. 24_4. 25_5.
				26_6. 27_7. 28_8. 29_9. 
*/
u8 code smgZK[30]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,
	                      0x82,0xf8,0x80,0x90,0x88,
                        0x83,0xc6,0xa1,0x86,0x8e,
                        0xff,0xbf,0x89,0x8c,
									 0x40,0x79,0x24,0x30,0x19,0x12,
									      0x02,0x78,0x00,0x10
									};

在定时器中每8ms一次不断地去刷新数码管:

 至于nr1~nr8的值,我在定时器的文件上如此定义了函数与变量来赋值:

u8 lm;      //数码管闪烁亮灭的标志
u8 nr1,nr2,nr3,nr4,nr5,nr6,nr7,nr8; //数码管打印内容
 
void give_nr()
{
	nr1=add/1000;      //add千位
	nr2=add/100%10;		 //add百位
	nr3=add/10%10;		 //add十位
	nr4=add%10;				 //add个位
	nr5=nr6=nr7=nr8=16;//后四个数码管常灭
	
	if(lm==1)          //接收到闪烁亮灭的标志lm=1
	{
		switch(wei)			 //就覆盖赋值使对应位的数码管灭掉
		{
			case 1:nr4=16;break;
			case 2:nr3=16;break;
			case 3:nr2=16;break;
			case 4:nr1=16;break;
		}
	}
}	

1.变量lm是亮灭标志,lm=0时,不会有数码管熄灭,(除了常灭的最后四位)

        lm=1时,根据wei变量,选择哪一位被灭掉。

2.覆盖赋值的方法使得不需要另外定义别的变量来记录nr x之前是什么值。

3.在定时器中,在适当的 定时计数的  时候将lm置零-->置1-->置0循环,

        以下代码直接写在定时器服务函数中:

	if(wei!=0)			//当有位被选中时
	{	
		tim_1++;      //550ms亮标志
		tim_2++;			//550ms灭标志
		if(tim_1==550)
		{
			tim_1=0;
			lm=1;
		}
		if(tim_2==1100)
		{
			tim_2=0;
			lm=0;
		}
	}
	if(wei==0) {lm=0;} //最后别忘了在没有位被选中时,lm保持为0

4.最后将函数give_nr()和数码管打印函数放一起就可以了。

 如图:

这样我们就完成这个实验了。

4.实验效果视频:

按键长按触发测试

5.事后问题发现与改进:

从视频中我们发现俩个问题:

1.长按之后执行不是一次,而是一直累时累加

2.长按之后会同步进行触发一次短按的效果

这俩个问题的结局十分简单,在按键的函数中增加一个长按状态的标志位

即可解决同步触发的问题:

#include "key_4589.h"

u8 key4_flag;   //S4长安标志位
u8 key8_flag;		//S8长安标志位
u8 key_long_state;  //长按松手禁短触

void Delay12ms()		//@12.000MHz
{
	unsigned char i, j;

	i = 141;
	j = 16;
	do
	{
		while (--j);
	} while (--i);
}



void key_scan_inint(u8 n)
{
	switch(n)
	{
		case 1:X1=0;X2=1;Y1=1;Y2=1;break;
		case 2:X1=1;X2=0;Y1=1;Y2=1;break;
	}
}

u8 key_return()
{
	u8 key_value;
	key_value=0;
	
	Delay12ms();     //消抖
	
	key_scan_inint(1);
	if(Y1==0)
	{
		while(Y1==0)		//长按保持key4_flag=1
		{key4_flag=1;}
		key_value=4;
		key4_flag=0;		//松手key4_flag=0
	}
	if(Y2==0)
	{
		while(Y2==0)		//长按保持key8_flag=1
		{key8_flag=1;}	
		key_value=8;
		key8_flag=0;		//松手key8_flag=0
	}	
	
	key_scan_inint(2);
	if(Y1==0)
	{
		while(Y1==0);key_value=5;
	}
	if(Y2==0)
	{
		while(Y2==0);key_value=9;
	}	
	
	if(key_long_state==1) //如果上次进行了长按
	{
		key_long_state=0;		//清零长按标志
		key_value=0;				//清除长按松手误发的键值
	}	
	
	return key_value;
}

这个标志位在定时器服务中断函数中对应的,长按抵达情况会被置1:

void Timer1_server() interrupt 3
{
	u8 i,key;
	u16 tim_1,tim_2;
	TL1=0X18;
	TH1=0XFC;
	i++;key++;
	
	if(i==8)           //8ms打印一次数码管
	{
		i=0;
		give_nr();
		smg_display(nr1,nr2,nr3,nr4,nr5,nr6,nr7,nr8);//打印数码管
	}
	
	if(wei!=0)			//当有位被选中时
	{	
		tim_1++;      //550ms亮标志
		tim_2++;			//550ms灭标志
		if(tim_1==550)
		{
			tim_1=0;
			lm=1;
		}
		if(tim_2==1100)
		{
			tim_2=0;
			lm=0;
		}
	}
	if(wei==0) {lm=0;} //最后别忘了在没有位被选中时,lm保持为0
	
	if(key==15)        //15ms扫描一次按键
	{
		key=0;
		key_flag=1;
	}
	
	//处理矩阵按键长按
	if(key4_flag==1)   		  //当标志位置1时,即按键按住时
	{ 
		key4_cnttime++;       //开始为按住这个过程计数计时
		if(key4_cnttime==800)	//当按住800ms时
		{
			key_long_state=1;   //记录为长按状态
//			key4_cnttime=0;			//计数清0,去除此行不清0,长按不连续触发
			wei++;							//执行位加操作,实现向左移位
			if(wei>=4) {wei=4;}  //注意边界
		}
	}
	//当标志位置0时,即按键松开时,计数保持置0
	if(key4_flag==0) {key4_cnttime=0;}
	
	if(key8_flag==1)   		  //当标志位置1时,即按键按住时
	{ 
		key8_cnttime++;       //开始为按住这个过程计数计时
		if(key8_cnttime==800)	//当按住800ms时
		{
			key_long_state=1;    //记录为长按状态
//			key8_cnttime=0;			//计数清0,去除此行不清0,长按不连续触发
			wei--;							//执行位减操作,实现向右移位
			if(wei<=0) {wei=1;} //注意边界
		}
	}
	//当标志位置0时,即按键松开时,计数保持置0
	if(key8_flag==0) {key8_cnttime=0;}		
	
}

从这段代码注释也可看到,将对应行代码注释掉,即可去除连续长按扫描效果:

 

  • 12
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 9
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

NULL指向我

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值