前段时间在做综合应用项目的时候大量使用了串口空闲中断+消息队列的设计;但是发现系统串口中断接收总是在运行不定时时间,不定时数据量的时候再也无法进入,该问题不会导致系统死机,无法触发看门狗复位芯片

一、问题代码与分析

1.ISR代码

static uint8_t DMARecvBuffer4[UART_MAX_RECV_LENGTH]={0};

void uartComUpRecv(void)
{
    HAL_StatusTypeDef ret=HAL_ERROR;
    ret=HAL_UARTEx_ReceiveToIdle_IT(&huart4,DMARecvBuffer4,UART_MAX_RECV_LENGTH);
    if(ret!=HAL_OK){
        LOG_ERROR(0,"Uart4 IT Enable Failed:%d",ret);
    }
}

void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
    // 其他串口...

    // 上位机网口
    if (huart == &huart4){
        DMARecvBuffer4[Size]='\0';
        
        SEGGER_SYSVIEW_RecordEnterISR();
        LOG_ARRAY(DMARecvBuffer4,Size,LOG_ARR_BYTE1,4);
        netRecvHandle(LINK_NET_UP,DMARecvBuffer4,Size);
        SEGGER_SYSVIEW_RecordExitISR();

        HAL_UARTEx_ReceiveToIdle_IT(&huart4,DMARecvBuffer4,UART_MAX_RECV_LENGTH);
    }
}

2.应用逻辑代码

void comUpTaskEntry(void *argument)
{
    mb_req_s modbus_req={0};
    MSGQUEUE_OBJ_t msg;
    uint8_t error_code=0;
    uint16_t modbus_reply_len=0;
    
    for(;;){
        // 获取上位机数据
        if(osMessageQueueGet(gParam.os_msg_com_up,&msg,NULL,1000)==osOK){
            error_code=mbReqParse(&modbus_req,msg.msg_buf,msg.msg_length);
            if(!error_code){
                // 生成Modbus响应
                error_code=mbReqReply(modbus_reply,&modbus_reply_len,
                                    &modbus_req,gParam.dat_ctl,&gParam.dat_sen[modbus_req.Id]);
                //LOG_ARRAY(modbus_reply,modbus_reply_len,LOG_ARR_BYTE1,4);
                // 发送Modbus响应
                if(!error_code){
                    netSend(LINK_NET_UP,modbus_reply,modbus_reply_len);
                }
            }else{
                LOG_ERROR(0,"modbus req parse failed:%d",error_code);
            }
        }
        uartComUpRecv();
    }
}

3.代码问题分析

HAL_UARTEx_ReceiveToIdle_IT API使用问题

ISR代码中不应该反复调用该API,正常逻辑应该在中断中发送消息队列后唤醒应用逻辑线程处理数据完成后再调用该API重新启动串口接收,如上ISR代码中需要移除该API。

应用代码中调用该API也过于频繁,ISR中调用后应用逻辑线程立即调用函数会概率返回HAL_BUSY问题,调整逻辑为成功接收数据后重启启动串口接收

HAL_UART_ErrorCallback 回调函数未实现

该函数用于捕获串口外设使用时的各种错误,其会导致数据接收异常,在该回调函数中处理错误并重新打开串口接收

二、修复后代码

1.ISR代码

static uint8_t DMARecvBuffer4[UART_MAX_RECV_LENGTH]={0};

void uartComUpRecv(void)
{
    HAL_StatusTypeDef ret=HAL_ERROR;
    ret=HAL_UARTEx_ReceiveToIdle_IT(&huart4,DMARecvBuffer4,UART_MAX_RECV_LENGTH);
    if(ret!=HAL_OK){
        LOG_ERROR(0,"Uart4 IT Enable Failed:%d",ret);
    }
}

// 删除HAL_UARTEx_ReceiveToIdle_IT API
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
    // 其他串口...

    // 上位机网口
    if (huart == &huart4){
        DMARecvBuffer4[Size]='\0';
        
        SEGGER_SYSVIEW_RecordEnterISR();
        LOG_ARRAY(DMARecvBuffer4,Size,LOG_ARR_BYTE1,4);
        netRecvHandle(LINK_NET_UP,DMARecvBuffer4,Size);
        SEGGER_SYSVIEW_RecordExitISR();
    }
}

// 添加串口错误回调函数实现
void HAL_UART_ErrorCallback( UART_HandleTypeDef *huart)
{
    HAL_StatusTypeDef ret=HAL_ERROR;
    huart->RxState = HAL_UART_STATE_READY;
    if (huart == &huart4){
        ret=HAL_UARTEx_ReceiveToIdle_IT(&huart4,DMARecvBuffer4,UART_MAX_RECV_LENGTH);
        if(ret!=HAL_OK){
            LOG_ERROR(0,"ErrorCB Uart4 IT Enable Failed:%d",ret);
        }
    }
}

2.应用逻辑代码

// 将打开串口接收函数放入获取上位机数据成功判断内
void comUpTaskEntry(void *argument)
{
    mb_req_s modbus_req={0};
    MSGQUEUE_OBJ_t msg;
    uint8_t error_code=0;
    uint16_t modbus_reply_len=0;
    
    for(;;){
        // 获取上位机数据
        if(osMessageQueueGet(gParam.os_msg_com_up,&msg,NULL,1000)==osOK){
            error_code=mbReqParse(&modbus_req,msg.msg_buf,msg.msg_length);
            if(!error_code){
                // 生成Modbus响应
                error_code=mbReqReply(modbus_reply,&modbus_reply_len,
                                    &modbus_req,gParam.dat_ctl,&gParam.dat_sen[modbus_req.Id]);
                //LOG_ARRAY(modbus_reply,modbus_reply_len,LOG_ARR_BYTE1,4);
                // 发送Modbus响应
                if(!error_code){
                    netSend(LINK_NET_UP,modbus_reply,modbus_reply_len);
                }
            }else{
                LOG_ERROR(0,"modbus req parse failed:%d",error_code);
            }
            uartComUpRecv();
        }
    }
}

该解决方案经过长时间连续通信验证暂未发现问题

文章作者:四文鱼Max

本文链接:https://blog.awolon.fun/archives/stm32-rtos-uart-idle-it-problem.html

许可协议:CC BY-SA 4.0

标签: freertos, stm32

添加新评论