【BUG处理】STM32F1和F2单片机上用HAL库的USART串口接收函数HAL_UART_Receive_IT循环接收串口字符,串口接收大批量数据后突然死机,不能继续接收的解决办法
发布日期:2021-06-29 10:16:58 浏览次数:3 分类:技术文章

本文共 9552 字,大约阅读时间需要 31 分钟。

其实说到底,就是Overrun(USART_SR_ORE)在作怪。

【问题描述】

程序采用FreeRTOS操作系统,主函数里面调用HAL_UART_Receive_IT接收串口字符,在中断回调函数HAL_UART_RxCpltCallback里面处理串口字符,然后再次调用HAL_UART_Receive_IT继续接收下一个串口字符。用串口调试助手长时间发送大量数据后,这个循环会突然中断,后面再也不能接收到新的字符。

【原因分析】

(一)串口发送函数HAL_UART_Transmit和串口接收函数HAL_UART_Receive_IT不能并发调用,必须用互斥量保护。这是因为,这两个函数内部都有__HAL_LOCK(huart),如果一个线程正在执行HAL_UART_Transmit函数,那么同一时刻另一个线程执行HAL_UART_Receive_IT函数执行__HAL_LOCK会失败,函数返回HAL_BUSY,不能启动数据接收。

#define __HAL_LOCK(__HANDLE__)                                           \                                do{                                        \                                    if((__HANDLE__)->Lock == HAL_LOCKED)   \                                    {                                      \                                       return HAL_BUSY;                    \                                    }                                      \                                    else                                   \                                    {                                      \                                       (__HANDLE__)->Lock = HAL_LOCKED;    \                                    }                                      \                                  }while (0U)

(二)串口接收函数HAL_UART_Receive_IT调用后,会有三种结果:

第一种:只调用HAL_UART_RxCpltCallback回调函数

第二种:HAL_UART_RxCpltCallback和HAL_UART_ErrorCallback回调函数都调用
第三种:只调用HAL_UART_ErrorCallback回调函数

长时间(半小时以上)发送大量串口数据时,单片机会因为来不及处理串口数据出现Overrun的错误,此时USART_SR_ORE位为1。

到了一定时候,当USART_SR寄存器的USART_SR_RXNE为0,且USART_SR_ORE位为1时,就会出现第三种情况,只有HAL_UART_ErrorCallback这个函数会被调用,而且USART_SR_ORE位始终不能自动清除,串口就不能继续接收数据,USART_SR_RXNE位一直为0,这样就产生了死循环。
函数调用HAL_UART_Receive_IT开始接收数据,而因为ORE=1,马上产生串口中断,进入HAL_UART_ErrorCallback,然后接收自动中止(注意看UART_EndRxTransfer函数,里面只是简单地中止接收)。RXNE位一直为0,永远进不了HAL_UART_RxCpltCallback。而HAL库函数又没有清除ORE位的功能,所以即便再次调用HAL_UART_Receive_IT也是同样的结果。

用串口调试助手每毫秒发一次数据,长时间不停地发

如图所示,这是在产生死循环后Keil在中断处理函数中打断点调试得到的结果,isrflags是USART1->SR寄存器的值。寄存器的值为0xd8,说明RXNE=0但ORE=1。

手册里面可以看到,ORE位是只读位,只能用特殊的方法清除:

Bit 3 ORE: Overrun error

This bit is set by hardware when the word currently being received in the shift register is
ready to be transferred into the RDR register while RXNE=1. An interrupt is generated if
RXNEIE=1 in the USART_CR1 register. It is cleared by a software sequence (an read to the
USART_SR register followed by a read to the USART_DR register).
0: No Overrun error
1: Overrun error is detected
Note: When this bit is set, the RDR register content will not be lost but the shift register will be
overwritten. An interrupt is generated on ORE flag in case of Multi Buffer
communication if the EIE bit is set.

清除ORE位的方法是,先读USART_SR寄存器,再读USART_DR寄存器。只要清除了ORE位,就可以打破这种死循环的状态。

HAL库里面有一个__HAL_UART_FLUSH_DRREGISTER宏可以用来读DR寄存器:
#define __HAL_UART_FLUSH_DRREGISTER(__HANDLE__) ((__HANDLE__)->Instance->DR)
所以,我们只要在ORE错误产生时,读一下DR寄存器,就可以解决这个bug,退出这种死循环。

void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart){  if (HAL_UART_GetError(huart) & HAL_UART_ERROR_ORE)    __HAL_UART_FLUSH_DRREGISTER(huart);}

另外,调试的时候要特别注意,不要把Keil的USART寄存器窗口打开了。因为Keil在读USART1->DR寄存器的时候,会导致USART_SR_RXNE位被清除,程序就可能收不到串口数据。

