构建库函数雏形

外设寄存器结构体的定义

考虑到外设寄存器的地址都是基于外设基地址的偏移地址,都是在外设基地址上逐个连续递增的,每个寄存器占 32 个字节,这种方式跟结构体里面的成员类似。所以我们可以定义一种外设结构体,结构体的地址等于外设的基地址,结构体的成员等于寄存器,成员的排列顺序跟寄存器的顺序一样。这样我们操作寄存器的时候就不用每次都找到绝对地址,只要知道外设的基地址就可以操作外设的全部寄存器,即操作结构体的成员可。

新建一个名为“stm32f10x.h”的头文件

//寄存器的值常常是芯片外设自动更改的,即使CPU没有执行程序,也有可能发生变化
//编译器有可能会对没有执行的程序的变量进行优化

//volatile 表示易变的变量,防止编译器优化
#define		__IO		volatile
typedef		unsigned int 	uint32_t
typedef		unsigned short int 	uint16_t

//GPIO  寄存器结构体定义
typedef struct
{
	__IO uint32_t	CRL;	//端口配置低寄存器 	    地址偏移0x00
	__IO uint32_t	CRH;	//端口配置高寄存器      地址偏移0x04
	__IO uint32_t	IDR;	//端口数据输入寄存器    地址偏移0x08
	__IO uint32_t	ODR;	//端口数据输出寄存器    地址偏移0x0c
	__IO uint32_t	BSRR;	//端口位设置/清除寄存器 地址偏移0x10
	__IO uint32_t   BRR;	//端口位清除寄存器 		地址偏移0x14
	__IO uint32_t	LCKR;	//端口配置锁定寄存器 	地址偏移0x18
}GPIO_TypeDef;

__IO 被宏定义为volatile ,所以每次使用到对应的寄存器都要求去寄存器的
地址读取寄存器的值,而不是在读取缓存中读取。若没有这个关键字修饰,在某些情况下,编译器认为没有代码修改该变量,就直接从 CPU 的某个缓存获取该变量值,这时可以加快执行速度,但该缓存中的是陈旧数据,与我们要求的寄存器最新状态可能会有出入。

外设寄存器的映射

外设寄存器结构体定义仅仅是一个定义,要想实现给这个结构体赋值就达到操作寄存
器的效果,我们还需要找到该寄存器的地址,就把寄存器地址跟结构体的地址对应起来。所以我们要再找到外设的地址,根据我们前面的学习,我们可以把这些外设的地址定义成一个个宏,实现外设存储器的映射

/*片上外设基地址*/
#define		PERIPH_BASE		((unsigned int)0x40000000)

/*APB2 总线基地址*/
#define APB2PERIPH_BASE		(PERIPH_BASE + 0X10000)

/*AHB 总线基地址*/
#define APB2PERIPH_BASE		(PERIPH_BASE + 0X20000)

/*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)

/*RCC 外设基地址*/
#define	RCC_BASE				(AHBPERIPH_BASE + 0X1000)

外设声明

定义好外设寄存器结构体,实现完外设存储器映射后,再把外设的基址强制类型转换成相应的外设寄存器结构体指针,然后再把该指针声明成外设名,这样一来,外设名就跟外设的地址对应起来了,而且该外设名还是一个该外设类型的寄存器结构体指针通过该指针可以直接操作该外设的全部寄存器

//GPIO外设声明
#define GPIOA 					((GOPIO_TypeDef *)GPIOA_BASE)
#define GPIOB 					((GOPIO_TypeDef *)GPIOB_BASE)
#define GPIOC 					((GOPIO_TypeDef *)GPIOC_BASE)
#define GPIOD 					((GOPIO_TypeDef *)GPIOD_BASE)
#define GPIOE 					((GOPIO_TypeDef *)GPIOE_BASE)
#define GPIOF 					((GOPIO_TypeDef *)GPIOF_BASE)
#define GPIOG 					((GOPIO_TypeDef *)GPIOG_BASE)

//RCC 外设声明
#define RCC						((RCC_TypeDef *)RCC_BASE)

//RCC的AHB1时钟使能寄存器地址,强制转换成指针

#define RCC_APB2ENR 			*(unsigned int*)(RCC_BASE + 0X18)

