Mobile wallpaper 1Mobile wallpaper 2Mobile wallpaper 3Mobile wallpaper 4
2728 字
14 分钟
按键相关功能实现和考点解析

按键底层#

基础按键底层#

特殊情况(独立按键)#

较简单的内容,这里没有记录。

特殊情况(串口使用)#

串口通信使用了P30和P31引脚,而这两个引脚同时也被用于矩阵键盘的按键扫描。为避免冲突,需要特别注意以下约束:

  1. 只能使用列扫描方式:由于P30和P31已被串口占用,矩阵键盘必须采用列扫描方式而非行扫描方式。

  2. 避免对P30/P31手动赋值:在键盘扫描过程中,不能对P30和P31直接进行赋值操作,否则会导致串口发送异常数据。

  3. 扫描与通信时序分离:在进行按键处理时,应暂停串口使用的定时器(在模板中统一为定时器2),确保两者互不干扰。

通过合理安排扫描时序和通信时序,可以实现矩阵键盘与串口在系统中的稳定工作。

代码示例:

unsigned char Key_Read()
{
unsigned char temp = 0;
AUXR &= ~(0x10); //关闭T2R 即 定时器2(串口波特率发生所用的定时器)
P44 = 0; P42 = 1; P35 = 1; P34 = 1;
if (P33 == 0) temp = 4;
if (P32 == 0) temp = 5;
if (P31 == 0) temp = 6;
if (P30 == 0) temp = 7;
P44 = 1; P42 = 0; P35 = 1; P34 = 1;
if (P33 == 0) temp = 8;
if (P32 == 0) temp = 9;
if (P31 == 0) temp = 10;
if (P30 == 0) temp = 11;
P44 = 1; P42 = 1; P35 = 0; P34 = 1;
if (P33 == 0) temp = 12;
if (P32 == 0) temp = 13;
if (P31 == 0) temp = 14;
if (P30 == 0) temp = 15;
P44 = 1; P42 = 1; P35 = 1; P34 = 0;
if (P33 == 0) temp = 16;
if (P32 == 0) temp = 17;
if (P31 == 0) temp = 18;
if (P30 == 0) temp = 19;
P3 = 0xff; //P3置位
AUXR |= 0x10; //打开T2R 即 定时器2(串口波特率发生所用的定时器)
return temp;
}

特殊情况(NE555)#

使用P34连接signal引脚进行NE555频率测量,因测量本质是对边沿进行计数,不能用P34作为按键的手动赋值和判断依据,这是硬件冲突,无法避免,需删除与P34有关的所有数据。

按键考点#

模式切换#

定义界面显示变量 Seg_show_mode,通过 Seg_show_mode = (++Seg_show_mode) % 3; 直接取余,就可保证这个值在0~2之间循环。

参数设置#

按键S8定义为“加”按键,在参数界面下按下温度参数值加1;S9定义为“减”按键,在参数界面下按下温度参数值减1;在时间回显子界面下,长按S9超过2秒后松开,清除所有已记录的数据,触发次数重置为0。(这里先不讨论长按问题)

代码示例:

//以这个为例,我们有一个参数
//首先需要定义参数
unsigned char dat; //需要调整的参数
unsigned char seg_show_mode; //1为参数界面
void Key_Proc()
{
static unsigned char Key_Val, Key_Down, Key_Up, Key_Old;
if (Key_Slow_Down)return;
Key_Slow_Down = 1;
Key_Val = Key_Read();
Key_Down = Key_Val & (Key_Old ^ Key_Val);
Key_Up = ~Key_Val & (Key_Old ^ Key_Val);
Key_Old = Key_Val;
//在参数界面下
if(seg_show_mode==1)
{
//一定注意上下限
if (Key_Down == 8)
{
//假定dat的取值的上限是100
dat = ( ++ dat > 100 )? 100 : dat;
}
if (Key_Down == 9)
{
//假定dat的取值下限是10
dat = ( -- dat < 10 )? 10 : dat;
}
}
}

参数保存#

参数保存考察的是修改的数据是否实时生效(题目中会明确说明)。如果是延迟生效,修改参数时原本参数不变,所以重要的是设一个变量去保存原本的参数,并且设另外一个变量去保存修改后的值。

按键长短按#

分为三种情况:

  1. 短按S4,数据+1。长按S4 1S,不用松手,执行一次LED的状态反转
  2. 短按S5,数据+1。长按S5 1S,不用松手,数据开始快速加
  3. 短按S6,数据+1。长按S6 1S以上,松手执行数据+5

按键方面代码示例:

