STM32F103C8T6 UART串口通信完整教程:从入门到精通

适用芯片: STM32F103C8T6(Blue Pill)
难度等级: 入门到进阶


📋 目录

  1. UART协议基础理论
  2. STM32F103 UART硬件资源
  3. UART寄存器详解
  4. UART配置与初始化
  5. 硬件电路连接
  6. 标准库开发实战
  7. HAL库开发实战
  8. 高级应用与优化
  9. 常见问题与调试技巧

1. UART协议基础理论

1.1 什么是UART?

UART(Universal Asynchronous Receiver/Transmitter,通用异步收发传输器)是一种串行、异步、全双工的通信协议。与SPI、I2C等同步协议不同,UART不需要时钟信号,通过约定的波特率进行数据传输。

1.2 UART协议特点

  • 异步通信: 不需要时钟信号,只需要波特率一致
  • 全双工通信: 可以同时发送和接收数据
  • 简单协议: 硬件实现简单,软件易于控制
  • 广泛应用: 广泛用于串口通信、调试、设备间通信
  • 长距离传输: 可以使用RS232/RS485等标准延长传输距离

1.3 UART信号线定义

UART通信最少只需要2根线:

信号线名称 全称 方向 说明
TX Transmitter 主机→从机 发送数据线
RX Receiver 主机←从机 接收数据线
GND Ground - 地线(必需)

注意事项:

  • TX连接对方的RX,RX连接对方的TX(交叉连接)
  • 必须共地才能正常工作

1.4 UART数据帧格式

UART数据帧由以下几部分构成:

┌─────┬──────┬─────┬──────┬─────┐
│起始位│数据位│校验位│停止位│空闲位│
│  0  │ 5-9  │ 0-1  │  1-2 │ 高  │
└─────┴──────┴─────┴──────┴─────┘

各部分详细说明

  1. 起始位(Start Bit):

    • 固定为低电平(逻辑0)
    • 表示数据帧的开始
    • 持续1个波特率周期
  2. 数据位(Data Bits):

    • 可以是5、6、7、8或9位
    • 最常用的是8位
    • 先发送最低位(LSB),后发送最高位(MSB)
  3. 校验位(Parity Bit)(可选):

    • 无校验(None): 不使用校验位
    • 奇校验(Odd): 使数据位+校验位的"1"的总数为奇数
    • 偶校验(Even): 使数据位+校验位的"1"的总数为偶数
  4. 停止位(Stop Bit):

    • 固定为高电平(逻辑1)
    • 持续1个或2个波特率周期
    • 表示数据帧的结束
  5. 空闲位(Idle):

    • 传输间隔期间保持高电平
    • 确保下次起始位能被正确识别

1.5 波特率(Baud Rate)

波特率定义

  • 波特率表示每秒传输的符号(位)数
  • 单位:bps(bits per second)
  • 常用的波特率:9600, 19200, 38400, 57600, 115200 等

波特率计算公式

STM32F103的UART波特率通过以下公式配置:

波特率 = fCK / (USARTDIV × 16)

其中:
- fCK: USART时钟频率
- USARTDIV: 分频因子

1.6 UART工作流程

发送流程

  1. 发送器将数据写入发送数据寄存器(TDR/DR)
  2. 数据从TDR传送到发送移位寄存器
  3. 按照LSB先发的顺序,逐位发送数据
  4. 添加起始位、停止位、校验位(如果启用)
  5. 通过TX引脚输出数据

接收流程

  1. 检测到RX引脚的下降沿(起始位)
  2. 按照约定的波特率采样数据位
  3. 数据从接收移位寄存器传送到接收数据寄存器(RDR/DR)
  4. 触发接收中断或标志位
  5. CPU读取接收到的数据

2. STM32F103 UART硬件资源

2.1 STM32F103C8T6 UART外设概述

STM32F103C8T6共有3个UART外设:

  • USART1: 全功能UART/USART(最高4.5Mbps)
  • USART2: 全功能UART/USART(最高2.25Mbps)
  • USART3: 全功能UART/USART(最高2.25Mbps)

UART vs USART

  • UART:只支持异步通信
  • USART:支持异步和同步通信(可以使用外部时钟)
  • STM32F103的USART可以通过配置只使用异步模式

2.2 UART引脚资源表

USART1引脚:

模式 引脚 功能 复用重映射
标准 PA9 TX 已启用
标准 PA10 RX 已启用
重映射 PB6 TX 需要重映射
重映射 PB7 RX 需要重映射

USART2引脚:

