# GPIO

## GPIO的工作模式

GPIO共有八种模式，在参考手册的8.1章节有描述，可总结为下表

| GPIO的八种模式 | 特点及应用                                                                                       |
| --------- | ------------------------------------------------------------------------------------------- |
| 输入浮空      | 输入用，完全浮空，状态不定，IO口没有上下拉电阻，适用于外部数字信号的输入检测，，但需要注意在输入之前要先设置上下拉电阻或者确保外部电路有上拉或下拉电阻，否则会导致IO口信号的漂移。 |
| 输入上拉      | 输入用，用内部上拉，默认是高电平，适用于外部数字信号的输入检测，且外部电路没有上拉电阻时。                                               |
| 输入下拉      | 输入用，用内部下拉，默认是低电平，适用于外部数字信号的输入检测，且外部电路没有下拉电阻时。                                               |
| 模拟功能      | ADC、DAC，可以进行模拟信号的输入和输出。适用于模拟量采集和输出等场合。                                                      |
| 开漏输出      | IO口可以输出低电平，但驱动能力弱，不能输出高电平适用于需要外部上拉电阻的场合，如软件IIC的SDA、SCL等                                     |
| 推挽输出      | 驱动能力强，25mA（max），通用输出，如驱动LED设备                                                               |
| 开漏式复用功能   | 片上外设功能（硬件IIC 的SDA、SCL引脚等）                                                                   |
| 推挽式复用功能   | 片上外设功能（SPI 的SCK、MISO、MOSI引脚等）                                                               |

在参考手册的8.1.9章节给出了GPIO复用时的设置，下面给出部分设置截图，但是并没有给出上下拉的具体设置，应当是需要根据实际外部硬件设置的，也有可能与连接的设备有关，查考连接的设备的数据手册，可以参考正点原子例程，或参考STM32cubeIDE的固件库的Project的相关例程，如果不确定在画PCB时可以预留一下上下拉电阻的焊盘。

