跳到主要内容

数字输出/输入

1. 数字信号

数字信号是用离散的数值来表示信息的信号。最简单、最常见的数字信号是二进制数字信号,它只有两种状态。

在 ESP32 的 GPIO 控制中,主要使用这种二进制数字信号,就像房间的电灯开关,二进制数字信号在任何时刻都处于两种明确状态之一:

  • 高电平 (HIGH): 代表逻辑上的 "1" 或 "真"。在 ESP32 开发板上,这通常意味着引脚输出接近 3.3V 的电压。
  • 低电平 (LOW): 代表逻辑上的 "0" 或 "假"。在 ESP32 开发板上,这通常意味着引脚输出大约 0 伏特的电压,也就是连接到地 (GND)。

方波信号图

简单来说,数字信号就是用这两种电压状态来传递是 (HIGH) / 否 (LOW) 或者 1 / 0 这样的信息。

  • 当 ESP32 输出一个数字信号时,它控制一个引脚变成高电平或低电平,就像是在“说话”,比如控制 LED 的开关。
  • 当 ESP32 输入一个数字信号时,它检测一个引脚上是高电平还是低电平,就像是在“倾听”,比如检测按钮是否被按下。
为什么 HIGH 是 3.3V?

HIGH 电平总是等于微控制器的工作电压:

  • ESP32 的工作电压是 3.3V → HIGH = 3.3V
  • Arduino Uno 的工作电压是 5V → HIGH = 5V
  • 某些低功耗芯片工作电压是 1.8V → HIGH = 1.8V

因此,"HIGH"所代表的具体电压值取决于所使用的开发板。

2. 数字输出

本示例将使用 ESP32 开发板和 Arduino 环境,使一个外接 LED 闪烁。本示例将演示如何使用 Arduino IDE 控制 ESP32 开发板的数字输出。

2.1 搭建电路

需要使用的器件有:

  • LED * 1
  • 330Ω 电阻 * 1
  • 面包板 * 1
  • 导线
  • ESP32 开发板

按照下面接线图连接电路:

ESP32-S3-Zero 引脚图

ESP32-S3-Zero-Pinout

接线图

电路工作原理

让我们理解一下这个简单电路是如何工作的:

  1. 电流路径: 当 GPIO7 输出高电平 (3.3V) 时,电流从引脚流出 → 经过 330Ω 电阻 → 通过 LED → 回到 ESP32 的 GND 引脚,形成完整的电路回路。

  2. 电阻的作用: 330Ω 电阻是限流电阻

    • 保护 LED:防止过大电流烧坏 LED
    • 保护 ESP32:防止 GPIO 引脚输出过大电流
  3. LED 极性:

    • 长脚(阳极):连接电阻的另一端
    • 短脚(阴极):连接 GND
    • 接反了 LED 不会亮,但一般不会损坏
提示

如果没有 330Ω 电阻,可以使用 220Ω-1kΩ 范围内的电阻替代。

2.2 代码

打开 Arduino IDE,运行下面的代码。

const int ledPin = 7;  // LED 连接的引脚号

// setup 函数在开发板上电或复位后运行一次
void setup() {
// 将引脚初始化为输出模式
pinMode(ledPin, OUTPUT);
}

// loop 函数会永远反复运行
void loop() {
digitalWrite(ledPin, HIGH); // 点亮 LED(HIGH 表示高电平)
delay(1000); // 等待一秒
digitalWrite(ledPin, LOW); // 引脚输出低电平,关闭 LED
delay(1000); // 等待一秒
}

上传代码后,与 ESP32 开发板连接的 LED 将会亮起一秒,熄灭一秒,如此循环往复。

代码解析

  1. const int ledPin = 7;

    • 使用 const int 定义一个常量,这样如果要换引脚,只需修改这一处。
    • const 表示这个值在程序运行期间不会改变。
  2. pinMode(ledPin, OUTPUT);

    • pinMode()函数的作用: 配置指定引脚的工作模式,需要在使用数字引脚前调用。
    • 第一个参数: 引脚编号
    • 第二个参数: 模式类型
      • OUTPUT:输出模式,ESP32 可以控制这个引脚输出高电平或低电平。
  3. digitalWrite(ledPin, HIGH)

    • digitalWrite()函数的作用: 向输出引脚输出值
    • 第一个参数: 引脚编号
    • 第二个参数: 要输出的电平
      • HIGH:输出高电平(3.3V),LED 点亮。
      • LOW:输出低电平(0V),LED 熄灭。
  4. delay(1000);

    • delay()函数的作用: 暂停程序执行指定的毫秒数
    • 1000 毫秒 = 1 秒。
    • delay() 期间,程序不执行其他操作。这意味着在 delay() 期间,ESP32 不能响应其他事件,例如读取传感器或按钮。在更复杂的项目中,需要使用非阻塞的延时方法。