引脚 功能 说明
PA2 TX 串口2发送
PA3 RX 串口2接收

USART3引脚:

模式 引脚 功能
标准 PB10 TX
标准 PB11 RX
部分重映射 PC10 TX
部分重映射 PC11 RX
完全重映射 PD8 TX
完全重映射 PD9 RX

2.3 UART时钟源配置

STM32F103的UART时钟源:

USART1: 挂载在APB2总线上 (最高72MHz)
USART2: 挂载在APB1总线上 (最高36MHz)
USART3: 挂载在APB1总线上 (最高36MHz)

波特率分频器配置

通过配置BRR寄存器来设置波特率分频:

BRR寄存器 = 16位
- 高4位:小数部分
- 低12位:整数部分

常用波特率配置表(72MHz系统时钟,USART1):

波特率 分频比 BRR值(Hex)
9600 750 0x1D4C
19200 375 0xEA6
38400 187.5 0x753
57600 125 0x4E2
115200 62.5 0x271
230400 31.25 0x139
460800 15.625 0x9D
921600 7.8125 0x4F

3. UART寄存器详解

3.1 状态寄存器(USART_SR)

USART状态寄存器用于反映当前UART的状态:

名称 说明
7 TXE 发送数据寄存器空:可以写入新数据
6 TC 传输完成:数据已完全发送
5 RXNE 接收数据寄存器非空:有数据可读
4 IDLE 空闲总线检测
3 ORE 溢出错误
2 NE 噪声错误
1 FE 帧错误
0 PE 校验错误

3.2 数据寄存器(USART_DR)

  • 写入操作:数据写入发送数据寄存器(TDR)
  • 读取操作:从接收数据寄存器(RDR)读取数据
  • 该寄存器为8位,但在9位数据长度模式下可扩展到9位

3.3 波特率寄存器(USART_BRR)

16位寄存器,用于配置波特率:

位15-4:Mantissa(整数部分)
位3-0:Fraction(小数部分)

波特率 = fCK / (16 × USARTDIV)
USARTDIV = Mantissa + (Fraction/16)

3.4 控制寄存器1(USART_CR1)

名称 说明
13 UE USART使能
12 M 字长度:0=8位,1=9位
10 PCE 奇偶校验使能
9 PS 奇偶选择:0=偶校验,1=奇校验
7 TXEIE TXE中断使能
6 TCIE TC中断使能
5 RXNEIE RXNE中断使能
3 TE 发送使能
2 RE 接收使能
0 SBK 发送断开帧

3.5 控制寄存器2(USART_CR2)

名称 说明
13-12 STOP 停止位长度
5 CLKEN 时钟使能(同步模式)
3 CPHA 时钟相位
2 CPOL 时钟极性

3.6 控制寄存器3(USART_CR3)

名称 说明
7 DMAT DMA发送使能
6 DMAR DMA接收使能
5 SCEN 智能卡模式使能
1 IREN 红外模式使能

4. UART配置与初始化

4.1 UART初始化步骤

  1. 使能时钟: 使能UART和GPIO的时钟
  2. 配置GPIO: 配置TX为复用推挽输出,RX为浮空输入
  3. 配置UART参数:
    • 波特率
    • 数据位长度
    • 停止位
    • 校验位
    • 硬件流控(可选)
  4. 使能UART: 使能UART外设
  5. 配置中断(可选): 使能发送/接收中断

4.2 波特率计算

STM32提供的便捷计算公式:

// 波特率计算公式
uint16_t usartdiv = (uint16_t)(SystemCoreClock / (baudrate * 16));
uint8_t div_fraction = (uint8_t)(usartdiv % 16);
uint8_t div_mantissa = (uint8_t)(usartdiv / 16);

BRR = (div_mantissa << 4) | div_fraction;

4.3 常用配置参数

常用波特率

  • 9600: 低速通信,稳定可靠
  • 115200: 高速通信,常用调试
  • 921600: 超高速通信

数据位配置

  • 8位: 最常用,一个字节
  • 9位: 可用于多机通信

停止位配置

  • 1位: 标准配置
  • 0.5位: 不需要
  • 2位: 用于噪声环境

校验位配置

  • None: 无校验(常用)
  • Even: 偶校验
  • Odd: 奇校验

5. 硬件电路连接

5.1 STM32与电脑连接

STM32F103C8T6                USB转TTL模块
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    PA9 (TX)  ────────────> RX
    PA10(RX)  <──────────── TX
    GND       ────────────> GND
    3.3V      ────────────> VCC (可选)

