关于STM32用DMA传输UART空闲中断中接收的数据时无法接收数据问题以及解决办法

文章介绍了如何在STM32CubeIDE中配置DMA进行串口接收,强调了普通模式和循环模式的区别,并指出在空闲中断处理数据时的模式选择。同时,文章指出了初始化顺序错误和开启传输失败可能导致的问题及解决方案,包括清空接收缓冲区和错误标志。最后,提供了一个代码实例展示初始化和中断处理过程。

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

一、stm32 cube ide 配置

1、DMA串口接收数据的ide配置如下图所示

        串口1相关的设置及printf函数的使用,这里没放,建议先实现串口打印功能

可以参考:使用STM32 CUBE IDE配置STM32F7 用DMA传输多通道ADC数据_stm32cubeide 配置adc_一只小白啊的博客-优快云博客

2、相关的知识点

        普通模式和循环模式的区别在于,普通模式下,DMA只会接收一次数据,接收完成后就会停止,需要接收时再开启;而循环模式下,DMA会一直接收数据,直到接收缓存区满或者手动停止

       根据自己需求定模式,如果是数据有间隔,空闲中断的这种情况下,处理一帧数据,建议用Normal,有很多例程用Circular模式的情况下处理一帧数据,还要在中断中使用函数HAL_UART_DMAStop(); 来停止此次接收,处理数据后再重新启动接收,就显得有点多次两举了,如果用Circular模式,没有用HAL_UART_DMAStop停止接收的话,DMA会一直循环填充数据,size为最大,因为数据是循环填充的,所以数据的位置不定,看上去是乱的。

        关于禁用DMA传输的半传输中断,在空闲中断接收数据时,因为是不定长,假如在开启接收最大长度为128,那么当接收到64字节数据的时候就会触发半传输中断,虽然半传输中断的回调函数是HAL_UART_RxHalfCpltCallback(); 但是测试发现传输一半字节长度也会触发接收事件回调函数HAL_UARTEx_RxEventCallback(); 所以,还是关闭半传输中断。

 二、无法接收数据问题

1、初始化顺序不对导致DMA无法正常传输串口接收的数据。

        具体原因查看:

使用STM32 CUBE IDE配置STM32F7 用DMA传输多通道ADC数据_stm32cubeide 配置adc_一只小白啊的博客-优快云博客        文章最后部分的注意事项。

解决办法:先初始化DMA再初始化串口。

2、开启传输失败,导致DMA传输被重置为标准接收模式,无法触发空闲中断。

        产生原因:在使用HAL_UARTEx_ReceiveToIdle_DMA时,如果出现错误,即函数返回值=HAL_ERROR时,会被重置为HAL_UART_RECEPTION_STANDARD 标准接收模式,即中断回调函数为HAL_UART_RxCpltCallback,触发条件为当接收的数据满启动时指定的Size,如启动接收时指定接收长度为128字节,当接收到128字节时就会触发一次中断(如果DMA为循环模式)。

        这个错误可能出现的情况:开启状态下已经触发了一个中断,比如溢出或者重启时别的设备发送了一串数据,触发了一个空闲中断,同时有数据在串口的缓冲区,就会导致开启HAL_UARTEx_ReceiveToIdle_DMA时产生一个错误返回值,同时被置为标准接收模式。

        解决办法:清空接收缓冲区(不清空的话,依旧会返回HAL_ERROR),同时清除错误标志。调用HAL_UART_AbortReceive函数可以同时满足停止DMA传输、清空接收缓冲区、清除标志。

三、代码实例

整个实现逻辑如下图所示:

1、基本设置

/******************************************************************************
 * 参数:UART
 *****************************************************************************/
#define USART_REC_LEN 128 // 一次最大接收的字节数

typedef struct
{
    uint8_t Receive_buffer[USART_REC_LEN]; // DMA数据接收缓存
    _Bool Receive_state;                   // 接收状态 表示一帧数据是否完成接收
    uint8_t Receive_length;                // 表示一帧数据长度,因为最大接收长度定义为128 所以uint8_t 够用
    _Bool Receive_flag;                    // 表示一帧数据正确,如果判断接收的数据为符合协议的能用的数据,则置1

} UART_Receive_Def;
UART_Receive_Def UART5_DMA;

/******************************************************************************
 * 功能:串口错误代码
 *****************************************************************************/
uint8_t Error_code_uart5;

/******************************************************************************
 * 功能:打印串口状态标志
 *****************************************************************************/
