uart的配置和使用

一、串口通信原理

串口通信(Serial Communication)是一种逐位传输数据的通信方式,常用于 MCU 与外设或 PC 通信。

1. 工作模式

STM32 支持全双工通信(TX、RX):

  • TX(Transmit):发送引脚

  • RX(Receive):接收引脚

2. 参数配置

关键参数包括:

  • 波特率(Baudrate):如 115200bps

  • 数据位:一般为 8 位

  • 停止位:1 位或 2 位

  • 校验位:无校验、偶校验、奇校验

  • 流控:无、硬件流控(RTS/CTS)

二、串口通信实现(使用 STM32 HAL 库)

1. 初始化步骤

a. 使能串口及 GPIO 时钟
__HAL_RCC_USART1_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();

b. 配置 GPIO 引脚为复用功能

GPIO_InitTypeDef GPIO_InitStruct = {0};

// PA9 -> USART1_TX, PA10 -> USART1_RX
GPIO_InitStruct.Pin = GPIO_PIN_9 | GPIO_PIN_10;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

c. 初始化 USART 外设

UART_HandleTypeDef huart1;

void MX_USART1_UART_Init(void)
{
    huart1.Instance = USART1;
    huart1.Init.BaudRate = 115200;
    huart1.Init.WordLength = UART_WORDLENGTH_8B;
    huart1.Init.StopBits = UART_STOPBITS_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;
    if (HAL_UART_Init(&huart1) != HAL_OK)
    {
        Error_Handler();
    }
}

2. 数据收发示例

a. 发送数据
uint8_t msg[] = "Hello UART\r\n";
HAL_UART_Transmit(&huart1, msg, sizeof(msg) - 1, HAL_MAX_DELAY);

b. 接收数据(阻塞)

uint8_t rx_data;
HAL_UART_Receive(&huart1, &rx_data, 1, HAL_MAX_DELAY);

c. 使用中断接收(推荐)

uint8_t rx_buffer[10];

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    if (huart->Instance == USART1)
    {
        // 处理接收到的数据
        HAL_UART_Transmit(&huart1, rx_buffer, 1, 10);
        HAL_UART_Receive_IT(&huart1, rx_buffer, 1);  // 继续接收
    }
}

开启接收中断:

HAL_UART_Receive_IT(&huart1, rx_buffer, 1);

stm32f1xx_it.c 中加入:

void USART1_IRQHandler(void)
{
    HAL_UART_IRQHandler(&huart1);
}

三、开发中需要注意的问题

  1. 波特率不匹配
    MCU 与上位机/外设波特率不一致会导致乱码或收不到数据。

  2. 缓冲区溢出
    中断模式下若处理不及时,可能导致接收缓冲区溢出。

  3. 时钟配置错误
    HCLK/APB 时钟错误会导致串口实际波特率不准,建议在 CubeMX 中统一配置。

  4. FIFO 与缓存清理问题
    使用 DMA 模式时需正确处理空闲中断和接收长度。

  5. 中断优先级配置不合理
    如果串口中断优先级太低,可能导致数据丢失或系统响应慢。

四、调试与问题排查

1. 常见问题排查

问题现象可能原因解决方案
无数据发送/接收1. 波特率不匹配
2. 线序错误
3. 未初始化
1. 检查波特率设置
2. 交换TX/RX
3. 检查初始化代码
接收数据不完整/乱码1. 时钟配置错误
2. 缓冲区溢出
3. 电气干扰
1. 检查时钟树配置
2. 增加缓冲区大小
3. 添加滤波电容
通信一段时间后中断1. 缓冲区未及时处理
2. 错误标志未清除
1. 优化数据处理速度
2. 清除错误标志 __HAL_UART_CLEAR_FLAG()
DMA传输卡死1. DMA通道冲突
2. 内存对齐问题
1. 检查DMA通道映射
2. 确保4字节对齐
低功耗模式下无法唤醒1. 未配置唤醒功能
2. 时钟源错误
1. 启用串口唤醒功能
2. 使用HSI时钟

