当前位置: 首页 > news >正文

UART通信HAL库API

UART通信HAL库API

  • 一,HAL_UART_Receive_IT
  • 二,HAL_UART_RxCpltCallback
  • 三,HAL_UART_Transmit
  • 四,阻塞 vs. 非阻塞 (中断/DMA)
    • 1·,阻塞模式 (HAL_UART_Transmit)
    • 2,非阻塞模式 (_IT / _DMA)
  • 五,"精准计时器":uwTick
  • 六,HAL库的设计哲学
    • 1,抽象与可移植性
    • 2,句柄 (Handle) 与状态管理
    • 3,回调 (Callback) 与弱函数 (Weak Functions)
    • 4,多模式支持 (阻塞/中断/DMA)
  • 七,HAL_UARTEx_ReceiveToIdle_DMA
  • 八,HAL_UART_DMAStop
  • 九,HAL_UARTEx_RxEventCallback
  • 十,__HAL_DMA_DISABLE_IT (宏)
  • 十一,HAL_UART_IRQHandler

一,HAL_UART_Receive_IT

HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);

API功能:
这位英雄的角色是"快递预订员"。每次调用它,就好比你跟前台说:“嘿,请帮我留意一下,下一个(而且仅仅一个,因为我们Size=1)送来的包裹,到了之后立刻(中断方式)通知我(触发中断),并把它放到这个指定的位置(pData指向的内存)。”

函数形参:
UART_HandleTypeDef *huart: 告诉预订员是哪个"快递站"(比如 &huart1)的包裹。
uint8_t *pData: 指定包裹送达后要存放的"货位地址"(比如 &uart_rx_buffer[uart_rx_index])。
uint16_t Size: 指定这次预订接收的包裹"数量"。在我们的超时解析法中,为了能及时响应每个字节,我们每次都只预订 1 个字节。

关键点 IT (Interrupt - 中断): 函数名末尾的 _IT 是关键!它表示这是一个非阻塞的操作。你跟前台预订完就可以走了,去做别的事情。当包裹真的到达时,硬件会自动触发一个"中断信号",就像前台给你打了个紧急电话,而不是让你一直在那里傻等。这使得 CPU 可以高效地处理其他任务,只在数据到达时才介入。

何时使用:
1,初始化时: 在系统启动后,需要调用一次 HAL_UART_Receive_IT(&huart1, &uart_rx_buffer[0], 1); 来"预订"第一个字节的到来,相当于第一次给门口的传感器通电。
2,中断回调中: 当一个字节成功接收并触发了中断回调函数(HAL_UART_RxCpltCallback)后,必须在回调函数内部再次调用 HAL_UART_Receive_IT 来"预订"下一个字节。这就像警报响过后,你需要手动再次给传感器"重新通电",否则它检测到一次之后就失效了。

二,HAL_UART_RxCpltCallback

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);

API功能:
这位是"收货确认员",但它不是你主动去调用的,而是 HAL 库这位"大管家"在特定事件发生时自动呼叫的。哪个特定事件呢?就是你之前通过 HAL_UART_Receive_IT 预订的那个字节,它完整接收成功了
这个函数就像是前台给你的那个"紧急电话":“喂!你之前订的那个包裹(那 1 个字节)已经送到了!请指示下一步操作。”

函数形参:
UART_HandleTypeDef *huart: 表明是哪个"快递站"(比如 USART1)打来的电话,方便你管理多个串口。

回调 (Callback) 的含义: 不需要显式地调用它,只需要把它实现好。HAL 库会在合适的时机(数据接收完成时)自动"回头调用" (call back) 你写的这个函数。

弱函数 (Weak Function)
HAL 库中的很多回调函数(包括这个)都被声明为 __weak (或者类似的编译器特定语法)。如果你不提供自己的版本,系统会使用这个默认的、通常什么也不做的版本。但一旦你在自己的代码中定义了一个同名的、非 weak 的函数,链接器就会忽略那个弱的默认版本,转而使用你提供的版本。这使得用户可以方便地在自己的代码中"覆盖"或"重写" HAL 库的默认中断处理行为,而无需修改库本身的源码,大大提高了代码的可维护性和模块化。

三,HAL_UART_Transmit

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

API功能:
这位是"快递发货员"。当你需要通过串口向外发送数据时,就调用它。就像你把打包好的、写好地址的一批货交给发货员,告诉他:"把这些货(数据)从这个快递站(串口)发出去。