void print_uart_sr(UART_HandleTypeDef *huart)
{
    printf("UART_FLAG_RWU       UART_FLAG_SBKF -->  %X -->  %X\r\n", __HAL_UART_GET_FLAG(huart, UART_FLAG_RWU), __HAL_UART_GET_FLAG(huart, UART_FLAG_SBKF));
    printf("UART_FLAG_CMF       UART_FLAG_BUSY -->  %X -->  %X\r\n", __HAL_UART_GET_FLAG(huart, UART_FLAG_CMF), __HAL_UART_GET_FLAG(huart, UART_FLAG_BUSY));
    printf("UART_FLAG_ABRF      UART_FLAG_ABRE -->  %X -->  %X\r\n", __HAL_UART_GET_FLAG(huart, UART_FLAG_ABRF), __HAL_UART_GET_FLAG(huart, UART_FLAG_ABRE));
    printf("UART_FLAG_CTS       UART_FLAG_LBDF -->  %X -->  %X\r\n", __HAL_UART_GET_FLAG(huart, UART_FLAG_CTS), __HAL_UART_GET_FLAG(huart, UART_FLAG_LBDF));
    printf("UART_FLAG_TXE         UART_FLAG_TC -->  %X -->  %X\r\n", __HAL_UART_GET_FLAG(huart, UART_FLAG_TXE), __HAL_UART_GET_FLAG(huart, UART_FLAG_TC));

    printf("UART_FLAG_RXNE      UART_FLAG_RTOF -->  %X -->  %X\r\n", __HAL_UART_GET_FLAG(huart, UART_FLAG_RXNE), __HAL_UART_GET_FLAG(huart, UART_FLAG_RTOF));
    printf("UART_FLAG_IDLE       UART_FLAG_ORE -->  %X -->  %X\r\n", __HAL_UART_GET_FLAG(huart, UART_FLAG_IDLE), __HAL_UART_GET_FLAG(huart, UART_FLAG_ORE));
    printf("UART_FLAG_NE          UART_FLAG_FE -->  %X -->  %X\r\n", __HAL_UART_GET_FLAG(huart, UART_FLAG_NE), __HAL_UART_GET_FLAG(huart, UART_FLAG_FE));
    printf("UART_FLAG_PE                       --> %X \r\n", __HAL_UART_GET_FLAG(huart, UART_FLAG_PE));
}

2、初始化,不查看错误代码的话,启动只需要一条命令。

    /******************************************************************************
     * 功能:DMA接收,空闲中断
     *****************************************************************************/
    // 开启DMA传输UART空闲中断中接收的数据,并在接收到UART空闲中断后停止传输。
    Error_code_uart5 = HAL_UARTEx_ReceiveToIdle_DMA(&huart5, UART5_DMA.Receive_buffer, USART_REC_LEN);
    __HAL_DMA_DISABLE_IT(&hdma_uart5_rx, DMA_IT_HT);

    // 打印UART状态标志和错误码
    print_uart_sr(&huart5);
    printf("Error_code_uart5 --> %ld \r\n", Error_code_uart5);

    // 如果有错误的话
    if (Error_code_uart5 != HAL_OK)
    {
        // 清空缓冲,置位一些标志,具体看函数内部
        HAL_UART_AbortReceive(&huart5);
        // 重启接收
        Error_code_uart5 = HAL_UARTEx_ReceiveToIdle_DMA(&huart5, UART5_DMA.Receive_buffer, USART_REC_LEN);
    }

    // 打印UART状态标志和错误码
    print_uart_sr(&huart5);
    printf("Error_code_uart5 --> %ld \r\n", Error_code_uart5);

3、中断回调,触发了空闲中断,接收到了一帧数据

void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *UartHandle, uint16_t Size)
{
    if (UartHandle == &huart5)
    {
        /******************************************************************************
         * 功能:DMA接收,空闲中断
         *****************************************************************************/
		
        //处理数据(UART5_DMA.Receive_buffer, Size)
        //串口1打印或者其他处理,调试查看等
        //HAL_UART_Transmit(&huart1, UART5_DMA.Receive_buffer, Size, 0xFFFF);
		//重启接收
        HAL_UARTEx_ReceiveToIdle_DMA(&huart5, UART5_DMA.Receive_buffer, USART_REC_LEN);
        __HAL_DMA_DISABLE_IT(&hdma_uart5_rx, DMA_IT_HT);
}
}

### 关于串口DMA传输半完成中断 对于串口DMA传输中的半完成中断处理,在某些应用场景中确实存在需求,尤其是在需要实监控数据传输进度的情况下。然而,标准的STM32 HAL库并未直接提供针对DMA半完成事件的支持。 为了实现这一功能,可以通过自定义方式捕获DMA通道的状态变化,并据此判断是否达到了半个缓冲区的数据量。具体做法是在DMA配置阶段启用传输错误、溢出以及传输完成中断,然后在对应的中断服务程序(ISR)里检查已传输字节数与设定阈值的关系: ```c void DMA1_Channel5_IRQHandler(void) { /* 检查DMA传输完成标志 */ if (__HAL_DMA_GET_FLAG(&hdma_usart1_rx, __HAL_DMA_GET_TC_FLAG_INDEX(&hdma_usart1_rx)) != RESET){ // 清除TC位 __HAL_DMA_CLEAR_FLAG(&hdma_usart1_rx,DMA_FLAG_TC5); // 如果当前已经接收到一半以上的数据,则触发回调函数或其他操作 uint16_t halfSize = BUFFER_SIZE / 2; if(HAL_DMA_GetCurrentMemoryAddress(&hdma_usart1_rx) >= (uint32_t)(aRxBuffer + halfSize)){ // 执行相应动作... } } /* 处理其他可能发生的异常情况 */ } ``` 上述方法依赖于对DMA控制器内部寄存器的操作来获取实际传输了多少个元素[^1]。需要注意的是这种方法并不是最理想的解决方案,因为其准确性取决于具体的硬件架构及序安排;更推荐的做法是修改底层驱动或者寻找第三方库支持此特性。 另一种更为通用的方法是采用定器配合查询的方式定期检测DMA剩余未传数量,当发现接近预设的一半位置即刻做出响应。这种方式虽然牺牲了一定程度上的即性,但在很多情况下足以满足应用需求。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值