news 2026/4/16 10:22:06

手把手教你实现UART串口通信:新手友好型实战教程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
手把手教你实现UART串口通信:新手友好型实战教程

从零开始玩转UART:一个工程师的串口实战笔记

你有没有遇到过这样的场景?

调试板子时,代码烧进去了,但程序就是不跑。LED不闪,电机不动——一切仿佛死机了。这时候,最绝望的事莫过于:没有日志输出

而救你于水火之中的,往往不是什么高大上的协议,也不是复杂的无线通信,恰恰是最朴素的那个接口:UART串口

今天,我就带你彻底搞懂这个“嵌入式开发的第一课”——UART串口通信。不讲虚的,只讲你在实际项目中会用到的东西。我们会从原理出发,一路写代码、调硬件、踩坑排错,手把手实现稳定收发,让你真正把这项技能攥在手里。


为什么是UART?它凭什么这么“能扛”

你说现在都2025年了,SPI、I2C、USB、以太网哪个不比UART快?为啥还要学它?

答案很简单:因为它简单,而且无处不在

  • 几乎每块MCU都自带至少一个UART外设;
  • 只需两根线(TX和RX),就能打通两个设备之间的“对话”;
  • 不需要共享时钟线,省下一组引脚资源;
  • 开发阶段,它是你看系统状态的“眼睛”;
  • 即使产品出厂后,很多固件升级仍然靠UART完成。

更重要的是:当你面对一块陌生的电路板时,第一个想接上去看输出的,永远是串口

它不像WiFi要配网络,也不像CAN得懂ID帧格式。只要连上,打开串口助手,啪啪几行打印出来,你就知道系统活没活着,走到哪一步了。

所以我说,掌握UART,等于握住了嵌入式世界的入门钥匙。


UART是怎么工作的?别被术语吓住

我们先抛开那些文档里写的“起始位、停止位、波特率”这些词,来打个比方:

想象两个人打电话,但他们不能同时说话(半双工先不管),只能轮流讲。而且——他们没有表。

那怎么保证对方听得清楚呢?

办法是:提前约好语速

比如,“我说话的速度是一秒说115200个字”。虽然我没告诉你每个字什么时候开始,但只要你按这个速度去听,就能对上节奏。

这就是UART的核心思想:异步 + 预设速率

数据是怎么一帧一帧传的?

当你要发送一个字节(8位)数据时,UART会把它包装成这样一串信号:

[空闲高电平] → [低电平:起始位] → [D0][D1][D2][D3][D4][D5][D6][D7] → [校验位(可选)] → [高电平:停止位]
  • 起始位:拉低1比特时间,告诉对方:“我要开始说了!”
  • 数据位:通常8位,低位在前(LSB first)
  • 校验位:可有可无,用来简单检测错误(奇校验或偶校验)
  • 停止位:保持高电平1~2个比特时间,表示这一帧结束

最常见的配置叫8-N-1
- 8位数据
- No Parity(无校验)
- 1位停止位

这种组合几乎成了行业默认标准,99%的场合都能通。

⚠️ 注意:发送和接收双方必须完全一致地设置这些参数!哪怕只是停止位差了0.5,也可能导致乱码甚至完全收不到数据。


波特率到底有多重要?差一点都不行!

波特率(Baud Rate)不是传输速度的单位,而是每秒传输的符号数。对于UART来说,一个符号就是一个bit,所以我们也常说“115200 bps”。

常见值包括:9600、19200、57600、115200、460800……

为什么大家都爱用115200?因为它是平衡点:够快(适合打印日志)、又不至于对时钟精度要求太高。

但这里有个致命细节:你的系统主频分频后,能不能精确生成目标波特率?

举个例子,在STM32F1上,APB2时钟为72MHz,经过一系列分频器到达USART1的输入时钟为72MHz。如果想得到115200波特率,就需要计算合适的DIV值:

DIV = 72,000,000 / (16 × 115200) ≈ 39.0625

结果不是整数!这意味着实际产生的波特率会有偏差。算下来误差约0.16%,还在容忍范围内(一般建议<±2%)。但如果时钟源不准或者倍频链设计不合理,误差可能扩大,直接导致通信失败。

🔧小技巧:用STM32CubeMX配置时,它会自动帮你计算并标红超限项。一定要看一眼“Actual Baud Rate”是不是接近预期。


在STM32上动手:HAL库实战收发

接下来我们进入正题。假设你正在用STM32F103C8T6做开发,要用USART1实现串口通信。

第一步:用CubeMX快速搭建工程

  1. 打开STM32CubeMX,选择芯片型号;
  2. 启用USART1,模式选Asynchronous
  3. 设置参数为:115200-8-N-1;
  4. 引脚自动分配为PA9(TX)、PA10(RX),记得设为复用推挽输出;
  5. 生成代码,导入Keil或VSCode。