2. 调试技巧

  • 使用 USARTx->DRUSARTx->SR 直接调试底层寄存器可确认是否有数据发送/接收

  • 使用示波器/逻辑分析仪观察串口波形是否正常

  • 使用串口工具(如 SSCOM、RealTerm)查看接收数据是否符合预期

  • 打印调试信息到串口,确认程序运行流程

  • CubeMX 生成代码时可自动配置 NVIC 和 GPIO,避免人工遗漏

一、STM32 串口 DMA + 空闲中断

1、DMA + 空闲中断接收的原理

在高频率或大数据量串口通信场景中,频繁进入中断处理不高效。此时可使用 DMA 接收 + 空闲中断识别帧尾 的方案。

原理流程:

  1. 启动 UART DMA 接收(HAL_UART_Receive_DMA)

  2. 每次数据帧传输完成,串口会进入 空闲中断

  3. 在中断中读取 DMA 接收的实际长度

  4. 将接收到的数据拷贝或处理(如加入环形缓冲区)

  5. 重新启动 DMA 接收

2、代码实现:DMA + 空闲中断接收帧

  1、环境准备(CubeMX)

  • 使能 USART1,勾选 USART1 global interruptDMA Rx

  • 选择 RX DMA 为 Normal ModeCircular

  • NVIC 里开启 USART1 global interrupt

2. 定义变量

#define UART_DMA_RX_BUF_SIZE 256

uint8_t uart1_dma_rx_buf[UART_DMA_RX_BUF_SIZE];  // DMA 接收缓冲区
uint8_t uart1_frame_buf[UART_DMA_RX_BUF_SIZE];   // 接收帧处理缓冲区
uint16_t uart1_frame_len = 0;

3、初始化 DMA 接收 + 空闲中断

HAL_UART_Receive_DMA(&huart1, uart1_dma_rx_buf, UART_DMA_RX_BUF_SIZE);
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);  // 开启 IDLE 中断

4、中断服务函数

void USART1_IRQHandler(void)
{
    HAL_UART_IRQHandler(&huart1);
    
    // 空闲中断处理
    if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE))
    {
        __HAL_UART_CLEAR_IDLEFLAG(&huart1);
        
        // 计算接收字节数
        uint16_t len = UART_DMA_RX_BUF_SIZE - __HAL_DMA_GET_COUNTER(huart1.hdmarx);

        // 拷贝接收到的数据
        memcpy(uart1_frame_buf, uart1_dma_rx_buf, len);
        uart1_frame_len = len;

        // 处理数据(例如:Modbus)
        Modbus_Parse(uart1_frame_buf, uart1_frame_len);

        // 重启 DMA
        HAL_UART_Receive_DMA(&huart1, uart1_dma_rx_buf, UART_DMA_RX_BUF_SIZE);
    }
}

二、使用 DMA 时的注意事项

问题原因解决方案
空闲中断不触发没有启用 UART_IT_IDLE 中断__HAL_UART_ENABLE_IT(..., UART_IT_IDLE)
DMA 重启失败或溢出使用循环 DMA 且未读取数据用 Normal 模式手动重启,及时处理数据
帧截断/拼接没有判断帧头帧尾、没有超时机制配合定时器、空闲中断、帧头帧尾标志同步判断
HAL_UART_Receive_DMA 崩溃DMA 未停止/数据未处理完成重启 DMA 前应禁用或先清空状态

调试工具与技术

  1. 逻辑分析仪:验证信号波形、波特率、帧结构

  2. 串口调试助手:验证数据内容(推荐:Putty、Tera Term)

  3. STM32CubeMonitor:实时监控变量和寄存器

  4. ErrorCode分析:检查huart->ErrorCode的值

  5. 断点调试:在回调函数设置断点

三、总结

  • DMA + 空闲中断是高效、稳定的串口接收方案

  • 配合帧协议(如 Modbus RTU)可以处理复杂应用场景

  • 串口通信调试建议采用逐步验证法(先收,再校验 CRC,再解析)

