Ai8051 独立按键控制LED实验
一、实验目标
- 用板载 K1~K4 独立按键,控制对应 LED1~LED4 亮灭
- 掌握:按键消抖、按键扫描、IO口配置、模块化编程
二、硬件电路(原理图)

- 按键:P3.2、P3.3、P3.4、P3.5(低电平有效)
- LED:P2.0、P2.1、P2.2、P2.3
- 按键按下 → IO口为 低电平(0)
三、AiCube 配置步骤
-
开启系统时钟
- 主时钟:内部高速 IRC
- 系统时钟:40MHz

-
图形化IO配置
- 按键端口(P3.2~P3.5):设为 上拉输入
- LED端口(P2.0~P2.3):设为 推挽输出


-
勾选启用 P2、P3 端口

四、完整代码(逐行注释)
1. key.h(按键头文件)
c
#ifndef _KEY_H // 防止头文件重复包含(条件编译)
#define _KEY_H
#include "config.h" // 包含系统配置头文件(Ai8051自动生成,含IO/时钟定义)
// 按键引脚定义(根据实际硬件修改,此处为示例:K1~K4对应P3.2~P3.5)
#define KEY1 P3_2 // 定义KEY1为P3.2引脚
#define KEY2 P3_3 // 定义KEY2为P3.3引脚
#define KEY3 P3_4 // 定义KEY3为P3.4引脚
#define KEY4 P3_5 // 定义KEY4为P3.5引脚
// 按键返回值宏定义(增强代码可读性,避免魔法数字)
#define KEY1_PRESS 1 // KEY1按下返回1
#define KEY2_PRESS 2 // KEY2按下返回2
#define KEY3_PRESS 3 // KEY3按下返回3
#define KEY4_PRESS 4 // KEY4按下返回4
// 按键扫描函数声明(mode参数:0=单次扫描,1=连续扫描,此处暂用0)
u8 KEY_Scan(u8 mode);
#endif // 结束条件编译
2. key.c(按键驱动)
c
#include "key.h"
/**
* @brief 按键扫描函数(修复版:单次触发,双次消抖+等待松开)
* @param mode:0=单次扫描(按一次只触发一次),1=连续扫描
* @retval 按键值:1=KEY1,2=KEY2,3=KEY3,4=KEY4,0=无按键
* @note 增加"等待松开"+"二次消抖",解决多次按才生效的问题
*/
u8 KEY_Scan(u8 mode)
{
static u8 key_flag = 1; // 按键状态标记(1=未按下,0=已按下)
u8 key_val = 0; // 临时存储按键值
// 只有按键未按下时,才检测新的按键(防止连触发)
if(key_flag == 1)
{
// 第一步:检测是否有按键按下(低电平有效)
if(!KEY1 || !KEY2 || !KEY3 || !KEY4)
{
delay_ms(10); // 第一次消抖:过滤按下时的机械抖动
// 第二步:二次确认按键确实按下(避免误触发)
if(!KEY1) key_val = KEY1_PRESS;
else if(!KEY2) key_val = KEY2_PRESS;
else if(!KEY3) key_val = KEY3_PRESS;
else if(!KEY4) key_val = KEY4_PRESS;
// 第三步:如果检测到有效按键,等待松开+松开消抖
if(key_val != 0)
{
key_flag = 0; // 标记按键已按下,防止重复检测
// 等待按键松开(核心!解决"多次按才生效")
while(!KEY1 || !KEY2 || !KEY3 || !KEY4);
delay_ms(10); // 第二次消抖:过滤松开时的机械抖动
return key_val; // 返回有效按键值
}
}
}
// 第四步:所有按键都松开后,重置状态
else if(KEY1 && KEY2 && KEY3 && KEY4)
{
key_flag = 1; // 重置标记,允许下次检测
}
return 0; // 无按键按下,返回0
}
3. led.h(LED头文件)
c
#ifndef _LED_H // 防止头文件重复包含
#define _LED_H
#include "config.h" // 包含系统配置头文件
// LED引脚定义(根据实际硬件修改,此处为示例:LED1~LED4对应P2.0~P2.3)
#define LED1 P2_0 // 定义LED1为P2.0引脚
#define LED2 P2_1 // 定义LED2为P2.1引脚
#define LED3 P2_2 // 定义LED3为P2.2引脚
#define LED4 P2_3 // 定义LED4为P2.3引脚
#endif // 结束条件编译
4. main.c(主函数)
c
/*******************************************
* 头文件包含
*******************************************/
#include "config.h" // 包含系统配置头文件(由AiCube生成,含时钟/IO初始化)
#include "key.h" // 包含按键驱动头文件(定义按键引脚和扫描函数)
#include "led.h" // 包含LED驱动头文件(定义LED引脚)
/*******************************************
* 项目主函数
* 入口参数:无
* 函数返回:无
* 作用:程序入口,死循环执行按键扫描和LED控制
*******************************************/
void main(void)
{
u8 key_val = 0; // 定义变量存储按键返回值(0=无按键,1~4=对应KEY1~KEY4)
SYS_Init(); // 系统初始化函数(配置时钟、IO口模式等,必须调用)
while(1) // 死循环(单片机程序核心,一直运行)
{
// ========== 按键扫描逻辑 ==========
// 调用按键扫描函数,mode=0 表示【单次扫描】
// 效果:按一次按键只触发一次,必须松开后才能再次触发
key_val = KEY_Scan(0);
// ========== LED控制逻辑 ==========
// 根据key_val的值,执行对应的LED动作
switch(key_val)
{
case KEY1_PRESS: // 如果检测到KEY1按下
LED1 = !LED1; // LED1状态翻转(灭变亮,亮变灭)
break; // 跳出switch,防止case穿透
case KEY2_PRESS: // 如果检测到KEY2按下
LED2 = !LED2; // LED2状态翻转
break;
case KEY3_PRESS: // 如果检测到KEY3按下
LED3 = !LED3; // LED3状态翻转
break;
case KEY4_PRESS: // 如果检测到KEY4按下
LED4 = !LED4; // LED4状态翻转
break;
default: // 默认分支(无按键按下时,不执行操作)
break;
}
// 主循环加短延时(可选)
// 作用:降低CPU占用,避免程序跑飞,不影响按键检测灵敏度
delay_ms(5);
}
}
五、关键注释说明
- 静态变量
key_flag:
static u8 key_flag = 1;是单次扫描的核心,静态变量只会初始化一次,按键按下时设为0,松开后重置为1,避免"按一次触发多次"。 - 消抖延时
delay_ms(10):
机械按键按下/松开时会有5~10ms的电平抖动,必须加延时过滤,否则会误判"没按键"或"多次按键"。 !LED1翻转逻辑 :
LED1 = !LED1等价于LED1 = (LED1 == 0) ? 1 : 0,实现"按一下亮、再按灭"的效果。