在我们的 my_printf 函数中,就是调用了它来将格式化好的字符串通过串口发送给电脑的串口助手。

函数形参:
UART_HandleTypeDef *huart: 指定从哪个"快递站"(比如 &huart1)发货。
uint8_t *pData: 指向你要发送的那批"货物"(数据缓冲区,比如 my_printf 函数中的 buffer)。
uint16_t Size: 这批货物的"总件数"(要发送的数据长度,比如 my_printf 计算出的 len)。
uint32_t Timeout: 发货超时时间(单位毫秒)。如果在这个时间内,硬件一直忙碌无法开始发送,函数就会放弃并返回一个超时错误。你可以设置为 HAL_MAX_DELAY 表示一直等待直到能发送为止。

四,阻塞 vs. 非阻塞 (中断/DMA)

1·,阻塞模式 (HAL_UART_Transmit)

你把一箱货交给发货员,然后就站在旁边一直盯着他,直到他把箱子里所有的货一件件发出去(或者等到超时你放弃了),你才能离开。

优点:代码简单,逻辑直接。

缺点:如果货物很多或发送慢,CPU 会一直卡在这里等待,无法处理其他紧急事务。

2,非阻塞模式 (_IT / _DMA)

你把货交给发货员,然后告诉他:“货发完了打我电话(中断)。” 然后你就离开了,去做别的事。发货员自己处理,完成后通过中断通知你。

优点:CPU 不会被阻塞,可以高效处理其他任务。

缺点:需要配合中断回调函数来处理"发送完成"事件,逻辑稍微复杂一点。DMA 方式效率最高,连字节搬运都无需 CPU 操心。

对于少量调试信息,阻塞方式足够简单。但在需要同时处理接收和其他任务的场景下,非阻塞方式通常是更好的选择。

五,“精准计时器”:uwTick

extern volatile uint32_t uwTick;

这位不是函数,而是一个全局变量,可以理解为整个系统(或大楼)的"中央时钟脉冲计数器"。

来源: 它通常由一个叫做 SysTick 的硬件定时器驱动。你可以把 SysTick 配置为每 1 毫秒产生一次中断
工作方式: 在 SysTick 定时器的中断服务函数(通常是 SysTick_Handler)中,HAL 库会自动执行 HAL_IncTick() 函数,这个函数的作用就是把 uwTick 这个变量的值加 1。
作用: 因为 uwTick 每毫秒加 1,所以它的值就代表了从系统启动开始经过的总毫秒数。

uwTick是超时判断的基石: 在我们的超时解析逻辑 if (uwTick - uart_rx_ticks > UART_TIMEOUT_MS) 中,uwTick 提供了当前的精确时间,uart_rx_ticks 保存了上次收到字节的时间戳,两者相减就得到了时间间隔,从而判断是否超时。uwTick 是实现各种基于时间的逻辑(延时、超时等)的基础。

为什么使用volatile 关键字:
这个关键字告诉编译器,uwTick 的值随时可能在程序当前流程之外被改变(被 SysTick 中断改变)。因此,每次读取 uwTick 时,编译器都必须老老实实地从内存中重新获取它的最新值,而不能使用可能存在于寄存器中的旧的缓存值,从而保证了计时逻辑的准确性。

六,HAL库的设计哲学

了解了具体的 API 后,我们不妨退后一步,看看 STMicroelectronics 设计 HAL (Hardware Abstraction Layer - 硬件抽象层) 库的初衷和一些核心思想。这有助于我们理解为什么 API 是这样设计的,以及如何更有效地使用它。

1,抽象与可移植性

想象一下,如果没有 HAL 库,你需要直接操作底层的硬件寄存器。不同的 STM32 型号(比如 F1, F4, L4, H7 系列)它们的寄存器地址、位定义可能千差万别。为 F1 写的外设驱动代码,几乎无法直接用在 F4 上。
HAL 库的核心价值就在于抽象。它提供了一套统一的、功能导向的 API,屏蔽了底层硬件寄存器的差异。你只需要调用像 HAL_UART_Transmit 这样的函数,而不需要关心具体是往哪个地址的哪个寄存器的哪个位写入了什么值。这使得你的应用程序代码具有更好的可移植性,更换不同的 STM32 芯片时,上层代码的修改量大大减少。

