STM32(二)USART串口

  • UART (Universal Asynchronous Receiver/Transmitter,通用异步收发器):异步通信,只需要 TX(发送)RX(接收) 两根线。
  • USART (Universal Synchronous/Asynchronous Receiver/Transmitter,通用同步/异步收发器):同步和异步都支持。它比UART多了一种同步模式。在同步模式下,除了TX、RX,还需要一根时钟线(CK),数据在时钟信号的节拍下传输,速度可以更快,抗干扰能力也更强。

2.1 通信协议

  • 空闲时拉高。
  • 起始位:1位。空闲时接收到低电平表示起始。
  • 数据位:8/9位。从数据的低位开始发送。可以选择是否有校验位。校验位可以选择奇校验还是偶校验(奇校验表示校验数据位1的个数是否为奇数)。
  • 停止位:宽度可选。数据位发送完拉高表示停止位。
  • 最后如果空闲则拉高,如果还有数据需要发送则再次发送起始位。

2.2 USART

2.2.1 USART模块

下图是USART模块的示意图:

  • 发送和接收都有数据寄存器,它们是移位寄存器,用于串并转换。
  • 控制电路需要传入数据帧格式。
  • 左上角有一个总开关,需要使用下面的USART_Cmd控制。
  • 总开关的右边是时钟分频模块,通过设置波特率寄存器的值可以控制分频器除以几,进而控制输出波特率,计算如下(实际使用时不需要关注这些)。

2.2.2 标志位

上面的模块示意图右上角有一些标志位:

  • TxE(Transmit Data Register Empty):表示发送数据寄存器(TDR)是否为空,空则返回1。
  • TC(Transmit Complete):表示发送是否完成,发送数据寄存器和移位寄存器都为空时返回1。
  • RxNE(Receive Data Register Not Empty):表示接收数据寄存器(RDR)是否非空,非空则返回1。
  • PE(Parity Error):奇偶校验错误。
  • FE(Frame Error):帧格式错误,接收到了无效的数据帧(比如没有停止位)。
  • NE(Noise Error):噪声错误,接收的数据中检测到了噪声(比如数据位0的波形中有高电平脉冲)。
  • ORE(Overrun Error):过载错误,由于读接收寄存器太慢导致数据丢失。

2.2.3 编程接口

发送的数据可能是9位,因此是uint16_t类型。

接收数据前,需要等待接收数据寄存器非空。

完整配置代码如下:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);

USART_InitTypeDef USART_InitStruct;

USART_InitStruct.USART_BaudRate = 115200;
USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_InitStruct.USART_WordLength = USART_WordLength_8b;
USART_InitStruct.USART_StopBits = USART_StopBits_1;
USART_InitStruct.USART_Parity = USART_Parity_No;

USART_Init(USART1, &USART_InitStruct);
USART_Cmd(USART1, ENABLE);

2.3 串口IO引脚

目前只使用TX和RX引脚。

2.3.1 引脚分布表和重映射表

数据手册第3章Table 5有如下表格(节选):

  • 默认的TX和RX引脚是PA9PA10
  • (如果上面的引脚被占用)重映射后的引脚是PB6PB7

参考手册8.3.8节中也有类似表格:

2.3.2 IO配置表

在参考手册8.1.11节中有如下配置表,不同模式参考表格配置即可。

全双工模式下RX推荐选择上拉输入,这样默认高电平,对应空闲时拉高。

2.3.3 代码

  • 默认引脚(PA9PA10
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStruct;

// Tx PA9 复用输出推挽
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);

// Rx PA10 输入上拉
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOA, &GPIO_InitStruct);
  • 重映射引脚(PB6PB7
// 重映射需要AFIO模块,因此先开启时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);

// 开启USART重映射(USART1_REMAP = 1)
GPIO_PinRemapConfig(GPIO_Remap_USART1, ENABLE);

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitTypeDef GPIO_InitStruct;

// Tx PB6 复用输出推挽
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_Init(GPIOB, &GPIO_InitStruct);

// Rx PB7 输入上拉
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_7;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOB, &GPIO_InitStruct);

2.4 USART实验

2.4.1 发送数据

#include "stm32f10x.h"
#include <stdio.h>

void My_USART_SendBytes(USART_TypeDef* USARTx, uint8_t* pData, uint32_t size) {
    for (uint32_t i = 0; i < size; i++) {
        // 等待发送数据寄存器为空
        while (USART_GetFlagStatus(USARTx, USART_FLAG_TXE) == RESET);
        // 将要发送的数据写入发送数据寄存器
        USART_SendData(USARTx, pData[i]);
    }
    // 等待数据发送完成
    while (USART_GetFlagStatus(USARTx, USART_FLAG_TC) == RESET);
}

