当前位置:   article > 正文

STM32 HAL——GPIO_stm32 hal库 gpio pin source

stm32 hal库 gpio pin source

HAL的代码规范建议

以下内容是我自己参照HAL做的总结,如果公司有固定的编码规范,就跟公司保持一致,如果没有,那就推荐和所用库保持一致。注意,因人而异,并不具有普遍适用性。

HAL库有如下代码规范:

1、目录名除了专有词汇外,统一是单词首字母大写,其他小写,比如MyApplication,STM32Project,如果有多个单词,可使用下划线或者横杠隔开,比如MDK-ARM、STM32F1xx_HAL_Driver等;

2、文件名统一小写,相应的,头文件包含时也是小写,比如:

#include "stm32f1xx_hal_def.h"

3、Debug宏统一用大写,末尾加下划线_H,也可在前后各加上下划线;比如

  1. #ifndef __STM32F1xx_HAL_DMA_H
  2. #define __STM32F1xx_HAL_DMA_H
  3. ……
  4. #endif

4、条件编译用小写或者大写,比如:

  1. #ifdef __cplusplus
  2. extern "C" {
  3. #endif
  4. #ifdef HAL_DMA_MODULE_ENABLED
  5. //推荐用大写

5、宏定义统一用大写;

6、枚举内统一用大写;

7、函数名、变量名可采用首字母大写的方式,专有名词可直接写,适当时加上下划线来分隔,比如:

  1. HAL_DMA_CallbackIDTypeDef;
  2. Lock;

以上仅供参考,有些地方并不固定,比如变量名,有些地方就用的全小写,我建议是变量名全小写,函数名大小写。

在HAL中,一个缩进用的是2个空格,不过,我们在编程时,通常使用4个空格。

结构体封装

在HAL编程中,大量使用结构体来对代码进行封装。

如何封装呢?

对于每一个外设,都有一个单独的文件,可以看做一个类,类里面有属性和方法,对应的就是变量和函数。然后,怎么方便地去访问这些变量和函数呢?在java中,我们可以直接通过创建对象来访问。那么,在c语言中,怎么去模拟这种访问方式呢?

为外设定义一个结构体,将对应的变量和函数放进结构体中。

我们使用时,通过创建结构体变量,类似于创建了一个对象,用结构体访问元素的方式来访问外设变量和调用外设函数。

注意,是HAL里大量使用了这种思想来实现封装。我们使用HAL库的时候,可以不用去管,先会用即可。但是,推荐看懂源码。

比如,为了配置GPIO口,在GPIO的驱动文件stm32f1xx_hal_gpio.h中,定义了一个结构体类型:

里面的引脚、模式、上下拉、速度,都在该头文件下方做了对应的声明(枚举或者宏定义)

之后,我们在进行业务开发时,比如取名叫gpio.c,在此文件中:

可以看到,先初始化了一个GPIO_InitTypeDef,赋初值为0。

之后就是时钟初始化;设置引脚;接着,就是通过结构体变量来设置引脚的属性;最后,进行初始化。

这就是结构体封装的方式。

理论上,可以不用结构体,而是把这些变量定义成全局变量,直接包含头文件,然后直接赋值即可。但是,这样会有一些弊端,比如太零散,代码不便阅读和维护,还有全局变量本身就不建议定义在头文件中后被调用。

在GPIO中,函数并没有被纳入结构体之中,而是直接被调用的。函数可以被重复调用。

可以说,引入结构体封装主要是为了代码的可阅读性和规范性,更易理解。

程序框架思想

我们通常是使用MX来生成初始化代码,之后又要按注释在里面写上我们自己的代码,但是,如果我们自己的代码和MX代码互相渗透,那么,当我修改MX后,很有可能导致用户代码丢失,并且,这样掺杂的结构,会给后续的维护、移植等带来不便,大大增加了工作量。

所以,合理地进行框架设计,是很有必要的。在前辈大神那里学到一种架构方式,可供参考学习。

一、在项目中再添加一个目录,专门用于管理用户代码,比如命名为MyApplication,同时,建议在该目录下创建MyAppInc和MyAppSrc目录,分别用于存放相应的c和h文件。在该目录中,添加以下4个文件以及对应的头文件,分别为:

  • public.c,公共文件,用于存放那些公共使用的代码,比如延时函数;

  • callback.c,回调文件,回调函数都写在这个文件中;

  • system.c,系统文件,跟系统有关的,比如运行、待机等函数;

  • myinit.c,初始化文件,未被系统初始化的其他一些外设的初始化。

二、新增MyApplication.h头文件,包含所有用户代码的头文件与外设头文件,当调整外设或者用户文件时,只需要调整此文件内相应头文件即可。

三、main.c标准化,之后,无需要调整main函数,标准化内容如下:

1、添加头文件集合;

2、添加用户初始化函数;

3、标准化主循环;

4、标准化错误处理函数;

  1. 标准化断言失败处理函数;

  1. /* USER CODE BEGIN Header */
  2. /**
  3. ******************************************************************************
  4. * @file : main.c
  5. * @brief : Main program body
  6. ******************************************************************************
  7. * @attention
  8. *
  9. * <h2><center>&copy; Copyright (c) 2020 STMicroelectronics.
  10. * All rights reserved.</center></h2>
  11. *
  12. * This software component is licensed by ST under BSD 3-Clause license,
  13. * the "License"; You may not use this file except in compliance with the
  14. * License. You may obtain a copy of the License at:
  15. * opensource.org/licenses/BSD-3-Clause
  16. *
  17. ******************************************************************************
  18. */
  19. /* USER CODE END Header */
  20. /* Includes ------------------------------------------------------------------*/
  21. #include "main.h"
  22. #include "gpio.h"
  23. /* Private includes ----------------------------------------------------------*/
  24. /* USER CODE BEGIN Includes */
  25. #include "MyApplication.h"
  26. /* USER CODE END Includes */
  27. /* Private typedef -----------------------------------------------------------*/
  28. /* USER CODE BEGIN PTD */
  29. /* USER CODE END PTD */
  30. /* Private define ------------------------------------------------------------*/
  31. /* USER CODE BEGIN PD */
  32. /* USER CODE END PD */
  33. /* Private macro -------------------------------------------------------------*/
  34. /* USER CODE BEGIN PM */
  35. /* USER CODE END PM */
  36. /* Private variables ---------------------------------------------------------*/
  37. /* USER CODE BEGIN PV */
  38. /* USER CODE END PV */
  39. /* Private function prototypes -----------------------------------------------*/
  40. void SystemClock_Config(void);
  41. /* USER CODE BEGIN PFP */
  42. /* USER CODE END PFP */
  43. /* Private user code ---------------------------------------------------------*/
  44. /* USER CODE BEGIN 0 */
  45. /* USER CODE END 0 */
  46. /**
  47. * @brief The application entry point.
  48. * @retval int
  49. */
  50. int main(void)
  51. {
  52. /* USER CODE BEGIN 1 */
  53. /* USER CODE END 1 */
  54. /* MCU Configuration--------------------------------------------------------*/
  55. /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  56. HAL_Init();
  57. /* USER CODE BEGIN Init */
  58. /* USER CODE END Init */
  59. /* Configure the system clock */
  60. SystemClock_Config();
  61. /* USER CODE BEGIN SysInit */
  62. /* USER CODE END SysInit */
  63. /* Initialize all configured peripherals */
  64. MX_GPIO_Init();
  65. /* USER CODE BEGIN 2 */
  66. MyInit.Peripheral_Set();
  67. /* USER CODE END 2 */
  68. /* Infinite loop */
  69. /* USER CODE BEGIN WHILE */
  70. while (1)
  71. {
  72. System.Run();
  73. /* USER CODE END WHILE */
  74. /* USER CODE BEGIN 3 */
  75. }
  76. /* USER CODE END 3 */
  77. }
  78. /**
  79. * @brief System Clock Configuration
  80. * @retval None
  81. */
  82. void SystemClock_Config(void)
  83. {
  84. RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  85. RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
  86. /** Initializes the CPU, AHB and APB busses clocks
  87. */
  88. RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  89. RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  90. RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  91. RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  92. RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  93. RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  94. RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  95. if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  96. {
  97. Error_Handler();
  98. }
  99. /** Initializes the CPU, AHB and APB busses clocks
  100. */
  101. RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
  102. |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  103. RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  104. RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  105. RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  106. RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
  107. if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  108. {
  109. Error_Handler();
  110. }
  111. /** Enables the Clock Security System
  112. */
  113. HAL_RCC_EnableCSS();
  114. }
  115. /* USER CODE BEGIN 4 */
  116. /* USER CODE END 4 */
  117. /**
  118. * @brief This function is executed in case of error occurrence.
  119. * @retval None
  120. */
  121. void Error_Handler(void)
  122. {
  123. /* USER CODE BEGIN Error_Handler_Debug */
  124. /* User can add his own implementation to report the HAL error return state */
  125. System.Error_Handler();
  126. /* USER CODE END Error_Handler_Debug */
  127. }
  128. #ifdef USE_FULL_ASSERT
  129. /**
  130. * @brief Reports the name of the source file and the source line number
  131. * where the assert_param error has occurred.
  132. * @param file: pointer to the source file name
  133. * @param line: assert_param error line source number
  134. * @retval None
  135. */
  136. void assert_failed(uint8_t *file, uint32_t line)
  137. {
  138. /* USER CODE BEGIN 6 */
  139. /* User can add his own implementation to report the file name and line number,
  140. tex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  141. System.Assert_Failed();
  142. /* USER CODE END 6 */
  143. }
  144. #endif /* USE_FULL_ASSERT */
  145. /************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

注意:每次添加新的文件后,都要确保项目设置中,将对应头文件路径包含进去。

进一步理解封装思想

在上述框架的封装中,不会对外暴露文件中的函数或者全局变量。而是定义一个结构体,将对应的全局变量或者函数包括进来,然后对外通过结构体变量去访问,这就是面向对象的思想。比如:

为了实现外设初始化,本来,我可以将函数声明出去供其他地方直接调用,但是我不这么做,而是将这些函数都封装成一个结构体,然后定义一个结构体全局变量,并在头文件中通过extern声明出去,这样,外部就可以通过结构体访问元素的方式来访问对应的函数。

//MyInit.h

  1. #ifndef __MyInit_H__
  2. #define __MyInit_H__
  3. //定义结构体类型
  4. typedef struct
  5. {
  6. void (*Peripheral_Set)(void);
  7. } MyInit_t;
  8. /* extern variables-----------------------------------------------------------*/
  9. extern MyInit_t MyInit;
  10. /* extern function prototypes-------------------------------------------------*/
  11. #endif
  12. /********************************************************
  13. End Of File
  14. ********************************************************/

//MyInit.c

  1. /* Includes ------------------------------------------------------------------*/
  2. #include "MyApplication.h"
  3. /* Private define-------------------------------------------------------------*/
  4. /* Private variables----------------------------------------------------------*/
  5. static void Peripheral_Set(void);
  6. /* Public variables-----------------------------------------------------------*/
  7. MyInit_t MyInit =
  8. {
  9. Peripheral_Set
  10. };
  11. /* Private function prototypes------------------------------------------------*/
  12. /*
  13. * @name Peripheral_Set
  14. * @brief 外设设置
  15. * @param None
  16. * @retval None
  17. */
  18. static void Peripheral_Set()
  19. {
  20. }
  21. /********************************************************
  22. End Of File
  23. ********************************************************/

GPIO闪烁

一、先认真阅读参考手册中GPIO部分内容

GPIO部分大概有25页,关键内容记录如下:

1、每个GPI/O端口有两个32位配置寄存器(GPIOx_CRL,GPIOx_CRH),两个32位数据寄存器 (GPIOx_IDR和GPIOx_ODR),一个32位置位/复位寄存器(GPIOx_BSRR),一个16位复位寄存器(GPIOx_BRR)和一个32位锁定寄存器(GPIOx_LCKR)。

2、端口可配置四种输入和四种输出:

─ 输入浮空

─ 输入上拉

─ 输入下拉

─ 模拟输入

─ 开漏输出

─ 推挽式输出

─ 推挽式复用功能

─ 开漏复用功能

3、端口位输出表:

4、必须以字(32位)的方式操作这些外设寄存器。

……

更多内容详见参考手册。

二、GPIO有哪些变量和函数?

……

更多内容详见源代码。

三、学会使用这些函数

  1. static void Run(void)
  2. {
  3. HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin);
  4. HAL_Delay(100);
  5. }

