You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

STM32L476使用HAL_UARTEx_ReceiveToIdle_DMA循环模式实现串口回显时数据异常问题求助

STM32L476使用HAL_UARTEx_ReceiveToIdle_DMA循环模式实现串口回显时数据异常问题求助

看起来你遇到的是循环DMA串口接收时的数据拷贝越界、索引计算错误以及接收流程中断的问题,结合你的代码和现象,我来帮你梳理几个核心问题和修复方案:

1. 未初始化关键变量wr_index

你定义了uint8_t wr_index;但没有在main()函数中初始化为0,这会导致第一次进入回调时wr_index是随机值——虽然第一次运行可能刚好"蒙对",但后续的索引计算会完全混乱,这是后续数据异常的潜在诱因。

修复:
在main函数的初始化部分添加:

wr_index = 0;

2. 回调函数中未重新启动DMA接收

HAL_UARTEx_ReceiveToIdle_DMA在触发IDLE或TC事件后,会自动停止DMA接收(即使配置了循环模式,UART的DMA请求也会被暂停)。你的代码只在main函数启动了一次DMA,后续的串口数据无法被正确接收,这会导致数据覆盖或不完整。

修复:
HAL_UARTEx_RxEventCallback的末尾(switch语句结束后)添加重新启动DMA的代码:

HAL_UARTEx_ReceiveToIdle_DMA(&huart2, main_buffer, 16);

3. 数据拷贝未处理缓冲区绕回情况

当DMA在循环模式下接收数据时,若本次接收的Size字节跨了main_buffer的末尾和开头(比如wr_index=14,接收3字节:数据存在14、15、0位置),直接用一次memcpy会导致越界访问数组,拷贝到错误的内存数据——这正是你后续回显出现乱码的核心原因。

修复:
需要分两次拷贝数据:先拷贝从当前索引到缓冲区末尾的字节,再拷贝从缓冲区开头到剩余的字节:

uint16_t bytes_to_end = 16 - wr_index;
if (Size <= bytes_to_end) {
    memcpy(final_buffer + item_count, main_buffer + wr_index, Size);
} else {
    // 拷贝到缓冲区末尾的部分
    memcpy(final_buffer + item_count, main_buffer + wr_index, bytes_to_end);
    // 拷贝缓冲区开头的剩余部分
    memcpy(final_buffer + item_count + bytes_to_end, main_buffer, Size - bytes_to_end);
}

4. 结束符判断的越界问题

你原来的结束符判断用了main_buffer[wr_index - 1]main_buffer[wr_index - 2],当wr_index为0或1时,这会访问数组的越界地址(uint8_t溢出为255、254),导致读取错误的内存数据,进而触发错误的process标记。

修复:
建议基于已拷贝到final_buffer的数据判断结束符,这样无需考虑缓冲区绕回:

if (item_count >= 2 && final_buffer[item_count - 1] == 'r' && final_buffer[item_count - 2] == 'q') {
    process = true;
}

5. 简化wr_index的更新逻辑

原来的wr_index = 16 - X;依赖DMA计数器,不如直接用模运算更可靠:每次接收Size字节后,wr_index更新为(wr_index + Size) % 16,自动处理绕回情况。

修复:
在回调的事件处理中替换为:

wr_index = (wr_index + Size) % 16;

修复后的完整回调函数示例

void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size) {
    if (huart->Instance != USART2) return; // 只处理UART2的事件

    uint16_t bytes_to_end = 16 - wr_index;
    switch(HAL_UARTEx_GetRxEventType(&huart2)) {
        case HAL_UART_RXEVENT_IDLE:
        case HAL_UART_RXEVENT_TC: {
            // 处理跨缓冲区的数据拷贝
            if (Size <= bytes_to_end) {
                memcpy(final_buffer + item_count, main_buffer + wr_index, Size);
            } else {
                memcpy(final_buffer + item_count, main_buffer + wr_index, bytes_to_end);
                memcpy(final_buffer + item_count + bytes_to_end, main_buffer, Size - bytes_to_end);
            }

            // 更新接收计数
            item_count += Size;

            // 判断结束符
            if (item_count >= 2 && final_buffer[item_count - 1] == 'r' && final_buffer[item_count - 2] == 'q') {
                process = true;
            }

            // 更新下一次DMA写入的起始索引
            wr_index = (wr_index + Size) % 16;
            break;
        }
        default:
            break;
    }

    // 重新启动DMA接收
    HAL_UARTEx_ReceiveToIdle_DMA(&huart2, main_buffer, 16);
}

额外调试建议

  1. 在回调函数中可以临时添加串口打印(用HAL_UART_Transmit)输出wr_indexSize等值,观察每次接收的索引变化是否符合预期;
  2. 检查DMA的配置是否确实是循环模式(CubeMX中DMA的Mode设置为Circular);
  3. 确保final_buffer的大小足够,避免多次接收后溢出。

内容来源于stack exchange

火山引擎 最新活动