还记得大学时候学嵌入式的第一个实验就是流水灯,那个时候我已经接触了GPIO(General-purpose I/Os),但是那时候只是简单复制粘贴代码而已并没有学到很多的东西。
GPIO有4个32位配置寄存器,分别是GPIOX_MODER,GPIOX_OTYPER,GPIOX_OSPEEDR和GPIOX_PUPDR,2个32位数据寄存器IDR和ODR,1个32位设置/复位寄存器BSRR,1个32位锁存器LCKR,2个32位功能选择寄存器AFRH,AFRL(名称的翻译都是本人私自翻译,原文见STM32F2XX数据手册),
下面是它一个基本结构图
因为比起硬件我还是对软件熟悉一点,所以我把主要精力用在研究代码上面,通过对代码的理解掌握如何设置寄存器位从而在硬件方面也有所突破。
下面贴出项目中的一段GPIO初始化配置代码:
void LEDInit(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* LED1 LED2 LED3 GPIO Config */
/* GPIOA ande GPIOC Periph clock enable */
RCC_AHB1PeriphClockCmd(CSTXJT_GJY_LED_GPIO_CLK, ENABLE);
/* Configure PC3 in output pushpull mode */
GPIO_InitStructure.GPIO_Pin = CSTXJT_GJY_GPIO_Pin_LED1;
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_UP;
GPIO_Init(CSTXJT_GJY_LED1_GPIO, &GPIO_InitStructure);
/* Configure PA0 PA1 in output pushpull mode */
GPIO_InitStructure.GPIO_Pin = CSTXJT_GJY_GPIO_Pin_LED2 | CSTXJT_GJY_GPIO_Pin_LED3;
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_UP;
GPIO_Init(CSTXJT_GJY_LED2_3_GPIO, &GPIO_InitStructure);
}
首先是 RCC_AHB1PeriphClockCmd(CSTXJT_GJY_LED_GPIO_CLK, 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;
}
}
这段代码中有一个assert_param函数,起初我一直不明白是何用意,经过网上的查询以及程序的定位跟踪才明白这是一个断言机制,它在宏定义中出现,这涉及到另外一个知识已作笔记,故不详述,只需要明白这是对参数的检查,如果参数为空或0,如果是则返回0,如果不是程序中断。后面的if…else代码则是对传过来的GPIO进行时钟启动,为什么要先启动时钟才能使用GPIO这个问题我还没有得到答案。
那么CSTXJT_GJY_LED_GPIO_CLK又是什么呢,文件是这样定义的:
#define CSTXJT_GJY_LED_GPIO_CLK RCC_AHB1Periph_GPIOA | RCC_AHB1Periph_GPIOC
从字面上就能看出这是选择了GPIOA和GPIOC并启动其时钟控制。
后面的代码是对结构变量GPIO_InitStructure的设置,其申明如下:
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;
这些属性对应的是GPIO的相关寄存器,配置完之后才是对已经启动的GPIO来初始化
GPIO_Init(CSTXJT_GJY_LED1_GPIO, &GPIO_InitStructure);
其定义如下,其中的中文注释也只是我个人的理解:
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)
{
uint32_t pinpos = 0x00, pos = 0x00 , currentpin = 0x00;
/* Check the parameters */
assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
assert_param(IS_GPIO_PIN(GPIO_InitStruct->GPIO_Pin));
assert_param(IS_GPIO_MODE(GPIO_InitStruct->GPIO_Mode));
assert_param(IS_GPIO_PUPD(GPIO_InitStruct->GPIO_PuPd));
/* -------------------------Configure the port pins---------------- */
/*-- GPIO Mode Configuration --*/
for (pinpos = 0x00; pinpos < 0x10; pinpos++)/*遍历当前引脚1~16*/
{
pos = ((uint32_t)0x01) << pinpos; /*0x01左移pinpos位,定位到相应的引脚 */
/* Get the port pins position */
currentpin = (GPIO_InitStruct->GPIO_Pin) & pos;/*用currentpin保存当前的引脚号,参数和pos进行与操作,只有2者都是1时currentpin才是当前引脚*/
if (currentpin == pos) /*如果是当前引脚*/
{
GPIOx->MODER &= ~(GPIO_MODER_MODER0 << (pinpos * 2));
GPIOx->MODER |= (((uint32_t)GPIO_InitStruct->GPIO_Mode) << (pinpos * 2));
if ((GPIO_InitStruct->GPIO_Mode == GPIO_Mode_OUT) || (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_AF))
{
/* Check Speed mode parameters */
assert_param(IS_GPIO_SPEED(GPIO_InitStruct->GPIO_Speed));
/* Speed mode configuration */
GPIOx->OSPEEDR &= ~(GPIO_OSPEEDER_OSPEEDR0 << (pinpos * 2));
GPIOx->OSPEEDR |= ((uint32_t)(GPIO_InitStruct->GPIO_Speed) << (pinpos * 2));
/* Check Output mode parameters */
assert_param(IS_GPIO_OTYPE(GPIO_InitStruct->GPIO_OType));
/* Output mode configuration*/
GPIOx->OTYPER &= ~((GPIO_OTYPER_OT_0) << ((uint16_t)pinpos)) ;
GPIOx->OTYPER |= (uint16_t)(((uint16_t)GPIO_InitStruct->GPIO_OType) << ((uint16_t)pinpos));
}
/* Pull-up Pull down resistor configuration*/
GPIOx->PUPDR &= ~(GPIO_PUPDR_PUPDR0 << ((uint16_t)pinpos * 2));
GPIOx->PUPDR |= (((uint32_t)GPIO_InitStruct->GPIO_PuPd) << (pinpos * 2));
}
}
}
大部分代码都比较好理解,但是
GPIOx->MODER &= ~(GPIO_MODER_MODER0 << (pinpos * 2));
GPIOx->MODER |= (((uint32_t)GPIO_InitStruct->GPIO_Mode) << (pinpos * 2));
这两句和下面类似的代码都令我百思不得其解,前面的代码显示选择的引脚是CSTXJT_GJY_GPIO_Pin_LED1,找到它的定义:
#define CSTXJT_GJY_GPIO_Pin_LED1 GPIO_Pin_3
#define GPIO_Pin_3 ((uint16_t)0x0008) /* Pin 3 selected */
每循环一次便将0x01左移多少次直到找到所给的引脚,这里是8,即0x01左移8位,即0000 0001
找到GPIOx->MODER &= ~(GPIO_MODER_MODER0 << (pinpos * 2));中的GPIO_MODER_MODER0
#define GPIO_MODER_MODER0 ((uint32_t)0x00000003)
即 … 0011(省略为前面的28位0),取反则为 … 1100(省略前28位1)
pinpos=8,即将… 1100左移16位,依然是…1100
GPIOX->MODER在原来的函数可以找到其值
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
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;
不难看出是0x01,即0000 0001,将它和… 1100进行与运算则为全零,于是乎我晕了。不过GPIO大致的初始化配置我还是基本掌握了,也没白琢磨这些,在实际写代码其实的很少直接编写这些,因为GPIO初始化函数在库函数已经给出,只需要调用就可以了。然因为基础不牢固就只能稍微了解一下其原理了。