当前位置: 首页 > news >正文

05_ESP32 串行通信 (UART)

05_ESP32 串行通信 (UART)

UART(Universal Asynchronous Receiver/Transmitter,通用异步收发器)是一种硬件接口电路,用于实现异步串行通信。常见应用包括:与传感器/模块通信,开发板与电脑之间的数据收发(如打印日志、调试信息)等。

基于 UART 的串行通信具有以下特点:

  • 异步通信:发送和接收设备不需要共享时钟信号,而是通过预先约定的波特率来同步数据传输。
  • 串行传输:数据按位逐个发送,而不是并行传输多个位。
  • 全双工:可以同时进行发送和接收操作。

UART 是 异步 通信,意味着它没有共享的时钟线。为了实现数据的正确收发,它们约定使用相同 波特率数据帧格式

波特率 (Baud Rate) 表示每秒钟传输的数据位数(bps,bits per second)。通信双方必须使用相同的波特率才能正确传输数据。常见的波特率有 9600 和 115200。

每个 UART 数据帧 包含以下部分:

  • 起始位 (Start Bit):1 位,总是 0,表示数据传输开始
  • 数据位 (Data Bits):通常 5-9 位,常用 8 位,包含实际要传输的数据
  • 奇偶校验位 (Parity Bit):可选,用于错误检测
  • 停止位 (Stop Bits):1-2 位,总是 1,表示数据传输结束
image-20260519232107360

UART 通信需要两根核心信号线:

  • TX (Transmit):发送数据线
  • RX (Receive):接收数据线
  • GND (地): 通信双方的“共同参考点”,确保电压信号能被正确解读。
  • 连接方式:两个设备之间需要交叉连接,即设备 A 的 TX 连接设备 B 的 RX,设备 A 的 RX 连接设备 B 的 TX。此外,两个设备必须共地(连接 GND),以确保信号电平有稳定的参考点。
image-20260519232227259

ESP32 中的 UART

ESP32 芯片通常有两个或更多 UART 控制器。每个 UART 控制器可以独立配置波特率、数据位长度、位顺序、停止位位数、奇偶校验位等参数。

在 Arduino 环境中,可以通过 SerialSerial1 等对象来使用它们。

  • Serial 是默认串口,通常连接到开发板上的 USB 转串口芯片。如果没有搭载 USB 转串口芯片,它也可以通过开启 USB CDC on boot 指向原生 USB。无论是哪种情况,Serial 都可用于与电脑串口监视器通信,也是上传代码和调试常用的接口。
  • Serial1 其他串口是额外的硬件 UART,几乎可分配给任意空闲 GPIO 用于连接外部设备。
  • SerialSerial1 外,部分 ESP32 型号(如 ESP32-S3)还支持 Serial2 等更多串口。各型号支持的 UART 数量可查阅芯片数据手册或 [ESP32 系列产品介绍表](https://products.espressif.com/static/Espressif SoC Product Portfolio.pdf)。

这种设计让我们能够同时用 Serial 与电脑调试、打印日志,同时使用 Serial1 等其它 UART 与模块独立通信,互不干扰。

image-20260519232400614

示例 1:通过串口监视器控制 LED

这个示例将演示 UART 的经典应用:通过电脑发送指令,控制 ESP32 的硬件。将通过 Arduino IDE 的串口监视器发送 "on" 或 "off" 字符串,来点亮或熄灭连接在 ESP32 上的 LED。

const int ledPin = 7;          // 定义 LED 连接的引脚void setup(){pinMode(ledPin,OUTPUT);      // 设置 LED 引脚为输出模式Serial.begin(115200);        // 初始化串口通信,波特率为 115200while(!Serial){};            // 等待串口准备好
}void loop(){if(Serial.available() > 0){                      // 如果串口收到数据String msg = Serial.readStringUntil('\n');     // 读取一行输入(以换行结束)msg.trim();                                    // 去除首位占用空间但不可见的字符,比如空格和换行符if(msg == "on"){// 如果输入为“on”,点亮 LEDdigitalWrite(ledPin,HIGH);Serial.println("LED 已打开");} else if(msg == "off"){// 如果输入为“off”,熄灭 LEDdigitalWrite(ledPin,LOW);Serial.println("LED 已关闭");}else{Serial.println("请输入'on'或'off'");           // 提示有效输入}} 
}

代码解析

  1. Serial.begin(115200);: 初始化 Serial,并将波特率设置为 115200。串口监视器的波特率也需要设置为此值。
  2. Serial.available(): 检查串口接收缓冲区中是否有数据。如果大于 0,说明电脑发送了新消息。
  3. Serial.readStringUntil('\n'): 读取串口缓冲区中的字符,直到遇到换行符 \n 或超时为止,并将读取到的字符组合成一个 String 对象。这种方式适合接收由串口监视器发送的、以回车结尾的指令。
  4. msg.trim();: 当我们从串口监视器发送文本并按回车时,除了文本本身,通常还会发送一个换行符(\n)或回车符(\r)。trim() 函数会移除字符串首尾的这些空白字符,确保 msg == "on" 这样的比较能够成功。
  5. if / else if: 根据清理过的 msg 字符串内容,执行相应的 digitalWrite() 操作来控制 LED,并向串口监视器打印反馈信息。

示例 2:ESP32 之间串口通信

这个示例将展示如何使用 ESP32 的额外硬件串口(Serial1)实现两块 ESP32 开发板之间的通信。通过一块开发板连接的按钮,来控制另一块开发板上连接的 LED。

image-20260519235201567

image-20260519234452928

发送端代码 (ESP32 开发板 A)

#define UART1_RX_PIN 11  // 定义 UART1 的接收引脚(RX)
#define UART1_TX_PIN 12  // 定义 UART1 的发送引脚(TX)const int buttonPin = 7;    // 按钮连接的引脚
int lastButtonState = LOW;  // 初始状态为未按下void setup() {// 启动默认串口,用于调试输出到电脑Serial.begin(115200);// while(!Serial){};// 启动 Serial1,并指定 RX 和 TX 引脚,用于设备间通信Serial1.begin(9600, SERIAL_8N1, UART1_RX_PIN, UART1_TX_PIN);pinMode(buttonPin, INPUT_PULLUP);  // 配置按钮引脚为上拉输入模式Serial.println("Sender Ready. Press the button.");
}void loop() {int currentButtonState = digitalRead(buttonPin);// 如果按钮状态发生变化if (currentButtonState != lastButtonState) {if (currentButtonState == HIGH) {Serial1.write('0');  // 按钮松开时发送'0'Serial.println("Sent: 0 (Button Released)");} else {Serial1.write('1');  // 按钮按下时发送'1'Serial.println("Sent: 1 (Button Pressed)");}lastButtonState = currentButtonState;  // 更新按钮状态delay(50);                             // 简单的防抖动}
}

