按键底层
基础按键底层
特殊情况(独立按键)
较简单的内容,这里没有记录。
特殊情况(串口使用)
串口通信使用了P30和P31引脚,而这两个引脚同时也被用于矩阵键盘的按键扫描。为避免冲突,需要特别注意以下约束:
-
只能使用列扫描方式:由于P30和P31已被串口占用,矩阵键盘必须采用列扫描方式而非行扫描方式。
-
避免对P30/P31手动赋值:在键盘扫描过程中,不能对P30和P31直接进行赋值操作,否则会导致串口发送异常数据。
-
扫描与通信时序分离:在进行按键处理时,应暂停串口使用的定时器(在模板中统一为定时器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; } }}参数保存
参数保存考察的是修改的数据是否实时生效(题目中会明确说明)。如果是延迟生效,修改参数时原本参数不变,所以重要的是设一个变量去保存原本的参数,并且设另外一个变量去保存修改后的值。
按键长短按
分为三种情况:
- 短按S4,数据+1。长按S4 1S,不用松手,执行一次LED的状态反转
- 短按S5,数据+1。长按S5 1S,不用松手,数据开始快速加
- 短按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=7 | S11=8 | S15=9 |
|---|---|---|
| S6=4 | S10=5 | S14=6 |
| S5=1 | S9=2 | S13=3 |
| S4验证密码 | S8=0 | S12清空 |
验证密码错误后,清空当前输入的密码(一共输入八位)。
示例:
输入序列: 1,2,3
- n = 3
Seg_Buf[7] = input_passwd[2] = 3Seg_Buf[6] = input_passwd[1] = 2Seg_Buf[5] = input_passwd[0] = 1Seg_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; // 按键计时清零}部分信息可能已经过时














