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

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管理
行使您的权利
关注我们
st-img 微信公众号
st-img 手机版