接收端代码 (ESP32 开发板 B)

#define UART1_RX_PIN 1  // 定义 UART1 的接收引脚(RX)
#define UART1_TX_PIN 2  // 定义 UART1 的发送引脚(TX)const int ledPin = 7;void setup() {// 启动默认串口,用于调试输出到电脑Serial.begin(115200);// while(!Serial){};// 启动 Serial1,并指定 RX 和 TX 引脚,用于设备间通信Serial1.begin(9600, SERIAL_8N1, UART1_RX_PIN, UART1_TX_PIN);pinMode(ledPin, OUTPUT);  // 配置 LED 引脚为输出模式Serial.println("Receiver Ready. Waiting for commands...");
}void loop() {// 检查是否从 UART1 串口接收到数据if (Serial1.available()) {char command = Serial1.read();  // 读取一个字节(字符)// 根据接收到的命令控制 LEDif (command == '1') {// 接收到'1'时点亮 LEDdigitalWrite(ledPin, HIGH);Serial.println("Received: 1 -> LED ON");} else if (command == '0') {// 接收到'0'时熄灭 LEDdigitalWrite(ledPin, LOW);Serial.println("Received: 0 -> LED OFF");}}
}

两份代码的共同点

  1. #define UART1_RX_PIN ... / #define UART1_TX_PIN ...: 使用宏定义来指定 Serial1 的 RX 和 TX 引脚。这让代码更易读,方便修改。
  2. Serial.begin(115200);: 两块板子都启动了默认的 Serial 口,这样它们可以分别连接到两台电脑(或同一个电脑的两个串口工具)上,打印调试信息,方便我们观察通信过程。
  3. Serial1.begin(9600, SERIAL_8N1, UART1_RX_PIN, UART1_TX_PIN);:
    • 这是本示例的核心。它初始化了 Serial1 通道。两个开发板通过各自的 Serial1 通道通信。
    • 9600: 这是两块 ESP32 之间通信的波特率,必须保持一致
    • SERIAL_8N1: 这是标准的串口配置(8 数据位,无校验,1 停止位)。
      • 8: 8 位数据长度(可选 5、6、7 位)
      • N: 无校验(可选偶校验 E、奇校验 O)
      • 1: 1 位停止位(可选 2 位)
    • UART1_RX_PIN, UART1_TX_PIN: 将 Serial1 绑定到定义的 GPIO 引脚上。

