【STM32】HAL库学习笔记20260707-STM32 HAL库函数综合手册:参数详解 · 调用顺序 · 代码归属实战

本手册综合两份参考文档及全网技术资料,系统说明STM32 HAL库各外设函数的每个参数的数据类型、格式规范与具体示例使用方法调用顺序代码在main.c中的正确放置位置(确保不被CubeMX重新生成代码删除),适用于STM32全系列(F1/F4/H7等)。


目录


一、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
格式规范 直接传入整数常量、宏定义或变量,如 81RX_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
格式规范 直接传入毫秒整数,如 1001000HAL_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_1DAC_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 格式规范 使用预定义端口基地址宏,如 GPIOAGPIOB
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 开发者只需做两件事

  1. 在正确位置调用启动函数(如 HAL_UART_Receive_IT
  2. 重写对应的回调函数(如 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* 预定义端口基地址宏,直接使用 GPIOAGPIOBGPIOCGPIOD 目标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* 预定义端口宏 GPIOAGPIOB 目标端口
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* 预定义端口宏 GPIOAGPIOB 目标端口
GPIO_Pin uint16_t 预定义引脚掩码宏 GPIO_PIN_13 要读取的引脚
返回值 GPIO_PinState 与枚举值比较 GPIO_PIN_SETGPIO_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_0GPIO_PIN_5GPIO_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 传入毫秒整数 100HAL_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 字节数整数 81sizeof(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_bufuint8_t tx_buf[1024]; DMA发送缓冲区
Size uint16_t 字节数整数 1024512 DMA传输字节数

HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)

参数名 C数据类型 格式规范 示例值 说明
huart UART_HandleTypeDef* 句柄加 & &huart1 串口句柄
pData uint8_t* 全局数组名 rx_bufuint8_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库的扩展APIHAL_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_bufuint8_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_bufuint8_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_bufuint16_t adc_buf[32];需强转) DMA目标缓冲区,ADC DMA要求uint32_t指针,实际数据是uint16_t
Length uint32_t 采样次数整数 32(采32次)、ADC_SAMPLES(宏定义) DMA传输长度=采样次数,不是字节数

📌 注意 pData 的强转:adc_bufuint16_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_waveuint32_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_txuint8_t spi_tx[4] = {0x03,0x00,0x00,0x00};)、dummy_tx(dummy字节数组 {0xFF,0xFF,...} 发送数据指针。只读时发送dummy字节产生时钟
pRxData uint8_t* 全局数组名 spi_rxuint8_t spi_rx[4]; 接收数据缓冲区
Size uint16_t 收发字节数整数 4(4字节)、8(8字节) 收发字节数,Tx和Rx长度必须相同
Timeout uint32_t 传入毫秒整数 1001000HAL_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_datauint8_t i2c_data[6]; 接收数据缓冲区
Size uint16_t 读取字节数整数 6(读6字节)、1(读1字节)、2(读2字节) 要读取的字节数
Timeout uint32_t 传入毫秒整数 1001000 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位 0xA00x50<<1 设备地址
MemAddress uint16_t 寄存器地址整数 0x00 写入目标寄存器地址
MemAddSize uint16_t 预定义宏 I2C_MEMADD_SIZE_8BIT 地址位数
pData uint8_t* 全局数组名 write_data 写入数据指针
Size uint16_t 写入字节数 14 要写入的字节数
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* 取结构体变量地址,加 & &filterCAN_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_headerCAN_TxHeaderTypeDef tx_header; 发送报文头结构体
aData uint8_t[] 全局数组名 tx_datauint8_t tx_data[8] = {0x01,...,0x08}; 报文数据,最多8字节
pTxMailbox uint32_t* 取邮箱变量地址,加 & &tx_mailboxuint32_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_FIFO0CAN_RX_FIFO1 从哪个FIFO读取
pHeader CAN_RxHeaderTypeDef* 取结构体地址,加 & &rx_headerCAN_RxHeaderTypeDef rx_header; 接收报文头(传出参数)
aData uint8_t[] 全局数组名 rx_datauint8_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* 取结构体变量地址,加 & &sTimeRTC_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* 取结构体地址,加 & &sDateRTC_DateTypeDef sDate; 日期结构体(传出参数)
Format uint32_t 预定义格式宏 RTC_FORMAT_BINRTC_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_AMRTC_HOURFORMAT12_PM 12小时制AM/PM(24小时制不用)
DayLightSaving uint32_t 预定义宏 RTC_DAYLIGHTSAVING_SUB1HRTC_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* 取变量地址,加 & &PageErroruint32_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_1FLASH_SECTOR_51 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)

📌 MspInitMCU 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 回调函数使用注意事项

  1. 必须重写:不重写则事件触发后无任何操作
  2. 区分外设实例:多个同类型外设时需通过huart->Instance == USART1判断
  3. 严禁阻塞操作:回调运行在中断上下文,禁止HAL_Delay、长循环
  4. 重新使能中断:IT模式回调后HAL自动关闭中断,需重新调用使能函数
  5. 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_InitMX_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_InitMX_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等)

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