跳到主要内容

数字输出/输入

本节介绍 GPIO(通用输入/输出)的基本概念,并通过控制 LED 闪烁和读取按键状态的示例,讲解如何在 ESP32 MicroPython 环境中控制 GPIO 的输出与输入。

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 开发板和 MicroPython 环境,控制外接 LED 闪烁,演示如何使用 Thonny 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Ω 范围内的电阻替代。阻值越大,LED 亮度越低。

2.2 REPL 交互

首先通过 REPL 熟悉 GPIO 相关函数,以下是一些常用操作。

在 Shell 中逐行输入以下命令并观察运行结果:

from machine import Pin  # 导入 Pin 类
led = Pin(7, Pin.OUT)  # 初始化引脚
led.on()               # 点亮
led.off()              # 熄灭
led.toggle()           # 翻转状态
led.toggle()
led.value(1)           # 设置为高电平
led.value(0)           # 设置为低电平
led.value()            # 读取当前状态

2.3 完整代码示例

在 Thonny IDE 中新建文件,输入以下代码并运行。

import time
from machine import Pin

# 定义 LED 引脚号
LED_PIN = 7

# 初始化引脚:创建 Pin 对象,设置为输出模式 (Pin.OUT)
led = Pin(LED_PIN, Pin.OUT)

while True:
led.value(1) # 点亮 LED (1 代表高电平)
time.sleep(1) # 等待 1 秒
led.value(0) # 关闭 LED (0 代表低电平)
time.sleep(1) # 等待 1 秒

运行后,与 ESP32 开发板连接的 LED 将呈现亮 1 秒、灭 1 秒的循环闪烁效果。

代码解析

  1. from machine import Pin:

    • 从 MicroPython 的内置 machine 模块中导入 Pin 类。这是控制 GPIO 引脚的核心类。
  2. led = Pin(LED_PIN, Pin.OUT):

    • 实例化 Pin 对象:创建一个名为 led 的对象来控制指定引脚。
    • 参数 1 (LED_PIN):指定引脚编号(本例为 7)。
    • 参数 2 (Pin.OUT):配置引脚模式。Pin.OUT 表示输出模式,允许 ESP32 向该引脚输出高低电平。
  3. led.value(1)led.value(0):

    • value() 方法:用于控制引脚的输出电平。
    • 1:输出高电平 (3.3V),LED 点亮。
    • 0:输出低电平 (GND),LED 熄灭。
    • 注:也可以使用 led.on()led.off() 方法,效果相同。
  4. time.sleep(1):

    • 暂停程序执行指定的秒数。
    • 在此期间,程序保持阻塞状态,不进行其他操作。对于简单的闪烁任务,使用 sleep 是最直观的方法。

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

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

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

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

ESP32-S3-Zero-Pinout

理想情况按钮按下

3.2 REPL 交互

  1. 初始化引脚: 输入以下命令,配置 GPIO8 为输入模式并启用内部上拉。

    from machine import Pin
    button = Pin(8, Pin.IN, Pin.PULL_UP)
  2. 读取状态

    • 松开按钮,在 Shell 输入 button.value(),回车。

      button.value()

      解释:由于上拉电阻存在,未按下时读取到高电平 (1)。

    • 按住按钮不放,在 Shell 输入 button.value(),回车。

      button.value()

      解释:按下按钮引脚接地,读取到低电平 (0)。

3.3 完整代码示例

示例 1:读取按钮状态

import time
from machine import Pin

# 定义按钮连接的引脚
BUTTON_PIN = 8

# 初始化引脚:设置为输入模式,并启用内部上拉电阻
# 当按钮未按下时,引脚将被拉高读取为 1;按下接地时读取为 0
button = Pin(BUTTON_PIN, Pin.IN, Pin.PULL_UP)

while True:
# 读取引脚的电平状态
button_state = button.value()

# 将读取到的状态打印到控制台
print(button_state)

# 添加微小的延时以防止数据刷新过快导致控制台卡顿或设备无法响应中断
time.sleep_ms(20)

代码解释:

  1. button = Pin(BUTTON_PIN, Pin.IN, Pin.PULL_UP):

    • Pin.IN: 将引脚配置为输入模式。
    • Pin.PULL_UP: 启用 ESP32 芯片内部上拉电阻。这决定了按钮的默认状态为高电平 (1)。
  2. button.value():

    • 在输入模式下,该函数返回引脚当前的逻辑电平。
    • 返回 1: 表示高电平(按钮未按下)。
    • 返回 0: 表示低电平(按钮被按下,引脚连接到 GND)。

运行结果:

运行代码后,观察 Thonny 下方的 Shell 窗口。按钮未按下时,Shell 窗口会持续显示"1";按下按钮时,显示"0"。通过按压和释放按钮,可观察到状态的变化。

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

