STM32F103C8T6_UART串口通信完整教程
STM32F103C8T6 UART串口通信完整教程:从入门到精通
适用芯片: STM32F103C8T6(Blue Pill)
难度等级: 入门到进阶
📋 目录
- UART协议基础理论
- STM32F103 UART硬件资源
- UART寄存器详解
- UART配置与初始化
- 硬件电路连接
- 标准库开发实战
- HAL库开发实战
- 高级应用与优化
- 常见问题与调试技巧
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 │ 高 │
└─────┴──────┴─────┴──────┴─────┘
各部分详细说明:
-
起始位(Start Bit):
- 固定为低电平(逻辑0)
- 表示数据帧的开始
- 持续1个波特率周期
-
数据位(Data Bits):
- 可以是5、6、7、8或9位
- 最常用的是8位
- 先发送最低位(LSB),后发送最高位(MSB)
-
校验位(Parity Bit)(可选):
- 无校验(None): 不使用校验位
- 奇校验(Odd): 使数据位+校验位的"1"的总数为奇数
- 偶校验(Even): 使数据位+校验位的"1"的总数为偶数
-
停止位(Stop Bit):
- 固定为高电平(逻辑1)
- 持续1个或2个波特率周期
- 表示数据帧的结束
-
空闲位(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工作流程
发送流程:
- 发送器将数据写入发送数据寄存器(TDR/DR)
- 数据从TDR传送到发送移位寄存器
- 按照LSB先发的顺序,逐位发送数据
- 添加起始位、停止位、校验位(如果启用)
- 通过TX引脚输出数据
接收流程:
- 检测到RX引脚的下降沿(起始位)
- 按照约定的波特率采样数据位
- 数据从接收移位寄存器传送到接收数据寄存器(RDR/DR)
- 触发接收中断或标志位
- 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初始化步骤
- 使能时钟: 使能UART和GPIO的时钟
- 配置GPIO: 配置TX为复用推挽输出,RX为浮空输入
- 配置UART参数:
- 波特率
- 数据位长度
- 停止位
- 校验位
- 硬件流控(可选)
- 使能UART: 使能UART外设
- 配置中断(可选): 使能发送/接收中断
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) ────────────> RXPA10(RX) <──────────── TXGND ────────────> GND3.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 ────> RDRX <────── R1OUT <──── R1IN <──── TD3.3V ─────> VCCGND ──────> 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; // 波特率115200USART_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; // 接收和发送模式// 初始化USART1USART_Init(USART1, &USART_InitStructure);// 使能USART1USART_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 256typedef 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: 接收不到数据
可能原因:
- TX和RX接反
- 波特率不匹配
- 没有共地
- GPIO配置错误
解决方法:
- 检查TX接对方RX,RX接对方TX
- 确保波特率一致
- 确保GND连接
- 检查GPIO是否配置为复用功能
问题2: 数据乱码
可能原因:
- 波特率错误
- 时钟配置错误
- 数据位/停止位不匹配
解决方法:
- 重新配置波特率
- 检查系统时钟配置
- 确保参数匹配
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串口通信,包括:
- 理论基础: UART协议原理、帧格式、波特率
- 硬件资源: STM32 UART外设和引脚资源
- 寄存器配置: 详细解释了关键寄存器
- 实战编程: 提供了标准库和HAL库的完整代码
- 高级应用: DMA、环形缓冲区、printf重定向
- 调试技巧: 常见问题排查方法
希望本文档能帮助大家快速掌握STM32F103的UART通信!
祝您学习愉快,开发顺利! 🎉
