news 2026/7/22 20:29:06

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32-HAL--F429-HAL 库驱动框架(2026/7/22)

目录

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

第一层:句柄结构体

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

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

2.2 My_UART1_Init —— 你的总初始化

2.3 My_UART1_DMA_Init —— 用 DMA 模式时才调

第三层:三种操作模式

③-① 阻塞轮询 Polling

③-② 中断 IT

③-③ DMA

③-④ 辅助操作

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) { // 主循环跑业务逻辑 // 数据来了自动进回调,不用在这儿轮询 } }

三模式速查

你调的函数谁搬数据你什么时候拿到数据TimeoutCPU 占用
PollingTransmit/ReceiveCPU 等着搬函数返回时✅ 有全程占着
ITxxx_ITHAL 在 ISR 里搬回调里❌ 无每字节打断一次
DMAxxx_DMADMA 自己搬回调里(同 IT)❌ 无几乎不占

句柄六成员速查

成员谁填什么时候
Instance初始化时
Init初始化时
hdmatx/hdmarx(DMA 时)My_DMA_Init里挂上
LockHAL 库每次操作前后自动改
StateHAL 库每次操作前后自动改
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/22 20:27:10

太原同城瓷砖胶

在太原的装修市场上,瓷砖铺贴一直是业主和施工方关注的焦点。无论是新房装修还是旧房翻新,瓷砖胶作为关键的辅料,其选择与施工质量直接影响着家居的安全性与耐久性。然而,面对市场上琳琅满目的品牌与产品,许多太原业主…

作者头像 李华
网站建设 2026/7/22 20:23:24

Air框架高级教程:构建大型AI驱动Web应用的架构设计

Air框架高级教程:构建大型AI驱动Web应用的架构设计 【免费下载链接】air The first web framework designed for AI to write. Built on Python, FastAPI, Pydantic, and HTMX. By the authors of Two Scoops of Django. 项目地址: https://gitcode.com/gh_mirror…

作者头像 李华
网站建设 2026/7/22 20:23:22

太原本地口碑银元回收

在太原乃至整个山西的民间收藏市场中,银元回收始终是一个热门的变现领域。无论是祖辈传下的袁大头、龙洋,还是偶然发现的清代光绪元宝,许多家庭都面临着“怎么卖、卖给谁、值多少钱”的现实难题。然而,由于行业信息不透明、流动摊…

作者头像 李华
网站建设 2026/7/22 20:22:59

如何用Reqnroll实现高效行为驱动开发(BDD):从入门到精通

如何用Reqnroll实现高效行为驱动开发(BDD):从入门到精通 【免费下载链接】Reqnroll Open-source Cucumber-style BDD test automation framework for .NET. 项目地址: https://gitcode.com/gh_mirrors/re/Reqnroll Reqnroll是一个开源…

作者头像 李华
网站建设 2026/7/22 20:16:31

Arch Linux系统回滚完全手册:downgrade与pacman协同使用

Arch Linux系统回滚完全手册:downgrade与pacman协同使用 【免费下载链接】downgrade Downgrade packages in Arch Linux 项目地址: https://gitcode.com/gh_mirrors/do/downgrade 在Arch Linux系统中,软件包更新有时可能带来兼容性问题或意外行为…

作者头像 李华
网站建设 2026/7/22 20:11:11

arrow.nvim自定义快捷键:打造专属你的文件导航体验

arrow.nvim自定义快捷键:打造专属你的文件导航体验 【免费下载链接】arrow.nvim Bookmark your files, separated by project, and quickly navigate through them. 项目地址: https://gitcode.com/gh_mirrors/ar/arrow.nvim arrow.nvim是一款专为Neovim用户…

作者头像 李华