Arduino 基础实践学习笔记:按键控制与LED/数码管应用
一、核心知识点总览
本笔记围绕 Arduino 两个核心实践场景展开:按键消抖与计数控制数码管、LED 闪烁(阻塞/非阻塞延时),涵盖硬件电路搭建、C语言代码逻辑、关键函数用法(如 millis()
、digitalRead()
)及实际问题解决(如按键抖动),适合 Arduino 入门学习者巩固基础操作与编程思维。
二、模块一:按键消抖与数码管显示
1. 按键为什么需要消抖?
- 抖动现象原理:机械按键按下/松开时,内部金属触点会因弹性产生 5~20ms 的高频通断抖动(非人为操作的电平波动),如下图示波器所示(标注“前沿”“后沿”为抖动区间)。
- 未消抖的危害:Arduino 检测电平速度极快(微秒级),会误将抖动识别为多次按键,导致计数错误、功能紊乱(如按1次却计数+3)。
- 消抖方案:分为 硬件消抖(并联电容)和 软件消抖(代码延时/循环检测),本项目以软件消抖为主(更灵活,无需额外元件)。
2. 硬件电路搭建(以 Tinkercad 仿真为例)
元件 |
连接方式(Arduino 引脚) |
作用 |
按键 |
一端接数字引脚12,一端接GND |
输入触发信号 |
共阳极数码管 |
8个段引脚(a~g+小数点)接2~9脚 |
显示计数结果(0~9循环) |
面包板 |
辅助元件接线,避免线路混乱 |
电路搭建载体 |
3. 代码实现与详细注释(含消抖逻辑)
3.1 基础按键计数(初始版:简单延时消抖)
// 1. 定义按键引脚(数字引脚12)
const int buttonPin = 12;
void setup() {
// 2. 初始化引脚模式:按键为输入模式(INPUT)
pinMode(buttonPin, INPUT);
// 3. 初始化串口通信(波特率9600,用于调试打印计数)
Serial.begin(9600);
}
// 4. 计数变量:记录按键按下次数(初始为0)
int valueCnt = 0;
void loop() {
// 5. 检测按键:若引脚为LOW(按键按下,因一端接GND)
if (digitalRead(buttonPin) == LOW) {
Serial.println("---------"); // 串口打印分隔线(便于查看每次按键)
valueCnt += 1; // 计数+1
Serial.println(valueCnt); // 串口打印当前计数
delay(1000); // 简单消抖:延时1秒,避免抖动误触发(缺点:阻塞其他操作)
}
}
3.2 优化版:精准软件消抖(前沿+后沿抖动过滤)
const int buttonPin = 12;
void setup() {
pinMode(buttonPin, INPUT);
Serial.begin(9600);
}
int valueCnt = 0;
void loop() {
// 检测按键按下(初始低电平触发)
if (digitalRead(buttonPin) == LOW) {
// 1. 过滤前沿抖动:延时20ms(跳过按下瞬间的不稳定电平)
delay(20);
// 2. 等待按键松开:若按键仍按下(LOW),则死循环阻塞,直到松开(变为HIGH)
while (digitalRead(buttonPin) == LOW) {}
// 3. 过滤后沿抖动:延时20ms(跳过松开瞬间的不稳定电平)
delay(20);
// 4. 按键稳定后执行计数与打印
Serial.println("---------");
valueCnt += 1;
Serial.println(valueCnt);
}
}
3.3 进阶版:按键控制数码管显示(0~9循环)
// 1. 定义硬件引脚
const int buttonPin = 12; // 按键引脚
const int pinArray[] = {2, 3, 4, 5, 6, 7, 8, 9}; // 数码管8个段引脚(a~g+小数点)
// 2. 数码管段码表(共阳极:1=段灭,0=段亮;索引0~9对应数字0~9,10=小数点,11~12=预留)
const int disNum[20][8] = {
{1,1,1,1,1,1,0,0},// 0:a~f亮,g灭,小数点灭
{0,1,1,0,0,0,0,0},// 1:b~c亮,其他灭
{1,1,0,1,1,0,1,0},// 2:a~b,g,e~d亮
{1,1,1,1,0,0,1,0},// 3:a~b,g,c~d亮
{0,1,1,0,0,1,1,0},// 4:f~g,b~c亮
{1,0,1,1,0,1,1,0},// 5:a,f,g,c~d亮
{1,0,1,1,1,1,1,0},// 6:a,f,g,c~d,e亮
{1,1,1,0,0,0,0,0},// 7:a~c亮
{1,1,1,1,1,1,1,0},// 8:全亮(除小数点)
{1,1,1,1,0,1,1,0},// 9:a~b,g,c~d,f亮
{0,0,0,0,0,0,0,1},// .:仅小数点亮
{1,1,1,1,1,1,0,0},// 预留(显示0)
{0,0,0,0,0,0,0,0},// 预留(全灭)
};
void setup() {
// 3. 初始化按键引脚为输入
pinMode(buttonPin, INPUT);
// 4. 初始化串口(波特率9600)
Serial.begin(9600);
// 5. 初始化数码管所有段引脚为输出模式,并打印引脚号(调试用)
for (int i = 0; i < 8; i++) {
pinMode(pinArray[i], OUTPUT);
Serial.println(String(pinArray[i]) + " "); // 串口打印当前初始化的引脚
}
// 6. 初始显示数字0(开机默认状态)
disPlayNum(0, 0);
}
// 7. 数码管显示函数:参数1=要显示的数字(0~9),参数2=显示延迟时间(ms)
void disPlayNum(int number, int delayTime) {
for (int i = 0; i <= 7; i++) {
// 取反操作:因数码管是共阳极,段码表1=灭,需转为LOW(低电平)才点亮
digitalWrite(pinArray[i], !disNum[number][i]);
}
delay(delayTime); // 保持显示状态(0=持续显示,直到下次更新)
}
// 8. 计数变量(0~9循环)
int valueCnt = 0;
void loop() {
// 9. 检测按键按下(低电平触发)
if (digitalRead(buttonPin) == LOW) {
Serial.println("---------"); // 串口分隔线
valueCnt += 1; // 计数+1
Serial.println(valueCnt); // 打印当前计数
// 10. 消抖逻辑:过滤前沿+后沿抖动
delay(20); // 前沿抖动过滤
while (digitalRead(buttonPin) == LOW) {} // 等待按键松开
delay(20); // 后沿抖动过滤
// 11. 计数循环:达到10时重置为0(实现0~9循环)
if (valueCnt == 10) valueCnt = 0;
// 12. 控制数码管显示当前计数
disPlayNum(valueCnt, 0);
}
}
三、模块二:LED 闪烁控制(阻塞 vs 非阻塞延时)
1. 基础概念:delay()
与 millis()
的区别
函数 |
作用 |
特点(阻塞/非阻塞) |
适用场景 |
delay(ms) |
让程序暂停指定毫秒数 |
阻塞:暂停期间无法执行其他代码 |
简单场景(如固定间隔闪烁) |
millis() |
返回 Arduino 开机到当前的毫秒数(无符号长整型) |
非阻塞:仅获取时间,不暂停程序 |
多任务场景(如同时闪烁LED+检测按键) |
2. 场景1:阻塞延时 LED 闪烁(delay()
实现)
2.1 硬件电路
- LED 正极通过 220Ω 电阻接数字引脚3,负极接 GND(电阻用于限流,保护LED和引脚)。
2.2 代码与注释
void setup() {
// 初始化引脚3为输出模式(控制LED亮灭)
pinMode(3, OUTPUT);
}
void loop() {
digitalWrite(3, HIGH); // 引脚3输出高电平,LED点亮
delay(2000); // 阻塞2秒(期间无法执行其他操作)
digitalWrite(3, LOW); // 引脚3输出低电平,LED熄灭
delay(2000); // 再阻塞2秒,实现“亮2秒灭2秒”循环
}
3. 场景2:非阻塞延时 LED 闪烁(millis()
实现)
3.1 核心逻辑
- 记录上一次 LED 状态改变的时间(
oldTime
),每次 loop()
循环时,用当前时间(millis()
)减去 oldTime
,若差值≥目标间隔(如1000ms),则切换 LED 状态并更新 oldTime
,避免程序阻塞。
3.2 代码与注释
void setup() {
// 1. 初始化LED引脚(3号)为输出
pinMode(3, OUTPUT);
// 2. 初始化串口(波特率9600,用于打印LED状态,调试用)
Serial.begin(9600);
}
// 3. 变量定义
int oldTime = 0; // 存储上一次LED状态改变的时间(初始为0)
bool flag = false; // LED状态标记:false=灭,true=亮
void loop() {
// 4. 非阻塞延时判断:当前时间 - 上一次状态时间 ≥ 1000ms(1秒间隔)
if (millis() - oldTime >= 1000) {
flag = !flag; // 切换LED状态(灭→亮,亮→灭)
oldTime = millis(); // 更新“上一次状态时间”为当前时间
Serial.println(flag); // 串口打印当前状态(true=亮,false=灭,调试用)
}
// 5. 根据状态标记控制LED
if (flag == true) {
digitalWrite(3, HIGH); // 亮
} else {
digitalWrite(3, LOW); // 灭
}
// (此处可添加其他代码,如按键检测,不会被延时阻塞)
}
四、常见问题与解决方案
问题现象 |
可能原因 |
解决方案 |
按键按1次计数多次 |
未消抖,金属触点抖动 |
添加软件消抖(delay(20)+while循环 )或硬件消抖(并联104电容) |
数码管显示乱码 |
段引脚接线与 pinArray 定义不匹配;段码表与数码管类型(共阴/共阳)不匹配 |
核对接线;共阴极数码管删除 digitalWrite 中的取反(! ) |
millis() 计时不准 |
变量类型错误(用 int 而非 unsigned long ) |
将 oldTime 定义为 unsigned long oldTime = 0; (millis() 返回值范围超 int ) |
五、学习总结
- 按键消抖是机械按键应用的必做步骤,软件消抖(延时+循环检测)更灵活,需理解“前沿/后沿抖动”的过滤逻辑。
- 数码管显示核心是“段码表”,需根据共阴/共阳类型调整电平(取反或不取反),引脚定义需与硬件接线严格对应。
- 延时选择:简单场景用
delay()
,多任务场景必须用 millis()
实现非阻塞延时,避免程序“卡死”。
- 调试技巧:善用
Serial.begin()
和 Serial.println()
打印变量(如计数、时间、状态),通过串口监视器排查问题。