news 2026/8/26 19:42:13

串口通信协议在STM32中的配置:手把手教程(从零实现)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
串口通信协议在STM32中的配置:手把手教程(从零实现)

从寄存器到通信:在STM32上手写一个可靠的串口驱动

你有没有遇到过这样的情况——项目紧急,板子焊好了,代码跑不起来,串口没输出?用CubeMX生成的代码太大,Bootloader里塞不下;HAL库又太“重”,还藏着你看不见的坑。这时候,如果能自己动手、从零配置一个干净利落的串口通信,那感觉,就像在荒野中点亮了一盏灯。

今天我们就来干这件事:不用任何库函数,不依赖CubeMX,直接操作寄存器,在STM32F103上实现一套完整的中断式串口收发系统。这不是理论课,是实打实的“裸机编程”实战,适合想真正搞懂外设机制的工程师,也适合需要精简固件或调试底层问题的开发者。


为什么还要学寄存器级串口配置?

你说现在都有HAL和LL库了,动动鼠标就能出代码,为啥还要啃寄存器?

因为——当你面对的是Bootloader、安全启动、资源极度受限的MCU,或者某个莫名其妙丢数据的问题时,那些封装好的API反而成了黑盒

而当你亲手写过一遍USART->BRR = ...,调过一次NVIC优先级,清过一次溢出标志位,你就知道:

  • 哪些步骤不能少
  • 哪些顺序不能乱
  • 哪些错误会悄悄吞噬你的数据

这才是嵌入式开发的底气。


先看一眼硬件:USART到底是怎么工作的?

我们常说“串口”,其实在STM32里叫USART(通用同步/异步收发器)。它支持同步和异步两种模式,但我们最常用的,就是异步串行通信(UART模式)

它的基本帧结构很简单:

[起始位] [数据位(8)] [校验位(可选)] [停止位(1)]

发送方和接收方没有共用时钟,全靠事先约定好的波特率来对齐采样点。比如115200bps,意味着每秒传115200个比特。

STM32内部是怎么做到精准控制这个节奏的呢?核心靠三个部分:

  1. 波特率发生器:基于PCLK分频,算出一个叫USARTDIV的值,填进BRR寄存器;
  2. 移位寄存器:把并行字节变成一位一位往外推;
  3. 状态机与中断逻辑:告诉你“可以发了”、“有数据来了”。

整个过程不需要CPU一直盯着,只要设置好中断,剩下的交给硬件自动完成。


第一步:打开时钟——所有初始化的前提

在STM32的世界里,一切外设操作都始于时钟使能。没电的东西,再聪明也没用。

我们要用的是 USART1,它是挂在 APB2 总线上的(比APB1快),对应的IO口是PA9(Tx)、PA10(Rx),属于GPIOA。

所以第一步,必须打开这三个时钟:
- GPIOA 时钟
- AFIO 时钟(即使不用重映射,F1系列也要开)
- USART1 时钟

RCC->APB2ENR |= RCC_APB2ENR_IOPAEN // 使能GPIOA | RCC_APB2ENR_AFIOEN // 使能AFIO(F1必需) | RCC_APB2ENR_USART1EN; // 使能USART1

⚠️ 注意顺序:一定要先开时钟,再配置引脚!否则你写的寄存器可能根本不起作用。


第二步:配置GPIO复用——让PA9和PA10“改行”

默认情况下,PA9和PA10就是普通IO口。要让它变成串口Tx/Rx,就得启用“复用功能”(Alternate Function)。

对于STM32F1系列:
- PA9 → USART1_Tx → 复用推挽输出
- PA10 → USART1_Rx → 浮空输入(也可以上拉)

我们通过GPIOA->CRH寄存器来配置这两个引脚(因为它们属于高8位端口PIN8~15):

// 清除PA9和PA10原有配置 GPIOA->CRH &= ~(0xFF << 4); // 清CRH中CNF9和MODE9 GPIOA->CRH &= ~(0xF << 8); // 清CNF10和MODE10 // 配置PA9为复用推挽输出,最大速度50MHz GPIOA->CRH |= (0xB << 4); // 1011: MODE=11, CNF=11 → AF PP // 配置PA10为浮空输入 GPIOA->CRH |= (0x4 << 8); // 0100: MODE=00, CNF=01 → Floating Input

🔍 小知识:为什么Tx要用推挽?为了驱动能力强,信号边沿陡峭;Rx设为浮空是因为通常由外部设备拉低,内部无需主动驱动。


第三步:计算并设置波特率

这是最容易出错的地方之一。很多人以为随便设个数就行,结果通信不稳定、乱码频发。

STM32使用公式:

$$
\text{Baud Rate} = \frac{f_{PCLK}}{16 \times \text{USARTDIV}}
$$