首先通过强制类型转换把外设的基地址转换成 GPIO_TypeDef 类型的结构体指针,然
后通过宏定义把 GPIOA、GPIOB 等定义成外设的结构体指针,通过外设的结构体指针我们就可以达到访问外设的寄存器的目的。通过操作外设结构体指针的方式,可以直接操作寄存器

修改main函数

修改是在https://mp.csdn.net/mdeditor/99543866#这篇文章上面修改的。

int mian(void)
{
	// 开启 GPIOB 端口时钟
	RCC->APB2ENR |= (1<<3);
	
	//清空控制 PB0 的端口位
	GPIOB->CRL &= ~( 0x0F<< (4*0));
	
	// 配置 PB0 为通用推挽输出,速度为 10M
	GPIOB->CRL |= (1<<4*0);
	
	// PB0 输出 低电平
	GPIOB->ODR |= (0<<0);
	
	while (1);
 }

定义位操作数

新建“stm32f10x_gpio.c”文件并且定义两个位操作函数,分别用于控制引脚输出高电平和低电平。
根据官方的中文手册

GPIO_BSRR寄存器
在这里插入图片描述
从图中可以看出端口x的y(0到15位)(Port x Set bit y) 只能以16位的形式操作
0:对对应的ODRy位不产生影响
1:设置对应的ODRy位为1 (只要在对应的为写入1,则芯片会自动将该位置1,)

GPIO_BRR寄存器
在这里插入图片描述在这里插入图片描述
从图中可以看出端口x的y(0到15位)(Port x Set bit y) 只能以16位的形式操作
0:对对应的ODRy位不产生影响
1:设置对应的ODRy位为0 (只要在对应的为写入1,则芯片会自动将该位置0,)

代码如下:
1GPIO 置位函数(置1)与复位函数(置0)的定义

/*
**函数功能:设置引脚为高电平
**参数说明:GPIOx:该参数为GPIO_TypeDef类型的指针,指向GPIO端口的地址
**			GPIO Pin:选择要设置的GPIO端口硬件,可输入宏GPIO Pin 0-15,
					表示GPIOx端口的0-15号引脚
*/
void GPIO_SetBits(GPIO_TypeDef *GPIOx,uint16_t GPIO_Pin)
{
	/*设置GPIOx端口BSRR寄存器的第GPIO_Pin位,使其输出高电平*/
	/*因为BSRR寄存器写0不影响,宏GPIO_Pin只是对应位为1,其它位均为0,所以可以直接赋值*/
	
	GPIOx->BRSR = GPIO_Pin;
}



/*
**函数功能:设置引脚为低电平
**参数说明:GPIOx:该参数为GPIO_TypeDef类型的指针,指向GPIO端口的地址
**			GPIO Pin:选择要设置的GPIO端口硬件,可输入宏GPIO Pin 0-15,
					表示GPIOx端口的0-15号引脚
*/
void GPIO_ResetBits(GPIO_TypeDef *GPIOx,uint16_t GPIO_Pin)
{
	/*设置GPIOx端口BSRR寄存器的第GPIO_Pin位,使其输出低电平*/
	/*因为BSRR寄存器写0不影响,
	宏GPIO_Pin只是对应位为1,其它位均为0,所以可以直接赋值*/
	
	GPIOx->BRR = GPIO_Pin;
}

利用这两个位操作函数,可以方便地操作各种 GPIO 的引脚电平,控制各种端口引脚。
其代码示例:

/*控制GPIOB的引脚10输出高电平*/
GPIO_SetBits(GPIOB,(uint16_t)(1<< (1*10)));
/*控制GPIOB的引脚10输出低电平*/
GPIO_ResetBits(GPIOB,(uint16_t)(1<< (1*10)));

/*控制GPIOB的引脚10,引脚11输出高电平,使用“|”同时控制多个引脚*/
GPIO_SetBits(GPIOB,(uint16_t)(1<< (1*10)) | (uint16_t)(1<< (1*11)));
/*控制GPIOB的引脚10,引脚11输出高电平,使用“|”同时控制多个引脚*/
GPIO_ResetBits(GPIOB,(uint16_t)(1<< 1*10) | (uint16_t)(1<< (1*11)));

