第1课【寄存器开发到库开发】寄存器 库 位操作 封装 分层 GPIO

2023-05-16

目录

  • 基本知识框架
  • 课堂笔记
    • 什么是寄存器开发
    • 什么是库开发
    • 寄存器开发和库开发的关联
      • 寄存器开发的基本流程
        • 寄存器开发的优缺点
      • 库开发的基本流程
        • 库开发的优缺点
      • 结论
    • 如何从寄存器开发实现库开发(GPIO口为例,通过操作其寄存器点亮LED灯)
      • 寄存器基本位操作
        • 置位操作
        • 复位操作
        • 反转位操作
      • 寄存器实现方式
      • 封装 外设存储器地址
      • 封装 GPIO基地址 / 外设寄存器结构体 / GPIO输入输出模式枚举变量
      • 封装GPIO操作函数
      • 基本库实现方式
  • 基本知识框架Xmind文件下载

基本知识框架

在这里插入图片描述

课堂笔记

什么是寄存器开发

使用STM32,由于其内部的系统结构无需经常变化,所以大部分时候的STM32的开发,是和外设打交道。而操作外设需要通过操作寄存器去间接实现,所以STM32开发也可以看作是寄存器开发

什么是库开发

首先要知道什么是就是一系列具有通用性函数和二进制的集合

库分为静态库动态库

静态库动态库的文件形式

  • Windows:静态库 xx.lib || 动态库 xx.dll
  • LInux: 静态库 xx.a. || 动态库 xx.so

静态库动态库使用方法

  • 静态库:使用时,对应静态链接操作,在生成可执行程序的链接环节,会被链接到程序中去,最终生成可执行文件
  • 动态库:使用时,对应动态链接操作,相对于静态链接,动态链接的文件和可执行程序是相对独立的,但可执行程序中有动态库的位置信息和其中函数的接口信息,据此找到动态库并实现函数调用

STM32开发中的标准库是ST公司编写的,属于静态库,里面包含了外设相关的所有函数和相关结构体定义等等,格式标准规范,实现方式优雅

研究STM32的标准库,对于学习STM32是很有帮助的

寄存器开发和库开发的关联

寄存器开发的基本流程

在这里插入图片描述

  • 首先要根据要开发的功能,查阅外设的数据手册,找到相应的寄存器
  • 根据寄存器的说明。逐位进行寄存器的读写配置
  • 后期维护或者调试,也需要根据外设的数据手册,进行纠错

寄存器开发的优缺点

优点:
程序运行效率高,需要那个寄存器就配置那个寄存器,冗余代码量少
缺点:
如果遇到下列情况,使用寄存器开发的难度会大大提高

  1. 遇到大型项目或者需配置寄存器数量较多时,需要频繁翻阅数据手册,影响开发效率。过于依赖数据手册
  2. 程序如果需要移植,那么底层很多的寄存器操作需要重写。程序可移植性差
  3. 后期维护后者调试的时候,如果没有数据手册,很多寄存器的读写很不好理解。程序可读性差

库开发的基本流程

在这里插入图片描述

  • 根据要开发的功能,查看库接口文档,找到所需的函数,结构体或者宏定义
  • 调用相应的函数接口,声明结构体或者使用宏定义的方式去实现功能
  • 后期维护和调试,由于重新封装了直观的函数,结构体和宏定义名称,可以不用过多参考库接口文档

库开发的优缺点

优点:
相对于寄存器开发,在遇到如上相似的情况时,库开发就更具优势

  1. 遇到大型项目或者需配置寄存器数量较多时,可以直观的操作想要的寄存器。开发效率较高
  2. 程序如果需要移植,底层只需要进行小的改动即可在新平台上使用。程序的可移植性较高
  3. 后期维护或者调试,不过于依赖技术手册。程序可读性高

缺点:
由于库对底层的寄存器,某些结构体等重新进行了宏定义的类型定义,所以抽象结构上多了一层库函数层,实际程序运行时也需要处理更多的代码

结论

随着技术的进步和社会需求的提高,STM32的需要调用的外设资源会越来越多,且要处理的项目也会趋于大型化和复杂化,这给项目的前期搭建和后期维护带来很大的挑战——如何高效的调用资源实现需求,如何高效的修改程序Debug

库开发使用了封装的概念很好的解决了这些问题。封装就是把一个抽象的事物的属性及属性相关的操作函数打包在一起,外界的模块只能通过这个抽象事物对外提供的函数接口,对事物的属性进行访问。封装使得上层使用者只需要调用接口,无需过于关心寄存器操作是怎么实现的,从而更高效的解决需求

如何从寄存器开发实现库开发(GPIO口为例,通过操作其寄存器点亮LED灯)

使用STM32CubeIDE来建立项目,通过项目的实践可以更好的理解库函数是怎么工作的
一般来说简单项目包含以下文件:

  • main.c主资源文件
  • 库相关文件(*.h头文件+*.c资源文件 或 封装后的.lib库文件)

下面以GPIO寄存器的操作为例,从寄存器开发实现基础库开发

寄存器基本位操作

要进行寄存器开发,首先要了解基本的位操作

置位操作

使用按位操作符实现置位

int GPIO = 0x00000000;
GPIO |= 1<<4;

说明:1<<4后得到0x00001000,|=使得只有GPIO第4位置成了1,其他位不受影响

复位操作

使用按位与操作符实现复位

int GPIO = 0x11111111;
GPIO &= ~(1<<4);

说明:1<<4后得到0x00001000,~(1<<4)后得到0x11110111,&=使得只有GPIO第4位置成了0,其他位不受影响

反转位操作

使用按位异或操作符实现位反转

int GPIO = 0x00000000;
GPIO ^= 1<<4;

说明:1<<4后得到0x00001000,^=使得只有GPIO第4位置成了1(如果第4位是1,则会被置成0),其他位不受影响

寄存器实现方式

main.c的代码主体部分实现

#include <stdio.h>

int main(void)
{
	// 开启APB2总线上端口GPIOB的时钟
	*(unsigned int*)(0x40021018) |=  (0x01 << 3);
	// 复位GPIOB的CRL寄存器
	*(unsigned int*)(0x40010c00) &= ~(0x0F << 4);
	// 置位GPIOB的CRL寄存器,配置端口为通用推挽输出,速度为10M
	*(unsigned int*)(0x40010c00) &= ~(0x01 << 4);
	// 职位GPIOB的ODR寄存器,配置端口PB0输出低电平
	*(unsigned int*)(0x40010c0c) |=  (0 << 0);
	
	while(1);
}

封装 外设存储器地址

主要目的:

  • 为外设存储器基地址重新定义宏名,方便访问外设

头文件stm32f10x.h代码主体部分实现

// 定义寄存器结构体
// volatile修饰的变量不会被编译器优化,有些编译器会为未赋初值的变量赋值,这样出现意料外的情况
#define __IO volatile
// 重新定义变量类型
typedef unsigned int   uint32_t;
typedef unsigned short uint16_t;
// 宏定义外设存储器及地址
#define PERIPH_BASE     ((unsigned int)0x40000000)
// 宏定义外设总线基地址
#define APBP1ERIPH_BASE (PERIPH_BASE + 0x00000)
#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)
#define AHBPERIPH_BASE  (PERIPH_BASE + 0x20000) 

封装 GPIO基地址 / 外设寄存器结构体 / GPIO输入输出模式枚举变量

主要目的:

  • 为GPIO基地址重新定义宏名,方便访问GPIO
  • 定义GPIO寄存器结构体,方便访问寄存器
  • 使用枚举变量定义GPIO输入输出模式 ,输出速率

头文件stm32f10x_gpio.h代码主体部分实现

// GPIO输出速率枚举
typedef enum
{
	GPIO_Speed_10MHz = 1,
	GPIO_Speed_2MHz,
	GPIO_Speed_50MHz
}GPIOSpeed_TypeDef;
// GPIO输入输出模式枚举
typedef enum
{
	GPIO_Mode_ANALOG_IN   = 0x00,
	GPIO_Mode_FLOATING_IN = 0x04,
	GPIO_Mode_INU         = 0x28,
	GPIO_Mode_IND         = 0x48,

	GPIO_Mode_OUT_OD = 0x14,
	GPIO_Mode_OUT_PP = 0x10,
	GPIO_Mode_AF_OD  = 0x1c,
	GPIO_Mode_AF_PP  = 0x18
}GPIOMode_TypeDef;
// 定义GPIO寄存器结构体
typedef struct
{
	__IO uint32_t CRL;
	__IO uint32_t CRH;
	__IO uint32_t IDR;
	__IO uint32_t ODR;
	__IO uint32_t BSSR;
	__IO uint32_t BRR;
	__IO uint32_t LCKR;
}GPIO_TypeDef;
// 宏定义总线上GPIO口的基地址
#define GPIOA_BASE      (APB2PERIPH_BASE + 0x0800)
#define GPIOB_BASE      (APB2PERIPH_BASE + 0x0c00)
#define GPIOC_BASE      (APB2PERIPH_BASE + 0x1000)
#define GPIOD_BASE      (APB2PERIPH_BASE + 0x1400)
#define GPIOE_BASE      (APB2PERIPH_BASE + 0x1800)
#define GPIOF_BASE      (APB2PERIPH_BASE + 0x1c00)
#define GPIOG_BASE      (APB2PERIPH_BASE + 0x2000) 
// 使用寄存器结构体,重定义GPIO口
#define GPIOA           ((GPIO_TypeDef*)GPIOA_BASE)
#define GPIOB           ((GPIO_TypeDef*)GPIOB_BASE)
#define GPIOC           ((GPIO_TypeDef*)GPIOC_BASE)
#define GPIOD           ((GPIO_TypeDef*)GPIOD_BASE)
#define GPIOE           ((GPIO_TypeDef*)GPIOE_BASE)
#define GPIOF           ((GPIO_TypeDef*)GPIOF_BASE)
#define GPIOG           ((GPIO_TypeDef*)GPIOG_BASE)
// 宏定义GPIO口对应的时钟基地址及时钟地址
#define RCC_BASE       *(AHBPERIPH_BASE + 0x1000)
#define RCC_APB2ENR    *(unsigned  int*)(RCC_BASE + 0x18)

封装GPIO操作函数

主要目的:

  • 定义GPIO操作函数,可以通过函数控制GPIO口
  • 定义GPIO初始化函数,可以通过函数初始化GPIO口

源文件stm32f10x_gpio.c代码主体部分实现

#include <stm32f10x_gpio.h>

// GPIO置位函数,能将指定GPIO的引脚置位
void GPIO_SetBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_PIN)
{
	GPIOx->BSRR = GPIO_PIN;
}

// GPIO复位函数,能将指定GPIO的引脚复位
void GPIO_ResetBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_PIN)
{
	GPIOx->BRR = GPIO_PIN;
}

// GPIO初始化函数,通过初始化结构体可以对指定的GPIO口的指定引脚进行初始化
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStructure)
{
	uint32_t currentmode  = 0x00;
	uint32_t tmpreg       = 0x00;
	uint32_t currentgroup = 0x00; 
	uint32_t currentpin   = 0x00;
	
	// 设定GPIO口的Mode
	currentmode = ((uint32_t)GPIO_InitStructure->GPIO_Mode) & ((uint32_t)0x0F);
	// 判断GPIO口是否是输出模式,是的话需要设定GPIO口的Speed
	if (0x00 != (((uint32_t)GPIO_InitStructure->GPIO_Mode) & ((uint32_t)0x10)))
	{
		currentmode |= (uint32_t)GPIO_InitStructure->GPIO_Speed;
	}
	// 判断需要初始化的引脚是否是GPIO口的低8位引脚
	if (0x00 != (((uint32_t)GPIO_InitStructure->GPIO_PIN) & (uint32_t)0x00FF))
	{
		// 备份CRL寄存器的值到tmpreg
		tmpreg = GPIOx->CRL;
		// 将寄存器分成8个group,每4位1个group,对应1位引脚
		for (currentgroup=0x00; currentgroup<0x08; currentgroup++)
		{
			// 通过当前的group得到当前的引脚
			currentpin = (uint32_t)0x01 << currentgroup;
			// 判断当前引脚是否需要初始化
			if (0x00 != ((uint32_t)GPIO_InitStructure->GPIO_PIN & (uint32_t)currentpin))
			{
				// 将tmpreg当前引脚对应group的4位寄存器复位,<< 2的操作相当于x4
				tmpreg &= ~((uint32_t)(0x0F <<(currentgroup << 2)));
				// 将tmpreg当前引脚对应group的4位寄存器置成对应的Mode和Speed
				tmpreg |= (currentmode << (currentgroup << 2));
				// 判断当前的Mode是否是上拉输入模式或者下拉输入模式,是的话需要对对应的BSSR和BRR寄存器进行操作
				if (GPIO_Mode_INU == GPIO_InitStructure->GPIO_Mode)
					GPIOx->BSRR = (uint32_t)0x01 << currentgroup;
				else if (GPIO_Mode_IND == GPIO_InitStructure->GPIO_Mode)
					GPIOx->BRR  = (uint32_t)0x01 << currentgroup;
			}
		}
		// 将tmpreg的值赋给CRL寄存器
		GPIOx->CRL = tmpreg;
	}
	
	// 判断需要初始化的引脚是否是GPIO口的高8位引脚
	if ((uint32_t)GPIO_InitStructure->GPIO_PIN > 0x00FF)
	{
		// 备份CRL寄存器的值到tmpreg
		tmpreg = GPIOx->CRH;
		// 将寄存器分成8个group,每4位1个group,对应1位引脚
		for (currentgroup=0x08; currentgroup<0x10; currentgroup++)
		{
			// 通过当前的group得到当前的引脚
			currentpin = (uint32_t)0x01 << currentgroup;
			// 判断当前引脚是否需要初始化
			if (0x00 != ((uint32_t)GPIO_InitStructure->GPIO_PIN & (uint32_t)currentpin))
			{
				// 将tmpreg当前引脚对应group的4位寄存器复位,<< 2的操作相当于x4
				tmpreg &= ~(uint32_t)(0x0F <<(currentgroup << 2));
				// 将tmpreg当前引脚对应group的4位寄存器置成对应的Mode和Speed
				tmpreg |= (currentmode << (currentgroup << 2));
				// 判断当前的Mode是否是上拉输入模式或者下拉输入模式,是的话需要对对应的BSSR和BRR寄存器进行操作
				if (GPIO_Mode_INU == GPIO_InitStructure->GPIO_Mode)
					GPIOx->BSRR = (uint32_t)0x01 << currentgroup;
				else if (GPIO_Mode_IND == GPIO_InitStructure->GPIO_Mode)
					GPIOx->BRR  = (uint32_t)0x01 << currentgroup;
			}
		}
		// 将tmpreg的值赋给CRL寄存器
		GPIOx->CRH = tmpreg;
	}
}