其中USARTDIV是一个带小数的数,存储在BRR寄存器中,格式为:
- 高12位:整数部分(DIV_Mantissa)
- 低4位:小数部分(DIV_Fraction)

假设系统时钟72MHz,PCLK2也是72MHz(USART1挂APB2),目标波特率115200:

$$
\text{USARTDIV} = \frac{72000000}{16 \times 115200} ≈ 39.0625
$$

于是:
- 整数部分 = 39
- 小数部分 = 0.0625 × 16 ≈ 1

所以BRR = (39 << 4) | 1

USART1->BRR = (39 << 4) | 1;

✅ 推荐做法:写一个宏来自动生成BRR值,避免手动计算错误。

#define UART_BRR(pclk, baud) ((pclk) / (baud * 16))

但注意,这只是一个近似值,实际应用中建议测试误差是否小于3%。


第四步:配置USART控制寄存器

接下来是关键一步:告诉USART我要干什么。

我们需要设置:
- 使能发送(TE)和接收(RE)
- 使能接收中断(RXNEIE)
- 使能USART本身(UE)

这些都在CR1寄存器里搞定:

USART1->CR1 = 0; // 先清零,避免残留位影响 USART1->CR1 |= USART_CR1_TE // 使能发送 | USART_CR1_RE // 使能接收 | USART_CR1_RXNEIE // 使能接收中断 | USART_CR1_UE; // 启动USART

其他参数如数据位(8/9)、校验位等也可以在这里设置:
-M位控制字长(0=8位,1=9位)
-PCEPS控制是否开启奇偶校验

目前我们用最常见组合:8N1(8数据位,无校验,1停止位),保持默认即可。


第五步:开启NVIC中断,让CPU响应事件

光开了外设中断还不行,还得让CPU的中断控制器(NVIC)允许这个中断进来。

USART1 的中断向量号是 37,在CMSIS中定义为USART1_IRQn

NVIC_EnableIRQ(USART1_IRQn);

如果你的系统中有多个中断,还可以设置优先级:

NVIC_SetPriority(USART1_IRQn, 5); // 设置优先级为5

这样当数据到来时,CPU就会暂停当前任务,跳转到中断服务程序去处理。


第六步:编写中断服务函数——真正干活的地方

中断来了之后做什么?两件事:
1. 判断是不是接收中断(RXNE置位)
2. 读取DR寄存器拿数据,并存入缓冲区

这里我们引入一个环形缓冲区,防止高速输入时数据被覆盖。

#define RX_BUFFER_SIZE 64 uint8_t rx_buffer[RX_BUFFER_SIZE]; volatile uint16_t rx_head = 0; // 写指针 void USART1_IRQHandler(void) { if (USART1->SR & USART_SR_RXNE) { // 接收到新数据? uint8_t data = USART1->DR; // 读DR自动清除标志 rx_buffer[rx_head % RX_BUFFER_SIZE] = data; rx_head++; } }

🧠 关键点解释:
-volatile是必须的,告诉编译器别优化掉这个变量。
-% RX_BUFFER_SIZE实现循环索引,空间满了就从头覆盖(可根据需求改为防溢出策略)。
- 读DR寄存器的同时会清除RXNE标志,这是硬件设计决定的。


补充:实现非阻塞发送

虽然接收用了中断,但发送我们可以选择更灵活的方式。

方式一:轮询发送(简单实用)

适用于偶尔发几个字节,比如打印调试信息。

