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

五、学习总结

  1. 按键消抖是机械按键应用的必做步骤,软件消抖(延时+循环检测)更灵活,需理解“前沿/后沿抖动”的过滤逻辑。
  2. 数码管显示核心是“段码表”,需根据共阴/共阳类型调整电平(取反或不取反),引脚定义需与硬件接线严格对应。
  3. 延时选择:简单场景用 delay(),多任务场景必须用 millis() 实现非阻塞延时,避免程序“卡死”。
  4. 调试技巧:善用 Serial.begin()Serial.println() 打印变量(如计数、时间、状态),通过串口监视器排查问题。

0 条评论

目前还没有评论...