void My_USART_Init() {
    // 1. 初始化 IO 引脚
    // 重映射
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
    GPIO_PinRemapConfig(GPIO_Remap_USART1, ENABLE);

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    GPIO_InitTypeDef GPIO_InitStruct;

    // Tx PB6 复用输出推挽
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_10MHz;
    GPIO_Init(GPIOB, &GPIO_InitStruct);

    // Rx PB7 输入上拉
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_7;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_Init(GPIOB, &GPIO_InitStruct);
    
    // 2. 初始化 USART1
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
    
    USART_InitTypeDef USART_InitStruct;

    USART_InitStruct.USART_BaudRate = 115200;
    USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
    USART_InitStruct.USART_WordLength = USART_WordLength_8b;
    USART_InitStruct.USART_StopBits = USART_StopBits_1;
    USART_InitStruct.USART_Parity = USART_Parity_No;

    USART_Init(USART1, &USART_InitStruct);
    USART_Cmd(USART1, ENABLE);
}

int main(void)
{
    My_USART_Init();
    
    // 调用自定义函数逐个发送单字节数据
    uint8_t bytes[] = {1, 2, 3, 4, 5};
    My_USART_SendBytes(USART1, bytes, 5);

    // 通过覆盖 fputc 方法,重定向 printf 输出到 USART1
    printf("Hello, USART1!\n");

    while (1) {}
}

// 覆盖 fputc
int fputc(int ch, FILE *f) {
    // 等待发送数据寄存器为空
    while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
    // 发送数据
    USART_SendData(USART1, (uint8_t)ch);
    // 保持原返回值
    return ch;
}

2.4.2 接收数据控制LED

#include "stm32f10x.h"

void My_USART_Init(void);
void My_OnBoardLED_Init(void);

int main(void)
{
    My_USART_Init();
    My_OnBoardLED_Init();

    while (1) {
        // 等待接收数据寄存器非空
        while (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET);
        // 读取数据
        uint8_t byteRcvd = USART_ReceiveData(USART1);
        // 控制 LED
        if (byteRcvd == '0') {
            GPIO_WriteBit(GPIOC, GPIO_Pin_13, Bit_SET); // 关闭 LED
        } else if (byteRcvd == '1') {
            GPIO_WriteBit(GPIOC, GPIO_Pin_13, Bit_RESET); // 打开 LED
        }
    }
}

// 初始化 USART1
void My_USART_Init(void) {
    // 1. 初始化 IO 引脚
    // 重映射
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
    GPIO_PinRemapConfig(GPIO_Remap_USART1, ENABLE);

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    GPIO_InitTypeDef GPIO_InitStruct;

    // Tx PB6 复用输出推挽
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_10MHz;
    GPIO_Init(GPIOB, &GPIO_InitStruct);

    // Rx PB7 输入上拉
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_7;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_Init(GPIOB, &GPIO_InitStruct);

    // 2. 初始化 USART1
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
    
    USART_InitTypeDef USART_InitStruct;

    USART_InitStruct.USART_BaudRate = 115200;
    USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
    USART_InitStruct.USART_WordLength = USART_WordLength_8b;
    USART_InitStruct.USART_StopBits = USART_StopBits_1;
    USART_InitStruct.USART_Parity = USART_Parity_No;

    USART_Init(USART1, &USART_InitStruct);
    USART_Cmd(USART1, ENABLE);
}

void My_OnBoardLED_Init(void) {
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
    GPIO_InitTypeDef GPIO_InitStruct;

    // PC13 通用输出开漏
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_13;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
    GPIO_Init(GPIOC, &GPIO_InitStruct);

    // 默认关闭 LED
    GPIO_WriteBit(GPIOC, GPIO_Pin_13, Bit_SET);
}

2.5 封装函数

my_lib文件夹下有封装好的USART相关函数,引入头文件就可以使用,包含如下函数:

void My_USART_SendByte(USART_TypeDef *USARTx, const uint8_t Data);
void My_USART_SendBytes(USART_TypeDef *USARTx, const uint8_t *pData, uint16_t Size);
void My_USART_SendChar(USART_TypeDef *USARTx, const char C);
void My_USART_SendString(USART_TypeDef *USARTx, const char *Str);
void My_USART_Printf(USART_TypeDef *USARTx, const char *Format, ...);

uint8_t  My_USART_ReceiveByte(USART_TypeDef *USARTx);
uint16_t My_USART_ReceiveBytes(USART_TypeDef *USARTx, uint8_t *pDataOut, uint16_t Size, int Timeout);
int      My_USART_ReceiveLine(USART_TypeDef *USARTx, char *pStrOut, uint16_t MaxLength, uint16_t LineSeperator, int Timeout);

STM32(二)USART串口
https://shuusui.site/blog/2026/04/24/hardware/stm32-2/
作者
Shuusui
发布于
2026年4月24日
更新于
2026年4月24日
许可协议