以下是我要讲解的GPIO初始化程序段,尽量讲解小白学习过程中不解的每一个方面。
代码讲解时我是根据一个程序边讲边跳入它的声明或是定义中讲解的。
voidLED_GPIO_Config(void)
{
/*定义一个GPIO_InitTypeDef类型的结构体*/
1. GPIO_InitTypeDef GPIO_InitStructure;
/*开启GPIOF的外设时钟*/
2. RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF,ENABLE);
/*选择要控制的GPIOF引脚*/
3. GPIO_InitStructure.GPIO_Pin =GPIO_Pin_9 | GPIO_Pin_10;
/*设置引脚模式为通用推挽输出*/
4. GPIO_InitStructure.GPIO_Mode =GPIO_Mode_OUT;
5. GPIO_InitStructure.GPIO_OType =GPIO_OType_PP;
/*设置引脚速率为100MHz */
6. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
7. GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
/*调用库函数,初始化GPIOF*/
8. GPIO_Init(GPIOF, &GPIO_InitStructure);
}
解读:
1:对于1来讲,正如注解所说,先定义一个结构体,结构体中有需要初始化的一些结构体变量,而这些变量又有什么意义呢?这是我们要思考的问题。
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;
这是结构体中定义的变量
再看下GPIOMode_TypeDef,这是一个枚举变量。而几个枚举元素的值其实代表了对相应的寄存器的赋值(相应的赋值对应相应的模式)。例如将GPIO模式寄存器赋值0x00,则表示引脚配置为输入模式。
typedef enum
{
GPIO_Mode_IN = 0x00, /*!< GPIO Input Mode */
GPIO_Mode_OUT = 0x01, /*!< GPIO Output Mode */
GPIO_Mode_AF = 0x02, /*!< GPIO Alternate function Mode */
GPIO_Mode_AN = 0x03 /*!< GPIO Analog Mode */
}GPIOMode_TypeDef;
现在解读《2》句,RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE);
void RCC_AHB1PeriphClockCmd(uint32_t RCC_AHB1Periph, FunctionalState NewState)
{
/* Check the parameters */
assert_param(IS_RCC_AHB1_CLOCK_PERIPH(RCC_AHB1Periph));
assert_param(IS_FUNCTIONAL_STATE(NewState));
if (NewState != DISABLE)
{
RCC->AHB1ENR |= RCC_AHB1Periph;
}
else
{
RCC->AHB1ENR &= ~RCC_AHB1Periph;
}
}
这是一个配置时钟的函数,有两个参数,第一个参数的专业介绍:
RCC_AHBPeriph:specifies the AHB1 peripheral to gates its clock.
This parameter canbe any combination of the following values:
现在我来解释一下,大体意思是:指定AHB1外设去给它的时钟装个门,很形象,第二句的意思是说这个参数可以为一下的一些值,当然这里我没给出来,篇幅所限。
结合2的实例,RCC_AHB1Periph_GPIOF,它的意思是(先透露一下是对AHB1外设时钟使能寄存器的置位值,即将Bit7置位1,使能GPIOF端口时钟)
#defineRCC_AHB1Periph_GPIOF ((uint32_t)0x00000020)。
再看这个函数中的第5句,理解好这句的执行十分重要。
RCC->AHB1ENR |=RCC_AHB1Periph;
#define RCC ((RCC_TypeDef *) RCC_BASE)(RCC是个宏,先透露,猜也猜得到是个地址)
RCC表示一个地址,同时通过强制转换将该地址指向了(或者说赋值给了)RCC_TypeDef这个结构体,而这个结构体中,是许多的寄存器,前面已经讲了,这个结构体的初始地址已经有了,具体是什么意思后面再讲。而结构体中的成员按照顺序依照C语言的语法规则,每个成员依次占用4个字节(一个寄存器32位,占4个字节),刚好是每个寄存器的偏移地址。所以说这个结构体变量RCC与实际芯片中的每个寄存器(我们已经按顺序取好了名字)的物理地址顺序是一致的。我们通过寄存器操作就可以达到相应的目的,将相应的寄存器位置位来实现。而且每个寄存器都是见名知义。
所以至此我应该是解释了RCC的内容以及作用,下面我还要深扒这个地址的根源是谁,究竟代表什么意思,前面我虽然讲了该地址是指向RCC-TypeDef,但为什么指向它,指向它又是什么意思我还没讲。
#define RCC_BASE (AHB1PERIPH_BASE+ 0x3800)(这是挂载在AHB1总线上的外设首地址了)
#define AHB1PERIPH_BASE (PERIPH_BASE + 0x00020000)(外设基地址加偏移地址指向AHB1总线基地址)
#define PERIPH_BASE ((uint32_t)0x40000000) /*!< Peripheral base address in the aliasregion(注释的意思我猜是:这个别名区域代表外设基地址)
{
参考:零死角玩转STM32—F407霸天虎.pdf
第6.5.1节中有详细介绍。(这个资料自己百度也行,或者)
片上外设区分为四条总线,根据外设速度的不同,不同总线挂载着不同的外设, APB
挂载低速外设, AHB 挂载高速外设。相应总线的最低地址我们称为该总线的基地址,总线
基地址也是挂载在该总线上的首个外设的地址。其中 APB1总线的地址最低,片上外设从
这里开始,也叫外设基地址。
}
所以,RCC_TypeDef指定地址后,就代表了芯片上实实在在的一片物理地址空间,这片地址上有各种寄存器,通过对不同寄存器进行置位,就能实现不同的功能(具体不同寄存器的不同位有什么不同功能需参考技术手册)。
具体有哪些寄存器见下:见名知义,我们用到的是其中的时钟使能寄存器。
__IO uint32_t AHB1ENR; /*!< RCC AHB1 peripheral clock register, Address offset: 0x30 */
typedef struct
{
__IO uint32_t AHB1ENR; /*!< RCC AHB1 peripheral clock register, Address offset: 0x30 */
__IO uint32_t AHB2ENR; /*!< RCC AHB2 peripheral clock register, Address offset: 0x34 */
__IO uint32_t AHB3ENR; /*!< RCC AHB3 peripheral clock register, Address offset: 0x38 */
uint32_t RESERVED2; /*!< Reserved, 0x3C */
__IO uint32_t APB1ENR; /*!< RCC APB1 peripheral clock enable register, Address offset: 0x40 */
__IO uint32_t APB2ENR; /*!< RCC APB2 peripheral clock enable register, Address offset: 0x44 */
uint32_t RESERVED3[2]; /*!< Reserved, 0x48-0x4C */
__IO uint32_t AHB1LPENR; /*!< RCC AHB1 peripheral clock enable in low power mode register, Address offset: 0x50 */
__IO uint32_t AHB2LPENR; /*!< RCC AHB2 peripheral clock enable in low power mode register, Address offset: 0x54 */
__IO uint32_t AHB3LPENR; /*!< RCC AHB3 peripheral clock enable in low power mode register, Address offset: 0x58 */
uint32_t RESERVED4; /*!< Reserved, 0x5C */
__IO uint32_t APB1LPENR; /*!< RCC APB1 peripheral clock enable in low power mode register, Address offset: 0x60 */
__IO uint32_t APB2LPENR; /*!< RCC APB2 peripheral clock enable in low power mode register, Address offset: 0x64 */
uint32_t RESERVED5[2]; /*!< Reserved, 0x68-0x6C */
__IO uint32_t BDCR; /*!< RCC Backup domain control register, Address offset: 0x70 */
__IO uint32_t CSR; /*!< RCC clock control & status register, Address offset: 0x74 */
uint32_t RESERVED6[2]; /*!< Reserved, 0x78-0x7C */
__IO uint32_t SSCGR; /*!< RCC spread spectrum clock generation register, Address offset: 0x80 */
__IO uint32_t PLLI2SCFGR; /*!< RCC PLLI2S configuration register, Address offset: 0x84 */
__IO uint32_t PLLSAICFGR; /*!< RCC PLLSAI configuration register, Address offset: 0x88 */
__IO uint32_t DCKCFGR; /*!< RCC Dedicated Clocks configuration register, Address offset: 0x8C */
} RCC_TypeDef;
这里再多说一句:__IO是什么意思,#define __IO volatile /*!< Defines 'read / write' permissions */
volatile又是什么意思呢,简单的说,就是不让编译器进行优化,即每次读取或者修改值的时候,都必须重新从内存或者寄存器中读取或者修改。
现在再回到RCC->AHB1ENR |= RCC_AHB1Periph;前半句的意思是访问RCC上的AHB1ENR寄存器,见名知义这是AHB1外设时钟使能寄存器。后半句的意思是将RCC_AHB1Periph的值与AHB1ENR的值先或运算再赋给AHB1ENR,因为AHB1ENR初始化为0,实际上是直接复制给AHB1ENR的。(这里注意一个地方就是我一下子说AHB1ENR寄存器,一下子又把AHB1ENR当做一个变量值对待赋值,实际上是指AHB1ENR代表的那个地址区域,它被称作寄存器,对那个区域进行赋值操作,反映在C语言语法上就是对一个变量进行操作,我们软件设计使得这个变量地址落在该寄存器物理地址上)
现在解释第《3》句话,GPIO_InitStructure.GPIO_Pin= GPIO_Pin_9 | GPIO_Pin_10;
这句话很好理解了,就是选定这两个引脚,哦对了,刚刚想起有个知识点需要提一下,一个寄存器分高16位和低16位,控制的是GPIOx端口的0-15个引脚,相邻两个引脚的地址相差2倍。上面这个语句其实是选定了Pin9和Pin10这两个引脚(他们的宏定义的意思都是指偏移地址,相对于GPIOx基地址的)。那么程序上如何实现选定到这两个引脚的呢?我们已经知道了,要挂载GPIOF外设,并且上一条程序已经使能了GPIOF时钟,而且最终我们是一起初始化GPIOF,包括其他相关设定列输入输出模式,速度等。那么如何将GPIOF基地址与我们所选定的两个引脚的偏移地址链接(通俗的讲是联系)起来呢?
这就有点复杂了,请耐心,休息一下…。首先我们来讲一下这个FOR方法体。
for (pinpos = 0x00; pinpos < 0x10; pinpos++)
{
pos = ((uint32_t)0x01) << pinpos;
/* Get the port pins position */
currentpin = (GPIO_InitStruct->GPIO_Pin) & pos;
if (currentpin == pos)
{
GPIOx->MODER &= ~(GPIO_MODER_MODER0 << (pinpos * 2));
GPIOx->MODER |= (((uint32_t)GPIO_InitStruct->GPIO_Mode) << (pinpos * 2));
1. FOR循环中的循环条件告诉我们是依次检索16个引脚。
2. 方法体中的第一条语句表示循环左移得到我们要检索的引脚或是要操作的引脚,语句2表示该待检索的引脚与我们预先设定的引脚相与,如果依然得到待检索的引脚值,那么表明就是我们选定的引脚。
3. 按照2的思路如果条件成立,执行IF方法体,现在先介绍一下GPIOX->MODER,两层意思,GPIOX实质上代表的是某GPIO的端口地址,经过GPIO_TypeDef强制转换成指针,再宏定义得来的。所以现在GPIOX指向的是GPIO_TypeDef结构体的首地址。即这句话表示指向GPIOX的端口模式寄存器,通过对该寄存器赋值控制端口模式。
4. 现在需要了解GPIO_MODER_MODER0什么意思,这条程序的具体含义是什么,否则,下一条更是完全不懂。
5. 其实这个名字就是表示该寄存器的端口配置位,占用寄存器(前面已经说过它有32位,16个引脚,全部配置共占用32位)两个位。这样就好理解了,那句程序的意思就可以理解为对寄存器操作了。
6. 这里还需解释一个东西就是为什么程序里要PINS*2,我已经讲了,每个端口配置位占用寄存器两个位,即你要控制(或者说配置)那个引脚,在配置寄存器的位时,位数为引脚号乘以2.。
7. GPIOx->MODER &= ~(GPIO_MODER_MODER0 << (pinpos *2));
#define GPIO_MODER_MODER0 ((uint32_t)0x00000003)
所以这句程序的意思是先移位得到(前面我们要配置引脚9和11)0000 0000 0000 1100 0000 0000 0000 0000取反得到1111 11111111 0011 1111 1111…,相与等于1111 1111 1111 0011 …,即将除要配置的引脚之外的引脚在寄存器内的配置位全置为11,
8. GPIOx->MODER |= (((uint32_t)GPIO_InitStruct->GPIO_Mode)<< (pinpos * 2));
这里的话自己根据C语言的运算语法算一遍很容易发现是将我们选定的引脚配置成我们初始化时设定的GPIO口模式。具体实现是通过将模式寄存器的相应引脚配置位配置成相应的代码模式(即我们初始化时设定的GPIO_MODE).这样一来未选定的引脚被配置为模拟模式,选定的引脚则配置成想配置的模式了。
9.
至此我已经解释完了第<3>句,实际上附带介绍了第四句,即怎么将控制的引脚配置成相应的模式。第<5>句的配置道理其实同第四句,也是通过控制相应的寄存器,将寄存器相应的引脚配置位置为对应的模式代码。
具体程序见最后两句。(参照相应寄存器即模式代码如下)
#define GPIO_PUPDR_PUPDR0 ((uint32_t)0x00000003)
#define GPIO_PUPDR_PUPDR0_0 ((uint32_t)0x00000001)
#define GPIO_PUPDR_PUPDR0_1 ((uint32_t)0x00000002)
10. 然后相信各位对于速度的配置也能了然于胸了。
11. 相应的GPIO_Init();这个函数就自然知道是啥意思了。