【参考程序】

main.c:

#include 
#include
#include
#include
#include
#include "common.h"extern UART_HandleTypeDef huart1;RTC_HandleTypeDef hrtc;static SemaphoreHandle_t uart_sem;static void main_task(void *arg){ RTC_DateTypeDef date; RTC_TimeTypeDef time; hrtc.Instance = RTC; while (1) { HAL_RTC_GetTime(&hrtc, &time, RTC_FORMAT_BCD); HAL_RTC_GetDate(&hrtc, &date, RTC_FORMAT_BCD); printf_s("ticks=%u, time=20%02x-%x-%x %02x:%02x:%02x\n", HAL_GetTick(), date.Year, date.Month, date.Date, time.Hours, time.Minutes, time.Seconds); printf_s("rxstate=%#x, RXNEIE=%d\n", huart1.RxState, __HAL_UART_GET_IT_SOURCE(&huart1, UART_IT_RXNE) != RESET); vTaskDelay(pdMS_TO_TICKS(10)); }}static void uart_task(void *arg){ uint8_t data; BaseType_t bret; HAL_StatusTypeDef status; uart_sem = xSemaphoreCreateBinary(); HAL_NVIC_SetPriority(USART1_IRQn, 1, 0); HAL_NVIC_EnableIRQ(USART1_IRQn); HAL_UART_Receive_IT(&huart1, &data, 1); while (1) { bret = xSemaphoreTake(uart_sem, 100); if (bret == pdTRUE) { if (data == 'a') printf_s("Received: %c!\n", data); } if (huart1.RxState == HAL_UART_STATE_READY) { printf_lock(); status = HAL_UART_Receive_IT(&huart1, &data, 1); configASSERT(status == HAL_OK); printf_unlock(); } }}int main(void){ HAL_Init(); clock_init(); usart_init(115200); printf("STM32F217VE USART1\n"); printf("SystemCoreClock=%u\n", SystemCoreClock); xTaskCreate_s(main_task, NULL, 2 * configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY, NULL); xTaskCreate_s(uart_task, NULL, 2 * configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY, NULL); vTaskStartScheduler(); return 0;}void USART1_IRQHandler(void){ HAL_UART_IRQHandler(&huart1);}void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart){ if (HAL_UART_GetError(huart) & HAL_UART_ERROR_ORE) __HAL_UART_FLUSH_DRREGISTER(huart);}void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){ BaseType_t woken = pdFALSE; if (huart == &huart1) xSemaphoreGiveFromISR(uart_sem, &woken); portYIELD_FROM_ISR(woken);}

common.c(用于printf绑定串口,且不用勾选Use MicroLIB):