以上代码可以实现LED1的闪烁。

注意这里的名字中的LED1和MX中的标签名是一致的。

GPIO复用和重映射

STM32上有很多I/O口,也有很多的内置外设如I2C、ADC、ISP、USART等,为了节省引出管脚,这些内置外设基本上是与I/O口共用管脚的,也就是I/O管脚的复用功能。

很多复用的I/O引脚可以通过重映射功能从其他的I/O管脚引出,即复用功能的引脚是可通过程序改变的。具体查看技术手册。

数据手册上有说明:

阅读GPIO相关源码

从main函数开始。。。

一开始就是HAL初始化HAL_Init();这个函数是干什么的?在哪定义的?在哪声明的?

注意:在c中,基本上都是c和h文件成双成对的,头文件是在c文件前面被展开的。所以,需要配合头文件和c文件才完整。要看就两个一起看。

main.h

在该头文件中,包含了一个头文件stm32f1xx_hal.h,这个头文件看起来像是总的头文件,因为它的命名中,只有32和hal,没有其他任何外设的信息。

有错误处理函数的声明,另外有个void SystemClock_Config(void);是main的私有函数,所以就没有声明在头文件中,不过,该私有函数并没有加上static来限制其私有性。不加也可以,只要没有声明出去,别的地方调用时就会报错,但是此时可以选择声明出去。而一旦加上了static,就无法声明出去了,一旦声明出去就会报错。