生成的初始化函数会自动调用MX_USART1_UART_Init(),里面完成了寄存器配置。

第二步:写代码实现收发

#include "main.h" #include "usart.h" #include <string.h> UART_HandleTypeDef huart1; uint8_t rx_data; // 存储单字节接收缓存 // 发送字符串(阻塞方式) void UART_SendString(const char *str) { HAL_UART_Transmit(&huart1, (uint8_t*)str, strlen(str), HAL_MAX_DELAY); } // 接收回调函数(中断触发) void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart->Instance == USART1) { // 收到一个字节后立即回应 UART_SendString("Received: OK\r\n"); // 重新开启下一次中断接收 HAL_UART_Receive_IT(&huart1, &rx_data, 1); } } int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_USART1_UART_Init(); // 启动中断接收(每次只收1字节) HAL_UART_Receive_IT(&huart1, &rx_data, 1); // 开机提示 UART_SendString("UART Communication Started!\r\n"); while (1) { HAL_Delay(1000); UART_SendString("Hello from STM32!\r\n"); } }

关键点解析:

  • HAL_UART_Transmit()是阻塞发送,适用于短消息;
  • HAL_UART_Receive_IT()启动中断接收,避免轮询浪费CPU;
  • 回调函数中处理完数据后必须重新注册接收,否则只能收到一次;
  • 若想支持多字节接收,应使用DMA或环形缓冲区(ring buffer)机制。

💡进阶建议:不要长期使用单字节中断接收。频繁打断主程序会影响实时性。更优方案是配合DMA+空闲中断(IDLE Line Detection)实现高效接收不定长数据包。


换个平台试试?ESP32 + Arduino也一样简单

如果你追求快速验证原型,ESP32配上Arduino IDE简直是神器。

ESP32有三个独立UART控制器,其中:
-Serial:对应UART0,连接USB转串芯片,用于打印日志;
-Serial1/Serial2:可用于与其他模块通信(如GPS、蓝牙、传感器等)

示例:通过UART2与外部设备通信

#define RXD2 16 #define TXD2 17 void setup() { // 初始化调试串口(连接PC) Serial.begin(115200); // 初始化第二路串口(UART2) Serial2.begin(115200, SERIAL_8N1, RXD2, TXD2); Serial.println("UART2 Initialized!"); } void loop() { // 判断是否有数据到达 if (Serial2.available()) { String msg = Serial2.readStringUntil('\n'); // 按换行符分割 Serial.print("Received via UART2: "); Serial.println(msg); // 回应确认 Serial2.println("ACK: Message Received"); } // 每2秒发个心跳 delay(2000); Serial2.println("Heartbeat from ESP32"); }

这段代码已经可以胜任大多数文本协议交互任务,比如对接Modbus ASCII设备、读取NMEA格式的GPS数据等。

📌注意引脚映射:ESP32的IO功能灵活,但某些引脚在启动阶段会被占用(如GPIO0、2等),建议避开。


实际项目中常见的“坑”,我都替你踩过了

再好的理论也架不住现场出问题。下面这几个故障,我敢说你迟早会遇到。

❌ 现象一:串口助手里看到一堆乱码

最常见的原因就两个:
1.波特率不匹配—— 检查两边是否都是115200?
2.电平不兼容—— 3.3V MCU连5V设备?直接烧片!

✅ 解决方案:
- 统一测试环境,两端都设为115200-8-N-1;
- 加电平转换芯片,如MAX3232(RS232电平)、TXS0108E(3.3V↔5V逻辑电平);

❌ 现象二:偶尔丢包,或者接收断续

这通常是软件层面的问题:
- 中断服务函数太慢;
- 没及时重启接收;
- 缓冲区溢出。

✅ 解决方案:
- 使用DMA接收,降低CPU负担;
- 配合IDLE中断识别帧边界;
- 自建环形缓冲区管理接收流;

❌ 现象三:发送卡死,程序停在HAL_UART_Transmit()

这是因为你用了HAL_MAX_DELAY,一旦硬件异常(比如TX引脚悬空),函数就会一直等下去。

✅ 正确做法:

HAL_StatusTypeDef ret = HAL_UART_Transmit(&huart1, data, size, 100); // 超时100ms if (ret != HAL_OK) { // 处理超时或错误 }

如何让UART更可靠?三点设计经验分享

UART虽简单,但也得用心设计才能稳定运行。以下是我在工业项目中的几点心得:

1. 加协议头+CRC校验

原始UART只是传原始字节流,容易误判。建议自定义简单协议,例如:

