UART_DMA_DBUS-DEMO

本文详细介绍了如何通过DMA加速UART的异步通信过程,包括配置步骤、DMA初始化与使用,以及在串口中断回调中调用DMA接收。特别关注了双缓冲区策略和反相器(DBUS)的应用,以提高数据传输效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

UART_DMA_DBUS

UART

  • 打开UART或者USART-选择Asynchronous

  • Asynchronous - 异步通信

    Synchronous - 同步通信

  • 打开Parameter Settings选项卡

  • 分别配置波特率、数据位长度、校验位、停止位

  • 数据方向根据需求配置

  • 过采样一般默认16

  • NVIC Setting选项卡中打开串口中断-根据需求自行开启

  • DMA Setting-Add-一般默认即可

  • 优先级根据需求调整

  • GPIO Setting-检查IO口是否选择正确

UART基础配置完成


DMA

  • DMA配置可以使用HAL库函数直接配置
  • 也可以自己仿照HAL库函数进行配置
  • 此处采用自己配置的方式配置DMA

DAM_init

void DMA_init(void)
{
    //enable the DMA transfer for the receiver request
    SET_BIT(huart3.Instance->CR3, USART_CR3_DMAR);
    //enalbe idle interrupt
    __HAL_UART_ENABLE_IT(&huart3, UART_IT_IDLE);
    //disable DMA
    __HAL_DMA_DISABLE(&hdma_usart3_rx);
    while(hdma_usart3_rx.Instance->CR & DMA_SxCR_EN)
    {
        __HAL_DMA_DISABLE(&hdma_usart3_rx);
    }
    hdma_usart3_rx.Instance->PAR = (uint32_t) & (USART3->DR);
    //memory buffer 1
    hdma_usart3_rx.Instance->M0AR = (uint32_t)(sbus_rx_buf[0]);
    //memory buffer 2
    hdma_usart3_rx.Instance->M1AR = (uint32_t)(sbus_rx_buf[1]);
    //data length
    hdma_usart3_rx.Instance->NDTR = SBUS_RX_BUF_NUM;
    //enable double memory buffer
    SET_BIT(hdma_usart3_rx.Instance->CR, DMA_SxCR_DBM);
    //enable DMA
    __HAL_DMA_ENABLE(&hdma_usart3_rx);

}

注:此处需要更改的关键字如下:

huart3 — 对应串口句柄

hdma_usart3_rx — 对应串口DMA接收句柄

USART3 — 对应串口句柄

sbus_rx_buf[0] — 对应DMA第一缓冲区

sbus_rx_buf[1] — 对应DMA第二缓冲区

DMA_use

void DMA_receive(void)
{
	if(huart3.Instance->SR & UART_FLAG_RXNE)
    {
        __HAL_UART_CLEAR_PEFLAG(&huart3);
    }
    else if(USART3->SR & UART_FLAG_IDLE)
    {
        static uint16_t this_time_rx_len = 0;
        __HAL_UART_CLEAR_PEFLAG(&huart3);
        if ((hdma_usart3_rx.Instance->CR & DMA_SxCR_CT) == RESET)
        {
            /* Current memory buffer used is Memory 0 */
            //disable DMA
            __HAL_DMA_DISABLE(&hdma_usart3_rx);
            //get receive data length, length = set_data_length - remain_length
            this_time_rx_len = SBUS_RX_BUF_NUM - hdma_usart3_rx.Instance->NDTR;
            //reset set_data_lenght
            hdma_usart3_rx.Instance->NDTR = SBUS_RX_BUF_NUM;
            //set memory buffer 1
            hdma_usart3_rx.Instance->CR |= DMA_SxCR_CT;
            //enable DMA
            __HAL_DMA_ENABLE(&hdma_usart3_rx);
            if(this_time_rx_len == RC_FRAME_LENGTH)
            {
               sbus_to_rc(sbus_rx_buf[0], &rc);
            }
        }
        else
        {
            /* Current memory buffer used is Memory 1 */
            //disable DMA
            __HAL_DMA_DISABLE(&hdma_usart3_rx);
            //get receive data length, length = set_data_length - remain_length
            this_time_rx_len = SBUS_RX_BUF_NUM - hdma_usart3_rx.Instance->NDTR;
            //reset set_data_lenght
            hdma_usart3_rx.Instance->NDTR = SBUS_RX_BUF_NUM;
            //set memory buffer 0
            DMA1_Stream1->CR &= ~(DMA_SxCR_CT);
            //enable DMA
            __HAL_DMA_ENABLE(&hdma_usart3_rx);
            if(this_time_rx_len == RC_FRAME_LENGTH)
            {
                sbus_to_rc(sbus_rx_buf[1], &rc);
            }
        }
    }
}

