用 Python 控制 Arduino:从 Firmata 接线到数字输入输出

2026-08-18 25 预计阅读时间: 1 分钟
来源: realpython.com AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:8 分钟

用 Python 操作 Arduino 的关键,不是把 Python 程序直接烧录到开发板,而是让 Arduino 运行 Firmata 固件,再由电脑上的 Python 程序通过串口发送命令。理解这条通信链路之后,搭建电路、读取按钮和控制 LED 就会清晰得多。

Firmata 在系统中扮演什么角色

典型结构可以拆成三层:

  1. Arduino 负责实际的引脚读写。
  2. Firmata 固件把引脚操作封装成串口协议。
  3. Python 库通过 USB 串口发送命令,并接收输入状态。

这意味着 Python 程序停止运行后,依赖它持续下发命令的控制逻辑也会停止。对于需要断开电脑后独立工作的设备,仍应把核心逻辑写进 Arduino 固件;Firmata 更适合教学、原型验证、交互实验和上位机控制。

开始前需要准备 Arduino 开发板、USB 数据线、LED、220Ω 至 330Ω 电阻,以及可选的按钮和面包板。不同开发板的工作电压与引脚能力可能不同,接线前应核对对应型号的规格。

烧录固件并确认串口

在 Arduino IDE 中连接开发板,选择正确的开发板型号和端口,然后打开:

File > Examples > Firmata > StandardFirmata

将示例上传到 Arduino。上传完成后关闭 Arduino IDE 的串口监视器,因为同一串口通常不能同时被监视器和 Python 程序占用。

安装 Python 依赖可以这样实践:

python -m venv .venv

# macOS / Linux
source .venv/bin/activate

# Windows PowerShell 使用:
# .venv\Scripts\Activate.ps1

python -m pip install pyfirmata

查找串口时,可以在 macOS 或 Linux 上执行:

ls /dev/tty.* /dev/ttyUSB* /dev/ttyACM* 2>/dev/null

Windows 用户可以在设备管理器的“端口”区域查看类似 COM3 的名称。

第一个输出实验:让 LED 闪烁

下面假设 Arduino 已烧录 StandardFirmata,LED 正极通过限流电阻连接到数字引脚 13,负极连接 GND。许多 Arduino 板载 LED 也使用 13 号引脚,因此可以先不接外部 LED 验证通信。

把代码中的串口名称改成实际端口:

import time
from pyfirmata import Arduino

PORT = "/dev/ttyACM0"  # Windows 示例:COM3
LED_PIN = 13

board = Arduino(PORT)

try:
    while True:
        board.digital[LED_PIN].write(1)
        time.sleep(0.5)
        board.digital[LED_PIN].write(0)
        time.sleep(0.5)
except KeyboardInterrupt:
    pass
finally:
    board.digital[LED_PIN].write(0)
    board.exit()

保存为 blink.py 后运行:

python blink.py

这个实验验证了完整链路:Python 生成输出状态,Firmata 通过串口传输命令,Arduino 最终改变引脚电平。

同时读取按钮并控制 LED

输入实验需要处理悬空引脚。如果按钮仅连接输入引脚而没有上拉或下拉,读数可能随机跳动。可以这样实践:按钮一端连接数字引脚 2,另一端连接 GND,并在程序中启用内部上拉电阻。

import time
from pyfirmata import Arduino, INPUT, OUTPUT, util

PORT = "/dev/ttyACM0"  # 改成实际端口
BUTTON_PIN = 2
LED_PIN = 13

board = Arduino(PORT)
iterator = util.Iterator(board)
iterator.start()

button = board.digital[BUTTON_PIN]
led = board.digital[LED_PIN]
button.mode = INPUT
button.write(1)  # 请求启用内部上拉;是否生效取决于固件和库版本
led.mode = OUTPUT

time.sleep(1)

try:
    while True:
        value = button.read()
        if value is not None:
            pressed = value == 0  # 上拉接法中,按下时读取低电平
            led.write(1 if pressed else 0)
        time.sleep(0.02)
except KeyboardInterrupt:
    pass
finally:
    led.write(0)
    board.exit()

Iterator 会持续处理 Firmata 发回的数据;没有它,输入读取可能得不到及时更新。程序还必须接受 read() 暂时返回 None,因为连接建立后不一定立刻收到第一个采样值。

如果当前库或固件组合无法通过 button.write(1) 启用内部上拉,应使用约 10kΩ 的外部上拉电阻,或者改用明确支持 pull-up 配置的 Firmata 客户端。机械按钮还会产生抖动,正式项目应增加约 20 至 50 毫秒的软件消抖,或使用硬件滤波。

排错时按通信链路逐层检查

遇到无响应时,不要同时修改代码和接线。按以下顺序定位更有效:

  • 确认 USB 线支持数据传输,而不只是充电。
  • 确认 Arduino IDE 中选择了正确的开发板和端口。
  • 确认 StandardFirmata 已成功上传。
  • 关闭串口监视器和其他可能占用端口的程序。
  • 检查 Python 中的端口名称与系统显示是否一致。
  • 先运行板载 LED 实验,再接入外部按钮和 LED。
  • 确认 LED 串联了限流电阻,并检查极性与共地连接。

Linux 上如果出现串口权限错误,可先检查设备所属组。部分发行版使用 dialout

ls -l /dev/ttyACM0
sudo usermod -aG dialout "$USER"

修改用户组后通常需要注销并重新登录。不要为了省事长期用 root 运行控制程序。

是否应该在项目中采用 Firmata

Firmata 的优势是反馈快:修改 Python 后直接重启脚本,无需反复编译和烧录 Arduino 固件。它也便于接入数据分析、桌面界面或网络服务。

边界同样明确。控制依赖电脑、USB 和 Python 进程,串口通信还会引入延迟,因此它不适合严格实时控制、安全关键设备或必须离线运行的产品。采用前可以检查四件事:设备是否必须脱离电脑运行、延迟是否可接受、断线后输出是否需要进入安全状态,以及目标引脚的电压和电流是否符合硬件规格。对原型和学习项目,Firmata 是一条短路径;对长期部署,应评估是否把稳定后的控制逻辑下沉到 Arduino 固件中。


相关推荐