#include 
#include
#include
#include
#include
#include
#include "common.h"#pragma import(__use_no_semihosting) // 禁用半主机模式 (不然调用printf就会进HardFault)typedef struct{ TaskFunction_t func; void *arg;} SafeTask;FILE __stdout = {1};FILE __stderr = {2};UART_HandleTypeDef huart1;static SemaphoreHandle_t printf_mutex;/* main函数返回时执行的函数 */void _sys_exit(int returncode){ taskDISABLE_INTERRUPTS(); printf("Exited! returncode=%d\n", returncode); while (1);}void _ttywrch(int ch){ if (ch == '\n') HAL_UART_Transmit(&huart1, (uint8_t *)"\r\n", 2, HAL_MAX_DELAY); else HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY);}// HAL库参数错误警告#ifdef USE_FULL_ASSERTvoid assert_failed(uint8_t *file, uint32_t line){ taskDISABLE_INTERRUPTS(); printf("%s: file %s on line %d\n", __FUNCTION__, file, line); while (1);}#endif// 配置系统和总线时钟void clock_init(void){ HAL_StatusTypeDef status; RCC_ClkInitTypeDef clk = {0}; RCC_OscInitTypeDef osc = {0}; osc.OscillatorType = RCC_OSCILLATORTYPE_HSE; osc.HSEState = RCC_HSE_ON; osc.PLL.PLLM = 25; osc.PLL.PLLN = 240; osc.PLL.PLLP = RCC_PLLP_DIV2; osc.PLL.PLLQ = 5; osc.PLL.PLLSource = RCC_PLLSOURCE_HSE; osc.PLL.PLLState = RCC_PLL_ON; status = HAL_RCC_OscConfig(&osc); assert_param(status == HAL_OK); clk.ClockType = RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2; clk.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; clk.AHBCLKDivider = RCC_SYSCLK_DIV1; clk.APB1CLKDivider = RCC_HCLK_DIV4; clk.APB2CLKDivider = RCC_HCLK_DIV2; HAL_RCC_ClockConfig(&clk, FLASH_LATENCY_3);}void dump_data(const void *data, int len){ const uint8_t *p = data; printf_lock(); while (len--) printf("%02X", *p++); printf("\n"); printf_unlock();}int fflush(FILE *stream){ if (stream->handle == 1 || stream->handle == 2) while (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_TC) == RESET); return 0;}// printf和perror重定向到串口int fputc(int ch, FILE *fp){ if (fp->handle == 1 || fp->handle == 2) { _ttywrch(ch); return ch; } return EOF;}void printf_lock(void){ xSemaphoreTakeRecursive(printf_mutex, portMAX_DELAY);}int printf_s(const char *__restrict format, ...){ int ret; va_list args; printf_lock(); __va_start(args, format); ret = vprintf(format, args); __va_end(args); printf_unlock(); return ret;}void printf_unlock(void){ xSemaphoreGiveRecursive(printf_mutex);}uint32_t sys_now(void){ return HAL_GetTick();}void usart_init(int baud_rate){ GPIO_InitTypeDef gpio = {0}; printf_mutex = xSemaphoreCreateRecursiveMutex(); __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_USART1_CLK_ENABLE(); gpio.Alternate = GPIO_AF7_USART1; gpio.Mode = GPIO_MODE_AF_PP; gpio.Pin = GPIO_PIN_9 | GPIO_PIN_10; gpio.Pull = GPIO_NOPULL; gpio.Speed = GPIO_SPEED_FREQ_VERY_HIGH; HAL_GPIO_Init(GPIOA, &gpio); huart1.Instance = USART1; huart1.Init.BaudRate = baud_rate; huart1.Init.Mode = UART_MODE_TX_RX; HAL_UART_Init(&huart1);}void vApplicationStackOverflowHook(TaskHandle_t xTask, char *pcTaskName){ taskDISABLE_INTERRUPTS(); printf("Stask overflow!\n"); while (1);}static void __xTaskCreate_s(void *arg){ SafeTask task = *(SafeTask *)arg; vPortFree(arg); task.func(task.arg); vTaskDelete(NULL);}/* 创建可以使用return语句的任务 */BaseType_t xTaskCreate_s(TaskFunction_t pxTaskCode, const char *pcName, configSTACK_DEPTH_TYPE usStackDepth, void *pvParameters, UBaseType_t uxPriority, TaskHandle_t *pxCreatedTask){ BaseType_t ret; SafeTask *p; p = pvPortMalloc(sizeof(SafeTask)); if (p == NULL) return pdFALSE; p->func = pxTaskCode; p->arg = pvParameters; ret = xTaskCreate(__xTaskCreate_s, pcName, usStackDepth, p, uxPriority, pxCreatedTask); if (ret == pdFALSE) vPortFree(p); return ret;}void HardFault_Handler(void){ taskDISABLE_INTERRUPTS(); printf("Hard Error!\n"); while (1);}

程序的里面有两个任务,一个任务不停地在用printf打印,往串口输出数据,另一个任务专门负责接收串口字符。发送和接收函数用printf_mutex这个互斥量来保护。接收串口字符时等待xSemaphoreTake信号量,若xSemaphoreTake信号量有信号,说明串口收到了字符。然后判断接收是否已停止,若huart1.RxState == HAL_UART_STATE_READY,说明接收已停止,调用HAL_UART_Receive_IT函数重新开始接收。

在HAL_UART_RxCpltCallback回调函数中使能信号量唤醒接收线程。在HAL_UART_ErrorCallback函数中处理Overrun错误(ORE),及时读取DR寄存器清除ORE位,防止陷入死循环。

特别注意,while(1)里面的if (huart1.RxState == HAL_UART_STATE_READY)不能放到if (bret == pdTRUE)内,因为调用HAL_UART_Receive_IT后有可能只有HAL_UART_ErrorCallback回调函数被调用,这个时候是没有唤醒信号量的,线程就不能够进入if (bret == pdTRUE)里面,调用HAL_UART_Receive_IT恢复数据接收。

转载地址:https://blog.csdn.net/ZLK1214/article/details/105624510 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:在PCB板上调试104(0.1μF)独石电容驱动MAXIM MAX3232串口芯片的心得
下一篇:【程序】STM32F107VC单片机驱动DP83848以太网PHY芯片,移植lwip 2.1.2协议栈,并加入网线热插拔检测的功能(HAL库)

发表评论

最新留言

路过按个爪印,很不错,赞一个!
[***.219.124.196]2024年04月08日 10时26分54秒