news 2026/4/16 10:43:54

STM32的HAL库中句柄变量的理解以及状态机思想

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32的HAL库中句柄变量的理解以及状态机思想

最近在看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这个字段)的含义。

首先,我认为句柄的含义是“起始变量集”,起始的含义是无论函数操作那些数据都是从句柄变量开中查找读写获取的。变量集的意思是内部有非常多的变量。也就是说模块对应函数体内部不断的将数据的的值写入到从句柄变量开始找到的具体成员变量中,也不断的从句柄变量开始查找所需成员变量中读取数据。这样做的好处非常明显,大大降低了函数内部代码编写难度,只需要不断将有用数据写入到句柄变量内部成员变量中,不断从句柄变量成员中提取有用数据,这里暗含了一种比较简洁的算法逻辑。有点像数组排序中的“冒泡排序”的做法。如果是人类,因为人脑非常擅长总结抽象而不擅长计算,人脑可以非常快的将数从大到小排列好,排列的过程其实非常复杂。冒泡排序就不一样了,无论数组成员数值是大于还是小于比较的值,都从第一个元素开始比较。这样虽然表看做了非常多无用比较(因为有些比较不需要互换位置),但是由于计算机运行速度非常快,这些浪费的时间基本可以忽略不计,好处就是大大降低了计算机难度。

总之,充分利用句柄变量这种“记事本”的特点,将所有有关数据都记录到这个“记事本”中,我们需要的时候,不用从大量复习习题中查找,只需要到这个“记事本”中查找就能找到对应数据。就算有时候直接找对应习题是最快的办法,也不要使用这种做法,因为会增加算法复杂性。

(3)HAL库中的“状态机”思维。

首先,我们都知道,代码其实是一个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里面的大循环。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/16 11:02:07

【RAG新范式】超越向量搜索:企业级知识库构建必知的3大RAG高级策略

【RAG新范式】超越向量搜索&#xff1a;企业级知识库构建必知的3大RAG高级策略 摘要&#xff1a;本文深度剖析企业级知识库构建中RAG&#xff08;检索增强生成&#xff09;技术的进阶实践。通过电商客服系统案例&#xff0c;我们将揭示传统向量搜索的三大瓶颈&#xff1a;语义鸿…

作者头像 李华
网站建设 2026/4/16 11:09:05

Flutter-OH三方库适配:从兼容性检查到社区提交的完整指南

Flutter-OH三方库适配&#xff1a;从兼容性检查到社区提交的完整指南 欢迎大家加入开源鸿蒙跨平台开发者社区&#xff1a; 随着 OpenHarmony&#xff08;OH&#xff09;生态的快速发展&#xff0c;将成熟的 Flutter 应用迁移到鸿蒙平台已成为许多开发者的选择。然而&#xff…

作者头像 李华
网站建设 2026/4/16 12:41:45

金融系统OA如何集成百度编辑器的PDF转存功能?

河南某集团企业项目需求评估与实施记录&#xff08;基于UEditor的信创兼容方案&#xff09; 一、项目背景与核心需求 作为集团项目负责人&#xff0c;需在企业网站后台管理系统&#xff08;基于UEditor、Vue2/Vue3/React前端、SpringBoot后端&#xff09;中新增以下功能&…

作者头像 李华
网站建设 2026/4/15 21:23:44

AI 智能体的开发

AI 智能体的开发已从简单的“提示词工程”进化为“以工作流为核心的工程化开发”。目前的开发方法论核心在于&#xff1a;不只依赖模型性能&#xff0c;而是通过结构化的设计来弥补模型的随机性。以下是 2026 年主流的 AI 智能体开发方法论&#xff1a;1. 核心设计模式目前的开…

作者头像 李华
网站建设 2026/4/16 11:59:18

打造个性壁纸库?看这里!支持HTTPS+瀑布流的全自动采集建站

温馨提示&#xff1a;文末有资源获取方式想搭建一个与众不同的壁纸分享站&#xff0c;却苦恼于内容更新和用户体验&#xff1f;一款融合了自动采集、优雅设计与强大扩展性的源码系统&#xff0c;正是你苦苦寻觅的答案。它不仅能让你的网站“活”起来&#xff0c;还能让它“美”…

作者头像 李华
网站建设 2026/4/16 12:00:08

Python装饰器:动态增强函数的神器

python 装饰器是什么 装饰器(Decorator) 是 Python 中一种奇妙的“包装”技术。它允许你在不修改原有函数代码的情况下,给函数动态地添加新功能。 想象一下:你写了一个函数,现在想给它加个“执行耗时统计”的功能。你不需要去改动函数内部,只需要在函数头上戴顶“帽子”…

作者头像 李华