注意事项

  • STM32的UART是3.3V电平
  • 必须使用3.3V的USB转TTL模块
  • 不要连接5V模块,会损坏STM32

5.2 两个STM32互相通信

STM32#1                    STM32#2
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    PA9 (TX)  ────────────> PA10(RX)
    PA10(RX)  <──────────── PA9 (TX)
    GND       ────────────> GND

重要: TX接RX,RX接TX(交叉连接)

5.3 通过RS232模块连接

STM32                    MAX232模块          电脑串口
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
    TX ──────> T1IN ────> T1OUT ────> RD
    RX <────── R1OUT <──── R1IN <──── TD
    3.3V ─────> VCC
    GND ──────> GND

6. 标准库开发实战

6.1 GPIO配置

/**
 * USART1 GPIO初始化配置
 * TX: PA9
 * RX: PA10
 */
void USART1_GPIO_Config(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    
    // 使能GPIOA和USART1时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE);
    
    // 配置PA9为复用推挽输出(TX)
    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);
    
    // 配置PA10为浮空输入(RX)
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 浮空输入
    GPIO_Init(GPIOA, &GPIO_InitStructure);
}

6.2 UART配置

/**
 * USART1初始化配置
 * 波特率: 115200
 * 数据位: 8
 * 停止位: 1
 * 校验位: 无
 * 流控: 无
 */
void USART1_Config(void)
{
    USART_InitTypeDef USART_InitStructure;
    
    // USART初始化结构体配置
    USART_InitStructure.USART_BaudRate = 115200;                           // 波特率115200
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;           // 8位数据
    USART_InitStructure.USART_StopBits = USART_StopBits_1;                // 1个停止位
    USART_InitStructure.USART_Parity = USART_Parity_No;                   // 无校验
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 无流控
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;       // 接收和发送模式
    
    // 初始化USART1
    USART_Init(USART1, &USART_InitStructure);
    
    // 使能USART1
    USART_Cmd(USART1, ENABLE);
}

6.3 发送函数实现

/**
 * USART1发送一个字节
 * @param data: 要发送的数据
 */
void USART1_SendByte(uint8_t data)
{
    // 等待发送数据寄存器空
    while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
    
    // 发送数据
    USART_SendData(USART1, data);
}

/**
 * USART1发送字符串
 * @param str: 要发送的字符串
 */
void USART1_SendString(char *str)
{
    while(*str)
    {
        USART1_SendByte(*str);
        str++;
    }
}

/**
 * USART1发送数组
 * @param buf: 要发送的数据缓冲区
 * @param len: 数据长度
 */
void USART1_SendBuffer(uint8_t *buf, uint16_t len)
{
    uint16_t i;
    for(i = 0; i < len; i++)
    {
        USART1_SendByte(buf[i]);
    }
}

/**
 * USART1格式化输出(简单版)
 * 支持 %d, %x, %c, %s
 */
void USART1_Printf(const char *fmt, ...)
{
    char buffer[256];
    va_list args;
    
    va_start(args, fmt);
    vsnprintf(buffer, sizeof(buffer), fmt, args);
    va_end(args);
    
    USART1_SendString(buffer);
}

6.4 接收函数实现

/**
 * USART1接收一个字节(阻塞方式)
 * @return: 接收到的数据
 */
uint8_t USART1_ReceiveByte(void)
{
    // 等待接收数据就绪
    while(USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET);
    
    // 读取接收到的数据
    return USART_ReceiveData(USART1);
}

/**
 * USART1非阻塞接收
 * @param data: 接收数据指针
 * @return: 1-成功, 0-失败
 */
uint8_t USART1_ReceiveByte_NonBlocking(uint8_t *data)
{
    if(USART_GetFlagStatus(USART1, USART_FLAG_RXNE) != RESET)
    {
        *data = USART_ReceiveData(USART1);
        return 1;
    }
    return 0;
}

6.5 中断配置

/**
 * USART1中断配置
 */
void USART1_NVIC_Config(void)
{
    NVIC_InitTypeDef NVIC_InitStructure;
    
    // 配置USART1中断优先级
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
    
    NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
    
    // 使能接收中断
    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
    
    // 使能发送完成中断(可选)
    USART_ITConfig(USART1, USART_IT_TC, ENABLE);
}

/**
 * USART1中断服务函数
 */