![](https://s2.loli.net/2023/03/12/wlfn84OasDNRxE1.png)

在HAL实际操作中，在stm32f1xx\_hal\_gpio.h进行了宏定义，可以设置以下几个类型，

```
#define  GPIO_MODE_INPUT                        0x00000000u   /*!< Input Floating Mode                   */
#define  GPIO_MODE_OUTPUT_PP                    0x00000001u   /*!< Output Push Pull Mode                 */
#define  GPIO_MODE_OUTPUT_OD                    0x00000011u   /*!< Output Open Drain Mode                */
#define  GPIO_MODE_AF_PP                        0x00000002u   /*!< Alternate Function Push Pull Mode     */
#define  GPIO_MODE_AF_OD                        0x00000012u   /*!< Alternate Function Open Drain Mode    */
#define  GPIO_MODE_AF_INPUT                     GPIO_MODE_INPUT          /*!< Alternate Function Input Mode         */

#define  GPIO_MODE_ANALOG                       0x00000003u   /*!< Analog Mode  */

#define  GPIO_MODE_IT_RISING                    0x10110000u   /*!< External Interrupt Mode with Rising edge trigger detection          */
#define  GPIO_MODE_IT_FALLING                   0x10210000u   /*!< External Interrupt Mode with Falling edge trigger detection         */
#define  GPIO_MODE_IT_RISING_FALLING            0x10310000u   /*!< External Interrupt Mode with Rising/Falling edge trigger detection  */

#define  GPIO_MODE_EVT_RISING                   0x10120000u   /*!< External Event Mode with Rising edge trigger detection               */
#define  GPIO_MODE_EVT_FALLING                  0x10220000u   /*!< External Event Mode with Falling edge trigger detection              */
#define  GPIO_MODE_EVT_RISING_FALLING           0x10320000u   /*!< External Event Mode with Rising/Falling edge trigger detection       */
```

* GPIO\_MODE\_INPUT：输入模式，引脚浮空输入。
* GPIO\_MODE\_OUTPUT\_PP：推挽输出模式，引脚输出高或低电平。
* GPIO\_MODE\_OUTPUT\_OD：开漏输出模式，引脚输出高电平或者不输出电平。
* GPIO\_MODE\_AF\_PP：复用推挽输出模式，引脚输出复用功能。
* GPIO\_MODE\_AF\_OD：复用开漏输出模式，引脚输出复用功能。
* GPIO\_MODE\_AF\_INPUT：复用输入模式，引脚浮空输入。
* GPIO\_MODE\_ANALOG：模拟输入模式，引脚输入模拟信号。
* GPIO\_MODE\_IT\_RISING：上升沿中断模式，引脚上升沿触发中断。
* GPIO\_MODE\_IT\_FALLING：下降沿中断模式，引脚下降沿触发中断。
* GPIO\_MODE\_IT\_RISING\_FALLING：上升和下降沿中断模式，引脚上升或下降沿触发中断。
* GPIO\_MODE\_EVT\_RISING：上升沿事件模式，引脚上升沿触发事件。
* GPIO\_MODE\_EVT\_FALLING：下降沿事件模式，引脚下降沿触发事件。
* GPIO\_MODE\_EVT\_RISING\_FALLING：上升和下降沿事件模式，引脚上升或下降沿触发事件。

上下拉的有以下几种类型

```
#define  GPIO_NOPULL        0x00000000u   /*!< No Pull-up or Pull-down activation  */
#define  GPIO_PULLUP        0x00000001u   /*!< Pull-up activation                  */
#define  GPIO_PULLDOWN      0x00000002u   /*!< Pull-down activation                */
```

* GPIO\_NOPULL：不使用上下拉电阻，引脚的电平由外部电路确定。
* GPIO\_PULLUP：使用上拉电阻，引脚在未连接时会被拉高。
* GPIO\_PULLDOWN：使用下拉电阻，引脚在未连接时会被拉低。

## GPIO的初始化

以正点原子的精英版跑马灯实验为例，LED的初始化代码如下

```
void LED_Init(void)
{
    GPIO_InitTypeDef GPIO_Initure;

    __HAL_RCC_GPIOB_CLK_ENABLE();               //开启GPIOB时钟
    __HAL_RCC_GPIOE_CLK_ENABLE();               //开启GPIOE时钟

    GPIO_Initure.Pin=GPIO_PIN_5;                 //PB5
    GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP;      //推挽输出
    GPIO_Initure.Pull=GPIO_PULLUP;              //上拉
    GPIO_Initure.Speed=GPIO_SPEED_FREQ_HIGH;    //高速
    HAL_GPIO_Init(GPIOB,&GPIO_Initure);

    GPIO_Initure.Pin=GPIO_PIN_5;                 //PE5
    HAL_GPIO_Init(GPIOE,&GPIO_Initure);

    HAL_GPIO_WritePin(GPIOB,GPIO_PIN_5,GPIO_PIN_SET);    //PB5置1，默认初始化后灯灭
    HAL_GPIO_WritePin(GPIOE,GPIO_PIN_5,GPIO_PIN_SET);    //PE5置1，默认初始化后灯灭
}
```

分为以下几个步骤

* 开启GPIO时钟
* 进行IO的初始化，设置驱动的引脚模式，上下拉，以及速度

## 详解HAL\_GPIO\_Init函数

该函数在stm32f1xx\_hal\_gpio.c文件中定义，有两个入口参数

```
void HAL_GPIO_Init(GPIO_TypeDef  *GPIOx, GPIO_InitTypeDef *GPIO_Init)
```

### GPIO\_TypeDef

一个是**GPIO\_TypeDef**类型指针，参数传入的正是宏定义的寄存器指针，可以传入GPIOA，GPIOB....这样的宏定义，在stm32f103xe.h文件中宏定义，实际上就是在告诉HAL\_GPIO\_Init函数要操作的引脚组的寄存器地址

```
#define GPIOA               ((GPIO_TypeDef *)GPIOA_BASE)
#define GPIOB               ((GPIO_TypeDef *)GPIOB_BASE)
#define GPIOC               ((GPIO_TypeDef *)GPIOC_BASE)
#define GPIOD               ((GPIO_TypeDef *)GPIOD_BASE)
#define GPIOE               ((GPIO_TypeDef *)GPIOE_BASE)
#define GPIOF               ((GPIO_TypeDef *)GPIOF_BASE)
#define GPIOG               ((GPIO_TypeDef *)GPIOG_BASE)
```

其中GPIO\_TypeD类型的结构体的定义如下，成员全是要操作的寄存器，排列顺序实际上是按照他们的实际物理地址的排列顺序，这样的好处是,传入GPIOA\_BASE这样的基地址（首地址，该地址等于CRL寄存器地址）即可，后续HAL\_GPIO\_Init函数要操作CRH,IDR寄存器时就无需在传入具体地址，也无需进行地址计算，直接用GPIOx->BSRR这样的形式访问机构提成员即可，因为形参GPIOx已经被传入GPIOA\_BASE这样的基地址，而C语言结构体成员的地址偏移是4字节，即C语言上如果CRL成员的地址是0x01，则CRH的地址是0X05，以此类推，而我们要设置GPIOA，给CRL传入了它的寄存器实际地址0x4001 0800，那么GPIOx->CRH的地址就会是0x4001 0804，与CRH的寄存器实际地址一致，因为寄存器的偏移也是4字节。

```
typedef struct
{
  __IO uint32_t CRL;
  __IO uint32_t CRH;
  __IO uint32_t IDR;
  __IO uint32_t ODR;
  __IO uint32_t BSRR;
  __IO uint32_t BRR;
  __IO uint32_t LCKR;
} GPIO_TypeDef;
```

### GPIO\_InitTypeDef

实际上第二个参数就是**GPIO\_InitTypeDef**类型指针，传入的正是GPIO的速度，模式属性。在该函数中会将这些属性解析出对应的值操作GPIOx结构体写入寄存器中，实现GPIO外设的初始化操作。

### GPIO的置位与复位

其实就是设置输出高电平还是低电平，可以通过操作端口位设置/清除寄存器(GPIOx\_BSRR)或端口位清除寄存器(GPIOx\_BRR)，两者的具体描述可看参考手册，一般是操作GPIOx\_BSRR寄存器，在HAL库中操作HAL\_GPIO\_WritePin函数

```
void HAL_GPIO_WritePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState)
{
  /* Check the parameters */
  assert_param(IS_GPIO_PIN(GPIO_Pin));
  assert_param(IS_GPIO_PIN_ACTION(PinState));

  if (PinState != GPIO_PIN_RESET)
  {
    GPIOx->BSRR = GPIO_Pin;
  }
  else
  {
    GPIOx->BSRR = (uint32_t)GPIO_Pin << 16u;
  }
}
```

* GPIOx：表示要使用的GPIO端口，可以是A\~G中的一个，根据不同的芯片型号而有所不同。
* GPIO\_Pin：表示要设置/清除的GPIO引脚，可以是0\~15中的一个，根据不同的芯片型号而有所不同。
* PinState：表示要设置的引脚状态，可以是GPIO\_PIN\_RESET或GPIO\_PIN\_SET，分别表示清除引脚和设置引脚。 这个函数在实现时使用了GPIOx\_BSRR寄存器，以确保原子性的读/修改操作，避免在修改过程中被中断打断的风险。该函数可以用于控制GPIO引脚的状态，例如控制LED灯的亮灭、控制继电器的开关等等。

举例：HAL\_GPIO\_WritePin(GPIOB,GPIO\_PIN\_5,GPIO\_PIN\_SET);

### GPIO的状态读取

通过操作端口输入数据寄存器(GPIOx\_IDR)实现，在HAL库中可以使用

```
GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin)
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://oceanaparts.gitbook.io/halnote/gpio.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
