你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

HAL库串口锁死,无法接收,搞了几天没头绪

[复制链接]
lockyer 提问时间:2019-5-3 10:09 /
本帖最后由 wuxiaoluo 于 2019-5-3 10:19 编辑

用cubeMX生成工程,设置串口为中断接收。 在主程序中开启中断接收,1个字节
主程序while中printf打印变量
  MX_USART1_UART_Init();
__HAL_UART_ENABLE_IT(&huart1, UART_IT_ERR);
HAL_UART_Receive_IT(&huart1,(uint8_t*) &Uart1_Rx_Byte,1); //接收1个字符
while (1)
  {printf("\r\n-----RcvCount=%8d  , ErrCount=%8d  RcErrCount=%8d\r\n",RcvCount,ErrCount,RcErrCount);
HAL_Delay(100);
}
中断接收什么都不处理
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{        RcvCount++;
RcvErr = HAL_UART_Receive_IT(&huart1,(uint8_t*) &Uart1_Rx_Byte,1); //再次接收1个字符
}
void  HAL_UART_ErrorCallback( UART_HandleTypeDef *huart) {
                ErrCount++;
               HAL_UART_Receive_IT(&huart1,(uint8_t*) &Uart1_Rx_Byte,1);        //恢复接收
}
上位机快速发送字符很快就会没接收了(几十个到几百个字符,随机不等)。无法再次进入接收中断。(RcvCount不会增加 , 返回的 RcvErr=HAL_BUSY  (2),就是再次HAL_UART_Receive_IT失败了)有时会while无法发送(pc没收到消息)



收藏 评论12 发布时间:2019-5-3 10:09

举报

12个回答
edmundlee 回答时间:2019-5-5 16:00:23
楼主你是对任务没有一丁点的时间慨念,
接收一个字节, 然后通过打印了N个字节, 可有考虑打印一个字节的需时? 应该是跟接收一个字节的需要是一样吧? 对不?
比如你每86us接收一字节, 然后用了N*86us打印, 然后再回来接收另一个字节
你觉得能收到第二个字节么?

评分

参与人数 1蝴蝶豆 +3 收起 理由
STMCU + 3

查看全部评分

废鱼 回答时间:2019-5-5 17:13:31
如果是中断接收应该不会有问题,我在使用时,在线仿真时,断点会导致数据接收溢出,不能接收数据。需要重新初始化串口解决。

评分

参与人数 1蝴蝶豆 +2 收起 理由
STMCU + 2

查看全部评分

lvfeng123 回答时间:2019-5-6 08:46:08
本帖最后由 lvfeng123 于 2019-5-6 08:54 编辑

这样没有多大问题。 但是你err中断处理有问题,
err中断你恢复接收,err中断都没清理, 肯定恢复不了啊!进入err中断,计数完要清理掉中断才能继续的。
(recieve中断,你用的那个接收函数帮你做完清理了)
最起码也要最后把错误状态置为 ready先啊。 recieve_it 不会帮你做这个的。

评分

参与人数 1蝴蝶豆 +2 收起 理由
STMCU + 2

查看全部评分

lockyer 回答时间:2019-5-6 09:11:24
edmundlee 发表于 2019-5-5 16:00
楼主你是对任务没有一丁点的时间慨念,
接收一个字节, 然后通过打印了N个字节, 可有考虑打印一个字节的 ...

所以才要用中断接收呀,无论你输出多少,接收来了中断就把字节放到缓存区数组中,这样不会影响输出,也不会影响输入。因为48M以上处理一下接收字节转存到自己写的环形存储buffer需要时间很短,而115200一个字节需要时间大概是 80us,48M主频每条指令是0.02us,中断函数大概不到0.5us,转存接收到的字节数据怎么都不会超过5us,一般在1us以下。所以收发可以同时不受影响的。主线程可以做接收数据分析,接收中断保证数据不丢。中断就是应付这些多任务和等待事件而不影响普通任务用的。
lockyer 回答时间:2019-5-6 09:12:35
本帖最后由 wuxiaoluo 于 2019-5-6 09:37 编辑
lvfeng123 发表于 2019-5-6 08:46
这样没有多大问题。 但是你err中断处理有问题,
err中断你恢复接收,err中断都没清理, 肯定恢复不了啊!进 ...