3. 数字输入

本例将通过 ESP32 开发板制作一个简单的按钮电路,通过读取按钮状态来学习数字输入的基本操作。

3.1 搭建电路

需要使用的器件有:

  • 按钮 * 1
  • 面包板 * 1
  • 导线
  • ESP32 开发板
  • 10kΩ 电阻 * 1(可选,使用内部上拉时不需要)
为什么需要上拉电阻?

如果按钮引脚既没有连接电源也没有连接地,它就处于"浮空 (Floating)"状态,读取的值是不确定的。上拉电阻确保在按钮未按下时引脚有明确的高电平状态。

ESP32-S3-Zero 引脚图

ESP32-S3-Zero-Pinout

内部上拉电阻(推荐)外部上拉电阻
内部上拉外部上拉
连接方式:
• 按钮一端 → GPIO8
• 按钮另一端 → GND

工作原理:
• ESP32 内部上拉电阻将 GPIO8 拉到 HIGH(3.3V)
• 按钮未按下:读取 HIGH
• 按钮按下:读取 LOW

优点:
• 节省元器件
• 接线简单
• 代码:pinMode(buttonPin, INPUT_PULLUP);
连接方式:
• 按钮一端 → GPIO8
• 按钮另一端 → GND
• 10kΩ 电阻:3.3V ↔ GPIO8

工作原理:
• 外部电阻将 GPIO8 拉到 HIGH(3.3V)
• 按钮未按下:读取 HIGH
• 按钮按下:读取 LOW

优点:
• 上拉电流可控
• 通用性更好
• 代码:pinMode(buttonPin, INPUT);
ESP32-S3-Zero 引脚图

ESP32-S3-Zero-Pinout

理想情况按钮按下

3.2 代码

示例 1:读取按钮状态

const int buttonPin = 8;  // 定义按钮连接的引脚

void setup() {
Serial.begin(9600); // 初始化串口通信,波特率 9600
while (!Serial) {} // 等待串口连接建立

pinMode(buttonPin, INPUT_PULLUP); // 设置按钮引脚为上拉输入模式
}

void loop() {
int buttonState = digitalRead(buttonPin); // 读取按钮当前状态
Serial.println(buttonState); // 串口输出按钮状态值
}

代码解释:

  1. const int buttonPin = 8;:定义按钮连接的引脚。
  2. Serial.begin(9600);:初始化串口通信,波特率设置为 9600。
  3. pinMode(buttonPin, INPUT_PULLUP);
    • buttonPin 设置为 INPUT_PULLUP 模式。
    • INPUT_PULLUP 是一个特殊的输入模式,它会启用 ESP32 GPIO 引脚内部的上拉电阻。这样,当按钮未按下时,引脚会被拉到高电平;当按钮按下将引脚连接到 GND 时,引脚变为低电平。这避免了连接外部上拉电阻的需要。
  4. int buttonState = digitalRead(buttonPin);
    • digitalRead() 函数用于读取指定数字引脚的电平状态。
    • 它返回 HIGH (通常是整数 1) 或 LOW (通常是整数 0)。
    • 由于使用了 INPUT_PULLUP
      • 按钮未按下:buttonStateHIGH
      • 按钮按下:buttonStateLOW
  5. Serial.println():将括号内的内容打印到串口监视器,并换行。

运行结果:

将代码烧录到 ESP32 开发板后,打开串口监视器。按钮未按下时,串口监视器会持续显示"1";按下按钮时,显示"0"。你可以通过按压和释放按钮来观察这些状态的变化。

示例 2:记录按钮按下的次数

const int buttonPin = 8;

int lastButtonState = 1; // 上一次按钮状态
int currentButtonState; // 当前按钮状态
int count = 0; // 计数器

void setup() {
Serial.begin(9600); // 初始化串口通信
while (!Serial) {} // 等待串口连接

pinMode(buttonPin, INPUT_PULLUP); // 设置按钮引脚为上拉输入
}

void loop() {
currentButtonState = digitalRead(buttonPin); // 读取当前按钮状态

if (lastButtonState == HIGH && currentButtonState == LOW) {
// 按钮被按下

} else if (lastButtonState == LOW && currentButtonState == HIGH) {
// 按钮被释放
count = count + 1; // 计数器加 1
Serial.println(count); // 串口输出当前计数值
}

lastButtonState = currentButtonState; // 保存当前按钮状态
}

