news 2026/7/9 12:21:57

新手必学串口通信协议:全面讲解起始位与停止位

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
新手必学串口通信协议:全面讲解起始位与停止位

串口通信的灵魂:起始位与停止位,你真的懂了吗?

在嵌入式开发的日常中,UART 几乎是每个工程师最早接触、也最常使用的通信接口之一。它不像 SPI 那样需要四根线,也不像 I2C 那样要处理地址和仲裁——一根 TX,一根 RX,连上就能“说话”。但正是这种看似简单的协议背后,藏着一套精巧的时间机制,而其中最关键的两个角色,就是起始位停止位

它们不携带任何有效数据,却决定了整个通信能否正确进行。如果你曾经遇到过串口接收乱码、帧错误频繁触发、或者设备偶尔“失联”的问题,很可能不是代码写错了,而是你还没真正理解这两个“沉默的守门人”。


为什么异步通信需要“信号灯”?

我们先来思考一个根本问题:没有共同时钟线的情况下,接收端怎么知道什么时候开始读数据?

想象两个人用对讲机通话,如果一方突然开始讲话,另一方正在走神,就可能错过开头几个字。串口通信也有同样的难题——发送端随时可以发数据,接收端必须能立刻察觉并准确采样每一位。

这就是起始位存在的意义:它是一个强制同步信号,就像一声哨响,告诉接收方:“注意!接下来我要传数据了,请立即启动你的内部时钟。”

空闲状态:高电平才是常态

在 UART 中,线路空闲时保持高电平(逻辑1)。这是约定俗成的标准(TTL/RS-232 均如此)。当没有任何数据传输时,TX 和 RX 都处于高电平状态。

一旦有数据要发送,发送端做的第一件事不是直接发数据,而是先拉低一个比特时间——这个低电平,就是起始位

✅ 关键点:起始位永远是逻辑0,持续时间为1个波特率周期

比如在 9600 bps 下,每个比特宽约 104.17 μs,那么起始位就是在这段时间内保持低电平。


起始位如何实现“自同步”?

让我们拆解一下接收端的动作:

  1. 接收器不断检测 RX 线上的电平变化;
  2. 一旦发现从高到低的跳变(falling edge),就认为这是起始位到来;
  3. 此时立即启动一个定时器,设定为半个波特率周期后进行第一次采样;
  4. 之后每隔一个完整波特率周期采样一次,确保在每一位的中间位置读取电平值(抗干扰最佳点);
  5. 连续采样 8 次(假设数据位为8),完成一个字节的恢复。

这种设计非常聪明:通过一个边沿触发,建立起局部时间基准,从而实现了无需外部时钟的异步同步

🧠小贴士:现代 UART 多采用 16 倍过采样技术,即每比特时间内采样 16 次,取中间几次的结果做判断,进一步提升抗噪声能力。


数据帧长什么样?一张图胜千言

一个典型的 UART 数据帧结构如下:

[起始位] [D0] [D1] [D2] [D3] [D4] [D5] [D6] [D7] [校验位?] [停止位(s)] ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ 0 LSB MSB (可选) 1 or 1.5 or 2 bits
  • 起始位:固定低电平,1 bit
  • 数据位:通常 5~9 位,常用 8 位,低位先行(LSB First)
  • 校验位:可选奇偶校验,用于简单检错
  • 停止位:固定高电平,长度可配置

看到没?真正的“数据”只占一小部分,其余全是辅助信息。但这恰恰体现了工程上的权衡:用少量开销换取更高的可靠性。


停止位:不只是“结束标记”,更是容错窗口

如果说起始位是“开始的号角”,那停止位就是“安全的句号”。

它的作用远不止于表示一帧结束:

1. 提供恢复时间

MCU 在处理完一帧数据后,可能需要执行中断服务、更新缓冲区或切换任务。停止位的存在给了系统一段“喘息时间”,避免下一帧紧随其后导致漏判。

2. 实现帧边界确认

接收端在完成所有数据位采样后,会检查接下来是否真的是高电平(停止位)。如果不是,说明:
- 波特率不匹配
- 时钟漂移过大
- 信号受到干扰

此时硬件会置位Framing Error(帧错误)标志,提醒开发者通信异常。

3. 容忍时钟误差

实际应用中,发送端和接收端的晶振不可能完全一致。一般允许 ±5% 的频率偏差。更长的停止位(如 1.5 或 2 位)相当于增加了“安全裕量”,降低因时钟不同步导致误码的概率。

停止位长度使用场景
1 bit标准配置,高速稳定信道(如板内通信)
1.5 bit低速老旧设备(<300bps),现已少见
2 bits长距离、噪声环境、工业现场(如 RS-485)

📌注意:1.5 位停止位仅适用于特定波特率组合,且并非所有芯片都支持,使用前需查阅手册。


实战中的坑:配置不一致,神仙难救

我在调试某款 GPS 模块时曾遇到这样一个问题:STM32 接收到的数据总是错一位,’GPGGA’ 变成了 ‘PGGAA’,像是整体左移了一位。

排查良久才发现,原来是 GPS 模块默认输出2 位停止位,而我的 UART 初始化配置的是1 位停止位

结果接收端在第 9 个比特(原应是停止位)结束后,立刻又等下一个起始位下降沿。但由于真实停止位更长,它误把延长的高电平中间当作下一个起始位的下降沿,造成采样偏移,最终解码错乱。

解决方案:统一双方帧格式!

推荐新手使用行业通用标准配置:
👉9600, 8-N-1
即:9600 波特率,8 数据位,无校验,1 停止位

这组参数兼容性最好,绝大多数传感器、蓝牙模块、调试工具都默认支持。


代码里的真相:起始位不用“写”,但得会“看”