发送端 (ESP32 开发板 A)

  • if (currentButtonState != lastButtonState): 这个判断用于检测按钮状态的变化(从按下到松开,或从松开到按下),确保只在状态改变时发送一次数据,而不是持续发送。
  • Serial1.write('1');: 当按钮被按下时(状态变为LOW),通过 Serial1 发送单个字符 '1' 给接收端。
  • Serial1.write('0');: 当按钮松开时(状态变为HIGH),发送 '0'

接收端 (ESP32 开发板 B)

  • if (Serial1.available()): 在主循环中,不断检查 Serial1 的接收缓冲区是否有数据。
  • char command = Serial1.read();: 如果有数据,读取一个字节(字符)并存入 command 变量。
  • if (command == '1'): 判断接收到的字符。如果是 '1',就点亮 LED;如果是 '0',就熄灭 LED。同时,通过自己的 Serial 口打印收到的信息和执行的动作,方便调试。

运行结果

  1. 将两份代码分别上传到两块 ESP32 开发板。
  2. 你可以用两根 USB 线将两块板子都连接到电脑上,并打开两个串口监视器窗口,分别对应两块板子的 COM 端口。
  3. 按下发送端 (ESP32 开发板 A)的按钮,你会观察到:
    • ESP32 A 的串口监视器打印出 "Sent: 1 (Button Pressed)"
    • 接收端 (ESP32 开发板 B) 上的 LED 亮起
    • ESP32 B 的串口监视器打印出 "Received: 1 -> LED ON"
  4. 松开按钮,你会观察到:
    • ESP32 A 的串口监视器打印出 "Sent: 0 (Button Released)"
    • 接收端 (ESP32 开发板 B) 上的 LED 熄灭
    • ESP32 B 的串口监视器打印出 "Received: 0 -> LED OFF"
http://www.jsqmd.com/news/847962/

相关文章:

  • 2026年乐山乐山必吃公司榜单好评分析 - 品牌推广大师
  • 基于MAX98306 D类功放的便携音响DIY:从原理到组装实战
  • CTFshow Web红包题第六弹实战复盘:我是如何用Python脚本+条件竞争拿下flag的
  • 基于RISC-V开发板的B站消息监测终端:Python脚本与硬件交互实践
  • 基于Arduino与CC3000的便携式WiFi探测器:硬件选型、低功耗设计与实践
  • PNPM依赖管理实战:从`outdated`发现漏洞到`update`精准修复的安全升级指南
  • Codex CLI 与 Cursor 双工具联动:3 步实现项目迁移、配置互通与能力互补
  • 微软与东南大学联手:让AI助手真正学会“拖拽“和“画图“
  • 从Wi-Fi信号变弱到高速PCB设计:S参数S21插入损耗到底在说什么?
  • 微信小程序自定义TabBar实战:从配置到隐藏,手把手教你打造个性化底部导航(附完整代码)
  • 大型工程重构×细节调试:OpenAI Codex CLI 与 Cursor 联动的 4 步落地流程
  • 2026北京旅游定制旅行社推荐:口碑性价比综合测评解析 - 品牌企业推荐师(官方)
  • 【Perplexity认证考试终极指南】:2024最新考纲解析、通过率数据与3天冲刺计划
  • 避坑指南:在Ubuntu 22.04上用Anaconda配置Vision-Mamba环境,解决‘bimamba_type‘报错
  • 别再死记命令了!用H3C模拟器搞定ACL配置,我踩过的坑你别踩(附实验拓扑)
  • 2026 Finder继电器总代理权威选型指南:官方授权正品供应商推荐 - 品牌企业推荐师(官方)
  • 别再手写WebSocket适配器了!用Websockify 5分钟搞定TCP服务Web化(附Python/JS客户端踩坑实录)
  • ECCV2020 ParSeNet论文精读与复现:手把手搭建你的3D点云参数化表面拟合环境
  • 如何彻底卸载Windows 10中的OneDrive:一键解决方案指南
  • 训练大模型太烧钱?Nous Research找到让AI“一目十行“的学习秘诀
  • 基于Raspberry Pi Pico与步进开关打造多功能桌面控制器
  • kindle 5.18.6 越狱经验贴
  • CircuitJS1电路仿真器:3天从零到精通的完整入门指南
  • Windows IoT Core远程配置开机自启动应用:PowerShell与IoTStartup实战指南
  • 终极指南:如何快速免费解决GBK到UTF-8编码转换难题
  • 告别C语言:利用CH9329与Lua脚本轻松打造USB自动化控制工具
  • 别再只盯着串口了!STM32F103C8T6的SWD下载电路,手把手教你画(附BOOT引脚配置详解)
  • 玩转OpenWrt旁路由:用LuCI界面+Shell命令双重监控局域网所有设备状态
  • 虚拟机共享文件挂载
  • CircuitPython实战:NeoPixel、I2C传感器与电容触摸的嵌入式交互开发