unsigned int Time_1s_4, Time_1s_5, Time_1s_6;
bit Long_Press_4, Long_Press_5, Long_Press_6;
bit Led_Flag;
unsigned char my_data1, my_data2, my_data3;
bit Func_Run_Flag; //是否运行
void Key_Proc()
{
static unsigned char Key_Val, Key_Down, Key_Up, Key_Old;
if (Key_Slow_Down)return;
Key_Slow_Down = 1;
Key_Val = Key_Read();
Key_Down = Key_Val & (Key_Old ^ Key_Val); // 按键按下检测
Key_Up = ~Key_Val & (Key_Old ^ Key_Val); // 按键抬起检测
Key_Old = Key_Val;
//情况1
// 按下后不用抬起生效,并且只生效一次
if (Key_Down == 4)
{
Long_Press_4 = 1; // 按键4按下,开始长按检测
Func_Run_Flag=0; //程序未执行
}
if (Time_1s_4 == 1000) // 到达时间节点,应该执行一次
{
//程序没有执行过
if(Func_Run_Flag == 0)
{
LED_Flag ^= 1; //LED的状态反转
Func_Run_Flag=1; //代表程序已经执行过一次
}
}
if (Key_Up == 4) //检测是否为短按
{
if(Time_1s_4<1000)
{
my_data1++; // 短按数据+1
Long_Press_4 = 0; // 按键4抬起,长按结束
Time_1s_4 = 0; // 重置时间
}
}
//情况2
// 短按慢加,长按快加(S5按键)
if (Key_Down == 5)
Long_Press_5 = 1;
if (Time_1s_5 >= 1000)
{
if (Key_Old == 5)
my_data2++; // 长按一直+1
}
if (Key_Up == 5)
{
if (Time_1s_5 < 1000)
{
my_data2++; // 短按+1
Long_Press_5 = 0;
Time_1s_5 = 0;
}
}
//情况3
// 长按松手执行任务(S6按键,数据+5)
if (Key_Down == 6)
Long_Press_6 = 1;
if (Key_Up == 6)
{
if (Time_1s_6 >=1000)
my_data3 += 5;
else
{
my_data3++;
Long_Press_6 = 0;
Time_1s_6 = 0;
}
}
}
//定时器方面
void Timer1_Isr(void) interrupt 3
{
if (long_Press_4)
{
if (++Time_1s_4 >= 1000)
Time_1s_4 = 1001; // 卡死时间
else
Time_1s_4 = 0; // 清零
}
if (long_Press_5)
{
if (++Time_1s_5 >= 1000)
Time_1s_5 = 1001; // 卡死时间
else
Time_1s_5 = 0; // 清零
}
if (long_Press_6)
{
if (++Time_1s_6 >= 1000)
Time_1s_6 = 1001; // 卡死时间
else
Time_1s_6 = 0; // 清零
}
}

按下跳转,松手返回#

按下可以用 Key_Old 来判断,松开可以用 Key_Up 来判断。

密码门输入#

从右到左输入显示数据,高位熄灭。(需修改底层适应键盘,不能修改 temp=0!!!,建议使用100 - 109来映射键盘0 - 9以防止按键冲突)。

键盘设定:

S7=7S11=8S15=9
S6=4S10=5S14=6
S5=1S9=2S13=3
S4验证密码S8=0S12清空

验证密码错误后,清空当前输入的密码(一共输入八位)。

示例:

输入序列: 1,2,3

  • n = 3
  • Seg_Buf[7] = input_passwd[2] = 3
  • Seg_Buf[6] = input_passwd[1] = 2
  • Seg_Buf[5] = input_passwd[0] = 1
  • Seg_Buf[0]~Seg_Buf[4] = 熄灭

显示效果: _ _ _ _ _ 1 2 3

**代码示例:**
//这里要把底层的temp返回值换成相应的101~109
//注意unsigned char temp = 0;不能改,这里代表无按键按下
unsigned char current_passwd[8] = {1, 2, 3, 4, 5, 6, 7, 8}; // 系统设定的正确密码
unsigned char input_passwd[8] = {0}; // 用户输入的密码缓存
unsigned char input_passwd_index = 0; // 当前输入密码的位置索引
bit passwd_error = 0; // 密码错误标志位
// 密码键盘处理函数
void Key_Proc()
{
static unsigned char Key_Val, Key_Down, Key_Up, Key_Old; // 按键防抖动处理
if (Key_Slow_Down)
return;
Key_Slow_Down = 1;
// 读取按键值并处理按键状态
Key_Val = Key_Read();
Key_Down = Key_Val & (Key_Old ^ Key_Val); // 检测按键按下
Key_Up = ~Key_Val & (Key_Old ^ Key_Val); // 检测按键释放
Key_Old = Key_Val;
// 数字键输入处理(密码未满8位时接受输入)
if (input_passwd_index < 8)
{
// 检测数字键按下(100 - 109对应0 - 9)
if (Key_Down >= 100 && Key_Down <= 109)
{
input_passwd[input_passwd_index] = Key_Down - 100; // 转换为实际数字0 - 9
input_passwd_index++; // 索引指向下一位
}
}
// 验证密码按键处理
if (Key_Down == 4)
{
// 验证输入的密码是否正确
if (memcmp(input_passwd, current_passwd, 8) == 0)
{
// 密码正确处理
passwd_error = 0;
// 可在此添加开门等后续动作
}
else
{
// 密码错误处理
passwd_error = 1;
// 可在此添加错误提示等功能
}
// 无论正确与否,都清空当前输入
input_passwd_index = 0;
memset(input_passwd, 0, 8);
}
// 清空按键处理
if (Key_Down == 12)
{
input_passwd_index = 0; // 重置索引
memset(input_passwd, 0, 8); // 清空输入缓存
}
}
//数码管显示函数
void Seg_Proc()
{
unsigned char i;
// 显示刷新防抖处理
if (Seg_Slow_Down)
return;
Seg_Slow_Down = 1;
// 当有数字输入时进行显示处理
if (input_passwd_index > 0)
{
// 从右向左显示输入的数字(右对齐)
for (i = 0; i < input_passwd_index; i++)
{
// 数组索引映射:从最右边(7)开始,显示最近输入的数字
Seg_Buf[7 - i] = input_passwd[input_passwd_index - i - 1];
}
// 未输入的高位数码管熄灭
for (; i < 8; i++) //这里的i值继承上方代码,统一实现数码管显示
{
Seg_Buf[7 - i] = 10; // 10代表段码全灭
}
}
// 无数据输入时全部熄灭
else
{
for (i = 0; i < 8; i++)
{
Seg_Buf[i] = 10; // 所有数码管熄灭
}
}
}