后来发现,根本不会err,只是忙,被发送锁定了,发送执行时遇到接收中断。因为它是HAL库发送时自己标志锁,导致接收函数判断为忙而返回,从而无法开启接收。如果根据忙来做一个while,是可以恢复一部分,但没过多久就收发都不行了。发送也卡死了。HAL的互斥锁,是为了多线程保护usart结构数据的。一般小单片机小任务用不上,反而会出现死锁几率。得不偿失。
lockyer 回答时间:2019-5-6 09:21:19
发现官方的要照顾很多用不到的功能判断,效率极低。耗时太长,函数调用太多,接收时发回去都吃力。还是花了几个小时重新自己用寄存器接替了官方HAL的收发库了。终于了解为什么微软系统那么庞大了,大公司的通病,都是按代码量来考核和发工资的。。。能把一行的代码写成10行。

解决办法:
最简单把printf的函数的putc改为寄存器发送,这样HAL就只是接收,不会锁死了。
int fputc(int ch,FILE *f)
{
        while( __HAL_UART_GET_FLAG( &huart1 , UART_FLAG_TC ) == RESET ); //等待串口1能发送
   huart1.Instance->TDR = ch & (uint8_t)0xFFU;  //串口1发送数据
   return ch;
}

要把所有的HAL_UART_Transmit都换了

如果需要效率,就把中断接收也换了:

1)        在main中初始化结构接收缓充,代替HAL_UART_Receive_IT启动中断接收。:
                volatile  uint8_t RxByte;   //存放接收数据的全局字节变量。volatile保证全局不会被优化掉。
                huart1.pRxBuffPtr = &RxByte;  //把变量地址赋值给结构,好让在中断中赋值
                huart1.RxXferSize = 1;  huart1.RxXferCount = 0;        //接收长度为1个字节。已接收字节数为0
                SET_BIT(huart1.Instance->CR1, USART_CR1_RXNEIE);//接收使能
                //SET_BIT(huart1.Instance->CR1, USART_CR1_PEIE);//接收时发生错误产生中断使能。一般不开启

2)        在stm32f0xx_it.c 的中断函数 void USART1_IRQHandler(void) 中处理接收
void USART1_IRQHandler(void)
{
         /* USER CODE BEGIN USART1_IRQn 0 */
          uint32_t isrflags   = READ_REG(huart1.Instance->ISR);  //各种状态标志寄存器
          uint32_t cr1its     = READ_REG(huart1.Instance->CR1); //配置寄存器
          if(((isrflags & USART_ISR_RXNE) != RESET) &&\
                                 ((cr1its & USART_CR1_RXNEIE) != RESET))//接收到字符,且接收中断开启
             {
                        // __HAL_UART_CLEAR_IT(&huart1, USART_ISR_RXNE);//读了就不需要手动清除了
                     huart1.pRxBuffPtr[0] = READ_REG(huart1.Instance->RDR);//字符放到
                        HAL_UART_RxCpltCallback(&huart1);                //接收回调处理函数
                      return;//接收处理完就返回
     }               
        //只处理溢出错误,其他错误不处理,不开启其他错误中断
         if(((isrflags & USART_ISR_ORE) != RESET) && \
                                 ((cr1its & USART_CR1_RXNEIE) != RESET) )
    {
      __HAL_UART_CLEAR_IT(&huart1, UART_CLEAR_OREF);  //手动清除溢出标志
      huart1.ErrorCode |= HAL_UART_ERROR_ORE;                        //记录错误号
    }
        HAL_UART_ErrorCallback( &huart1 );                                         //错误回调处理函数
        return; //一定要返回,这样不执行原来的处理
  /* USER CODE END USART1_IRQn 0 */
  //HAL_UART_IRQHandler(&huart1);  //上面返回了,这里原来的HAL处理就都不执行了
  /* USER CODE BEGIN USART1_IRQn 1 */
  /* USER CODE END USART1_IRQn 1 */
}

这样就等于接收一个字符的中断调用了。如果需要错误判断,则打开错误中断。
在main中添加串口错误处理函数
SET_BIT(huart->Instance->CR3, USART_CR3_EIE);//溢出错误,帧错误,噪声错误中断使能
SET_BIT(huart1.Instance->CR1, USART_CR1_PEIE);//校验错误中断使能