void USART1_IRQHandler(void)
{
    uint8_t recv_data;
    
    // 接收中断
    if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
    {
        // 清除中断标志
        USART_ClearITPendingBit(USART1, USART_IT_RXNE);
        
        // 读取接收到的数据
        recv_data = USART_ReceiveData(USART1);
        
        // 处理接收到的数据
        // 例如:回传数据或处理命令
        USART1_SendByte(recv_data);
    }
    
    // 发送完成中断
    if(USART_GetITStatus(USART1, USART_IT_TC) != RESET)
    {
        USART_ClearITPendingBit(USART1, USART_IT_TC);
        // 可以在这里更新发送完成标志
    }
}

6.6 主函数示例

#include "stm32f10x.h"

extern void USART1_GPIO_Config(void);
extern void USART1_Config(void);
extern void USART1_SendByte(uint8_t data);
extern void USART1_SendString(char *str);
extern uint8_t USART1_ReceiveByte(void);

int main(void)
{
    uint8_t recv_data;
    
    // 系统初始化
    SystemInit();
    
    // USART1初始化
    USART1_GPIO_Config();
    USART1_Config();
    
    // 发送测试信息
    USART1_SendString("STM32F103 UART Test!\r\n");
    
    while(1)
    {
        // 接收数据
        recv_data = USART1_ReceiveByte();
        
        // 回传数据
        USART1_SendByte(recv_data);
    }
}

7. HAL库开发实战

7.1 HAL库UART初始化

#include "main.h"
#include "stm32f1xx_hal.h"

UART_HandleTypeDef huart1;

/**
 * USART1初始化(HAL库版本)
 */
void MX_USART1_UART_Init(void)
{
    huart1.Instance = USART1;
    huart1.Init.BaudRate = 115200;                          // 波特率
    huart1.Init.WordLength = UART_WORDLENGTH_8B;           // 8位数据
    huart1.Init.StopBits = UART_STOPBITS_1;                // 1个停止位
    huart1.Init.Parity = UART_PARITY_NONE;                 // 无校验
    huart1.Init.Mode = UART_MODE_TX_RX;                    // 收发模式
    huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;           // 无流控
    huart1.Init.OverSampling = UART_OVERSAMPLING_16;       // 过采样16倍
    
    if(HAL_UART_Init(&huart1) != HAL_OK)
    {
        Error_Handler();
    }
}

/**
 * UART底层初始化(MSP回调函数)
 */
void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    
    if(huart->Instance == USART1)
    {
        // 使能时钟
        __HAL_RCC_USART1_CLK_ENABLE();
        __HAL_RCC_GPIOA_CLK_ENABLE();
        
        // 配置PA9(TX)
        GPIO_InitStruct.Pin = GPIO_PIN_9;
        GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
        
        // 配置PA10(RX)
        GPIO_InitStruct.Pin = GPIO_PIN_10;
        GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
        GPIO_InitStruct.Pull = GPIO_NOPULL;
        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
        
        // 使能UART中断(可选)
        HAL_NVIC_SetPriority(USART1_IRQn, 0, 0);
        HAL_NVIC_EnableIRQ(USART1_IRQn);
    }
}

7.2 HAL库发送接收

/**
 * HAL库:UART发送数据
 * @param data: 数据指针
 * @param size: 数据大小
 * @param timeout: 超时时间(ms)
 */
void UART1_Transmit(uint8_t *data, uint16_t size, uint32_t timeout)
{
    HAL_UART_Transmit(&huart1, data, size, timeout);
}

/**
 * HAL库:UART接收数据
 * @param data: 数据缓冲区
 * @param size: 数据大小
 * @param timeout: 超时时间(ms)
 */
void UART1_Receive(uint8_t *data, uint16_t size, uint32_t timeout)
{
    HAL_UART_Receive(&huart1, data, size, timeout);
}

/**
 * HAL库:中断方式发送
 */
void UART1_Transmit_IT(uint8_t *data, uint16_t size)
{
    HAL_UART_Transmit_IT(&huart1, data, size);
}

/**
 * HAL库:中断方式接收
 */
void UART1_Receive_IT(uint8_t *data, uint16_t size)
{
    HAL_UART_Receive_IT(&huart1, data, size);
}

/**
 * HAL库:DMA方式发送
 */
void UART1_Transmit_DMA(uint8_t *data, uint16_t size)
{
    HAL_UART_Transmit_DMA(&huart1, data, size);
}

/**
 * HAL库:DMA方式接收
 */
void UART1_Receive_DMA(uint8_t *data, uint16_t size)
{
    HAL_UART_Receive_DMA(&huart1, data, size);
}

7.3 HAL库回调函数

