STM32固件库

文章详细介绍了STM32固件库如何封装,通过逐步迭代从直接操作寄存器到使用结构体和宏定义,提高代码可读性和可移植性。重点在于理解GPIO的初始化过程,以及如何通过结构体和枚举类型进行配置。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

STM32固件库封装

配置GPIOx的固件库,使用起来很方便。

主要路线是学会自己封装固件库(基础的寄存器映射到固件库的封装完成)

例如:点灯,只需要改变对于的参数即可。

现象:

image-20230407205702710

配置函数:

image-20230407205116594

GPIOx固件库函数

写到后面涉及到了一个库函数的可移植性问题

只要宏定义封装的好,一切移植都轻轻松松,一步即可✨✨✨

方法:

宏定义一下需要点灯的过程

image-20230407213156901

然后主函数里边的那些涉及到GPIO的参数都使用宏定义的语句,这样想要切换随时都可以一步到位.

	GPIO_InitTypeDef  GPIO_InitStructure;
	//打开GPIOB的时钟
	RCC->APB2ENR  |=((1)<<3);
	
	GPIO_InitStructure.GPIO_Pin = LED_G_GPIO_PIN;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHZ;
	GPIO_Init(LED_G_GPIO_PORT,&GPIO_InitStructure);
	while(1)
	{
		GPIO_SetBits(LED_G_GPIO_PORT,LED_G_GPIO_PIN);//置位端口
		Delay(0xFFFF);
		GPIO_ResetBits(LED_G_GPIO_PORT,LED_G_GPIO_PIN);//清除端口清零
		Delay(0xFFFF);
	}
//该代码图一的区别就是参数换成了宏定义的语句.

固件库的封装过程


#if 0
	
#include <rge52.h>

sbit LED = P0^0

void main(void)
{
	P0=FE;		//总线操作
	
	LED=0;		//位操作
}


#endif



#include "stm32f10x.h" 
#include "stm32f10x_gpio.h"

#define LED_G_GPIO_PORT			GPIOB
#define LED_G_GPIO_CLK_ENABLE	RCC->APB2ENR  |=((1)<<3)
#define LED_G_GPIO_PIN			GPIO_Pin_1


void Delay(uint32_t count)
{
	for( ;count!=0;count-- );
}

int main(void)
{	
#if 0
	//此处是打开 GPIOB 端口的时钟  故使能RCC,运行单片机的心脏
	*(unsigned int *)0x40021018 |=((1)<<3);//端口时钟基本不变,都是用来使能GPIOB的
	
	//配置IO口为输出,这里用到的是端口配置低寄存器,用来使IO口输出低电平的,顾名思义CRL(control register low)跟CRH相反
	*(unsigned int *)0x40010C00 |=((1)<<(4*5));
	
	//控制 ODR 寄存器(open data register)控制16个IO的其中之一
	//ODR是用来配置哪个GPIO输出数据的,同样的IO的话也不用改了。
	*(unsigned int *)0x40010C0C &= ~(1<<1);
#elif 0
	
	RCC_APB2ENR |=((1)<<3);
	GPIOB_CRL |=((1)<<(4*5));
	GPIOB_BSRR	|=((1)<<5);
	GPIOB_ODR &= ~(1<<1);	
	
#elif 0
	
	//打开GPIOB的时钟
	RCC->APB2ENR  |=((1)<<3);
	
	//配置IO为输出
	GPIOB->CRL	&=	~((0x0f)<<(4*1));
	GPIOB->CRL	|=	((1)<<(4*1));

	//控制ODR寄存器
	GPIOB->ODR	&=~(1<<0);
	//GPIOB->ODR	|=	(1<<0);
#elif 0
	
	//打开GPIOB的时钟
	RCC->APB2ENR  |=((1)<<3);
	
	//配置IO为输出
	GPIOB->CRL	&=	~((0x0f)<<(4*0));
	GPIOB->CRL	|=	((1)<<(4*0));

	GPIO_SetBits(GPIOB,GPIO_Pin_0);//置位端口
	GPIO_ResetBits(GPIOB,GPIO_Pin_0);//清除端口清零
#elif 1
	
	GPIO_InitTypeDef  GPIO_InitStructure;
	//打开GPIOB的时钟
	RCC->APB2ENR  |=((1)<<3);
	
	GPIO_InitStructure.GPIO_Pin = LED_G_GPIO_PIN;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHZ;
	GPIO_Init(LED_G_GPIO_PORT,&GPIO_InitStructure);
	while(1)
	{
		GPIO_SetBits(LED_G_GPIO_PORT,LED_G_GPIO_PIN);//置位端口
		Delay(0xFFFF);
		GPIO_ResetBits(LED_G_GPIO_PORT,LED_G_GPIO_PIN);//清除端口清零
		Delay(0xFFFF);
	}
	

#endif	
}	