注:此处需要更改的关键字如下:

huart3 — 对应串口句柄

hdma_usart3_rx — 对应串口DMA接收句柄

USART3 — 对应串口句柄

sbus_rx_buf[0] — 对应DMA第一缓冲区

sbus_rx_buf[1] — 对应DMA第二缓冲区

SBUS_RX_BUF_NUM — DMA缓冲区给的空间长度

RC_FRAME_LENGTH — DMA接收到的目标数据长度

sbus_to_rc — 处理DMA接收到的数据的函数

DMA1_Stream1 — 对应的DMA数据流

最后需要在串口回调函数里调用 - DMA_receive()

eg: - 此处以串口3的回调函数为例

void USART3_IRQHandler(void)
{
  /* USER CODE BEGIN USART3_IRQn 0 */
	DMA_receive();
  /* USER CODE END USART3_IRQn 0 */
  HAL_UART_IRQHandler(&huart3);
  /* USER CODE BEGIN USART3_IRQn 1 */
  /* USER CODE END USART3_IRQn 1 */
}

对DMA的理解

DMA_init()

  • 使能DMA串口接收
  • 使能串口空闲中断
  • 先暂时关闭DMA,以便对DMA进行配置
  • 配置DMA外设地址 - 此处用于串口接收,所以配置为串口的DR数据寄存器
  • 配置DMA第一缓冲区地址
  • 配置DMA第二缓冲区地址
  • 配置DMA数据长度 - 此处一般配置为实际接收数据的两倍
  • 使能双缓冲区配置
  • 使能DMA - DMA配置完成后打开DMA

DMA_use

  • 如果接收到数据并且串口有空闲中断触发
  • 先关闭DMA
  • 获取这一次接收到的数据长度
  • 重新设定下一次接收的数据长度
  • 重新设定下一次接收的缓冲器的地址
  • 打开DMA-使能DMA
  • 如果这一次接收到的数据长度等于我DMA接收目标数据长度
  • 则对DMA接收到的数据进行解析处理 - sbus_to_rc ()
  • 下一次就使用另外一个缓冲区进行接收
  • 最后在串口中断回调函数里调用就可以

DBUS

在串口前面加一个反相器

在这里插入图片描述