抽象层级示意:
±----------------------+
| 你的应用程序代码 |
| (调用 HAL_UART_… ) |
±----------±----------+
|
|
|
|
v
±----------±----------+
| HAL 库 API |
| (HAL_UART_Transmit 等) |
±----------±----------+
|
|
|
| (HAL 内部根据不同芯片型号,执行不同的底层代码)
|
|
v
±----------±----------+ ±----------±----------+
| STM32F1xx 底层驱动 | | STM32F4xx 底层驱动 | …
| (操作 F1 寄存器) | | (操作 F4 寄存器) |
±----------------------+ ±----------------------+

2,句柄 (Handle) 与状态管理

你会发现几乎所有的 HAL 函数都需要一个 UART_HandleTypeDef *huart 这样的参数。这个被称为"句柄" (Handle) 的结构体非常关键。

它就像是这个外设(比如 USART1)的"身份证"和"状态记录本"。
1,配置信息:初始化时设定的波特率、数据位、停止位等参数。
2,状态变量:记录当前外设是空闲、正在发送还是正在接收(比如 huart->gState, huart->RxState)。HAL 函数会根据这些状态来判断能否执行某个操作。
3,数据指针和计数器:在中断或 DMA 模式下,记录用户数据缓冲区地址(pRxBuffPtr)、还需要接收/发送多少数据(RxXferSize, RxXferCount)等。
4,底层硬件寄存器地址:指向具体的硬件实例(比如 huart->Instance = USART1;)。

通过传递这个句柄,HAL 函数就能知道要操作哪个具体的硬件实例,以及它当前的状态和相关数据信息,从而实现正确的操作和管理。

3,回调 (Callback) 与弱函数 (Weak Functions)

正如在 HAL_UART_RxCpltCallback 中提到的,HAL 库广泛使用回调机制来处理异步事件(如中断)。

它定义了一系列标准的事件回调函数(如接收完成、发送完成、错误发生等),并将它们声明为弱函数。这意味着:

1,HAL 库提供了一个(通常为空的)默认实现。
2,用户可以在自己的代码中提供一个同名的、非弱的实现来"覆盖"默认行为。
3,当对应的硬件事件发生时(例如,在中断服务程序中检测到接收完成),HAL 的中断处理代码会调用这个(可能是用户覆盖后的)回调函数。
3,这种设计使得用户代码和库代码得以解耦。用户无需修改库源码,就能方便地将自己的处理逻辑"挂载"到特定的硬件事件上。

4,多模式支持 (阻塞/中断/DMA)

HAL 库通常为数据传输操作(如 UART 收发、SPI 收发等)提供多种模式:
1,阻塞模式 (Polling): 函数会一直等待操作完成才返回。简单直接,但会阻塞 CPU。
2,中断模式 (Interrupt): 函数启动操作后立刻返回,操作完成后通过中断和回调函数通知用户。CPU 不被阻塞,但中断处理本身会消耗 CPU 时间。
3,DMA 模式 (Direct Memory Access): 配置 DMA 控制器来处理内存和外设之间的数据传输,传输过程几乎不占用 CPU 时间,完成后通过中断通知用户。效率最高,尤其适合大数据量传输。

重点:
理解了这些设计思路,能帮助我们更好地利用 HAL 库的优势,并更容易地排查问题。例如,当中断没有按预期触发时,我们知道
1,要去检查是否正确调用了 _IT 版本的函数、
2,是否在回调中重新启动了接收、
3,中断是否被正确使能等。

七,HAL_UARTEx_ReceiveToIdle_DMA

HAL_StatusTypeDef HAL_UARTEx_ReceiveToIdle_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);

API功能:
这位英雄是 HAL_UART_Receive_IT 的升级版,专门用于配合 DMA 和空闲中断。它不仅启动接收( HAL_UART_Receive_IT 的功能),还同时配置和启动了 DMA 控制器,并设置了空闲线路检测。
它的运行更复杂:“嘿,DMA 搬运工 (已通过 huart 句柄关联好),你现在去串口收货口 (huart->Instance) 守着。一旦有货 (数据字节) 进来,你就用你专属的通道,直接把它搬到仓库的这个临时区域 (pData)。这个区域总共能放 Size 件货。你一直搬,同时注意听着收货口的动静,如果突然安静了超过一个字节的时间(总线空闲,IDLE 事件),或者你把这个临时区域 (pData 指向的内存,大小为 Size) 给堆满了 (DMA TC 事件),都要立刻通过内部专线(中断)向我(CPU)汇报!”

函数参数和 HAL_UART_Receive_IT 类似:
huart: 指定哪个串口及其关联的 DMA 配置。
pData: DMA 搬运的目标内存地址(通常是 DMA 专用缓冲区)。
Size: 本次 DMA 传输期望的最大字节数(DMA 缓冲区的总大小)。DMA 会持续接收,直到接收满 Size 个字节,或者中途检测到总线空闲。

