详细的学习一下GPIO_Init()函数,比如下面的一段程序:
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOE, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
第一行为:
GPIO_InitTypeDef GPIO_InitStructure;
定义一个GPIO_InitTypeDef数据类型的数,取名叫GPIO_InitStructure,所以需要知道 GPIO_InitTypeDef 是什么数据类型, 其定义如下:
typedef struct
{
uint16_t GPIO_Pin; /*!< Specifies the GPIO pins to be configured.
This parameter can be any value of @ref GPIO_pins_define */
GPIOSpeed_TypeDef GPIO_Speed; /*!< Specifies the speed for the selected pins.
This parameter can be a value of @ref GPIOSpeed_TypeDef */
GPIOMode_TypeDef GPIO_Mode; /*!< Specifies the operating mode for the selected pins.
This parameter can be a value of @ref GPIOMode_TypeDef */
}GPIO_InitTypeDef;
GPIO_InitTypeDef是一个结构体类型,里面有三个成员
第一个是无符号16位的数如下:
uint16_t GPIO_Pin;
即GPIO_Pin可以在0000 0000 0000 0000到1111 1111 1111 1111 之间随意取值。
第二个成员如下,需要知道GPIOSpeed_TypeDef是什么类型,GPIOSpeed_TypeDef定义如下:
GPIOSpeed_TypeDef GPIO_Speed;// 成员
//###################################下面是它的类型定义################################
typedef enum
{
GPIO_Speed_10MHz = 1,
GPIO_Speed_2MHz,
GPIO_Speed_50MHz
}GPIOSpeed_TypeDef;
第二个成员GPIO_Speed是一个枚举型数据,枚举型是一个集合,第一个成员如果没有取值就默认取值为1,后续成员没有取值就默认取值为前一成员值+1,比如上面GPIO_Speed_2MHz的值为2,GPIO_Speed_50MHz的值为3。
第三个成员是GPIO_Mode,它的类型是GPIOMode_TypeDef,也是一个枚举型数据
GPIOMode_TypeDef GPIO_Mode; //成员
//###################################下面是它的类型定义################################
typedef enum
{ GPIO_Mode_AIN = 0x0,
GPIO_Mode_IN_FLOATING = 0x04,
GPIO_Mode_IPD = 0x28,
GPIO_Mode_IPU = 0x48,
GPIO_Mode_Out_OD = 0x14,
GPIO_Mode_Out_PP = 0x10,
GPIO_Mode_AF_OD = 0x1C,
GPIO_Mode_AF_PP = 0x18
}GPIOMode_TypeDef;
GPIO_Mode有8个成员,将其取值展开成2进制如下,可以看到第5位是“0”就为输入,第5位是“1”就为输出。
GPIO_Mode_AIN = 0000 0000 //模拟输入
GPIO_Mode_IN_FLOATING = 0000 0100 //浮空输入
GPIO_Mode_IPD = 0010 1000 //下拉输入
GPIO_Mode_IPU = 0100 1000 //上拉输入
GPIO_Mode_Out_OD = 0001 0100 //开漏输出
GPIO_Mode_Out_PP = 0001 0000 //推挽输出
GPIO_Mode_AF_OD = 0001 1100 //复用开漏
GPIO_Mode_AF_PP = 0001 1000 //服用推挽
然后看下面的程序
GPIO_Init(GPIOB, &GPIO_InitStructure);
其中GPIO_InitStructure我们已经知道了它是一个结构体数据,它都有三个成员,上面都有详细描述,但我们还需要知道它是用在哪的,给谁用的,这个在GPIO_Init()函数里面写着给GPIOB用,我们先来看GPIOB这个东西它是什么。
#define GPIOB ((GPIO_TypeDef *) GPIOB_BASE)
GPIOB是将 GPIOB_BASE这个东西强制转换成(GPIO_TypeDef *)指针类型,然后取名叫GPIOB,所以需要知道GPIOB_BASE是什么东西、GPIO_TypeDef是什么类型,如下:
#define PERIPH_BASE ((uint32_t)0x40000000)
#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)
#define GPIOB_BASE (APB2PERIPH_BASE + 0x0C00)
所以GPIOB_BASE是一个指向0x4001 0c00地址的指针,然后这个指针的结构是GPIO_TypeDef,所以需要知道GPIO_TypeDef是什么数据结构,如下:
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;
这是一个结构体,成员都是无符号32位的数,所以GPIOB实际上是一个结构体指针,指向首地址为0x4001 0c00的一大块区间,这块区间就是这个结构体GPIO_TypeDef,里面7个成员全都是32位,所以区间大小为7*(32/8)=28层=0x1c层。即区间0x4001 0c00 到 0x4001 0c1c。
这里把STM32单片机看成是一栋高楼,高楼一共有0xFFFF FFFF层,每层有8个房间,就是8位,所以一个32位的数要占4层楼。
现在我们就知道 GPIO_Init(GPIOB, &GPIO_InitStructure)函数中这两个参数具体是什么了,GPIOB是指向首地址为0x4001 0c00的一个结构体指针,&GPIO_InitStructure是取GPIO_InitStructure这个结构体的地址,取这个结构体地址有什么用呢,往下看
我们再来看初始化函数如下:
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)
这里将&GPIO_InitStructure定义成了指针,所以第二个参数还是这个结构体地址里面的数据。下面看这个初始化函数的内容:
uint32_t currentmode = 0x00, currentpin = 0x00, pinpos = 0x00, pos = 0x00;
uint32_t tmpreg = 0x00, pinmask = 0x00;
/* Check the parameters */
assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
assert_param(IS_GPIO_MODE(GPIO_InitStruct->GPIO_Mode));
assert_param(IS_GPIO_PIN(GPIO_InitStruct->GPIO_Pin));
首先是一些参数定义,和assert_param函数,这个函数的意思是检查参数是否有效,如 assert_param(IS_GPIO_ALL_PERIPH(GPIOx));就是检查GPIOx这个参数是否有效,假如我输入一个GPIOK进去,那就是无效参数,会报错。
currentmode = ((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x0F);
if ((((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x10)) != 0x00)
{
/* Check the parameters */
assert_param(IS_GPIO_SPEED(GPIO_InitStruct->GPIO_Speed));
/* Output mode */
currentmode |= (uint32_t)GPIO_InitStruct->GPIO_Speed;
}
//########################GPIO_Mode选择如下#################################
GPIO_Mode_Out_OD = 0001 0100 //开漏输出
前面详细说了GPIO_Mode有8个成员,接下来是把GPIO_InitStruct->GPIO_Mode设置为其中一个成员(用来设置输入输出模式的),假设为 GPIO_Mode_Out_OD,和(uint32_t)0x0F相与,就是前四位清零、后四位保留,然后给currentmode这个参数,所以currentmode=0000 0100 ;
接着用if语句判断GPIO_Mode的第五位是0还是1(是输入还是输出),是输出的话就执行{}内容
然后currentmode和GPIO_InitStruct->GPIO_Speed相或,假设GPIO_InitStruct->GPIO_Speed取值为(设置为2MHZ)GPIO_Speed_2MHz,即GPIO_InitStruct->GPIO_Speed=0000 0010,和currentmode相或后,currentmode=0000 0110,此时currentmode后四位就携带了输入输出模式信息和输出速度信息。
if (((uint32_t)GPIO_InitStruct->GPIO_Pin & ((uint32_t)0x00FF)) != 0x00)
{
tmpreg = GPIOx->CRL;
for (pinpos = 0x00; pinpos < 0x08; pinpos++)
{
pos = ((uint32_t)0x01) << pinpos;
/* Get the port pins position */
currentpin = (GPIO_InitStruct->GPIO_Pin) & pos;
if (currentpin == pos)
{
pos = pinpos << 2;
/* Clear the corresponding low control register bits */
pinmask = ((uint32_t)0x0F) << pos;
tmpreg &= ~pinmask;
/* Write the mode configuration in the corresponding bits */
tmpreg |= (currentmode << pos);
GPIOx->CRL = tmpreg;
//###############
接下来判断引脚是高八位还是第八位,为什么是8位呢,因为一个引脚的输入输出模式和速度是由CNF[1:0]和MODE[1:0]四位决定的,CNF决定输入输出模式MODE决定速度,而这4位在GPIOx->CRL;这个地址里,CRL之前说过,是32位的,所以CRL能设置8个IO的模式和速度。
所以,上面程序功能就是,将携带着模式和速度信息的currentmode赋值给对应的需要进行设置的IO口。
上面程序的整个过程是:判断是否位低八位,如果是,将32位的CRL值给tmpreg,然后进入for循环去找引脚,这里对32位的0.....0000 0000 0000 0001 每次左移pinpos位(pinpos在0-7之间)然后赋值给pos,(假如我要设置GPIOB的Pin3,则GPIO_Pin(前面有讲过,他是GPIO_InitTypeDef的第一个成员)的取值为0000 0000 0000 1000),假如某次循环pinpos为3,那么pos为0.....0000 1000,GPIO_Pin的值相同,所以currentpin == pos,进入if语句,将pinpos左移2位(乘以4)赋值给pos,此时pos为12,接着将0......0000 0000 0000 1111向左移pos位(12位)赋值给pinmask,此时pinmask第15-12位为1,取反后和tmpreg相与(将15-12位清零,其余位保留),最后把currentmode左移pos位(12位)后把4位(15-12位)的模式速度信息,填入tmpreg刚才清零的地方,最最后把tmpreg赋值给CRL,完成GPIOB的Pin3的模式速度配置。
如果设置成输入上拉或者下拉的话还有一段程序如下:
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD)
{
GPIOx->BRR = (((uint32_t)0x01) << pinpos);
}
else
{
/* Set the corresponding ODR bit */
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)
{
GPIOx->BSRR = (((uint32_t)0x01) << pinpos);
}
}
这个很容易了,就不讲了。