void usart_send_byte(uint8_t byte) { while (!(USART1->SR & USART_SR_TXE)); // 等待发送寄存器空 USART1->DR = byte; } void usart_send_string(const char* str) { while (*str) { usart_send_byte(*str++); } }

方式二:中断发送(适合连续大数据)

如果你想发一包数据而不卡主线程,可以用TXE中断。

思路是:
- 第一个字节手动写入DR触发发送;
- 每次TXE中断检查是否有更多数据要发;
- 发完最后一个字节后关闭TXEIE。

这种方式稍复杂,但在实时系统中很有价值。


常见“踩坑”与调试秘籍

别以为写了代码就万事大吉,下面这些坑我全都踩过:

❌ 数据乱码?

  • 检查PCLK频率是否正确(尤其是倍频后);
  • 波特率计算有没有四舍五入偏差;
  • 双方是否都是8N1?Windows串口助手常默认有校验位!

❌ 收不到中断?

  • NVIC有没有使能?
  • 是否忘了开全局中断__enable_irq()
  • 引脚接反了?Tx连Tx?

❌ 缓冲区溢出?

  • 中断处理太慢,主循环来不及消费数据;
  • 解决方案:加DMA,或者提高中断优先级。

❌ ORE(溢出错误)频繁出现?

  • CPU来不及处理上一条数据,新数据又到了;
  • 检查中断是否被长时间屏蔽(比如关总中断太久);
  • 考虑增加硬件流控或降低波特率。

实际应用场景举例

这套轻量级串口驱动特别适合以下场景:

✅ Bootloader通信协议

  • 接收PC下发的固件包
  • 使用简单的帧格式(如SOH + LEN + DATA + CRC + EOT
  • 主循环解析命令,中断负责收数据

✅ 传感器数据透传

  • STM32采集ADC、温湿度,通过串口转发给网关
  • 不用手动轮询,节省功耗

✅ 多机协同控制

  • 多块STM32之间用串口组网
  • 每台分配地址,支持广播/点对点

进阶方向:你可以继续做什么?

你现在掌握的只是一个起点。下一步可以尝试:

  • 加入DMA:实现零CPU干预的大批量收发;
  • 实现软件流控:用XON/XOFF应对缓冲压力;
  • 添加超时机制:识别不定长帧结束(如JSON字符串);
  • 封装成标准接口:提供read()/write()类POSIX API;
  • 移植到其他型号:F4/F7/H7系列寄存器略有不同,但原理一致。

甚至可以用这套思想去理解Linux下的TTY子系统——底层逻辑永远相通。


写在最后:回归本质的力量

在这个动辄调用.init()函数的时代,愿意静下心来看一眼BRR寄存器的人越来越少。

但正是这些看似“过时”的技能,让你在芯片启动的第一毫秒就能掌控全局,在别人还在查HAL库bug的时候,你已经把第一行日志打出来了。

真正的自由,不是依赖多少工具,而是知道工具背后的真相

下次当你面对一块新的MCU,没有CubeMX支持,也没有现成例程时,希望你能想起今天这一课:
从时钟开始,一步步点亮串口,让两个世界第一次对话。

如果你实现了自己的串口驱动,欢迎在评论区分享你的经验。

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

G-Helper深度体验报告:华硕ROG笔记本的轻量化控制革命

G-Helper深度体验报告&#xff1a;华硕ROG笔记本的轻量化控制革命 【免费下载链接】g-helper Lightweight Armoury Crate alternative for Asus laptops. Control tool for ROG Zephyrus G14, G15, G16, M16, Flow X13, Flow X16, TUF, Strix, Scar and other models 项目地址…

作者头像 李华
网站建设 2026/8/20 9:30:27

商业场景实战:用Youtu-2B快速搭建企业智能客服系统

商业场景实战&#xff1a;用Youtu-2B快速搭建企业智能客服系统 1. 引言 在数字化转型加速的今天&#xff0c;企业对高效、低成本客户服务的需求日益增长。传统人工客服面临响应慢、成本高、服务质量不稳定等问题&#xff0c;而基于大语言模型&#xff08;LLM&#xff09;的智…

作者头像 李华
网站建设 2026/8/25 21:35:51

终极指南:Windows 11安卓子系统WSA一键配置教程

终极指南&#xff1a;Windows 11安卓子系统WSA一键配置教程 【免费下载链接】WSA-Script Integrate Magisk root and Google Apps into WSA (Windows Subsystem for Android) with GitHub Actions 项目地址: https://gitcode.com/gh_mirrors/ws/WSA-Script 在Windows 11…

作者头像 李华
网站建设 2026/8/25 21:35:50

GTE中文语义相似度计算详细指南:领域适配方法

GTE中文语义相似度计算详细指南&#xff1a;领域适配方法 1. 引言 随着自然语言处理技术的不断演进&#xff0c;语义相似度计算已成为信息检索、问答系统、文本去重和推荐系统等场景中的核心能力。传统的关键词匹配方法难以捕捉文本间的深层语义关联&#xff0c;而基于预训练…

作者头像 李华
网站建设 2026/8/23 20:18:23

Qwen3-4B-Instruct-2507部署教程:智能写作助手完整实现

Qwen3-4B-Instruct-2507部署教程&#xff1a;智能写作助手完整实现 1. 简介 Qwen3-4B-Instruct-2507 是阿里云推出的一款高性能开源大语言模型&#xff0c;专为指令遵循和复杂任务理解设计。该模型在多个维度实现了显著优化&#xff0c;适用于智能写作、内容生成、编程辅助、…

作者头像 李华
网站建设 2026/8/22 5:57:33

批量生成数字人视频?HeyGem这几点你必须知道

批量生成数字人视频&#xff1f;HeyGem这几点你必须知道 1. 系统核心功能与使用场景解析 HeyGem 数字人视频生成系统是一款基于 AI 驱动的音视频合成工具&#xff0c;专注于实现语音与数字人口型的高度同步。其最大亮点在于支持批量处理模式&#xff0c;能够将一段音频自动匹…

作者头像 李华