• stm32之GPIO库函数点灯分析


            stm32官方为了方便开发者,利用CubeMX 生成HAL库有关的C代码。HAL库就是硬件抽象层(hardware abstraction layer),生成一系列的函数帮助我们快速生成工程,脱离复杂的寄存器配置。stm32相对于51来功能强大,但是寄存器的数量也不是一个量级,单靠配置寄存器来做项目的话,进度会非常缓慢。但是在学习阶段还是有必要研究一个寄存器配置或者说研究HAL是如何操作寄存器的。

    一、利用CubeMX 生成代码

    具体怎么操作的这里就不讲解了,网上一大堆。实验用的开发板用的led,一端接到vcc,一端接到PB9(GPIO_B的第9个引脚),当PB9输出低电平时,led就会亮,反之则灭。

    生成的工程中,main函数内调用了MX_GPIO_Init函数,这个是GPIO_B_9引脚的初始化。

    函数示例如下:

    1. void MX_GPIO_Init(void)
    2. {
    3. GPIO_InitTypeDef GPIO_InitStruct = {0};
    4. /* GPIO Ports Clock Enable */
    5. __HAL_RCC_GPIOD_CLK_ENABLE();
    6. __HAL_RCC_GPIOA_CLK_ENABLE();
    7. __HAL_RCC_GPIOB_CLK_ENABLE();
    8. /*Configure GPIO pin Output Level */
    9. HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_RESET);
    10. /*Configure GPIO pin : PB9 */
    11. GPIO_InitStruct.Pin = GPIO_PIN_9;
    12. GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    13. GPIO_InitStruct.Pull = GPIO_NOPULL;
    14. GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    15. HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
    16. }

    整个过程就是

    1. 配置__HAL_RCC_GPIOB_CLK_ENABLE时钟,
    2. 配置GPIO_InitTypeDef变量,
    3. GPIO_PIN_9进行reset操作,也是给引脚设置成0
    4. HAL_GPIO_Init进行初始化。

    1.1、 GPIO_InitTypeDef类型定义

    1. typedef struct
    2. {
    3. uint32_t Pin; /*!< Specifies the GPIO pins to be configured.
    4. This parameter can be any value of @ref GPIO_pins_define */
    5. uint32_t Mode; /*!< Specifies the operating mode for the selected pins.
    6. This parameter can be a value of @ref GPIO_mode_define */
    7. uint32_t Pull; /*!< Specifies the Pull-up or Pull-Down activation for the selected pins.
    8. This parameter can be a value of @ref GPIO_pull_define */
    9. uint32_t Speed; /*!< Specifies the speed for the selected pins.
    10. This parameter can be a value of @ref GPIO_speed_define */
    11. } GPIO_InitTypeDef;

     结构体内部有四个成员变量

    • Pin: 要配置的引脚
    • Mode: 要配置的模式,输入或输出
    • Pull: 配置上拉、下拉或者两者都不
    • Speed:这里的speed暂时有三种模式,low(2MHZ), medium(10MHZ), high(50MHZ),后续会有说明

    引脚有两种状态,0或1,定义如下

    1. typedef enum
    2. {
    3. GPIO_PIN_RESET = 0u,
    4. GPIO_PIN_SET
    5. } GPIO_PinState;

    1.2、给GPIOB开启时钟

    在stm32中每个片上外设都有对应的时钟,所以在使用时都需要开启。GPIOB时钟开启的代码如下所示

    1. #define __HAL_RCC_GPIOB_CLK_ENABLE() do { \
    2. __IO uint32_t tmpreg; \
    3. SET_BIT(RCC->APB2ENR, RCC_APB2ENR_IOPBEN);\
    4. /* Delay after an RCC peripheral clock enabling */\
    5. tmpreg = READ_BIT(RCC->APB2ENR, RCC_APB2ENR_IOPBEN);\
    6. UNUSED(tmpreg); \
    7. } while(0U)

    代码里面就是搞了一个宏进行设置的。其中__IO 也是一个宏,它的类型是volatile,意为“直接存取原始内存地址”。

    1.2.1、 SET_BIT是一个宏,作用就是将寄存器的某一个设置成1。
    #define SET_BIT(REG, BIT)     ((REG) |= (BIT))

    另外所有GPIO是挂载到APB2总线上的,

    SET_BIT的第一个参数是APB2ENR,这里就涉及到RCC_APB2ENR寄存器。

     所有外设都需要时钟驱动,这里用到的是GPIO_B, 所以要把RCC_APB2ENR中的第3位置1。

    SET_BIT的第二个参数是一个宏

    1. #define RCC_APB2ENR_IOPBEN_Pos (3U)
    2. #define RCC_APB2ENR_IOPBEN_Msk (0x1UL << RCC_APB2ENR_IOPBEN_Pos) /*!< 0x00000008 */
    3. #define RCC_APB2ENR_IOPBEN RCC_APB2ENR_IOPBEN_Msk /*!< I/O port B clock enable */

    它代表的意思是通过移位将第3位(从0开始数)变成1,其它位都是0。

    这样一来 SET_BIT(RCC->APB2ENR, RCC_APB2ENR_IOPBEN) 代表的意思就是将RCC_APB2ENR寄存器中的第三位置1,也即使能GPIOB。

    另外一点是RCC_APB2ENR_IOPBEN寄存器的获取是通过RCC->APB2ENR这种形式。这里又涉及到RCC的问题。

    1.2.2、RCC相关寄存器

    RCC内部包括一系列和时钟相关的寄存器,当然也包括APB2ENR。RCC是一个宏,我把相关代码集中到一块如下所示

    1. // 所有外设的基址
    2. #define PERIPH_BASE 0x40000000UL /*!< Peripheral base address in the alias region */
    3. // 这里AHB的基址
    4. #define AHBPERIPH_BASE (PERIPH_BASE + 0x00020000UL)
    5. // RCC 是挂载到AHB上的
    6. define RCC_BASE (AHBPERIPH_BASE + 0x00001000UL)
    7. // RCC 包括众多寄存器,这里通过C语言的结构体和指针进行操作,将RCC的地址绑定到RCC_BASE
    8. #define RCC ((RCC_TypeDef *)RCC_BASE)
    9. typedef struct
    10. {
    11. __IO uint32_t CR;
    12. __IO uint32_t CFGR;
    13. __IO uint32_t CIR;
    14. __IO uint32_t APB2RSTR;
    15. __IO uint32_t APB1RSTR;
    16. __IO uint32_t AHBENR;
    17. __IO uint32_t APB2ENR;
    18. __IO uint32_t APB1ENR;
    19. __IO uint32_t BDCR;
    20. __IO uint32_t CSR;
    21. } RCC_TypeDef

    通过上面的操作,就可以利用RCC这个指针来操作各个寄存器,RCC内的变量都是按照寄存器的顺序排列的。这里再强制一点,RCC是挂载到AHB总线上的,所以它的地址是相对AHB总线进行偏移的。

    1.2.3、READ_BIT

    知道了SET_BIT, 那么READ_BIT也清楚了,采取的操作都差不多,作用就是取出指定的位。

    将要取出的位置1,其它位全部是0,这样就取出了指定位。

    #define READ_BIT(REG, BIT)    ((REG) & (BIT))

    1.3、HAL_GPIO_WritePin 

    这个函数的定义如下:

    1. void HAL_GPIO_WritePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState)
    2. {
    3. /* Check the parameters */
    4. assert_param(IS_GPIO_PIN(GPIO_Pin));
    5. assert_param(IS_GPIO_PIN_ACTION(PinState));
    6. if (PinState != GPIO_PIN_RESET)
    7. {
    8. GPIOx->BSRR = GPIO_Pin;
    9. }
    10. else
    11. {
    12. GPIOx->BSRR = (uint32_t)GPIO_Pin << 16u;
    13. }
    14. }

    这里可以看到主要是设置寄存器BSRR。这个函数传入的第一个参数是GPIOB,GPIOB的配置和上面说的RCC采用相似的策略

    1. // 所有外设的基址
    2. #define PERIPH_BASE 0x40000000UL /*!< Peripheral base address in the alias region */
    3. // APB2总线基址
    4. #define APB2PERIPH_BASE (PERIPH_BASE + 0x00010000UL)
    5. // GPIOB总线基址,GPIO是挂载到APB2上的
    6. #define GPIOB_BASE (APB2PERIPH_BASE + 0x00000C00UL)
    7. // GPIOB 包括众多寄存器,这里通过C语言的结构体和指针进行操作,将GPIOB的地址绑定到GPIOB_BASE
    8. #define GPIOB ((GPIO_TypeDef *)GPIOB_BASE)
    9. typedef struct
    10. {
    11. __IO uint32_t CRL;
    12. __IO uint32_t CRH;
    13. __IO uint32_t IDR;
    14. __IO uint32_t ODR;
    15. __IO uint32_t BSRR;
    16. __IO uint32_t BRR;
    17. __IO uint32_t LCKR;
    18. } GPIO_TypeDef;

     BSRR寄存器定义如下:

    ODR(端口输出数据寄存器)定义如下:

    上面简单来讲

    • 给BSy设置1,ODR对应的位会输出1
    • 给BRy设置1,ODR对应的位会输出0

    所以设置将引脚设置成0时需要在BSRR高16位进行操作,输出1 时在BSRR低16位进行操作。

    1.4、HAL_GPIO_Init

    这里涉及主要的初始化配置,是重中之重。涉及的函数比较多,已经在里面添加了注释。

    1. void HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init)
    2. {
    3. //通过这个变量遍历所有的引脚
    4. uint32_t position = 0x00u;
    5. uint32_t ioposition;
    6. uint32_t iocurrent;
    7. uint32_t temp;
    8. uint32_t config = 0x00u;
    9. //
    10. __IO uint32_t *configregister; /* Store the address of CRL or CRH register based on pin number */
    11. // 引脚的偏移量(每个引脚由CNF和MODE总共4位组成)
    12. uint32_t registeroffset; /* offset used during computation of CNF and MODE bits placement inside CRL or CRH register */
    13. /* Check the parameters */
    14. assert_param(IS_GPIO_ALL_INSTANCE(GPIOx));
    15. assert_param(IS_GPIO_PIN(GPIO_Init->Pin));
    16. assert_param(IS_GPIO_MODE(GPIO_Init->Mode));
    17. // 判断后续是否还有引脚设置了1
    18. while (((GPIO_Init->Pin) >> position) != 0x00u)
    19. {
    20. /* 获取引脚的位置 */
    21. ioposition = (0x01uL << position);
    22. /* 再次能过Pin获取当前要设置的引脚 */
    23. iocurrent = (uint32_t)(GPIO_Init->Pin) & ioposition;
    24. // 两种方式求得的引脚比较如果相同才进行处理
    25. if (iocurrent == ioposition)
    26. {
    27. /* Check the Alternate function parameters */
    28. assert_param(IS_GPIO_AF_INSTANCE(GPIOx));
    29. /* Based on the required mode, filling config variable with MODEy[1:0] and CNFy[3:2] corresponding bits */
    30. // 根据模式来配置config
    31. switch (GPIO_Init->Mode)
    32. {
    33. /* 推挽输出模式 */
    34. case GPIO_MODE_OUTPUT_PP:
    35. /* Check the GPIO speed parameter */
    36. assert_param(IS_GPIO_SPEED(GPIO_Init->Speed));
    37. config = GPIO_Init->Speed + GPIO_CR_CNF_GP_OUTPUT_PP;
    38. break;
    39. /* 开漏输出模式 */
    40. case GPIO_MODE_OUTPUT_OD:
    41. /* Check the GPIO speed parameter */
    42. assert_param(IS_GPIO_SPEED(GPIO_Init->Speed));
    43. config = GPIO_Init->Speed + GPIO_CR_CNF_GP_OUTPUT_OD;
    44. break;
    45. /* 复用推挽模式 */
    46. case GPIO_MODE_AF_PP:
    47. /* Check the GPIO speed parameter */
    48. assert_param(IS_GPIO_SPEED(GPIO_Init->Speed));
    49. config = GPIO_Init->Speed + GPIO_CR_CNF_AF_OUTPUT_PP;
    50. break;
    51. /* 复用开漏输出模式 */
    52. case GPIO_MODE_AF_OD:
    53. /* Check the GPIO speed parameter */
    54. assert_param(IS_GPIO_SPEED(GPIO_Init->Speed));
    55. config = GPIO_Init->Speed + GPIO_CR_CNF_AF_OUTPUT_OD;
    56. break;
    57. /* 下面是输入,要么事件输入要么中断输入 */
    58. // 输入悬空
    59. case GPIO_MODE_INPUT:
    60. // 中断上升沿
    61. case GPIO_MODE_IT_RISING:
    62. // 中断下降沿
    63. case GPIO_MODE_IT_FALLING:
    64. // 中断上长或下降沿
    65. case GPIO_MODE_IT_RISING_FALLING:
    66. // 事件上长沿
    67. case GPIO_MODE_EVT_RISING:
    68. // 事件下降沿
    69. case GPIO_MODE_EVT_FALLING:
    70. // 事件上长或下降沿
    71. case GPIO_MODE_EVT_RISING_FALLING:
    72. /* Check the GPIO pull parameter */
    73. assert_param(IS_GPIO_PULL(GPIO_Init->Pull));
    74. if (GPIO_Init->Pull == GPIO_NOPULL)
    75. {
    76. config = GPIO_CR_MODE_INPUT + GPIO_CR_CNF_INPUT_FLOATING;
    77. }
    78. else if (GPIO_Init->Pull == GPIO_PULLUP)
    79. {
    80. config = GPIO_CR_MODE_INPUT + GPIO_CR_CNF_INPUT_PU_PD;
    81. /* Set the corresponding ODR bit */
    82. GPIOx->BSRR = ioposition;
    83. }
    84. else /* GPIO_PULLDOWN */
    85. {
    86. config = GPIO_CR_MODE_INPUT + GPIO_CR_CNF_INPUT_PU_PD;
    87. /* Reset the corresponding ODR bit */
    88. GPIOx->BRR = ioposition;
    89. }
    90. break;
    91. /* 模拟输入 */
    92. case GPIO_MODE_ANALOG:
    93. config = GPIO_CR_MODE_INPUT + GPIO_CR_CNF_ANALOG;
    94. break;
    95. /* Parameters are checked with assert_param */
    96. default:
    97. break;
    98. }
    99. /* Check if the current bit belongs to first half or last half of the pin count number
    100. in order to address CRH or CRL register*/
    101. /*
    102. 点灯目前没有用到输入模式,只用到输出模式
    103. 1、如果当前的引脚是低8位,那就是么对CRL的操作,否则就是对CRH的操作
    104. 2、如果当前的引脚是低8位,将Postion向左偏移四位,操作的就是当前引脚的MODE和CNF.
    105. 如果是高8位,将(position - 8u)向左偏移四位,操作的就是当前引脚的MODE和CNF.
    106. */
    107. configregister = (iocurrent < GPIO_PIN_8) ? &GPIOx->CRL : &GPIOx->CRH;
    108. registeroffset = (iocurrent < GPIO_PIN_8) ? (position << 2u) : ((position - 8u) << 2u);
    109. /* 清除当前引脚的配置,并设置新配置 */
    110. MODIFY_REG((*configregister), ((GPIO_CRL_MODE0 | GPIO_CRL_CNF0) << registeroffset), (config << registeroffset));
    111. // 由于没有中断这里的操作是可以删除的
    112. /*--------------------- EXTI Mode Configuration ------------------------*/
    113. /* Configure the External Interrupt or event for the current IO */
    114. if ((GPIO_Init->Mode & EXTI_MODE) == EXTI_MODE)
    115. {
    116. /* Enable AFIO Clock */
    117. __HAL_RCC_AFIO_CLK_ENABLE();
    118. temp = AFIO->EXTICR[position >> 2u];
    119. CLEAR_BIT(temp, (0x0Fu) << (4u * (position & 0x03u)));
    120. SET_BIT(temp, (GPIO_GET_INDEX(GPIOx)) << (4u * (position & 0x03u)));
    121. AFIO->EXTICR[position >> 2u] = temp;
    122. /* Enable or disable the rising trigger */
    123. if ((GPIO_Init->Mode & RISING_EDGE) == RISING_EDGE)
    124. {
    125. SET_BIT(EXTI->RTSR, iocurrent);
    126. }
    127. else
    128. {
    129. CLEAR_BIT(EXTI->RTSR, iocurrent);
    130. }
    131. /* Enable or disable the falling trigger */
    132. if ((GPIO_Init->Mode & FALLING_EDGE) == FALLING_EDGE)
    133. {
    134. SET_BIT(EXTI->FTSR, iocurrent);
    135. }
    136. else
    137. {
    138. CLEAR_BIT(EXTI->FTSR, iocurrent);
    139. }
    140. /* Configure the event mask */
    141. if ((GPIO_Init->Mode & GPIO_MODE_EVT) == GPIO_MODE_EVT)
    142. {
    143. SET_BIT(EXTI->EMR, iocurrent);
    144. }
    145. else
    146. {
    147. CLEAR_BIT(EXTI->EMR, iocurrent);
    148. }
    149. /* Configure the interrupt mask */
    150. if ((GPIO_Init->Mode & GPIO_MODE_IT) == GPIO_MODE_IT)
    151. {
    152. SET_BIT(EXTI->IMR, iocurrent);
    153. }
    154. else
    155. {
    156. CLEAR_BIT(EXTI->IMR, iocurrent);
    157. }
    158. }
    159. }
    160. position++;
    161. }
    162. }

    代码相关的解释已经写到注释里了,这里有一个比较有意思的点。

    开始是GPIO_Init->Speed设置的是GPIO_SPEED_FREQ_LOW,它其实是一个有关MODE的宏,就是和CNY对应的MODE的宏。

    1. #define GPIO_CRL_MODE0_Pos (0U)
    2. // 这个就是50MHZ的输出模式
    3. #define GPIO_CRL_MODE0_Msk (0x3UL << GPIO_CRL_MODE0_Pos) /*!< 0x00000003 */
    4. // 这个就是50MHZ的输出模式
    5. #define GPIO_CRL_MODE0 GPIO_CRL_MODE0_Msk /*!< MODE0[1:0] bits (Port x mode bits, pin 0) */
    6. // 这个就是1的输出模式
    7. #define GPIO_CRL_MODE0_0 (0x1UL << GPIO_CRL_MODE0_Pos) /*!< 0x00000001 */
    8. // 这个就是2MHZ的输出模式
    9. #define GPIO_CRL_MODE0_1 (0x2UL << GPIO_CRL_MODE0_Pos) /*!< 0x00000002 */
    10. #define GPIO_SPEED_FREQ_LOW (GPIO_CRL_MODE0_1) /*!< Low speed */
    11. #define GPIO_SPEED_FREQ_MEDIUM (GPIO_CRL_MODE0_0) /*!< Medium speed */
    12. #define GPIO_SPEED_FREQ_HIGH (GPIO_CRL_MODE0) /*!< High speed */

    所以初始化的Speed其实就是设置了MODE。而对于每个输出模式,在HAL_GPIO_Init函数里都用到这样的操作

    1. // 低二位都是0,为了配合Mode
    2. #define GPIO_CR_CNF_GP_OUTPUT_PP 0x00000000u /*!< 00: General purpose output push-pull */
    3. // 低二位都是0,为了配合Mode
    4. #define GPIO_CR_CNF_GP_OUTPUT_OD 0x00000004u /*!< 01: General purpose output Open-drain */
    5. // 低二位都是0,为了配合Mode
    6. #define GPIO_CR_CNF_AF_OUTPUT_PP 0x00000008u /*!< 10: Alternate function output Push-pull */
    7. // 低二位都是0,为了配合Mode
    8. #define GPIO_CR_CNF_AF_OUTPUT_OD 0x0000000Cu /*!< 11: Alternate function output Open-drain */
    9. // 这里就是配置CNF+Mode
    10. config = GPIO_Init->Speed + GPIO_CR_CNF_GP_OUTPUT_PP;

    其实就是将MODE和CNF结合到一块共同控制一个引脚。

    二、在main()自定义操作

    知道了库函数的操作,其实我们自己也可以将Led灯进行点亮或者熄灭,比如

    1. // 熄灭
    2. GPIOB->BSRR = GPIO_PIN_9;
    3. // 点亮
    4. GPIOB->BSRR = GPIO_PIN_9 << 16;

    其实最终就是操作的寄存器(不研究也知道操作的寄存器,哈哈哈)。

    点灯对寄存器的操作如下:

    1. 配置APB2ENR开启GPIOB时钟
    2. 配置CRLCRH,来操作引脚的CNFMODE,让其作为输入或输出
    3. 配置BSRR使ODR对应的位输出1或0

    通过对stm32内存映射和GPIO寄存器的分析,大致了解了HAL的一些细节,确实单独操作寄存器难度比较大,但是通过对HAL函数的研究,加深了自己对HAL操作的了解以及寄存器细节的了解。

  • 相关阅读:
    计算机基础-BAT入门进阶
    分享两个模糊照片修复方法,轻松实现照片修复
    Vue技术 props配置
    【【萌新的SOC学习之AXI-DMA环路测试】】
    【MySQL】1.索引
    JavaScript 之 Symbol 数据类型
    五年制专转本备考中如何进行有效的自我管理
    动画:面试官问我插入排序和冒泡排序哪个更牛逼?
    day2_QT
    查看linux版本是centos还是redhat linux
  • 原文地址:https://blog.csdn.net/TSC1235/article/details/132890759