Uart之数据溢出
问题背景:
在项目现场实施项目的过程之中,出现了一个问题。stm32的控制器,莫名其妙会死机,不定时会出现这个问题。出现之后系统指示灯不在闪烁,网络死机,服务器再也ping不同,下位机的控制器。经过debug发现了抓到了这个死机的点。我发现并不是硬件错误,没有调用hart_fault这个中断服务函数。但是主线程却没有跑起来。意外发现原来程序是反复进入,串口的中断里面。检查错误标志,发现是串口溢出错误。原来是假死机,系统并没有崩溃,只是全部的资源都被这个错误中断给占用了。下面开始解决这个问题。
串口dma加空闲中断的方式。
老代码里面是使用,单字节串口接收中断来实现的。由于新增的功能变得越来越多,关中断的地方也越来越多,因此中断响应不及时就会造成这种现象。目前使用的是4个串口同时工作。为了解决这个问题,使用dma加空闲中断的方式来解决的这个问题。不需要每个字节都进入中断,减少进入中断的次数,使用dma方式,无需cpu参与,减少这种情况发生的概率。增加对溢出中断的错误处理。
- 串口初始化的配置
这里需要注意的是,调用UART_Init的时候就需要调用Start_UART_DMA_Receive(eDevice);因为开启了串口,就会接收数据,如果此时不开始dma接收,就会导致过载。因此刚开始的时候,就要开始dma接收。
void UART_Init(Uart_Dev eDevice, u32 dwbound, u32 WordLength, u32 dwStopBits, u32 dwParity)
{
memset((u8 *)&g_sUartDrvRxd[eDevice], 0, sizeof(APP_UART_MSG));
UART_Handler[eDevice].Instance = pUART_BASE_ADDR[eDevice];
UART_Handler[eDevice].Init.BaudRate = dwbound;
UART_Handler[eDevice].Init.WordLength = WordLength;
UART_Handler[eDevice].Init.StopBits = dwStopBits;
UART_Handler[eDevice].Init.Parity = dwParity;
UART_Handler[eDevice].Init.HwFlowCtl = UART_HWCONTROL_NONE;
UART_Handler[eDevice].Init.Mode = UART_MODE_TX_RX; //UART_DEV6 == eDevice ? UART_MODE_TX : UART_MODE_TX_RX;
HAL_UART_Init(&UART_Handler[eDevice]);
Start_UART_DMA_Receive(eDevice);
}
static void USART1_MspInit(UART_HandleTypeDef *huart)
{
GPIO_InitTypeDef GPIO_Initure;
static DMA_HandleTypeDef hdma_tx1;
static DMA_HandleTypeDef hdma_rx1;
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_USART1_CLK_ENABLE();
/* Enable DMA2 clock */
__HAL_RCC_DMA2_CLK_ENABLE();
GPIO_Initure.Pin = GPIO_PIN_9 | GPIO_PIN_10;
GPIO_Initure.Mode = GPIO_MODE_AF_PP;
GPIO_Initure.Pull = GPIO_PULLUP;
GPIO_Initure.Speed = GPIO_SPEED_FAST;
GPIO_Initure.Alternate = GPIO_AF7_USART1;
HAL_GPIO_Init(GPIOA, &GPIO_Initure);
/* Configure the DMA streams ##########################################*/
/* Configure the DMA handler for Transmission process */
hdma_tx1.Instance = USART1_TX_DMA_STREAM;
hdma_tx1.Init.Channel = USART1_TX_DMA_CHANNEL;
hdma_tx1.Init.Direction = DMA_MEMORY_TO_PERIPH;
hdma_tx1.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_tx1.Init.MemInc = DMA_MINC_ENABLE;
hdma_tx1.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_tx1.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_tx1.Init.Mode = DMA_NORMAL;
hdma_tx1.Init.Priority = DMA_PRIORITY_LOW;
hdma_tx1.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
hdma_tx1.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL;
hdma_tx1.Init.MemBurst = DMA_MBURST_INC4;
hdma_tx1.Init.PeriphBurst = DMA_PBURST_INC4;
HAL_DMA_Init(&hdma_tx1);
/* Associate the initialized DMA handle to the the UART handle */
__HAL_LINKDMA(huart, hdmatx, hdma_tx1);
/* Configure the DMA handler for Transmission process */
hdma_rx1.Instance = USART1_RX_DMA_STREAM;
hdma_rx1.Init.Channel = USART1_RX_DMA_CHANNEL;
hdma_rx1.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_rx1.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_rx1.Init.MemInc = DMA_MINC_ENABLE;
hdma_rx1.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_rx1.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_rx1.Init.Mode = DMA_NORMAL;
hdma_rx1.Init.Priority = DMA_PRIORITY_HIGH;
hdma_rx1.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
hdma_rx1.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL;
hdma_rx1.Init.MemBurst = DMA_MBURST_INC4;
hdma_rx1.Init.PeriphBurst = DMA_PBURST_INC4;
HAL_DMA_Init(&hdma_rx1);
/* Associate the initialized DMA handle to the the UART handle */
__HAL_LINKDMA(huart, hdmarx, hdma_rx1);
__HAL_UART_ENABLE_IT(&UART_Handler[UART_DEV1], UART_IT_IDLE);
HAL_NVIC_EnableIRQ(USART1_IRQn);
HAL_NVIC_SetPriority(USART1_IRQn, 3, 3);
g_sDma_buf[UART_DEV1] = USART1_DMA_BUF;
}
static BOOLEAN Start_UART_DMA_Receive(Uart_Dev eDevice)
{
if(eDevice >= UART_NUM)
{
// AGV_Logo(AGV_MODULE_ID_SENSOR_NAVI,LEVEL_ERROR,"Invalid uart_index %d in Start_UART_DMA_Receive\n",eDevice);
return FALSE;
}
g_sUartDrvRxd[eDevice].byDmaReceiveFlag = 0;
memset(g_sUartDrvRxd[eDevice].byRxdBuf,0,USART_REC_MAXLEN);
HAL_UART_Receive_DMA(&UART_Handler[eDevice], g_sDma_buf[eDevice], dma_size_buf[eDevice]);
return TRUE;
}
- 中断处理函数
void USART1_IRQHandler()
{
OSIntEnter();
if(__HAL_UART_GET_FLAG(&UART_Handler[UART_DEV1],UART_FLAG_ORE) != RESET)
{
__HAL_UART_CLEAR_OREFLAG(&UART_Handler[UART_DEV1]);
Start_UART_DMA_Receive(UART_DEV1);
CT_PRINTF("uart1 overrun\n");
}
if((__HAL_UART_GET_FLAG(&UART_Handler[UART_DEV1],UART_FLAG_IDLE) != RESET))
{
__HAL_UART_CLEAR_IDLEFLAG(&UART_Handler[UART_DEV1]);
Uart_Receive_Idle(&UART_Handler[UART_DEV1], UART_DEV1);
}
OSIntExit();
}
static void Uart_Receive_Idle(UART_HandleTypeDef *huart, Uart_Dev eDevice)
{
uint32_t temp;
temp = huart->Instance->SR;
HAL_UART_DMAStop(huart);
temp = huart->hdmarx->Instance->NDTR;
g_sUartDrvRxd[eDevice].wRxdLen = dma_size_buf[eDevice] - temp;
g_sUartDrvRxd[eDevice].dwTick = HAL_GetTick();
g_sUartDrvRxd[eDevice].byDmaReceiveFlag = 1;
memcpy(g_sUartDrvRxd[eDevice].byRxdBuf, g_sDma_buf[eDevice], g_sUartDrvRxd[eDevice].wRxdLen);
HAL_UART_DMAResume(huart);
__HAL_UART_ENABLE_IT(&UART_Handler[UART_DEV6], UART_IT_IDLE);
HAL_UART_Receive_DMA(huart, g_sDma_buf[eDevice], dma_size_buf[eDevice]);
}
这里清除ore这个flag是很有意思的。网上有很多说法,但是我还是以自己调试代码的实际情况为准:
首先过载的时候,调用__HAL_UART_GET_FLAG就可以知道接下来需要清除ore,调用函数__HAL_UART_CLEAR_OREFLAG
#define __HAL_UART_CLEAR_PEFLAG(__HANDLE__) \
do{ \
__IO uint32_t tmpreg = 0x00U; \
tmpreg = (__HANDLE__)->Instance->SR; \
tmpreg = (__HANDLE__)->Instance->DR; \
UNUSED(tmpreg); \
} while(0U)
#define __HAL_UART_CLEAR_OREFLAG(__HANDLE__) __HAL_UART_CLEAR_PEFLAG(__HANDLE__)
可以看到清除标志位只是去读了,sr和dr寄存器
因此实际上清除ore的同时rxne等寄存器也已经清除掉了,因此不需要单独再清除rxne。最后一定要再次调用dma开始读下一次数据,这样避免再一次过载。程序就可以稳健的运行。
- 用户获取数据
Get_Frame_Status Uart_Get_One_Frame(Uart_Dev eDevice, APP_UART_MSG *buffer)
{
if (g_sUartDrvRxd[eDevice].byDmaReceiveFlag == 0 ||
g_sUartDrvRxd[eDevice].wRxdLen == 0) {
return NO_FRAME;
}
memcpy(buffer, &g_sUartDrvRxd[eDevice], sizeof(g_sUartDrvRxd[eDevice]));
g_sUartDrvRxd[eDevice].byDmaReceiveFlag = 0;
return HAS_FRAME;
}
这样用户每次调用就可以直接获取一帧数据,非常方便,而且可以避免串口数据溢出的问题。