前言:对于初学者,刚开始学习GPIO的工作模式时,可能会发现一个问题,官方给的关于8种工作模式的枚举定义里面,有出现诸如“0x28”、"0x48"、“0x14”、“0x1C”等等数值,与官方给的端口配置低/高寄存器(GPIOx_CRL)或(GPIOx_CRH)比较之后发现对应不上,因此产生许多不理解,此文粗鄙说明一下这种情况。
以下是官方给的关于8种工作模式的枚举定义:
typedef enum
{
GPIO_Mode_AIN = 0x00, // 模拟输入
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;
一、分别观察高低四位
通过观察我们可以发现,四个输入变量和四个输出变量的低四位,基本从“0”、“4”、“8”和“C”这四个之间取值,而对于高四位,四个输入变量都为非“1”,四个输出变量都为“1”.
再观察官方的参考手册
二、结合以上发现:
1、观察“CNFy[1:0]”
如果先不管“MODEy[1:0]”的值,只观察"CNFy[1:0]"的值,我们能发现,输入输出几个模式的取值都是在“00”、“01”、“10”和“11”之间选择,跟我们之前观察的“0”、“4”、“8”和“C”四个相对应
2、观察”MODEy[1:0]“
对于MODEy[1:0],这个两位是设置端口的最大输出速度,所以输入端口不用设置,因此可以发现,对于区分输入输出,官方统一将MODEy[1:0]的选择,用之前观察到的高四位来区分
3、”下拉/上拉输入模式“
至于下拉/上拉输入模式,在寄存器说明里面,他们的"CNFy[1:0]"一样,都为“10”,即对应为“8”,在枚举中,官方定义下拉/上拉输入模式分别为“0x28”和"0x48",其中低四位的“8”就是对应"CNFy[1:0]"的“10”,高四位的“2”和“4”,个人觉得只是作为区分两者而已。
三、分析GPIO端口初始化函数
以下是初始化函数的代码+个人的注释(或者理解)
1、"assert_param()"函数
在程序开发调试时,该函数用于检测传入的参数的有效性
2、GPIO模式配置
2-1、“currentmode = ((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x0F);”
执行该语句后,结果是将(uint32_t)GPIO_InitStruct->GPIO_Mode的高四位清零,低四位保持不变,意在获取低四位的值当作端口的模式,诸如前文提到的“0”、“4”、“8”和“C”。
接下来的代码是来验证(uint32_t)GPIO_InitStruct->GPIO_Mode的高四位的是”0000“还是”0001“
2-2、if ((((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x10)) != 0x00)
如果是”0000“则该端口为输入模式,不需要设置速度参数,若为”0001“则该端口为输出模式,需要设置速度参数,进入函数后,先检测参数有效性,接着将速度的值加进去
3、判断该端口是属于高八or低八
每个GPIO一共有16个IO口,端口配置由”GPIOx_CRL“和”GPIOx_CRH“两个端口配置寄存器来配置,”GPIOx_CRL“配置低8个IO口,即0~7,”GPIOx_CRH“配置高8个IO口,即8~15。
if (((uint32_t)GPIO_InitStruct->GPIO_Pin & ((uint32_t)0x00FF)) != 0x00)
代码中先判断该端口是否为”GPIOx_CRL“,若是则配置”GPIOx_CRL“寄存器,否则配置”GPIOx_CRH“寄存器
tmpreg = GPIOx->CRL;
读取寄存器的值存放到临时变量,后面配置参数(端口号和输入输出模式)会用到
4、判断该端口是哪个IO口
”GPIOx_CRL“存放着八个IO的配置,因此需要知道传进来的参数是属于哪个端口的,以便知道该左移多少位
一个for循环至多运行八次
pos = ((uint32_t)0x01) << pinpos;每次多左移一位
currentpin = (GPIO_InitStruct->GPIO_Pin) & pos;确定端口位置,进入if函数
5、在”GPIOx_CRL“寄存器的指定位置配置参数
”GPIOx_CRL“寄存器每次进行读写操作时,不能位寻址,只通过字操作(即两个字节共16位),为了不改变其他位的值,其他位一般采用”跟’1‘进行’&’操作“,参数位的写入采用”跟‘0’进行‘|’操作“
5-1、pinmask = ((uint32_t)0x0F) << pos;
该语句是将参数位的四个位挖空做准备(即该四位都为0,其他位都为1)
5-2、tmpreg &= ~pinmask;
该语句将tmpreg中指定四位清零,其他位保持不变
5-3、tmpreg |= (currentmode << pos);
该语句配置指定四位的值,即写入参数
6、判断是下拉输入还是上拉输入
在官方数据手册里面,下拉输入和上拉输入的输入模式端口配置在端口配置寄存器的值是一样的,都为”1000“即”0x08“,在枚举变量中,他们的值却初始化分别为:下拉输入”0x28“,上拉输入"0x48",为了在配置参数时,能够区分两者,则将”0x08“的高四位设置为”2“和”4“,个人认为”2“和”4“的设置是随意的,设置其他值也是允许的,如”0“和”4“或”0“和”8“或”2“和”8“或”4“和”8“等等。
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD)
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)
该语句判断是”GPIO_Mode_IPD“下拉输入,还是”GPIO_Mode_IPU“上拉输入
GPIOx->BRR = (((uint32_t)0x01) << pinpos);
GPIOx->BSRR = (((uint32_t)0x01) << pinpos);
该语句设置对应”GPIOx->ODR“对应的端口值为0或1
其中,GPIOx->BRR和GPIOx->BSRR可自行查看手册