/**
 * 发送完成回调函数
 */
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
    if(huart->Instance == USART1)
    {
        // 发送完成处理
    }
}

/**
 * 接收完成回调函数
 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    if(huart->Instance == USART1)
    {
        // 接收完成处理
        // 可以重新启动接收
        HAL_UART_Receive_IT(&huart1, buffer, 1);
    }
}

/**
 * 错误回调函数
 */
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
{
    if(huart->Instance == USART1)
    {
        // 错误处理
    }
}

/**
 * USART1中断服务函数
 */
void USART1_IRQHandler(void)
{
    HAL_UART_IRQHandler(&huart1);
}

8. 高级应用与优化

8.1 DMA方式传输

/**
 * UART DMA发送配置
 */
void UART1_DMA_Config(void)
{
    // DMA配置...
    HAL_UART_Transmit_DMA(&huart1, tx_buffer, tx_len);
}

8.2 环形缓冲区实现

#define UART_BUFFER_SIZE 256

typedef struct
{
    uint8_t buffer[UART_BUFFER_SIZE];
    volatile uint16_t head;
    volatile uint16_t tail;
} uart_ring_buffer_t;

uart_ring_buffer_t rx_buffer;

/**
 * 环形缓冲区初始化
 */
void RingBuffer_Init(uart_ring_buffer_t *rbuf)
{
    rbuf->head = 0;
    rbuf->tail = 0;
}

/**
 * 写入数据到环形缓冲区
 */
uint8_t RingBuffer_Put(uart_ring_buffer_t *rbuf, uint8_t data)
{
    uint16_t next = (rbuf->head + 1) % UART_BUFFER_SIZE;
    
    if(next == rbuf->tail)
        return 0; // 缓冲区满
    
    rbuf->buffer[rbuf->head] = data;
    rbuf->head = next;
    return 1;
}

/**
 * 从环形缓冲区读取数据
 */
uint8_t RingBuffer_Get(uart_ring_buffer_t *rbuf, uint8_t *data)
{
    if(rbuf->head == rbuf->tail)
        return 0; // 缓冲区空
    
    *data = rbuf->buffer[rbuf->tail];
    rbuf->tail = (rbuf->tail + 1) % UART_BUFFER_SIZE;
    return 1;
}

8.3 printf重定向

#include <stdio.h>

/**
 * 重定向fputc函数到UART
 */
int fputc(int ch, FILE *f)
{
    USART1_SendByte((uint8_t)ch);
    return ch;
}

// 现在可以使用printf了
printf("Hello World! Value = %d\r\n", value);

9. 常见问题与调试技巧

9.1 常见问题排查

问题1: 接收不到数据

可能原因:

  1. TX和RX接反
  2. 波特率不匹配
  3. 没有共地
  4. GPIO配置错误

解决方法:

  • 检查TX接对方RX,RX接对方TX
  • 确保波特率一致
  • 确保GND连接
  • 检查GPIO是否配置为复用功能
问题2: 数据乱码

可能原因:

  1. 波特率错误
  2. 时钟配置错误
  3. 数据位/停止位不匹配

解决方法:

  • 重新配置波特率
  • 检查系统时钟配置
  • 确保参数匹配

9.2 调试技巧

使用逻辑分析仪
  • 抓取TX/RX波形
  • 验证波特率
  • 检查数据格式
使用串口助手
  • 设置正确的参数
  • 十六进制显示
  • 发送/接收测试

9.3 实际应用案例

案例1: 串口命令解析
void UART1_Command_Process(uint8_t cmd)
{
    switch(cmd)
    {
        case '1':
            USART1_SendString("LED1 ON\r\n");
            GPIO_SetBits(GPIOA, GPIO_Pin_0);
            break;
        case '0':
            USART1_SendString("LED1 OFF\r\n");
            GPIO_ResetBits(GPIOA, GPIO_Pin_0);
            break;
        default:
            USART1_SendString("Unknown Command\r\n");
            break;
    }
}

📚 总结

本文档详细介绍了STM32F103C8T6的UART串口通信,包括:

  1. 理论基础: UART协议原理、帧格式、波特率
  2. 硬件资源: STM32 UART外设和引脚资源
  3. 寄存器配置: 详细解释了关键寄存器
  4. 实战编程: 提供了标准库和HAL库的完整代码
  5. 高级应用: DMA、环形缓冲区、printf重定向
  6. 调试技巧: 常见问题排查方法

希望本文档能帮助大家快速掌握STM32F103的UART通信!


祝您学习愉快,开发顺利! 🎉

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