STM32 串口通信详解(标准库函数版)
一、STM32串口(USART)核心原理
1. 串口通信基础
STM32的USART(通用同步异步收发器)支持:
- 通信模式:全双工/半双工/单工
- 数据格式:8/9位数据位,1/2位停止位,奇偶校验
- 波特率:最高4.5Mbps(F103系列)
- 工作模式:异步(常用)、同步、智能卡、IrDA、LIN
2. 硬件资源(以STM32F103C8T6为例)
| 串口 | TX引脚 | RX引脚 | 时钟源 | 中断 |
|---|---|---|---|---|
| USART1 | PA9 | PA10 | APB2 (72MHz) | USART1_IRQn |
| USART2 | PA2 | PA3 | APB1 (36MHz) | USART2_IRQn |
| USART3 | PB10 | PB11 | APB1 (36MHz) | USART3_IRQn |
3. 波特率计算
波特率 = APBx时钟 / (USARTDIV)
USARTDIV = DIV_Mantissa + (DIV_Fraction / 16)
例如:APB2=72MHz,波特率115200
USARTDIV = 72000000 / 115200 ≈ 625
DIV_Mantissa = 625 / 16 = 39 (0x27)
DIV_Fraction = 625 % 16 = 1 (0x01)
二、硬件设计方案
1. 最小硬件连接
STM32F103C8T6 USB转TTL模块
PA9(TX) ────────────── RX
PA10(RX) ───────────── TX
GND ───────────────── GND
3.3V ─────────────── 3.3V (可选)
2. 硬件注意事项
- 电平匹配:STM32为3.3V TTL电平,需与USB转TTL模块电平一致
- 交叉连接:TX接RX,RX接TX
- 上拉电阻:RX引脚建议加10kΩ上拉电阻(抗干扰)
- 保护电路:串接100Ω电阻,防止短路损坏引脚
三、软件设计与核心代码
1. 系统架构
初始化 → 配置GPIO → 配置USART参数 → 使能中断(可选)→ 发送/接收数据
2. 核心代码实现(标准库)
2.1 串口初始化函数
#include "stm32f10x.h"
#include "stm32f10x_usart.h"
#include "stm32f10x_rcc.h"
#include "stm32f10x_gpio.h"
#include "misc.h"
// 串口1初始化函数
void USART1_Init(uint32_t baudrate)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
// 1. 使能时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
// 2. 配置GPIO
// TX引脚 (PA9) - 复用推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
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);
// 3. 配置USART参数
USART_InitStructure.USART_BaudRate = baudrate;
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; // 收发模式
USART_Init(USART1, &USART_InitStructure);
// 4. 配置中断(可选)
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);
// 5. 使能接收中断
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); // 接收非空中断
// 6. 使能USART
USART_Cmd(USART1, ENABLE);
}
2.2 数据发送函数
// 发送单个字节
void USART1_SendByte(uint8_t data)
{
USART_SendData(USART1, data);
while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); // 等待发送完成
}
// 发送字符串
void USART1_SendString(char *str)
{
while(*str != '\0')
{
USART1_SendByte((uint8_t)*str);
str++;
}
}
// 发送数字(整数)
void USART1_SendNumber(int32_t num)
{
char buffer[12]; // 最大11位数字 + '\0'
sprintf(buffer, "%d", num);
USART1_SendString(buffer);
}
// 发送浮点数
void USART1_SendFloat(float num, uint8_t decimal)
{
char buffer[20];
sprintf(buffer, "%.*f", decimal, num);
USART1_SendString(buffer);
}
// printf重定向(需勾选Use MicroLIB)
int fputc(int ch, FILE *f)
{
USART_SendData(USART1, (uint8_t)ch);
while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
return ch;
}
2.3 数据接收函数
// 接收单个字节
uint8_t USART1_ReceiveByte(void)
{
while(USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET); // 等待接收
return USART_ReceiveData(USART1);
}
// 接收字符串(以'\n'结束)
void USART1_ReceiveString(char *buffer, uint16_t max_len)
{
uint16_t i = 0;
char ch;
while(1)
{
ch = USART1_ReceiveByte();
if(ch == '\n' || i >= max_len-1) // 换行或缓冲区满
{
buffer[i] = '\0';
break;
}
buffer[i++] = ch;
}
}
2.4 中断处理函数
// 接收缓冲区
#define RX_BUFFER_SIZE 128
uint8_t rx_buffer[RX_BUFFER_SIZE];
uint16_t rx_index = 0;
uint8_t rx_complete = 0;
// USART1中断服务函数
void USART1_IRQHandler(void)
{
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) // 接收中断
{
uint8_t data = USART_ReceiveData(USART1); // 读取接收到的数据
if(rx_index < RX_BUFFER_SIZE-1)
{
rx_buffer[rx_index++] = data;
// 判断是否接收完成(以'\n'为结束符)
if(data == '\n')
{
rx_buffer[rx_index] = '\0'; // 字符串结束符
rx_complete = 1; // 接收完成标志
rx_index = 0; // 重置索引
}
}
else
{
rx_index = 0; // 缓冲区溢出,重置
}
}
}
2.5 主程序示例
int main(void)
{
// 系统初始化
SystemInit();
// 串口初始化(115200波特率)
USART1_Init(115200);
// 发送欢迎信息
USART1_SendString("STM32 USART Test Started!\r\n");
USART1_SendString("Please send data...\r\n");
while(1)
{
// 检查是否接收到完整数据
if(rx_complete)
{
USART1_SendString("Received: ");
USART1_SendString((char*)rx_buffer);
USART1_SendString("\r\n");
rx_complete = 0; // 清除完成标志
}
// 定时发送心跳包
static uint32_t last_time = 0;
if(HAL_GetTick() - last_time > 1000) // 每秒发送一次
{
USART1_SendString("Heartbeat\r\n");
last_time = HAL_GetTick();
}
}
}
四、高级应用
1. DMA传输(提高效率)
// DMA发送函数
void USART1_DMA_Send(uint8_t *buffer, uint16_t length)
{
// 使能DMA时钟
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
// 配置DMA
DMA_InitTypeDef DMA_InitStructure;
DMA_DeInit(DMA1_Channel4); // USART1_TX使用DMA1_Channel4
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)buffer;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
DMA_InitStructure.DMA_BufferSize = length;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel4, &DMA_InitStructure);
// 使能USART DMA发送
USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);
// 启动DMA传输
DMA_Cmd(DMA1_Channel4, ENABLE);
}
2. 环形缓冲区(解决数据丢失)
// 环形缓冲区结构
typedef struct {
uint8_t buffer[256];
volatile uint16_t head;
volatile uint16_t tail;
volatile uint16_t count;
} RingBuffer_t;
RingBuffer_t usart_rx_ring;
// 初始化环形缓冲区
void RingBuffer_Init(RingBuffer_t *ring)
{
ring->head = 0;
ring->tail = 0;
ring->count = 0;
}
// 写入数据到环形缓冲区
void RingBuffer_Write(RingBuffer_t *ring, uint8_t data)
{
if(ring->count < 256)
{
ring->buffer[ring->head] = data;
ring->head = (ring->head + 1) % 256;
ring->count++;
}
}
// 从环形缓冲区读取数据
uint8_t RingBuffer_Read(RingBuffer_t *ring)
{
uint8_t data = 0;
if(ring->count > 0)
{
data = ring->buffer[ring->tail];
ring->tail = (ring->tail + 1) % 256;
ring->count--;
}
return data;
}
// 在中断中使用
void USART1_IRQHandler(void)
{
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
{
uint8_t data = USART_ReceiveData(USART1);
RingBuffer_Write(&usart_rx_ring, data); // 写入环形缓冲区
}
}
参考代码 STM32 串口通信(库函数) www.youwenfan.com/contentcnt/133675.html
五、调试与优化
1. 常见问题排查
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 无法通信 | 波特率不匹配 | 检查双方波特率设置 |
| 接收乱码 | 时钟配置错误 | 确认系统时钟和APB时钟 |
| 数据丢失 | 接收不及时 | 使用中断或DMA接收 |
| 只能接收一次 | 中断标志未清除 | 确保正确清除中断标志 |
2. 性能优化建议
- 使用DMA:大量数据传输时使用DMA,减少CPU占用
- 环形缓冲区:解决中断接收数据丢失问题
- 波特率选择:平衡速度和稳定性(115200最常用)
- 错误处理:添加帧错误、噪声错误检测
六、扩展应用
1. 多串口通信
// 串口1用于调试,串口2用于设备通信
USART1_Init(115200); // 调试串口
USART2_Init(9600); // 设备通信串口
2. Modbus协议实现
// Modbus RTU帧格式
// [地址][功能码][数据][CRC16]
void Modbus_SendFrame(uint8_t addr, uint8_t func, uint16_t *data, uint8_t len)
{
uint8_t frame[256];
uint16_t crc;
uint8_t i = 0;
frame[i++] = addr;
frame[i++] = func;
for(uint8_t j = 0; j < len; j++)
{
frame[i++] = (data[j] >> 8) & 0xFF; // 高字节
frame[i++] = data[j] & 0xFF; // 低字节
}
crc = CRC16(frame, i); // 计算CRC16
frame[i++] = crc & 0xFF; // CRC低字节
frame[i++] = (crc >> 8) & 0xFF; // CRC高字节
USART1_SendData(frame, i); // 发送帧
}
七、总结
STM32串口通信是嵌入式开发的基础技能,关键要点:
- 正确配置:时钟、GPIO、USART参数
- 选择合适的方式:轮询、中断或DMA
- 做好错误处理:超时、缓冲区溢出等
- 考虑实际应用:数据格式、协议设计