GPIO
GPIO库函数
GPIO全称General Purpose Input Output,简称通用输入输出外设。在这个系列的芯片中,一共有144个引脚,其中除去一些电源、地、BOOT、晶振等引脚,剩下114个引脚可以作为GPIO口使用。
其实在使用这个芯片的时候ST公司为了让我们有良好的用户体验,每一个外设都给我们写了一个例子。也是在帮助手册中,寻找这个例子。如下在左侧的目录中。

需要注意的是,这些初始化之类的动作,一般而言只需要抄。但是步骤还是需要记一下。
点击去就可一看见代码。把代码关键赋值放在下面
#include "stm32f4xx.h"
#define LED1_PIN GPIO_Pin_6
#define LED2_PIN GPIO_Pin_8 //这两个宏是定义在头文件的,把他放在这里
int main()
{
GPIO_InitTypeDef GPIO_InitStructure; //1.定义结构体
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOG, ENABLE); //2.配置时钟,置于配置什么时钟,在寄存器配置时会详细的说
/* Configure PG6 and PG8 in output pushpull mode */
GPIO_InitStructure.GPIO_Pin = LED1_PIN | LED2_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; //3.给结构体变量赋值
GPIO_Init(GPIOG, &GPIO_InitStructure); //4.初始化结构体
while (1)
{
/* Set PG6 and PG8 */ //Set:置位,设置为高电平
GPIOG->BSRRL = LED1_PIN | LED2_PIN; //意思是把PG6和PG8设置为高电平
/* Reset PG6 and PG8 */ //Reset:复位,设置为低电平
GPIOG->BSRRH = LED1_PIN | LED2_PIN; //意思是把PG6和PG8设置为低电平
return 0;
}
- 在创建工程是,我们加入了一个
xx_gpio.c这个文件,注意每一个外设都有一个自己的.c源文件和头文件。并且每一个文件都会说明这个外设的使用下面就是gpio外设的说明
===============================================================================
##### How to use this driver #####
===============================================================================
[..]
(#) Enable the GPIO AHB clock using the following function
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOx, ENABLE);
(#) Configure the GPIO pin(s) using GPIO_Init()
Four possible configuration are available for each pin:
(++) Input: Floating, Pull-up, Pull-down.
(++) Output: Push-Pull (Pull-up, Pull-down or no Pull)
Open Drain (Pull-up, Pull-down or no Pull). In output mode, the speed
is configurable: 2 MHz, 25 MHz, 50 MHz or 100 MHz.
(++) Alternate Function: Push-Pull (Pull-up, Pull-down or no Pull) Open
Drain (Pull-up, Pull-down or no Pull).
(++) Analog: required mode when a pin is to be used as ADC channel or DAC
output.
(#) To get the level of a pin configured in input mode use GPIO_ReadInputDataBit()
(#) To set/reset the level of a pin configured in output mode use
GPIO_SetBits()/GPIO_ResetBits()
对于上面的部分解释,放到下面、复用功能之后再说。

- 下面是这个GPIO_InitTypeDef结构体的成员
typedef struct
{
//引脚
uint32_t GPIO_Pin; /*!< Specifies the GPIO pins to be configured.
This parameter can be any value of @ref GPIO_pins_define */
//模式(输出、输入、模拟、复用)
GPIOMode_TypeDef GPIO_Mode; /*!< Specifies the operating mode for the selected pins.
This parameter can be a value of @ref GPIOMode_TypeDef */
//速度(速度)
GPIOSpeed_TypeDef GPIO_Speed; /*!< Specifies the speed for the selected pins.
This parameter can be a value of @ref GPIOSpeed_TypeDef */
//输出类型(开漏、推挽)
GPIOOType_TypeDef GPIO_OType; /*!< Specifies the operating output type for the selected pins.
This parameter can be a value of @ref GPIOOType_TypeDef */
//操作方式(上下拉)
GPIOPuPd_TypeDef GPIO_PuPd; /*!< Specifies the operating Pull-up/Pull down for the selected pins.
This parameter can be a value of @ref GPIOPuPd_TypeDef */
}GPIO_InitTypeDef;
置于每一个函数的参数,需要自己查看原函数的注释。由于太多,就不说了。
这里将一下这些成员的作用 ,每一个成员旁边都有一个这个参数,可以在这个文件全局搜索这个参数@ref GPIO_pins_define
- 开漏:开漏不能输出高电平,只能输出低电平。
- 推挽:推挽可以输出高电平,也可以输出低电平。
GPIO寄存器开发
-
一般而言,想要控制硬件就要控制硬件的寄存器。
-
寄存器:寄存器可以存储一个一组bit数据,寄存器是由触发器构成,了解过数电就知道,一组触发器可以构成一个寄存器,触发器存储数据是需要时钟信号的,所以想要把数据写入寄存器中,必须先打开时钟。还有就是每次的复位,为了降低功耗stm32会把所有的外设时钟给关闭,所以需要每一次复位都打开。如果没有打开时钟,那么数据写不进寄存器里面,那么就会导致控制不了硬件。寄存器相当于已经申请好的变量,直接进行赋值即可。
-
//想对一个寄存器赋值,首先要知道它的物理地址 /* * 这两个最长用的公式,必须要牢牢记住 */ //置位,将寄存器的第n位设置为1 (*((volatile unsigned int *)(物理地址))) |= (1<<n); //复位,将寄存器的第n位设置为0 (*((volatile unsigned int *)(物理地址))) &= ~(1<<n); -
需要注意的是,stm32内部是很多条总线的,外设都是挂载到总线上面的,根据总线可以知道外设的地址,想要更深入的了解这些总线,需要去参考Cortex-M4的权威指南。
下面是STM32的数据手册的内存分布(数据手册第四章),可以知道的地址,外设的物理地址 = 基地址 + 偏移地址。

后面考可以查阅stm32的参考手册,了解GPIO的寄存器这里只讲一个寄存器下面的寄存器作为参考
实际的物理地址就是
物理地址 = (0x4002_1800 + 0x00);
如下给一个IO口设置为输出模式GPIO-->9
//复位,将寄存器的第19位设置为0
(*((volatile unsigned int *)(0x4002_1800 + 0x00))) &= ~(1<<(2*9+1);
//置位,将寄存器的第18位设置为1
(*((volatile unsigned int *)(0x4002_1800 + 0x00))) |= (1<<(2*9);

以下的寄存器都是如此。
- 注意:看完上面的内存映射,应该知道RCC这个外设是挂载到AHB1总线上面的,下面这个寄存器比较复杂一点,但是掌握了上面两个公式,也复杂不到哪里去。
例如想要使能GPIOG这个外设时钟
物理地址 = (0x4002_3800 + 0x30);
如下给一个GPIOG口设置时钟使能
//置位,将寄存器的第6位设置为1
(*((volatile unsigned int *)(0x4002_3800 + 0x30))) |= (1<<(6);

下面就是使用寄存器写一个例子
#define RCC_AHB1ENR ( * (volatile unsigned int *)( 0x40023800 + 0x30 ) ) //外设使能时钟AHB1
#define GPIOF_MODER ( * (volatile unsigned int *)( 0x40021400 + 0x00 ) ) //GPIOF模式寄存器
#define GPIOF_OTYPER ( * (volatile unsigned int *)( 0x40021400 + 0x04 ) ) //GPIOF类型寄存器
#define GPIOF_OSPEEDR ( * (volatile unsigned int *)( 0x40021400 + 0x08 ) ) //GPIOF速度寄存器
#define GPIOF_PUPDR ( * (volatile unsigned int *)( 0x40021400 + 0x0C ) ) //GPIOF上下拉寄存器
#define GPIOF_ODR ( * (volatile unsigned int *)( 0x40021400 + 0x14 ) ) //GPIOF输出寄存器
#define GPIOE_MODER ( * (volatile unsigned int *)( 0x40021000 + 0x00 ) ) //GPIOE模式寄存器
#define GPIOE_OTYPER ( * (volatile unsigned int *)( 0x40021000 + 0x04 ) ) //GPIOE类型寄存器
#define GPIOE_OSPEEDR ( * (volatile unsigned int *)( 0x40021000 + 0x08 ) ) //GPIOE速度寄存器
#define GPIOE_PUPDR ( * (volatile unsigned int *)( 0x40021000 + 0x0C ) ) //GPIOE上下拉寄存器
#define GPIOE_ODR ( * (volatile unsigned int *)( 0x40021000 + 0x14 ) ) //GPIOE输出寄存器
int main()
{
//1.使能GPIOF和GPIOE的时钟
RCC_AHB1ENR |= (1<<5);
RCC_AHB1ENR |= (1<<4);
//2.设置GPIOF10的模式、速度、类型、上下拉
GPIOF_MODER &= ~(1<<(2*10+1));
GPIOF_MODER |= (1<<(2*10)); //设置为输出模式
GPIOF_OTYPER &= ~(1<<10); //设置为推挽模式
GPIOF_OSPEEDR |= (1<<(2*10+1));
GPIOF_OSPEEDR |= (1<<(2*10)); //设置为速度高速
GPIOF_PUPDR &= ~(1<<(2*10+1));
GPIOF_PUPDR &= ~(1<<(2*10)); //设置不上下拉
//3.设置GPIOE13的模式、速度、类型、上下拉
GPIOE_MODER &= ~(1<<(2*13+1));
GPIOE_MODER |= (1<<(2*13)); //设置为输出模式
GPIOE_OTYPER &= ~(1<<10); //设置为推挽模式
GPIOE_OSPEEDR |= (1<<(2*13+1));
GPIOE_OSPEEDR |= (1<<(2*13)); //设置为速度高速
GPIOE_PUPDR &= ~(1<<(2*13+1));
GPIOE_PUPDR &= ~(1<<(2*13)); //设置不上下拉
//输出低电平
GPIOF_ODR &= ~(1<<10);
GPIOE_ODR &= ~(1<<13);
while(1);
}
- 对于寄存器开发,其实也没必要怎么复杂,ST公司已经把所有的寄存器都放在一个头文件
stm32f4xx.h里面,想要使用这个头文件必须要包含它。

随机参考一个GPIO端口的地址,可见这个代码的逻辑时非常清晰的,一步步都是有基地址加偏移地址得到的。
利用ST公司提供的寄存器开发,例程
#include "stm32f4xx.h"
int main()
{
//1.使能GPIOF和GPIOE的时钟
RCC->AHB1ENR |= (1<<5);
RCC->AHB1ENR |= (1<<4);
//2.设置GPIOF10的模式、速度、类型、上下拉
GPIOF->MODER &= ~(1<<(2*10+1));
GPIOF->MODER |= (1<<(2*10)); //设置为输出模式
GPIOF->OTYPER &= ~(1<<10); //设置为推挽模式
GPIOF->OSPEEDR |= (1<<(2*10+1));
GPIOF->OSPEEDR |= (1<<(2*10)); //设置为速度高速
GPIOF->PUPDR &= ~(1<<(2*10+1));
GPIOF->PUPDR &= ~(1<<(2*10)); //设置不上下拉
//3.设置GPIOE2的模式、速度、类型、上下拉
GPIOE->MODER &= ~(1<<(2*2+1));
GPIOE->MODER &= ~(1<<(2*2)); //设置为输入模式
GPIOE->PUPDR &= ~(1<<(2*2+1));
GPIOE->PUPDR &= ~(1<<(2*2)); //设置不上下拉
//输出低电平
GPIOF->ODR &= ~(1<<10);
while(1);
return 0;
}
- 注意比如有一些寄存器,比如一些只读寄存器,它一次读取只能把所有的比特位(32位读取出来),一次性读取32个比特位

- 输入模式和输出模式差不读查看上面的gpio.c文件如何使用这个驱动,他就会说需要配置什么模式。

浙公网安备 33010602011771号