“Ex” 与 “ToIdle”: 函数名中的 “Ex” (Extended) 表明这是 HAL 库的扩展功能。 “ReceiveToIdle” 则清晰地指出了它的核心机制:接收数据,直到检测到总线空闲。它内部封装了:
启动 DMA、
使能 UART 空闲中断 (IDLEIE)
使能 DMA 半满/全满中断

八,HAL_UART_DMAStop

HAL_StatusTypeDef HAL_UART_DMAStop(UART_HandleTypeDef *huart);

API功能:
老板(CPU)通过内部电话(中断)得知收货口没货了(空闲事件),但搬运工(DMA)可能还在那里傻等(因为它被告知要搬满 Size 个才算完)(UART 空闲中断和DMA 传输完成/半完成可以自行开启和关闭,在我们的应用中,主要关心的是 IDLE 空闲事件。)。老板立刻通知搬运工:“好了好了,别等了,活儿先停下!”

这就是为什么要在 RxEventCallback 中调用?
在 DMA + 空闲中断模式下,当空闲中断触发 RxEventCallback 时,我们明确知道发送方已经停止发送。但 DMA 控制器本身可能还在运行(因为它设置的目标是接收 Size 个字节,可能还没满)。此时调用 HAL_UART_DMAStop 可以利落地终止当前的 DMA 传输,清理相关状态,防止 DMA 出错或继续无效等待,为后续重新启动 DMA 接收做好准备。

函数形参:
huart: 指明要停止哪个串口的 DMA 操作。

九,HAL_UARTEx_RxEventCallback

void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size);

何时调用?
这位是 DMA + 空闲中断模式下的"核心事件处理员"。它也是一个弱函数回调,但触发它的事件比较特殊,通常是以下两者之一(或两者都配置了):
1,UART 空闲中断 (IDLE): 检测到串口总线进入空闲状态,表明一次数据传输很可能已经结束。
2,DMA 传输完成/半完成 (TC/HT): DMA 控制器完成了预设数量(Size 或 Size/2)的数据搬运。

在我们的应用中,主要关心的是 IDLE 空闲事件。当这个事件发生时,HAL 库的 HAL_UART_IRQHandler 会捕捉到,并最终调用这个回调函数。

函数参数:
huart: 哪个串口的事件。
Size: 至关重要! 这个参数告诉你,从上次启动 HAL_UARTEx_ReceiveToIdle_DMA 到本次事件(特别是空闲事件)发生时,DMA 搬运工实际搬运了多少个字节的数据到内存缓冲区。这个值可能小于、等于你启动 DMA 时设定的缓冲区总大小。

内部配置:
函数内部的标准处理流程(DMA+空闲中断模式):