代码解释:

  1. int lastButtonState = HIGH;:由于我们使用 INPUT_PULLUP 模式,按钮未按下时引脚为高电平,所以初始值设为 HIGH

  2. 状态检测逻辑

    • lastButtonState == HIGH && currentButtonState == LOW:引脚由高电平到低电平,检测按钮刚被按下的瞬间
    • lastButtonState == LOW && currentButtonState == HIGH:引脚由低电平到高电平,检测按钮刚被释放的瞬间
    • 我们选择在按钮释放时计数
  3. lastButtonState = currentButtonState;:在每次循环结束时更新状态,为下一次比较做准备。

运行结果:

烧录代码后打开串口监视器。尝试多次按下按钮,你可能会发现计数器有时会增加 1,但有时会突然增加 2、3 或者更多。这就是按钮抖动 (Button Bouncing)。

什么是按钮抖动 (Bouncing)?

机械按钮在按下或松开的瞬间,其内部的金属触点会发生微小的、快速的物理弹跳。这导致在人感觉只按了一次的几毫秒内,电路实际上快速地接通、断开了很多次。ESP32 的运行速度非常快,它能捕捉到每一次微小的通断,因此会错误地将其识别为多次按下按钮。

按钮和弹起时的抖动

示例 3:记录按钮按下的次数(简单防抖)

一种简单的消除抖动的方法是在检测到一次按键后,加入一个短暂的延时,忽略掉后续的抖动信号。

const int buttonPin = 8;

int lastButtonState = 1; // 上一次按钮状态
int currentButtonState; // 当前按钮状态
int count = 0; // 计数器

void setup() {
Serial.begin(9600); // 初始化串口通信
while (!Serial) {} // 等待串口连接

pinMode(buttonPin, INPUT_PULLUP); // 设置按钮引脚为上拉输入
}

void loop() {
currentButtonState = digitalRead(buttonPin); // 读取当前按钮状态

if (lastButtonState == HIGH && currentButtonState == LOW) {
// 按钮被按下

} else if (lastButtonState == LOW && currentButtonState == HIGH) {
// 按钮被释放
count = count + 1; // 计数器加 1
Serial.println(count); // 串口输出当前计数值
delay(100); // 延时 100 毫秒防抖
}

lastButtonState = currentButtonState; // 保存当前按钮状态
}

运行结果:

和上面一样,每松开按钮一次,计数器加 1,并且因为加了 delay(100),每次按钮弹起只会计数一次,有效减少了误触发数量。你可以尝试快速多次按按钮,留意计数器增量是否与实际按动匹配。

代码解释:

  • 在每次检测到由 LOW 变为 HIGH 的瞬间(即按钮松开),更新计数器,执行 count=count+1Serial.println(count);
  • 加入 delay(100); 暂停 100 毫秒,简单抑制因按钮弹跳带来的重复计数

4. 拓展练习

请尝试实现:当按钮按下时,LED 亮起。松开按钮时,LED 熄灭。

接线图:

接线图

代码:

const int ledPin = 7;     // LED 连接的引脚号
const int buttonPin = 8; // 按钮连接的引脚号
int buttonState; // 存储按钮状态

void setup() {
pinMode(ledPin, OUTPUT); // 设置 LED 引脚为输出模式
pinMode(buttonPin, INPUT_PULLUP); // 设置按钮引脚为上拉输入模式
}

void loop() {
buttonState = digitalRead(buttonPin); // 读取按钮状态

if (buttonState == LOW) { // 按钮按下时(LOW)
digitalWrite(ledPin, HIGH); // 点亮 LED
} else { // 按钮未按下时(HIGH)
digitalWrite(ledPin, LOW); // 熄灭 LED
}
}
请尝试实现:按按钮一次,切换 LED 状态一次。

接线图:

接线图

代码:

const int ledPin = 7;     // LED 连接的引脚号
const int buttonPin = 8; // 按钮连接的引脚号

int lastButtonState = HIGH; // 上一次按钮状态
int ledState = LOW; // LED 当前状态(LOW=灭,HIGH=亮)
int currentButtonState; // 当前按钮状态

void setup() {
pinMode(ledPin, OUTPUT); // 设置 LED 引脚为输出模式
pinMode(buttonPin, INPUT_PULLUP); // 设置按钮引脚为上拉输入模式
}

void loop() {
currentButtonState = digitalRead(buttonPin); // 读取当前按钮状态

// 检测按钮从按下变为松开的瞬间
if (lastButtonState == LOW && currentButtonState == HIGH) {
ledState = !ledState; // 切换 LED 状态(亮变灭,灭变亮)
digitalWrite(ledPin, ledState); // 应用新的 LED 状态
delay(100); // 防抖延时
}

lastButtonState = currentButtonState; // 保存当前状态,用于下次比较
}

5. 相关链接