STM32的HAL库中句柄变量的理解以及状态机思想 📅 发布时间:2026/7/8 18:38:23 👁️ 浏览次数: 最近在看stm32的HAL库程序发现了一些非常重要的编写技巧也充分感受到了HAL库作者功力深厚所以不要因为STM32是单片机就小瞧了对应代码的含金量代码暗含了非常多也非常重要的一些编程理念这里做一下简单记录1第一点HAL库中每一个源文件对应一个模块每一个源文件中都首先定义了一个非常复杂的句柄性质的数据类型这个类型包含了大量数据其中就包含Instance这个指向寄存器组的指针变量当然还包含了state状态用于记录程序所在状态parent成员变量用于重所在子变量找到对应父变量。源文件中的函数传参都包含这个自定义类型的参数变量用于在函数内部去读写里面的成员变量。例如HAL库中的I2C模块对应源文件和头文件是stm32f1xx_hal_i2c.c以及stm32f1xx_hal_i2c.h这一对文件。自定义i2c这个模块类型的句柄变量类型如下所示typedef struct { I2C_TypeDef *Instance; /*! I2C registers base address */ I2C_InitTypeDef Init; /*! I2C communication parameters */ uint8_t *pBuffPtr; /*! Pointer to I2C transfer buffer */ uint16_t XferSize; /*! I2C transfer size */ __IO uint16_t XferCount; /*! I2C transfer counter */ __IO uint32_t XferOptions; /*! I2C transfer options */ __IO uint32_t PreviousState; /*! I2C communication Previous state and mode context for internal usage */ DMA_HandleTypeDef *hdmatx; /*! I2C Tx DMA handle parameters */ DMA_HandleTypeDef *hdmarx; /*! I2C Rx DMA handle parameters */ HAL_LockTypeDef Lock; /*! I2C locking object */ __IO HAL_I2C_StateTypeDef State; /*! I2C communication state */ __IO HAL_I2C_ModeTypeDef Mode; /*! I2C communication mode */ __IO uint32_t ErrorCode; /*! I2C Error code */ __IO uint32_t Devaddress; /*! I2C Target device address */ __IO uint32_t Memaddress; /*! I2C Target memory address */ __IO uint32_t MemaddSize; /*! I2C Target memory address size */ __IO uint32_t EventCount; /*! I2C Event counter */ }I2C_HandleTypeDef;这个类型变量内部成员包含了I2c这个模块所有特征是所有特性的集合是咱在I2c这个模块角度考虑的成员变量比较多但是涵盖了I2C这个模块所有特点以及I2C功能实现所需数据。2第二点充分理解句柄变量含有Handle这个字段的含义。首先我认为句柄的含义是“起始变量集”起始的含义是无论函数操作那些数据都是从句柄变量开中查找读写获取的。变量集的意思是内部有非常多的变量。也就是说模块对应函数体内部不断的将数据的的值写入到从句柄变量开始找到的具体成员变量中也不断的从句柄变量开始查找所需成员变量中读取数据。这样做的好处非常明显大大降低了函数内部代码编写难度只需要不断将有用数据写入到句柄变量内部成员变量中不断从句柄变量成员中提取有用数据这里暗含了一种比较简洁的算法逻辑。有点像数组排序中的“冒泡排序”的做法。如果是人类因为人脑非常擅长总结抽象而不擅长计算人脑可以非常快的将数从大到小排列好排列的过程其实非常复杂。冒泡排序就不一样了无论数组成员数值是大于还是小于比较的值都从第一个元素开始比较。这样虽然表看做了非常多无用比较因为有些比较不需要互换位置但是由于计算机运行速度非常快这些浪费的时间基本可以忽略不计好处就是大大降低了计算机难度。总之充分利用句柄变量这种“记事本”的特点将所有有关数据都记录到这个“记事本”中我们需要的时候不用从大量复习习题中查找只需要到这个“记事本”中查找就能找到对应数据。就算有时候直接找对应习题是最快的办法也不要使用这种做法因为会增加算法复杂性。3HAL库中的“状态机”思维。首先我们都知道代码其实是一个while (1)的无条件死循环中不断运行也就说每一个函数都可以不断的被调用到在程序运行阶段不断的被调用执行。其次句柄变量中定义了名字为state的变量用于记录代码运行到这里的时候的状态在函数内部有大量代码判断执行当前状态如果状态合适才执行有效代码如果状态不合适直接返回或者发生超时时将status写入超时标志。从代码整体看整个程序其实就是一个非常大非常分散的状态机一般的状态机都是使用一个switch-case-break阶梯判断HAL库没有使用传统的状态机而是将状态机打散了在每一个函数中不断读取句柄变量的状态去指导后面代码执行代码执行后如果状态发生变化了更新状态。HAL_StatusTypeDef HAL_I2C_DisableListen_IT(I2C_HandleTypeDef *hi2c) { /* Declaration of tmp to prevent undefined behavior of volatile usage */ uint32_t tmp; /* Disable Address listen mode only if a transfer is not ongoing */ if(hi2c-State HAL_I2C_STATE_LISTEN) { tmp (uint32_t)(hi2c-State) I2C_STATE_MSK; hi2c-PreviousState tmp | (uint32_t)(hi2c-Mode); hi2c-State HAL_I2C_STATE_READY; hi2c-Mode HAL_I2C_MODE_NONE; /* Disable Address Acknowledge */ hi2c-Instance-CR1 ~I2C_CR1_ACK; /* Disable EVT and ERR interrupt */ __HAL_I2C_DISABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_ERR); return HAL_OK; } else { return HAL_BUSY; } }函数内部一进来就条件判断句柄变量hi2c中的State的值如果是HAL_I2C_STATE_LISTEN状态才处理否则直接返回结束函数调用。如果是LISREN状态做完必要工作后将hi2c指向的Status进行更新成了HAL_I2C_STATE_READY这样再次运行到函数内部的时候就直接返回了也可以指导其他函数中如果满足hi2c-State HAL_I2C_STATE_READY时运行对应代码。4主程序是一个大死循环所有函数都有无数次机会得到运行冗余重复代码不要写。将这段冗余代码只放在某一个函数体内部形成一份需要的时候能够确保冗余代码所在函数被调用执行即可。例如HAL_StatusTypeDef HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout);该函数内部有一个while(hi2c-XferSize 0U)的循环实现了真正数据从内存到寄存器中写入也就是完成了真正干活代码如下图所示hi2c-Instance-DR (*hi2c-pBuffPtr); hi2c-XferCount--; hi2c-XferSize--;在中断主发送函数中就没有这个大循环而是只将有效数据写入到记事本i2c中。HAL_StatusTypeDef HAL_I2C_Master_Transmit_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size) /**中断就不需要超时处理了 */ { __IO uint32_t count 0U; if(hi2c-State HAL_I2C_STATE_READY) { /* Wait until BUSY flag is reset */ count I2C_TIMEOUT_BUSY_FLAG * (SystemCoreClock /25U /1000U); do { if(count-- 0U) { hi2c-PreviousState I2C_STATE_NONE; hi2c-State HAL_I2C_STATE_READY; /* Process Unlocked */ __HAL_UNLOCK(hi2c); return HAL_TIMEOUT; } } while(__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_BUSY) ! RESET); /* Process Locked */ __HAL_LOCK(hi2c); /* Check if the I2C is already enabled */ if((hi2c-Instance-CR1 I2C_CR1_PE) ! I2C_CR1_PE) { /* Enable I2C peripheral */ __HAL_I2C_ENABLE(hi2c); } /* Disable Pos */ hi2c-Instance-CR1 ~I2C_CR1_POS; hi2c-State HAL_I2C_STATE_BUSY_TX; hi2c-Mode HAL_I2C_MODE_MASTER; hi2c-ErrorCode HAL_I2C_ERROR_NONE; /* Prepare transfer parameters */ hi2c-pBuffPtr pData; hi2c-XferCount Size; hi2c-XferOptions I2C_NO_OPTION_FRAME; hi2c-XferSize hi2c-XferCount; hi2c-Devaddress DevAddress; /* Generate Start */ hi2c-Instance-CR1 | I2C_CR1_START; /* Process Unlocked */ __HAL_UNLOCK(hi2c); /* Note : The I2C interrupts must be enabled after unlocking current process to avoid the risk of I2C interrupt handle execution before current process unlock */ /* Enable EVT, BUF and ERR interrupt */ __HAL_I2C_ENABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_BUF | I2C_IT_ERR); return HAL_OK; } else { return HAL_BUSY; } }主要原因是在整个主程序其实是一个大循环每一个函数都有无数次被循环运行到这段冗余代码只需要写在一个函数“HAL_I2C_Master_Transmit”中当发生中断的时候跳转到“HAL_I2C_Master_Transmit_IT”中对句柄变量hi2c指向的变量写入有效数据并及时跳出中断服务程序这就是尽量只在中断服务程序中做必要的部分让中断服务程序尽量短一些这样有利于提高代码相应及时性运行主程序的时候就有机会运行HAL_I2C_Master_Transmit里面的大循环。
开源自动采集壁纸源码系统 带完整的搭建部署教程 温馨提示:文末有资源获取方式踏入图片内容领域,你是否被内容来源和网站体验两座大山挡住了去路?无需再彷徨,一款集自动化内容聚合与顶级前端体验于一身的壁纸网站源码系统现已登场,它将复杂的技术难题打包解决… 2026/7/8 18:37:29
私有知识库+本地300种模型一键部署,私密性拉满!这个国产开源工具太绝了 使用的大模型并不是本地部署,都是远程调用大模型厂商的API。然后我发现评论区很多朋友的使用场景对私密性要求很高,希望能全链路(包括大模型)本地部署,整个RAG过程完全在内网,这样才能保证数据安全。 虽然本… 2026/5/17 2:46:21
javascript之双重循环打印九九乘法表 javascript通过双重循环打印九九乘法表案例这里需要用到双重循环,i是控制行,j是控制列,j的值最多等于ifor(let i1;i<9;i){for(let j1;j<i;j){document.write(j*i(i*j))document.write(" ")}document.write(<br>… 2026/7/8 7:00:02
Matlab鸽群优化PIO调参的深度极限学习机DELM回归预测工具包 本文还有配套的精品资源,点击获取 简介:一套开箱即用的Matlab预测工具包,聚焦多输入单输出回归任务,核心是用鸽群优化算法(PIO)自动搜索深度极限学习机(DELM)的最优权重和偏置参数… 2026/7/8 18:37:45
Badge 应用场景与落地实践指南 在运营一款用户产品时,我们常会遇到这样的瓶颈:功能完善、内容充足,但用户的活跃度却始终不温不火,留存率也难以突破。很多时候,问题并不出在核心业务逻辑上,而是缺乏一套能够持续激发用户内在动力的机制。… 2026/7/8 18:37:45
VS Code Agents窗口:原生AI协作者工作流详解 1. 项目概述:这不是一次普通更新,而是IDE工作流的范式迁移 “VS Code 迎来史诗级更新:全新 Agents 窗口发布”——这个标题在开发者社区刷屏时,我正用它调试一个嵌入式Python脚本。没点开公告前,我以为又是主题色微调或… 2026/7/8 18:35:43
GitHub Copilot 2026.6深度解析:1M上下文、可配置推理与Agent API实战 1. 这不是一次普通更新:Copilot 正在从“代码补全工具”蜕变为“开发协作者”2026年6月4日这个时间点,对很多长期用 GitHub Copilot 的人来说,可能只是日历上一个普通日期。但当你真正打开 VS Code 或 JetBrains IDE,看到状态栏里… 2026/7/8 18:35:43
国内Codex本地部署指南:VSCode插件+Ollama+Qwen2.5-Coder全链路实践 1. 项目概述:这不是“AI编程助手”的安装教程,而是一次对本地化智能开发工作流的重新校准Codex 这个名字,在2026年已经不再指向那个早已停服的旧模型服务,而是国内开发者社区对一类深度集成于VSCode、具备代码理解与生成能力、且能… 2026/7/8 18:35:43
OpenClaw 2.6.6 Win10原生部署指南:零基础落地本地化技能自动化 1. 项目概述:这不是一个普通软件安装,而是一次面向 Win10 环境的 OpenClaw 2.6.6 本地化能力落地实践 OpenClaw 这个名字最近在技术圈里出现频率明显升高,尤其在需要快速构建轻量级自动化工作流、本地化技能调用或私有化 API 封装的场景中。它… 2026/7/8 18:25:37
BetterNCM安装器:高效管理网易云插件的最佳选择 BetterNCM安装器:高效管理网易云插件的最佳选择 【免费下载链接】BetterNCM-Installer 一键安装 Better 系软件 项目地址: https://gitcode.com/gh_mirrors/be/BetterNCM-Installer 还在为网易云音乐插件的繁琐安装流程而烦恼吗?BetterNCM安装器是… 2026/7/8 0:02:48
运动控制系统安全设置对比:ECI3808的3种限位保护与急停逻辑实现 运动控制系统安全机制深度解析:限位保护与急停逻辑的设计哲学在精密制造与自动化领域,运动控制系统的安全设计绝非简单的功能堆砌,而是一套融合了机械工程、电气原理和软件算法的防御体系。当一台数控机床以每分钟数万转的速度运转࿰… 2026/7/8 0:06:48
AI大模型应用开发:小白也能抓住的红利风口,收藏这篇入门指南! 文章指出,虽然微软等科技巨头在裁员,但英伟达等公司却在积极扩招AI相关人才,尤其是具身智能、仿真等领域。AI行业正在经历结构性调整,传统岗位被淘汰,而大模型应用开发等新岗位需求旺盛。对于想转行或学习AI的普通人来… 2026/7/8 0:10:49
6个月转型AI工程师:实战路径与核心技能 1. 项目概述:6个月转型AI工程师的可行性路径在2023年大模型技术爆发的背景下,AI工程师岗位需求同比增长217%(LinkedIn数据)。不同于传统算法工程师需要3-5年培养周期,现代AI工程师更侧重工程化落地能力。我在硅谷科技公… 2026/7/7 11:26:57
TPAFE0808与PIC18F87K22的多通道信号采集方案 1. 项目背景与核心需求在工业自动化、医疗设备和科研仪器等领域,多通道信号采集与系统监测是基础且关键的技术需求。传统方案往往面临通道数量不足、信号调理复杂、系统集成度低等问题。TPAFE0808作为一款8通道模拟前端芯片,与PIC18F87K22微控制器的组合… 2026/7/7 11:26:57
STC3115与PIC18LF26K80构建高精度电池管理系统 1. STC3115与PIC18LF26K80在电池管理系统中的核心价值在现代电子设备中,电池管理系统(BMS)的重要性不亚于设备的核心处理器。STC3115作为一款高精度电池电量监测IC,与PIC18LF26K80微控制器的组合,构成了一个既能精确监控又能智能管理的完整解… 2026/7/8 14:25:08