/*控制GPIOA的引脚8输出高电平*/
GPIO_SetBits(GPIOA,(uint16_t)(1<<(1*8)));

/*控制GPIOA的引脚9输出高电平*/
GPIO_ResetBits(GPIOA,(uint16_t)(1<<(1*9)));

使用以上函数输入参数,设置引脚号时,还是稍感不便,为此我们把表示 16 个引脚的
操作数都定义成宏,

/*GPIO 引脚号定义*/
#define GPIO_Pin_0		((uint16_t)0x0001)		 //选择Pin0(1<< (1*0))
#define GPIO_Pin_1		((uint16_t)0x0002)		 //选择Pin1(1<< (1*1))
#define GPIO_Pin_2		((uint16_t)0x0004)		 //选择Pin2(1<< (1*2))
#define GPIO_Pin_3		((uint16_t)0x0008)		 //选择Pin3(1<< (1*3))
#define GPIO_Pin_4		((uint16_t)0x0010) 		 //选择Pin4(1<< (1*4))
#define GPIO_Pin_5		((uint16_t)0x0020)		 //选择Pin5(1<< (1*5))
#define GPIO_Pin_6		((uint16_t)0x0040) 		 //选择Pin6(1<< (1*6))
#define GPIO_Pin_7		((uint16_t)0x0080) 		//选择Pin7(1<< (1*7))
#define GPIO_Pin_8		((uint16_t)0x0100) 		//选择Pin8(1<< (1*8))
#define GPIO_Pin_9		((uint16_t)0x0200) 		//选择Pin9(1<< (1*9))
#define GPIO_Pin_10		((uint16_t)0x0400)	 	//选择Pin10(1<< (1*10))
#define GPIO_Pin_11		((uint16_t)0x0800) 		//选择Pin11(1<< (1*11))
#define GPIO_Pin_12		((uint16_t)0x1000) 		//选择Pin12(1<< (1*12))
#define GPIO_Pin_13		((uint16_t)0x2000)		//选择Pin13(1<< (1*13))
#define GPIO_Pin_14		((uint16_t)0x4000) 		//选择Pin14(1<< (1*14))
#define GPIO_Pin_15		((uint16_t)0x8000) 		//选择Pin15(1<< (1*15))
#define GPIO_Pin_ALL	((uint16_t)0xFFFF)		//选择所以引脚

这些宏代表的参数是某位置“ 1 ”其它位置“ 0 ”的数值,其中最后一个“GPIO_Pin_ALL”是所有数据位都为“1”,所以用它可以一次控制设置整个端口的 0-15所有引脚。

使用位操作函数及宏控制GPIO

/*控制GPIOB的引脚10输出高电平*/
GPIO_SetBits(GPIOB,GPIO_Pin_10);
/*控制GPIOB的引脚10输出低电平*/
GPIO_ResetBits(GPIOB,GPIO_Pin_10);

/*控制GPIOB的引脚10,引脚11输出高电平,使用“|”同时控制多个引脚*/
GPIO_SetBits(GPIOB,GPIO_Pin_10 | GPIO_Pin_11);
/*控制GPIOB的引脚10,引脚11输出高电平,使用“|”同时控制多个引脚*/
GPIO_ResetBits(GPIOB,GPIO_Pin_10 | GPIO_Pin_11);

/*控制GPIOA的引脚8输出高电平*/
GPIO_SetBits(GPIOA,GPIO_Pin_8);

/*控制GPIOA的引脚9输出高电平*/
GPIO_ResetBits(GPIOA,GPIO_Pin_9);

使用以上代码控制 GPIO,我们就不需要再看寄存器了,直接从函数名和输入参数就
可以直观看出这个语句要实现什么操作。(英文中―Set‖表示“置位”,即高电平,“Reset”
表示“复位”,即低电平)

定义初始化结构体 GPIO_InitTypeDef

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

这个结构体中包含了初始化 GPIO 所需要的信息,包括引脚号、工作模式、输出速率设计这个结构体的思路是:初始化 GPIO 前,先定义一个这样的结构体变量,根据需配置GPIO 的模式,对这个结构体的各个成员进行赋值,然后把这个变量作为“GPIO 初始函数”的输入参数,该函数能根据这个变量值中的内容去配置寄存器,从而实现GPIO 的初始化。

