(1)实验平台:普中51单片机开发板-A2&A3&A4
前几章介绍的都是 IO 口输出的使用, 这一章我们通过独立按键实验来介绍IO 口作为输入的使用。 开发板上板载 4 个独立按键。 本章所要实现的功能是:通过开发板上的独立按键 K1 控制 D1 指示灯亮灭。 学习本章可以参考前面的实验章节内容。 本章分为如下几部分内容:
[13.1 按键介绍](#13.1 按键介绍)
[13.2 硬件设计](#13.2 硬件设计)
[13.3 软件设计](#13.3 软件设计)
[13.4 实验现象](#13.4 实验现象)
13.1 按键介绍
按键是一种电子开关, 使用时轻轻按开关按钮就可使开关接通, 当松开手时,开关断开。 开发板上使用的按键及内部简易图如下图所示:

按键管脚两端距离长的表示默认是导通状态, 距离短的默认是断开状态, 如果按键按下, 初始导通状态变为断开, 初始断开状态变为导通。
通常的按键所用开关为机械弹性开关,当机械触点断开、 闭合时, 电压信号如下图所示:

由于机械点的弹性作用, 按键开关在闭合时不会马上稳定的接通, 在断开时也不会一下子断开, 因而在闭合和断开的瞬间均伴随着一连串的抖动。 抖动时间的长短由按键的机械特性决定的, 一般为 5ms 到 10ms。 按键稳定闭合时间的长短则由操作人员的按键动作决定的, 一般为零点几秒至数秒。 按键抖动会引起按键被误读多次。 为了确保 CPU 对按键的一次闭合仅作一次处理, 必须进行消抖。
按键消抖有两种方式, 一种是硬件消抖, 另一种是软件消抖。 为了使电路更加简单, 通常采用软件消抖。 我们开发板也是采用软件消抖, 一般来说一个简单的按键消抖就是先读取按键的状态, 如果得到按键按下之后, 延时 10ms, 再次读取按键的状态, 如果按键还是按下状态, 那么说明按键已经按下。 其中延时10ms 就是软件消抖处理, 至于硬件消抖, 大家可以百度了解下, 网上都有非常详细的介绍。 这里给大家列出单片机常用的软件去抖动方法:
1, 先设置 IO 口为高电平(由于开发板 IO 都有上拉电阻, 所以默认 IO 为高电平) 。
2, 读取 IO 口电平确认是否有按键按下。
3, 如有 IO 电平为低电平后, 延时几个毫秒。
4, 再读取该 IO 电平, 如果仍然为低电平, 说明按键按下。
5, 执行按键控制程序。
独立按键电路构成是由各个按键的一个管脚连接在一起接地, 按键其他引脚分别接到单片机 IO 口。
我们知道单片机的 IO 口既可作为输出也可作为输入使用, 当检测按键时用的是它的输入功能, 独立按键的一端接地, 另一端与单片机的 I/O 口相连, 开始时先给该 IO 口赋一高电平, 然后让单片机不断地检测该 I/O 口是否变为低电平, 当按键闭合时, 即相当于该 I/O 口通过按键与地相连, 变成低电平, 程序一旦检测到 I/O 口变为低电平则说明按键被按下, 然后执行相应的指令。
13.2 硬件设计
本实验使用到硬件资源如下:
(1) LED 模块中 D1 指示灯
(2) K1 按键
LED 模块电路在前面章节都介绍过, 这里就不多说, 开发板上的独立按键模块电路如下图所示:

从上图中可以看出, 4 个独立按键的控制管脚连接到 51 单片机的 P3.0-P3.3脚上。 其中 K1 连接在 P3.1 上, K2 连接在 P3.0 上, K3 连接在 P3.2 上, K4 连接在 P3.3 上。 4 个按键另一端全部连接在 GND, 当按键按下后, 对应 IO 口即为低电平。
13.3 软件设计
本章所要实现的功能是: 通过开发板上的独立按键 K1 控制 D1 指示灯亮灭。
我们打开"\4--实验程序\1--基础实验\8-独立按键实验" 工程, 控制代码全部都在 main.c 中, 代码如下:
cpp
/**************************************************************************************
深圳市普中科技有限公司(PRECHIN 普中)
技术支持:www.prechin.net
PRECHIN
普中
实验名称:独立按键实验
接线说明:
实验现象:下载程序后,按下"独立按键"模块中K1键,控制D1指示灯亮灭
注意事项:
***************************************************************************************/
#include "reg52.h"
typedef unsigned int u16; //对系统默认数据类型进行重定义
typedef unsigned char u8;
//定义独立按键控制脚
sbit KEY1=P3^1;
sbit KEY2=P3^0;
sbit KEY3=P3^2;
sbit KEY4=P3^3;
//定义LED1控制脚
sbit LED1=P2^0;
//使用宏定义独立按键按下的键值
#define KEY1_PRESS 1
#define KEY2_PRESS 2
#define KEY3_PRESS 3
#define KEY4_PRESS 4
#define KEY_UNPRESS 0
/*******************************************************************************
* 函 数 名 : delay_10us
* 函数功能 : 延时函数,ten_us=1时,大约延时10us
* 输 入 : ten_us
* 输 出 : 无
*******************************************************************************/
void delay_10us(u16 ten_us)
{
while(ten_us--);
}
/*******************************************************************************
* 函 数 名 : key_scan
* 函数功能 : 检测独立按键是否按下,按下则返回对应键值
* 输 入 : mode=0:单次扫描按键
mode=1:连续扫描按键
* 输 出 : KEY1_PRESS:K1按下
KEY2_PRESS:K2按下
KEY3_PRESS:K3按下
KEY4_PRESS:K4按下
KEY_UNPRESS:未有按键按下
*******************************************************************************/
u8 key_scan(u8 mode)
{
static u8 key=1;
if(mode)key=1;//连续扫描按键
if(key==1&&(KEY1==0||KEY2==0||KEY3==0||KEY4==0))//任意按键按下
{
delay_10us(1000);//消抖
key=0;
if(KEY1==0)
return KEY1_PRESS;
else if(KEY2==0)
return KEY2_PRESS;
else if(KEY3==0)
return KEY3_PRESS;
else if(KEY4==0)
return KEY4_PRESS;
}
else if(KEY1==1&&KEY2==1&&KEY3==1&&KEY4==1) //无按键按下
{
key=1;
}
return KEY_UNPRESS;
}
/*******************************************************************************
* 函 数 名 : main
* 函数功能 : 主函数
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void main()
{
u8 key=0;
while(1)
{
key=key_scan(0);
if(key==KEY1_PRESS)//检测按键K1是否按下
LED1=!LED1;//LED1状态翻转
}
}
key_scan 函数带一个形参 mode, 该参数用来设定是否连续扫描按键, 如果mode 为 0, 只能操作一次按键, 只有当按键松开后才能触发下次的扫描, 这样做的好处是可以防止按下一次出现多次触发的情况。 如果 mode 为 1, 函数是支持连续扫描的, 即使按键未松开, 在函数内部有 if(mode==1)这条判断语句, 因此 key 始终是等于 1 的, 所以可以连续扫描按键, 当按下某个按键, 会一直返回这 个按键的键值, 这样做的好处是可以很方便实现连按操作。 函数内的delay_10us(1000)即为软件消抖处理, 通常延时 10ms 即可。
key_scan 函数还带有一个返回值, 如果未有按键按下, 返回值即为KEY_UNPRESS, 否则返回值即为对应按键的键值, 如 KEY1_PRESS、 KEY2_PRESS、KEY3_PRESS、 KEY4_PRESS, 这都是程序开头定义好的宏, 方便大家理解和使用。函数内定义了一个 static 变量 key, 相当于全局变量, 所以该函数不是一个可重入函数。 还有一点要注意的就是该函数按键的扫描是有优先级的, 因为函数内用了 if...else if...else 格式, 所以最先扫描处理的按键是 KEY1, 其次是KEY2, 然后是 KEY3, 最后是 KEY4。 如果需要将其优先级设置一样, 那么可以全部用 if 语句。
main 函数中主要就是调用 key_scan 函数用于检测按键, 此时传入的 mode值为 0, 表示单次扫描按键, 然后将扫描按键的值保存在变量 key 中, 最后通过if 判断语句控制 LED1 状态。
13.4 实验现象
使用 USB 线将开发板和电脑连接成功后(电脑能识别开发板上 CH340 串口) ,把编译后产生的.hex 文件烧入到芯片内, 实现现象如下: 当按下 K1 键, D1 指示灯亮, 再按下 K1 键, D1 指示灯灭, 如此循环。