串口(UART/USART)是单片机内部 的一个通信硬件模块,通过 TX 和 RX 引脚让设备之间以字节为单位交换数据。
UART 是一种:
- 异步(无时钟);字符串式(按字节发送);全双工(收发独立);基于电平高低的通信协议
串口通信是"双向的",你可以同时:
-
用 TX 把调试数据打印到电脑
-
用 RX 让电脑发送命令给 STM32(如电脑发送:"LEDON",STM32 收到后点灯)
常见格式:起始位:1 bit(低电平) ;数据位:8 bit; 校验位:可选; 停止位:1 bit
波特率:9600 / 115200 等
串口发送
一.对比串口通信中Serial_SendXXX和Serial_Printf
串口(UART)只能发送"字节数据",不能直接发送"数字本身"。
1.串口发送的本质:只能发送 0~255 的字节
UART 的发送寄存器(USARTx->DR)每次只能发送1 个字节(8 bit)。
例如:Serial_SendByte(0x41);
串口只知道:我要发一个字节:0x41,它 不知道什么是整数 123、浮点数 3.14,它只会发出一个又一个字节。
2.C 语言里"数字"不是 ASCII 字符
例如数字:123(int 类型),它在内存里的存储是 二进制整数:0x00 0x00 0x00 0x7B
串口如果直接发这些字节,电脑会收到乱码。
但我们希望电脑串口助手看到的是:123 ,也就是字符 '1' '2' '3'。
ASCII 对应是:'1' → 0x31 '2' → 0x32 '3' → 0x33
因此必须先把数字变成字符(字符串)。
Serial_SendNumber(123, 3) 会把整数 123:
1>分解成每个数字:1、2、3
2>把每个数字+0x30 转成 ASCII
3>再通过串口一个一个发出去
最终电脑看到:123
3.如果不转换成字符串,会发生什么?
例如直接发 int 类型:int a = 123;
Serial_SendByte(a); // 错误示例
实际发送的是 0x7B(也就是 123 的低 8 位),终端只会看到乱码或一个不可打印字符。
| 函数名 | 作用 | 是否支持格式化 | 典型用途 |
|---|---|---|---|
| Serial_SendByte | 发送 1 个字节 | ❌ 不支持 | 发送单字符或控制字节 |
| Serial_SendArray | 发送字节数组 | ❌ 不支持 | 发送二进制数据、ASCII 字节流 |
| Serial_SendString | 发送字符串(以 \0 结尾) |
❌ 不支持 | 发送固定内容字符串 |
| Serial_SendNumber | 将数字转为字符串并发送 | ❌(仅数字) | 显示整数、计数等 |
| Serial_Printf | 发送格式化字符串 | ✅ 支持格式化 | 功能类似 printf,用最强大最灵活 |
1.所有 Serial_SendXXX 函数 只能发送已经存在的内容:
-
要么是固定字符串
"Hello" -
要么是数字
123 -
要么是字节数组
{0x41, 0x42,...}
它们不能自动组合字符串、不能插入变量、不能格式化内容。
2.Serial_Printf 能做更多:支持格式化、变参、字符串构造
比如:Serial_Printf("温度=%.2f C, 湿度=%d%%\r\n", temp, hum);
它会自动:格式化字符串(构造最终字符串)把 "温度=21.53 C, 湿度=45%" 通过串口发出去
相当于**:** sprintf(buffer, ...); Serial_SendString(buffer);
但开发者不用自己写缓冲区,因为已经封装好了。
3.使用场景对比
🔹 使用 Serial_SendXXX 的场景
-
发送固定内容(例如指令、固定格式)
-
调试简单输出
-
发送二进制数据(例如传输结构体、传感器原始数据)
-
节省程序空间(不需要 printf 库)
🔹 使用 Serial_Printf 的场景
-
输出调试信息
-
输出变量值
-
输出格式化数据(浮点、整型、字符串混合等)
-
需要像
printf一样灵活的输出
4.总结:一句话区分它们
✨ Serial_SendXXX:直接发已有内容,不格式化
("我给你什么,你就发什么")
✨ Serial_Printf:先拼接格式化字符串,再发送
("我告诉你模板,你自动把内容拼好再发")
二.三种常见在嵌入式系统中输出格式化字符串的做法
方法1:直接重定向printf,但printf函数只有一个,此方法不能在多处使用
printf("\r\nNum2=%d", 222);
串口发送printf打印的格式化字符串,需要重定向fputc函数,并在工程选项里勾选Use MicroLIB
解释:
1>你用 printf() 想把内容输出到串口,但printf 默认输出到"标准输出 stdout**"。** 在电脑上,stdout = 屏幕,在 STM32 上,stdout = 没有实际设备(默认什么都不输出),所以如果你直接写:printf("Hello"); STM32 不知道"要送到哪里",所以 不会显示、也不会自动发到串口。printf 发送每个字符时,调用 fputc() 函数输出单个字符。所以你要告诉 fputc:"每次 printf 输出字符,都帮我通过串口发送"。这样做,所有 printf() 输出内容 → 自动发送到串口助手。如 printf("Num=%d\r\n", 123); 会通过 USART(串口)打印到电脑。
2>在工程选项里勾选 Use MicroLIB"是什么意思?
这是 Keil MDK 的一个选项,MicroLIB = 微型 C 标准库**,**勾上后:含有更小的 printf库,代码更小,适合 Flash 很小的 MCU更好支持用户重定向 printf
Keil → Options for Target →Target→ Use MicroLIB ✔