定义引脚模式的枚举类型

上面定义的结构体很直接,美中不足的是在对结构体中各个成员赋值实现某个功能时还需要查询手册的寄存器说明,我们不希望每次用到的时候都要去查询手册,我们可以使用 C 语言中的枚举定义功能,根据手册把每个成员的所有取值都定义好GPIO_Speed和 GPIO_Mode 这两个成员对应的寄存器是 CRL 和 CRH 这两个端口配寄存器。

C语言的枚举功能就是假设第一个变量的值是 1,默认的情况是下面每个变量的值便是1依次递增下(无自己定义)。

端口配置低寄存器 GPIOx_CRL
在这里插入图片描述在这里插入图片描述

端口配置高寄存器 GPIOx_CRH
在这里插入图片描述在这里插入图片描述

枚举类型定义示例

typedef enum
{
	GPIO Speed 10MHz = 1,  //10MHz 数值自定义为 1
	GPIO Speed 2MHz ,	   //2MHz 数值默认为 2
	GPIO Speed 50MHz,	   //2MHz 数值默认为 3	
}GPIOSpeed_TypedDef;

typed enum
{
	GPIO_Mode_AIN = 0x0,			// 模拟输入  Analog
	GPIO_Mode_IN_FLOATINT = 0X40,	// 浮空输入  Input floating
	GPIO_Mode_IPD = 0x28,			// 下列输入  Input pull-down
	GPIO_Mode_IPU = 0x48,			// 上拉输入  Input pull-up
	
	GPIO_Mode_OUT_PP = 0x10,		// 开漏输出  General purpose output Open-drain
	GPIO_Mode_OUT_OD = 0x14,		// 推挽输出  General purpose output push-pull
	GPIO_Mode_AF_OD = 0x1c,			// 复用开漏输出  Alternate function output Open-drain
	GPIO_Mode_AF_PP = 0x18			// 复用推挽输出  Alternate function output Push-pull
} GPIOMode_TypedDef;

梳理一下枚举型
在这里插入图片描述
如果但从这些枚举值的十六进制来看,很难发现规律,转化成二进制之后,就比较容
易发现规律。bit4 用来区分端口是输入还是输出,0 表示输入,1 表示输出,bit2 和 bit3 对应寄存器的 CNFY[1:0]位,是我们真正要写入到 CRL 和 CRH 这两个端口控制寄存器中的值。bit0 和 bit1 对应寄存器的 MODEY[1:0]位,这里我们暂不初始化,GPIO_Init()初始化函数中用来跟 GPIOSpeed 的值相加即可实现速率的配置。有关具体的代码分析见 GPIO_Init()库函数。其中在下拉输入和上拉输入中我们设置 bit5 和 bit6 的值为 01 和 10 来以示区别。

使用枚举来定义的GPIO初始化结构体