[0xAA][0x55][LEN][DATA...][CRC]
  • 帧头标识开始位置;
  • 长度字段告知后续多少字节;
  • CRC校验防止数据出错。

这样即使中间出现干扰,也能有效过滤无效帧。

2. 长距离通信走RS485

普通TTL电平只能传几米。超过10米建议换成RS485总线,抗干扰强、支持多点通信。

只需加个SP3485芯片,即可将UART转为差分信号,轻松实现百米级通信。

3. 调试信息重定向到printf

不想每次都写UART_SendString("xxx")?可以重定向printf到串口:

int __io_putchar(int ch) { HAL_UART_Transmit(&huart1, (uint8_t*)&ch, 1, HAL_MAX_DELAY); return ch; }

然后就可以愉快地写:

printf("Temperature: %.2f°C, Humidity: %d%%\r\n", temp, humi);

调试效率瞬间提升一大截。


写在最后:UART不会被淘汰,因为它本就不追求速度

有人问我:“现在都物联网时代了,UART还有前途吗?”

我想说的是:技术不分新旧,只有适不适合

高铁再快,也不能代替家门口的小推车。同理,MQTT再炫酷,也替代不了开机那一句printf("System init done.\n");带给你的安心感。

UART就像嵌入式世界里的“基础工具箱”:螺丝刀、钳子、万用表。它们看起来不起眼,但每一次调试、每一回上线,都离不开它们的身影。

更重要的是——学会UART的过程,其实是学会如何与硬件对话的过程。你知道怎么配置寄存器、怎么处理中断、怎么分析波形……这些底层能力,才是决定你能走多远的关键。

所以别急着追AIoT、边缘计算这些热词。先把UART弄明白,再谈别的。

毕竟,所有伟大的系统,都是从“Hello World”开始的。

如果你也在学习嵌入式通信的路上,欢迎留言交流你的踩坑经历。说不定下一次解决问题的灵感,就藏在评论区里。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/14 17:28:59

Miniconda环境下使用aria2加速大文件下载

Miniconda环境下使用aria2加速大文件下载 在AI模型训练和数据科学项目中&#xff0c;一个常见的瓶颈往往不是算法本身&#xff0c;而是——如何快速、稳定地把几十GB的预训练模型或大规模数据集从远程服务器拉到本地&#xff1f; 你有没有经历过这样的场景&#xff1a;深夜启…

作者头像 李华
网站建设 2026/4/12 22:07:24

Source Han Serif CN字体终极指南:5分钟精通专业中文排版

Source Han Serif CN字体终极指南&#xff1a;5分钟精通专业中文排版 【免费下载链接】source-han-serif-ttf Source Han Serif TTF 项目地址: https://gitcode.com/gh_mirrors/so/source-han-serif-ttf 还在为中文内容排版效果不理想而苦恼吗&#xff1f;Source Han Se…

作者头像 李华
网站建设 2026/4/15 7:10:09

PyTorch图像风格迁移项目启动指南

PyTorch图像风格迁移项目启动指南 在深度学习驱动创意应用的今天&#xff0c;图像风格迁移早已不再是实验室里的概念玩具。从手机滤镜到影视特效&#xff0c;这项技术正悄然改变我们与视觉内容互动的方式。想象一下&#xff1a;只需几行代码&#xff0c;一张普通街景照片就能变…

作者头像 李华
网站建设 2026/4/15 13:42:57

高防护等级工业控制PCB板生产厂家制造揭秘

高防护等级工业控制PCB制造实录&#xff1a;从材料到工艺的硬核拆解在一座位于华南的现代化PCB智能工厂里&#xff0c;一块即将发往某轨道交通项目的控制主板正经历最后一道检测。它将在零下40℃的极寒车厢和持续震动的轨道环境中连续运行十年以上——没有重启、无人维护&#…

作者头像 李华
网站建设 2026/4/14 22:25:12

OpenCore Configurator终极配置指南:从零到精通的黑苹果神器

OpenCore Configurator终极配置指南&#xff1a;从零到精通的黑苹果神器 【免费下载链接】OpenCore-Configurator A configurator for the OpenCore Bootloader 项目地址: https://gitcode.com/gh_mirrors/op/OpenCore-Configurator 作为一名黑苹果爱好者&#xff0c;我…

作者头像 李华
网站建设 2026/4/12 10:53:48

WordCloud2.js 完整教程:打造专业级词云可视化的终极指南

WordCloud2.js 完整教程&#xff1a;打造专业级词云可视化的终极指南 【免费下载链接】wordcloud2.js Tag cloud/Wordle presentation on 2D canvas or HTML 项目地址: https://gitcode.com/gh_mirrors/wo/wordcloud2.js WordCloud2.js 是一款基于 HTML5 Canvas 技术的轻…

作者头像 李华