添加错误处理
/* USER CODE BEGIN 4 */
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
{
        L2++; //记录错误次数的全局变量值
        pRx=0;
        uint32_t isrflags   = READ_REG(huart->Instance->ISR);//手册上有讲,清错误都要先读SR
        if((__HAL_UART_GET_FLAG(huart, UART_FLAG_PE))!=RESET)
        {
                READ_REG(huart->Instance->RDR);//PE 校验错
                __HAL_UART_CLEAR_FLAG(huart, UART_FLAG_PE);
                                huart->ErrorCode  = HAL_UART_ERROR_PE;
        }
        if((__HAL_UART_GET_FLAG(huart, UART_FLAG_FE))!=RESET)
        {
                READ_REG(huart->Instance->RDR);//FE 帧错误
                __HAL_UART_CLEAR_FLAG(huart, UART_FLAG_FE);
                                huart->ErrorCode  = HAL_UART_ERROR_FE;
        }
        
        if((__HAL_UART_GET_FLAG(huart, UART_FLAG_NE))!=RESET)
        {
                READ_REG(huart->Instance->RDR);//NE 噪音错误
                __HAL_UART_CLEAR_FLAG(huart, UART_FLAG_NE);
                                huart->ErrorCode  =  HAL_UART_ERROR_NE;
        }        
        
        if((__HAL_UART_GET_FLAG(huart, UART_FLAG_ORE))!=RESET)
        {
                READ_REG(huart->Instance->CR1);//ORE 溢出
                __HAL_UART_CLEAR_FLAG(huart, UART_FLAG_ORE);
                                huart->ErrorCode  = HAL_UART_ERROR_ORE;
        }        
                LastErr = huart->ErrorCode; //记录错误代号
}

发送可以用两个函数代替

添加两个发送函数代替 HAL_Transmit
void  Transmit_Data( UART_HandleTypeDef *huart , uint8_t* data , uint32_t len )
{
        uint32_t i;
        for(i=0;i<len;i++)
        {
                while( __HAL_UART_GET_FLAG( huart, UART_FLAG_TC ) == RESET ); //ISR, bit 6 (0x40)
                huart->Instance->TDR = data[i];
        }
}
void  Transmit_Str( UART_HandleTypeDef *huart , uint8_t* data )
{
        uint8_t *ch=data;
        while( (*ch) != 0 )
        {
                while( __HAL_UART_GET_FLAG( huart, UART_FLAG_TC ) == RESET ); //ISR, bit 6 (0x40)
                huart->Instance->TDR = *ch;
                ch++;
        }
}





lockyer 回答时间:2019-5-6 09:25:01
安 发表于 2019-5-5 17:13
如果是中断接收应该不会有问题,我在使用时,在线仿真时,断点会导致数据接收溢出,不能接收数据。需要重新 ...

你可以CUBEMX简单建一个 工程,开一个串口中断接收 HAL_UART_Receive_IT ,里面啥也不要干,就一个全局变量 RecvCount++。
然后main的while循环里面,每隔300ms打印一个RecvCount++消息到串口。然后电脑100ms连续发送。很快就无法中断接收了。
lockyer 回答时间:2019-5-6 09:28:30
本帖最后由 wuxiaoluo 于 2019-5-6 09:44 编辑

还是要用寄存器处理才行,HAL库看来只是用来配置设置方便,真正干活部分还得用寄存器或者自己用底层的HAL函数重写了。HAL库特别是低功耗降低主频低速度时,效率下降带来的处理不过来数据问题更明显。
lockyer 回答时间:2019-5-6 09:32:54
一直向同事力推CUBEMX,说简单方便,做些小程序简单测测都没啥问题。只是效率问题。这下第一个项目用它,结果一测试都是坑。被同事笑死了。不过又舍不得cube的可视化配置,没有库配置那么容易出错漏。最后还是决定保留CUBE的HAL库,但中断处理部分都删掉HAL的库,抽取所需的关键寄存器部分,全部自己寄存器写了。其实也很快,而且高效放心。效率能够提高5-10倍。
lockyer 回答时间:2019-5-6 09:46:54
安 发表于 2019-5-5 17:13
如果是中断接收应该不会有问题,我在使用时,在线仿真时,断点会导致数据接收溢出,不能接收数据。需要重新 ...

HAL库的自锁问题,换了寄存器改了就没事了。

所属标签

相似问题

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版