该头文件中的这段代码关注一下,这是main.h中最关键的代码:

  1. /* Private defines -----------------------------------------------------------*/
  2. #define LED1_Pin GPIO_PIN_4
  3. #define LED1_GPIO_Port GPIOE
  4. #define LED2_Pin GPIO_PIN_5
  5. #define LED2_GPIO_Port GPIOE
  6. #define LED3_Pin GPIO_PIN_6
  7. #define LED3_GPIO_Port GPIOE

这里对应的是MX中配置引脚时的标签名,本来我们还要去查看原理图去查看配置的到底是哪个端口,这个端口的哪个引脚,但是,这里自动生成了相应的宏定义,通过这种定义别名的方式,让我们直接面向“对象”编程。用LED1/LED2/LED3相关符号就可以了。

上面宏定义中的GPIO_PIN_4和GPIOE这些名称是啥意思?打开定义和声明查看。

首先,看到这些名称都是大写,猜想可能是一个宏定义。

先打开GPIO_PIN_4的声明/定义(对于宏定义来说,打开声明或者定义都是同一个地方)

跳转到了stm32f1xx_hal_gpio.h文件。

里面对各个引脚进行了编号。

用的是个16位的二进制数,通过最低位到高位依次赋予高电平来选择。

uint16_t是什么意思?