<think>我们之前讨论的UART DMA Frame Receiver程序,在错误回调中直接重启DMA接收是必要的,但需要增加一些关键步骤以确保稳定性。 原始代码直接重启DMA,但未处理错误状态清除和DMA停止,可能导致问题。 以下是改进后的错误回调函数,包含必要的步骤: 1. 检查是否是目标UART(如果有多个UART) 2. 停止当前的DMA传输(使用HAL_UART_DMAStop) 3. 清除UART错误标志(通过清除huart->ErrorCode) 4. 重置接收状态(如currentIndex等,根据你的状态结构体) 5. 重新启动DMA接收 因此,修改后的错误回调函数应该如下:</think>### UART DMA Frame Receiver 错误回调的完善实现 您提供的错误回调函数基本正确,但需要添加关键的错误恢复步骤。以下是完整的优化实现: ```c // 完善的UART错误回调函数 void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart) { // 1. 检查是否是目标UART实例 if (huart->Instance != USART1) // 替换为实际使用的UART return; // 2. 获取状态结构体指针 UART_DMA_State* state = &uart_state; // 全局状态变量 // 3. 停止当前DMA传输(关键步骤) HAL_UART_DMAStop(huart); // 4. 清除HAL错误标志(必须执行) huart->ErrorCode = HAL_UART_ERROR_NONE; // 5. 重置接收状态 state->currentIndex = 0; state->frameReady = 0; // 6. 重启DMA接收 HAL_UART_Receive_DMA(huart, state->activeBuffer, MAX_FRAME_SIZE); // 7. 更新状态标志 state->receiving = 1; state->lastRxTime = HAL_GetTick(); // 8. 错误统计(可选但推荐) state->errorCount++; } ``` ### 关键改进说明: 1. **DMA停止操作** (`HAL_UART_DMAStop`): - 在重启接收前必须显式停止当前DMA传输 - 防止DMA控制器处于不确定状态 - 避免残留的传输请求导致问题 2. **错误标志清除** (`huart->ErrorCode = HAL_UART_ERROR_NONE`): - 必须重置HAL库的内部错误状态 - 否则后续操作可能失败或行为异常 - 这是HAL库正常工作的重要前提 3. **状态重置**: ```c state->currentIndex = 0; // 重置缓冲区索引 state->frameReady = 0; // 清除帧就绪标志 ``` - 确保从干净的状态开始新的接收 - 避免错误发生时的部分数据被误判为有效帧 4. **实例检查**: ```c if (huart->Instance != USART1) return; ``` - 防止处理错误的UART实例 - 在多个UART的系统中尤其重要 ### 完整的状态结构体定义 结合之前的帧接收程序,状态结构体应包含错误处理字段: ```c #define MAX_FRAME_SIZE 128 #define ERROR_THRESHOLD 5 // 连续错误阈值 typedef struct { uint8_t activeBuffer[MAX_FRAME_SIZE]; uint8_t backupBuffer[MAX_FRAME_SIZE]; volatile uint8_t receiving; volatile uint32_t lastRxTime; volatile uint16_t currentIndex; volatile uint8_t frameReady; // 错误处理增强字段 uint32_t errorCount; // 总错误计数 uint32_t lastErrorTime; // 上次错误时间戳 uint8_t consecutiveErrors; // 连续错误计数 } UART_DMA_State; // 全局状态实例 UART_DMA_State uart_state = {0}; ``` ### 增强型错误回调(带智能恢复) ```c void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart) { if (huart->Instance != USART1) return; UART_DMA_State* state = &uart_state; // 停止当前传输并清除错误 HAL_UART_DMAStop(huart); huart->ErrorCode = HAL_UART_ERROR_NONE; // 重置接收状态 state->currentIndex = 0; state->frameReady = 0; // 重启接收 HAL_UART_Receive_DMA(huart, state->activeBuffer, MAX_FRAME_SIZE); state->receiving = 1; state->lastRxTime = HAL_GetTick(); // 更新错误统计 state->errorCount++; state->lastErrorTime = HAL_GetTick(); state->consecutiveErrors++; // 连续错误处理(阈值触发) if (state->consecutiveErrors > ERROR_THRESHOLD) { // 严重错误处理:硬件复位 emergency_uart_reset(huart); // 重置连续错误计数 state->consecutiveErrors = 0; // 记录严重错误事件 log_critical_error("UART连续错误超过阈值"); } } // 紧急复位函数 void emergency_uart_reset(UART_HandleTypeDef *huart) { // 1. 停止所有UART相关操作 HAL_UART_DMAStop(huart); HAL_UART_Abort(huart); // 2. 重新初始化硬件 HAL_UART_DeInit(huart); HAL_Delay(10); // 短暂延迟确保硬件稳定 MX_USART1_UART_Init(); // 重新初始化函数(CubeMX生成) // 3. 重新配置DMA HAL_UART_Receive_DMA(huart, uart_state.activeBuffer, MAX_FRAME_SIZE); // 4. 更新状态 uart_state.receiving = 1; uart_state.lastRxTime = HAL_GetTick(); } ``` ### 为什么需要这些改进? 1. **错误恢复的可靠性**: - 简单重启可能无法解决硬件层面的问题 - 连续错误时硬件复位更可靠 2. **状态一致性**: - 未重置状态可能导致部分接收的数据被误用 - 索引和标志重置确保干净的接收开始 3. **系统稳定性**: - 错误计数帮助识别间歇性故障 - 阈值触发防止错误循环消耗资源 4. **诊断能力**: - 错误统计有助于后期故障分析 - 连续错误计数指示通信链路质量 ### 最终推荐实现 ```c // 优化的UART错误回调 void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart) { // 仅处理目标UART if (huart->Instance != USART1) return; // 获取状态 UART_DMA_State* state = &uart_state; // 关键恢复序列 HAL_UART_DMAStop(huart); huart->ErrorCode = HAL_UART_ERROR_NONE; state->currentIndex = 0; state->frameReady = 0; // 重启接收 HAL_UART_Receive_DMA(huart, state->activeBuffer, MAX_FRAME_SIZE); state->receiving = 1; state->lastRxTime = HAL_GetTick(); // 更新错误计数 state->errorCount++; state->lastErrorTime = HAL_GetTick(); // 连续错误处理(使用安全增量) if (state->consecutiveErrors < 255) state->consecutiveErrors++; // 阈值检测(每5次错误触发复位) if (state->consecutiveErrors >= 5) { emergency_uart_reset(huart); state->consecutiveErrors = 0; // 重置计数器 } } ``` 这个实现提供了健壮的错误恢复机制,同时保持代码简洁高效。它确保在错误发生后UARTDMA能回到一致状态,并在连续错误时执行更彻底的硬件复位。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值