12-USART串口.md 12 KB


title: USART串口 tags: [STM32, USART, 串口, 通信协议]

created: 2026-07-06

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

USART 外设结构

发送:
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 未读时新数据覆盖

API 表格

USART_InitTypeDef

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 枚举

含义
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 校验错误

GPIO 复用映射

USART TX RX CTS RTS
USART1 PA9 PA10 PA11 PA12
USART2 PA2 PA3 PA0 PA1
USART3 PB10 PB11 PB13 PB14

代码示例

1. USART1 基本收发 (查询方式)

#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); // 回显
    }
}

2. USART1 中断接收

#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); // 回显收到的字符串
        }
    }
}

3. printf 重定向 (使用 MicroLIB)

#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);

4. HEX 数据包协议

// 自定义数据包格式:
// 帧头(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;
    }
}

常见坑点

  1. TXE vs TC

    • TXE(发送寄存器空):数据从 TDR 转移到移位寄存器后置位,此时 TDR 可写入新数据
    • TC(发送完成):移位寄存器也移位完成(所有位发送完毕)后才置位
    • 发单个字节等待 TXE 足够;发完数据后关串口或进入低功耗前等 TC
  2. USART_SendData 参数:函数原型 void USART_SendData(USART_TypeDef* USARTx, uint16_t Data),数据位 8 位时用低 8 位;数据位 9 位时需要用 (uint16_t) 强制转换,且根据 USART_WordLength_9b 选择 USART_Parity 配置

  3. USART_ReceiveData 返回值:返回 uint16_t,8 位模式取低 8 位即可 (uint8_t)USART_ReceiveData(USARTx)

  4. 波特率误差 < 3%:USART 使用 16 倍过采样,理论最大误差容限约 3%。72MHz 下计算:

    • 115200: 72000000/(16×115200) = 39.0625 → DIV_Mantissa=39, DIV_Fraction=1 → 实际 ≈ 115384 → 误差 0.16%
    • 250000: 72000000/(16×250000) = 18 → 精确
    • 460800: 72000000/(16×460800) = 9.765 → 误差约 0.2%
  5. GPIO 模式

    • TX(发送):GPIO_Mode_AF_PP(复用推挽输出),USART 控制引脚
    • RX(接收):GPIO_Mode_IN_FLOATINGGPIO_Mode_IPU。浮空输入更适合外部有上拉的情况;如果外部没有上拉,用 IPU 避免不确定电平
  6. MicroLIB 注意:使用 printf 时必须勾选 Use MicroLIB(Keil),否则链接失败。GCC 使用 -specs=nano.specs-u _printf_float

  7. USART_Cmd 必须最后调用:所有 USART 配置完成后才调用 USART_Cmd(USARTx, ENABLE)。如果在配置中断或 GPIO 之前就使能,可能产生错误中断

  8. 中断处理:RXNE 中断中必须尽早读取 DR(USART_ReceiveData),否则 ORE 溢出标志置位,后续接收停止

  9. CH340 连接:交叉连接 — STM32 TX → CH340 RX,STM32 RX → CH340 TX,GND 共地

  10. USART_GetFlagStatus vs USART_GetITStatus

    • USART_GetFlagStatus 检查所有状态标志(包括不产生中断的)
    • USART_GetITStatus 检查已使能的中断标志,不可混用