再次跳转定义。

发现无法跳转。说明其既不是宏定义,也不是变量,而是一种c语言的语法。

其实,我知道这是单片机中自定义的一种类型,也就是unsigned short,只不过进行了类型重定义,以简化使用。

怎么找到其源头?

既然能使用,那么肯定有地方进行了类型重定义,要么就在文件上面,要么就在包含的头文件中。

上面没有,那就只能在头文件中。其包含了一个头文件stm32f1xx_hal_def.h

  1. ******************************************************************************
  2. * @file stm32f1xx_hal_def.h
  3. * @author MCD Application Team
  4. * @brief This file contains HAL common defines, enumeration, macros and
  5. * structures definitions.
  6. ******************************************************************************

可知该头文件包含了HAL共用的定义、枚举、宏定义以及结构体定义。

但在该头文件中,依然没有找到uint16_t,那就只能继续找其包含的头文件stm32f1xx.h,看名字像一个最顶层的头文件。继续找,还是没找到。

还是没找到,奇了怪了。。。。。。。。。。。。。。。。。。。。。。。

那就只能直接搜索了。搜索太多了,不好找。

无意中发现,uint32_t右键能够跳转。。。。跳到了stdint.h文件。

  1. ……
  2. /* exact-width unsigned integer types */
  3. typedef unsigned char uint8_t;
  4. typedef unsigned short int uint16_t;
  5. typedef unsigned int uint32_t;
  6. typedef unsigned __INT64 uint64_t;
  7. ……