typedef struct
{
	uint16_t GPIO_Pin;	   /*< 选择要配置的 GPIO 引脚可输入 GPIO_Pin_ 定义的宏 */
	
	GPIOSpeed_TypeDef GPIO_Speed; /* 选择 GPIO 引脚的速率,
								可输入 GPIOSpeed_TypeDef 定义的枚举值*/
																
	GPIOMode_TypedDef 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;
/*设置引脚的输出的速率为50MHz**/
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

接着前面的思路,对初始化结构体赋值后,把它输入到 GPIO 初始化函数,由它来实
现寄存器配置.

代码如下GPIP初始化函数

/**
**函数功能:初始化引脚模式
**参数说明: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;
	uint16_t tmpreg = 0xoo,pinmask = 0x00;
	
	/*------------GPIO配置模式-------------*/
	//把输入参数GPIO_Mode的低四位暂存在currentmode
	currentmode = ((uint32_t )GPIO_InitStruct->GPIO_Mode & (uint32_t)0x0F);
	
	//bit4是1表示输出,bit4是0表示输入注意bit是从bit0开始的
	//判断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 )
	//判断是否是Pin0~Pin7
	{
		//先备份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
					GPIO->BRR = (((uint32_t) 0X01) << pinpos);
				}
				else
				{
					//判断是否为上拉输入模式
					if(GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)
					{
						//上拉输入模式,引脚默认值为1,对BSRR寄存器写1对引脚置1
						GPIO->BSRR = (((uint32_t)0x01)<< pinpos);
					}
				}
			}
		}
		
		// 把前面处理后的暂存值写入到CRL寄存器中
		GPIOx->CRL = tmpreg;
	}
	
	/*--------GPIO_CRH 寄存器配置 CRH 寄存器控制着高 8 位 IO- -----*/
	// 配置端口高8位即Pin8 ~ Pin15
	if(((uint32_t)GPIO_InitStruct->GPIO_Pin >> ((uint32_t)0x00FF) )
	//判断是否是Pin0~Pin7
	{
		//先备份CRL寄存器的值
		tmpreg = GPIOx->CRL;
		
		//循环,从Pin8开始配对,找出具体的Pin
		for(pinpos = 0x00; pinpos < 0x08; pinpos++)
		{
			// pos 的值为1左移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
					GPIO->BRR = (((uint32_t) 0X01) << (pinpos + 0x08));
				}
				else
				{
					//判断是否为上拉输入模式
					if(GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)
					{
						//上拉输入模式,引脚默认值为1,对BSRR寄存器写1对引脚置1
						GPIO->BSRR = (((uint32_t)0x01)<< (pinpos + 0x08));
					}
				}
			}
		}
		
		// 把前面处理后的暂存值写入到CRL寄存器中
		GPIOx->CRL = tmpreg;
	}
	
}

使用自定义的库点亮LED1

stm32f10x.h完整版

/*片上外设基地址*/
#define		PERIPH_BASE		((unsigned int)0x40000000)

/*APB2 总线基地址*/
#define APB2PERIPH_BASE		(PERIPH_BASE + 0X10000)

/*AHB 总线基地址*/
#define APB2PERIPH_BASE		(PERIPH_BASE + 0X20000)

/*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)

/*RCC 外设基地址*/
#define	RCC_BASE				(AHBPERIPH_BASE + 0X1000)


外设声明
//GPIO外设声明
#define GPIOA 					((GOPIO_TypeDef *)GPIOA_BASE)
#define GPIOB 					((GOPIO_TypeDef *)GPIOB_BASE)
#define GPIOC 					((GOPIO_TypeDef *)GPIOC_BASE)
#define GPIOD 					((GOPIO_TypeDef *)GPIOD_BASE)
#define GPIOE 					((GOPIO_TypeDef *)GPIOE_BASE)
#define GPIOF 					((GOPIO_TypeDef *)GPIOF_BASE)
#define GPIOG 					((GOPIO_TypeDef *)GPIOG_BASE)

//RCC 外设声明
#define RCC						((RCC_TypeDef *)RCC_BASE)

//RCC的AHB1时钟使能寄存器地址,强制转换成指针

#define RCC_APB2ENR 			*(unsigned int*)(RCC_BASE + 0X18)

/*GPIO 引脚号定义*/
#define GPIO_Pin_0		((uint16_t)0x0001) //选择Pin0(1<< (1*0))
#define GPIO_Pin_1		((uint16_t)0x0002) //选择Pin1(1<< (1*1))
#define GPIO_Pin_2		((uint16_t)0x0004) //选择Pin2(1<< (1*2))
#define GPIO_Pin_3		((uint16_t)0x0008) //选择Pin3(1<< (1*3))
#define GPIO_Pin_4		((uint16_t)0x0010) //选择Pin4(1<< (1*4))
#define GPIO_Pin_5		((uint16_t)0x0020) //选择Pin5(1<< (1*5))
#define GPIO_Pin_6		((uint16_t)0x0040) //选择Pin6(1<< (1*6))
#define GPIO_Pin_7		((uint16_t)0x0080) //选择Pin7(1<< (1*7))
#define GPIO_Pin_8		((uint16_t)0x0100) //选择Pin8(1<< (1*8))
#define GPIO_Pin_9		((uint16_t)0x0200) //选择Pin9(1<< (1*9))
#define GPIO_Pin_10		((uint16_t)0x0400) //选择Pin10(1<< (1*10))
#define GPIO_Pin_11		((uint16_t)0x0800) //选择Pin11(1<< (1*11))
#define GPIO_Pin_12		((uint16_t)0x1000) //选择Pin12(1<< (1*12))
#define GPIO_Pin_13		((uint16_t)0x2000) //选择Pin13(1<< (1*13))
#define GPIO_Pin_14		((uint16_t)0x4000) //选择Pin14(1<< (1*14))
#define GPIO_Pin_15		((uint16_t)0x8000) //选择Pin15(1<< (1*15))