void SystemInit(void)
{
	//函数体为空,目的是为了骗过编译器不报错
}


1、写代码基本素养:
函数迭代:
#include "stm32f10x.h"
#include "stm32f10x_gpio.h"

int main()
{
#if 0

......			//程序不可执行区间

#elif 0
    
......			//程序不可执行区间
    
#elif  1
    
......			//程序可执行区间
    
#endif  
}    
定义文件:
#ifndef __STM32F10X_H
#define __STM32F10X_H

......			//写入需要调用的基本函数,可供外部调用

#endif
2、接下来开始讲解固件库封装过程
第一区域的代码:
#if 0
	//此处是打开 GPIOB 端口的时钟  故使能RCC,运行单片机的心脏
	*(unsigned int *)0x40021018 |=((1)<<3);//端口时钟基本不变,都是用来使能GPIOB的
	
	//配置IO口为输出,这里用到的是端口配置低寄存器,用来使IO口输出低电平的,顾名思义CRL(control register low)跟CRH相反
	*(unsigned int *)0x40010C00 |=((1)<<(4*5));
	
	//控制 ODR 寄存器(open data register)控制16个IO的其中之一
	//ODR是用来配置哪个GPIO输出数据的,同样的IO的话也不用改了。
	*(unsigned int *)0x40010C0C &= ~(1<<1);
#elif 0

这个是直接指针操纵内存,连基本的寄存器都不是

第二区域的代码:
#elif 0
	//打开GPIOB  端口的时钟
	RCC_APB2ENR |=((1)<<3);
	//配置IO口为输出
	GPIOB_CRL |=((1)<<(4*5));
	GPIOB_BSRR	|=((1)<<5);
	//控制ODR寄存器
	GPIOB_ODR &= ~(1<<1);	
	
#elif 0

我们直接实现了寄存器的映射(如下代码所示)

这些都是通过找到寄存器的绝对地址来实现的

#define	 GPIOB_CRL					*(unsigned int*)(GPIOB_BASE+0x00)
#define	 GPIOB_CRH					*(unsigned int*)(GPIOB_BASE+0x04)
#define	 GPIOB_IDR					*(unsigned int*)(GPIOB_BASE+0x08)
#define	 GPIOB_ODR					*(unsigned int*)(GPIOB_BASE+0x0C)	
#define	 GPIOB_BSRR					*(unsigned int*)(GPIOB_BASE+0x10)	
#define	 GPIOB_BRR					*(unsigned int*)(GPIOB_BASE+0x14)
#define	 GPIOB_LCKR					*(unsigned int*)(GPIOB_BASE+0x18)
于是有了第三个区域的代码:(重点理解)✨✨✨✨✨
#elif 0
	//打开GPIOB的时钟
	RCC->APB2ENR  |=((1)<<3);
	
	//配置IO为输出
	GPIOB->CRL	&=	~((0x0f)<<(4*1));
	GPIOB->CRL	|=	((1)<<(4*1));

	//控制ODR寄存器
	GPIOB->ODR	&=~(1<<0);
	//GPIOB->ODR	|=	(1<<0);
#elif 0