stdint.h是c99中引进的一个标准C库的头文件,里面定义了一些整数类型,具体参考:关于stdint.h头文件_willorfang的博客-CSDN博客

继续看GPIOE是啥意思?

跳转,打开了stm32f103xe.h,这个就跟具体型号有关了,具体到了103这一款。

看其描述:

  1. /** @addtogroup Peripheral_declaration
  2. * @{
  3. */

这好像是所有外设的什么声明,具体看:

以GPIOE为例

#define GPIOE               ((GPIO_TypeDef *)GPIOE_BASE)

该宏定义替换后,是后面的内容,看起来像是把一个宏定义内容进行强制类型转换,那么,被转换的是什么呢?又是转换成了什么类型呢?最终的效果又是什么呢?

在该文件上面,找到了宏定义:

#define GPIOE_BASE            (APB2PERIPH_BASE + 0x00001800UL)

可知,被转换的好像是个地址,一个基地址再加上一个数,继续查找基地址:

#define APB2PERIPH_BASE       (PERIPH_BASE + 0x00010000UL)

继续查找PERIPH_BASE

#define PERIPH_BASE           0x40000000UL /*!< Peripheral base address in the alias region */

涉及到基地址加上一个数,联想到基地址加偏移量,又提到了别名区,难道是位带操作?

具体查找数据手册,看内存映射图中外设起始地址是哪个。

或者查看参考手册中,存储器映像表,看起始地址。

0x4000 0000 - 0x4000 03FF TIM2定时器

可知,起始地址为:0x4000 0000

上面的地址经过相加,可得出为0x4001 1800,查阅手册得知,正好是端口E的地址。0x4001 1800 - 0x4001 1BFF GPIO端口E

由此可知,GPIOE表示的就是端口E的地址所对应的值。

此时,只是一个值,被转成了一个指针。

那么,又转换成了什么类型的指针呢?

那就要看,GPIO_TypeDef,是怎么定义的?

  1. typedef struct
  2. {
  3. __IO uint32_t CRL;
  4. __IO uint32_t CRH;
  5. __IO uint32_t IDR;
  6. __IO uint32_t ODR;
  7. __IO uint32_t BSRR;
  8. __IO uint32_t BRR;
  9. __IO uint32_t LCKR;
  10. } GPIO_TypeDef;

这是一个结构体,定义了GPIO每个端口的寄存器。

因为,结构体的指针,指向的是首元素的首地址,所以,上面的强制转换的含义就是,指明端口E每一个寄存器的地址。

搞了这么多,就是定义了端口E各个寄存器的地址。虽然略显复杂,但是实现了标准化。

所以,GPIOE啥意思?就是端口E对应寄存器结构体的地址。所以对端口E的操作都基于GPIOE,其他端口,甚至所有外设,同理。