确认串口 (if 判断)。
1,停止 DMA (HAL_UART_DMAStop(huart)😉: 如前所述,确保停止当前的 DMA 操作。
2,处理数据 (memcpy + 设置标志): 使用回调函数提供的 Size 参数,将 DMA 缓冲区中实际接收到的有效数据,拷贝到应用程序的处理缓冲区(例如 uart_dma_buffer)。3,然后设置一个标志(如 uart_flag = 1;)通知主循环。
4,清空 DMA 缓冲区 (memset): 为下一次接收做准备。
5,重新启动接收 (HAL_UARTEx_ReceiveToIdle_DMA(…)): 必须再次调用它,才能接收下一帧数据。

关键在于 Size: 正确使用 Size 参数是从 DMA 缓冲区提取有效数据的关键。如果忽略 Size 而总是处理整个 DMA 缓冲区,可能会处理到上次残留的数据或者无效数据。

十,__HAL_DMA_DISABLE_IT (宏)

__HAL_DMA_DISABLE_IT(HANDLE, INTERRUPT)

严格来说,这位不是一个函数,而是一个宏 (Macro)。宏在编译预处理阶段就会被替换成实际的寄存器操作代码。它的作用是关闭指定的 DMA 通道上的特定中断。

DMA 搬运工除了能在"完成任务"或"检测到空闲"时给老板打电话(触发中断)外,还可能在"任务完成一半"(Half Transfer)时也打个电话。如果我们不关心这个"中途汇报",觉得它是个干扰,就可以用这个宏告诉系统:“别让搬运工因为’完成一半’这种小事就给我打电话!”

参数:
1,HANDLE: DMA 的句柄(例如 &hdma_usart1_rx),指明是哪个 DMA 通道。
2,INTERRUPT: 要禁用的具体中断类型,HAL 库定义了一些常量,
例如:
DMA_IT_TC: 传输完成中断 (Transfer Complete)
DMA_IT_HT: 半传输中断 (Half Transfer)
DMA_IT_TE: 传输错误中断 (Transfer Error)
在我们的例子中,使用 DMA_IT_HT 来禁用半传输中断。

为何禁用 HT 中断?
在空闲中断接收模式下,我们通常只关心数据帧的结束(由 IDLE 中断指示),而不关心 DMA 缓冲区是否刚好填满了一半。HT 中断如果触发,可能会调用与 TC/IDLE 相同的 RxEventCallback,干扰我们判断数据帧结束的逻辑。因此,显式禁用 HT 中断可以简化处理流程,避免不必要的干扰。

注意再次:
如果在初始化后调用了一次 __HAL_DMA_DISABLE_IT(…, DMA_IT_HT),那么在 RxEventCallback 中重新启动 DMA (HAL_UARTEx_ReceiveToIdle_DMA) 之后,可能也需要再次调用这个宏来确保 HT 中断保持禁用状态,因为启动函数可能会重新使能它。

十一,HAL_UART_IRQHandler

我们已经接触了各种回调函数,它们是特定事件警报被触发时产生的"响应"。但这些响应是如何被精确调度的?
答案在UART 的"中断控制室"——HAL_UART_IRQHandler 函数中。
UART中断流程:
1,外设事件(如 RXNE、TXE、IDLE、ERROR 等)在 UART 硬件模块内部产生,并且对应的中断使能位已置位。
2,NVIC(中断控制器) 收到外设的中断请求后,根据优先级判断是否向 CPU 发起中断。
3,CPU 响应 NVIC,跳转到对应的中断向量入口,也就是 USART1_IRQHandler(以 USART1 为例)。
4,在 USART1_IRQHandler 里,第一件事就是调用 HAL_UART_IRQHandler(&huart1),把控制权交给 HAL 库统一去处理各类 UART 事件:
void USART1_IRQHandler(void)
{
HAL_UART_IRQHandler(&huart1);
}
5,HAL_UART_IRQHandler 内部会:

5.1检查各个事件标志(RXNE、TC、IDLE、ERROR…)
5.2调用相应的回调函数(如 HAL_UART_RxCpltCallback、HAL_UART_TxCpltCallback 等)
5.3清除已处理的中断标志

eg HAL_UART_IRQHandler :
if (__HAL_UART_GET_FLAG(huart, UART_FLAG_RXNE)) {
// 调用接收完成回调,清 RXNE 标志
}
if (__HAL_UART_GET_FLAG(huart, UART_FLAG_TC)) {
// 调用传输完成回调,清 TC 标志
}
if (__HAL_UART_GET_FLAG(huart, UART_FLAG_IDLE)) {
// 调用空闲检测回调,清 IDLE 标志
}
// ……其他标志

相关文章:

  • Pydantic 是一个 Python 库,核心是做数据验证、设置管理和数据转换
  • 知行之桥如何将消息推送到钉钉群?
  • php中配置variables_order详解
  • 监控 Oracle Cloud 负载均衡器:使用 Applications Manager 释放最佳性能
  • 使用Nginx + Keepalived配置实现Web站点高可用方案
  • UE5 编辑器工具蓝图
  • Chroma 向量数据库使用示例
  • 可视化图解算法46:用两个栈实现队列
  • 6.4.2_3最短路径问题_Floyd算法
  • Open3D上可视化Nuscenes 数据集
  • 操作系统(Operator System)
  • 【Python】 -- 趣味代码 - 佩奇
  • 数据结构-代码总结
  • golang 柯里化(Currying)
  • 嵌入式开发学习(第二阶段 C语言笔记)
  • Golang | gRPC索引服务
  • Java jdk8版本特性(未完成版)
  • 截图后怎么快速粘贴到notability?
  • 常规算法学习
  • 打印Yolo预训练模型的所有类别及对应的id
  • 做招聘的网站/中国十大电商培训机构
  • 网络运维前景/seo入门课程
  • 做公益网站赚钱吗/外贸平台推广
  • 网站seo竞争分析工具/网站排名怎么优化
  • 做网站一定要用云解析吗/2022拉新推广赚钱的app
  • 怎么做微信领券网站/优化设计三年级上册语文答案