#define GPIO_Pin_ALL	((uint16_t)0xFFFF) //选择所以引脚

#define		__IO		volatile
typedef		unsigned int 	uint32_t
typedef		unsigned short int 	uint16_t
//GPIO  寄存器结构体定义
typedef struct
{
	__IO uint32_t	CRL;	//端口配置低寄存器 	    地址偏移0x00
	__IO uint32_t	CRH;	//端口配置高寄存器      地址偏移0x04
	__IO uint32_t	IDR;	//端口数据输入寄存器    地址偏移0x08
	__IO uint32_t	ODR;	//端口数据输出寄存器    地址偏移0x0c
	__IO uint32_t	BSRR;	//端口位设置/清除寄存器 地址偏移0x10
	__IO uint32_t   BRR;	//端口位清除寄存器 		地址偏移0x14
	__IO uint32_t	LCKR;	//端口配置锁定寄存器 	地址偏移0x18
}GPIO_TypeDef;

//初始化GPIO口结构体的定义
typedef struct
{
	uint16_t GPIO_Pin;
	uint16_t GPIO_Speed;
	uint16_t GPIO_Mode;
} GPIO_InitTypeDef;

//GPIO 枚举类型定义
typedef enum
{
	GPIO Speed 10MHz = 1,  //10MHz 数值自定义为 1
	GPIO Speed 2MHz ,	   //2MHz 数值默认为 2
	GPIO Speed 50MHz,	   //2MHz 数值默认为 3	
}GPIOSpeed_TypedDef;

typed enum
{
	GPIO_Mode_AIN = 0x0,			// 模拟输入  Analog
	GPIO_Mode_IN_FLOATINT = 0X40,	// 浮空输入  Input floating
	GPIO_Mode_IPD = 0x28,			// 下列输入  Input pull-down
	GPIO_Mode_IPU = 0x48,			// 上拉输入  Input pull-up
	
	GPIO_Mode_OUT_PP = 0x10,		// 开漏输出  General purpose output Open-drain
	GPIO_Mode_OUT_OD = 0x14,		// 推挽输出  General purpose output push-pull
	GPIO_Mode_AF_OD = 0x1c,			// 复用开漏输出  Alternate function output Open-drain
	GPIO_Mode_AF_PP = 0x18			// 复用推挽输出  Alternate function output Push-pull
}GPIOMode_TypedDef;

//使用枚举定义的GPIO初始化结构体
typedef struct
{
	uint16_t GPIO_Pin;			  /*< 选择要配置的 GPIO 引脚
									可输入 GPIO_Pin_ 定义的宏 */
	GPIOSpeed_TypeDef GPIO_Speed; /* 选择 GPIO 引脚的速率,
									可输入 GPIOSpeed_TypeDef 定义的枚举值*/
	GPIOMode_TypedDef GPIO_Mode;  /*选择 GPIO 引脚的工作模式,
									可输入 GPIOMode_TypeDef 定义的枚举值*/
} GPIO_InitTypeDef;


//设置引脚为高电平
void GPIO_SetBits(GPIO_TypeDef *GPIOx,uint16_t GPIO_Pin);

//设置引脚为低电平
void GPIO_ResetBits(GPIO_TypeDef *GPIOx,uint16_t GPIO_Pin);

stm32f10x.c 完整代码如下

#include“stm32f10x.h”
#include<stdio.h>
GPIO 置位函数与复位函数的定义
/*
**函数功能:设置引脚为高电平
**参数说明:GPIOx:该参数为GPIO_TypeDef类型的指针,指向GPIO端口的地址
**			GPIO Pin:选择要设置的GPIO端口硬件,可输入宏GPIO Pin 0-15,
					表示GPIOx端口的0-15号引脚
*/
void GPIO_SetBits(GPIO_TypeDef *GPIOx,uint16_t GPIO_Pin)
{
	/*设置GPIOx端口BSRR寄存器的第GPIO_Pin位,使其输出高电平*/
	/*因为BSRR寄存器写0不影响,宏GPIO_Pin只是对应位为1,
	其它位均为0,所以可以直接赋值*/
	
	GPIOx->BRSR = GPIO_Pin;
}



