【BUG处理】STM32F1和F2单片机上用HAL库的USART串口接收函数HAL_UART_Receive_IT循环接收串口字符,串口接收大批量数据后突然死机,不能继续接收的解决办法

其实说到底,就是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 <FreeRTOS.h>
#include <semphr.h>
#include <stdio.h>
#include <stm32f2xx.h>
#include <task.h>
#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 <FreeRTOS.h>
#include <semphr.h>
#include <stdarg.h>
#include <stdio.h>
#include <stm32f2xx.h>
#include <task.h>
#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_ASSERT
void 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恢复数据接收。

  • 12
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
### 回答1: MCGS串口收发数据实例可以通过以下步骤来实现。 首先,在MCGS软件中创建一个串口通信对象,并设置串口的相关参数,例如波特率、停止位、数据位等。 接下来,在主程序中通过调用串口对象的打开函数,打开串口进行通信。 然后,可以使用串口对象的发送函数数据发送给外部设备。例如,可以通过串口发送一个字节的数据。 发送数据实例代码如下: ``` char sendData = 0x55; // 要发送的数据 serialPort.write(&sendData, 1); // 发送数据 ``` 在发送数据后,需要根据需求设置适当的延时,以确保数据发送完成。 接下来,可以通过串口对象的接收函数接收外部设备发送的数据。例如,可以接收外部设备发送的一个字节的数据接收数据实例代码如下: ``` char recvData; // 接收数据的变量 serialPort.read(&recvData, 1); // 接收数据 ``` 接收数据后,可以根据需求对接收到的数据进行处理,例如判断数据的值,并根据结果进行相应的操作。 最后,在程序结束前,需要调用串口对象的关闭函数,关闭串口通信。 以上就是MCGS串口收发数据的一个简单实例。通过以上步骤,可以实现MCGS软件与外部设备之间的串口通信。 ### 回答2: MCGS(绿色软件)是一种人机界面管理软件,用于控制工业自动化设备。MCGS支持串口通讯功能,可实现与外部设备的数据收发。 MCGS串口收发数据的实例可以分为两方面的操作:发送数据接收数据。 首先,使用MCGS发送数据需要进行以下步骤:首先在MCGS中配置好串口的通讯参数,如波特率、数据位、校验位等。然后,可以在MCGS中添加一个按钮或者触摸板,通过点击按钮或者触摸板事件触发发送数据。通过写入相应的脚本代码,将需要发送的数据写入到串口中。可以选择发送的数据格式,如ASCII码、十六进制等。最后,点击按钮或触摸板后,MCGS会向串口发送相应的数据。 其次,使用MCGS接收数据也需要进行以下操作:同样需要在MCGS中配置好串口的通讯参数。然后,通过写入相应的脚本代码,在MCGS中监听串口接收到的数据。可以选择监听的数据格式,如ASCII码、十六进制等。一旦MCGS接收数据,可以通过写入的脚本代码进行处理,如显示接收到的数据、保存到文件中等。 需要注意的是,在进行串口通讯前,需要保证串口连接正确,串口线正常连接到目标设备,并且设备之间的通讯协议也需要一致。 总结起来,MCGS串口收发数据实例的实现步骤包括配置串口通讯参数、发送数据接收数据。通过在MCGS中编写相应的脚本代码,可以实现与外部设备的数据交互。这种串口通讯功能在工业自动化控制中具有重要的应用价值。 ### 回答3: mcgs串口收发数据实例需要通过MC GS软件来实现。串口是一种常见的数据传输接口,我们可以通过串口来实现设备之间的数据收发。 首先,我们需要在MC GS软件中创建一个通信对象并设置串口通信的相关参数,例如串口号、波特率、数据位、停止位等。然后,我们可以利用MC GS软件提供的函数或指令来进行数据的收发操作。 下面是一个简单的mcgs串口收发数据实例: 1. 首先,在MC GS软件的通信对象配置中设置串口号为COM1,波特率为9600,数据位为8位,停止位为1位。 2. 在HMI界面中添加一个文本框用于显示收到的数据。 3. 在HMI界面中添加一个按钮,用于发送数据。 4. 在按钮的触发事件中编写代码,通过串口发送数据。 例如,我们可以编写一个发送指定数据函数sendData,并在按钮的触发事件中调用该函数实现数据的发送: ``` Function sendData(data As String) SerialPort1.Write(data) End Function Sub Button1_Click(Sender) Dim sendStr As String sendStr = "Hello World" sendData(sendStr) End Sub ``` 在上述代码中,sendStr表示要发送的数据字符串,通过调用sendData函数数据发送到串口。 当串口接收数据时,我们可以通过MC GS软件提供的事件或函数来实现数据处理和显示。例如,我们可以编写一个接收数据的事件recvData,并在该事件中将接收到的数据显示在文本框中: ``` Sub SerialPort1_DataReceive(Sender, data As String) TextBox1.Text = TextBox1.Text & data End Sub ``` 在上述代码中,TextBox1表示用于显示接收到的数据的文本框。 通过以上步骤,我们就可以在MC GS软件中实现串口数据收发功能。用户可以通过按钮发送数据接收到的数据会实时显示在文本框中。这样就完成了mcgs串口收发数据的实例。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

巨大八爪鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值