缘由:
在进行STM32与ESP8266通信时,我在接收中断中使用了HAL_UART_Transmit与printf函数,发现ESP8266返回来的数据有一定的丢失与错位现象,在对逻辑进行改写后发现问题与HAL库本身函数有关,这引起了我对其的兴趣。
原理:
我使用了串口空闲中断接收数据,而HAL_UART_Transmit在底层调用了一个接收中断打断函数,将丢失数据对应的接收中断位清除,使得该数据未被单片机接收,放在开发者眼中就是接收数据出现了错位与丢失。
实践过程:
起初,我在串口的空闲接收中断内将接收到的数据发送至调试串口,具体代码如下
可是这段代码却有一个巨大乃至无法忽视的BUG:使用该种方式接收的数据出现了字节错位乃至丢失这种堪称致命的BUG,具体情况如下图所示:
int HAL_uart_IDLE(UART_HandleTypeDef *huart, uint8_t *RX_Buffer, uint8_t *RX_GetData, uint8_t *RX_GetFlag, uint16_t RX_count)
{
// static uint8_t RX_count = 0;
// uint16_t i = 0;
uint8_t ch = RX_Buffer[0];
if (__HAL_UART_GET_IT_SOURCE(huart, UART_IT_IDLE) == RESET)
{
return 0;
}
__HAL_UART_CLEAR_IDLEFLAG(huart);
__HAL_UART_DISABLE_IT(huart, UART_IT_IDLE);
if (RX_GetFlag)
{
HAL_UART_Receive_IT(huart, RX_Buffer, 1);
RX_GetFlag = RESET;
goto move_RX_data;
}
move_RX_data:
{
if (Serial_RxFlag && ((RX_count<80)&&(RX_count>42)))
{
RX_GetData[RX_count-43] = ch;
}
//HAL_UART_Transmit(&huart1,&ch,1,1);
//printf("%c",ch);
if (ESP8266_Fram_Record_Struct.InfBit.FramLength < (RX_BUF_MAX_LEN - 1))
{
ESP8266_Fram_Record_Struct.Data_RX_BUF[ESP8266_Fram_Record_Struct.InfBit.FramLength++] = ch;
}
return 1;
}
}//串口空闲中断接收处理函数
图1 在接收中断中使用HAL_UART_Transmit向上位机发送调试数据
图2 在接收中断中使用printf向上位机发送调试数据
我们可以很直观的看到:图1与图2都有着不同程度的数据丢失现象,且图1 的严重程度远大于图2,在我的一番改良之后,我解决了数据的丢失问题,但是数据为什么会丢失呢?从以下几个图中,我似乎发现了这个现象的原因。这个现象在我当时看来是因为HAL_UART_Transmit与printf的运行时间不一样,我认为后者比前者要快,不然如何解释在调试串口波特率略大于数据接收串口的情况下,图4出现了数据丢失而图5却没有任何数据丢失呢?
图3 在调试串口波特率(115200)略大于接收串口波特率(100000)的情况下,使用HAL_UART_Transmit向上位机发送调试数据
图4 在图3的情况下,使用printf向上位机发送调试数据
图5 在图1与图2 的架构上进行的针对性升级,彻底解决了数据丢失现象
因为我根据以上结果得出了printf比HAL_UART_Transmit要快,所以我使用了仿真调试去尝试证明我的猜想,但是我得到的结果与我的猜想大相径庭,printf跟HAL_UART_Transmit相比,在同样持续运行5000次的结果下,前者仅比后者快上2微秒。具体结果如图所示:
void A_TEST()
{
for (d = 0; d < 5000; d++)
{
printf("a");
}
}
void A_TEST3()
{
for (d = 0; d < 5000; d++)
{
HAL_UART_Transmit(&huart1,&ch,1,1);
//HAL_UART_Transmit_IT(&huart1,&ch,1);
}
}
void A_TEST1()
{
for (d = 0; d < 1; d++)
{
printf("a");
}
}
void A_TEST2()
{
for (d = 0; d < 1; d++)
{
HAL_Delay(1000);
}
}//测试代码
void main()
{
A_TEST2();
A_TEST1();
A_TEST();
A_TEST3();
}//测试函数具体调用顺序
图6 函数的执行时间,与上述代码的函数顺序一一对应
这个结果推翻我先前的所有假设:2微秒的时间对于一个5000字节的数据集合来说根本不会产生什么质的变化,更别提这个误差很有可能是因为仿真器的测试误差出现的,因此,我对该现象的研究进入了瓶颈。
我不禁思索:如果不是因为时间,那是因为什么呢?
在这样的困扰下,我开始查看HAL库的底层代码,很幸运的是,我发现了数据丢失现象的根源。具体代码及分析放在下面。
while (huart->TxXferCount > 0U)
{
if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TXE, RESET, tickstart, Timeout) != HAL_OK)
{
huart->gState = HAL_UART_STATE_READY;
return HAL_TIMEOUT;
}
if (pdata8bits == NULL)
{
huart->Instance->DR = (uint16_t)(*pdata16bits & 0x01FFU);
pdata16bits++;
}
else
{
huart->Instance->DR = (uint8_t)(*pdata8bits & 0xFFU);
pdata8bits++;
}
huart->TxXferCount--;
}
if (UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TC, RESET, tickstart, Timeout) != HAL_OK)
{
huart->gState = HAL_UART_STATE_READY;
return HAL_TIMEOUT;
}
//HAL_UART_Transmit的核心代码
通过该图我们可以看到HAL_UART_Transmit的运行逻辑就是先判断传输数据是否超时,如果没有,就会传输一个字节同时数据指针自增。连续传输就是重复以上过程。
但是这里并没有我想要的内容,于是,我进入UART_WaitOnFlagUntilTimeout,
具体代码及分析放在下面。
static HAL_StatusTypeDef UART_WaitOnFlagUntilTimeout(UART_HandleTypeDef *huart, uint32_t Flag, FlagStatus Status,
uint32_t Tickstart, uint32_t Timeout)
{
/* Wait until flag is set */
while ((__HAL_UART_GET_FLAG(huart, Flag) ? SET : RESET) == Status)
{
/* Check for the Timeout */
if (Timeout != HAL_MAX_DELAY)
{
if (((HAL_GetTick() - Tickstart) > Timeout) || (Timeout == 0U))
{
return HAL_TIMEOUT;
}
if ((READ_BIT(huart->Instance->CR1, USART_CR1_RE) != 0U) && (Flag != UART_FLAG_TXE) && (Flag != UART_FLAG_TC))
{
if (__HAL_UART_GET_FLAG(huart, UART_FLAG_ORE) == SET)
{
/* Clear Overrun Error flag*/
__HAL_UART_CLEAR_OREFLAG(huart);
/* Blocking error : transfer is aborted
Set the UART state ready to be able to start again the process,
Disable Rx Interrupts if ongoing */
UART_EndRxTransfer(huart);
huart->ErrorCode = HAL_UART_ERROR_ORE;
/* Process Unlocked */
__HAL_UNLOCK(huart);
return HAL_ERROR;
}
}
}
}
return HAL_OK;
}
想必你也看见了:Blocking error下的Disable Rx Interrupt if ongoing,这正是关于之前数据丢失现象的症结所在:传输函数每次发送一个字节,都要进行一次乃至更多的是否超时检查,而超时检查会失能接收中断。这本身没有问题,但是如果我们在接收中断中使用了传输函数就会使得我们本要接收的数据对应的那个中断标志位被清除,使得数据未被接收。UART_EndRxTransfer的具体代码及分析如下图所示。
/**
* @brief End ongoing Rx transfer on UART peripheral (following error detection or Reception completion).
* @param huart UART handle.
* @retval None
*/
static void UART_EndRxTransfer(UART_HandleTypeDef *huart)
{
/* Disable RXNE, PE and ERR (Frame error, noise error, overrun error) interrupts */
ATOMIC_CLEAR_BIT(huart->Instance->CR1, (USART_CR1_RXNEIE | USART_CR1_PEIE));
ATOMIC_CLEAR_BIT(huart->Instance->CR3, USART_CR3_EIE);
/* In case of reception waiting for IDLE event, disable also the IDLE IE interrupt source */
if (huart->ReceptionType == HAL_UART_RECEPTION_TOIDLE)
{
ATOMIC_CLEAR_BIT(huart->Instance->CR1, USART_CR1_IDLEIE);
}
/* At end of Rx process, restore huart->RxState to Ready */
huart->RxState = HAL_UART_STATE_READY;
huart->ReceptionType = HAL_UART_RECEPTION_STANDARD;
}
从图中我们可以很清晰的看到该函数关闭了数个中断,并且开启了对串口空闲中断的检查,如果空闲中断被开启,该函数就会清除空闲中断的标志位。如果我们的已置位空闲中断在我们传输时被清除,那单片机就不会接收到该次中断应该接收到的数据,也就发生了数据丢失现象。
那为什么printf函数与标准库的UART_SendData函数不会出现这样的情况呢?
二者的具体代码及分析如下图。
#if 1
#pragma import(__use_no_semihosting)
// 标准库需要的支持函数
struct __FILE
{
int handle;
};
FILE __stdout;
// 定义_sys_exit()以避免使用半主机模式
void _sys_exit(int x)
{
x = x;
}
// 重定义fputc函数
int fputc(int ch, FILE *f)
{
while ((USART1->SR & 0X40) == 0)
;
USART1->DR = (uint8_t)ch;
return ch;
}
#endif
//本人使用的printf函数
/**
* @brief Transmits single data through the USARTx peripheral.
* @param USARTx: Select the USART or the UART peripheral.
* This parameter can be one of the following values:
* USART1, USART2, USART3, UART4 or UART5.
* @param Data: the data to transmit.
* @retval None
*/
void USART_SendData(USART_TypeDef* USARTx, uint16_t Data)
{
/* Check the parameters */
assert_param(IS_USART_ALL_PERIPH(USARTx));
assert_param(IS_USART_DATA(Data));
/* Transmit Data */
USARTx->DR = (Data & (uint16_t)0x01FF);
}
//标准库提供的串口发送函数
从图片中我们可以知道,printf函数没有涉及HAL库的中断关闭,而标准库的发送函数则仅有对相应外设的宏参数检查,只要输入的参数合法,就可以直接调用串口底层寄存器发送数据,也不用涉及串口中断的关闭,所以原代码的接收中断不会出现数据丢失现象。
标签:HAL,UART,RX,huart,串口,发送数据,接收 From: https://blog.csdn.net/2301_80285967/article/details/144153124