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

基本知识框架

在这里插入图片描述

课堂笔记

什么是寄存器开发

使用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文件下载

链接:资源下载

  • 4
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
以下是 GD32F3 的 GPIO 寄存器操作宏定义示例: ```c #define GPIO_BASE(port) (GPIOA_BASE + (port - GPIOA) * 0x0400U) // GPIO 基地址宏定义 #define GPIO_PIN(port, pin) (1U << (pin)) // GPIO 引脚宏定义 #define GPIO_MODER(port) (*(volatile uint32_t *)(GPIO_BASE(port) + 0x00U)) // GPIO 模式寄存器宏定义 #define GPIO_OTYPER(port) (*(volatile uint32_t *)(GPIO_BASE(port) + 0x04U)) // GPIO 输出类型寄存器宏定义 #define GPIO_OSPEEDR(port) (*(volatile uint32_t *)(GPIO_BASE(port) + 0x08U)) // GPIO 输出速度寄存器宏定义 #define GPIO_PUPDR(port) (*(volatile uint32_t *)(GPIO_BASE(port) + 0x0CU)) // GPIO 上下拉寄存器宏定义 #define GPIO_IDR(port) (*(volatile uint32_t *)(GPIO_BASE(port) + 0x10U)) // GPIO 输入数据寄存器宏定义 #define GPIO_ODR(port) (*(volatile uint32_t *)(GPIO_BASE(port) + 0x14U)) // GPIO 输出数据寄存器宏定义 #define GPIO_BSRR(port) (*(volatile uint32_t *)(GPIO_BASE(port) + 0x18U)) // GPIO 置位/复位寄存器宏定义 #define GPIO_LCKR(port) (*(volatile uint32_t *)(GPIO_BASE(port) + 0x1CU)) // GPIO 锁定寄存器宏定义 #define GPIO_AFRL(port) (*(volatile uint32_t *)(GPIO_BASE(port) + 0x20U)) // GPIO 复用功能低位寄存器宏定义 #define GPIO_AFRH(port) (*(volatile uint32_t *)(GPIO_BASE(port) + 0x24U)) // GPIO 复用功能高位寄存器宏定义 ``` 使用这些宏定义,可以方便地对 GD32F3 的 GPIO 寄存器进行操作,例如: ```c // 设置 GPIOA 的第 1 个引脚为输出模式,推挽输出,最大输出速度为 50MHz GPIO_MODER(GPIOA) |= (0x01U << (1U * 2U)); GPIO_OTYPER(GPIOA) &= ~(0x01U << 1U); GPIO_OSPEEDR(GPIOA) |= (0x03U << (1U * 2U)); // 设置 GPIOA 的第 2 个引脚为输入模式,上拉输入模式 GPIO_MODER(GPIOA) &= ~(0x03U << (2U * 2U)); GPIO_PUPDR(GPIOA) &= ~(0x03U << (2U * 2U)); GPIO_PUPDR(GPIOA) |= (0x01U << (2U * 2U)); // 读取 GPIOA 的第 2 个引脚的输入数据 uint32_t input_data = GPIO_IDR(GPIOA) & GPIO_PIN(GPIOA, 2U); ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值