STM32的GPIO使用的函数剖析
一、GPIO_Init函数解析1
本文引用地址:https://www.eepw.com.cn/article/201611/317503.htm1、参数GPIO_TypeDef1
2、参数GPIO_InitStruct2
3、函数代码详解4
4、备注6
一、GPIO_Init函数解析
首先来看一下GPIO_Init函数的原型voidGPIO_Init(GPIO_TypeDef*GPIOx,GPIO_InitTypeDef*GPIO_InitStruct)。这个函数的实现是在Stm32f10x_gpio.c文件中,若要使用该函数在相应的应用程序的前面包含Stm32f10x_gpio.h头文件。
1、参数GPIO_TypeDef
该函数的第一个参数为GPIO_TypeDef,它是一个结构体类型,该类型在Stm32f10x.h中被定义。定义的原型为:
typedefstruct
{
__IOuint32_tCRL;
__IOuint32_tCRH;
__IOuint32_tIDR;
__IOuint32_tODR;
__IOuint32_tBSRR;
__IOuint32_tBRR;
__IOuint32_tLCKR;
}GPIO_TypeDef;
在这个结构体类型当中有7个32(8字节)位的变量,这些变量在存储空间的地址是相邻的。打开STM32数据手册不难看出,每个端口对应有16的引脚,由7个寄存器控制GPIO行为,并且这7个寄存器的顺序也是连续的。各个端口都有相同的结构。STM32的固件库就将这种结构抽象出一个类型GPIO_TypeDef。在操作寄存器之前你一定要有一个寄存器映射的操作,否则无法访问指定的寄存器,在这里我们只需要映射一次而不需要映射7此。这样做是不是很方便,也提高了代码的可读性,使代码规范化。
既然GPIO_Init的第一个参数GPIO_TypeDef的指针变量,这个指针变量存放的就是某一个端口的首地址。某一个程序的调用语句是这样的GPIO_Init(GPIOD,&GPIO_InitStructure);//初始化GPIOD
GPID是固件库中定义的一个宏,在编译的时候会宏展开,先列出与GPIOD端口地址映射有关的宏定义如下:
#defineGPIOD((GPIO_TypeDef*)GPIOD_BASE)
#defineGPIOD_BASE(APB2PERIPH_BASE+0x1400)
#defineAPB2PERIPH_BASE(PERIPH_BASE+0x10000)
#definePERIPH_BASE((uint32_t)0x40000000)
看到了0x40000000这个数字是不是非常熟悉,它是外设的首地址。在STM32芯片的内部STM32有两个,一个叫APB1,一个叫APB2。每一个APB桥都会管理很多外设。STM32F10x把这两个APB的外设寄存器访问地址放在了不同的存储空间。0x10000就是APB2外设的存储空间首地址相对于整个外设的偏移。而0x1400是GPIOD端口外设首地址相对于APB2外设的存储空间首地址的偏移。这样就找到了GPIOD外设的基地址了!而((GPIO_TypeDef*)GPIOD_BASE)可以同时实现所有控制GPIOD端口的7个寄存器的映射。若访问某一个寄存器只需要通过指向GPIO_TypeDef变量的指针。
2、参数GPIO_InitStruct
第二个参数的为GPIO_InitTypeDef*GPIO_InitStruct。就是一个指向GPIO_InitTypeDef的地址。第一个参数只找到配置的目标寄存器,第二个参数就是对相应端口如何配置的数据参数。这些参数存储在指向GPIO_InitTypeDef变量的首地址处。先列处该参数由来的一断代码
GPIO_InitTypeDefGPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_InitTypeDef是一个结构体的变量,该变量在Stm32f10x_gpio.h头文件中被定义,定义的原型如下:
typedefstruct
{
uint16_tGPIO_Pin;
GPIOSpeed_TypeDefGPIO_Speed;
GPIOMode_TypeDefGPIO_Mode;
}GPIO_InitTypeDef;
GPIO_InitTypeDef的第一个变量为GPIO_Pin是一个16为的无符号数,该数只有16位,每一位代表一个引脚,若要配置某一个端口的某一个引脚只需要把相应的位设置为1就可以了。在STM32的固件库中有如下引脚号定义:
#defineGPIO_Pin_0((uint16_t)0x0001)/*! #defineGPIO_Pin_1((uint16_t)0x0002)/*! #defineGPIO_Pin_2((uint16_t)0x0004)/*! #defineGPIO_Pin_3((uint16_t)0x0008)/*! #defineGPIO_Pin_4((uint16_t)0x0010)/*! #defineGPIO_Pin_5((uint16_t)0x0020)/*! #defineGPIO_Pin_6((uint16_t)0x0040)/*! #defineGPIO_Pin_7((uint16_t)0x0080)/*! #defineGPIO_Pin_8((uint16_t)0x0100)/*! #defineGPIO_Pin_9((uint16_t)0x0200)/*! #defineGPIO_Pin_10((uint16_t)0x0400)/*! #defineGPIO_Pin_11((uint16_t)0x0800)/*! #defineGPIO_Pin_12((uint16_t)0x1000)/*! #defineGPIO_Pin_13((uint16_t)0x2000)/*! #defineGPIO_Pin_14((uint16_t)0x4000)/*! #defineGPIO_Pin_15((uint16_t)0x8000)/*! #defineGPIO_Pin_All((uint16_t)0xFFFF)/*! 使用这些定义好的宏就方便多了,要配置某几个引脚只需要把相应的引脚相或就可以了。若你要多某一个端口的所有为进行配置,那么只需要使用一个宏GPIO_Pin_All。简单吧!哈哈! GPIOSpeed_TypeDef是一个枚举变量,它用于存储GPIO速度的参数,它的定义如下: typedefenum { GPIO_Speed_10MHz=1, GPIO_Speed_2MHz, GPIO_Speed_50MHz }GPIOSpeed_TypeDef; 通过定义可以知道,GPIOSpeed_TypeDef的变量有三种取值,那么GPIO的速度有三种, 枚举变量的值 对应的速度 1 10MHZ 2 2MHZ 3 50MHZ GPIOMode_TypeDef也是一个枚举变量,它用于存储GPIO工作的模式,它的定义如下: typedefenum {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; 设计这个枚举变量的可取值有一定的意义。在第四位当中只用到了其中的高两位,这两位数据用来存储到某一个引脚的模式控制位MODEx[1:0],而高四位用来标志某一些标志。 高四位的取值 意义 0 输入模式 1 输出模式 2 下拉输入 4 上拉输入 上面是GPIO_Init函数参数的解释。我在我们就可以进入GPIO_Init函数的内部看看了。 先把函数的代码列出,对代码的解释都放在了注释当中,如下: voidGPIO_Init(GPIO_TypeDef*GPIOx,GPIO_InitTypeDef*GPIO_InitStruct) { uint32_tcurrentmode=0x00,currentpin=0x00,pinpos=0x00,pos=0x00; uint32_ttmpreg=0x00,pinmask=0x00; /*Checktheparameters*/ 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)); /*----------------------------GPIOModeConfiguration-----------------------*/ currentmode=((uint32_t)GPIO_InitStruct->GPIO_Mode)&((uint32_t)0x0F); if((((uint32_t)GPIO_InitStruct->GPIO_Mode)&((uint32_t)0x10))!=0x00)//若为输出上拉就会配置GPIO的速度 { /*Checktheparameters*/ assert_param(IS_GPIO_SPEED(GPIO_InitStruct->GPIO_Speed)); /*Outputmode*/ currentmode|=(uint32_t)GPIO_InitStruct->GPIO_Speed; } /*----------------------------GPIOCRLConfiguration------------------------*/ /*Configuretheeightlowportpins*/ if(((uint32_t)GPIO_InitStruct->GPIO_Pin&((uint32_t)0x00FF))!=0x00)//若对第八个引脚进行配置,GPIO_Pin的值某一位为1就会对该引脚配置 { tmpreg=GPIOx->CRL;//暂存GPIO控制寄存器原来的值 for(pinpos=0x00;pinpos<0x08;pinpos++)//扫描8次决定,查看哪一引脚需要配置,若//需要配置则进行配置 { pos=((uint32_t)0x01)< /*Gettheportpinsposition*/ currentpin=(GPIO_InitStruct->GPIO_Pin)&pos;//currentpin的值为0或者为pos if(currentpin==pos)//若为pos说明该位需要配置 { pos=pinpos<<2;//pinpos的值乘以4得到某一引脚配置位的最低位号:0,4,8......28 /*Clearthecorrespondinglowcontrolregisterbits*///用于屏蔽某一个引脚的配置位,使这4位为0 pinmask=((uint32_t)0x0F)< tmpreg&=~pinmask; /*Writethemodeconfigurationinthecorrespondingbits*/ tmpreg|=(currentmode< /*ResetthecorrespondingODRbit*/ if(GPIO_InitStruct->GPIO_Mode==GPIO_Mode_IPD)//若为输入下拉,需要打开相应的开关 { GPIOx->BRR=(((uint32_t)0x01)< } else { /*SetthecorrespondingODRbit*/ if(GPIO_InitStruct->GPIO_Mode==GPIO_Mode_IPU)//若为输入下拉,需要打开相应的开关 { GPIOx->BSRR=(((uint32_t)0x01)< } } } } GPIOx->CRL=tmpreg;//对低8个引脚配置寄存器赋值 } /*----------------------------GPIOCRHConfiguration------------------------*/ /*Configuretheeighthighportpins*/ if(GPIO_InitStruct->GPIO_Pin>0x00FF) { tmpreg=GPIOx->CRH; for(pinpos=0x00;pinpos<0x08;pinpos++) { pos=(((uint32_t)0x01)<<(pinpos+0x08)); /*Gettheportpinsposition*/ currentpin=((GPIO_InitStruct->GPIO_Pin)&pos); if(currentpin==pos) { pos=pinpos<<2; /*Clearthecorrespondinghighcontrolregisterbits*/ pinmask=((uint32_t)0x0F)< tmpreg&=~pinmask; /*Writethemodeconfigurationinthecorrespondingbits*/ tmpreg|=(currentmode< /*ResetthecorrespondingODRbit*/ if(GPIO_InitStruct->GPIO_Mode==GPIO_Mode_IPD) { GPIOx->BRR=(((uint32_t)0x01)<<(pinpos+0x08)); } /*SetthecorrespondingODRbit*/ if(GPIO_InitStruct->GPIO_Mode==GPIO_Mode_IPU) { GPIOx->BSRR=(((uint32_t)0x01)<<(pinpos+0x08)); } } } GPIOx->CRH=tmpreg; } } assert_param函数是对参数的检测。参数要么是逻辑0或者1。IS_GPIO_ALL_PERIPH也是一个宏,宏定义为: #defineIS_GPIO_ALL_PERIPH(PERIPH)(((PERIPH)==GPIOA)|| ((PERIPH)==GPIOB)|| ((PERIPH)==GPIOC)|| ((PERIPH)==GPIOD)|| ((PERIPH)==GPIOE)|| ((PERIPH)==GPIOF)|| ((PERIPH)==GPIOG)) 其他的参数检测函数当中使用的宏都是相似的,具体可以查看相应的宏定义,在此不一一列出。 对低8位的配置和对高8位的配置原理是一样的。所以在此只对低8引脚配置进行说明。3、函数代码详解
4、备注
评论