STM32 串口通信详解(标准库函数版)

STM32 串口通信详解(标准库函数版)

一、STM32串口(USART)核心原理

1. 串口通信基础

STM32的USART(通用同步异步收发器)支持:

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. 硬件注意事项

三、软件设计与核心代码

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. 性能优化建议

六、扩展应用

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串口通信是嵌入式开发的基础技能,关键要点:

  1. 正确配置:时钟、GPIO、USART参数
  2. 选择合适的方式:轮询、中断或DMA
  3. 做好错误处理:超时、缓冲区溢出等
  4. 考虑实际应用:数据格式、协议设计

专注于matlab/simulink,电子电路,编程