目录
GPIO_InitTypeDef* GPIO_InitStruct结构体地址变量配置
前言
上节课我们讲解了如何实现 RCC 这个外设的寄存器结构体声明,把时钟相关的代码改成寄存器结构体操作的方式,以及编写函数库(端口置位以及复位函数),包括防止重复定义框架·,具体代码以及主函数的更新还有main函数代码的解释。本课我们学习如何更加系统的优化程序以及运用STM32官方给出的标准库。
STM32第四节:自己写库——构建库函数雏形(第三节)
优化代码
上节课我们编写了我们的固件库函数——置位和清除端口函数,而本节课我们继续完善代码的初始化部分。通过代码我们可知,在初始化模块中不管是二进制还是十六进制,都不能一下让我们看懂,必须返回到参考手册中去查看相关寄存器的解释。把原函数代码再次拷贝一份,将#elif的值改为1,原值改为0。
#elif 1
//打开 GPIOB 端口的时钟
RCC->APB2ENR |= ((1)<<3);
//配置IO口为输出
GPIOB->CRL &= ~((0x0f)<<(4*0));
GPIOB->CRL |= ((1)<<(4*0));//*0,*1,*5
GPIO_SetBits(GPIOB,GPIO_Pin_0);
GPIO_ResetBits(GPIOB,GPIO_Pin_0);
那么怎么优化代码呢?
定义初始化结构体 GPIO_InitTypeDef
我们可以定义初始化结构体 GPIO_InitTypeDef。在第一节课的代码中定义位操作函数后,简化了控制GPIO输出电平的代码,但在控制GPIO输出电平前,还有一步是需要初始化GPIO引脚的各种模式,比如推挽输出模式。这部分代码涉及的寄存器有很多,我们希望Init GPIO也能以如此简单的方法去实现。为此,我们先根据 GPIO 初始化时涉及到的初始化参数以结构体的形式封装起来,声明一个名为 GPIO_InitTypeDef 的结构体类型。
typedef struct
{
uint16_t GPIO_Pin;
uint16_t GPIO_Speed;
uint16_t GPIO_Mode;
}GPIO_InitTypeDef;
枚举定义来限定结构体成员
其中,Pin为寄存器的io口(位数),Speed代表寄存器输出的速度,Mode代表寄存器输出的模式,均用uint16_t(short类型)定义。但是阅读相关手册我们发现,如果端口模式位为输出的话,就只有00,01,11这三种模式输出。可我们用short类型定义的话,就有65536位,为了不犯错,我们可以采用C语言中的枚举定义来限定结构体成员Speed(3)和Mode(8)的模式。
/**
* GPIO工作速度枚举定义
*/
typedef enum
{
GPIO_Speed_10MHz = 1, // 10MHZ (01)b
GPIO_Speed_2MHz, // 2MHZ (10)b
GPIO_Speed_50MHz // 50MHZ (11)b
}GPIOSpeed_TypeDef;
/**
* GPIO工作模式枚举定义
*/
typedef enum
{ GPIO_Mode_AIN = 0x0, // 模拟输入 (0000 0000)b
GPIO_Mode_IN_FLOATING = 0x04, // 浮空输入 (0000 0100)b
GPIO_Mode_IPD = 0x28, // 下拉输入 (0010 1000)b
GPIO_Mode_IPU = 0x48, // 上拉输入 (0100 1000)b
GPIO_Mode_Out_OD = 0x14, // 开漏输出 (0001 0100)b
GPIO_Mode_Out_PP = 0x10, // 推挽输出 (0001 0000)b
GPIO_Mode_AF_OD = 0x1C, // 复用开漏输出 (0001 1100)b
GPIO_Mode_AF_PP = 0x18 // 复用推挽输出 (0001 1000)b
}GPIOMode_TypeDef;
调用GPIO_Init函数
除此之外还有一个官方已经写好的函数GPIO_Init函数,有两个形参输入,无返回值。如下述代码所示,第一个形参写的是这种结构体的指针传进来是哪一个GPIO外设,第二个形参编写的一个GPIO_InitTypeDef结构体对应的指针参数,而这个结构体在之前的代码中已经写出来了。
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;
/*---------------------- GPIO 模式配置 --------------------------*/
// 把输入参数GPIO_Mode的低四位暂存在currentmode
currentmode = ((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x0F);
// bit4是1表示输出,bit4是0则是输入
// 判断bit4是1还是0,即首选判断是输入还是输出模式
if ((((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x10)) != 0x00)
{
// 输出模式则要设置输出速度
currentmode |= (uint32_t)GPIO_InitStruct->GPIO_Speed;
}
/*-------------GPIO CRL 寄存器配置 CRL寄存器控制着低8位IO- -------*/
// 配置端口低8位,即Pin0~Pin7
if (((uint32_t)GPIO_InitStruct->GPIO_Pin & ((uint32_t)0x00FF)) != 0x00)
{
// 先备份CRL寄存器的值
tmpreg = GPIOx->CRL;
// 循环,从Pin0开始配对,找出具体的Pin
for (pinpos = 0x00; pinpos < 0x08; pinpos++)
{
// pos的值为1左移pinpos位
pos = ((uint32_t)0x01) << pinpos;
// 令pos与输入参数GPIO_PIN作位与运算,为下面的判断作准备
currentpin = (GPIO_InitStruct->GPIO_Pin) & pos;
//若currentpin=pos,则找到使用的引脚
if (currentpin == pos)
{
// pinpos的值左移两位(乘以4),因为寄存器中4个寄存器位配置一个引脚
pos = pinpos << 2;
//把控制这个引脚的4个寄存器位清零,其它寄存器位不变
pinmask = ((uint32_t)0x0F) << pos;
tmpreg &= ~pinmask;
// 向寄存器写入将要配置的引脚的模式
tmpreg |= (currentmode << pos);
// 判断是否为下拉输入模式
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD)
{
// 下拉输入模式,引脚默认置0,对BRR寄存器写1可对引脚置0
GPIOx->BRR = (((uint32_t)0x01) << pinpos);
}
else
{
// 判断是否为上拉输入模式
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)
{
// 上拉输入模式,引脚默认值为1,对BSRR寄存器写1可对引脚置1
GPIOx->BSRR = (((uint32_t)0x01) << pinpos);
}
}
}
}
// 把前面处理后的暂存值写入到CRL寄存器之中
GPIOx->CRL = tmpreg;
}
/*-------------GPIO CRH 寄存器配置 CRH寄存器控制着高8位IO- -----------*/
// 配置端口高8位,即Pin8~Pin15
if (GPIO_InitStruct->GPIO_Pin > 0x00FF)
{
// // 先备份CRH寄存器的值
tmpreg = GPIOx->CRH;
// 循环,从Pin8开始配对,找出具体的Pin
for (pinpos = 0x00; pinpos < 0x08; pinpos++)
{
pos = (((uint32_t)0x01) << (pinpos + 0x08));
// pos与输入参数GPIO_PIN作位与运算
currentpin = ((GPIO_InitStruct->GPIO_Pin) & pos);
//若currentpin=pos,则找到使用的引脚
if (currentpin == pos)
{
//pinpos的值左移两位(乘以4),因为寄存器中4个寄存器位配置一个引脚
pos = pinpos << 2;
//把控制这个引脚的4个寄存器位清零,其它寄存器位不变
pinmask = ((uint32_t)0x0F) << pos;
tmpreg &= ~pinmask;
// 向寄存器写入将要配置的引脚的模式
tmpreg |= (currentmode << pos);
// 判断是否为下拉输入模式
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD)
{
// 下拉输入模式,引脚默认置0,对BRR寄存器写1可对引脚置0
GPIOx->BRR = (((uint32_t)0x01) << (pinpos + 0x08));
}
// 判断是否为上拉输入模式
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)
{
// 上拉输入模式,引脚默认值为1,对BSRR寄存器写1可对引脚置1
GPIOx->BSRR = (((uint32_t)0x01) << (pinpos + 0x08));
}
}
}
// 把前面处理后的暂存值写入到CRH寄存器之中
GPIOx->CRH = tmpreg;
}
}
GPIO_InitTypeDef* GPIO_InitStruct结构体地址变量配置
接下来就要在主函数中写入该GPIO_InitTypeDef* GPIO_InitStruct结构体地址变量,注意该变量要放在尽可能上面,然后打开时钟,进行代码的运行。具体步骤如下述代码所示:
#elif 1
GPIO_InitTypeDef GPIO_InitStructure;
//打开 GPIOB 端口的时钟
RCC->APB2ENR |= ((1)<<3);
GPIO_SetBits(GPIOB,GPIO_Pin_0);
GPIO_ResetBits(GPIOB,GPIO_Pin_0);
紧接着我们来配置该结构体中的具体变量,
包括(Pin,Mode,Speed)然后再进行GPIO_Init函数的调用完成全部代码:
在这里,由于之前已经配置了该结构体内容具体的模式代码,所以我们就可以直接输入我们所需要的代码定义就好。然后要注意的是,我们应该在第二个变量前加取地址符&,把地址引入变量中。我们观察这几行代码,如果我们用寄存器来读它们的话,很难读懂。但是我们如果用固件库编程的话,就很好修改且容易读懂。
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
小结
到这里我们就完成了所有的代码,具体所有的代码我以后会尽快放到下一个专栏,大家要记得订阅哦!本课我们学习如何更加系统的优化程序以及运用STM32官方给出的标准库,包括如何定义初始化结构体以及枚举定义的运用,还有官方给的GPIO_Init函数和如何配置结构体变量。下节课我们继续学习自己写库——构建库函数雏形的内容。
创作不易,点个三连吧!!!