很多初学者疑惑:“我在代码里明明没写起始位,它是怎么加上去的?”

答案是:硬件自动添加

以 STM32 HAL 库为例:

UART_HandleTypeDef huart1; void MX_USART1_UART_Init(void) { huart1.Instance = USART1; huart1.Init.BaudRate = 9600; huart1.Init.WordLength = UART_WORDLENGTH_8B; huart1.Init.StopBits = UART_STOPBITS_1; huart1.Init.Parity = UART_PARITY_NONE; huart1.Init.Mode = UART_MODE_TX_RX; HAL_UART_Init(&huart1); } // 发送数据 HAL_UART_Transmit(&huart1, (uint8_t*)"A", 1, 100);

你看不到起始位的操作,但它确实存在。当你调用HAL_UART_Transmit()时,硬件会在数据 ‘A’(0x41)前自动插入一个低电平起始位,并在末尾补上高电平停止位,然后逐位串行发出。

反过来说,你在逻辑分析仪上看到的波形,才是真正完整的通信过程。


如何捕捉通信异常?中断中的帧错误检测

除了主动配置,我们还需要被动防御。UART 外设通常提供多种错误标志位,其中最重要的是帧错误(Framing Error)

以下是在中断中检测帧错误的典型做法:

void USART1_IRQHandler(void) { // 检查是否发生帧错误 if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_FE)) { __HAL_UART_CLEAR_FEFLAG(&huart1); // 清除标志位 Handle_Framing_Error(); // 自定义处理函数 } // 正常接收数据 if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE)) { uint8_t data = huart1.Instance->RDR; Process_Received_Data(data); } }

💡什么时候会触发帧错误?
- 接收端未在预期时间内检测到高电平停止位
- 波特率严重不匹配(如一端 9600,另一端 115200)
- 强电磁干扰导致信号畸变
- 发送端异常复位或电源波动

一旦出现 FE,说明通信已经不可靠,建议重新初始化或提示用户检查接线。


工程实践建议:从原理出发的设计思维

1. 优先选用标准帧格式

除非特殊需求,一律使用8-N-1。减少沟通成本,避免“我以为你是1位,其实你是2位”的尴尬。

2. 差分信号更可靠

对于长距离或工业环境,不要用 TTL 电平直连!改用RS-485RS-232等差分/高压电平标准,抗干扰能力强得多。

3. 合理选择波特率

常见波特率如 9600、19200、115200 等都是基于传统 UART 时钟源(1.8432MHz)分频而来。若自定义波特率,务必确认误差 < 2%,否则容易出错。

可用公式估算:

误差 = |(实际波特率 - 目标波特率)| / 目标波特率 × 100%

4. 必要时加入校验

虽然现代系统多用 CRC 或上层协议保障完整性,但在资源受限场景下,启用偶校验(Even Parity)仍是一种低成本的检错手段。


写在最后:看得见每一位,才做得好通信

串口看似简单,实则处处是细节。起始位和停止位虽不起眼,却是整个异步通信得以成立的基础。

下次当你用串口助手看到一行清晰的日志时,不妨想一想:
那一串字符的背后,有多少个精准跳变的起始位,有多少段安静等待的停止位,在默默守护着每一次正确的传输。

掌握这些底层机制,不仅让你在调试时更有底气,也为深入学习其他通信协议(如 CAN、I2S、甚至是自定义 Bit-Banging 协议)打下坚实基础。

如果你也曾在串口通信中踩过坑,欢迎在评论区分享你的“血泪史”——也许正是那个小小的停止位,改变了你对嵌入式的理解。

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

Universal Ctags终极指南:让代码阅读效率翻倍

作为一名开发者&#xff0c;你是否曾为在庞大代码库中迷失方向而苦恼&#xff1f;想要快速定位某个函数定义却不得不在文件间反复切换&#xff1f;Universal Ctags正是为解决这一痛点而生的强大工具。本文将带你从零开始&#xff0c;掌握这款能显著提升开发效率的代码索引神器。…

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

如何在Mac上快速制作Windows启动盘:WinDiskWriter完整教程

如何在Mac上快速制作Windows启动盘&#xff1a;WinDiskWriter完整教程 【免费下载链接】windiskwriter &#x1f5a5; A macOS app that creates bootable USB drives for Windows. &#x1f6e0; Patches Windows 11 to bypass TPM and Secure Boot requirements. 项目地址:…

作者头像 李华
网站建设 2026/7/1 0:58:46

PaddlePaddle训练日志解读:准确率、损失值变化怎么看?

PaddlePaddle训练日志解读&#xff1a;准确率、损失值变化怎么看&#xff1f; 在深度学习项目中&#xff0c;模型跑起来只是第一步。真正决定成败的&#xff0c;是能否“读懂”它在训练过程中留下的每一条线索——尤其是控制台里不断刷新的准确率&#xff08;Accuracy&#xff…

作者头像 李华
网站建设 2026/7/1 10:37:10

18、C 用户自定义类型转换全解析

C# 用户自定义类型转换全解析 在 C# 编程中,用户自定义类型转换是一项强大的功能,它允许我们在类或结构体与系统中的其他对象之间定义转换规则。下面我们将深入探讨这一功能,通过丰富的示例和详细的解释,帮助你全面掌握用户自定义类型转换的使用方法和设计原则。 1. 用户…

作者头像 李华
网站建设 2026/7/1 8:08:24

Chunker:Minecraft世界转换的终极解决方案

Chunker&#xff1a;Minecraft世界转换的终极解决方案 【免费下载链接】Chunker Convert Minecraft worlds between Java Edition and Bedrock Edition 项目地址: https://gitcode.com/gh_mirrors/chu/Chunker 还在为不同设备间的Minecraft存档迁移而烦恼吗&#xff1f;…

作者头像 李华