双按键(十四届国赛新增考点)#

S8、S9定义“恢复出厂设置”功能。若在任意界面下,检测到S8、S9按键均处于按下状态,且状态持续时间超过2秒,则恢复到初始状态。

底层代码修改示例:

unsigned char Key_Read()
{
unsigned char temp = 0;
P44 = 0; P42 = 1; P35 = 1; P34 = 1;
if (P33 == 0) temp = 4;
if (P32 == 0) temp = 5;
if (P31 == 0) temp = 6;
if (P30 == 0) temp = 7;
P44 = 1; P42 = 0; P35 = 1; P34 = 1;
if (P33 == 0) temp = 8;
if (P32 == 0) temp = 9;
if (P31 == 0) temp = 10;
if (P30 == 0) temp = 11;
if(P33 == 0 && P32 == 0) temp = 89;
P44 = 1; P42 = 1; P35 = 0; P34 = 1;
if (P33 == 0) temp = 12;
if (P32 == 0) temp = 13;
if (P31 == 0) temp = 14;
if (P30 == 0) temp = 15;
P44 = 1; P42 = 1; P35 = 1; P34 = 0;
if (P33 == 0) temp = 16;
if (P32 == 0) temp = 17;
if (P31 == 0) temp = 18;
if (P30 == 0) temp = 19;
// 新增temp=89的判断放在最后
if (P33 == 0 && P32 == 0) temp = 89;
P3 = 0xff;
return temp;
}
//键盘函数
void Key_Proc()
{
static unsigned char Key_Val, Key_Down, Key_Up, Key_Old;
if (Key_Slow_Down)return;
Key_Slow_Down = 1;
Key_Val = Key_Read();
Key_Down = Key_Val & (Key_Old ^ Key_Val);
Key_Up = ~Key_Val & (Key_Old ^ Key_Val);
Key_Old = Key_Val;
if (Key_Old == 89) //注意这里检测Old,因为如果检测down,若按下相差10ms,会产生误判
{
// 双按键按下
}
if (Key_Up == 8)
{
// S8按键抬起处理逻辑
}
if (Key_Up == 9)
{
// S9按键抬起处理逻辑
}
}

按键多击#

这里假设规定按键按下的阈值时间为100ms,然后就可以通过计数实现按键多击功能。

以S4按键为例。

代码示例:

// 按键处理函数
void Key_Proc()
{
static unsigned char Key_Val, Key_Down, Key_Up, Key_Old;
if (Key_Slow_Down)return;
Key_Slow_Down = 1;
Key_Val = Key_Read();
Key_Down = Key_Val & (Key_Old ^ Key_Val);
Key_Up = ~Key_Val & (Key_Old ^ Key_Val);
Key_Old = Key_Val;
if (Key_Down == 4)
{
Time_100ms = 0;
Key_Click_Flag = 1;
Key_Click_Count++;
}
if (Time_100ms >= 100)
{
switch (Key_Click_Count)
{
case 1:
// 单击处理逻辑
break;
case 2:
// 双击处理逻辑
break;
case 3:
// 三击处理逻辑
break;
}
Key_Click_Count = 0; // 清零,为后面做准备
Key_Click_Flag = 0;
Time_100ms = 0;
}
}
//定时器方面
void Timer1_Isr(void) interrupt 3
{
if (Key_Click_Flag)
{
if (++Time_100ms >= 100)
Time_100ms = 101; // 按键计时增加
}
else
Time_100ms = 0; // 按键计时清零
}
按键相关功能实现和考点解析
https://mizuki.mysqil.com/posts/蓝桥杯单片机/按键相关功能实现和考点解析/
作者
风过无痕
发布于
2025-11-07
许可协议
CC BY 4.0

部分信息可能已经过时

封面
Sample Song
Sample Artist
封面
Sample Song
Sample Artist
0:00 / 0:00