再回过头看看stm32f103xe.h的功能说明:

  1. * @file stm32f103xe.h
  2. * @author MCD Application Team
  3. * @brief CMSIS Cortex-M3 Device Peripheral Access Layer Header File.
  4. * This file contains all the peripheral register's definitions, bits
  5. * definitions and memory mapping for STM32F1xx devices.
  6. *
  7. * This file contains:
  8. * - Data structures and the address mapping for all peripherals
  9. * - Peripheral's registers declarations and bits definition
  10. * - Macros to access peripheral抯 registers hardware

HAL_Init();

看名称就知道是HAL库的初始化,那么HAL库的初始化要做哪些事情呢?

先看声明:

跳转打开了stm32f1xx_hal.h

  1. /* Initialization and de-initialization functions */
  2. HAL_StatusTypeDef HAL_Init(void);
  3. HAL_StatusTypeDef HAL_DeInit(void);
  4. ……

可知,其返回一个什么,可查看到返回的是一个状态码,该状态码是个枚举类型,定义在头文件stm32f1xx_hal_def.h中,

  1. /**
  2. * @brief HAL Status structures definition
  3. */
  4. typedef enum
  5. {
  6. HAL_OK = 0x00U,
  7. HAL_ERROR = 0x01U,
  8. HAL_BUSY = 0x02U,
  9. HAL_TIMEOUT = 0x03U
  10. } HAL_StatusTypeDef;

再跳转到对应c中去看定义:

  1. HAL_StatusTypeDef HAL_Init(void)
  2. {
  3. /* Configure Flash prefetch */
  4. #if (PREFETCH_ENABLE != 0)
  5. #if defined(STM32F101x6) || defined(STM32F101xB) || defined(STM32F101xE) || defined(STM32F101xG) || \
  6. defined(STM32F102x6) || defined(STM32F102xB) || \
  7. defined(STM32F103x6) || defined(STM32F103xB) || defined(STM32F103xE) || defined(STM32F103xG) || \
  8. defined(STM32F105xC) || defined(STM32F107xC)
  9. /* Prefetch buffer is not available on value line devices */
  10. __HAL_FLASH_PREFETCH_BUFFER_ENABLE();
  11. #endif
  12. #endif /* PREFETCH_ENABLE */
  13. /* Set Interrupt Group Priority */
  14. HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);
  15. /* Use systick as time base source and configure 1ms tick (default clock after Reset is HSI) */
  16. HAL_InitTick(TICK_INT_PRIORITY);
  17. /* Init the low level hardware */
  18. HAL_MspInit();
  19. /* Return function status */
  20. return HAL_OK;
  21. }

根据英文注释不难看出来每个代码是干嘛的。

一直往下追踪,可以发现,其实底层都是对相关寄存器进行操作。

main.c中的后续初始化等代码类似,不再赘述。

GPIO模块化编程

单片机的封装通常都是分层的。

怎么说呢?

首先,底层针对寄存器的读写时序是第一层;

再往上第二层,就是针对特定硬件的一些基本功能,比如读和写;

再往上就是第三层,可能是面向对象层,实现特定硬件的具体功能;

再往上,就是业务层,通过硬件的具体功能来实现不同的业务需求。

……

现在,有三个LED灯,对其进行模块化编程。

首先,HAL提供了写引脚、转换引脚电平、读引脚电平等功能。

我们进一步封装,实现该LED灯的具体功能,有打开、关闭以及转换开关状态(此时不用关注下一层的细节问题,面向的是具体的LED灯)

思路如下:

为LED外设单独创建一个文件;

创建LED.c和LED.h,放到MyApplications中;

在myapplication.h中添加对应的头文件;