/*
**函数功能:设置引脚为低电平
**参数说明:GPIOx:该参数为GPIO_TypeDef类型的指针,指向GPIO端口的地址
**			GPIO Pin:选择要设置的GPIO端口硬件,可输入宏GPIO Pin 0-15,
					表示GPIOx端口的0-15号引脚
*/
void GPIO_ResetBits(GPIO_TypeDef *GPIOx,uint16_t GPIO_Pin)
{
	/*设置GPIOx端口BSRR寄存器的第GPIO_Pin位,使其输出低电平*/
	/*因为BSRR寄存器写0不影响,
	宏GPIO_Pin只是对应位为1,其它位均为0,所以可以直接赋值*/
	
	GPIOx->BRR = GPIO_Pin;
}

//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;
	uint16_t tmpreg = 0xoo,pinmask = 0x00;
	
	/*------------GPIO配置模式-------------*/
	//把输入参数GPIO_Mode的低四位暂存在currentmode
	currentmode = ((uint32_t )GPIO_InitStruct->GPIO_Mode & (uint32_t)0x0F);
	
	//bit4是1表示输出,bit4是0表示输入注意bit是从bit0开始的
	//判断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 )
	//判断是否是Pin0~Pin7
	{
		//先备份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
					GPIO->BRR = (((uint32_t) 0X01) << pinpos);
				}
				else
				{
					//判断是否为上拉输入模式
					if(GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)
					{
						//上拉输入模式,引脚默认值为1,对BSRR寄存器写1对引脚置1
						GPIO->BSRR = (((uint32_t)0x01)<< pinpos);
					}
				}
			}
		}
		
		// 把前面处理后的暂存值写入到CRL寄存器中
		GPIOx->CRL = tmpreg;
	}
	
	/*--------GPIO_CRH 寄存器配置 CRH 寄存器控制着高 8 位 IO- -----*/
	// 配置端口高8位即Pin8 ~ Pin15
	if(((uint32_t)GPIO_InitStruct->GPIO_Pin >> ((uint32_t)0x00FF) )
	//判断是否是Pin0~Pin7
	{
		//先备份CRL寄存器的值
		tmpreg = GPIOx->CRL;
		
		//循环,从Pin8开始配对,找出具体的Pin
		for(pinpos = 0x00; pinpos < 0x08; pinpos++)
		{
			// pos 的值为1左移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
					GPIO->BRR = (((uint32_t) 0X01) << (pinpos + 0x08));
				}
				else
				{
					//判断是否为上拉输入模式
					if(GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)
					{
						//上拉输入模式,引脚默认值为1,对BSRR寄存器写1对引脚置1
						GPIO->BSRR = (((uint32_t)0x01)<< (pinpos + 0x08));
					}
				}
			}
		}
		
		// 把前面处理后的暂存值写入到CRL寄存器中
		GPIOx->CRL = tmpreg;
	}
	
}

main.c函数代码如下

int main(void)
{
	//定义一个 GPIO_InitTypeDef类型的结构体
	GPIO_InitTypeDef GPIO_InitStructure;
	
	//开启GPIO端口时钟
	RCC_APB2ENR |= (1 << 3);
	
	//选择需要配置的GPIO 引脚11输出高电平
	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 灯与之前直接控制寄存器已经有了很大的区别:main 函数中先定义了一个 GPIO 初始化结构体变量 GPIO_InitStructure,然后对该变量的各个成员按点亮 LED 灯所需要的 GPIO 配置模式进行赋值,赋值后,调用 GPIO_Init 函数,
让它根据结构体成员值对 GPIO 寄存器写入控制参数,完成 GPIO 引脚初始化。控制电平时,直接使用 GPIO_SetBits 和 GPIO_Resetbits 函数控制输出。如若对其它引脚进行不同模式的初始化,只要修改 GPIO 初始化结构体 GPIO_InitStructure 的成员值,把新的参数值输入到 GPIO_Init 函数再调用即可。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值