蓝桥杯入门培训(2):按键控制入门
一、认识按键:单片机的“感受器官”
如果把单片机(微控制器)看作一个机器人的大脑,那么LED就像是它的嘴巴——用来对外表达(输出信息),而按键就像是它的耳朵和触觉——用来感知外界的操作(输入信息)。
与LED不同,按键操作是单片机读取外部状态,而不是写入数据。
硬件原理简析
以常见的单片机为例,按键通常这样连接:
-
按键一端接地(GND,0V),另一端连接到单片机的引脚
-
当按键按下时,引脚直接连接到地,电压变为0(低电平)
-
当按键松开时,引脚通过上拉电阻连接到电源,电压为1(高电平)
简单比喻:想象P3口是一个有8个孔的水管(P3_0到P3_7),按键像一个个水坝开关。当按下按键时,对应水管被打开,水流(电流)通向地面,我们就能检测到"有水流出"(低电平0);松开时水管关闭,我们就检测到"没有水流"(高电平1)。
二、独立按键读取
基础按键读取函数
/* 按键读取函数 */
unsigned char Key_Read()
{
unsigned char temp = 0;
if(P3_4 == 0) temp = 1; // 如果P3_4引脚为0(低电平),说明按键1按下
if(P3_5 == 0) temp = 2; // 如果P3_5引脚为0(低电平),说明按键2按下
if(P3_6 == 0) temp = 3; // 如果P3_6引脚为0(低电平),说明按键3按下
if(P3_7 == 0) temp = 4; // 如果P3_7引脚为0(低电平),说明按键4按下
return temp; // 返回按键编号,如果没有按键按下则返回0
}
工作方式:
-
没有按键按下时,所有引脚都是1(高电平),函数返回0
-
有按键按下时,对应引脚变为0(低电平),函数返回对应的按键编号
三、高级技巧:按键边沿检测
在实际应用中,我们经常需要检测按键的按下瞬间和松开瞬间,而不仅仅是按键是否被按住。这就需要用到"边沿检测"技术。
核心变量解释
/* 变量声明区域 */
unsigned char Key_Val; // 当前时刻读取的按键值(0-4)
unsigned char Key_Down; // 按下瞬间的按键(只有按下那一刻不为0)
unsigned char Key_Up; // 松开瞬间的按键(只有松开那一刻不为0)
unsigned char Key_Old; // 上一次的按键值(用于比较)
生动比喻:
-
Key_Val:现在谁在敲门?(1-4表示不同人,0表示没人) -
Key_Old:刚才谁在敲门?(记住上一次的状态) -
Key_Down:刚刚开始敲门的那一刻(门铃响起) -
Key_Up:敲门声刚刚停止的那一刻(门铃停止)
边沿检测算法
void main()
{
while(1) // 单片机主循环,不断重复执行
{
Key_Val = Key_Read(); // 读取当前的按键状态
// 魔法公式分解:
// 1. Key_Val ^ Key_Old:找出状态变化的部分(异或运算)
// 2. Key_Val & (变化部分):只有在当前为1,之前为0时,结果才为1(下降沿检测)
// 3. ~Key_Val & (变化部分):只有在当前为0,之前为1时,结果才为1(上升沿检测)
Key_Down = Key_Val & (Key_Val ^ Key_Old); // 检测下降沿(按下瞬间)
Key_Up = ~Key_Val & (Key_Val ^ Key_Old); // 检测上升沿(松开瞬间)
Key_Old = Key_Val; // 更新"上一次"的状态,为下一次比较做准备
}
}
真值表理解(以按键1为例):
| Key_Old | Key_Val | Key_Down | Key_Up | 说明 |
|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 持续没有按下 |
| 0 | 1 | 1 | 0 | 刚刚按下(下降沿) |
| 1 | 1 | 0 | 0 | 持续按住 |
| 1 | 0 | 0 | 1 | 刚刚松开(上升沿) |
四、应用实例:不同按键触发方式
要求实现:
-
按键1按下瞬间,灯0点亮
-
按键2松开瞬间,灯0熄灭
-
按键3按住期间,灯2点亮;松开则熄灭
// 在主循环的边沿检测代码之后添加:
if(Key_Down == 1) // 按键1按下瞬间
P1_0 = 0; // 灯0点亮(假设低电平点亮)
if(Key_Up == 2) // 按键2松开瞬间
P1_0 = 1; // 灯0熄灭
if(Key_Old == 3) // 按键3持续按住(Key_Old保存着持续状态)
P1_1 = 0; // 灯2点亮
else
P1_1 = 1; // 灯2熄灭
实际应用场景:
-
Key_Down:用于触发一次性动作,如计数器加1、切换模式 -
Key_Up:用于确认动作,如长按后的确认释放 -
Key_Old:用于检测持续状态,如长按加速、连续发射
五、标志位:状态记忆的"开关"
当某种事物只有两种状态时,我们可以使用bit(位)类型的标志位。
bit led_state = 0; // 定义一个位变量,只有0或1两种值
// 使用标志位控制LED状态切换
if(Key_Down == 1)
{
led_state = !led_state; // 取反:0变1,1变0
P1_0 = led_state; // 根据标志位控制LED
}
比喻:标志位就像电灯开关,只有"开"和"关"两种状态,按一下切换一次。
六、矩阵键盘:节省引脚的设计
独立按键每个按键占用一个引脚,当需要多个按键时,引脚不够用。矩阵键盘通过行列交叉的方式,用较少的引脚控制更多的按键。
4×4矩阵键盘原理
工作方式(扫描法):
-
选行:将某一行的引脚设置为0(低电平),其他行设置为1(高电平)
-
查列:读取所有列引脚的状态
-
判断:如果某列为0,说明该行该列的交叉点按键被按下
-
轮询:按顺序切换选中的行,实现所有按键的检测
生动比喻:
想象一个4层楼(4行)每层4个房间(4列)的酒店:
-
你首先站在1楼大喊(设置P3_0=0)
-
听每个房间是否有人回应(检查P3_4到P3_7)
-
然后去2楼、3楼、4楼重复这个过程
-
通过"楼层+房间号"就能确定是哪个按键被按下
矩阵键盘读取函数
/* 4×4矩阵键盘读取函数 */
unsigned char Key_Read()
{
unsigned char temp = 0;
// 第一行扫描(P3_0=0,选中第一行)
P3_0 = 0; P3_1 = 1; P3_2 = 1; P3_3 = 1;
if(P3_4 == 0) temp = 1; // 第1行第1列 -> 按键1
if(P3_5 == 0) temp = 2; // 第1行第2列 -> 按键2
if(P3_6 == 0) temp = 3; // 第1行第3列 -> 按键3
if(P3_7 == 0) temp = 4; // 第1行第4列 -> 按键4
// 第二行扫描(P3_1=0,选中第二行)
P3_0 = 1; P3_1 = 0; P3_2 = 1; P3_3 = 1;
if(P3_4 == 0) temp = 5; // 第2行第1列 -> 按键5
if(P3_5 == 0) temp = 6; // 第2行第2列 -> 按键6
if(P3_6 == 0) temp = 7; // 第2行第3列 -> 按键7
if(P3_7 == 0) temp = 8; // 第2行第4列 -> 按键8
// 第三行扫描(P3_2=0,选中第三行)
P3_0 = 1; P3_1 = 1; P3_2 = 0; P3_3 = 1;
if(P3_4 == 0) temp = 9; // 第3行第1列 -> 按键9
if(P3_5 == 0) temp = 10; // 第3行第2列 -> 按键10
if(P3_6 == 0) temp = 11; // 第3行第3列 -> 按键11
if(P3_7 == 0) temp = 12; // 第3行第4列 -> 按键12
// 第四行扫描(P3_3=0,选中第四行)
P3_0 = 1; P3_1 = 1; P3_2 = 1; P3_3 = 0;
if(P3_4 == 0) temp = 13; // 第4行第1列 -> 按键13
if(P3_5 == 0) temp = 14; // 第4行第2列 -> 按键14
if(P3_6 == 0) temp = 15; // 第4行第3列 -> 按键15
if(P3_7 == 0) temp = 16; // 第4行第4列 -> 按键16
// 恢复所有行为高电平(释放所有行)
P3_0 = 1; P3_1 = 1; P3_2 = 1; P3_3 = 1;
return temp; // 返回按键编号(1-16)
}
七、实际编程注意事项
1. 按键消抖
实际按键按下时会有机械抖动,可能被误判为多次按下:
// 简单的软件消抖延时
if(Key_Val != 0) // 检测到按键按下
{
Delay_ms(10); // 延时10ms跳过抖动期
if(Key_Val == Key_Read()) // 再次确认按键状态
{
// 确认按键真的按下了
}
}
2. 扫描频率
-
按键扫描需要放在主循环中不断执行
-
扫描频率不能太低(否则会漏检),也不能太高(浪费CPU资源)
-
通常10-50ms扫描一次比较合适
3. 矩阵键盘的优化
上面的示例代码是基础版本,实际应用中可以优化:
-
使用循环简化代码
-
添加消抖处理
-
处理多个按键同时按下的情况
总结
按键是单片机与用户交互的重要方式,掌握按键编程是嵌入式开发的基础:
-
独立按键:简单直接,适合按键数量少的场景
-
边沿检测:区分按下瞬间、持续按住、松开瞬间,实现更精细的控制
-
矩阵键盘:用较少的引脚控制更多的按键,适合复杂输入需求
-
标志位:管理二进制状态,简化程序逻辑