tags: [source-summary] type: source source: "尚硅谷嵌入式技术之STM32单片机(基础篇)V1.0.2 — 第8章:USART通信 + 配套代码07~12" author: "尚硅谷研究院" date: 2026-07-15
用生活理解:USART 串口通信就像两个人之间拉了一根电话线——一次只能一个人说(半双工),或者可以两个人同时说(全双工)。双方约定好说话的速度(波特率)、一句话的格式(数据位+校验+停止位),才能正常沟通。printf 重定向就是给这个电话线配了一个自动翻译机——把 printf 的内容自动通过串口发出去。
| 分类 | 定义 | 示例 |
|---|---|---|
| 单工 | 数据只在一个方向传输 | 广播 |
| 半双工 | 双方可以交替发送,但不能同时 | 对讲机 |
| 全双工 | 双方可同时发送 | 电话 |
| 同步 | 有时钟线同步 | SPI、I2C |
| 异步 | 无时钟线,约定波特率 | USART |
USART = Universal Synchronous/Asynchronous Receiver/Transmitter 通用同步/异步收发器。STM32F103 的 USART1 支持同步模式,USART2/3 仅异步。
空闲 起始位 数据位(8/9位) 校验位(可选) 停止位(1/2位)
──────┐ ┌──┐──┐──┐──┐──┐──┐──┐──┐──┌──┐────┐────────
│ │ │ │ │ │ │ │ │ │ │ │ │
──────┘──┘ └──┘──┘──┘──┘──┘──┘──┘──┘──┘────┘────────
↑ LSB(先发) MSB(后发)
| 段 | 位数 | 说明 |
|---|---|---|
| 起始位 | 1 | 拉低电平,表示开始发送 |
| 数据位 | 8或9 | 先发 LSB(最低有效位) |
| 校验位 | 0或1 | 奇校验/偶校验/无校验 |
| 停止位 | 1/1.5/2 | 拉高电平,表示传输结束 |
校验位计算:
波特率 = 每秒传输的位数(bps)。
常用值:9600、19200、38400、115200、921600
STM32 USART 波特率计算公式:
Tx/Rx 波特率 = fCK / (16 × USARTDIV)
其中: fCK = USART 时钟频率
USARTDIV = BRR 寄存器中写入的分频值(含小数部分)
示例:fCK=72MHz(USART1 挂在 APB2),目标 115200bps USARTDIV = 72,000,000 / (16 × 115200) = 39.0625 BRR 寄存器写入值 = 0x271
参考:参考手册 §18(USART 寄存器描述)
各 USART 模块的频率差异:
| USART | 总线 | 时钟频率 |
|---|---|---|
| USART1 | APB2 | 72MHz |
| USART2 | APB1 | 36MHz |
| USART3 | APB1 | 36MHz |
注意:USART2/3 只有 APB1 时钟的一半,相同 BRR 值下波特率也减半。
关键组成部分:
参考:参考手册 §18 中的 USART 功能框图
| 寄存器 | 地址偏移 | 功能 |
|---|---|---|
| SR | 0x00 | 状态寄存器(TXE、RXNE、IDLE、TC 等标志位) |
| DR | 0x04 | 数据寄存器(发送/接收共用) |
| BRR | 0x08 | 波特率寄存器(USARTDIV 值) |
| CR1 | 0x0C | 控制寄存器1(UE、TE、RE、M、PCE、PS、TXEIE、RXNEIE) |
| CR2 | 0x10 | 控制寄存器2(STOP 位配置、LINEN 等) |
| CR3 | 0x14 | 控制寄存器3(CTSE、RTSE、DMAT、DMAR) |
SR 状态寄存器的重要位:
| 位 | 名称 | 说明 | 用途 |
|---|---|---|---|
| 5 | RXNE | 接收数据寄存器非空 | 有数据收到时=1,读DR后自动清0 |
| 6 | TC | 发送完成 | 发送完成后=1 |
| 7 | TXE | 发送数据寄存器空 | 可写入数据时=1,写DR后自动清0 |
| 4 | IDLE | 空闲线路检测 | 收到空闲帧(高电平超过1帧)时=1 |
USART1 使用 PA9(TX)和 PA10(RX):
通常还需要连接 CH340G(USB转串口芯片)与电脑通信。
参考:开发板原理图串口部分、CH340G 数据手册
通过串口发送字符/字符串,接收并回显收到的数据。波特率 115200,8N1(8 数据位、无校验、1 停止位)。
项目路径:stm32/07_usart_polling_register
文件:stm32/07_usart_polling_register/Hardware/USART/usart.h
#ifndef __USART_H
#define __USART_H
#include "stm32f10x.h"
void USART_Init(void);
void USART_SendChar(uint8_t ch);
uint8_t USART_ReceiveChar(void);
void USART_SendString(uint8_t *str, uint8_t size);
void USART_ReceiveString(uint8_t buffer[], uint8_t *size);
#endif
文件:stm32/07_usart_polling_register/Hardware/USART/usart.c
#include "usart.h"
// USART1 初始化(PA9-TX, PA10-RX, 115200-8N1)
void USART_Init(void)
{
/* ===== 1. 开启时钟 ===== */
// USART1 挂载在 APB2 总线上
RCC->APB2ENR |= RCC_APB2ENR_USART1EN; // |=: USART1时钟使能
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN; // |=: GPIOA时钟使能
/* ===== 2. 配置GPIO模式 ===== */
// PA9 (TX): 复用推挽输出 CNF=10, MODE=11(50MHz)
// CRH控制Pin8~Pin15, PA9的位域在第4~7位
GPIOA->CRH |= GPIO_CRH_MODE9; // MODE9[1:0]=11
GPIOA->CRH |= GPIO_CRH_CNF9_1; // CNF9[1]=1
GPIOA->CRH &= ~GPIO_CRH_CNF9_0; // CNF9[0]=0
// PA10 (RX): 浮空输入 CNF=01, MODE=00
GPIOA->CRH &= ~GPIO_CRH_MODE10; // MODE10=00(输入)
GPIOA->CRH &= ~GPIO_CRH_CNF10_1; // CNF10[1]=0
GPIOA->CRH |= GPIO_CRH_CNF10_0; // CNF10[0]=1
/* ===== 3. 配置USART ===== */
// 3.1 波特率 115200 (72MHz / (16 * 115200) = 39.0625 = 0x271)
USART1->BRR = 0x271;
// 3.2 使能USART1、发送、接收
USART1->CR1 |= USART_CR1_UE; // UE=1: USART使能
USART1->CR1 |= (USART_CR1_TE | USART_CR1_RE); // TE=1: 发送使能, RE=1: 接收使能
// 3.3 配置帧格式: 8位数据位, 无校验, 1位停止位
USART1->CR1 &= ~USART_CR1_M; // M=0: 8位数据位
USART1->CR1 &= ~USART_CR1_PCE; // PCE=0: 无校验
USART1->CR2 &= ~USART_CR2_STOP; // STOP=00: 1位停止位
}
// 发送一个字符(轮询方式)
void USART_SendChar(uint8_t ch)
{
// 等待 TXE (发送数据寄存器空) = 1
// TXE=1 表示 DR 中数据已转移到移位寄存器,可以写入新数据
while ((USART1->SR & USART_SR_TXE) == 0) // &测试: TXE位是否为0
{
}
// 写入数据到 DR,硬件自动移位发送
USART1->DR = ch;
}
// 接收一个字符(轮询方式)
uint8_t USART_ReceiveChar(void)
{
// 等待 RXNE (接收数据寄存器非空) = 1
// RXNE=1 表示 DR 中已收到新数据
while ((USART1->SR & USART_SR_RXNE) == 0)
{
}
return USART1->DR;
}
// 发送字符串
void USART_SendString(uint8_t *str, uint8_t size)
{
for (uint8_t i = 0; i < size; i++)
{
USART_SendChar(str[i]);
}
}
// 接收字符串(检测空闲帧 IDLE 判断结束)
void USART_ReceiveString(uint8_t buffer[], uint8_t *size)
{
uint8_t i = 0;
// 逐字符接收直到检测到空闲帧(IDLE=1)
while ((USART1->SR & USART_SR_IDLE) == 0) // IDLE=0 时说明还在接收数据
{
buffer[i] = USART_ReceiveChar();
i++;
}
// 读取 DR 清除 IDLE 标志(先读SR再读DR)
USART1->DR;
*size = --i; // 减去IDLE引起的多余计数
}
项目路径:stm32/11_usart_printf_register
通过重写 fputc 函数,将 printf 输出重定向到 USART1。
文件:stm32/11_usart_printf_register/User/main.c
#include "stm32f10x.h"
#include <stdio.h> // 使用 printf 需要
// 重写 fputc —— printf 底层调用此函数输出每个字符
// 参数: ch = 要输出的字符, f = 文件流指针(未使用)
// 返回: 输出成功的字符
int fputc(int ch, FILE *f)
{
// USART1 发送一个字符
while ((USART1->SR & USART_SR_TXE) == 0) // 等待 TX 缓冲区空
{
}
USART1->DR = ch; // 写入字符到 DR
return ch;
}
int main(void)
{
USART_Init(); // USART初始化(与轮询版相同)
printf("尚硅谷 STM32 串口 printf 实验\r\n"); // \r\n 是回车换行
printf("当前系统时钟: 72MHz\r\n");
printf("波特率: 115200, 数据格式: 8N1\r\n");
while (1)
{
}
}
Keil 配置:要使用 printf,必须在 Keil 中勾选 "Use MicroLIB"(微库): Options for Target → Target → Code Generation → Use MicroLIB 微库提供了精简版的
printf实现。
HAL 库封装了 USART 的寄存器操作,使用结构体 UART_HandleTypeDef 管理串口。
HAL 库串口配置流程:
1. CubeMX 选择 USART1 异步模式
2. 配置波特率、数据位、校验位、停止位
3. 生成代码
4. HAL_UART_Transmit() 发送 / HAL_UART_Receive() 接收
HAL 库发送接收函数:
// 发送(超时模式)
HAL_UART_Transmit(&huart1, (uint8_t *)"Hello", 5, 1000);
// 参数: huart句柄, 数据指针, 数据长度, 超时ms
// 接收(超时模式)
HAL_UART_Receive(&huart1, buffer, 10, 1000);
// 中断方式接收(推荐)
HAL_UART_Receive_IT(&huart1, buffer, 10);
// 接收10个字节后自动调用 HAL_UART_RxCpltCallback()
printf 重定向(HAL 库版):
// 基于 HAL_UART_Transmit 的重写
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 1000);
return ch;
}
| 函数 | 说明 | 实现要点 |
|---|---|---|
USART_Init() |
时钟+GPIO+波特率+帧格式 | CR1/BRR/CR2 配置 |
USART_SendChar(ch) |
发送1字节 | 轮询 TXE 标志 |
USART_ReceiveChar() |
接收1字节 | 轮询 RXNE 标志 |
USART_SendString(str, len) |
发送字符串 | 循环调用 SendChar |
USART_ReceiveString(buf, &len) |
接收字符串 | 使用 IDLE 判断结束 |
| HAL函数 | 说明 |
|---|---|
HAL_UART_Transmit(huart, data, size, timeout) |
轮询发送 |
HAL_UART_Receive(huart, data, size, timeout) |
轮询接收 |
HAL_UART_Receive_IT(huart, data, size) |
中断接收 |
HAL_UART_Transmit_IT(huart, data, size) |
中断发送 |
HAL_UART_RxCpltCallback(huart) |
接收完成回调 |
_IT 中断方式