优秀的编程知识分享平台

网站首页 > 技术文章 正文

STM32F4 HAL库学习笔记之串口通讯 2

nanyue 2025-05-21 15:30:54 技术文章 5 ℃

#头号有新人#

STM32F4HAL库学习笔记之串口通讯 2

  • 步骤
  • 函数使用
  • 串口初始化串口中断串口接收串口发送
  • 串口使用流程
  • 总结

步骤

  1. GPIO和串口时钟使能;
  2. 初始化GPIO,复用;
  3. 设置串口参数及中断;
  4. 使能串口;
  5. 编写中断服务函数。

函数使用

串口初始化

HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef *huart);

串口初始化函数,这个函数有一个入口参数huart(句柄), UART_HandleTypeDef 结构体指针类型,UART_HandleTypeDef 中包含了另外的结构体和参数,用到了句柄用来专门登记各应用对象在内存中的地址变化。

typedef struct
{
USART_TypeDef *Instance;
UART_InitTypeDef Init;
uint8_t *pTxBuffPtr;
uint16_t TxXferSize;
uint16_t TxXferCount;
uint8_t *pRxBuffPtr;
uint16_t RxXferSize;
uint16_t RxXferCount;
DMA_HandleTypeDef *hdmatx;
DMA_HandleTypeDef *hdmarx;
HAL_LockTypeDef Lock;
__IO HAL_UART_StateTypeDef State;
__IO uint32_t ErrorCode;
}UART_HandleTypeDef;

UART_HandleTypeDef 中配置串口相关设置,在HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef *huart)调用时会使能响应串口,不需要另外单独使能。

执行HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef *huart)时会先调用MSP初始化回调函数进行MCU初始化,打开这个函数,在356行有

HAL_UART_MspInit(huart);

这句前面是看用户有没有自定义回调函数,判断是否执行这个。自定义下面会写。在这里面可以执行GPIO和中断相关配置。打开这个函数里面是

__weak void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
UNUSED(huart);
/* NOTE: This function should not be modified, when the callback is needed,
the HAL_UART_MspInit could be implemented in the user file
}

__weak是定义的一个弱函数,如果用户重新定义了这个函数,那么会优先执行用户定义的函数,可以重新定义这个函数配置与MCU级别相关的硬件初始化,这也是HAL库的优点。

所以HAL库中的初始化流程为:先初始化与MCU无关的串口协议,再初始化与MCU相关的硬件配置,这样在移植过程中只需要修改回调函数中的参数,即可完成适配。

回调函数:

函数A调用函数B的时候,通过参数将函数C的指针传递给了函数B(也就是函数B的入口参数写为函数C的指针)。在函数B执行过程中调用了函数C,这个动过叫做回调。在这个过程中先是被当作指针传入又被回调的函数C就是回调函数。

很多资料中说HAL_UART_MspInit(huart)是回调函数,其实这里只是写成了回调函数的样式,如果用户没有自定义回调函数,这个就可以看做是普通调用。

用户自定义回调函数:

#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
UART_InitCallbacksToDefault(huart);
if (huart->MspInitCallback == NULL)
{
huart->MspInitCallback = HAL_UART_MspInit;
}
huart->MspInitCallback(huart);
#else
HAL_UART_MspInit(huart);
#endif

如果定义了
USE_HAL_UART_REGISTER_CALLBACKS=1,那么用户可以自定义串口的初始化函数

#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
void UART_InitCallbacksToDefault(UART_HandleTypeDef *huart)
{
/* Init the UART Callback settings */
huart->TxHalfCpltCallback = HAL_UART_TxHalfCpltCallback; /* Legacy weak TxHalfCpltCallback */
huart->TxCpltCallback = HAL_UART_TxCpltCallback; /* Legacy weak TxCpltCallback */
huart->RxHalfCpltCallback = HAL_UART_RxHalfCpltCallback; /* Legacy weak RxHalfCpltCallback */
huart->RxCpltCallback = HAL_UART_RxCpltCallback; /* Legacy weak RxCpltCallback */
huart->ErrorCallback = HAL_UART_ErrorCallback; /* Legacy weak ErrorCallback */
huart->AbortCpltCallback = HAL_UART_AbortCpltCallback; /* Legacy weak AbortCpltCallback */
huart->AbortTransmitCpltCallback = HAL_UART_AbortTransmitCpltCallback; /* Legacy weak AbortTransmitCpltCallback */
huart->AbortReceiveCpltCallback = HAL_UART_AbortReceiveCpltCallback; /* Legacy weak AbortReceiveCpltCallback */
}

这种状态就是回调函数了。
最后调用HAL_UART_Receive_IT函数开启接收中断,并设置接收缓冲以及最大接收数量。

串口中断

要使用中断,首先需要配置中断,由于NVIC属于MCU级别,所以放在HAL_UART_Msplnit中初始化,与库函数不同,HAL库中使用了两个函数配置中断:

void HAL_NVIC_EnableIRQ(IRQn_Type IRQn)//使能中断
void HAL_NVIC_SetPriority(IRQn_Type IRQn, uint32_t PreemptPriority, uint32_t SubPriority)//配置抢占优先级、子优先级。

串口接收

  1. 编写中断服务函数
USARTX_IRQHandler

HAL库中定义了一个串口中断处理通用函数:

void HAL_UART_IRQHandler(UART_HandleTypeDef *huart);

打开这个函数,找到如下部分:

if (errorflags == RESET)
{
/* UART in mode Receiver -------------------------------------------------*/
if (((isrflags & USART_SR_RXNE) != RESET) && ((cr1its & USART_CR1_RXNEIE) != RESET))
{
UART_Receive_IT(huart);
return;
}
}

isrflags 宏定义串口状态寄存器,cr1its控制状态寄存器,调用上面的通用函数后,会判断串口状态,是否进入UART_Receive_IT(huart),在这个函数中开始启动接收工作并调用串口数据处理函数:

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)

这个函数和上面一样是一个回调函数(用户可自定义)库中该函数的定义为弱函数,在这里进行串口数据处理,HAL库中所有的串口数据处理在这里面进行,所以这里面会判断是哪一个串口的数据再进行判断,剩下的流程就和用库函数处理是一样的了。

串口发送

HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)

这是HAL库中的串口发送函数,在标准库中串口发送只需要指定串口和数据,但是需要发送多个字节时是需要自己写函数的,如果要求比较高还要另外再做超时程序。在HAl库中使用这个函数可以定义发送长度,增加了超时处理,还是挺方便的,减少了开发的工作量。

调用后要等待发送完成

while(__HAL_UART_GET_FLAG(&UART1_Handler,UART_FLAG_TC)!=SET);

不然会有各种奇奇怪怪的错误。

串口使用流程

上面只是记录了各个函数的使用和原理,毕竟连函数的运行机制都没搞明白也写不出来好的程序,出了错误也找不到。

如果只是想用串口而已,知道下面写这些就可以了。

首先对串口与硬件级无关的参数进行初始化,对串口初始化

时会调用串口硬件级初始化的回调函数,所以要做好回调函数,在回调函数中做好与硬件相关的串口参数配置,这样便于移植。

编写串口中断服务程序,中断触发后会调用串口数据处理回调函数。注意:串口处理函数是公用的,串口中断服务函数不是。

串口发送只需要调用库中定义好的函数,指定串口、数据、长度、超时即可。但要注意等待发送完成。

总结

C功底不够扎实,库中有很多东西看不明白,各种指针也是绕来绕去,我当初为什么要选择这个专业QAQ,想想当条咸鱼也没什么不好的。

Tags:

最近发表
标签列表