基本库实现方式

在经过封装后,就完成了最基础的库的构建,通过这个很基础的库可以更快更清晰地实现开发者的需求,省去很多开发或者调试中很多不必要的工作量

除了GPIO口,STM32上还有许多其他外设,但无需开发者再去为其单独开发库,ST官方已经为开发者提供了官方版本的外设库,在进行开发时只要根据需求直接使用即可

对于开发者,在一定阶段后可以对ST官方库进行研究。官方库实现的方式相当的优雅和严谨,研究官方库的具体实现,可以对STM32开发有很大的帮助,同时也可以提升C语言的能力

main.c代码主体部分实现

#include <stm32f10x.h>
#include <stm32f10x_gpio.h>

void SystemInit(void);

int main(void)
{
	// 定义初始化结构体GPIOB_ForLED
	GPIO_InitTypeDef GPIOB_ForLED;
	// 使能RCC时钟
	RCC_APB2ENR |=  (0x01 << 3);
	// 设定结构体需要初始化的引脚
	GPIOB_ForLED.GPIO_PIN = GPIO_PIN_0;
	// 设定结构体需要初始化引脚的输出速度
	GPIOB_ForLED.GPIO_Speed = GPIO_Speed_50MHz;
	// 设定结构体需要初始化引脚的输出模式:推挽模式
	GPIOB_ForLED.GPIO_Mode = GPIO_Mode_Out_PP;
	// 通过结构体初始化函数初始化GPIO口相应引脚
	GPIO_Init(GPIOB, &GPIOB_ForLED);
	// 通过复位函数将指定引脚置低电平
	GPIO_ResetBit(GPIOB, GPIO_PIN_0);
	
	while(1);
}

// 用于通过编译
void SystemInit(void)
{
		
}

基本知识框架Xmind文件下载

链接:资源下载

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

第1课【寄存器开发到库开发】寄存器 库 位操作 封装 分层 GPIO 的相关文章

  • Ubuntu 18.04在 VMware中启动时一直停留在Starting Update UTMP about System Runlevel Changes

    环境 Ubuntu 18 04 安装在VMware中 问题 启动时一直停留在Starting Update UTMP about System Runlevel Changes 解决思路 由于本人的虚拟机磁盘空间所剩无几 xff0c 怀疑是
  • linux零碎知识点

    源码包安装的 Apache 默认伪用户 xff1a daemonTCP IP是一个工业标准而非国际标准TCP IP可以用于同一主机上不同进程之间的通信Linux 由 Kernel Shelll 应用程序 组成网络协议三要素 xff1a 语义
  • 算法小知识

    一个数如果恰好等于它的因子之和 xff0c 这个数就称为 完数 例如6 61 1 xff0b 2 xff0b 3 xff0c 再如8的因子和是7 xff08 即1 43 2 43 4 xff09 xff0c 8不是完数回文数是指正序 xff
  • 启动 tomcat ,一直停在com.alibaba.druid.pool.DruidDataSource:init

    启动 tomcat 一直停在com alibaba druid pool DruidDataSource init clean一下就可以了
  • Win10 触摸屏 快捷键操作

    Win10 触摸板 快捷键操作 在 Windows 10 笔记本电脑的触摸板上试用这些手势 xff1a 快捷键 选择项目 xff1a 点击触摸板右键单击效果 xff1a 两根手指点击一次应用滚动 xff1a 将两个手指放在触摸板上 xff0
  • JCE cannot authenticate the provider BC

    JCE cannot authenticate the provider BC 解决办法 xff1a 修改 JAVA HOME jre lib security java security 文件 添加如下内容 security provid
  • Docker命令之:load命令

    作用 xff1a Load span class hljs operator an span image span class hljs built in from span span class hljs operator a span
  • Docker命令之: tag命令

    作用 xff1a span class hljs operator span class hljs keyword Create span a tag TARGET IMAGE that refers span class hljs key
  • installshield中增加BDE组件

    在C Program Files Common Files Borland Shared BDE下面有一个bdeinst cab xff0c 把它解压开后有一个bdeinst dll xff0c 把他打包到你的安装程序 xff0c 然后选择
  • intellij idea 合并分支到主分支,主分支代码同步到某一分支

    将gith或者Gitee上的项目clone到本地 git span class hljs keyword clone span https span class hljs comment xxxx span 打开intellij idea
  • springboot项目 intellij idea 找不到或者无法加载主类

    试了很多种网上的方法 xff0c 例如清除缓存 xff0c 指定module path 重启等都没有起作用 最后发现是 project structure project settings project project compiler
  • Vivado综合running时间太长

    cancel后查看log日志 xff0c 出现警告warnning PID not specified xff0c 解决办法 xff1a 新建一个工程 xff0c 导入原工程源文件 xff0c 重新进行综合 xff0c 就可以了
  • 使用putty和ssh登录时进不去

    我的是win10系统 初步理解ssh是一个协议 xff0c putty是一个使用这个协议连接本地电脑和远程服务器的开源软件 我知道连接ssh有两种方式 xff1a 第一种 xff1a 电脑左下角搜索界面输入 terminal 打开命令行cm
  • 联想win10安全模式进入以及退出

    进入安全模式的办法 xff1a 1 按住shift再点击重启 疑难解答 可进入带命令提示符的安全模式 进去后仅命令行一个窗口 xff0c 关闭后会出现黑屏 xff0c 仅有一个光标 2 桌面运行win 43 r 勾选安全引导 可直接进入安全
  • Linux网络编程之tcpdump抓包分析TCP三次握手过程

    使用TCP协议进行网络通讯时 xff0c 通信的两端首先需要建立起一条连接链路 xff0c 当然这并不表示使用UDP通信不需要 连接链路 xff0c 这里说的连接链路指的是通信协议范畴的东东 xff0c 并不是物理介质或者电磁波信号 xff
  • inceptionv3迁移学习 训练+测试

    迁移学习在实际应用中的意义非常大 xff0c 它可以将之前已学过的知识 xff08 模型参数 xff09 迁移到一项新的任务上 xff0c 使学习效率大大的提高 我们知道 xff0c 要训练一个复杂的深度学习模型 xff0c 成本是十分巨大
  • 60分钟闪击速成PyTorch(Deep Learning with PyTorch: A 60 Minute Blitz)学习笔记

    诸神缄默不语 个人CSDN博文目录 本笔记是我学习 Deep Learning with PyTorch A 60 Minute Blitz 这一PyTorch官方教程后的学习笔记 该教程在官网上更新过 xff0c 因此未来还可能继续更新
  • VSCode上的Git使用手记(持续更新ing...)

    诸神缄默不语 个人CSDN博文目录 本笔记是我想要学习如何将本地文件发布到GitHub上时开始看廖雪峰的Git教程 xff0c 然后打开了VSCode xff0c 发现VSCode上面集成的Git辅助使用功能真的很好用 基本上到了不用看教程
  • 诸神缄默不语-个人CSDN博文目录

    突然发现我也是一个有好多篇文章的博主了 xff0c 因此设置一个自己的目录 xff0c 方便查找 感觉列在一篇文章里然后直接用Ctrl 43 F都比CSDN内置的目录和分类方便 优先按学科进行分类 xff0c 此外列出面经和其他两个分类 文
  • spring 拦截器的实现

    spring 拦截器是spring AOP体系下的一个重要的子功能 它类似于web中的filter xff0c 但又比filter灵活 xff0c 强大得多 许多AOP框架 xff0c 包括Spring xff0c 都是以拦截器做通知模型

随机推荐