【STM32】HAL库学习笔记20260707-STM32 HAL库函数综合手册:参数详解 · 调用顺序 · 代码归属实战
【STM32】HAL库学习笔记20260707-STM32 HAL库函数综合手册:参数详解 · 调用顺序 · 代码归属实战
本手册综合两份参考文档及全网技术资料,系统说明STM32 HAL库各外设函数的每个参数的数据类型、格式规范与具体示例、使用方法、调用顺序、代码在main.c中的正确放置位置(确保不被CubeMX重新生成代码删除),适用于STM32全系列(F1/F4/H7等)。
目录
- 一、HAL库核心编程思想与命名规范
- 二、通用参数类型速查(理解所有函数参数的基础)
- 三、函数使用总则(三步走 + 中断/DMA配套流程)
- 四、CubeMX代码保护机制:USER CODE区域详解
- 五、核心外设函数详解与调用顺序
- 六、MSP函数与MX_Init函数的区别
- 七、回调函数机制深度解析
- 八、函数调用顺序速查总表
- 九、完整main.c代码架构模板
- 十、常见错误与排查清单
- 十一、CubeMX配置与开发流程备忘
一、HAL库核心编程思想与命名规范
STM32 HAL库(Hardware Abstraction Layer)遵循 HAL_[外设]_[操作]_[模式] 的统一命名规范,提供三种核心编程模型:
| 模式 | 命名后缀 | 说明 | 适用场景 |
|---|---|---|---|
| 轮询模式(Polling) | 无后缀或_Timeout |
阻塞式操作,CPU等待完成或超时 | 简单初始化、低速通信 |
| 中断模式(Interrupt) | _IT |
非阻塞,通过回调函数处理事件 | 实时响应、低功耗场景 |
| DMA模式 | _DMA |
数据直接搬运,无需CPU干预 | 大数据量传输(ADC采样、串口收发) |
命名规范示例:
HAL_UART_Transmit()— 轮询发送HAL_UART_Transmit_IT()— 中断发送HAL_UART_Transmit_DMA()— DMA发送HAL_UARTEx_ReceiveToIdle_IT()— 扩展API:空闲中断接收
HAL库返回值类型 HAL_StatusTypeDef:
| 返回值 | 数据类型枚举值 | 含义 | 使用场景 | 示例 |
|---|---|---|---|---|
HAL_OK |
0x00U |
操作成功 | 函数返回后可继续执行 | if(ret == HAL_OK) |
HAL_ERROR |
0x01U |
操作错误 | 检查参数或硬件配置 | if(ret == HAL_ERROR) Error_Handler(); |
HAL_BUSY |
0x02U |
外设忙(正在执行其他操作) | 等待当前操作完成后再调用 | while(HAL_UART_GetState(&huart1) != HAL_UART_STATE_READY) |
HAL_TIMEOUT |
0x03U |
操作超时 | 检查Timeout参数或外设响应 | if(ret == HAL_TIMEOUT) { /*增大Timeout重试*/ } |
建议在调用关键函数后检查返回值,如
if (HAL_UART_Transmit(&huart1, tx_data, 8, 100) != HAL_OK) { Error_Handler(); }
二、通用参数类型速查(理解所有函数参数的基础)
在阅读每个函数的参数详解前,先理解以下HAL库通用参数类型,它们在几乎所有函数中反复出现。
2.1 外设句柄(Handle)
| 要素 | 说明 |
|---|---|
| C数据类型 | UART_HandleTypeDef*、TIM_HandleTypeDef*、ADC_HandleTypeDef* 等各外设对应的结构体指针 |
| 格式规范 | 传参时必须加 & 取地址符,如 &huart1。变量名由CubeMX自动生成,声明在main.c全局区 |
| 命名规则 | h + 外设缩写 + 序号,如 huart1 = UART1句柄、htim2 = TIM2句柄、hadc1 = ADC1句柄、hspi1 = SPI1句柄、hi2c1 = I2C1句柄、hcan1 = CAN1句柄、hdac = DAC句柄、hrtc = RTC句柄、hiwdg = IWDG句柄、hwwdg = WWDG句柄 |
| 多实例示例 | &huart1(串口1)、&huart2(串口2)、&huart3(串口3);&htim2(定时器2)、&htim3(定时器3) |
| 来源 | CubeMX在main.c中自动声明:UART_HandleTypeDef huart1; 等,用户无需手动定义 |
| ⚠️ 注意 | 不要自己定义句柄变量,CubeMX已生成;不要修改句柄结构体内部的成员,那是CubeMX配置的结果 |
2.2 数据缓冲区指针(pData)
| 要素 | 说明 |
|---|---|
| C数据类型 | uint8_t*(串口/SPI/I2C常用)、uint32_t*(ADC DMA用) |
| 格式规范 | 传入全局数组名(自动退化为指针),如 rx_buf;或用 & 取单个全局变量的地址,如 &rx_byte |
| 示例值 | rx_buf(数组:uint8_t rx_buf[256];)、&rx_byte(单字节:uint8_t rx_byte;)、tx_data(数组:uint8_t tx_data[8] = {0x01,...};)、(uint32_t*)adc_buf(ADC DMA强转) |
| ⚠️ 注意 | ① 必须是全局变量或static变量,局部变量函数返回后释放会导致数据错乱 ② DMA模式下数组大小必须 ≥ Size参数 ③ 中断模式下接收1字节可用 &rx_byte,多字节用数组名 rx_buf |
2.3 数据长度(Size)
| 要素 | 说明 |
|---|---|
| C数据类型 | uint16_t |
| 格式规范 | 直接传入整数常量、宏定义或变量,如 8、1、RX_BUF_SIZE |
| 示例值 | 1(接收单字节)、8(收发8字节)、256(DMA缓冲区大小)、sizeof(tx_data)(自动计算)、ADC_SAMPLES(宏定义如32) |
| 取值范围 | 0 ~ 65535(0通常无意义,实际有效范围取决于缓冲区大小) |
| ⚠️ 注意 | ① 轮询/中断模式下 Size = 要收发的精确字节数 ② 空闲中断模式下 Size = 缓冲区最大容量,实际接收字数由回调的Size参数返回 ③ DMA Circular模式下 Size = 缓冲区总长度 |
2.4 超时时间(Timeout)
| 要素 | 说明 |
|---|---|
| C数据类型 | uint32_t |
| 格式规范 | 直接传入毫秒整数,如 100、1000、HAL_MAX_DELAY |
| 示例值 | 100(100ms超时,串口常用)、1000(1秒超时,I2C常用)、HAL_MAX_DELAY(无限等待,值为0xFFFFFFFF)、10(10ms短超时) |
| ⚠️ 注意 | ① 仅轮询模式函数有此参数,IT/DMA模式无超时参数 ② HAL_MAX_DELAY会一直阻塞直到操作完成,慎用 ③ 超时后函数返回HAL_TIMEOUT,不会自动重试 |
2.5 通道(Channel)
| 要素 | 说明 |
|---|---|
| C数据类型 | uint32_t |
| 格式规范 | 使用HAL库预定义的宏常量,如 TIM_CHANNEL_1、DAC_CHANNEL_1 |
| 示例值 | TIM_CHANNEL_1(TIM通道1)、TIM_CHANNEL_2(TIM通道2)、TIM_CHANNEL_3(TIM通道3)、TIM_CHANNEL_4(TIM通道4)、DAC_CHANNEL_1(DAC通道1)、DAC_CHANNEL_2(DAC通道2)、CAN_RX_FIFO0(CAN接收FIFO0)、CAN_RX_FIFO1(CAN接收FIFO1) |
| ⚠️ 注意 | TIM通道号与物理引脚对应关系由CubeMX配置决定,不可随意填数字 |
2.6 GPIO端口与引脚
| 要素 | 说明 |
|---|---|
| GPIOx 数据类型 | GPIO_TypeDef* |
| GPIOx 格式规范 | 使用预定义端口基地址宏,如 GPIOA、GPIOB |
| GPIOx 示例值 | GPIOA(端口A)、GPIOB(端口B)、GPIOC(端口C)、GPIOD(端口D) |
| GPIO_Pin 数据类型 | uint16_t |
| GPIO_Pin 格式规范 | 使用预定义引脚掩码宏,如 GPIO_PIN_5;多引脚组合用 ` |
| GPIO_Pin 示例值 | GPIO_PIN_5(引脚5)、GPIO_PIN_13(引脚13)、`GPIO_PIN_0 |
| ⚠️ 注意 | 不要直接填数字如 5,必须用宏 GPIO_PIN_5;组合多引脚时用 ` |
2.7 对齐方式(Alignment)
| 要素 | 说明 |
|---|---|
| C数据类型 | uint32_t |
| 格式规范 | 使用HAL库预定义的对齐宏 |
| 示例值 | DAC_ALIGN_12B_R(12位右对齐,DAC最常用)、DAC_ALIGN_12B_L(12位左对齐)、DAC_ALIGN_8B_R(8位右对齐) |
| ⚠️ 注意 | DAC最常用的对齐方式是 DAC_ALIGN_12B_R,对应数据范围 0~4095 |
2.8 I2C地址格式(DevAddress / MemAddSize)
| 要素 | 说明 |
|---|---|
| DevAddress 数据类型 | uint16_t |
| DevAddress 格式规范 | HAL库使用8位地址格式,7位地址需左移1位:0x68 << 1 = 0xD0 |
| DevAddress 示例值 | 0xD0(MPU6050,7位地址0x68左移)、0xA0(AT24C02,7位地址0x50左移)、0x3C(SSD1306 OLED,7位地址0x1C左移) |
| MemAddSize 数据类型 | uint16_t |
| MemAddSize 格式规范 | 使用预定义宏 |
| MemAddSize 示例值 | I2C_MEMADD_SIZE_8BIT(8位寄存器地址,如MPU6050)、I2C_MEMADD_SIZE_16BIT(16位寄存器地址,如AT24C256) |
| ⚠️ 注意 | I2C地址切记要左移!直接传 0x68 会通信失败 |
2.9 RTC格式(Format)
| 要素 | 说明 |
|---|---|
| C数据类型 | uint32_t |
| 格式规范 | 使用预定义宏 |
| 示例值 | RTC_FORMAT_BIN(二进制格式,如 Hours=23 直接就是23)、RTC_FORMAT_BCD(BCD编码格式,如 Hours=0x23 表示23点) |
| ⚠️ 注意 | 一般用户操作用 RTC_FORMAT_BIN 更直观;BCD格式是RTC硬件寄存器原生格式 |
2.10 HAL_StatusTypeDef 返回值
| 要素 | 说明 |
|---|---|
| C数据类型 | HAL_StatusTypeDef(枚举类型) |
| 格式规范 | 函数返回值,用 == 或 != 比较 |
| 示例值 | HAL_OK(成功)、HAL_ERROR(错误)、HAL_BUSY(忙)、HAL_TIMEOUT(超时) |
| 用法示例 | if(HAL_UART_Transmit(&huart1, data, 8, 100) != HAL_OK) { Error_Handler(); } |
| ⚠️ 注意 | 返回 HAL_BUSY 时不要立即重试,应等待外设状态变为 HAL_xxx_STATE_READY |
三、函数使用总则(三步走 + 中断/DMA配套流程)
3.1 任何外设使用三步走
① 时钟使能 → ② 初始化(结构体配置) → ③ 启动操作(轮询/中断/DMA)
3.2 中断/DMA模式固定配套流程
启动函数(如 HAL_UART_Receive_IT)
↓
硬件触发中断
↓
ISR自动调用公共处理函数(如 HAL_UART_IRQHandler) ← CubeMX自动生成,无需手动修改
↓
公共处理函数自动调用用户重写的回调函数(如 HAL_UART_RxCpltCallback) ← 用户重写
3.3 开发者只需做两件事
- 在正确位置调用启动函数(如
HAL_UART_Receive_IT) - 重写对应的回调函数(如
HAL_UART_RxCpltCallback)
中间链路(ISR → 公共处理函数)由CubeMX自动生成,无需手动编写或修改。
四、CubeMX代码保护机制:USER CODE区域详解
CubeMX在重新生成代码时,只会保留 /* USER CODE BEGIN XXX */ 与 /* USER CODE END XXX */ 标记之间的内容,标记之外的代码会被覆盖删除。
4.1 main.c中的安全编码区域一览
| 标记区域 | 位置 | 用途 | 示例代码 |
|---|---|---|---|
USER CODE BEGIN Includes |
头部include区域 | 自定义头文件引用 | #include "my_driver.h" |
USER CODE BEGIN PV |
头部include之后 | 定义全局变量、缓冲区、标志位 | uint8_t rx_buf[256]; volatile uint8_t rx_flag = 0; |
USER CODE BEGIN PFP |
PV之后 | 自定义函数的原型声明 | void Process_Data(void); |
USER CODE BEGIN 0 |
main函数之前 | 自定义函数实现(非回调) | void Process_Data(void){...} |
USER CODE BEGIN 1 |
main函数内部开头 | 初始化前的准备工作 | 极少使用 |
USER CODE BEGIN 2 |
外设Init之后、while之前 | 启动函数调用(开启中断/DMA/PWM) | HAL_UART_Receive_IT(&huart1, &rx_byte, 1); |
USER CODE BEGIN WHILE |
while(1)之前 | while循环前的一次性操作 | 极少使用 |
USER CODE BEGIN 3 |
while(1)循环体内 | 主循环业务逻辑(状态机、标志位检测) | if(rx_flag){ Process_Data(); } |
USER CODE BEGIN 4 |
main函数之后 | 重写回调函数、自定义函数实体 | void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){...} |
4.2 代码保护规则总结
| 规则 | 说明 |
|---|---|
| ✅ 放在标记之间 | 代码被保留,CubeMX重新生成不会删除 |
| ❌ 放在标记之外 | 代码被覆盖删除,CubeMX重新生成后丢失 |
| ❌ 修改stm32f1xx_it.c | 中断服务文件由CubeMX管理,严禁手动修改 |
| ❌ 修改MX_xxx_Init()函数 | 初始化函数由CubeMX生成,严禁手动修改 |
| ❌ 修改stm32f1xx_hal_msp.c | MSP底层配置由CubeMX管理,严禁手动修改 |
五、核心外设函数详解与调用顺序
5.1 系统与时钟控制
函数列表
| 函数名称 | 功能说明 | 调用时机 |
|---|---|---|
HAL_Init() |
初始化HAL库,配置SysTick | 必须在main首行调用 |
HAL_Delay() |
毫秒级阻塞延时(基于SysTick) | 任意位置(严禁在中断回调中调用) |
HAL_GetTick() |
获取系统运行时间(ms) | 任意位置 |
HAL_SuspendTick() |
挂起SysTick中断 | 进入低功耗前 |
HAL_ResumeTick() |
恢复SysTick中断 | 唤醒后 |
参数详解
HAL_Delay(uint32_t Delay)
| 参数名 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
Delay |
uint32_t |
直接传入毫秒整数 | 100(延时100ms)、500(延时500ms)、1000(延时1秒)、10(延时10ms) |
基于SysTick的阻塞延时,绝对禁止在中断回调或DMA回调中使用,否则会导致死锁 |
HAL_GetTick() — 无参数,返回 uint32_t
| 返回值 | C数据类型 | 格式规范 | 示例 | 说明 |
|---|---|---|---|---|
| 返回值 | uint32_t |
直接赋值给变量 | uint32_t now = HAL_GetTick(); |
自HAL_Init()以来的毫秒计数,可用于非阻塞延时 |
HAL_Init()、HAL_SuspendTick()、HAL_ResumeTick() — 无参数
调用顺序
HAL_Init() → SystemClock_Config() → HAL_Delay() / HAL_GetTick()
① ② ③
代码实例
int main(void)
{
HAL_Init(); // ① 必须第一行
SystemClock_Config(); // ② 时钟配置
HAL_Delay(100); // ③ 延时100ms(任意位置)
uint32_t now = HAL_GetTick(); // ④ 获取运行时间,如 now=100
}
非阻塞延时技巧(替代HAL_Delay)
/* USER CODE BEGIN PV */
uint32_t last_tick = 0; // 记录上次时间戳,类型uint32_t,示例0
/* USER CODE END PV */
/* USER CODE BEGIN 3 */
// 非阻塞延时:不卡死主循环
if (HAL_GetTick() - last_tick >= 500) { // 500ms间隔,传入整数500
last_tick = HAL_GetTick();
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5); // 每500ms翻转LED
}
/* USER CODE END 3 */
5.2 GPIO(通用输入输出)
函数列表
| 函数名称 | 功能说明 |
|---|---|
HAL_GPIO_Init() |
初始化GPIO引脚 |
HAL_GPIO_WritePin() |
设置引脚高低电平 |
HAL_GPIO_ReadPin() |
读取引脚电平状态 |
HAL_GPIO_TogglePin() |
翻转引脚电平 |
HAL_GPIO_EXTI_Callback() |
用户重写的外部中断回调 |
参数详解
HAL_GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_Init)
| 参数名 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
GPIOx |
GPIO_TypeDef* |
预定义端口基地址宏,直接使用 | GPIOA、GPIOB、GPIOC、GPIOD |
目标GPIO端口,不加&,它本身就是指针 |
GPIO_Init |
GPIO_InitTypeDef* |
取结构体变量的地址,加 & |
&GPIO_InitStruct |
GPIO配置结构体指针,需先填充结构体再传入 |
HAL_GPIO_WritePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState)
| 参数名 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
GPIOx |
GPIO_TypeDef* |
预定义端口宏 | GPIOA、GPIOB |
目标端口 |
GPIO_Pin |
uint16_t |
预定义引脚掩码宏 | GPIO_PIN_5(PA5)、GPIO_PIN_13(PB13)、`GPIO_PIN_0 |
GPIO_PIN_1`(组合) |
PinState |
GPIO_PinState |
预定义枚举值 | GPIO_PIN_SET(高电平=1)、GPIO_PIN_RESET(低电平=0) |
设置的电平状态 |
HAL_GPIO_ReadPin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
| 参数名 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
GPIOx |
GPIO_TypeDef* |
预定义端口宏 | GPIOA、GPIOB |
目标端口 |
GPIO_Pin |
uint16_t |
预定义引脚掩码宏 | GPIO_PIN_13 |
要读取的引脚 |
| 返回值 | GPIO_PinState |
与枚举值比较 | GPIO_PIN_SET、GPIO_PIN_RESET |
返回当前电平状态 |
HAL_GPIO_TogglePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
| 参数名 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
GPIOx |
GPIO_TypeDef* |
预定义端口宏 | GPIOA |
目标端口 |
GPIO_Pin |
uint16_t |
预定义引脚掩码宏 | GPIO_PIN_5 |
要翻转的引脚 |
HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
| 参数名 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
GPIO_Pin |
uint16_t |
由HAL库自动传入,用 == 比较 |
GPIO_PIN_13(判断是否是PB13触发) |
中断触发时HAL自动传入引脚号,用于区分不同按键 |
GPIO_InitTypeDef 结构体参数详解
| 成员 | C数据类型 | 格式规范 | 可选值(示例) | 说明 |
|---|---|---|---|---|
Pin |
uint32_t |
预定义宏,多引脚用 ` | ` 组合 | GPIO_PIN_0、GPIO_PIN_5、GPIO_PIN_13、`GPIO_PIN_0 |
Mode |
uint32_t |
预定义模式宏 | GPIO_MODE_OUTPUT_PP(推挽输出)、GPIO_MODE_OUTPUT_OD(开漏输出)、GPIO_MODE_INPUT(浮空输入)、GPIO_MODE_ANALOG(模拟)、GPIO_MODE_IT_RISING(上升沿中断)、GPIO_MODE_IT_FALLING(下降沿中断)、GPIO_MODE_IT_RISING_FALLING(双边沿)、GPIO_MODE_AF_PP(复用推挽)、GPIO_MODE_AF_OD(复用开漏) |
工作模式 |
Pull |
uint32_t |
预定义上下拉宏 | GPIO_NOPULL(无上下拉)、GPIO_PULLUP(上拉)、GPIO_PULLDOWN(下拉) |
上下拉电阻配置 |
Speed |
uint32_t |
预定义速度宏 | GPIO_SPEED_FREQ_LOW(低速2MHz)、GPIO_SPEED_FREQ_MEDIUM(中速25MHz)、GPIO_SPEED_FREQ_HIGH(高速50MHz) |
输出驱动速度(仅输出模式有效) |
结构体填充示例
GPIO_InitTypeDef GPIO_InitStruct = {0}; // 必须先清零!
GPIO_InitStruct.Pin = GPIO_PIN_13; // PB13引脚
GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING; // 下降沿中断
GPIO_InitStruct.Pull = GPIO_PULLUP; // 上拉(按键默认高电平)
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; // 低速即可
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); // 初始化GPIOB端口
调用顺序
① 使能端口时钟(__HAL_RCC_GPIOx_CLK_ENABLE)
↓
② 填充 GPIO_InitTypeDef 结构体
↓
③ 调用 HAL_GPIO_Init(GPIOx, &GPIO_InitStruct)
↓
④ 使用 HAL_GPIO_WritePin / ReadPin / TogglePin
↓
(如使用中断)⑤ HAL_NVIC_EnableIRQ(EXTI15_10_IRQn) → ⑥ 重写 HAL_GPIO_EXTI_Callback
完整实例:LED + 按键外部中断
代码放置位置:
| 代码内容 | 放置区域 |
|---|---|
btn_pressed 全局变量 |
USER CODE BEGIN PV |
| GPIO初始化(如非CubeMX配置) | USER CODE BEGIN 2 |
| 按键标志位检测 | USER CODE BEGIN 3(while循环内) |
| 外部中断回调函数 | USER CODE BEGIN 4 |
/* USER CODE BEGIN PV */
volatile uint8_t btn_pressed = 0; // volatile标志位,0=未按下,1=已按下
/* USER CODE END PV */
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init(); // CubeMX已配置GPIO,时钟自动使能
/* USER CODE BEGIN 2 */
// 如果GPIO未在CubeMX中配置,可手动初始化:
__HAL_RCC_GPIOB_CLK_ENABLE(); // 使能GPIOB时钟
GPIO_InitTypeDef GPIO_InitStruct = {0}; // 结构体先清零
GPIO_InitStruct.Pin = GPIO_PIN_13; // PB13引脚
GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING; // 下降沿中断模式
GPIO_InitStruct.Pull = GPIO_PULLUP; // 上拉电阻
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); // 初始化GPIOB
HAL_NVIC_EnableIRQ(EXTI15_10_IRQn); // 使能EXTI15_10中断
/* USER CODE END 2 */
while (1)
{
/* USER CODE BEGIN 3 */
if (btn_pressed) { // 检测标志位
btn_pressed = 0; // 清除标志
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5); // 翻转PA5 LED
}
/* USER CODE END 3 */
}
}
/* USER CODE BEGIN 4 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { // GPIO_Pin由HAL自动传入
if (GPIO_Pin == GPIO_PIN_13) { // 判断是否PB13触发
btn_pressed = 1; // 仅置标志,不做耗时操作
}
}
/* USER CODE END 4 */
⚠️ 回调函数内严禁调用
HAL_Delay()或执行复杂循环,仅做轻量标志或数据搬运。
5.3 UART/USART(串口通信)
函数列表
| 函数名称 | 功能说明 |
|---|---|
HAL_UART_Transmit() |
轮询模式阻塞发送 |
HAL_UART_Receive() |
轮询模式阻塞接收 |
HAL_UART_Transmit_IT() |
中断模式非阻塞发送 |
HAL_UART_Receive_IT() |
开启中断模式接收 |
HAL_UART_Transmit_DMA() |
DMA模式非阻塞发送 |
HAL_UART_Receive_DMA() |
DMA模式非阻塞接收 |
HAL_UART_TxCpltCallback() |
发送完成回调(用户重写) |
HAL_UART_RxCpltCallback() |
接收完成回调(用户重写) |
HAL_UART_ErrorCallback() |
通信错误回调(用户重写) |
参数详解
HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
| 参数名 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
huart |
UART_HandleTypeDef* |
CubeMX生成的句柄变量加 & |
&huart1(串口1)、&huart2(串口2)、&huart3(串口3) |
串口句柄指针,必须加&,变量名由CubeMX生成 |
pData |
uint8_t* |
全局数组名(自动退化为指针)或 & 取单字节变量地址 |
tx_data(数组:uint8_t tx_data[8] = "Hello\r\n";)、&rx_byte(单字节)、"Hello\r\n"(字符串常量) |
发送数据缓冲区,必须为全局变量或static变量 |
Size |
uint16_t |
传入字节数整数 | 8(发送8字节)、1(发送1字节)、strlen(msg)(自动计算)、sizeof(tx_data) |
要发送的字节数,不是缓冲区大小 |
Timeout |
uint32_t |
传入毫秒整数 | 100(100ms)、1000(1秒)、HAL_MAX_DELAY(无限等待) |
仅轮询模式有效,超时返回HAL_TIMEOUT |
HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
| 参数名 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
huart |
UART_HandleTypeDef* |
同Transmit | &huart1 |
串口句柄 |
pData |
uint8_t* |
全局数组名或 & 取单字节地址 |
rx_buf(接收数组)、&rx_byte(接收1字节) |
接收缓冲区,必须全局变量 |
Size |
uint16_t |
传入要接收的字节数 | 1(接收1字节)、8(接收8字节)、256(接收256字节) |
要接收的字节数,必须等于预期数据长度 |
Timeout |
uint32_t |
传入毫秒整数 | 100、HAL_MAX_DELAY |
阻塞等待超时 |
HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
| 参数名 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
huart |
UART_HandleTypeDef* |
句柄加 & |
&huart1 |
串口句柄 |
pData |
uint8_t* |
全局数组名或单字节地址 | tx_buf、&tx_byte |
发送数据指针,无Timeout参数 |
Size |
uint16_t |
字节数整数 | 8、1、sizeof(tx_buf) |
发送字节数 |
HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
| 参数名 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
huart |
UART_HandleTypeDef* |
句柄加 & |
&huart1、&huart2 |
串口句柄 |
pData |
uint8_t* |
全局数组名或 & 单字节地址 |
&rx_byte(接收1字节)、rx_buf(接收多字节到数组) |
接收缓冲区,必须全局变量 |
Size |
uint16_t |
精确接收字节数 | 1(单字节中断接收最常用)、8(固定8字节帧) |
每次中断接收的目标字节数,收满后触发RxCpltCallback |
📌 关键:
Size=1表示每收到1个字节就触发一次回调,这是最常用的串口中断接收方式。
HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
| 参数名 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
huart |
UART_HandleTypeDef* |
句柄加 & |
&huart1 |
串口句柄 |
pData |
uint8_t* |
全局数组名 | tx_buf(uint8_t tx_buf[1024];) |
DMA发送缓冲区 |
Size |
uint16_t |
字节数整数 | 1024、512 |
DMA传输字节数 |
HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
| 参数名 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
huart |
UART_HandleTypeDef* |
句柄加 & |
&huart1 |
串口句柄 |
pData |
uint8_t* |
全局数组名 | rx_buf(uint8_t rx_buf[256];) |
DMA接收缓冲区 |
Size |
uint16_t |
缓冲区大小 | 256 |
DMA接收缓冲区长度 |
回调函数参数:
HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
| 参数名 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
huart |
UART_HandleTypeDef* |
HAL自动传入,用 ->Instance 判断来源 |
huart->Instance == USART1(判断串口1)、huart->Instance == USART2(判断串口2) |
回调由HAL自动调用,参数是触发回调的串口句柄,多串口时需判断 |
HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) — 参数同上
HAL_UART_ErrorCallback(UART_HandleTypeDef *huart) — 参数同上,可通过 huart->ErrorCode 查看错误类型(如 HAL_UART_ERROR_ORE 溢出错误)
调用顺序(中断接收模式)
第1步:CubeMX配置USART1(波特率、引脚、NVIC使能)
↓
第2步:在 USER CODE BEGIN PV 定义接收缓冲区(全局变量)
↓
第3步:在 USER CODE BEGIN 2 调用 HAL_UART_Receive_IT(&huart1, &rx_byte, 1)
↓
第4步:数据到达 → 硬件中断 → HAL_UART_IRQHandler(CubeMX自动生成)
↓
第5步:HAL_UART_IRQHandler 内部自动调用 HAL_UART_RxCpltCallback
↓
第6步:在 USER CODE BEGIN 4 重写 HAL_UART_RxCpltCallback() → 置标志位
↓
第7步:在 USER CODE BEGIN 3 检测标志位,处理数据
↓
第8步:重新调用 HAL_UART_Receive_IT(&huart1, &rx_byte, 1) 以接收下一帧(关键!)
📌 关键点:
HAL_UART_Receive_IT()只启动一次接收,收到指定字节数后自动停止。必须重新调用才能继续接收,否则只能收到一次数据。
完整实例:单字节中断接收 + 回显
/* USER CODE BEGIN PV */
uint8_t rx_byte; // 接收缓冲区(1字节),类型uint8_t,必须全局变量
volatile uint8_t rx_flag = 0; // 接收完成标志,volatile防止编译器优化
/* USER CODE END PV */
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
HAL_UART_Receive_IT(&huart1, &rx_byte, 1); // &huart1=串口1句柄,&rx_byte=接收1字节到rx_byte,1=接收长度1
/* USER CODE END 2 */
while (1)
{
/* USER CODE BEGIN 3 */
if (rx_flag) {
rx_flag = 0;
HAL_UART_Transmit(&huart1, &rx_byte, 1, 100); // 回显1字节,Timeout=100ms
HAL_UART_Receive_IT(&huart1, &rx_byte, 1); // ⚠️ 重新开启接收!
}
/* USER CODE END 3 */
}
}
/* USER CODE BEGIN 4 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { // huart由HAL自动传入
if (huart->Instance == USART1) { // 判断是串口1触发
rx_flag = 1; // 仅置标志,实际处理在主循环
}
}
/* USER CODE END 4 */
5.4 UART空闲中断(不定长接收)—— 扩展API
这是HAL库的扩展API(
HAL_UARTEx_前缀),专为不定长数据接收设计。当发送方停止发送后RX线路空闲超过一帧时间,触发IDLE事件,回调立即响应——无需预知数据长度。
函数列表
| 函数名称 | 功能说明 |
|---|---|
HAL_UARTEx_ReceiveToIdle_IT() |
中断模式空闲中断接收 |
HAL_UARTEx_ReceiveToIdle_DMA() |
DMA模式空闲中断接收 |
HAL_UARTEx_RxEventCallback() |
空闲中断/缓冲区满触发回调(用户重写) |
参数详解
HAL_UARTEx_ReceiveToIdle_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
| 参数名 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
huart |
UART_HandleTypeDef* |
句柄加 & |
&huart1、&huart2 |
串口句柄 |
pData |
uint8_t* |
全局数组名 | rx_buf(uint8_t rx_buf[30];) |
接收缓冲区,必须全局数组 |
Size |
uint16_t |
传入缓冲区最大容量 | 30(缓冲区30字节)、256(缓冲区256字节) |
缓冲区最大容量,不是预期数据长度!实际接收字节数由回调的Size参数返回 |
📌 与标准
HAL_UART_Receive_IT的关键区别:这里的Size是缓冲区最大容量,不是要接收的精确字节数。
HAL_UARTEx_ReceiveToIdle_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
| 参数名 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
huart |
UART_HandleTypeDef* |
句柄加 & |
&huart1 |
串口句柄 |
pData |
uint8_t* |
全局数组名 | rx_buf(uint8_t rx_buf[RX_BUF_SIZE];,RX_BUF_SIZE=256) |
DMA接收缓冲区 |
Size |
uint16_t |
缓冲区最大容量 | RX_BUF_SIZE(=256)、256 |
DMA Circular模式缓冲区长度 |
HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
| 参数名 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
huart |
UART_HandleTypeDef* |
HAL自动传入,用 ->Instance 判断 |
huart->Instance == USART1 |
触发回调的串口句柄 |
Size |
uint16_t |
HAL自动传入,表示实际接收字节数 | 23(收到23字节)、8(收到8字节) |
本次实际接收到的字节数,与标准RxCpltCallback的固定Size不同!DMA Circular模式下Size是累积值 |
与标准API的区别(重要)
| 特性 | HAL_UARTEx_RxEventCallback(扩展) |
HAL_UART_RxCpltCallback(标准) |
|---|---|---|
| 配合函数 | HAL_UARTEx_ReceiveToIdle_IT/DMA |
HAL_UART_Receive_IT/DMA |
| 触发条件 | 缓冲区满 或 IDLE线路检测到 | 仅 缓冲区满 |
| Size含义 | 实际接收到的字节数 | 等于请求的缓冲区大小 |
| 支持变长数据 | ✅ 是 | ❌ 否(必须预知精确长度) |
| 是否需IDLE | ✅ 依赖IDLE检测 | ❌ 不需要 |
IT模式 vs DMA模式
| 特性 | IT模式 | DMA模式 |
|---|---|---|
| 适用场景 | 小帧数据(命令、RS485帧) | 大数据流(固件升级、KB级传输) |
| CPU参与 | CPU处理每个字节中断 | DMA搬运,CPU仅在事件时唤醒 |
| 是否需重新使能 | ✅ 必须在回调后重新调用 HAL_UARTEx_ReceiveToIdle_IT |
❌ Circular模式自动回绕,无需重新调用 |
| DMA配置 | 不需要 | 必须设为 Circular模式 |
IT模式完整实例
/* USER CODE BEGIN PV */
uint8_t rx_buf[30]; // 接收缓冲区数组,大小30字节
volatile uint16_t rx_len = 0; // 实际接收字节数,类型uint16_t,如23
volatile uint8_t rx_event = 0; // 接收事件标志
/* USER CODE END PV */
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
HAL_UARTEx_ReceiveToIdle_IT(&huart1, rx_buf, 30); // &huart1=串口1,rx_buf=缓冲区数组名(不加&),30=缓冲区容量
/* USER CODE END 2 */
while (1)
{
/* USER CODE BEGIN 3 */
if (rx_event) {
rx_event = 0;
// 处理 rx_buf[0..rx_len-1] 中的不定长数据,rx_len如23表示收到23字节
HAL_UART_Transmit(&huart1, rx_buf, rx_len, 100); // 回显实际收到的字节数
HAL_UARTEx_ReceiveToIdle_IT(&huart1, rx_buf, 30); // ⚠️ 必须重新使能!
}
/* USER CODE END 3 */
}
}
/* USER CODE BEGIN 4 */
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size) {
if (huart->Instance == USART1) { // 判断串口1
rx_len = Size; // Size=本次实际接收字节数,如23
rx_event = 1;
}
}
/* USER CODE END 4 */
DMA模式完整实例
/* USER CODE BEGIN PV */
#define RX_BUF_SIZE 256 // 缓冲区大小宏定义,256字节
uint8_t rx_buf[RX_BUF_SIZE]; // DMA接收缓冲区
volatile uint16_t rx_len = 0; // 实际接收字节数
volatile uint8_t rx_event = 0; // 接收事件标志
/* USER CODE END PV */
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_DMA_Init(); // ⚠️ DMA初始化必须在UART初始化之前!
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
HAL_UARTEx_ReceiveToIdle_DMA(&huart1, rx_buf, RX_BUF_SIZE); // Circular模式,RX_BUF_SIZE=256
/* USER CODE END 2 */
while (1)
{
/* USER CODE BEGIN 3 */
if (rx_event) {
rx_event = 0;
// 处理不定长数据...
// DMA Circular模式:无需重新调用!硬件自动回绕
}
/* USER CODE END 3 */
}
}
/* USER CODE BEGIN 4 */
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size) {
if (huart->Instance == USART1) {
rx_len = Size; // DMA Circular模式:Size为累积写入位置
rx_event = 1;
}
}
/* USER CODE END 4 */
📌 DMA Circular模式:Size是累积写入位置,非单次字节数。如连续收到3个23字节块,回调Size分别为23、46、69。计算单次长度需减去上一次Size值。
5.5 定时器(TIM)
函数列表
| 函数名称 | 功能说明 |
|---|---|
HAL_TIM_Base_Start() |
启动定时器(轮询模式) |
HAL_TIM_Base_Start_IT() |
启动带中断的定时器 |
HAL_TIM_PWM_Start() |
启动PWM输出 |
HAL_TIM_IC_Start_IT() |
启动输入捕获中断 |
__HAL_TIM_SET_COMPARE() |
宏函数,动态设置PWM占空比 |
__HAL_TIM_SET_AUTORELOAD() |
宏函数,动态修改ARR(周期) |
__HAL_TIM_GET_COUNTER() |
宏函数,读取当前计数值 |
HAL_TIM_PeriodElapsedCallback() |
周期溢出回调(用户重写) |
HAL_TIM_IC_CaptureCallback() |
输入捕获回调(用户重写) |
参数详解
HAL_TIM_Base_Start(TIM_HandleTypeDef *htim)
| 参数名 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
htim |
TIM_HandleTypeDef* |
CubeMX生成的句柄加 & |
&htim2(定时器2)、&htim3(定时器3)、&htim4(定时器4) |
定时器句柄指针,必须加& |
HAL_TIM_Base_Start_IT(TIM_HandleTypeDef *htim)
| 参数名 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
htim |
TIM_HandleTypeDef* |
句柄加 & |
&htim2 |
定时器句柄,开启中断模式 |
HAL_TIM_PWM_Start(TIM_HandleTypeDef *htim, uint32_t Channel)
| 参数名 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
htim |
TIM_HandleTypeDef* |
句柄加 & |
&htim3(TIM3输出PWM) |
定时器句柄 |
Channel |
uint32_t |
预定义通道宏 | TIM_CHANNEL_1(通道1)、TIM_CHANNEL_2(通道2)、TIM_CHANNEL_3(通道3)、TIM_CHANNEL_4(通道4) |
PWM输出通道号,与CubeMX配置的引脚对应 |
HAL_TIM_IC_Start_IT(TIM_HandleTypeDef *htim, uint32_t Channel)
| 参数名 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
htim |
TIM_HandleTypeDef* |
句柄加 & |
&htim2 |
定时器句柄 |
Channel |
uint32_t |
预定义通道宏 | TIM_CHANNEL_1 |
输入捕获通道 |
__HAL_TIM_SET_COMPARE(__HANDLE__, __CHANNEL__, __COMPARE__) — 宏函数
| 参数名 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
__HANDLE__ |
TIM_HandleTypeDef* |
句柄加 & |
&htim3 |
定时器句柄 |
__CHANNEL__ |
uint32_t |
预定义通道宏 | TIM_CHANNEL_1 |
目标通道 |
__COMPARE__ |
uint32_t |
直接传入CCR值整数 | 500(CCR=500)、pwm_duty(变量) |
比较值(CCR),决定占空比:占空比 = CCR/(ARR+1) |
__HAL_TIM_SET_AUTORELOAD(__HANDLE__, __AUTORELOAD__) — 宏函数
| 参数名 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
__HANDLE__ |
TIM_HandleTypeDef* |
句柄加 & |
&htim3 |
定时器句柄 |
__AUTORELOAD__ |
uint32_t |
直接传入ARR值整数 | 999(ARR=999)、1999(ARR=1999) |
自动重载值,决定PWM周期:频率=时钟/((PSC+1)*(ARR+1)) |
__HAL_TIM_GET_COUNTER(__HANDLE__) — 宏函数
| 参数名 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
__HANDLE__ |
TIM_HandleTypeDef* |
句柄加 & |
&htim2 |
定时器句柄 |
| 返回值 | uint32_t |
直接赋值给变量 | uint32_t cnt = __HAL_TIM_GET_COUNTER(&htim2); |
当前计数值 |
回调函数参数:
HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
| 参数名 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
htim |
TIM_HandleTypeDef* |
HAL自动传入,用 ->Instance 判断 |
htim->Instance == TIM2(判断TIM2溢出)、htim->Instance == TIM3 |
定时器溢出时HAL自动传入句柄,多定时器时需判断 |
HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
| 参数名 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
htim |
TIM_HandleTypeDef* |
HAL自动传入,用 ->Instance 判断 |
htim->Instance == TIM2 |
输入捕获事件触发时HAL自动传入 |
PWM频率与占空比计算
PWM频率 = TIM时钟 / ((PSC + 1) × (ARR + 1))
占空比 = CCR / (ARR + 1) (CCR = Compare值)
例:72MHz时钟,PSC=71,ARR=999 → 频率=72MHz/(72×1000)=1kHz,CCR=500时占空比50%
完整实例:PWM呼吸灯 + 定时器中断
/* USER CODE BEGIN PV */
uint16_t pwm_duty = 0; // CCR值,范围0~999,类型uint16_t
uint8_t pwm_dir = 1; // 0=下降,1=上升
/* USER CODE END PV */
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_TIM2_Init();
MX_TIM3_Init();
/* USER CODE BEGIN 2 */
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1); // &htim3=TIM3句柄,TIM_CHANNEL_1=通道1
HAL_TIM_Base_Start_IT(&htim2); // &htim2=TIM2句柄,开启定时器中断
/* USER CODE END 2 */
while (1) { /* USER CODE BEGIN 3 */ /* USER CODE END 3 */ }
}
/* USER CODE BEGIN 4 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { // htim由HAL自动传入
if (htim->Instance == TIM2) { // 判断TIM2溢出
if (pwm_dir) { pwm_duty += 10; if (pwm_duty >= 1000) { pwm_duty = 1000; pwm_dir = 0; } }
else { pwm_duty -= 10; if (pwm_duty <= 0) { pwm_duty = 0; pwm_dir = 1; } }
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, pwm_duty); // &htim3=TIM3,TIM_CHANNEL_1=通道1,pwm_duty=CCR值
}
}
/* USER CODE END 4 */
5.6 ADC(模数转换)
函数列表
| 函数名称 | 功能说明 |
|---|---|
HAL_ADC_Start() |
启动ADC(轮询模式) |
HAL_ADC_PollForConversion() |
等待转换完成 |
HAL_ADC_GetValue() |
读取转换结果 |
HAL_ADC_Start_DMA() |
启动DMA连续采样 |
HAL_ADC_Stop_DMA() |
停止DMA |
HAL_ADC_ConvCpltCallback() |
转换完成回调 |
HAL_ADC_ConvHalfCpltCallback() |
半转换完成回调(双缓冲用) |
参数详解
HAL_ADC_Start(ADC_HandleTypeDef *hadc)
| 参数名 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
hadc |
ADC_HandleTypeDef* |
句柄加 & |
&hadc1(ADC1)、&hadc2(ADC2) |
ADC句柄指针 |
HAL_ADC_PollForConversion(ADC_HandleTypeDef *hadc, uint32_t Timeout)
| 参数名 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
hadc |
ADC_HandleTypeDef* |
句柄加 & |
&hadc1 |
ADC句柄 |
Timeout |
uint32_t |
传入毫秒整数 | 100(100ms)、HAL_MAX_DELAY |
阻塞等待转换完成的超时 |
HAL_ADC_GetValue(ADC_HandleTypeDef *hadc)
| 参数名 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
hadc |
ADC_HandleTypeDef* |
句柄加 & |
&hadc1 |
ADC句柄 |
| 返回值 | uint32_t |
直接赋值给变量 | uint32_t val = HAL_ADC_GetValue(&hadc1);(val范围0~4095) |
12位ADC返回0~4095,需转换电压:V=(val/4095.0)*3.3 |
HAL_ADC_Start_DMA(ADC_HandleTypeDef *hadc, uint32_t *pData, uint32_t Length)
| 参数名 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
hadc |
ADC_HandleTypeDef* |
句柄加 & |
&hadc1 |
ADC句柄 |
pData |
uint32_t* |
强转后的数组指针 | (uint32_t*)adc_buf(uint16_t adc_buf[32];需强转) |
DMA目标缓冲区,ADC DMA要求uint32_t指针,实际数据是uint16_t |
Length |
uint32_t |
采样次数整数 | 32(采32次)、ADC_SAMPLES(宏定义) |
DMA传输长度=采样次数,不是字节数 |
📌 注意
pData的强转:adc_buf是uint16_t数组,但函数参数要求uint32_t*,需用(uint32_t*)adc_buf强转。
HAL_ADC_Stop_DMA(ADC_HandleTypeDef *hadc)
| 参数名 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
hadc |
ADC_HandleTypeDef* |
句柄加 & |
&hadc1 |
ADC句柄 |
回调函数参数:
HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
| 参数名 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
hadc |
ADC_HandleTypeDef* |
HAL自动传入 | hadc->Instance == ADC1 |
判断哪个ADC完成转换 |
HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef *hadc) — 参数同上,在DMA传输完成一半时触发(双缓冲处理用)
ADC值转电压公式
V = (ADC_Value / 4095.0) × VREF (12位ADC,VREF通常3.3V)
例:ADC_Value=2048 → V = (2048/4095.0) × 3.3 ≈ 1.65V
完整实例:DMA连续采样求平均
/* USER CODE BEGIN PV */
#define ADC_SAMPLES 32 // 采样次数宏定义,32次
uint16_t adc_buf[ADC_SAMPLES]; // ADC缓冲区,类型uint16_t,大小32
volatile uint8_t adc_ready = 0; // 转换完成标志
/* USER CODE END PV */
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_DMA_Init();
MX_ADC1_Init();
/* USER CODE BEGIN 2 */
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buf, ADC_SAMPLES); // &hadc1=ADC1句柄,(uint32_t*)adc_buf=强转指针,ADC_SAMPLES=32次
/* USER CODE END 2 */
while (1)
{
/* USER CODE BEGIN 3 */
if (adc_ready) {
adc_ready = 0;
uint32_t sum = 0;
for (int i = 0; i < ADC_SAMPLES; i++) sum += adc_buf[i];
uint16_t avg = sum / ADC_SAMPLES; // 求平均值,如 avg=2048
float voltage = (avg / 4095.0f) * 3.3f; // 转电压,如 1.65V
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buf, ADC_SAMPLES); // 重新启动DMA采样
}
/* USER CODE END 3 */
}
}
/* USER CODE BEGIN 4 */
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) { // hadc由HAL自动传入
if (hadc->Instance == ADC1) { adc_ready = 1; } // 判断ADC1完成
}
/* USER CODE END 4 */
5.7 DAC(数模转换)
函数列表
| 函数名称 | 功能说明 |
|---|---|
HAL_DAC_Start() |
启动DAC通道输出 |
HAL_DAC_SetValue() |
设置DAC输出值 |
HAL_DAC_Start_DMA() |
DMA模式连续波形输出 |
参数详解
HAL_DAC_Start(DAC_HandleTypeDef *hdac, uint32_t Channel)
| 参数名 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
hdac |
DAC_HandleTypeDef* |
句柄加 & |
&hdac(DAC句柄,通常只有一个DAC) |
DAC句柄指针 |
Channel |
uint32_t |
预定义通道宏 | DAC_CHANNEL_1(通道1)、DAC_CHANNEL_2(通道2,双通道DAC才有) |
DAC输出通道 |
HAL_DAC_SetValue(DAC_HandleTypeDef *hdac, uint32_t Channel, uint32_t Alignment, uint32_t Data)
| 参数名 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
hdac |
DAC_HandleTypeDef* |
句柄加 & |
&hdac |
DAC句柄 |
Channel |
uint32_t |
预定义通道宏 | DAC_CHANNEL_1 |
DAC通道 |
Alignment |
uint32_t |
预定义对齐宏 | DAC_ALIGN_12B_R(12位右对齐,最常用)、DAC_ALIGN_12B_L(12位左对齐)、DAC_ALIGN_8B_R(8位右对齐) |
数据对齐方式,影响Data的解读 |
Data |
uint32_t |
传入数字值整数,范围由对齐方式决定 | 0(0V输出)、2048(≈1.65V)、4095(≈3.3V)、dac_setval(变量) |
DAC输出数字值,12位右对齐时范围0~4095 |
HAL_DAC_Start_DMA(DAC_HandleTypeDef *hdac, uint32_t Channel, uint32_t *pData, uint32_t Length, uint32_t Alignment)
| 参数名 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
hdac |
DAC_HandleTypeDef* |
句柄加 & |
&hdac |
DAC句柄 |
Channel |
uint32_t |
预定义通道宏 | DAC_CHANNEL_1 |
DAC通道 |
pData |
uint32_t* |
全局uint32_t数组名 | dac_wave(uint32_t dac_wave[100];) |
DMA波形数据数组 |
Length |
uint32_t |
波形点数整数 | 100(100个点)、256(256个点) |
DMA传输长度=波形点数 |
Alignment |
uint32_t |
预定义对齐宏 | DAC_ALIGN_12B_R |
数据对齐方式 |
输出电压计算
Vout = (Data / 4095.0) × VREF (12位右对齐,VREF通常3.3V)
例:Data=2048 → Vout = (2048/4095.0) × 3.3 ≈ 1.65V
| Data值 | 输出电压 | 说明 |
|---|---|---|
0 |
0V | 最小输出 |
2048 |
≈1.65V | 中间值 |
4095 |
≈3.3V | 最大输出 |
完整实例:锯齿波渐变输出
/* USER CODE BEGIN PV */
uint16_t dac_setval = 0; // DAC设定值,类型uint16_t,范围0~4095
uint8_t dac_dir = 0; // 0=上升,1=下降
/* USER CODE END PV */
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_DAC_Init();
/* USER CODE BEGIN 2 */
HAL_DAC_Start(&hdac, DAC_CHANNEL_1); // &hdac=DAC句柄,DAC_CHANNEL_1=通道1
HAL_DAC_SetValue(&hdac, DAC_CHANNEL_1, DAC_ALIGN_12B_R, 0); // DAC_ALIGN_12B_R=12位右对齐,0=初始0V
/* USER CODE END 2 */
while (1)
{
/* USER CODE BEGIN 3 */
if (dac_dir == 0) { dac_setval++; if (dac_setval >= 4095) { dac_setval = 4095; dac_dir = 1; } }
else { dac_setval--; if (dac_setval <= 0) { dac_setval = 0; dac_dir = 0; } }
HAL_DAC_SetValue(&hdac, DAC_CHANNEL_1, DAC_ALIGN_12B_R, dac_setval); // dac_setval=当前设定值
HAL_Delay(1); // 1ms间隔,让锯齿波可见
/* USER CODE END 3 */
}
}
5.8 SPI(同步串行通信)
函数列表
| 函数名称 | 功能说明 |
|---|---|
HAL_SPI_TransmitReceive() |
全双工收发(轮询) |
HAL_SPI_TransmitReceive_IT() |
中断模式全双工 |
HAL_SPI_TransmitReceive_DMA() |
DMA模式全双工 |
参数详解
HAL_SPI_TransmitReceive(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData, uint16_t Size, uint32_t Timeout)
| 参数名 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
hspi |
SPI_HandleTypeDef* |
句柄加 & |
&hspi1(SPI1)、&hspi2(SPI2)、&hspi3(SPI3) |
SPI句柄指针 |
pTxData |
uint8_t* |
全局数组名或常量数组 | spi_tx(uint8_t spi_tx[4] = {0x03,0x00,0x00,0x00};)、dummy_tx(dummy字节数组 {0xFF,0xFF,...}) |
发送数据指针。只读时发送dummy字节产生时钟 |
pRxData |
uint8_t* |
全局数组名 | spi_rx(uint8_t spi_rx[4];) |
接收数据缓冲区 |
Size |
uint16_t |
收发字节数整数 | 4(4字节)、8(8字节) |
收发字节数,Tx和Rx长度必须相同 |
Timeout |
uint32_t |
传入毫秒整数 | 100、1000、HAL_MAX_DELAY |
超时时间(仅轮询模式) |
HAL_SPI_TransmitReceive_IT(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData, uint16_t Size)
| 参数名 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
hspi |
SPI_HandleTypeDef* |
句柄加 & |
&hspi1 |
SPI句柄 |
pTxData |
uint8_t* |
全局数组名 | spi_tx |
发送数据(无Timeout参数) |
pRxData |
uint8_t* |
全局数组名 | spi_rx |
接收缓冲区 |
Size |
uint16_t |
字节数整数 | 4 |
收发字节数 |
HAL_SPI_TransmitReceive_DMA(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData, uint16_t Size)
| 参数名 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
hspi |
SPI_HandleTypeDef* |
句柄加 & |
&hspi1 |
SPI句柄 |
pTxData |
uint8_t* |
全局数组名 | spi_tx |
DMA发送数据 |
pRxData |
uint8_t* |
全局数组名 | spi_rx |
DMA接收缓冲区 |
Size |
uint16_t |
字节数整数 | 256 |
DMA传输字节数 |
完整实例:SPI Flash读取
/* USER CODE BEGIN PV */
uint8_t spi_tx[4] = {0x03, 0x00, 0x00, 0x00}; // W25Q64读取命令+3字节地址
uint8_t spi_rx[4] = {0}; // 接收缓冲区
/* USER CODE END PV */
/* USER CODE BEGIN 3 */
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); // GPIOA=端口A,GPIO_PIN_4=PA4片选,GPIO_PIN_RESET=低电平(选中)
HAL_SPI_TransmitReceive(&hspi1, spi_tx, spi_rx, 4, 100); // &hspi1=SPI1句柄,spi_tx=发送数据,spi_rx=接收缓冲区,4=4字节,100=100ms超时
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); // GPIO_PIN_SET=高电平(释放片选)
/* USER CODE END 3 */
📌 SPI全双工:即使只想读取数据,也必须发送dummy字节来产生时钟。如只读:
spi_tx={0xFF,0xFF,...}(dummy数据)。
5.9 I2C(双向两线总线)
函数列表
| 函数名称 | 功能说明 |
|---|---|
HAL_I2C_Mem_Read() |
读外设寄存器 |
HAL_I2C_Mem_Write() |
写外设寄存器 |
HAL_I2C_Master_Transmit() |
主机发送(无寄存器地址) |
HAL_I2C_Master_Receive() |
主机接收(无寄存器地址) |
参数详解
HAL_I2C_Mem_Read(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout)
| 参数名 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
hi2c |
I2C_HandleTypeDef* |
句柄加 & |
&hi2c1(I2C1)、&hi2c2(I2C2) |
I2C句柄指针 |
DevAddress |
uint16_t |
7位地址左移1位的8位格式 | 0xD0(MPU6050,7位0x68左移:0x68<<1)、0xA0(AT24C02,7位0x50左移:0x50<<1)、0x3C(SSD1306,7位0x1C左移) |
设备地址,HAL使用8位格式,必须左移! |
MemAddress |
uint16_t |
传入寄存器地址整数 | 0x3B(MPU6050加速度X高位)、0x00(AT24C02首地址)、0x75(WHO_AM_I寄存器) |
目标寄存器/内存地址 |
MemAddSize |
uint16_t |
预定义地址宽度宏 | I2C_MEMADD_SIZE_8BIT(8位地址,如MPU6050)、I2C_MEMADD_SIZE_16BIT(16位地址,如AT24C256) |
寄存器地址位数 |
pData |
uint8_t* |
全局数组名 | i2c_data(uint8_t i2c_data[6];) |
接收数据缓冲区 |
Size |
uint16_t |
读取字节数整数 | 6(读6字节)、1(读1字节)、2(读2字节) |
要读取的字节数 |
Timeout |
uint32_t |
传入毫秒整数 | 100、1000 |
I2C超时,建议设较长(I2C通信较慢) |
HAL_I2C_Mem_Write(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout)
| 参数名 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
hi2c |
I2C_HandleTypeDef* |
句柄加 & |
&hi2c1 |
I2C句柄 |
DevAddress |
uint16_t |
7位地址左移1位 | 0xA0(0x50<<1) |
设备地址 |
MemAddress |
uint16_t |
寄存器地址整数 | 0x00 |
写入目标寄存器地址 |
MemAddSize |
uint16_t |
预定义宏 | I2C_MEMADD_SIZE_8BIT |
地址位数 |
pData |
uint8_t* |
全局数组名 | write_data |
写入数据指针 |
Size |
uint16_t |
写入字节数 | 1、4 |
要写入的字节数 |
Timeout |
uint32_t |
毫秒整数 | 100 |
超时 |
📌 I2C地址注意:HAL库使用8位地址格式。7位地址需左移1位。例如MPU6050的7位地址
0x68,传入时为0x68 << 1 = 0xD0。直接传0x68会导致通信失败!
完整实例:读取MPU6050加速度数据
/* USER CODE BEGIN PV */
uint8_t i2c_data[6]; // 6字节接收缓冲区
/* USER CODE END PV */
/* USER CODE BEGIN 3 */
// 读取MPU6050加速度X/Y/Z(寄存器0x3B起,6字节连续)
HAL_I2C_Mem_Read(&hi2c1, 0x68<<1, 0x3B, I2C_MEMADD_SIZE_8BIT, i2c_data, 6, 100);
// &hi2c1=I2C1句柄,0x68<<1=0xD0=MPU6050设备地址,0x3B=起始寄存器,
// I2C_MEMADD_SIZE_8BIT=8位地址宽度,i2c_data=接收数组,6=读6字节,100=100ms超时
int16_t accel_x = (int16_t)((i2c_data[0] << 8) | i2c_data[1]); // 高8位+低8位合成16位
/* USER CODE END 3 */
5.10 CAN总线
函数列表
| 函数名称 | 功能说明 |
|---|---|
HAL_CAN_Init() |
CAN控制器初始化 |
HAL_CAN_ConfigFilter() |
配置CAN过滤器 |
HAL_CAN_Start() |
启动CAN控制器 |
HAL_CAN_ActivateNotification() |
使能CAN中断通知 |
HAL_CAN_AddTxMessage() |
发送CAN报文 |
HAL_CAN_GetRxMessage() |
接收CAN报文 |
HAL_CAN_RxFifo0MsgPendingCallback() |
FIFO0消息挂起回调 |
参数详解
HAL_CAN_Init(CAN_HandleTypeDef *hcan)
| 参数名 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
hcan |
CAN_HandleTypeDef* |
句柄加 & |
&hcan1(CAN1)、&hcan2(CAN2,双CAN芯片才有) |
CAN句柄指针 |
HAL_CAN_ConfigFilter(CAN_HandleTypeDef *hcan, CAN_FilterTypeDef *filter)
| 参数名 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
hcan |
CAN_HandleTypeDef* |
句柄加 & |
&hcan1 |
CAN句柄 |
filter |
CAN_FilterTypeDef* |
取结构体变量地址,加 & |
&filter(CAN_FilterTypeDef filter;) |
过滤器配置结构体指针 |
CAN_FilterTypeDef 结构体参数详解:
| 成员 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
FilterBank |
uint32_t |
传入过滤器编号整数 | 0(过滤器0)、1(过滤器1) |
过滤器组编号,F1系列013,F4系列027 |
FilterMode |
uint32_t |
预定义模式宏 | CAN_FILTERMODE_IDMASK(掩码模式)、CAN_FILTERMODE_IDLIST(列表模式) |
过滤模式 |
FilterScale |
uint32_t |
预定义宽度宏 | CAN_FILTERSCALE_32BIT(32位宽)、CAN_FILTERSCALE_16BIT(16位宽) |
过滤器位宽 |
FilterIdHigh |
uint32_t |
传入ID高位整数 | 0x0000 |
过滤ID高16位 |
FilterIdLow |
uint32_t |
传入ID低位整数 | 0x0000 |
过滤ID低16位 |
FilterMaskIdHigh |
uint32_t |
传入掩码高位整数 | 0x0000(接收所有帧) |
掩码高16位 |
FilterMaskIdLow |
uint32_t |
传入掩码低位整数 | 0x0000(接收所有帧) |
掩码低16位 |
FilterFIFOAssignment |
uint32_t |
预定义FIFO宏 | CAN_RX_FIFO0(FIFO0)、CAN_RX_FIFO1(FIFO1) |
匹配报文存入的FIFO |
FilterActivation |
uint32_t |
预定义使能宏 | ENABLE(使能)、DISABLE(禁用) |
过滤器激活状态 |
SlaveStartFilterBank |
uint32_t |
传入起始编号整数 | 14(F1系列默认) |
slave CAN起始过滤器编号 |
HAL_CAN_Start(CAN_HandleTypeDef *hcan)
| 参数名 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
hcan |
CAN_HandleTypeDef* |
句柄加 & |
&hcan1 |
CAN句柄 |
HAL_CAN_ActivateNotification(CAN_HandleTypeDef *hcan, uint32_t ActiveIT)
| 参数名 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
hcan |
CAN_HandleTypeDef* |
句柄加 & |
&hcan1 |
CAN句柄 |
ActiveIT |
uint32_t |
预定义中断类型宏,可 ` | ` 组合 | CAN_IT_RX_FIFO0_MSG_PENDING(FIFO0有消息挂起)、CAN_IT_RX_FIFO1_MSG_PENDING(FIFO1挂起)、CAN_IT_TX_MAILBOX_EMPTY(发送邮箱空) |
HAL_CAN_AddTxMessage(CAN_HandleTypeDef *hcan, CAN_TxHeaderTypeDef *pHeader, uint8_t aData[], uint32_t *pTxMailbox)
| 参数名 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
hcan |
CAN_HandleTypeDef* |
句柄加 & |
&hcan1 |
CAN句柄 |
pHeader |
CAN_TxHeaderTypeDef* |
取结构体变量地址,加 & |
&tx_header(CAN_TxHeaderTypeDef tx_header;) |
发送报文头结构体 |
aData |
uint8_t[] |
全局数组名 | tx_data(uint8_t tx_data[8] = {0x01,...,0x08};) |
报文数据,最多8字节 |
pTxMailbox |
uint32_t* |
取邮箱变量地址,加 & |
&tx_mailbox(uint32_t tx_mailbox;) |
返回使用的发送邮箱编号 |
CAN_TxHeaderTypeDef 发送报文头参数详解:
| 成员 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
StdId |
uint32_t |
传入标准ID整数(11位范围) | 0x123(ID=0x123)、0x7FF(最大标准ID) |
标准标识符 |
ExtId |
uint32_t |
传入扩展ID整数(29位范围) | 0x12345(标准帧不用,填0) |
扩展标识符,标准帧时填 0 |
RTR |
uint32_t |
预定义帧类型宏 | CAN_RTR_DATA(数据帧)、CAN_RTR_REMOTE(远程帧) |
报文类型 |
IDE |
uint32_t |
预定义ID类型宏 | CAN_ID_STD(标准帧,11位ID)、CAN_ID_EXT(扩展帧,29位ID) |
ID扩展标志 |
DLC |
uint32_t |
传入数据长度整数 | 8(8字节,最大值)、4(4字节)、1(1字节) |
数据长度码(0~8) |
HAL_CAN_GetRxMessage(CAN_HandleTypeDef *hcan, uint32_t RxFifo, CAN_RxHeaderTypeDef *pHeader, uint8_t aData[])
| 参数名 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
hcan |
CAN_HandleTypeDef* |
句柄加 & |
&hcan1 |
CAN句柄 |
RxFifo |
uint32_t |
预定义FIFO宏 | CAN_RX_FIFO0、CAN_RX_FIFO1 |
从哪个FIFO读取 |
pHeader |
CAN_RxHeaderTypeDef* |
取结构体地址,加 & |
&rx_header(CAN_RxHeaderTypeDef rx_header;) |
接收报文头(传出参数) |
aData |
uint8_t[] |
全局数组名 | rx_data(uint8_t rx_data[8];) |
接收报文数据(传出参数) |
回调函数参数:
HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
| 参数名 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
hcan |
CAN_HandleTypeDef* |
HAL自动传入 | hcan->Instance == CAN1 |
触发回调的CAN句柄 |
CAN过滤器配置示例
CAN_FilterTypeDef filter; // 过滤器结构体
filter.FilterBank = 0; // 过滤器组0
filter.FilterMode = CAN_FILTERMODE_IDMASK; // 掩码模式
filter.FilterScale = CAN_FILTERSCALE_32BIT; // 32位宽
filter.FilterIdHigh = 0x0000; // ID高位=0
filter.FilterIdLow = 0x0000; // ID低位=0
filter.FilterMaskIdHigh = 0x0000; // 掩码高位=0(接收所有帧)
filter.FilterMaskIdLow = 0x0000; // 掩码低位=0
filter.FilterFIFOAssignment = CAN_RX_FIFO0; // 分配到FIFO0
filter.FilterActivation = ENABLE; // 使能过滤器
filter.SlaveStartFilterBank = 14; // slave从过滤器14开始
HAL_CAN_ConfigFilter(&hcan1, &filter); // &hcan1=CAN1句柄,&filter=过滤器配置
波特率计算
波特率 = APB1时钟 / (Prescaler × (BS1 + BS2 + SJW))
例:500kbps = 36MHz / (8 × (5 + 3 + 1)) = 36MHz / 72
调用顺序
① CubeMX配置CAN(波特率、引脚PA11/PA12、NVIC使能)
↓
② MX_CAN1_Init() ← CubeMX自动生成
↓
③ USER CODE BEGIN 2: HAL_CAN_ConfigFilter(&hcan1, &filter) → HAL_CAN_Start(&hcan1) → HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING)
↓
④ USER CODE BEGIN 4: 重写 HAL_CAN_RxFifo0MsgPendingCallback
↓
⑤ USER CODE BEGIN 3: 填充TxHeader → HAL_CAN_AddTxMessage(&hcan1, &tx_header, tx_data, &tx_mailbox)
完整实例:CAN发送与中断接收
/* USER CODE BEGIN PV */
CAN_TxHeaderTypeDef tx_header; // 发送报文头结构体
CAN_RxHeaderTypeDef rx_header; // 接收报文头结构体
uint8_t tx_data[8] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}; // 发送数据,8字节
uint8_t rx_data[8]; // 接收数据缓冲区,8字节
uint32_t tx_mailbox; // 发送邮箱编号变量
/* USER CODE END PV */
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_CAN1_Init();
/* USER CODE BEGIN 2 */
// CAN过滤器配置 + 启动 + 中断使能(详见上文过滤器配置示例)
CAN_FilterTypeDef filter = {0};
// ... 填充filter结构体 ...
HAL_CAN_ConfigFilter(&hcan1, &filter);
HAL_CAN_Start(&hcan1);
HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING);
/* USER CODE END 2 */
while (1)
{
/* USER CODE BEGIN 3 */
tx_header.StdId = 0x123; // 标准ID=0x123
tx_header.ExtId = 0; // 扩展ID=0(标准帧不用)
tx_header.RTR = CAN_RTR_DATA; // 数据帧
tx_header.IDE = CAN_ID_STD; // 标准帧
tx_header.DLC = 8; // 8字节数据
HAL_CAN_AddTxMessage(&hcan1, &tx_header, tx_data, &tx_mailbox);
HAL_Delay(1000); // 1秒间隔发送
/* USER CODE END 3 */
}
}
/* USER CODE BEGIN 4 */
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) { // hcan由HAL自动传入
if (HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &rx_header, rx_data) == HAL_OK) {
// hcan=CAN句柄,CAN_RX_FIFO0=FIFO0,&rx_header=接收报文头传出,rx_data=数据传出
// 处理接收到的报文:rx_header.StdId = 发送方ID,rx_data[0..7] = 数据内容
}
}
/* USER CODE END 4 */
⚠️ CAN总线两端必须各接一个120Ω终端电阻。
5.11 RTC实时时钟
函数列表
| 函数名称 | 功能说明 |
|---|---|
HAL_RTC_GetTime() |
获取当前时间 |
HAL_RTC_GetDate() |
获取当前日期 |
HAL_RTC_SetTime() |
设置时间 |
HAL_RTC_SetDate() |
设置日期 |
HAL_RTC_SetAlarm_IT() |
设置闹钟中断 |
HAL_RTC_AlarmAEventCallback() |
闹钟A事件回调 |
__HAL_RTC_SECOND_ENABLE_IT() |
使能秒更新中断(宏) |
HAL_RTCEx_RTCEventCallback() |
秒中断回调 |
HAL_RTCEx_SetWakeUpTimer_IT() |
设置RTC唤醒定时器 |
参数详解
HAL_RTC_GetTime(RTC_HandleTypeDef *hrtc, RTC_TimeTypeDef *sTime, uint32_t Format)
| 参数名 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
hrtc |
RTC_HandleTypeDef* |
句柄加 & |
&hrtc(RTC句柄,通常只有一个) |
RTC句柄指针 |
sTime |
RTC_TimeTypeDef* |
取结构体变量地址,加 & |
&sTime(RTC_TimeTypeDef sTime;) |
时间结构体(传出参数),HAL填充后读取 |
Format |
uint32_t |
预定义格式宏 | RTC_FORMAT_BIN(二进制格式,Hours=23就是23)、RTC_FORMAT_BCD(BCD格式,Hours=0x23表示23点) |
时间数据格式,推荐用BIN更直观 |
HAL_RTC_GetDate(RTC_HandleTypeDef *hrtc, RTC_DateTypeDef *sDate, uint32_t Format)
| 参数名 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
hrtc |
RTC_HandleTypeDef* |
句柄加 & |
&hrtc |
RTC句柄 |
sDate |
RTC_DateTypeDef* |
取结构体地址,加 & |
&sDate(RTC_DateTypeDef sDate;) |
日期结构体(传出参数) |
Format |
uint32_t |
预定义格式宏 | RTC_FORMAT_BIN、RTC_FORMAT_BCD |
日期格式 |
⚠️ 重要:读取时间后必须紧接着调用
HAL_RTC_GetDate(),否则时间不会更新(硬件阴影寄存器机制)!
HAL_RTC_SetTime(RTC_HandleTypeDef *hrtc, RTC_TimeTypeDef *sTime, uint32_t Format)
| 参数名 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
hrtc |
RTC_HandleTypeDef* |
句柄加 & |
&hrtc |
RTC句柄 |
sTime |
RTC_TimeTypeDef* |
取已填充结构体地址,加 & |
&sTime(需先填充:sTime.Hours=23; sTime.Minutes=59; sTime.Seconds=0;) |
要设置的时间 |
Format |
uint32_t |
预定义格式宏 | RTC_FORMAT_BIN |
时间格式 |
HAL_RTC_SetAlarm_IT(RTC_HandleTypeDef *hrtc, RTC_AlarmTypeDef *sAlarm, uint32_t Alarm)
| 参数名 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
hrtc |
RTC_HandleTypeDef* |
句柄加 & |
&hrtc |
RTC句柄 |
sAlarm |
RTC_AlarmTypeDef* |
取已填充结构体地址,加 & |
&sAlarm(需先填充闹钟时间和掩码) |
闹钟配置结构体 |
Alarm |
uint32_t |
预定义闹钟宏 | RTC_ALARM_A(闹钟A)、RTC_ALARM_B(闹钟B) |
选择闹钟A或B |
RTC_TimeTypeDef 结构体参数详解:
| 成员 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
Hours |
uint8_t |
BIN格式直接传入整数 | 23(23点)、0(0点)、12(12点) |
小时,BIN范围0~23 |
Minutes |
uint8_t |
BIN格式直接传入整数 | 59(59分)、30(30分) |
分钟,BIN范围0~59 |
Seconds |
uint8_t |
BIN格式直接传入整数 | 0(0秒)、45(45秒) |
秒,BIN范围0~59 |
TimeFormat |
uint32_t |
预定义格式宏 | RTC_HOURFORMAT12_AM、RTC_HOURFORMAT12_PM |
12小时制AM/PM(24小时制不用) |
DayLightSaving |
uint32_t |
预定义宏 | RTC_DAYLIGHTSAVING_SUB1H、RTC_DAYLIGHTSAVING_NONE |
夏令时调整 |
RTC_DateTypeDef 结构体参数详解:
| 成员 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
Year |
uint8_t |
BIN格式传入偏移值 | 26(2026年,从2000年起偏移)、25(2025年) |
年份,099,表示20002099 |
Month |
uint8_t |
预定义月份宏 | RTC_MONTH_JANUARY(1月)、RTC_MONTH_JULY(7月)、RTC_MONTH_DECEMBER(12月) |
月份 |
Date |
uint8_t |
BIN格式直接传入整数 | 8(8日)、1(1日) |
日期,1~31 |
WeekDay |
uint8_t |
预定义星期宏 | RTC_WEEKDAY_MONDAY(周一)、RTC_WEEKDAY_SUNDAY(周日) |
星期 |
__HAL_RTC_SECOND_ENABLE_IT(__HANDLE__, __INTERRUPT__) — 宏函数
| 参数名 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
__HANDLE__ |
RTC_HandleTypeDef* |
句柄加 & |
&hrtc |
RTC句柄 |
__INTERRUPT__ |
uint32_t |
预定义中断类型宏 | RTC_IT_SEC(秒更新中断) |
中断类型 |
HAL_RTCEx_SetWakeUpTimer_IT(RTC_HandleTypeDef *hrtc, uint32_t WakeUpCounter, uint32_t WakeUpClock)
| 参数名 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
hrtc |
RTC_HandleTypeDef* |
句柄加 & |
&hrtc |
RTC句柄 |
WakeUpCounter |
uint32_t |
传入计数值整数 | 4(5秒唤醒:CK_SPRE_16BITS模式下Counter+1=秒数)、0(1秒唤醒) |
唤醒计数器值 |
WakeUpClock |
uint32_t |
预定义时钟源宏 | RTC_WAKEUPCLOCK_CK_SPRE_16BITS(1Hz时钟,Counter+1=秒数)、RTC_WAKEUPCLOCK_RTCCLK_DIV16 |
唤醒定时器时钟源 |
回调函数参数:
HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc) — 参数同其他回调,hrtc由HAL自动传入
HAL_RTCEx_RTCEventCallback(RTC_HandleTypeDef *hrtc) — 秒中断回调,参数同上
HAL_RTCEx_WakeUpTimerEventCallback(RTC_HandleTypeDef *hrtc) — 唤醒定时器回调,参数同上
完整实例:秒中断 + 时间打印
/* USER CODE BEGIN PV */
RTC_TimeTypeDef sTime; // 时间结构体变量
RTC_DateTypeDef sDate; // 日期结构体变量
/* USER CODE END PV */
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_RTC_Init();
/* USER CODE BEGIN 2 */
__HAL_RTC_SECOND_ENABLE_IT(&hrtc, RTC_IT_SEC); // &hrtc=RTC句柄,RTC_IT_SEC=秒中断类型
/* USER CODE END 2 */
while (1) { /* USER CODE BEGIN 3 */ /* USER CODE END 3 */ }
}
/* USER CODE BEGIN 4 */
void HAL_RTCEx_RTCEventCallback(RTC_HandleTypeDef *hrtc) { // hrtc由HAL自动传入
HAL_RTC_GetTime(hrtc, &sTime, RTC_FORMAT_BIN); // &sTime=传出参数,RTC_FORMAT_BIN=二进制格式
HAL_RTC_GetDate(hrtc, &sDate, RTC_FORMAT_BIN); // ⚠️ 必须紧接着调用!
// sTime.Hours=23, sTime.Minutes=59, sTime.Seconds=0 (BIN格式直接可用)
// sDate.Year=26, sDate.Month=RTC_MONTH_JULY, sDate.Date=8
}
/* USER CODE END 4 */
5.12 看门狗(IWDG与WWDG)
IWDG与WWDG核心区别
| 对比项 | IWDG(独立看门狗) | WWDG(窗口看门狗) |
|---|---|---|
| 时钟源 | LSI(32kHz),独立于主时钟 | PCLK1(APB1时钟) |
| 喂狗规则 | 只需在超时前喂狗 | 必须在窗口范围内喂狗(过早或过晚→复位) |
| 早期预警 | 无 | 有EWI(早期唤醒中断) |
| 可靠性 | 主时钟失效时仍能工作 | 主时钟失效则失效 |
| 适用场景 | 防程序死循环/跑飞 | 精确监控代码执行时序 |
IWDG函数参数详解
HAL_IWDG_Init(IWDG_HandleTypeDef *hiwdg)
| 参数名 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
hiwdg |
IWDG_HandleTypeDef* |
句柄加 & |
&hiwdg(IWDG句柄,通常只有一个) |
IWDG句柄,CubeMX已配置参数 |
HAL_IWDG_Refresh(IWDG_HandleTypeDef *hiwdg)
| 参数名 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
hiwdg |
IWDG_HandleTypeDef* |
句柄加 & |
&hiwdg |
IWDG句柄,喂狗重载计数器 |
超时计算:T = (Prescaler × Reload) / LSI频率
例:Prescaler=64, Reload=625, LSI=32kHz → T=(64×625)/32000=1.25秒
WWDG函数参数详解
HAL_WWDG_Init(WWDG_HandleTypeDef *hwwdg)
| 参数名 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
hwwdg |
WWDG_HandleTypeDef* |
句柄加 & |
&hwwdg |
WWDG句柄 |
HAL_WWDG_Refresh(WWDG_HandleTypeDef *hwwdg, uint32_t Counter)
| 参数名 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
hwwdg |
WWDG_HandleTypeDef* |
句柄加 & |
&hwwdg |
WWDG句柄 |
Counter |
uint32_t |
传入7位计数器值整数 | 0x7F(127,最大值)、0x40(64,最小有效值) |
新计数器值,范围0x40~0x7F |
HAL_WWDG_EarlyWakeupCallback(WWDG_HandleTypeDef *hwwdg) — 回调函数,hwwdg由HAL自动传入
完整实例
/* USER CODE BEGIN 2 */
// IWDG由CubeMX配置,MX_IWDG_Init()自动调用
/* USER CODE END 2 */
/* USER CODE BEGIN 3 */
HAL_IWDG_Refresh(&hiwdg); // &hiwdg=IWDG句柄,主循环中定期喂狗
/* USER CODE END 3 */
/* USER CODE BEGIN 4 */
void HAL_WWDG_EarlyWakeupCallback(WWDG_HandleTypeDef *hwwdg) { // hwwdg由HAL自动传入
// 复位前的最后机会:保存关键数据或发送警报
HAL_WWDG_Refresh(&hwwdg, 0x7F); // &hwwdg=WWDG句柄,0x7F=最大计数值(紧急喂狗)
}
/* USER CODE END 4 */
⚠️ 喂狗应在主循环中,不要在独立中断中喂狗(可能掩盖主循环跑飞的故障)。
5.13 FLASH读写擦除
函数列表
| 函数名称 | 功能说明 |
|---|---|
HAL_FLASH_Unlock() |
解锁Flash(擦写前置步骤) |
HAL_FLASHEx_Erase() |
擦除指定页 |
HAL_FLASH_Program() |
写入数据 |
HAL_FLASH_Lock() |
上锁Flash(操作完成后保护) |
参数详解
HAL_FLASH_Unlock() — 无参数
HAL_FLASH_Lock() — 无参数
HAL_FLASHEx_Erase(FLASH_EraseInitTypeDef *pEraseInit, uint32_t *PageError)
| 参数名 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
pEraseInit |
FLASH_EraseInitTypeDef* |
取已填充结构体地址,加 & |
&EraseInitStruct |
擦除配置结构体指针 |
PageError |
uint32_t* |
取变量地址,加 & |
&PageError(uint32_t PageError = 0;) |
传出参数:出错时返回错误页地址,无错时为0xFFFFFFFF |
FLASH_EraseInitTypeDef 结构体参数详解:
| 成员 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
TypeErase |
uint32_t |
预定义擦除类型宏 | FLASH_TYPEERASE_PAGES(按页擦除,F1系列)、FLASH_TYPEERASE_SECTORS(按扇区擦除,F4系列) |
擦除方式 |
PageAddress / NbPages (F1) |
uint32_t |
传入起始地址整数和页数 | 0x08005000(起始地址)、1(擦1页) |
F1系列:起始页地址和页数 |
Banks / Sector / NbSectors (F4) |
uint32_t |
预定义Bank宏和扇区编号 | FLASH_BANK_1、FLASH_SECTOR_5、1 |
F4系列:Bank、扇区号、扇区数 |
HAL_FLASH_Program(uint32_t TypeProgram, uint32_t Address, uint64_t Data)
| 参数名 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
TypeProgram |
uint32_t |
预定义写入类型宏 | FLASH_TYPEPROGRAM_HALFWORD(16位,F1常用)、FLASH_TYPEPROGRAM_WORD(32位)、FLASH_TYPEPROGRAM_DOUBLEWORD(64位) |
写入数据位宽 |
Address |
uint32_t |
传入目标地址整数 | 0x08005000(Flash第5页起始地址)、0x08005002(偏移2字节) |
写入地址,必须对齐(HALFWORD需2字节对齐) |
Data |
uint64_t |
传入要写入的数据 | 0x1234(16位数据)、(uint64_t)led_state |
写入数据,虽类型是uint64_t,实际位宽由TypeProgram决定 |
操作步骤顺序
① HAL_FLASH_Unlock() 解锁(无参数)
↓
② 配置 FLASH_EraseInitTypeDef 结构体
↓
③ HAL_FLASHEx_Erase(&EraseInitStruct, &PageError) 擦除目标页
↓
④ HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, address, data) 写入数据
↓
⑤ HAL_FLASH_Lock() 上锁保护(无参数)
⚠️ 写入前必须先擦除。Flash读取无需HAL函数,直接指针取值。
完整实例:掉电记忆LED状态
/* USER CODE BEGIN 0 */
void FLASH_Write(uint32_t addr, uint16_t data) {
HAL_FLASH_Unlock(); // 解锁Flash(无参数)
FLASH_EraseInitTypeDef EraseInitStruct; // 擦除结构体
EraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGES; // 按页擦除
EraseInitStruct.PageAddress = addr; // 起始地址,如0x08005000
EraseInitStruct.NbPages = 1; // 擦1页
uint32_t PageError = 0; // 错误页号传出变量
HAL_FLASHEx_Erase(&EraseInitStruct, &PageError); // &EraseInitStruct=擦除配置,&PageError=错误页传出
HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, addr, data); // HALFWORD=16位写入,addr=目标地址,data=数据值
HAL_FLASH_Lock(); // 上锁保护(无参数)
}
/* USER CODE END 0 */
/* USER CODE BEGIN 3 */
// 读取Flash(直接指针取值)
uint32_t addr = 0x08005000; // 选择不覆盖程序代码的地址,如0x08005000
uint16_t saved_state = *(__IO uint16_t*)(addr); // __IO=volatile修饰,直接指针读取
// 写入Flash保存状态
FLASH_Write(addr, new_state); // addr=目标地址,new_state=要保存的数据值
/* USER CODE END 3 */
5.14 低功耗模式
三种模式对比
| 模式 | CPU | 外设 | SRAM | 唤醒方式 | 唤醒后代码 | 功耗 |
|---|---|---|---|---|---|---|
| Sleep | 停止 | 保持运行 | 保持 | 任意中断 | 继续执行 | 中 |
| Stop | 停止 | 时钟关闭 | 保持 | EXTI中断/RTC | 继续执行(需恢复时钟) | 低 |
| Standby | 停止 | 关闭 | 丢失 | WKUP引脚/RTC/复位 | 复位重启 | 最低 |
函数参数详解
HAL_PWR_EnterSLEEPMode(uint32_t Regulator, uint8_t SLEEPEntry)
| 参数名 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
Regulator |
uint32_t |
预定义调压器宏 | PWR_MAINREGULATOR_ON(主调压器开启) |
Sleep模式调压器配置 |
SLEEPEntry |
uint8_t |
预定义入口方式宏 | PWR_SLEEPENTRY_WFI(Wait For Interrupt,等中断唤醒)、PWR_SLEEPENTRY_WFE(Wait For Event,等事件唤醒) |
进入Sleep的方式 |
HAL_PWR_EnterSTOPMode(uint32_t Regulator, uint8_t STOPEntry)
| 参数名 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
Regulator |
uint32_t |
预定义调压器宏 | PWR_LOWPOWERREGULATOR_ON(低功耗调压器,最常用)、PWR_MAINREGULATOR_ON(主调压器) |
Stop模式调压器配置 |
STOPEntry |
uint8_t |
预定义入口方式宏 | PWR_STOPENTRY_WFI(等中断唤醒)、PWR_STOPENTRY_WFE(等事件唤醒) |
进入Stop的方式 |
HAL_PWR_EnterSTANDBYMode() — 无参数
HAL_PWR_EnableWakeUpPin(uint32_t WakeUpPinx)
| 参数名 | C数据类型 | 格式规范 | 示例值 | 说明 |
|---|---|---|---|---|
WakeUpPinx |
uint32_t |
预定义唤醒引脚宏 | PWR_WAKEUP_PIN1(WKUP1引脚,通常PA0)、PWR_WAKEUP_PIN2(WKUP2)、PWR_WAKEUP_PIN3(WKUP3) |
唤醒引脚编号 |
HAL_SuspendTick() / HAL_ResumeTick() — 无参数
Stop模式唤醒代码实例
/* USER CODE BEGIN 2 */
HAL_SuspendTick(); // 挂起SysTick(无参数)
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI); // PWR_LOWPOWERREGULATOR_ON=低功耗调压器,PWR_STOPENTRY_WFI=等中断唤醒
// 唤醒后恢复时钟!Stop模式唤醒后使用HSI,需重新配置
SystemClock_Config(); // 重新配置系统时钟
HAL_ResumeTick(); // 恢复SysTick(无参数)
/* USER CODE END 2 */
Standby模式 + RTC唤醒
/* USER CODE BEGIN 2 */
// 判断是否从Standby唤醒
if (__HAL_PWR_GET_FLAG(PWR_FLAG_SB) != RESET) { // PWR_FLAG_SB=Standby标志
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_SB); // 清除标志
} else {
// 正常上电复位
}
// 配置RTC唤醒定时器(5秒后唤醒)
HAL_RTCEx_SetWakeUpTimer_IT(&hrtc, 4, RTC_WAKEUPCLOCK_CK_SPRE_16BITS);
// &hrtc=RTC句柄,4=Counter值(4+1=5秒),RTC_WAKEUPCLOCK_CK_SPRE_16BITS=1Hz时钟源
// 使能WKUP唤醒引脚
HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN1); // PWR_WAKEUP_PIN1=PA0唤醒引脚
// 清除唤醒标志
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU); // PWR_FLAG_WU=唤醒标志
// 进入Standby模式
HAL_PWR_EnterSTANDBYMode(); // 无参数,唤醒后等同于复位
/* USER CODE END 2 */
六、MSP函数与MX_Init函数的区别
这是理解HAL库架构的关键概念:
| 函数 | 职责 | 由谁生成 | 是否可修改 |
|---|---|---|---|
MX_USART1_UART_Init() |
配置UART参数(波特率、数据位等) | CubeMX自动生成 | ❌ 严禁修改 |
HAL_UART_MspInit() |
配置底层硬件资源(GPIO引脚、时钟使能、NVIC优先级、DMA通道) | CubeMX在stm32f1xx_hal_msp.c中生成 |
❌ 严禁修改 |
HAL_UART_Init() |
HAL库内部初始化,调用MspInit |
HAL库源码 | ❌ 不修改 |
执行顺序:
MX_USART1_UART_Init() ← CubeMX生成的参数配置(波特率、数据位)
↓ 调用
HAL_UART_Init(&huart1) ← HAL库内部初始化
↓ 内部调用
HAL_UART_MspInit(&huart1) ← CubeMX生成的底层配置(GPIO/时钟/NVIC/DMA)
📌
MspInit是MCU Specific Package的缩写,负责芯片级底层配置(哪些引脚、哪个时钟、哪个DMA通道),而MX_Init负责外设参数级配置(波特率、模式等)。两者分工明确。
七、回调函数机制深度解析
7.1 __weak弱定义机制
HAL库中的回调函数默认使用__weak关键字定义:
// HAL库源码中的弱定义(如stm32f1xx_hal_uart.c)
__weak void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
UNUSED(huart); // 默认空实现,参数类型UART_HandleTypeDef*
}
弱定义规则:
- 用户在自己的代码中重写该函数 → 编译器使用用户版本
- 用户未重写 → 编译器使用弱定义的空实现
- 不必重写所有回调,只需重写实际使用到的
7.2 回调函数命名速查总表(完整版,含参数类型与示例)
| 外设 | 事件 | 回调函数签名 | huart/htim等判断示例 |
|---|---|---|---|
| GPIO | 外部中断 | void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) |
GPIO_Pin == GPIO_PIN_13 |
| UART | 接收完成 | void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) |
huart->Instance == USART1 |
| UART | 发送完成 | void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) |
huart->Instance == USART1 |
| UART | 空闲事件 | void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size) |
huart->Instance == USART1,Size=实际接收字节数 |
| UART | 通信错误 | void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart) |
huart->Instance == USART1 |
| TIM | 周期溢出 | void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) |
htim->Instance == TIM2 |
| TIM | 输入捕获 | void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) |
htim->Instance == TIM2 |
| ADC | 转换完成 | void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) |
hadc->Instance == ADC1 |
| ADC | 半转换完成 | void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef *hadc) |
hadc->Instance == ADC1 |
| SPI | 收发完成 | void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi) |
hspi->Instance == SPI1 |
| I2C | 主机发送完成 | void HAL_I2C_MasterTxCpltCallback(I2C_HandleTypeDef *hi2c) |
hi2c->Instance == I2C1 |
| CAN | FIFO0消息挂起 | void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) |
hcan->Instance == CAN1 |
| RTC | 闹钟A事件 | void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc) |
hrtc->Instance == RTC |
| RTC | 秒更新事件 | void HAL_RTCEx_RTCEventCallback(RTC_HandleTypeDef *hrtc) |
hrtc->Instance == RTC |
| RTC | 唤醒定时器 | void HAL_RTCEx_WakeUpTimerEventCallback(RTC_HandleTypeDef *hrtc) |
hrtc->Instance == RTC |
| WWDG | 早期唤醒 | void HAL_WWDG_EarlyWakeupCallback(WWDG_HandleTypeDef *hwwdg) |
hwwdg->Instance == WWDG |
| DMA | 传输完成 | void HAL_DMA_XferCpltCallback(DMA_HandleTypeDef *hdma) |
hdma->Instance == DMA1_Channel1 |
7.3 回调函数使用注意事项
- 必须重写:不重写则事件触发后无任何操作
- 区分外设实例:多个同类型外设时需通过
huart->Instance == USART1判断 - 严禁阻塞操作:回调运行在中断上下文,禁止
HAL_Delay、长循环 - 重新使能中断:IT模式回调后HAL自动关闭中断,需重新调用使能函数
- volatile关键字:回调中修改的标志位必须加
volatile,如volatile uint8_t rx_flag = 0;
八、函数调用顺序速查总表
| 外设 | 第1步 | 第2步 | 第3步 | 第4步 | 第5步 | 代码区域 |
|---|---|---|---|---|---|---|
| GPIO输出 | 使能时钟 | HAL_GPIO_Init |
WritePin/TogglePin |
— | — | BEGIN 2+3 |
| GPIO中断 | 使能时钟 | HAL_GPIO_Init |
NVIC_EnableIRQ |
重写EXTI_Callback |
回调置标志 | BEGIN 2+4 |
| UART轮询 | CubeMX配置 | MX_USARTx_Init |
Transmit(&huart1, data, 8, 100) |
— | — | BEGIN 3 |
| UART IT接收 | CubeMX+NVIC | MX_USARTx_Init |
Receive_IT(&huart1, &rx_byte, 1) |
重写RxCpltCallback |
重新启动接收 | BEGIN 2+4+3 |
| UART空闲IT | CubeMX+NVIC | MX_USARTx_Init |
ReceiveToIdle_IT(&huart1, rx_buf, 30) |
重写RxEventCallback |
重新使能 | BEGIN 2+4+3 |
| UART空闲DMA | CubeMX+DMA+Circular | MX_DMA_Init→MX_USARTx_Init |
ReceiveToIdle_DMA(&huart1, rx_buf, 256) |
重写RxEventCallback |
无需重新使能 | BEGIN 2+4 |
| TIM PWM | CubeMX配置 | MX_TIMx_Init |
PWM_Start(&htim3, TIM_CHANNEL_1) |
SET_COMPARE(&htim3, TIM_CHANNEL_1, 500) |
— | BEGIN 2+3/4 |
| TIM中断 | CubeMX+NVIC | MX_TIMx_Init |
Base_Start_IT(&htim2) |
重写PeriodElapsedCallback |
回调处理 | BEGIN 2+4 |
| ADC轮询 | CubeMX配置 | MX_ADCx_Init |
ADC_Start(&hadc1) |
PollForConversion(&hadc1, 100) |
GetValue(&hadc1) |
BEGIN 3 |
| ADC+DMA | CubeMX+DMA | MX_DMA_Init→MX_ADCx_Init |
ADC_Start_DMA(&hadc1, (uint32_t*)adc_buf, 32) |
重写ConvCpltCallback |
回调重启 | BEGIN 2+4 |
| DAC | CubeMX配置 | MX_DAC_Init |
DAC_Start(&hdac, DAC_CHANNEL_1) |
DAC_SetValue(&hdac, DAC_CHANNEL_1, DAC_ALIGN_12B_R, 2048) |
— | BEGIN 2+3 |
| SPI轮询 | CubeMX配置 | MX_SPIx_Init |
TransmitReceive(&hspi1, spi_tx, spi_rx, 4, 100) |
— | — | BEGIN 3 |
| I2C轮询 | CubeMX配置 | MX_I2Cx_Init |
Mem_Read(&hi2c1, 0x68<<1, 0x3B, I2C_MEMADD_SIZE_8BIT, data, 6, 100) |
— | — | BEGIN 3 |
| CAN | CubeMX+NVIC | MX_CAN_Init |
ConfigFilter+Start+ActivateNotification |
重写RxFifo0Callback |
AddTxMessage(&hcan1, &tx_header, tx_data, &tx_mailbox) |
BEGIN 2+4+3 |
| RTC秒中断 | CubeMX配置 | MX_RTC_Init |
SECOND_ENABLE_IT(&hrtc, RTC_IT_SEC) |
重写RTCEventCallback |
GetTime(&hrtc, &sTime, RTC_FORMAT_BIN)+GetDate |
BEGIN 2+4 |
| RTC闹钟 | CubeMX配置+NVIC | MX_RTC_Init |
SetAlarm_IT(&hrtc, &sAlarm, RTC_ALARM_A) |
重写AlarmAEventCallback |
— | BEGIN 2+4 |
| IWDG | CubeMX配置 | MX_IWDG_Init |
主循环Refresh(&hiwdg) |
— | — | BEGIN 3 |
| WWDG | CubeMX配置+NVIC | MX_WWDG_Init |
主循环Refresh(&hwwdg, 0x7F) |
重写EarlyWakeupCallback |
— | BEGIN 3+4 |
| FLASH读写 | 无需CubeMX | FLASH_Unlock() |
Erase(&EraseInitStruct, &PageError) |
Program(FLASH_TYPEPROGRAM_HALFWORD, addr, data) |
FLASH_Lock() |
BEGIN 0+3 |
| Stop低功耗 | CubeMX+EXTI | 配置唤醒源 | SuspendTick() |
EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI) |
唤醒后SystemClock_Config()+ResumeTick() |
BEGIN 2 |
⚠️ DMA初始化顺序:
MX_DMA_Init()必须在所有使用DMA的外设初始化之前调用!CubeMX生成的代码已自动处理此顺序。
九、完整main.c代码架构模板
/* Includes -----------------------------------------------*/
#include "main.h"
/* USER CODE BEGIN Includes */
// 自定义头文件
/* USER CODE END Includes */
/* USER CODE BEGIN PV */
// ========== 全局变量区 ==========
// UART接收
uint8_t rx_byte; // 单字节接收缓冲区,uint8_t类型
volatile uint8_t rx_flag = 0; // 接收标志,volatile防止优化
#define RX_BUF_SIZE 256 // 缓冲区大小宏定义
uint8_t rx_buf[RX_BUF_SIZE]; // 空闲中断接收缓冲区数组
volatile uint16_t rx_len = 0; // 实际接收字节数,uint16_t
volatile uint8_t rx_event = 0; // 接收事件标志
// TIM PWM
uint16_t pwm_duty = 0; // PWM占空比CCR值,uint16_t,范围0~999
uint8_t pwm_dir = 1; // 呼吸方向,1=上升,0=下降
// ADC DMA
#define ADC_SAMPLES 32 // ADC采样次数
uint16_t adc_buf[ADC_SAMPLES]; // ADC缓冲区数组,uint16_t
volatile uint8_t adc_ready = 0; // ADC转换完成标志
// DAC
uint16_t dac_setval = 0; // DAC设定值,uint16_t,范围0~4095
uint8_t dac_dir = 0; // DAC方向,0=上升,1=下降
// CAN
CAN_TxHeaderTypeDef tx_header; // CAN发送报文头结构体
CAN_RxHeaderTypeDef rx_header; // CAN接收报文头结构体
uint8_t tx_data[8] = {0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08}; // CAN发送数据
uint8_t rx_data[8]; // CAN接收数据缓冲区
uint32_t tx_mailbox; // CAN发送邮箱编号
// RTC
RTC_TimeTypeDef sTime; // RTC时间结构体
RTC_DateTypeDef sDate; // RTC日期结构体
/* USER CODE END PV */
/* USER CODE BEGIN PFP */
void Process_Data(void);
void Handle_ADC(void);
/* USER CODE END PFP */
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_DMA_Init(); // ⚠️ DMA必须在所有外设Init之前!
MX_GPIO_Init();
MX_USART1_UART_Init();
MX_TIM2_Init();
MX_TIM3_Init();
MX_ADC1_Init();
MX_DAC_Init();
MX_RTC_Init();
MX_CAN1_Init();
/* USER CODE BEGIN 2 */
// ========== 启动函数区 ==========
HAL_UARTEx_ReceiveToIdle_DMA(&huart1, rx_buf, RX_BUF_SIZE); // &huart1=串口1,rx_buf=缓冲区(不加&),RX_BUF_SIZE=256
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1); // &htim3=TIM3,TIM_CHANNEL_1=通道1
HAL_TIM_Base_Start_IT(&htim2); // &htim2=TIM2,开启中断
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buf, ADC_SAMPLES); // &hadc1=ADC1,(uint32_t*)adc_buf=强转,ADC_SAMPLES=32
HAL_DAC_Start(&hdac, DAC_CHANNEL_1); // &hdac=DAC句柄,DAC_CHANNEL_1=通道1
__HAL_RTC_SECOND_ENABLE_IT(&hrtc, RTC_IT_SEC); // &hrtc=RTC句柄,RTC_IT_SEC=秒中断
// CAN启动
CAN_FilterTypeDef filter = {0};
filter.FilterBank = 0;
filter.FilterMode = CAN_FILTERMODE_IDMASK;
filter.FilterScale = CAN_FILTERSCALE_32BIT;
filter.FilterIdHigh = 0x0000;
filter.FilterIdLow = 0x0000;
filter.FilterMaskIdHigh = 0x0000;
filter.FilterMaskIdLow = 0x0000;
filter.FilterFIFOAssignment = CAN_RX_FIFO0;
filter.FilterActivation = ENABLE;
filter.SlaveStartFilterBank = 14;
HAL_CAN_ConfigFilter(&hcan1, &filter);
HAL_CAN_Start(&hcan1);
HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING);
/* USER CODE END 2 */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE BEGIN 3 */
// ========== 主循环业务逻辑 ==========
if (rx_event) {
rx_event = 0;
Process_Data();
// DMA Circular模式无需重新调用
}
if (adc_ready) {
adc_ready = 0;
Handle_ADC();
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buf, ADC_SAMPLES);
}
// DAC锯齿波渐变
if (dac_dir == 0) { dac_setval++; if (dac_setval >= 4095) dac_dir = 1; }
else { dac_setval--; if (dac_setval <= 0) dac_dir = 0; }
HAL_DAC_SetValue(&hdac, DAC_CHANNEL_1, DAC_ALIGN_12B_R, dac_setval);
// 看门狗喂狗
HAL_IWDG_Refresh(&hiwdg);
/* USER CODE END 3 */
}
/* USER CODE END WHILE */
}
/* USER CODE BEGIN 4 */
// ========== 回调函数区 ==========
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size) {
if (huart->Instance == USART1) { rx_len = Size; rx_event = 1; }
}
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
if (htim->Instance == TIM2) {
if (pwm_dir) { pwm_duty += 10; if (pwm_duty >= 1000) { pwm_duty = 1000; pwm_dir = 0; } }
else { pwm_duty -= 10; if (pwm_duty <= 0) { pwm_duty = 0; pwm_dir = 1; } }
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, pwm_duty);
}
}
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) {
if (hadc->Instance == ADC1) { adc_ready = 1; }
}
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
if (GPIO_Pin == GPIO_PIN_13) { /* 置标志 */ }
}
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) {
if (HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &rx_header, rx_data) == HAL_OK) {
// 处理CAN接收报文
}
}
void HAL_RTCEx_RTCEventCallback(RTC_HandleTypeDef *hrtc) {
HAL_RTC_GetTime(hrtc, &sTime, RTC_FORMAT_BIN);
HAL_RTC_GetDate(hrtc, &sDate, RTC_FORMAT_BIN);
}
void Process_Data(void) { /* 处理不定长数据 */ }
void Handle_ADC(void) { /* ADC数据求平均 */ }
/* USER CODE END 4 */
十、常见错误与排查清单
| 错误现象 | 可能原因 | 解决方案 |
|---|---|---|
HAL_Delay()卡死 |
在中断回调中调用 | 改用HAL_GetTick()计数,在主循环检测 |
| 串口只收到一次数据 | 回调中未重新开启接收 | IT模式:重新调用HAL_UART_Receive_IT(&huart1, &rx_byte, 1) |
| UART空闲中断不触发 | 未使用HAL_UARTEx_函数 |
使用ReceiveToIdle_IT/DMA+RxEventCallback |
| PWM无输出 | 未调用HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1) |
在USER CODE BEGIN 2中调用启动函数 |
| ADC值全为0 | DMA未使能或未启动 | 调用HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buf, ADC_SAMPLES) |
| 回调函数不执行 | NVIC未使能或函数名拼写错 | ①CubeMX中勾选NVIC ②检查回调名拼写和参数签名 |
| CubeMX重新生成后代码丢失 | 代码写在USER CODE标记之外 | 严格放在BEGIN/END之间 |
| I2C通信失败 | 设备地址错误 | 使用7位地址左移1位:0x68<<1 = 0xD0 |
| SPI数据错位 | CPOL/CPHA不匹配 | 对照器件手册检查时钟极性/相位 |
| RTC时间不更新 | 只调了GetTime没调GetDate | 必须先GetTime(&hrtc, &sTime, RTC_FORMAT_BIN)再GetDate |
| Stop唤醒后时钟异常 | 唤醒后使用HSI | 唤醒后立即调用SystemClock_Config() |
| Standby唤醒后数据丢失 | Standby模式SRAM不保持 | 关键数据写入Flash或备份寄存器 |
| CAN收不到数据 | 过滤器配置不当 | 检查FilterMaskId设置,或设为0x0000接收所有帧 |
| 看门狗频繁复位 | 喂狗间隔大于超时时间 | 加大超时值或在主循环中定期HAL_IWDG_Refresh(&hiwdg) |
| DMA数据错乱 | DMA初始化顺序错误 | MX_DMA_Init()必须在所有外设Init之前 |
| HAL_BUSY返回值 | 外设正在执行其他操作 | 等待当前操作完成后再调用 |
| 编译报错"undefined reference" | 回调函数名与HAL不匹配 | 对照HAL源码确认函数签名(参数类型必须完全一致) |
| DAC输出全为0V | 未调用HAL_DAC_Start(&hdac, DAC_CHANNEL_1) |
必须先Start再SetValue |
十一、CubeMX配置与开发流程备忘
11.1 CubeMX配置要点
| 配置项 | 说明 |
|---|---|
| 时钟树 | 确保HCLK满足外设频率要求 |
| 外设模式 | 根据需求选择Polling/Interrupt/DMA |
| NVIC优先级 | 抢占优先级+子优先级(数值越小优先级越高) |
| DMA通道 | 确认外设与DMA通道映射正确 |
| DMA模式 | Normal(单次)vs Circular(连续循环)—— 空闲DMA接收必须用Circular |
| GPIO引脚 | 确认引脚复用功能正确 |
| RTC时钟源 | LSE(32.768kHz,精度高)优于LSI(精度低) |
11.2 DMA模式选择
| DMA模式 | 特点 | 适用场景 |
|---|---|---|
| Normal | 传输完成后自动停止 | 单次ADC采样、一次性数据发送 |
| Circular | 传输完成后自动回绕继续 | UART空闲DMA接收、ADC连续采样 |
⚠️ UART空闲DMA接收必须用Circular模式,否则缓冲区满后DMA停止,后续数据丢失。
11.3 标准开发流程
① CubeMX配置 → ② 生成代码 → ③ USER CODE编写 → ④ 编译烧录 → ⑤ 调试排错
↓ 如需修改CubeMX配置
⑥ 重新生成 → USER CODE自动保留 → 继续开发
11.4 调试技巧
| 技巧 | 说明 |
|---|---|
| 非阻塞延时 | HAL_GetTick()替代HAL_Delay() |
| 串口调试 | HAL_UART_Transmit(&huart1, (uint8_t*)"OK\r\n", 4, 100)打印信息 |
| 逻辑分析仪 | 验证SPI/I2C时序 |
volatile关键字 |
中断修改的标志位必须加volatile,如 volatile uint8_t rx_flag = 0; |
| 检查返回值 | if(HAL_UART_Transmit(&huart1, data, 8, 100) != HAL_OK) { Error_Handler(); } |
| HAL库优化体积 | 在stm32f1xx_hal_conf.h中注释未使用的外设模块 |
| 状态检查 | 使用HAL_UART_GetState(&huart1)等函数检查外设当前状态 |
手册结束 | 综合两份参考文档 + 全网技术资料补充 | 每个函数的每个参数均有完整的数据类型、格式规范、示例值 | 适用于STM32全系列HAL库(F1/F4/H7等)
更多推荐


所有评论(0)