目录
附上工程下载地址:
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.数码管被选中的亮灭闪烁动画:
我写的数码管打印代码是一次性打印八个位选的,在其他文件有介绍可提取:
要实现闪烁很简单,让对应位的段码在要求的时间内=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;}
}
从这段代码注释也可看到,将对应行代码注释掉,即可去除连续长按扫描效果: