IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> [STM32学习笔记(四)]初识库函数(下) -> 正文阅读

[嵌入式][STM32学习笔记(四)]初识库函数(下)

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 的库函数的实现。因为外设的库函数是很类似的,库外设都包含初始化结构体,以及特定的宏或枚举标识符,这些封装被库函数这些转化成相应的值,写入到寄存器之中,函数内部的具体实现是十分枯燥和机械的工作。
如果您有兴趣,在您掌握了如何使用外设的库函数之后,可以查看一下它的源码实现

  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2021-07-17 12:06:30  更:2021-07-17 12:06:32 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 19:22:25-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码