F429-HAL 库驱动框架(2026/7/22)

目录

[HAL 库驱动框架(完整代码示例)](#HAL 库驱动框架(完整代码示例))

第一层:句柄结构体

[第二层:初始化(Init → MspInit)](#第二层:初始化(Init → MspInit))

[2.1 HAL_UART_MspInit ------ 你写的"硬件接线"](#2.1 HAL_UART_MspInit —— 你写的"硬件接线")

[2.2 My_UART1_Init ------ 你的总初始化](#2.2 My_UART1_Init —— 你的总初始化)

[2.3 My_UART1_DMA_Init ------ 用 DMA 模式时才调](#2.3 My_UART1_DMA_Init —— 用 DMA 模式时才调)

第三层:三种操作模式

[③-① 阻塞轮询 Polling](#③-① 阻塞轮询 Polling)

[③-② 中断 IT](#③-② 中断 IT)

[③-③ DMA](#③-③ DMA)

[③-④ 辅助操作](#③-④ 辅助操作)

[main 函数总入口](#main 函数总入口)

三模式速查

句柄六成员速查


HAL 库驱动框架(完整代码示例)

平台:STM32F429 + HAL 库 | 外设:USART1(PA9=TX, PA10=RX)

覆盖脑图三个分支:句柄 → 初始化 → 三种操作模式


第一层:句柄结构体

xx_HandleTypeDef = Instance + Init + Hdma + Lock + State(6 个成员,你填 2 个 + HAL 管 3 个 + DMA 时填 1 个)

复制代码
UART_HandleTypeDef huart1;   // 一张"点菜单",6 个成员一览:
​
//   huart1.Instance → USART_TypeDef *           ← 【你要填】指向 USART1 寄存器基址 0x40011000
//   huart1.Init     → UART_InitTypeDef           ← 【你要填】波特率/停止位/校验等参数
//   huart1.hdmatx   → DMA_HandleTypeDef *        ← 【用 DMA 才填】DMA 发送通道句柄
//   huart1.hdmarx   → DMA_HandleTypeDef *        ← 【用 DMA 才填】DMA 接收通道句柄
//   huart1.Lock     → HAL_LockTypeDef            ← 【HAL 库自己管】你没填过,但它一直在变
//   huart1.State    → HAL_UART_StateTypeDef     ← 【HAL 库自己管】READY/BUSY/ERROR 自己流转
​
/* DMA 句柄——只有用 DMA 模式时才需要 */
DMA_HandleTypeDef hdma_usart1_tx;   // TX 用的 DMA 通道
DMA_HandleTypeDef hdma_usart1_rx;   // RX 用的 DMA 通道
​
/* 数据缓冲 */
uint8_t tx_buf[64];
uint8_t rx_buf[64];

第二层:初始化(Init → MspInit)

调用链:你调 HAL_UART_Init → 库内部调 HAL_UART_MspInit → 库内部设 Lock/State

2.1 HAL_UART_MspInit ------ 你写的"硬件接线"

放在 stm32f4xx_hal_msp.c。你不需要手动调它------HAL_UART_Init 内部会自动调用。

复制代码
void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
    if (huart != &huart1) return;
​
    GPIO_InitTypeDef GPIO_Init = {0};
​
    /* 1. 开时钟 */
    __HAL_RCC_GPIOA_CLK_ENABLE();
    __HAL_RCC_USART1_CLK_ENABLE();
    __HAL_RCC_DMA2_CLK_ENABLE();       // DMA 也要开时钟!
​
    /* 2. 配引脚 */
    // PA9 — TX
    GPIO_Init.Pin       = GPIO_PIN_9;
    GPIO_Init.Mode      = GPIO_MODE_AF_PP;
    GPIO_Init.Pull      = GPIO_PULLUP;
    GPIO_Init.Speed     = GPIO_SPEED_FREQ_HIGH;
    GPIO_Init.Alternate = GPIO_AF7_USART1;
    HAL_GPIO_Init(GPIOA, &GPIO_Init);
​
    // PA10 — RX
    GPIO_Init.Pin       = GPIO_PIN_10;
    GPIO_Init.Alternate = GPIO_AF7_USART1;
    HAL_GPIO_Init(GPIOA, &GPIO_Init);
​
    /* 3. 配中断(IT 和 DMA 模式都需要) */
    HAL_NVIC_SetPriority(USART1_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(USART1_IRQn);
​
    /* 4. 配 DMA 中断(DMA 模式才需要) */
    HAL_NVIC_SetPriority(DMA2_Stream7_IRQn, 0, 0);   // USART1_TX 用的 DMA 流
    HAL_NVIC_EnableIRQ(DMA2_Stream7_IRQn);
    HAL_NVIC_SetPriority(DMA2_Stream2_IRQn, 0, 0);   // USART1_RX 用的 DMA 流
    HAL_NVIC_EnableIRQ(DMA2_Stream2_IRQn);
}

2.2 My_UART1_Init ------ 你的总初始化

HAL_UART_Init 内部除了调 MspInit,还偷偷设了 Lock=UNLOCKEDState=READY。这两个字段你一行没写,但 HAL 靠它们保证不乱套。

复制代码
void My_UART1_Init(void)
{
    /* ===== 第一步:填 Instance ===== */
    huart1.Instance = USART1;          // "管的是 USART1 这个硬件"
    // 等价于:把这个句柄和 USART1 的寄存器基址(0x40011000)绑定
​
    /* ===== 第二步:填 Init ===== */
    huart1.Init.BaudRate     = 115200;
    huart1.Init.WordLength   = UART_WORDLENGTH_8B;
    huart1.Init.StopBits     = UART_STOPBITS_1;
    huart1.Init.Parity       = UART_PARITY_NONE;
    huart1.Init.Mode         = UART_MODE_TX_RX;
    huart1.Init.HwFlowCtl    = UART_HWCONTROL_NONE;
    huart1.Init.OverSampling = UART_OVERSAMPLING_16;
    // 这些值最终会被 HAL_UART_Init 写进 USART1 的控制寄存器(CR1/CR2/CR3)
​
    /* HAL_UART_Init 内部做了:
     *   ① 调 HAL_UART_MspInit  →  开时钟、配引脚、配中断
     *   ② 写 Init 参数到寄存器 →  波特率/停止位等生效
     *   ③ 设 Lock = UNLOCKED   →  初始状态"没锁"
     *   ④ 设 State = READY     →  标记外设"就绪,可以干活"
     */
    HAL_UART_Init(&huart1);
​
    // 至此,Lock 和 State 已由 HAL 库设好,你完全不需要关心它们的初始值
}

2.3 My_UART1_DMA_Init ------ 用 DMA 模式时才调

填的是 DMA 句柄自己的 Instance 和 Init,然后把它的指针挂到 huart1.hdmatx/hdmarx 上。

复制代码
void My_UART1_DMA_Init(void)
{
    /* ===== TX DMA ===== */
    hdma_usart1_tx.Instance = DMA2_Stream7;              // USART1_TX → DMA2 流 7
    hdma_usart1_tx.Init.Channel  = DMA_CHANNEL_4;       // 通道 4
    hdma_usart1_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;// 内存→外设
    hdma_usart1_tx.Init.PeriphInc = DMA_PINC_DISABLE;   // 外设地址不变
    hdma_usart1_tx.Init.MemInc    = DMA_MINC_ENABLE;    // 内存地址递增
    hdma_usart1_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
    hdma_usart1_tx.Init.MemDataAlignment    = DMA_MDATAALIGN_BYTE;
    hdma_usart1_tx.Init.Mode      = DMA_NORMAL;         // 单次模式
    hdma_usart1_tx.Init.Priority  = DMA_PRIORITY_MEDIUM;
    HAL_DMA_Init(&hdma_usart1_tx);   // 内部也会调 HAL_DMA_MspInit
​
    /* ===== RX DMA ===== */
    hdma_usart1_rx.Instance = DMA2_Stream2;              // USART1_RX → DMA2 流 2
    hdma_usart1_rx.Init.Channel  = DMA_CHANNEL_4;
    hdma_usart1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY; // 外设→内存
    hdma_usart1_rx.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_usart1_rx.Init.MemInc    = DMA_MINC_ENABLE;
    hdma_usart1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
    hdma_usart1_rx.Init.MemDataAlignment    = DMA_MDATAALIGN_BYTE;
    hdma_usart1_rx.Init.Mode      = DMA_NORMAL;
    hdma_usart1_rx.Init.Priority  = DMA_PRIORITY_HIGH;
    HAL_DMA_Init(&hdma_usart1_rx);
​
    /* 把 DMA 句柄挂到 UART 句柄上——这就是"什么时候填 Hdma"的答案 */
    huart1.hdmatx = &hdma_usart1_tx;
    huart1.hdmarx = &hdma_usart1_rx;
​
    __HAL_LINKDMA(&huart1, hdmatx, hdma_usart1_tx);  // 本质就是指针赋值
    __HAL_LINKDMA(&huart1, hdmarx, hdma_usart1_rx);
}

第三层:三种操作模式

选哪种?数据量小 + 时间短 → Polling | 不定长/不想等 → IT | 大批量搬运 → DMA


③-① 阻塞轮询 Polling

CPU 发出命令 → 原地死等 → 等到完或超时才走。 特征:函数名无 _IT_DMA,必带 Timeout 参数。

复制代码
void Polling_Example(void)
{
    HAL_StatusTypeDef ret;
​
    /* 发送 6 字节 */
    strcpy((char *)tx_buf, "Hello\r\n");
    ret = HAL_UART_Transmit(&huart1, tx_buf, 6, 1000);
    //       句柄      数据    长度   超时(ms)
    //
    //  CPU 卡在这 1000ms 内,等所有字节发完才走。
    //  正常发完 → 返回 HAL_OK,      State 恢复 READY
    //  超时     → 返回 HAL_TIMEOUT, State 恢复 READY
​
    /* 接收 10 字节——还是死等 */
    ret = HAL_UART_Receive(&huart1, rx_buf, 10, 1000);
    //  等 1000ms 内收齐 10 个字节,收不齐就超时。
    //  收齐后直接读 rx_buf 就行——数据已经到了。
}

HAL 内部对 Lock/State 的操作(你看不到,但它在做):

复制代码
┌─ HAL_UART_Transmit 入口 ────────────────┐
│  if (huart->State != READY) return BUSY;│  ← Lock/State 起作用了
│  huart->State = BUSY_TX;                │  ← HAL 自己改的
│  while (没发完) {                        │  ← 这儿等 Timeout
│      往 DR 寄存器塞一个字节;               │
│      if (超时) { State=READY; 返回; }    │
│  }                                       │
│  huart->State = READY;                  │  ← 发完,恢复就绪
└─────────────────────────────────────────┘

③-② 中断 IT

CPU 发起 → 立刻返回干别的 → 硬件触发中断 → HAL 搬数据 → 回调通知你。 特征:函数名以 _IT 结尾,没有 Timeout 参数。

启动(主循环里调一次):

复制代码
void IT_Example_Start(void)
{
    HAL_StatusTypeDef ret;
​
    ret = HAL_UART_Receive_IT(&huart1, rx_buf, 10);
    //      句柄      缓冲    收 10 字节
    //
    //  注意:没有 Timeout!
    //  这行执行完 CPU 就自由了,什么时候收齐,什么时候进回调。
​
    if (ret != HAL_OK) {
        // 启动失败(上次还没收完——State 不是 READY,被 HAL 挡了)
    }
}

中断服务函数(放在 stm32f4xx_it.c):

复制代码
void USART1_IRQHandler(void)
{
    /* HAL 库内部做了这些事(你不用管):
     *   - 检查 RXNE(收到一个字节?)
     *   - 读 DR 寄存器 → 放进 rx_buf
     *   - 计数器-1,看收够了没
     *   - 收够了 → 关中断 → State=READY → RxCpltCallback()
     */
    HAL_UART_IRQHandler(&huart1);
}

回调(数据到手,在这儿处理):

复制代码
/* 接收完成 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    if (huart == &huart1) {
        // 数据已在 rx_buf,处理完再启动下一轮
        HAL_UART_Receive_IT(&huart1, rx_buf, 10);
    }
}
​
/* 发送完成 */
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
    if (huart == &huart1) {
        // 上一批发完了,可以发下一批
    }
}
​
/* 出错 */
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
{
    if (huart == &huart1) {
        // State 已被 HAL 改成 ERROR,重启接收即可
        HAL_UART_Receive_IT(&huart1, rx_buf, 10);
    }
}

IT 模式 HAL 内部 State 流转:

复制代码
主循环:  Receive_IT → State=BUSY_RX → 开 RXNE 中断 → 立刻返回
中断里:  IRQHandler → 清标志/搬数据 → 收够了
         → 关中断 → State=READY → RxCpltCallback()

③-③ DMA

CPU 发起 → DMA 自己搬数据(CPU 完全不管)→ 搬完触发 DMA 中断 → 回调。 特征:函数名以 _DMA 结尾,没有 Timeout

和 IT 的关键区别:IT 每个字节打断 CPU 一次;DMA 整批搬完只打断一次。

复制代码
void DMA_Example_Start(void)
{
    /* 前提:My_UART1_DMA_Init() 已经把 hdmatx/hdmarx 挂到 huart1 上了 */
​
    /* DMA 发送 */
    strcpy((char *)tx_buf, "Large block via DMA...\r\n");
    HAL_UART_Transmit_DMA(&huart1, tx_buf, 64);
    //  HAL 内部:
    //   ① 检查 State==READY && Lock==UNLOCKED
    //   ② 设 State=BUSY_TX, Lock=LOCKED
    //   ③ 配 DMA(源=tx_buf, 目标=USART1->DR, 长度=64)
    //   ④ 启动 DMA → 立即返回, CPU 自由
    //   ⑤ 搬完 → DMA 中断 → IRQHandler → TxCpltCallback
    //   ⑥ 回调里 State=READY, Lock=UNLOCKED
​
    /* DMA 接收 */
    HAL_UART_Receive_DMA(&huart1, rx_buf, 64);
}

DMA 中断服务函数(stm32f4xx_it.c):

复制代码
void DMA2_Stream7_IRQHandler(void)   // TX DMA
{
    HAL_DMA_IRQHandler(&hdma_usart1_tx);
    //  搬完 → 调 HAL_UART_TxCpltCallback
}
void DMA2_Stream2_IRQHandler(void)   // RX DMA
{
    HAL_DMA_IRQHandler(&hdma_usart1_rx);
    //  搬完 → 调 HAL_UART_RxCpltCallback(和 IT 共用同一个回调!)
}

回调------DMA 和 IT 共用的,不用区分:

复制代码
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    if (huart == &huart1) {
        // 不管 IT 还是 DMA 搬完的,数据都在 rx_buf,统一处理
        HAL_UART_Receive_DMA(&huart1, rx_buf, 64);  // 再开下一轮
    }
}

③-④ 辅助操作

复制代码
void Misc_Operations(void)
{
    /* 查询标志 */
    if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE)) { /* 有数据来了 */ }
    if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_TC))   { /* 发送完成 */ }
​
    /* 清除标志 */
    __HAL_UART_CLEAR_FLAG(&huart1, UART_FLAG_TC);
​
    /* 开关中断 */
    __HAL_UART_ENABLE_IT(&huart1, UART_IT_RXNE);   // 开接收中断
    __HAL_UART_DISABLE_IT(&huart1, UART_IT_TC);    // 关发送完成中断
​
    /* 查询状态(读的就是句柄里的 State 字段) */
    if (huart1.State == HAL_UART_STATE_READY) { /* 空闲,可以干活 */ }
}

main 函数总入口

复制代码
int main(void)
{
    HAL_Init();
    SystemClock_Config();
​
    /* 初始化——填表 + 硬件接线一步到位 */
    My_UART1_Init();
​
    /* 如果要测 DMA,再加这行 */
    My_UART1_DMA_Init();
​
    /*
     * 三种模式选一种:
     *   Polling_Example();    // 简单快速,CPU 等着
     *   IT_Example_Start();   // CPU 不卡,收完进回调
     *   DMA_Example_Start();  // 大批量搬运,CPU 完全不管
     */
​
    IT_Example_Start();   // 假设选中断模式——启动一次即可
​
    while (1) {
        // 主循环跑业务逻辑
        // 数据来了自动进回调,不用在这儿轮询
    }
}

三模式速查

你调的函数 谁搬数据 你什么时候拿到数据 Timeout CPU 占用
Polling Transmit / Receive CPU 等着搬 函数返回时 ✅ 有 全程占着
IT xxx_IT HAL 在 ISR 里搬 回调里 ❌ 无 每字节打断一次
DMA xxx_DMA DMA 自己搬 回调里(同 IT) ❌ 无 几乎不占

句柄六成员速查

成员 谁填 什么时候
Instance 初始化时
Init 初始化时
hdmatx/hdmarx (DMA 时) My_DMA_Init 里挂上
Lock HAL 库 每次操作前后自动改
State HAL 库 每次操作前后自动改
相关推荐
小羊先生car3 小时前
F429-SysTick(2026/7/22)
stm32·单片机·嵌入式硬件
hongmai6668884 小时前
ESP32-C61-WROOM-1-N8R2:Wi-Fi 6与RISC-V融合的中坚力量
人工智能·单片机·嵌入式硬件·物联网·智能家居·risc-v
炸膛坦客13 小时前
单片机/C/C++八股:(二十六)IIC 专题(I²C)---- 上集
c语言·c++·单片机
华清远见IT开放实验室17 小时前
实验室建设案例 | 石家庄科技信息职业学院嵌入式实验室——从底层硬件到系统应用,一所应用型高校的嵌入式人才培养这样落地
linux·arm开发·stm32·嵌入式硬件·高校·实验室建设
AI的探索之旅20 小时前
AI辅助原理图评审:电源去耦、BOOT引脚、VCAP——19项逐一核查,遗漏?不存在的
人工智能·vscode·嵌入式硬件
茯苓gao20 小时前
嵌入式开发笔记:EtherCAT协议从硬件到软件完整配置指南——从零搭建一套EtherCAT通信系统
笔记·嵌入式硬件·学习
GeekArch20 小时前
第24讲:Vibe模式代码风格控制——适配Keil/STM32工程规范
人工智能·stm32·单片机·嵌入式硬件·mcu·决策树·ai编程
Freedom_my21 小时前
STM32项目3
stm32·单片机·嵌入式硬件
国科安芯1 天前
星间光链路:AS32S601型抗辐射MCU在空间激光通信终端控制中的技术实现
服务器·网络·单片机·嵌入式硬件·物联网·安全·信息与通信