初始库函数(下)
3.2 实验:构建库函数雏形(续)
3.2.5 定义初始化结构体 GPIO_InitTypeDef
定义位操作函数后,控制 GPIO 输出电平的代码得到了简化,但在控制 GPIO 输出电平前还需要初始化 GPIO 引脚的各种模式,这部分代码涉及的寄存器有很多。
1、先根据 GPIO 初始化时涉及到的初始化参数以结构体的形式封装起来
2、声明一个名为 GPIO_InitTypeDef 的结构体类型
typedef struct
{
uint16_t GPIO_Pin; /*!< 选择要配置的 GPIO 引脚 */
uint16_t GPIO_Speed; /*!< 选择 GPIO 引脚的速率 */
uint16_t GPIO_Mode; /*!< 选择 GPIO 引脚的工作模式 */
} GPIO_InitTypeDef;
这个结构体中包含了初始化 GPIO 所需要的信息,包括引脚号、工作模式、输出速率。
设计这个结构体的思路是:
1、初始化 GPIO前,先定义一个这样的结构体变量
2、根据需要配置 GPIO 的模式,对这个结构体的各个成员进行赋值
3、然后把这个变量作为“GPIO 初始化函数”的输入参数
4、该函数能根据这个变量值中的内容去配置寄存器,从而实现 GPIO 的初始化。
3.2.6 定义引脚模式的枚举类型
使用 C 语言中的枚举定义功能,根据手册把每个成员的所有取值都定义好。
GPIO_Speed 和 GPIO_Mode 这两个成员对应的寄存器是 CRL 和 CRH 这两个端口配置寄存器,具体见下图
代码如下:
/**
* 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引脚工作模式真值表分析图
有了这些枚举定义, GPIO_InitTypeDef 结构体就可以使用枚举类型来限定输入参数
/**
* GPIO 初始化结构体类型定义
*/
typedef struct
{
uint16_t GPIO_Pin; /*!< 选择要配置的 GPIO 引脚
可输入 GPIO_Pin_ 定义的宏 */
GPIOSpeed_TypeDef GPIO_Speed; /*!< 选择 GPIO 引脚的速率
可输入 GPIOSpeed_TypeDef 定义的枚举值
*/
GPIOMode_TypeDef GPIO_Mode; /*!< 选择 GPIO 引脚的工作模式
可输入 GPIOMode_TypeDef 定义的枚举值
*/
} GPIO_InitTypeDef;
利用这些枚举定义,给 GPIO_InitTypeDef 结构体类型赋值配置就变得非常直观,代码如下:
GPIO_InitTypeDef GPIO_InitStructure;
/* GPIO 端口初始化 */
/*选择要控制的 GPIO 引脚*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
/*设置引脚模式为输出模式*/
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
/*设置引脚的输出类型为推挽输出*/
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
3.2.7 定义GPIO初始化函数
接着前面的思路,对初始化结构体赋值后,把它输入到 GPIO 初始化函数,由它来实现寄存器配置。
/**
*函数功能:初始化引脚模式
*参数说明: GPIOx,该参数为 GPIO_TypeDef 类型的指针,指向 GPIO 端口的地址
* GPIO_InitTypeDef: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;
}
}
这个函数有 GPIOx 和 GPIO_InitStruct 两个输入参数,分别是 GPIO 外设指针和 GPIO初始化结构体指针。分别用来指定要初始化的 GPIO 端口及引脚的工作模式。
3.2.8 使用固件库点亮LED
// 使用固件库点亮 LED
int main(void)
{
// 定义一个 GPIO_InitTypeDef 类型的结构体
GPIO_InitTypeDef GPIO_InitStructure;
// 开启 GPIO 端口时钟
RCC_APB2ENR |= (1<<3);
// 选择要控制的 GPIO 引脚
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
// 设置引脚模式为通用推挽输出
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
// 设置引脚速率为 50MHz
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
// 调用库函数,初始化 GPIO 引脚
GPIO_Init(GPIOB, &GPIO_InitStructure);
// 使引脚输出低电平,点亮 LED1
GPIO_ResetBits(GPIOB,GPIO_Pin_0);
while (1)
{
// 使引脚输出低电平,点亮 LED
GPIO_ResetBits(GPIOB,GPIO_Pin_0);
/*延时一段时间*/
Delay(0xFFFF);
/*使引脚输出高电平,关闭 LED1*/
GPIO_SetBits(GPIOB,GPIO_Pin_0);
/*延时一段时间*/
Delay(0xFFFF);
}
}
使用函数来控制 LED 灯与之前直接控制寄存器已经有了很大的区别:
1、main 函数中先定义了一个 GPIO 初始化结构体变量GPIO_InitStructure
2、然后对该变量的各个成员按点亮 LED 灯所需要的 GPIO 配置模式进行赋值
3、赋值后,调用 GPIO_Init 函数,让它根据结构体成员值对 GPIO 寄存器写入控制参数,完成 GPIO 引脚初始化。
4、控制电平时,直接使用 GPIO_SetBits 和 GPIO_Resetbits 函数控制输出。
5、如若对其它引脚进行不同模式的初始化,只要修改 GPIO 初始化结构体 GPIO_InitStructure 的成员值,把新的参数值输入到 GPIO_Init 函数再调用即可。
代码中 Delay 函数,主要功能是延时,属于软件延迟,不准确。
要准确的延迟,需要使用STM32的定时器来实现。
3.3 总结
本章的学习,是照搬了ST标准库,目的是满足求知欲,学习库函数的编程方式和思想。
与直接配置寄存器相比,执行效率上会有额外的消耗,例如:
初始化变量赋值的过程、库函数被调用的时候耗费的时间等等。
它的宏、枚举等解释操作都是编译过程完成的,这部分不消耗时间。
为什么学习函数库?
学习函数库的目的是:
1、我们可以快速上手STM32控制器;
2、配置外设状态时,不需要纠结向寄存器写入什么数值;
3、交流方便,便于差错
在以后开发的工程中,一般不会去分析 ST 的库函数的实现。因为外设的库函数是很类似的,库外设都包含初始化结构体,以及特定的宏或枚举标识符,这些封装被库函数这些转化成相应的值,写入到寄存器之中,函数内部的具体实现是十分枯燥和机械的工作。
如果您有兴趣,在您掌握了如何使用外设的库函数之后,可以查看一下它的源码实现