import time
from machine import Pin

# 定义引脚
BUTTON_PIN = 8

# 初始化引脚
button = Pin(BUTTON_PIN, Pin.IN, Pin.PULL_UP)

# 初始化变量
last_button_state = 1 # 初始状态默认为 1 (高电平,未按下)
count = 0

while True:
# 读取当前按钮状态
current_button_state = button.value()

# 逻辑判断:检查状态边缘
if last_button_state == 1 and current_button_state == 0:
# 检测到下降沿:按钮刚刚被按下
pass # 此处不做处理

elif last_button_state == 0 and current_button_state == 1:
# 检测到上升沿:按钮刚刚被释放
count += 1 # 计数器加 1
print(count) # 打印当前计数值

# 更新状态,为下一次循环做准备
last_button_state = current_button_state

代码解释:

  1. 状态变量 (last_button_state):

    为了检测变化,需要记录上一次检测时的状态。程序通过比较 current_button_statelast_button_state 来判断是否发生了动作。

  2. 边缘检测:

    本代码选择在 上升沿 (Rising Edge) 触发计数,即按钮从按下 (0) 变回松开 (1) 的瞬间。这通常符合用户的操作直觉(松开手指完成一次点击)。

  3. 状态更新:

    last_button_state = current_button_state: 这是循环结束前的关键步骤,确保下一次循环能基于最新的历史数据进行比较。

运行结果:

运行代码后观察 Shell 窗口。尝试多次按下按钮,可能会观察到计数器有时会增加 1,但有时会突然增加 2、3 或者更多。这就是按钮抖动 (Button Bouncing)。

什么是按钮抖动 (Bouncing)?

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

按钮和弹起时的抖动

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

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

import time
from machine import Pin

# 定义引脚
BUTTON_PIN = 8

# 初始化引脚
button = Pin(BUTTON_PIN, Pin.IN, Pin.PULL_UP)

# 初始化变量
last_button_state = 1 # 初始状态默认为 1 (高电平,未按下)
count = 0

while True:
# 读取当前按钮状态
current_button_state = button.value()

# 逻辑判断:检查状态边缘
if last_button_state == 1 and current_button_state == 0:
# 检测到下降沿:按钮刚刚被按下
pass # 此处不做处理

elif last_button_state == 0 and current_button_state == 1:
# 检测到上升沿:按钮刚刚被释放
count += 1 # 计数器加 1
print(count) # 打印当前计数值

# 延时 100 毫秒防抖
time.sleep_ms(100)

# 更新状态,为下一次循环做准备
last_button_state = current_button_state

运行结果:

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

代码解释:

  • time.sleep_ms(100): 在确认按键动作后,程序暂停 100 毫秒。这段时间足以让机械触点的物理抖动平息。虽然在此期间 CPU 无法处理其他任务(阻塞),但对于简单的按键应用,这是一种高效且易于实现的解决方案。

4. 拓展练习

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

接线图:

接线图

代码:

import time
from machine import Pin

# 定义引脚编号
LED_PIN = 7
BUTTON_PIN = 8

# 初始化 LED 引脚为输出模式
led = Pin(LED_PIN, Pin.OUT)

# 初始化按钮引脚为输入模式,并启用内部上拉电阻
button = Pin(BUTTON_PIN, Pin.IN, Pin.PULL_UP)

while True:
# 读取按钮状态(0 表示被按下,1 表示未按下)
button_state = button.value()

if button_state == 0:
# 按钮被按下,点亮 LED
led.value(1)
else:
# 按钮未按下,熄灭 LED
led.value(0)

# 添加极短的延时以减少 CPU 占用
time.sleep_ms(10)
请尝试实现:按按钮一次,切换 LED 状态一次。

接线图:

接线图

代码:

import time
from machine import Pin

# 定义引脚
LED_PIN = 7
BUTTON_PIN = 8

# 初始化引脚
led = Pin(LED_PIN, Pin.OUT)
button = Pin(BUTTON_PIN, Pin.IN, Pin.PULL_UP)

# 初始化状态变量
last_button_state = 1 # 上一次按钮状态,初始化为高电平(未按下)
led_state = 0 # LED 当前状态,0 为灭,1 为亮

while True:
# 读取当前按钮状态
current_button_state = button.value()

# 检测上升沿:上一次是低电平(按下),当前是高电平(松开)
if last_button_state == 0 and current_button_state == 1:
# 切换 LED 状态变量 (0 变 1,1 变 0)
led_state = not led_state

# 应用新的状态到 LED (MicroPython 会自动将 True/False 转为 1/0)
led.value(led_state)

# 防抖延时
time.sleep_ms(100)

# 更新状态,用于下一次循环比较
last_button_state = current_button_state

# 添加极短的延时以减少 CPU 占用
time.sleep_ms(10)

5. 相关链接