要实现哪些功能?对应的要提供什么函数,什么变量?将这些变量封装成一个结构体。

  1. #ifndef _LED_H_
  2. #define _LED_H_
  3. #include "stdint.h"
  4. //确定要实现的led功能
  5. typedef struct
  6. {
  7. //点亮
  8. void (*led_light)(uint8_t);
  9. //熄灭
  10. void (*led_extinguish)(uint8_t);
  11. //转换亮灭
  12. void (*led_switch)(uint8_t);
  13. } led_funtcions;
  14. //有三个LED灯,定义成枚举,并编号
  15. typedef enum
  16. {
  17. LED1 = 1u, LED2, LED3
  18. } led_status;
  19. //将结构体声明出去
  20. extern led_funtcions led_operater;
  21. #endif

接着,在对应的c中,定义一个结构体全局变量(相应的在头文件中要声明出去),这个变量就作为一个对接人,也就是该文件的一个对象。之后,依次实现其中所定义的函数,并将函数赋值给结构体。同时注意,将所有函数设置成当前文件可见的,即加上static。我们不会对外暴露任何函数,要想访问函数,必须通过结构体变量去访问元素的形式。

  1. #include "myapplication.h"
  2. static void LedLight(uint8_t lednum);
  3. static void LedExtinguish(uint8_t lednum);
  4. static void LedSwitch(uint8_t lednum);
  5. static void Led1Blink(void);
  6. led_funtcions led_operater =
  7. {
  8. LedLight,
  9. LedExtinguish,
  10. LedSwitch
  11. };
  12. static void LedLight(uint8_t lednum)
  13. {
  14. switch(lednum)
  15. {
  16. case LED1 :
  17. HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_SET);
  18. break;
  19. case LED2 :
  20. HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_SET);
  21. break;
  22. case LED3 :
  23. HAL_GPIO_WritePin(LED3_GPIO_Port, LED3_Pin, GPIO_PIN_SET);
  24. break;
  25. default :
  26. Led1Blink();
  27. }
  28. }
  29. static void LedExtinguish(uint8_t lednum)
  30. {
  31. switch(lednum)
  32. {
  33. case LED1 :
  34. HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_RESET);
  35. break;
  36. case LED2 :
  37. HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_RESET);
  38. break;
  39. case LED3 :
  40. HAL_GPIO_WritePin(LED3_GPIO_Port, LED3_Pin, GPIO_PIN_RESET);
  41. break;
  42. default :
  43. Led1Blink();
  44. }
  45. }
  46. static void LedSwitch(uint8_t lednum)
  47. {
  48. switch(lednum)
  49. {
  50. case LED1 :
  51. HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin);
  52. break;
  53. case LED2 :
  54. HAL_GPIO_TogglePin(LED2_GPIO_Port, LED2_Pin);
  55. break;
  56. case LED3 :
  57. HAL_GPIO_TogglePin(LED3_GPIO_Port, LED3_Pin);
  58. break;
  59. default :
  60. Led1Blink();
  61. }
  62. }
  63. //如果输入的不是LED1/LED2/LED3则LED1闪烁
  64. static void Led1Blink(void)
  65. {
  66. HAL_Delay(100);
  67. HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin);
  68. }

通过结构体变量去访问:

  1. static void Run(void)
  2. {
  3. HAL_Delay(500);
  4. led_operater.led_light(LED1);
  5. led_operater.led_light(LED2);
  6. led_operater.led_light(LED3);
  7. HAL_Delay(500);
  8. led_operater.led_extinguish(LED1);
  9. led_operater.led_extinguish(LED2);
  10. led_operater.led_extinguish(LED3);
  11. HAL_Delay(500);
  12. led_operater.led_switch(LED1);
  13. HAL_Delay(500);
  14. led_operater.led_switch(LED2);
  15. HAL_Delay(500);
  16. led_operater.led_switch(LED3);
  17. HAL_Delay(500);
  18. led_operater.led_extinguish(LED3);
  19. HAL_Delay(500);
  20. led_operater.led_extinguish(LED2);
  21. HAL_Delay(500);
  22. led_operater.led_extinguish(LED1);
  23. }

在已有的框架下,我们不用去动任何main函数里的内容。只用处理好这里的Run函数和相应外设即可,便于移植和维护。

