news 2026/7/9 9:26:03

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/7/9 8:22:04

国家碳达峰试点城市建设下,企业园区该怎么做近零碳?

安科瑞刘鸿鹏172 一 ⑥⑨⑦② 一 5322从“近零碳试点”到“可运行落地”&#xff1a;EMS3.0如何成为常州碳达峰的加速器 在推进“双碳”战略的大背景下&#xff0c;常州市提出了加快培育近零碳试点的重要部署&#xff0c;将其作为助力国家碳达峰试点城市建设的重要抓手。政策明…

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

How to Share A Folder in Ubuntu 23.10 Updated [Fix ‘Error 255’ issue]

Contents: 1. Install the nautilus-share. 2. Start sharing a folder Fix “Error 255” permission denied error! 3. Setup a password for authentication 4. How to access the shared folder Want to share a folder over local network and allow access with cr…

作者头像 李华
网站建设 2026/6/26 10:39:29

量子机器学习:三行代码入门的2026指南

一、量子机器学习基础&#xff1a;测试从业者须知 量子机器学习&#xff08;QML&#xff09;利用量子比特的叠加与纠缠特性&#xff0c;加速经典机器学习任务&#xff0c;如分类、优化和模式识别。其核心优势在于处理高维数据时显著降低计算复杂度——例如&#xff0c;量子谱聚…

作者头像 李华
网站建设 2026/7/4 15:58:54

能耗管理平台和能源管理平台有什么区别?哪家好用?

在“双碳”目标深入推进、企业降本增效需求日益迫切的当下&#xff0c;数字化能源管控工具已成为各行各业的必备品。但很多企业在选型时&#xff0c;常会混淆“能耗管理平台”和“能源管理平台”两个概念&#xff0c;甚至将二者等同看待&#xff0c;最终导致所选系统无法匹配自…

作者头像 李华
网站建设 2026/7/4 10:38:08

2026别错过!9个AI论文软件测评:自考毕业论文写作必备工具推荐

2026年&#xff0c;随着AI技术在学术领域的不断深入&#xff0c;越来越多的自考学生开始依赖智能写作工具来提升论文撰写效率。然而&#xff0c;面对市场上种类繁多的AI论文软件&#xff0c;如何选择真正适合自己需求的产品成为一大难题。本文基于2026年的实际使用数据与用户反…

作者头像 李华
网站建设 2026/6/26 10:39:31

基于PLC的注塑机控制系统设计与调试

基于PLC的注塑机控制系统设计与调试 第一章 系统设计目标与核心需求 基于PLC的注塑机控制系统以“精准控压、时序可控、工艺可配、安全可靠”为核心设计目标&#xff0c;依托PLC工业级逻辑控制与抗干扰优势&#xff0c;突破传统注塑机继电器控制精度低、故障率高、工艺调整繁琐…

作者头像 李华