tips:
这是XJIE工作学习中遇到的一些问题,以及处理方式,是个人笔记(禁止没礼貌的转载)
工作描述
最近工作需要在PC端接收标签信号然后发送到我的板子上,和ADC数据信号一起打包发送到上位机。在调试UART5的是否出现一系列的问题。
一开始在直接加上UART5的时候,直接用HAL库的HAL_UART_Receive()
,程序直接卡死,在论坛上看了一些资料后,尝试看看发送功能是否正常,调用HAL_UART_Transmit()
,系统正常。
硬件
- 搭载STM32的板子
- win64bit的PC
通信链
- PC --> 板子:UART5
- 板子 --> 上位机:USART1
问题一:UART5
一开始在直接加上UART5的时候,直接用HAL库的HAL_UART_Receive()
,程序直接卡死,在论坛上看了一些资料后,尝试看看发送功能是否正常,调用HAL_UART_Transmit
,系统正常。
于是一步一步的寻找问题,试图解决问题
Debug
1、检查硬件连接:
- 确保UART5的RX(接收)引脚正确连接,并且没有物理损坏。
- 检查接收线是否正确接触到对应的引脚,没有接触不良或短路的问题。
2、检查引脚配置:
- 在STM32的初始化代码中,确认RX引脚是否被正确配置为UART接收功能。你可以通过查阅STM32F446的数据手册来确认引脚功能和配置。
3、查看软件设置:
- 检查UART初始化代码,确保波特率、数据位、停止位和奇偶校验位设置正确。
- 确认是否启用了UART接收中断或DMA接收,并在相应的中断服务例程中添加了处理接收数据的代码。
4、使用HAL库或LL库函数:
- 如果你使用的是STM32的HAL库,确保调用了
HAL_UART_Receive_IT()
(中断方式)或HAL_UART_Receive_DMA()
(DMA方式)来启动接收。 - 检查是否有错误处理代码,例如检查
HAL_UART_ErrorCallback()
是否被调用,以及是否处理了错误状态。
5、串口助手工具:
- 使用串口调试工具(如串口助手)来测试UART通信,确保问题不在于外部设备或电脑的串口设置。
6、固件和库版本:
- 确认你的STM32CubeMX或STM32CubeIDE工具包和固件库是最新版本,有时候库的BUG也会导致功能异常。
7、电源和接地:
1、检查供电和接地是否稳定,不稳定的电源和接地问题有时也会影响微控制器的功能。
8、示波器检测:
- 如果可能,使用示波器检查UART5 RX引脚上的信号,确保信号幅度和波形正常。
解决
在startup_stm32f446XX.s 中发现没有USRT4、USRT5的中断向量表。
添加DCD UART4_IRQHandler
以及 DCD UART5_IRQHandler
后,使用HAL_UART_Receive_IT
程序正常接收到,PC端发送来的数据。
问题二:中断接收
ps :在usart.c 以及stm32f4XX_it.c 中并未做任何的修改。
到了这一步遇到的问题是,我的PC端给MCU发送了15次标签信号,但是,MCU只接收到了5次、6次或者8次(很随机,包括但是不限于这三个数值)。
串口接收
于是我检查了PC断是否发出15次标签信号,于是我连接上了示波器使用tigger功能来捕捉串口的波形。简单的描述一下这个使用步骤:
- 给示波器上电,连接好示波器的探头。
- 把示波器恢复默认模式(师傅说因为你不知道上一个人设置了什么)。
- 在你的示波器上设置好你探头的X1或者X10挡位,我是设置了X10挡位,因为X10挡位的带宽高。
- 设置好tigger功能,按下sigal按钮,等待PC端发送信号。
串口中断原理
这里补充一点:中断时一个很重要的概念,我们需要了解,中断的触发机制(中断向量表的实现)。
续接上文,我是怎么发现,我的UART4无法进入中断的,我是怎么做让他进入中断的。
点开我的 .S 我发现他居然没有UART4的中断向量。于是在文件中加入
DCD UART4_IRQHandler ; UART4
DCD UART5_IRQHandler ; UART5
UART4和UART5均可以进入中断。
问题三:收发不匹配
上面的问题已经得到了很好的解决,但是结果很完美吗?不并不是 😂
这个时候就出现了上面的结果我的PC端给MCU发送了15次标签信号,但是,MCU只接收到了5次、6次或者8次。
HAL库中断函数
这里我调整了我代码,删除了不合时宜的代码,尽管现在他依然不是很完美。
UART5_IRQHandler()
这个函数我之前提到过,如果你还记得。他是中断向量表中我们添加的函数。换言之,一旦接收寄存器存满了数据,就触发中断,触发这个函数。如果你的代码模板是STM32CubeMX生成的,那么这个函数会存在stm 32f4xx_it.c
文件中。
中断服务程序(Interrupt Request Handler, IRQHandler)
void UART5_IRQHandler(void)
{
/* USER CODE BEGIN UART5_IRQn 0 */
/* USER CODE END UART5_IRQn 0 */
HAL_UART_IRQHandler(&huart5);
/* USER CODE BEGIN UART5_IRQn 1 */
HAL_UART_Receive_IT(&huart5, (uint8_t*)RxFreq, 1);
/* USER CODE END UART5_IRQn 1 */
}
很明显我们可以看到,在这个函数里并没有接收数据等操作。
HAL_UART_IRQHandler()
顾名思义,这个就是HAL编写的串口中断服务函数。
uint32_t isrflags = READ_REG(huart->Instance->SR);
uint32_t cr1its = READ_REG(huart->Instance->CR1);
uint32_t cr3its = READ_REG(huart->Instance->CR3);
uint32_t errorflags = 0x00U;
uint32_t dmarequest = 0x00U;
可以看出来在这个函数里开始经行幅值了。
if (((isrflags & USART_SR_RXNE) != RESET) && ((cr1its & USART_CR1_RXNEIE) != RESET))
{
UART_Receive_IT(huart);
return;
}
更重要的是,这个函数调用了一个名为UART_Receive_IT()
的函数。
UART_Receive_IT()
这个函数主要是来修改串口的状态的。在这个函数中又这样的描述(我挑重要的来讲)
/* Disable the UART Data Register not empty Interrupt */
__HAL_UART_DISABLE_IT(huart, UART_IT_RXNE);
/* Disable the UART Parity Error Interrupt */
__HAL_UART_DISABLE_IT(huart, UART_IT_PE);
/* Disable the UART Error Interrupt: (Frame error, noise error, overrun error) */
__HAL_UART_DISABLE_IT(huart, UART_IT_ERR);
/* Rx process is completed, restore huart->RxState to Ready */
huart->RxState = HAL_UART_STATE_READY;
他在函数里关闭了串口中断这点很重要,因为,在大部分的情况下我们需要整板可以连续的工作,而不是在,收到一个TAG后,就复位或者重新上电。
所以我们指定是要在其他的地方重新的中断。这里先卖一个关子。
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
/*Call registered Rx Event callback*/
huart->RxEventCallback(huart, huart->RxXferSize);
#else
/*Call legacy weak Rx Event callback*/
HAL_UARTEx_RxEventCallback(huart, huart->RxXferSize);
#endif /* USE_HAL_UART_REGISTER_CALLBACKS */
}
else
{
/* Standard reception API called */
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
/*Call registered Rx complete callback*/
huart->RxCpltCallback(huart);
#else
/*Call legacy weak Rx complete callback*/
HAL_UART_RxCpltCallback(huart);
#endif /* USE_HAL_UART_REGISTER_CALLBACKS */
好好好,在这个函数里提到了另一个重要的函数HAL_UART_RxCpltCallback()
这就是大名鼎鼎的中断回调函数了。这个名字特别奇怪,或许要从’线程‘的角度的来解释吧。我的理解就是,如果里希望在串口中断发生的时候进行一些其他的功能,比如点个灯,就可以写在这个函数里。对了,这个函数是一个弱函数,你可以在里工程的任何位置重新定义他。我一般习惯定义在usart.c
文件里。
HAL_UART_Receive_IT()
哦不!!上面说到UART_Receive_IT()
函数会关闭中断,不要着急。这个函数就是来重新开启中断的。
HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
{
/* Check that a Rx process is not already ongoing */
if (huart->RxState == HAL_UART_STATE_READY)
{
if ((pData == NULL) || (Size == 0U))
{
return HAL_ERROR;
}
/* Process Locked */
__HAL_LOCK(huart);
/* Set Reception type to Standard reception */
huart->ReceptionType = HAL_UART_RECEPTION_STANDARD;
return (UART_Start_Receive_IT(huart, pData, Size));
}
else
{
return HAL_BUSY;
}
}
这个函数有指向另一个函数UART_Start_Receive_IT()
UART_Start_Receive_IT()
HAL_StatusTypeDef UART_Start_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
{
huart->pRxBuffPtr = pData;
huart->RxXferSize = Size;
huart->RxXferCount = Size;
huart->ErrorCode = HAL_UART_ERROR_NONE;
huart->RxState = HAL_UART_STATE_BUSY_RX;
/* Process Unlocked */
__HAL_UNLOCK(huart);
if (huart->Init.Parity != UART_PARITY_NONE)
{
/* Enable the UART Parity Error Interrupt */
__HAL_UART_ENABLE_IT(huart, UART_IT_PE);
}
/* Enable the UART Error Interrupt: (Frame error, noise error, overrun error) */
__HAL_UART_ENABLE_IT(huart, UART_IT_ERR);
/* Enable the UART Data Register not empty Interrupt */
__HAL_UART_ENABLE_IT(huart, UART_IT_RXNE);
return HAL_OK;
}
在这个函数里不仅幅值了,更重要的是,他重新开启了串口中断。