状态机

我们所说的状态机是有限状态机,是一种思想,把复杂的控制逻辑分解成有限个稳定状态,组成闭环系统,通过事件触发,让状态机按设定的顺序处理事务。

单片机C语言的状态机编程,是利用条件选择语句(switch-case)切换状态,通过函数内部指令改变状态机状态,让程序按照设定的顺序执行。

举例说明,一个简单状态机:

同理,先创建两个文件,即stamachine.c和stamachine.h。

有五个状态,对应一个枚举;

每种状态对应一个函数执行;

需要有一个变量用于状态切换。

stamachine.h

  1. #ifndef _STAMACHINE_H_
  2. #define _STAMACHINE_H_
  3. #include "stdint.h"
  4. //5种状态
  5. typedef enum
  6. {
  7. STA1 = 1u,
  8. STA2,
  9. STA3,
  10. STA4,
  11. STA5,
  12. } machineState;
  13. //对应的函数封装
  14. typedef struct
  15. {
  16. machineState stateLocation;
  17. void (*sta1Func)(void);
  18. void (*sta2Func)(void);
  19. void (*sta3Func)(void);
  20. void (*sta4Func)(void);
  21. void (*sta5Func)(void);
  22. } state_machine;
  23. //将结构体声明出去
  24. extern state_machine state_machiner;
  25. #endif

stamachine.c

  1. #include "myapplication.h"
  2. static void Sta1Func(void);
  3. static void Sta2Func(void);
  4. static void Sta3Func(void);
  5. static void Sta4Func(void);
  6. static void Sta5Func(void);
  7. state_machine state_machiner =
  8. {
  9. STA1,
  10. Sta1Func,
  11. Sta2Func,
  12. Sta3Func,
  13. Sta4Func,
  14. Sta5Func,
  15. };
  16. static void Sta1Func(void)
  17. {
  18. HAL_Delay(500);
  19. led_operater.led_extinguish(LED1);
  20. led_operater.led_extinguish(LED2);
  21. led_operater.led_extinguish(LED3);
  22. state_machiner.stateLocation = STA2;
  23. }
  24. static void Sta2Func(void)
  25. {
  26. HAL_Delay(500);
  27. led_operater.led_light(LED1);
  28. HAL_Delay(500);
  29. led_operater.led_extinguish(LED1);
  30. state_machiner.stateLocation = STA3;
  31. }
  32. static void Sta3Func(void)
  33. {
  34. HAL_Delay(500);
  35. led_operater.led_light(LED2);
  36. HAL_Delay(500);
  37. led_operater.led_extinguish(LED2);
  38. state_machiner.stateLocation = STA4;
  39. }
  40. static void Sta4Func(void)
  41. {
  42. HAL_Delay(500);
  43. led_operater.led_light(LED3);
  44. HAL_Delay(500);
  45. led_operater.led_extinguish(LED3);
  46. state_machiner.stateLocation = STA5;
  47. }
  48. static void Sta5Func(void)
  49. {
  50. HAL_Delay(500);
  51. led_operater.led_light(LED1);
  52. led_operater.led_light(LED2);
  53. led_operater.led_light(LED3);
  54. state_machiner.stateLocation = STA1;
  55. }

状态机执行:

  1. static void Run(void)
  2. {
  3. switch(state_machiner.stateLocation)
  4. {
  5. case STA1 :
  6. state_machiner.sta1Func();
  7. break;
  8. case STA2 :
  9. state_machiner.sta2Func();
  10. break;
  11. case STA3 :
  12. state_machiner.sta3Func();
  13. break;
  14. case STA4 :
  15. state_machiner.sta4Func();
  16. break;
  17. case STA5 :
  18. state_machiner.sta5Func();
  19. break;
  20. default :
  21. state_machiner.stateLocation = STA1;
  22. }
  23. }

还是不用动主函数。添加新文件即可。

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop】
推荐阅读
相关标签
  

闽ICP备14008679号