一,因何用之?
之前曾经写过一篇《关于CubeMX的串口全双工接收发送锁死的问题》的文章,讨论了STM32的串口在全双工模式下会出现锁死问题的现象。当时的解决办法是在串口接收中断中加入解锁机制,貌似临时解决了这个问题。但这几天程序不知道怎么回事,又开始频繁地出现死机现象,而且仿真的时候会进入HardFault()。
二,缘何致之?
最让人头疼的问题就是大部分时间里没有问题。
———— mickey35
死机的现象总是在意想不到的情形下发生,可能刚下载完程序就出现了;或者正常跑了好几天才出现。所以,为了寻找死机的原因,采取了以下两种方式:
1,缩短任务执行的延时,加重MCU负担。此举可使死机的概率大幅提高。(仔细想来,之前貌似解决问题的时候,MCU的负担并不是很重,之后可能任务加多了,矛盾也就凸显出来了)
2,在网上找到的调试HardFault()问题的办法。除去检查数组越界和堆栈溢出之外,最简单的办法就是在HardFault()处添加断点,之后在监视窗查看调用代码就可以找到调用HardFault()的代码了。(经过博主实验,这个方法并非百试百灵,大部分情况下都跳到“MOVS r0,r0”去了,多试几次才可能找到罪魁祸首)
总之,经过一番调试之后,发现最可能的调用函数是HAL_UART_Receive_IT
个人猜测,因为串口数据来的非常快(115200),串口中断调用频繁,导致出现数据溢出,串口锁死,串口状态异常等等各种情况,最终进入硬件错误中断,然后导致死机。顺便一提,博主的板子上跑的是Freertos,其中一个任务负责LCD的显示,LCD的默认刷新率是50Hz,但有时候会出现明显的掉帧,甚至稳定只有十几Hz,这从侧面印证存在某个极其占用资源的任务,MCU负担过重。
三,如何为之?
单字节接收,中断保存数据,关键字节开始处理数据是从51时代就延续下来,专门处理串口接收数据的办法,但似乎在STM32上已经不好适应了。对于STM32这种提供了丰富外设和功能的芯片,串口接收也可以尝试一下一些新的方式,比如DMA+空闲中断接收。
仍然采用STM32CubeMX来创建工程,请忽略掉我的板子上那几个LED灯的配置。
在main.c里新建全局变量
uint8_t usart1_rx_flag = 0;
uint8_t usart1_rx_buffer[128];
uint8_t usart1_tx_buffer[128];
uint16_t usart1_tx_len = 0;
main函数中开启DMA接收和空闲中断:
void main(void)
{
...
/* USART1接收 */
if(HAL_UART_Receive_DMA(&huart1,(uint8_t *)&usart1_rx_buffer,128) != HAL_OK) Error_Handler();
/* 开启空闲接收中断 */
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);
...
}
在usart.c文件中添加处理空闲中断的回掉函数(2020.10.24修改此处代码):
/* 串口接收空闲中断 */
void UsartReceive_IDLE(UART_HandleTypeDef *huart)
{
uint16_t i = 0;
if((__HAL_UART_GET_FLAG(huart,UART_FLAG_IDLE) != RESET))
{
if(huart->Instance == USART1)
{
__HAL_UART_CLEAR_IDLEFLAG(huart);
i = huart->Instance->SR;
i = huart->Instance->DR;
i = hdma_usart1_rx.Instance->CNDTR;
/* 这里以前使用HAL_UART_DMAStop(huart),但这个函数会导致TX的DMA被关闭,小概率DMA发送会丢数据 */
/* 2020.10.24 修改为HAL_UART_AbortReceive(huart),对应DMA发送也需要做一些处理 */
HAL_UART_AbortReceive(huart);
/* 此处处理数据,主要是拷贝和置位标志位 */
if(usart1_rx_flag == 0)
{
memcpy(usart1_tx_buffer,usart1_rx_buffer,(128 - i));
usart1_rx_flag = 1;
}
/* 清空缓存,重新接收 */
memset(usart1_rx_buffer,0x00,128);
HAL_UART_Receive_DMA(huart,(uint8_t *)&usart1_rx_buffer,128);
}
}
}
在stm32f1xx_it.c中的中断入口点处添加空闲中断检查:
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
/* HAL库好像没有处理空闲中断的代码,需自己添加 */
if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE))
{
UsartReceive_IDLE(&huart1);
}
/* USER CODE END USART1_IRQn 0 */
HAL_UART_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */
/* USER CODE END USART1_IRQn 1 */
}
在主循环中加入检查标志位和发送数据的代码:
/* USART_Task function */
void USART_Task(void const * argument)
{
/* USER CODE BEGIN USART_Task */
/* Infinite loop */
for(;;)
{
if(usart1_rx_flag == 1)
{
dma_send(usart1_tx_buffer,usart1_tx_len);
usart1_rx_flag = 0;
}
osDelay(1);
}
/* USER CODE END USART_Task */
}
————————————————
版权声明:本文为CSDN博主「mickey35」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/mickey35/article/details/78521311
最终测试结果如下:
此处只是一个DEMO,单纯做了一个回环测试,但在实际项目使用中确实极大减轻了MCU的负担,目前暂时没有因为这种方式的串口接收导致的死机。但在接收一些超长的数据时,需要极大的缓存区。正在考虑是否可以同时开启串口接收中断和DMA接收中断。。。
老的DEMO链接
bug修复版DEMO下载地址
2020.10.24 PS:
评论区一位老哥发现了代码中存在的一处bug:
当DMA空闲中断触发时,会调用HAL_UART_DMAStop()。此时,如果DMA正在发生数据,会被强制关闭,导致发送数据出现缺失。
串口空闲中断回调函数的修改,见前文。
DMA发送函数中,根据huart句柄的gState,决定是否调用HAL_UART_AbortTransmit()。防止出现因状态未被复位,导致无法发送的情况,具体实现方式如下:
void dma_send(unsigned char *buffer,unsigned int length)
{
//等待上一次的数据发送完毕
while(HAL_DMA_GetState(&hdma_usart1_tx) == HAL_DMA_STATE_BUSY);
/* 2020.10.24 防止出现因状态未被复位,导致无法发送的情况(过去由HAL_UART_DMAStop()关闭,现在。。。) */
if(huart1.gState != HAL_UART_STATE_READY) HAL_UART_AbortTransmit(&huart1);
/* 关闭DMA */
__HAL_DMA_DISABLE(&hdma_usart1_tx);
//开始发送数据
HAL_UART_Transmit_DMA(&huart1,buffer,length);
}
一,因何用之?
之前曾经写过一篇《关于CubeMX的串口全双工接收发送锁死的问题》的文章,讨论了STM32的串口在全双工模式下会出现锁死问题的现象。当时的解决办法是在串口接收中断中加入解锁机制,貌似临时解决了这个问题。但这几天程序不知道怎么回事,又开始频繁地出现死机现象,而且仿真的时候会进入HardFault()。
二,缘何致之?
最让人头疼的问题就是大部分时间里没有问题。
———— mickey35
死机的现象总是在意想不到的情形下发生,可能刚下载完程序就出现了;或者正常跑了好几天才出现。所以,为了寻找死机的原因,采取了以下两种方式:
1,缩短任务执行的延时,加重MCU负担。此举可使死机的概率大幅提高。(仔细想来,之前貌似解决问题的时候,MCU的负担并不是很重,之后可能任务加多了,矛盾也就凸显出来了)
2,在网上找到的调试HardFault()问题的办法。除去检查数组越界和堆栈溢出之外,最简单的办法就是在HardFault()处添加断点,之后在监视窗查看调用代码就可以找到调用HardFault()的代码了。(经过博主实验,这个方法并非百试百灵,大部分情况下都跳到“MOVS r0,r0”去了,多试几次才可能找到罪魁祸首)
总之,经过一番调试之后,发现最可能的调用函数是HAL_UART_Receive_IT
个人猜测,因为串口数据来的非常快(115200),串口中断调用频繁,导致出现数据溢出,串口锁死,串口状态异常等等各种情况,最终进入硬件错误中断,然后导致死机。顺便一提,博主的板子上跑的是Freertos,其中一个任务负责LCD的显示,LCD的默认刷新率是50Hz,但有时候会出现明显的掉帧,甚至稳定只有十几Hz,这从侧面印证存在某个极其占用资源的任务,MCU负担过重。
三,如何为之?
单字节接收,中断保存数据,关键字节开始处理数据是从51时代就延续下来,专门处理串口接收数据的办法,但似乎在STM32上已经不好适应了。对于STM32这种提供了丰富外设和功能的芯片,串口接收也可以尝试一下一些新的方式,比如DMA+空闲中断接收。
仍然采用STM32CubeMX来创建工程,请忽略掉我的板子上那几个LED灯的配置。
在main.c里新建全局变量
uint8_t usart1_rx_flag = 0;
uint8_t usart1_rx_buffer[128];
uint8_t usart1_tx_buffer[128];
uint16_t usart1_tx_len = 0;
main函数中开启DMA接收和空闲中断:
void main(void)
{
...
/* USART1接收 */
if(HAL_UART_Receive_DMA(&huart1,(uint8_t *)&usart1_rx_buffer,128) != HAL_OK) Error_Handler();
/* 开启空闲接收中断 */
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);
...
}
在usart.c文件中添加处理空闲中断的回掉函数(2020.10.24修改此处代码):
/* 串口接收空闲中断 */
void UsartReceive_IDLE(UART_HandleTypeDef *huart)
{
uint16_t i = 0;
if((__HAL_UART_GET_FLAG(huart,UART_FLAG_IDLE) != RESET))
{
if(huart->Instance == USART1)
{
__HAL_UART_CLEAR_IDLEFLAG(huart);
i = huart->Instance->SR;
i = huart->Instance->DR;
i = hdma_usart1_rx.Instance->CNDTR;
/* 这里以前使用HAL_UART_DMAStop(huart),但这个函数会导致TX的DMA被关闭,小概率DMA发送会丢数据 */
/* 2020.10.24 修改为HAL_UART_AbortReceive(huart),对应DMA发送也需要做一些处理 */
HAL_UART_AbortReceive(huart);
/* 此处处理数据,主要是拷贝和置位标志位 */
if(usart1_rx_flag == 0)
{
memcpy(usart1_tx_buffer,usart1_rx_buffer,(128 - i));
usart1_rx_flag = 1;
}
/* 清空缓存,重新接收 */
memset(usart1_rx_buffer,0x00,128);
HAL_UART_Receive_DMA(huart,(uint8_t *)&usart1_rx_buffer,128);
}
}
}
在stm32f1xx_it.c中的中断入口点处添加空闲中断检查:
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
/* HAL库好像没有处理空闲中断的代码,需自己添加 */
if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE))
{
UsartReceive_IDLE(&huart1);
}
/* USER CODE END USART1_IRQn 0 */
HAL_UART_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */
/* USER CODE END USART1_IRQn 1 */
}
在主循环中加入检查标志位和发送数据的代码:
/* USART_Task function */
void USART_Task(void const * argument)
{
/* USER CODE BEGIN USART_Task */
/* Infinite loop */
for(;;)
{
if(usart1_rx_flag == 1)
{
dma_send(usart1_tx_buffer,usart1_tx_len);
usart1_rx_flag = 0;
}
osDelay(1);
}
/* USER CODE END USART_Task */
}
————————————————
版权声明:本文为CSDN博主「mickey35」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/mickey35/article/details/78521311
最终测试结果如下:
此处只是一个DEMO,单纯做了一个回环测试,但在实际项目使用中确实极大减轻了MCU的负担,目前暂时没有因为这种方式的串口接收导致的死机。但在接收一些超长的数据时,需要极大的缓存区。正在考虑是否可以同时开启串口接收中断和DMA接收中断。。。
老的DEMO链接
bug修复版DEMO下载地址
2020.10.24 PS:
评论区一位老哥发现了代码中存在的一处bug:
当DMA空闲中断触发时,会调用HAL_UART_DMAStop()。此时,如果DMA正在发生数据,会被强制关闭,导致发送数据出现缺失。
串口空闲中断回调函数的修改,见前文。
DMA发送函数中,根据huart句柄的gState,决定是否调用HAL_UART_AbortTransmit()。防止出现因状态未被复位,导致无法发送的情况,具体实现方式如下:
void dma_send(unsigned char *buffer,unsigned int length)
{
//等待上一次的数据发送完毕
while(HAL_DMA_GetState(&hdma_usart1_tx) == HAL_DMA_STATE_BUSY);
/* 2020.10.24 防止出现因状态未被复位,导致无法发送的情况(过去由HAL_UART_DMAStop()关闭,现在。。。) */
if(huart1.gState != HAL_UART_STATE_READY) HAL_UART_AbortTransmit(&huart1);
/* 关闭DMA */
__HAL_DMA_DISABLE(&hdma_usart1_tx);
//开始发送数据
HAL_UART_Transmit_DMA(&huart1,buffer,length);
}
举报