方法2:使用sprintf打印到字符数组,再用串口发送字符数组,此方法打印到字符数组,之后想怎么处理都可以,可在多处使用
char String[100];
sprintf(String, "\r\nNum3=%d", 333);
Serial_SendString(String);
定义字符数组,使用sprintf,把格式化字符串打印到字符数组串口发送字符数组(字符串)
**方法3:**将sprintf函数封装起来,实现专用的printf,此方法就是把方法2封装起来,更加简洁实用,可在多处使用
Serial_Printf("\r\nNum4=%d", 444); //串口打印字符串,使用自己封装的函数实现printf的效果
Serial_Printf("\r\n");
三.串口打印实现过程
1.Serial_Printf("Num=%d\r\n", 444);
会让单片机做以下事情:
① 把字符串格式化为 "Num=444\r\n"
② 一个字节一个字节写入串口的发送寄存器 USARTx->DR
③ 串口硬件把这些字节调制成 UART 信号
④ 通过 TX 引脚(如 PA9)发送出去
2.电脑并不能直接接收 UART 信号所以需要一个 USB 转串口(TTL)模块,如 CH340、CP2102。
STM32 TX ------> USB串口模块 RX
STM32 RX ------> USB串口模块 TX(如果需要接收)
STM32 GND ------> USB串口模块 GND
模块通过 USB 线连接电脑。
- 电脑串口助手(Serial Assistant)显示数据
电脑打开串口助手,当串口助手检测到数据从 USB 来,就显示出来。
最终你会看到:Num=444
四. 数据流的全过程
Serial_Printf()
↓
格式化字符串
↓
Serial_SendByte()
↓
USART1->DR(数据寄存器)
↓
串口硬件把数据从 TX 引脚送出去
↓
USB 转串口模块(CH340等)
↓
USB 电缆
↓
电脑驱动(虚拟串口 COMx)
↓
串口助手显示出来
总结:串口打印的数据永远是从 STM32 单片机发出来的,最后显示在电脑的串口助手软件中。
在 STM32 芯片内部,大概是这样:

DR 寄存器在 STM32 内部, 数据从 DR → TX 引脚,然后通过 USB 转串口模块 → 电脑 → 串口助手
串口助手显示的数据来自STM32 内部 USART 外设(DR 寄存器)发送的数据
字符 → 写入 STM32 内部 DR → 移位寄存器 → TX 引脚 → USB 转串口 → 电脑串口助手
烧录程序:CPU把"程序代码"写入 STM32 的 Flash(程序存储器)里。
串口发送:把"数据"写入 STM32 内部的 USART 寄存器 DR,再从 TX 引脚发出去。
写 Flash = 烧录程序,写寄存器 = 运行时操作(通信)
串口接收
一.串口接收的过程
TX 发出的电压波形 → MCU RX 引脚接收到
RX 引脚 → 串口硬件 → 移位寄存器 → DR寄存器
触发接收中断 → 程序读数据
电脑发送数据----波形来到 STM32 的 RX 引脚----串口硬件自动"采样并解码"----串口硬件把字节放进接收寄存器 DR----硬件置位 RXNE(接收非空标志,RXNE = 1 ,意味着 DR里有数据可读)----如果开了接收中断,会进入 USART1_IRQHandler----应用层使用收到的数据电脑 → 发送 "1"(如STM32 → 控制LED亮)