网站域名空间管理,南宁市建筑规划设计集团有限公司,端午节ppt模板免费下载,wordpress在哪看代码最近在看stm32的HAL库程序#xff0c;发现了一些非常重要的编写技巧#xff0c;也充分感受到了HAL库作者功力深厚#xff0c;所以不要因为STM32是单片机就小瞧了对应代码的含金量#xff0c;代码暗含了非常多#xff0c;也非常重要的一些编程理念#xff0c;这里做一下简…最近在看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里面的大循环。