ESP32 for Arduino 课程学习笔记:按键消抖与数码管控制

一、按键抖动原理及消抖方法

1.1 按键抖动的产生原因

按键内部由金属簧片构成,当按下或松开按键时,金属簧片会产生物理振动,导致电路在10-20ms内出现多次通断现象,这种现象称为"按键抖动"。

  • 理想状态:按键按下时信号瞬间从HIGH(1)变为LOW(0),松开时瞬间从LOW(0)变为HIGH(1)
  • 现实状态:按下和松开时都会产生10-20ms的抖动,之后才进入稳定状态

1.2 按键抖动的波形对比

状态 特征
理想波形 信号突变,无中间状态
现实波形 包含前沿抖动(10-20ms)、稳定状态、后沿抖动(10-20ms)

1.3 按键消抖的必要性

如果不进行消抖处理,控制器会误判按键被多次按下,导致程序执行错误(如LED频繁闪烁、计数错误等)。

二、按键消抖实现方法

2.1 基础检测代码(无消抖)

void setup() {
  pinMode(3, INPUT);       // 按键连接引脚3
  pinMode(4, OUTPUT);      // LED连接引脚4
  digitalWrite(4, 0);      // 初始关闭LED
  Serial.begin(115200);    // 初始化串口通信
}

boolean buttonState = false;

void loop() {
  // 直接读取按键状态,无消抖处理
  Serial.println(digitalRead(3));  // 串口输出按键状态
  delay(20);  // 短暂延时
}

2.2 简单消抖代码(延时法)

void setup() {
  pinMode(3, INPUT);       // 按键连接引脚3
  pinMode(4, OUTPUT);      // LED连接引脚4
  digitalWrite(4, 0);      // 初始关闭LED
  Serial.begin(115200);    // 初始化串口通信
}

boolean buttonState = false;

void loop() {
  // 当按键被按下(读取到LOW)
  if(digitalRead(3) == LOW) {
    buttonState = !buttonState;  // 切换状态(取反)
    Serial.println(buttonState); // 输出当前状态
    digitalWrite(4, buttonState); // 控制LED
    delay(1000);  // 延时跳过抖动区域(关键消抖步骤)
  }
  delay(20);
}

2.3 消抖原理说明

延时法消抖的核心思想是:当检测到按键状态变化后,延时一段时间(通常大于20ms),跳过抖动阶段,确保读取的是稳定后的状态。

// 关键消抖代码解析
if(digitalRead(3) == LOW) {  // 检测到按键按下
  buttonState = !buttonState;  // 切换状态
  delay(1000);  // 延时1秒,覆盖抖动时间(实际应用中20-50ms即可)
}

三、四位数码管控制

3.1 数码管控制原理

四位数码管通过"位选"和"段选"信号控制显示:

  • 位选:选择哪一位数码管点亮
  • 段选:控制数码管显示的数字

通常使用74HC595移位寄存器扩展IO口,通过shiftOut()函数输出控制信号。

3.2 shiftOut()函数详解

// 函数原型
shiftOut(dataPin, clockPin, bitOrder, value);

参数说明:

  • dataPin:数据输出引脚(连接74HC595的DS引脚)
  • clockPin:时钟引脚(连接74HC595的SH_CP引脚)
  • bitOrder:位输出顺序(MSBFIRST高位优先,LSBFIRST低位优先)
  • value:要输出的字节数据

3.3 数码管显示单个数字

// 定义引脚
#define dataPin 2  // 数据引脚
#define clockPin 3 // 时钟引脚
#define latchPin 4 // 锁存引脚

// 数字段码表(共阴极为例)
byte disNum[10] = {
  0b00111111, // 0
  0b00000110, // 1
  0b01011011, // 2
  0b01001111, // 3
  0b01100110, // 4
  0b01101101, // 5
  0b01111101, // 6
  0b00000111, // 7
  0b01111111, // 8
  0b01101111  // 9
};

void setup() {
  // 初始化引脚
  pinMode(dataPin, OUTPUT);
  pinMode(clockPin, OUTPUT);
  pinMode(latchPin, OUTPUT);
}

// 显示单个数字到指定位置
void disPlayNumber(int digit, int number) {
  byte v = 0b0;
  // 位选信号(选择第digit位)
  shiftOut(dataPin, clockPin, MSBFIRST, bitSet(v, digit-1));
  // 段选信号(显示number数字)
  shiftOut(dataPin, clockPin, LSBFIRST, ~disNum[number]);
  digitalWrite(latchPin, LOW);  // 开始传输
  digitalWrite(latchPin, HIGH); // 锁存数据(关键步骤)
}

