小华HC32F460串口性能问题与处理思路
问题描述
在使用HC32F460串口中断方式接收较多数据(每包209字节)且连续接收,预计发送20包数据。此时RxFull数据接收中断会出现似乎是性能瓶颈的问题,导致数据接收不及时出现丢包。
我的项目中设计方式为Rx中断不断接收数据,主循环不断处理数据且主循环参考ModbusRtu协议的处理方式以数据间的时间间隔来判断是否到达1帧数据,因此对数据的接收时间要求较高,接收不及时会导致丢包。
本文函数内容并不完整,只提供问题的记录和排查思路。
发现并测试该问题
首先是很通用的串口配置函数,启用了Rx数据接收中断和错误中断
void InitUSART1(int baud)
{stc_usart_uart_init_t stcUartInit;stc_irq_signin_config_t stcIrqSigninConfig;/* Configure USART RX/TX pin. */GPIO_SetFunc(USART1_RX_PORT, USART1_RX_PIN, USART1_RX_GPIO_FUNC);GPIO_SetFunc(USART1_TX_PORT, USART1_TX_PIN, USART1_TX_GPIO_FUNC);/* Enable peripheral clock */USART1_FCG_ENABLE();/* Initialize UART. */(void)USART_UART_StructInit(&stcUartInit);stcUartInit.u32ClockDiv = USART_CLK_DIV64;//stcUartInit.u32Baudrate = 115200UL;stcUartInit.u32Baudrate = baud;stcUartInit.u32OverSampleBit = USART_OVER_SAMPLE_8BIT;if (LL_OK != USART_UART_Init(USART1_UNIT, &stcUartInit, NULL)) {Led_SetDeviceState(DEV_STATE_ERROR);//红灯提示出错for (;;) {}}/* Register RX error IRQ handler && configure NVIC. */stcIrqSigninConfig.enIRQn = USART1_RX_ERR_IRQn;stcIrqSigninConfig.enIntSrc = USART1_RX_ERR_INT_SRC;stcIrqSigninConfig.pfnCallback = &USART1_RxError_IrqCallback;INTC_IrqInstalHandler(&stcIrqSigninConfig, DDL_IRQ_PRIO_DEFAULT);/* Register RX full IRQ handler && configure NVIC. */stcIrqSigninConfig.enIRQn = USART1_RX_FULL_IRQn;stcIrqSigninConfig.enIntSrc = USART1_RX_FULL_INT_SRC;stcIrqSigninConfig.pfnCallback = &USART1_RxFull_IrqCallback;INTC_IrqInstalHandler(&stcIrqSigninConfig, DDL_IRQ_PRIO_DEFAULT);// /* Register TX empty IRQ handler && configure NVIC. */
// stcIrqSigninConfig.enIRQn = USART1_TX_EMPTY_IRQn;
// stcIrqSigninConfig.enIntSrc = USART1_TX_EMPTY_INT_SRC;
// stcIrqSigninConfig.pfnCallback = &USART1_TxEmpty_IrqCallback;
// INTC_IrqInstalHandler(&stcIrqSigninConfig, DDL_IRQ_PRIO_DEFAULT);// /* Register TX complete IRQ handler && configure NVIC. */
// stcIrqSigninConfig.enIRQn = USART1_TX_CPLT_IRQn;
// stcIrqSigninConfig.enIntSrc = USART1_TX_CPLT_INT_SRC;
// stcIrqSigninConfig.pfnCallback = &USART1_TxComplete_IrqCallback;
// INTC_IrqInstalHandler(&stcIrqSigninConfig, DDL_IRQ_PRIO_DEFAULT);USART_FuncCmd(USART1_UNIT, (USART_RX | USART_INT_RX | USART_TX), ENABLE);//POLL使用,需要开启tx,但是会导致设备启动和复位时候发送垃圾数据0x00
}
而后是数据接收中断
/*** @brief USART1 RX IRQ callback* @param None* @retval None*/
void USART1_RxFull_IrqCallback(void)
{//读取串口数据到m_stcRingBufu8 u8Data = USART_ReadData(USART1_UNIT) & 0xFF;rx1[rx1_cnt++] = u8Data;//不断累加记录串口传回的字符串netProtocol_S.usart1TimeStamp = netProtocol_S.timeStampNow;//记录最后一次接收到数据的时间}
最后是在主循环中运行的数据处理。
此处为优化后的函数,去除了可能消耗性能的协议解析和数据移动内容,只进行简单的赋值和加减法。
当接收到的一包数据不足8字节的时候触发串口返回ERROR提示。
void Usart1RxPoll(void)
{//RX数据寄存器有数据后,读取
// if(SET == USART_GetStatus(USART1_UNIT, USART_FLAG_RX_FULL))
// {
// u8 u8Data = USART_ReadData(USART1_UNIT) & 0xFF;// rx1[rx1_cnt++] = u8Data;//不断累加记录串口传回的字符串
// //int processBytes = CProtocol_AddToStream(&g_proUsart1Rx, (u16*)&u8Data, 1, 1);
//
// netProtocol_S.usart1TimeStamp = netProtocol_S.timeStampNow;//记录最后一次接收到数据的时间
////
//// u8 u8Data = USART_ReadData(USART1_UNIT);
//// int processBytes = CProtocol_AddToStream(&g_proUsart1Rx, (u16*)&u8Data, 1, 1);
// }//以时间为间隔处理数据包if((netProtocol_S.timeStampNow - netProtocol_S.usart1TimeStamp >= 50 )//uint类型的数据无需担心溢出&& netProtocol_S.usart1TimeStamp != 0){netProtocol_S.usart1TimeStamp = 0;netProtocol_S.usart1LastPackageTime = netProtocol_S.timeStampNow;//记录最后一个接收到数据包的时间节点parseLen = rx1_cnt;//记录此时已经收到数据长度,将该长度的数据视作一帧进行处理//ATParse(rx1, parseLen);if(parseLen < 8){USART1SendBuf("ERROR", 5);}else{USART1SendBuf("OK", 2);memset(rx1, 0, RX1_TOTAL_LEN);}//memmove(rx1, rx1 + parseLen, RX1_TOTAL_LEN - parseLen);//将未处理的数据移动到前面,上一帧的数据丢弃rx1_cnt = rx1_cnt - parseLen;//移除已处理的长度}}
问题的触发方式
上位机通过串口向芯片下发大包数据并连续发送
参考的modbusRtu数据帧,209字节如下:
若出现数据接收不及时,串口就会返回ERROR
数据手动发送,大概发送间隔0.2s或更短(手动点击串口调试助手进行发送),包大小209字节
02 10 03 EA 00 64 C8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 FF F0 00 00 00 00 00 00 00 00 00 00 00 00 00 7F 00 3F 80 00 00 00 00 00 00 00 00 00 00 00 0F C0 00 00 FC 00 00 00 00 00 00 00 00 00 00 00 78 00 3F 00 07 80 00 00 00 00 00 00 00 00 00 01 C0 3F FF FF 00 E0 00 00 00 00 00 00 00 00 00 0F 03 FF FF FF F0 1C 00 00 00 00 00 00 00 00 00 38 1F FF FF FF FE 07 00 00 00 00 00 00 00 00 00 E0 FF FF FF FF FF C1 C0 00 00 00 00 00 00 00 01 83 FD BF FF FF FF F0 70 00 00 00 00 00 00 00 06 0F FC 3F FF FF FF FC 18 00 00 00 00 00 00 00 18 3F FC 3F FF 8E AE
为什么确认是中断性能问题
1、在InitUSART1函数中提高串口接收数据函数的中断优先级后,出现丢包概率变低,但仍会出现
/* Register RX full IRQ handler && configure NVIC. */stcIrqSigninConfig.enIRQn = USART1_RX_FULL_IRQn;stcIrqSigninConfig.enIntSrc = USART1_RX_FULL_INT_SRC;stcIrqSigninConfig.pfnCallback = &USART1_RxFull_IrqCallback;INTC_IrqInstalHandler(&stcIrqSigninConfig, DDL_IRQ_PRIO_00);
2、改善串口接收函数,每次进中断就接收完所有数据,出现丢包概率变低,但仍会出现
void USART1_RxFull_IrqCallback(void)
{while (USART_GetStatus(USART1_UNIT, USART_FLAG_RX_FULL) == SET) {u8 u8Data = USART_ReadData(USART1_UNIT) & 0xFF;rx1[rx1_cnt++] = u8Data;}netProtocol_S.usart1TimeStamp = netProtocol_S.timeStampNow;}
由上述现象总结判断,中断频繁触发导致性能不足,无法在限定时间内接收完数据,进而导致了主循环丢包。
我的芯片倍频后按照200Mhz运行,串口波特率115200,理论上性能应该不会不足,但还是出现该现象,只能将其暂时归类至中断性能问题。
最终处理方案
数据接收部分修改至主循环中,并关闭Rx数据接收中断,从而节约频繁切换中断对性能的消耗。
void Usart1RxPoll(void)
{//RX数据寄存器有数据后,读取if(SET == USART_GetStatus(USART1_UNIT, USART_FLAG_RX_FULL)){u8 u8Data = USART_ReadData(USART1_UNIT) & 0xFF;rx1[rx1_cnt++] = u8Data;//不断累加记录串口传回的字符串netProtocol_S.usart1TimeStamp = netProtocol_S.timeStampNow;//记录最后一次接收到数据的时间}//以时间为间隔处理数据包if((netProtocol_S.timeStampNow - netProtocol_S.usart1TimeStamp >= 50 )//uint类型的数据无需担心溢出&& netProtocol_S.usart1TimeStamp != 0){netProtocol_S.usart1TimeStamp = 0;netProtocol_S.usart1LastPackageTime = netProtocol_S.timeStampNow;//记录最后一个接收到数据包的时间节点parseLen = rx1_cnt;//记录此时已经收到数据长度,将该长度的数据视作一帧进行处理ATParse(rx1, parseLen);memmove(rx1, rx1 + parseLen, RX1_TOTAL_LEN - parseLen);//将未处理的数据移动到前面,上一帧的数据丢弃rx1_cnt = rx1_cnt - parseLen;//移除已处理的长度}}
总结
目前排查和总结为芯片存在的频繁切换中断导致的性能不足问题,通过在主循环中接收和处理数据,不使用中断,进而解决该问题。
不过仍然不排除是rx1_cnt字段被中断和主循环竞争使用导致的问题。字段竞争导致该问题的可能很小,因而不继续排查。
若是字段竞争导致的问题可以通过维护一个头尾相接的缓冲区,中断向缓冲写数据,主循环从末尾读走数据来解决。
