STM32 GPIO一共有七个寄存器:CRL、CRH、IDR、ODR、BSRR、BRR、LCKR
具体功能不再赘述,正点原子的库函数开发指南以及STM32参考手册中都有详细的讲解,网上也有很多大佬通俗易懂的解释。
补充:CRL、CRH寄存器的相关知识
CRL寄存器有32位,主要功能是配置pin0-pin7的工作模式,每一个pin由4位负责配置,以pin0为例,它由CRL的bit0~bit3负责,其中高两位bit3、bit2为CNF1、CNF0,低两位为MODE1、MODE0,这里不多逼逼他们之间的互相关系,直接放结论:
1. 先看MODE:
MODE = 00 : 输入模式
MODE > 00 : 输出模式:01--输出速度10M
10--输出速度2M
11--输出速度50M
2. 再看CNF1: CNF0
输入模式下:00--模拟输入 01--浮空输入
1x--上拉输入、下拉输入
输出模式下:00--推挽通用输出
01--开漏通用输出
10--推挽复用输出
11--开漏复用输出
CRH寄存器的工作原理同CRL一样,区别在于CRH用来负责配置pin8~pin15
这里浅析一下其中几个常用寄存器的库函数的程序含义,不当之处,还请大佬们批评指正(爱心)
void GPIO_Init(GPIO_TypeDef* GPIOx,GPIO_InitTypeDef* GPIO_InitStruct)
GPIO初始化函数,传递两个参数,一个是GPIO_TypeDef类型的指针,一个是 GPIO_InitTypeDef 类型的结构体指针,但STM32的库函数限制了它们具体的表达形式,其中GPIOx只能有如下的表达形式
#define IS_GPIO_ALL_PERIPH(PERIPH) (((PERIPH) == GPIOA) || \
((PERIPH) == GPIOB) || \
((PERIPH) == GPIOC) || \
((PERIPH) == GPIOD) || \
((PERIPH) == GPIOE) || \
((PERIPH) == GPIOF) || \
((PERIPH) == GPIOG))
GPIO_InitTypeDef是一个结构体类型,其成员变量分别表示管脚号、端口输出速度、端口工作模式
typedef struct
{
uint16_t GPIO_Pin;
GPIOSpeed_TypeDef GPIO_Speed;
GPIOMode_TypeDef GPIO_Mode;
}GPIO_InitTypeDef;
管脚号用无符号16位二进制数表示,其参数被限制:
#define GPIO_Pin_0 ((uint16_t)0x0001) /*!< Pin 0 selected */
#define GPIO_Pin_1 ((uint16_t)0x0002) /*!< Pin 1 selected */
#define GPIO_Pin_2 ((uint16_t)0x0004) /*!< Pin 2 selected */
#define GPIO_Pin_3 ((uint16_t)0x0008) /*!< Pin 3 selected */
#define GPIO_Pin_4 ((uint16_t)0x0010) /*!< Pin 4 selected */
#define GPIO_Pin_5 ((uint16_t)0x0020) /*!< Pin 5 selected */
#define GPIO_Pin_6 ((uint16_t)0x0040) /*!< Pin 6 selected */
#define GPIO_Pin_7 ((uint16_t)0x0080) /*!< Pin 7 selected */
#define GPIO_Pin_8 ((uint16_t)0x0100) /*!< Pin 8 selected */
#define GPIO_Pin_9 ((uint16_t)0x0200) /*!< Pin 9 selected */
#define GPIO_Pin_10 ((uint16_t)0x0400) /*!< Pin 10 selected */
#define GPIO_Pin_11 ((uint16_t)0x0800) /*!< Pin 11 selected */
#define GPIO_Pin_12 ((uint16_t)0x1000) /*!< Pin 12 selected */
#define GPIO_Pin_13 ((uint16_t)0x2000) /*!< Pin 13 selected */
#define GPIO_Pin_14 ((uint16_t)0x4000) /*!< Pin 14 selected */
#define GPIO_Pin_15 ((uint16_t)0x8000) /*!< Pin 15 selected */
#define GPIO_Pin_All ((uint16_t)0xFFFF) /*!< All pins selected */
GPIO_Speed是GPIOSpeed_TypeDef(枚举)数据类型
typedef enum
{
GPIO_Speed_10MHz = 1,
GPIO_Speed_2MHz,
GPIO_Speed_50MHz
}GPIOSpeed_TypeDef;
GPIO_Mode是GPIOMode_TypeDef(枚举)类型,注意其16进制表示,后面的程序中有妙用!
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;
在void GPIO_Init()中,GPIO_InitTypeDef的成员变量表达形式也被限制
#define IS_GPIO_MODE(MODE) (((MODE) == GPIO_Mode_AIN) || ((MODE) == GPIO_Mode_IN_FLOATING) || \
((MODE) == GPIO_Mode_IPD) || ((MODE) == GPIO_Mode_IPU) || \
((MODE) == GPIO_Mode_Out_OD) || ((MODE) == GPIO_Mode_Out_PP) || \
((MODE) == GPIO_Mode_AF_OD) || ((MODE) == GPIO_Mode_AF_PP))
AIN:模拟输入,用于ADC功能;IPD(Input pull-down):下拉输入
OUT_OD:开漏输出(I2C等需要“线与”时需要用或者需要5V输出时要用);
AF_OD:复用开漏输出;IN_FLOATING:浮空输入(系统初始化默认时的状态);
IPU:(Input Pull-up):上拉输入; OUT_PP(Push-Pull):推挽输出(最常用);
AF_PP(复用推挽输出);
复用功能时,端口的工作状态需要查表~
下面介绍GPIO Mode Configuration 代码段含义
/*---------------------------- GPIO Mode Configuration -----------------------*/
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;
}
((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x0F):两个相与的对象都转换位32位二进制数,相与的结果只保留后四位结果,其他位均为0,结果赋值给currentmode,结合上文所述GPIOMode_TypeDef(枚举)类型不难推出,currentmode的第四位结果只有0000、0100、1000、1100四种情况。这里只是配置了CNF1、CNF0这两个高两位!
if ((((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x10)) != 0x00) :此判断为真的条件为端口的Mode配置为输出模式:
OUT_OD、OUT_PP、AF_OD、AF_PP四种情况。说白了,这个枚举中真正有用的数据在后四位上,不难发现AIN对应的后四位是0000,浮空输入0100、上下拉输入1000、开漏输出0100、推挽输出0000、开漏复用1100、推挽复用1000(后四位中的标红的前两位对应CNF1、CNF0。后两位对应Speed,只是现在不确定是否要配置为输出模式,所以默认均为输入模式00)
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;
如果判断为真,再进行参数有效性判断。
assert_param(IS_GPIO_SPEED(GPIO_InitStruct->GPIO_Speed));
为什么这样做呢?这是因为STM32的GPIO只有在输出模式下才会配置端口的速度,输入模式下无需配置速度~
currentmode |= (uint32_t)GPIO_InitStruct->GPIO_Speed;
结合GPIO_Speed枚举
typedef enum
{
GPIO_Speed_10MHz = 1,
GPIO_Speed_2MHz,
GPIO_Speed_50MHz
}GPIOSpeed_TypeDef;
10M对应0001、2M对应0010、50M对应0011,有效位只有后两位!这样currentmode(目前配置好了高两位)与Speed相或,得到的结果就是CNF、MODE(高两位、低两位)均配置完毕了~
搞定了负责某个pin的四位寄存器配置,接下来要干的活就是把它们放到属于它们在CRL或者CRH中的位置中去。
/*---------------------------- GPIO CRL Configuration ------------------------*/
/* Configure the eight low port pins */
if (((uint32_t)GPIO_InitStruct->GPIO_Pin & ((uint32_t)0x00FF)) != 0x00) //确定是pin0-pin7,则对CRL进行操作
{
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; //如果非零,则对应找到了postion,这句函数的结果要么是0,要么是pos!
if (currentpin == pos) //对应上了pos
{
pos = pinpos << 2; //膨胀关系,8位膨胀到32位对应的映射关系,pos其实是指CRL对应的位置。由于CRL是一个表示pin0-pin7一共八个端口的32位寄存器,一个端口占用4bit,因此pos在pinpos的位置上乘以4以确定要修改的四个bit位置
/* Clear the corresponding low control register bits */
pinmask = ((uint32_t)0x0F) << pos; //第四位1111位移pos个位置,即找到对应的CRL中的位置
tmpreg &= ~pinmask;
/* Write the mode configuration in the corresponding bits */
tmpreg |= (currentmode << pos); //将设置好的currentmode对CRL中相应位置的位进行赋值
/* Reset the corresponding ODR bit */
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) //上拉输入模式下 , 将对应端口置1
{
GPIOx->BSRR = (((uint32_t)0x01) << pinpos);
}
}
}
}
GPIOx->CRL = tmpreg;
}
if (((uint32_t)GPIO_InitStruct->GPIO_Pin & ((uint32_t)0x00FF)) != 0x00) :这段代码的意思是判断pin的管脚号是pin0~pin7还是pin8~pin15,如果是低八位,则进行下面的CRL寄存器配置:
tmpreg = GPIOx->CRL: 建立一个临时寄存器tmpreg,读取目前CRL的信息并保存。
for (pinpos = 0x00; pinpos < 0x08; pinpos++) 从0开始到7进行遍历
pos = ((uint32_t)0x01) << pinpos;currentpin = (GPIO_InitStruct->GPIO_Pin) & pos:
每次将0000 0001向左位移pinpos位,每次位移后,都将其结果和GPIO_Pin进行相与,如果是0,则没有对应上,结果非零,说明对应上了。(位移遍历的操作是为了记录对应管脚在GPIO组内的位置,因为GPIO_Pin的宏定义和单纯的二进制数没有数量上的关系)
接下来骚操作来了!!!!
pos = pinpos << 2: pos不再表示pin引脚位置在二进制上的表示(例如0001 0000表示pin4),而是引脚号的四倍这个数据!目的是找到引脚对应在CRL中的位置!
结合CRL寄存器的说明,可以归纳出pinpos和CRL中pin之间的膨胀关系如图所示:
至此, 乘以四倍的原因找到了!!
pinmask = ((uint32_t)0x0F) << pos :将低四位1111 位移至pinpos对应的CRL中pin的位置,
tmpreg &= ~pinmask: pinmask取反以后与tmpreg相与,结果就是tmpreg将其他pinpos的CRL信息保留,把将要修改的pinpos在CRL中的四个位清除。
tmpreg |= (currentmode << pos):把刚刚设置好的mode信息位移至对应的CRL中对应的四位上,至此,CRL就配置完成了!!!!
当然,如果引脚是输入模式,需要手动配置与之对应的缺省电平
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD) //下拉输入模式下,将对应端口清零
{
GPIOx->BRR = (((uint32_t)0x01) << pinpos);
}
else
{
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU) //上拉输入模式下 , 将对应端口置1
{
GPIOx->BSRR = (((uint32_t)0x01) << pinpos);
}
简单记忆:BRR,用于端口清零,BSRR,用于端口置1
同理,CRH的配置与CRL非常类似,区别在于要找到pin8~pin15与CRL中bit0~bit32之间的膨胀关系,在代码中很容易实现,这里是在pinpos依然从0~7进行遍历,但pos偏移量不再是pinpos,而是pinpos+8,这样做的核心思想是虽然要找pin8~pin15与CRH的膨胀关系,但原理上依然是8位到32位的膨胀关系,与CRL其实是换汤不换药的。后续的赋值操作也无需修改代码,与CRL是一致的~
附:GPIO_Init()函数全部代码及注释
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)
{
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));
/*---------------------------- GPIO Mode Configuration -----------------------*/
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; //第八位前两位是CNF1,CNF0,后两位是MODE,或运算得到一组GPIO_PINx的设置
}
/*---------------------------- GPIO CRL Configuration ------------------------*/
/* Configure the eight low port pins */
if (((uint32_t)GPIO_InitStruct->GPIO_Pin & ((uint32_t)0x00FF)) != 0x00) //确定是pin0-pin7,则对CRL进行操作
{
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; //如果非零,则对应找到了postion,这句函数的结果要么是0,要么是pos!
if (currentpin == pos) //对应上了pos
{
pos = pinpos << 2; //膨胀关系,8位膨胀到32位对应的映射关系,pos其实是指CRL对应的位置。由于CRL是一个表示pin0-pin7一共八个端口的32位寄存器,一个端口占用4bit,因此pos在pinpos的位置上乘以4以确定要修改的四个bit位置
/* Clear the corresponding low control register bits */
pinmask = ((uint32_t)0x0F) << pos; //第四位1111位移pos个位置,即找到对应的CRL中的位置
tmpreg &= ~pinmask;
/* Write the mode configuration in the corresponding bits */
tmpreg |= (currentmode << pos); //将设置好的currentmode对CRL中相应位置的位进行赋值
/* Reset the corresponding ODR bit */
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) //上拉输入模式下 , 将对应端口置1
{
GPIOx->BSRR = (((uint32_t)0x01) << pinpos);
}
}
}
}
GPIOx->CRL = tmpreg;
}
/*---------------------------- GPIO CRH Configuration ------------------------*/
/* Configure the eight high port pins */
if (GPIO_InitStruct->GPIO_Pin > 0x00FF) //对应pin8-pin15
{
tmpreg = GPIOx->CRH;
for (pinpos = 0x00; pinpos < 0x08; pinpos++)
{
pos = (((uint32_t)0x01) << (pinpos + 0x08)); //pos从8遍历到15
/* Get the port pins position */
currentpin = ((GPIO_InitStruct->GPIO_Pin) & pos); //原理同CRL
if (currentpin == pos)
{
pos = pinpos << 2; //pinpos从0开始遍历到8而不是从8遍历到15的原因在此!!膨胀关系一定要从0开始!
/* Clear the corresponding high control register bits */
pinmask = ((uint32_t)0x0F) << pos; //原理同CRL
tmpreg &= ~pinmask;
/* Write the mode configuration in the corresponding bits */
tmpreg |= (currentmode << pos);
/* Reset the corresponding ODR bit */
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD)
{
GPIOx->BRR = (((uint32_t)0x01) << (pinpos + 0x08)); //BRR置零,高16位保持,低16位对应pin0-pin15
}
/* Set the corresponding ODR bit */
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)
{
GPIOx->BSRR = (((uint32_t)0x01) << (pinpos + 0x08)); //BSRR置1,高16位保持,低16位对应pin0-pin15
}
}
}
GPIOx->CRH = tmpreg;
}
}