void loop() {
  disPlayNumber(1, 8);  // 在第1位显示8
  delay(5);
}

3.4 多位数字显示

通过循环快速切换不同位的显示,利用人眼视觉暂留效果,实现多位同时显示的视觉效果:

// 显示多位数(如1234)
void disPlayNumbers(int n) {
  int arr[5] = {};
  // 拆分数字到数组
  arr[1] = n % 10;         // 个位
  arr[2] = n / 10 % 10;    // 十位
  arr[3] = n / 100 % 10;   // 百位
  arr[4] = n / 1000 % 10;  // 千位
  
  // 循环显示每一位
  for(int i = 1; i <= 4; i++) {
    disPlayNumber(4 - i + 1, arr[i]);
    delay(1);  // 短暂延时,保证显示稳定
  }
}

void loop() {
  disPlayNumbers(721);  // 显示721
}

四、位操作函数详解

4.1 bitSet() 函数

设置变量的某一位为1:

// 语法:bitSet(x, n)
// x: 操作的变量
// n: 位位置(从0开始计数)

byte v = 0b0;
bitSet(v, 0);  // 设置第0位为1,v变为0b00000001
bitSet(v, 2);  // 设置第2位为1,v变为0b00000101

4.2 bitClear() 函数

清除变量的某一位为0:

// 语法:bitClear(x, n)
byte v = 0b11111111;
bitClear(v, 1);  // 清除第1位,v变为0b11111101

4.3 bitWrite() 函数

向变量的某一位写入指定值(0或1):

// 语法:bitWrite(x, n, b)
byte v = 0b0;
bitWrite(v, 3, 1);  // 向第3位写入1,v变为0b00001000
bitWrite(v, 3, 0);  // 向第3位写入0,v变为0b00000000

4.4 位运算符

运算符 含义 示例
& 按位与 0b1010 & 0b1100 = 0b1000
| 按位或 0b1010 | 0b1100 = 0b1110
^ 按位异或 0b1010 ^ 0b1100 = 0b0110
~ 按位非 ~0b1010 = 0b0101
<< 左移 0b0001 << 2 = 0b0100
>> 右移 0b1000 >> 2 = 0b0010

五、综合应用示例

5.1 按键控制数码管计数

#define dataPin 2
#define clockPin 3
#define latchPin 4
#define buttonPin 5

byte disNum[10] = {0b00111111,0b00000110,0b01011011,0b01001111,0b01100110,
                   0b01101101,0b01111101,0b00000111,0b01111111,0b01101111};
int count = 0;
boolean buttonState = false;

void setup() {
  pinMode(dataPin, OUTPUT);
  pinMode(clockPin, OUTPUT);
  pinMode(latchPin, OUTPUT);
  pinMode(buttonPin, INPUT);
  Serial.begin(115200);
}

void disPlayNumber(int digit, int number) {
  byte v = 0b0;
  shiftOut(dataPin, clockPin, MSBFIRST, bitSet(v, digit-1));
  shiftOut(dataPin, clockPin, LSBFIRST, ~disNum[number]);
  digitalWrite(latchPin, LOW);
  digitalWrite(latchPin, HIGH);
}

void disPlayNumbers(int n) {
  int arr[5] = {};
  arr[1] = n % 10;
  arr[2] = n / 10 % 10;
  arr[3] = n / 100 % 10;
  arr[4] = n / 1000 % 10;
  
  for(int i = 1; i <= 4; i++) {
    disPlayNumber(4 - i + 1, arr[i]);
    delay(1);
  }
}

void loop() {
  // 按键消抖处理
  if(digitalRead(buttonPin) == LOW) {
    count++;  // 计数加1
    if(count > 9999) count = 0;  // 溢出归零
    Serial.println(count);
    delay(200);  // 消抖延时
  }
  disPlayNumbers(count);  // 显示计数
}

5.2 代码说明

  1. 初始化引脚和段码表
  2. disPlayNumber()函数控制单个数码管显示
  3. disPlayNumbers()函数处理多位数显示
  4. 按键检测部分加入消抖延时
  5. 主循环中不断刷新显示并检测按键状态

通过本教程,你应该掌握了按键消抖的原理与实现方法,以及四位数码管的控制方式。这些知识是ESP32开发中非常基础且重要的内容,可应用于各种交互式电子项目中。

0 条评论

目前还没有评论...