STM32 串口使用 HAL 库 + RingBuffer 接收数据说明

一、什么是 RingBuffer(环形缓冲区)

RingBuffer(循环缓冲区)是一种先进先出(FIFO)的数据结构。适合持续接收数据的场景,如串口中断模式下收发数据不定长、速率不固定的通信。

特点:

  • 固定大小内存,节省资源

  • 高效地避免数据覆盖(通过头尾指针控制)

  • 易于配合中断模式收数据

二、适用场景

  • 使用 HAL_UART_Receive_IT() 接收单字节

  • HAL_UART_RxCpltCallback() 中将接收的字节写入 RingBuffer

  • 主循环中从 RingBuffer 中读取数据解析帧

三、RingBuffer 实现

1. 定义结构体与缓冲区

#define RINGBUF_SIZE 256

typedef struct {
    uint8_t buffer[RINGBUF_SIZE];
    volatile uint16_t head;
    volatile uint16_t tail;
} RingBuffer_t;

RingBuffer_t uart1_rx_ringbuf;
uint8_t uart1_rx_temp;  // 临时变量接收单字节

2. RingBuffer 操作函数

void RingBuffer_Init(RingBuffer_t *rb)
{
    rb->head = rb->tail = 0;
}

int RingBuffer_IsEmpty(RingBuffer_t *rb)
{
    return rb->head == rb->tail;
}

int RingBuffer_IsFull(RingBuffer_t *rb)
{
    return ((rb->head + 1) % RINGBUF_SIZE) == rb->tail;
}

void RingBuffer_Put(RingBuffer_t *rb, uint8_t data)
{
    if (!RingBuffer_IsFull(rb)) {
        rb->buffer[rb->head] = data;
        rb->head = (rb->head + 1) % RINGBUF_SIZE;
    }
}

int RingBuffer_Get(RingBuffer_t *rb, uint8_t *data)
{
    if (!RingBuffer_IsEmpty(rb)) {
        *data = rb->buffer[rb->tail];
        rb->tail = (rb->tail + 1) % RINGBUF_SIZE;
        return 1;
    }
    return 0;
}

四、串口中断接收与 RingBuffer 结合

1. 初始化

在主函数中:

RingBuffer_Init(&uart1_rx_ringbuf);
HAL_UART_Receive_IT(&huart1, &uart1_rx_temp, 1);  // 启动单字节接收中断

2. 接收回调函数

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    if (huart->Instance == USART1) {
        RingBuffer_Put(&uart1_rx_ringbuf, uart1_rx_temp);
        HAL_UART_Receive_IT(&huart1, &uart1_rx_temp, 1);  // 继续接收
    }
}

五、主循环读取与帧解析

你可以在主循环中这样读取数据:

void Process_UART1_Data(void)
{
    static uint8_t frame[64];
    static uint8_t index = 0;
    uint8_t ch;

    while (RingBuffer_Get(&uart1_rx_ringbuf, &ch)) {
        if (ch == '\n') {
            frame[index] = 0;
            printf("接收到帧: %s\r\n", frame);
            index = 0;  // 重置接收
        } else if (index < sizeof(frame) - 1) {
            frame[index++] = ch;
        } else {
            index = 0;  // 超出最大帧长度则丢弃
        }
    }
}

六、RingBuffer 的注意事项

问题解决方法
数据被覆盖RingBuffer 满时拒绝写入或覆盖最旧数据
多线程/中断冲突在中断中写入,在主循环中读取,避免竞态
串口速度太快增加 RingBuffer 大小,或改为 DMA 模式
丢帧或粘包添加帧头/帧尾标志或固定长度协议

七、RingBuffer vs DMA 模式对比

特性RingBuffer + 中断DMA + 空闲中断
实现复杂度简单较复杂
性能一般高效,适合大数据量
适合场景命令帧/少量通信视频流、大帧、Modbus RTU
易维护性

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值