title: USART串口 tags: [STM32, USART, 串口, 通信协议]
TX 空闲 ───┐ ┌─ D0 ─ D1 ─ D2 ─ D3 ─ D4 ─ D5 ─ D6 ─ D7 ─┐ ┌─ 空闲
│ │ │ │
└─┘ ← 起始位(0) 数据位(8位) 校验位 → 停止位(1)
一帧数据:
起始位(1位) + 数据位(8/9位) + 校验位(0/1位) + 停止位(1/1.5/2位)
波特率定义单位时间传输的位数:
波特率 = 1 / 位时间
115200 bps → 每位 ≈ 8.68µs
发送:
CPU → TDR(数据寄存器) → 发送移位寄存器 → TX 引脚
↑
波特率发生器控制移位时钟
接收:
RX 引脚 → 接收移位寄存器 → RDR(数据寄存器) → CPU
↑
波特率发生器采样
USART_BRR:
USART_BRR = PCLK / (16 × 波特率)
例: 72MHz / (16 × 115200) = 39.0625 → USART_BRR = 0x271
DIV_Mantissa = 39 (0x27)
DIV_Fraction = 1 (0x1)
TXE (发送寄存器空): TDR 已空,可写入新数据
TC (发送完成): 移位寄存器也移位完成
RXNE (接收寄存器非空): RDR 有数据可读
IDLE (总线空闲): RX 空闲超过一帧时间
ORE (溢出错误): RDR 未读时新数据覆盖
typedef struct {
uint32_t USART_BaudRate; // 波特率 2400/4800/9600/19200/38400/57600/115200/...
uint16_t USART_WordLength; // USART_WordLength_8b / 9b
uint16_t USART_StopBits; // USART_StopBits_1 / 0.5 / 2 / 1.5
uint16_t USART_Parity; // USART_Parity_No / Even / Odd
uint16_t USART_Mode; // USART_Mode_Rx / Tx / Rx | Tx
uint16_t USART_HardwareFlowControl; // USART_HardwareFlowControl_None / RTS / CTS / RTS_CTS
} USART_InitTypeDef;
| 函数 | 描述 |
|---|---|
USART_Init(USARTx, &USART_InitStructure) |
初始化 USART |
USART_Cmd(USARTx, ENABLE) |
使能 USART |
USART_SendData(USARTx, uint16_t Data) |
发送一个字节 |
USART_ReceiveData(USARTx) |
接收一个字节(返回 uint16_t) |
USART_ITConfig(USARTx, USART_IT_RXNE, ENABLE) |
使能接收中断 |
USART_GetFlagStatus(USARTx, uint16_t USART_FLAG) |
获取状态标志 |
USART_ClearFlag(USARTx, uint16_t USART_FLAG) |
清除状态标志 |
USART_GetITStatus(USARTx, uint16_t USART_IT) |
获取中断状态 |
USART_ClearITPendingBit(USARTx, uint16_t USART_IT) |
清除中断挂起位 |
| 宏 | 含义 |
|---|---|
USART_FLAG_TXE |
发送数据寄存器空 (TDR empty) |
USART_FLAG_TC |
发送完成 (Transmission Complete) |
USART_FLAG_RXNE |
接收数据寄存器非空 |
USART_FLAG_IDLE |
总线空闲 |
USART_FLAG_ORE |
溢出错误 |
USART_FLAG_NE |
噪声错误 |
USART_FLAG_FE |
帧错误 |
USART_FLAG_PE |
校验错误 |
| USART | TX | RX | CTS | RTS |
|---|---|---|---|---|
| USART1 | PA9 | PA10 | PA11 | PA12 |
| USART2 | PA2 | PA3 | PA0 | PA1 |
| USART3 | PB10 | PB11 | PB13 | PB14 |
#include "stm32f10x.h"
void Delay_ms(uint32_t ms)
{
SysTick->LOAD = 72000 - 1;
SysTick->VAL = 0;
SysTick->CTRL = 0x05;
for (uint32_t i = 0; i < ms; i++) {
while (!(SysTick->CTRL & (1 << 16)));
}
SysTick->CTRL = 0;
}
void USART1_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
// TX: PA9 复用推挽输出
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// RX: PA10 浮空输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate = 115200;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_Init(USART1, &USART_InitStructure);
USART_Cmd(USART1, ENABLE);
}
void USART1_SendByte(uint8_t data)
{
USART_SendData(USART1, data);
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
}
void USART1_SendString(const char *str)
{
while (*str) {
USART1_SendByte(*str++);
}
}
uint8_t USART1_ReceiveByte(void)
{
while (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET);
return (uint8_t)USART_ReceiveData(USART1);
}
int main(void)
{
USART1_Init();
USART1_SendString("Hello STM32!\r\n");
while (1) {
uint8_t ch = USART1_ReceiveByte();
USART1_SendByte(ch); // 回显
}
}
#include "stm32f10x.h"
#define RX_BUF_SIZE 64
uint8_t rx_buffer[RX_BUF_SIZE];
volatile uint8_t rx_index = 0;
volatile uint8_t rx_flag = 0; // 收到一帧完成
void USART1_IRQHandler(void)
{
if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) {
uint8_t ch = (uint8_t)USART_ReceiveData(USART1);
if (rx_index < RX_BUF_SIZE) {
rx_buffer[rx_index++] = ch;
}
// 简单帧结束判断:换行符
if (ch == '\n' || ch == '\r') {
rx_buffer[rx_index - 1] = '\0'; // 替换结束符
rx_flag = 1;
rx_index = 0;
}
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
}
}
void USART1_Init_IT(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate = 115200;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_Init(USART1, &USART_InitStructure);
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
USART_Cmd(USART1, ENABLE);
}
int main(void)
{
USART1_Init_IT();
while (1) {
if (rx_flag) {
rx_flag = 0;
// 处理 rx_buffer 中的数据
// USART1_SendString(rx_buffer); // 回显收到的字符串
}
}
}
#include "stm32f10x.h"
// 前提:Keil 中勾选 "Use MicroLIB"
// 或使用 GCC 的 -specs=nano.specs -u _printf_float
int fputc(int ch, FILE *f)
{
USART_SendData(USART1, (uint8_t)ch);
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
return ch;
}
// 重定向后可直接使用:
// printf("Hello %d\r\n", 123);
// printf("Voltage: %.2fV\r\n", 3.28);
// 自定义数据包格式:
// 帧头(0xAA) + 长度(1字节) + 数据(N字节) + 校验和(1字节)
#define PACKET_HEADER 0xAA
uint8_t tx_packet[] = {
PACKET_HEADER,
0x04,
0x01, 0x02, 0x03, 0x04,
0x00 // 校验和占位
};
void USART_SendPacket(uint8_t *data, uint8_t len)
{
uint8_t checksum = 0;
USART1_SendByte(PACKET_HEADER);
USART1_SendByte(len);
for (uint8_t i = 0; i < len; i++) {
USART1_SendByte(data[i]);
checksum += data[i];
}
USART1_SendByte(checksum);
}
// 接收端状态机
typedef enum {
PKT_WAIT_HEADER,
PKT_WAIT_LEN,
PKT_WAIT_DATA,
PKT_WAIT_CHECKSUM
} PacketState;
PacketState pkt_state = PKT_WAIT_HEADER;
uint8_t pkt_len = 0;
uint8_t pkt_index = 0;
uint8_t pkt_buf[64];
uint8_t pkt_checksum = 0;
void USART_RxProcess(uint8_t ch)
{
switch (pkt_state) {
case PKT_WAIT_HEADER:
if (ch == PACKET_HEADER) pkt_state = PKT_WAIT_LEN;
break;
case PKT_WAIT_LEN:
pkt_len = ch;
pkt_index = 0;
pkt_checksum = 0;
pkt_state = (pkt_len > 0) ? PKT_WAIT_DATA : PKT_WAIT_CHECKSUM;
break;
case PKT_WAIT_DATA:
pkt_buf[pkt_index++] = ch;
pkt_checksum += ch;
if (pkt_index >= pkt_len) pkt_state = PKT_WAIT_CHECKSUM;
break;
case PKT_WAIT_CHECKSUM:
if (ch == pkt_checksum) {
// 校验成功,处理 pkt_buf[0..pkt_len-1]
}
pkt_state = PKT_WAIT_HEADER;
break;
}
}
TXE vs TC:
TXE(发送寄存器空):数据从 TDR 转移到移位寄存器后置位,此时 TDR 可写入新数据TC(发送完成):移位寄存器也移位完成(所有位发送完毕)后才置位USART_SendData 参数:函数原型 void USART_SendData(USART_TypeDef* USARTx, uint16_t Data),数据位 8 位时用低 8 位;数据位 9 位时需要用 (uint16_t) 强制转换,且根据 USART_WordLength_9b 选择 USART_Parity 配置
USART_ReceiveData 返回值:返回 uint16_t,8 位模式取低 8 位即可 (uint8_t)USART_ReceiveData(USARTx)
波特率误差 < 3%:USART 使用 16 倍过采样,理论最大误差容限约 3%。72MHz 下计算:
GPIO 模式:
GPIO_Mode_AF_PP(复用推挽输出),USART 控制引脚GPIO_Mode_IN_FLOATING 或 GPIO_Mode_IPU。浮空输入更适合外部有上拉的情况;如果外部没有上拉,用 IPU 避免不确定电平MicroLIB 注意:使用 printf 时必须勾选 Use MicroLIB(Keil),否则链接失败。GCC 使用 -specs=nano.specs 和 -u _printf_float
USART_Cmd 必须最后调用:所有 USART 配置完成后才调用 USART_Cmd(USARTx, ENABLE)。如果在配置中断或 GPIO 之前就使能,可能产生错误中断
中断处理:RXNE 中断中必须尽早读取 DR(USART_ReceiveData),否则 ORE 溢出标志置位,后续接收停止
CH340 连接:交叉连接 — STM32 TX → CH340 RX,STM32 RX → CH340 TX,GND 共地
USART_GetFlagStatus vs USART_GetITStatus:
USART_GetFlagStatus 检查所有状态标志(包括不产生中断的)USART_GetITStatus 检查已使能的中断标志,不可混用