我们定义了寄存器的结构体,然后把每个外设的基地址通过强制类型转换(转换成相应外设的结构体指针这样我就可以通过外设的基地址来访问结构体里边的成员,从而实现操作寄存器。(通过读取上下图来理解结构体的指针的使用方式)✨✨✨✨✨

typedef	struct 
{
	uint32_t	CRL;
	uint32_t	CRH;
	uint32_t	IDR;
	uint32_t	ODR;
	uint32_t	BSRR;
	uint32_t	BRR;
	uint32_t	LCKR;
}GPIO_TypeDef;

typedef	struct 
{
	uint32_t	CR;
	uint32_t	CFGR;
	uint32_t	CIR;
	uint32_t	APB2RSTR;
	uint32_t	APB1RSTR;
	uint32_t	AHBENR;
	uint32_t	APB2ENR;
	uint32_t	APB1ENR;
	uint32_t	BDCR;
	uint32_t	CSR;

}RCC_TypeDef;


#define	RCC	((RCC_TypeDef*)RCC_BASE)
#define	GPIOB	((GPIO_TypeDef*)GPIOB_BASE)
到了这一步我们就讲究程序的可读性了,于是有了区域四:
#elif 0
	
	//打开GPIOB的时钟
	RCC->APB2ENR  |=((1)<<3);
	
	//配置IO为输出
	GPIOB->CRL	&=	~((0x0f)<<(4*0));
	GPIOB->CRL	|=	((1)<<(4*0));

	GPIO_SetBits(GPIOB,GPIO_Pin_0);//置位端口
	GPIO_ResetBits(GPIOB,GPIO_Pin_0);//清除端口清零
#elif 1

区域四的改变就是定义了函数:

如:也就是函数初始化

	GPIO_SetBits(GPIOB,GPIO_Pin_0);//置位端口
//以下代码用户一看就知道这个函数的作用是定义GPIOB的,而且还是控制Pin_0的

该函数是通过指针访问结构地址的方法,把我们需要的函数从外设基地址取出来

void GPIO_SetBits(GPIO_TypeDef *GPIOx,uint16_t GPIO_Pin)
{
	GPIOx->BSRR |= GPIO_Pin;
}
void GPIO_ResetBits(GPIO_TypeDef *GPIOx,uint16_t GPIO_Pin)
{
	GPIOx->BRR  |= GPIO_Pin;
}
区域五:升级了函数的初始化✨✨✨✨✨
#elif 1
	
	GPIO_InitTypeDef  GPIO_InitStructure;
	//打开GPIOB的时钟
	RCC->APB2ENR  |=((1)<<3);
	
	GPIO_InitStructure.GPIO_Pin = LED_G_GPIO_PIN;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHZ;
	GPIO_Init(LED_G_GPIO_PORT,&GPIO_InitStructure);
	while(1)
	{
		GPIO_SetBits(LED_G_GPIO_PORT,LED_G_GPIO_PIN);//置位端口
		Delay(0xFFFF);
		GPIO_ResetBits(LED_G_GPIO_PORT,LED_G_GPIO_PIN);//清除端口清零
		Delay(0xFFFF);
	}

#endif

我们定义了结构体,并把所有能涉及到,配置到的外设参数全部都通过枚举的方式列举出来:

typedef struct
{
	uint16_t GPIO_Pin;
	uint16_t GPIO_Speed;
	uint16_t GPIO_Mode;

}GPIO_InitTypeDef;

最后的一步调用是在函数体宏定义实现的:宏定义体现了函数的可移植性。

image-20230407232515880
typedef enum
{
	GPIO_Speed_10MHZ = 1,			//10MHZ		(01)b
	GPIO_Speed_2MHZ,				//2MHZ		(10)b
	GPIO_Speed_50MHZ				//50MHZ		(11)b
}GPIOSpeed_TypeDef;

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;

#define GPIO_Pin_0    ((uint16_t)0x0001)  /*!< 选择Pin0 */    //(00000000 00000001)b
#define GPIO_Pin_1    ((uint16_t)0x0002)  /*!< 选择Pin1 */    //(00000000 00000010)b
#define GPIO_Pin_2    ((uint16_t)0x0004)  /*!< 选择Pin2 */    //(00000000 00000100)b
#define GPIO_Pin_3    ((uint16_t)0x0008)  /*!< 选择Pin3 */    //(00000000 00001000)b
#define GPIO_Pin_4    ((uint16_t)0x0010)  /*!< 选择Pin4 */    //(00000000 00010000)b
#define GPIO_Pin_5    ((uint16_t)0x0020)  /*!< 选择Pin5 */    //(00000000 00100000)b
#define GPIO_Pin_6    ((uint16_t)0x0040)  /*!< 选择Pin6 */    //(00000000 01000000)b
#define GPIO_Pin_7    ((uint16_t)0x0080)  /*!< 选择Pin7 */    //(00000000 10000000)b

#define GPIO_Pin_8    ((uint16_t)0x0100)  /*!< 选择Pin8 */    //(00000001 00000000)b
#define GPIO_Pin_9    ((uint16_t)0x0200)  /*!< 选择Pin9 */    //(00000010 00000000)b
#define GPIO_Pin_10   ((uint16_t)0x0400)  /*!< 选择Pin10 */   //(00000100 00000000)b
#define GPIO_Pin_11   ((uint16_t)0x0800)  /*!< 选择Pin11 */   //(00001000 00000000)b
#define GPIO_Pin_12   ((uint16_t)0x1000)  /*!< 选择Pin12 */   //(00010000 00000000)b
#define GPIO_Pin_13   ((uint16_t)0x2000)  /*!< 选择Pin13 */   //(00100000 00000000)b
#define GPIO_Pin_14   ((uint16_t)0x4000)  /*!< 选择Pin14 */   //(01000000 00000000)b
#define GPIO_Pin_15   ((uint16_t)0x8000)  /*!< 选择Pin15 */   //(10000000 00000000)b
#define GPIO_Pin_All  ((uint16_t)0xFFFF)  /*!< 选择全部引脚*/  //(11111111 11111111)b

于是我们编程的时候就可以把这些枚举定义出来的参数写到初始结构化的结构体里边,最后再调用外设初始化函数,把结构体里边配置好的成员写到相应的寄存器里边实现配置寄存器的操作。

外设初始化函数:

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;
  }
}

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;

}
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值