系列文章目录

Github开源地址
从头开始写STM32F103C8T6驱动库(一)——STM32CubeMX创建并调整工程结构
从头开始写STM32F103C8T6驱动库(二)——编写系统初始化程序,配置时钟树
从头开始写STM32F103C8T6驱动库(三)——编写GPIO驱动
从头开始写STM32F103C8T6驱动库(四)——编写延时函数,详解Systick


前言

I/O端口位的基本结构
在这里插入图片描述

1.创建文件

在这里插入图片描述
点击左上角New图标创建两个新文件,一个作为.c文件一个作为.h文件
在这里插入图片描述

按ctrl+s保存文件,保存至Drivers/Src路径下,命名为gpio.c
同样的道理另一个文件保存至Drivers/Inc路径下,命名为gpio.h

2.添加至工程

在这里插入图片描述
点击文件管理,将gpio.c添加至工程,并将system_stm32f1xx.c移动到Core文件夹中,调整后路径结果如上
在这里插入图片描述

3.添加文件注释

在这里插入图片描述
大家可以在Templates中右键空白处配置自己的备注信息
在这里插入图片描述
只需要在2处填写注释名称,在3出填写注释信息就可以快速写注释了
在这里插入图片描述
我们添加好.c文件和.h文件的注释信息

4. 添加.h文件防止重复编译

在这里插入图片描述
我们通过一个宏定义来防止gpio.h文件重复编译,大概的意思就是说如果没有定义_GPIO_H这个宏那么就定义这个宏然后开始编译.h文件,如果第二次编译该文件时,那么这个宏已经定义过了则不会进行第二次编译。

5.新建通用文件common.h

在这里插入图片描述

同样的方法我们将其保存在Core/Inc文件夹当中。主要代码如下。

/**
  ******************************************************************************
  * @file           : common.h
  * @brief          : 通用头文件
	* @author					: 满心欢喜
	* @contact				: QQ:320388825 VX:LHD0617_
	* @Created				: 2021/01/21
  ******************************************************************************
  * @attention
  *
  * 本程序只供学习使用,未经作者许可,不得用于其它任何用途。
  *
  ******************************************************************************
  */
#ifndef _COMMON_H
#define _COMMON_H

//数据类型声明
typedef unsigned char							uint8;													//  8 bits 
typedef unsigned short int						uint16;													// 16 bits 
typedef unsigned long int						uint32;													// 32 bits 
typedef unsigned long long						uint64;													// 64 bits 

typedef char									int8;														//  8 bits 
typedef short int								int16;													// 16 bits 
typedef long  int								int32;													// 32 bits 
typedef long  long								int64;													// 64 bits 

typedef volatile int8							vint8;													//  8 bits 
typedef volatile int16							vint16;													// 16 bits 
typedef volatile int32							vint32;													// 32 bits 
typedef volatile int64							vint64;													// 64 bits 

typedef volatile uint8							vuint8;													//  8 bits 
typedef volatile uint16							vuint16;												// 16 bits 
typedef volatile uint32							vuint32;												// 32 bits 
typedef volatile uint64							vuint64;												// 64 bits 


#endif



6.gpio.h文件编写

gpio.h文件当中主要是存放一些头文件、枚举类型、函数声明、宏定义

  1. 首先我们需要引入两个头文件
#include "stm32f1xx.h"
#include "common.h"

stm32f1xx.h就是stm32寄存器宏定义文件
common.h就是咱们刚刚创建的通用头文件,主要内容就是对数据类型的别名。

  1. 定义单片机引脚枚举类型
/*设置引脚枚举类型*/
typedef enum
{
    PA0,  PA1,  PA2,  PA3,  PA4,  PA5,  PA6,  PA7,  PA8,  PA9,  PA10, PA11, PA12, PA13, PA14, PA15,
                 
    PB0,  PB1,  PB2,  PB3,  PB4,  PB5,  PB6,  PB7,  PB8,  PB9,  PB10, PB11, PB12, PB13, PB14, PB15,
                 
    PC0,  PC1,  PC2,  PC3,  PC4,  PC5,  PC6,  PC7,  PC8,  PC9,  PC10, PC11, PC12, PC13, PC14, PC15,
}GPIO_Num;
  1. 定义IO口方向枚举类型
/*设置IO口方向枚举类型*/
typedef enum
{
	GPI,										// 定义管脚输入
	GPO,										// 定义管脚输出
}GPIO_Dir;

在这里插入图片描述

  1. 定义IO口模式枚举类型,枚举类型的数值就是将来要写入寄存器的值
/*设置IO口模式枚举类型*/
typedef enum
{
	GPI_ANAOG_IN 			=	0x00,						// 定义管脚模拟输入
	GPI_FLOATING_IN			=	0x04,						// 定义管脚浮空输入
	GPI_PULL_UD				=	0x08,						// 定义管脚上下拉输入

	GPO_PUSH_PULL			=	0x00,						// 定义管脚推挽输出
	GPO_OPEN_DTAIN			=	0x04,						// 定义管脚开漏输出
	GPO_AF_PUSH_PULL		=	0x08,						// 定义管脚复用推挽输出
	GPO_AF_OPEN_DTAIN		=	0x0C,						// 定义管脚复用开漏输出
	
}GPIO_Mode;

在这里插入图片描述

  1. 定义IO口速度枚举类型,枚举类型的数值就是将来要写入寄存器的值
/*设置IO口速度枚举类型*/
typedef enum
{
		GPIO_SPEED_2MHZ		= 	0x02,
		GPIO_SPEED_10MHZ	=	0x01,
		GPIO_SPEED_50MHZ	=	0x03,
}GPIO_Speed;
  1. 定义IO口引脚模块号
    我们知道C语言中的枚举类型本身也是数字也可以参与运算,那么也就是说PA0就是0,PA1就是1,PB0就是16。
    因为每一个引脚模块有十六个引脚(0-15)所以只需要将引脚右移4位就可以获得引脚的模块号
/*获取引脚模块号(A,B,C)*/
#define Get_Region(pin)		(pin>>4)
  1. 获取引脚编号
    而引脚的低四位也就是引脚的序号所以我们只需要将引脚和0x0F按位与就可以得到引脚编号
/*获取引脚序号*/
#define Get_Pin(pin)			(pin&0x0f)

7.GPIO初始化函数

  1. 首先我们需要定义一个GPIO寄存器数组,这样方便我们通过数组序号配置寄存器
GPIO_TypeDef *gpio_group[4] = {GPIOA, GPIOB, GPIOC, GPIOD};
  1. 定义函数
void gpio_init(GPIO_Num pin, GPIO_Dir dir, uint8 dat, GPIO_Mode mode, GPIO_Speed speed)

我定义函数名为gpio_init当然这个名称大家可以随便起。然后就是传入GPIO初始化所需要的参数:引脚、方向、初始的电平、GPIO模式、引脚速度。

  1. 获取引脚模块号和引脚号
uint8 Reg = Get_Region(pin);
uint8 Pin = Get_Pin(pin);
  1. 使能对应GPIO时钟
    因为出于系统节能的考虑,芯片复位默认是关闭所有外设时钟的,用户要用到那个再自行开启那个时钟
    在这里插入图片描述
    由于GPIO的时钟全部挂载在APB2外设桥上,所以我们就只需要获取引脚编号,然后在GPIOA上偏移即可
RCC->APB2ENR |= 0x01<<(2+Reg);
  1. GPIO模式配置
    在这里插入图片描述
    GPIO模式的配置由CRL和CRH两个寄存器控制
    CRL为低八位寄存器控制GPIO0-GPIO7
    CRH为高八位寄存器控制GPIO8-GPIO15
    假如我们要配置GPIO为上下拉输入模式就是将MODEy两位写入00,将CNFy两位配置为10
    假如我们要配置GPIO为推挽输出模式就是将MODEy两位写入11,将CNFy两位配置为00
    在写入寄存器之前要先将对应的寄存器位清空,防止出错。
    所以代码如下:
if(dir == GPI)
	{
		if(Pin<8)
		{
			gpio_group[Reg]->CRL &= ~(0x0f<<Pin*4);
			gpio_group[Reg]->CRL |= mode<<Pin*4;
		}
		else
		{
			gpio_group[Reg]->CRH &= ~(0x0f<<(Pin-8)*4);
			gpio_group[Reg]->CRH |= mode<<(Pin-8)*4;
		}
	}
	if(dir == GPO)
	{
		if(Pin<8)
		{
			gpio_group[Reg]->CRL &= ~(0x0f<<Pin*4);
			gpio_group[Reg]->CRL |= mode<<Pin*4;
			gpio_group[Reg]->CRL |= speed<<Pin*4;
		}
		else
		{
			gpio_group[Reg]->CRH &= ~(0x0f<<(Pin-8)*4);
			gpio_group[Reg]->CRH |= mode<<(Pin-8)*4;
			gpio_group[Reg]->CRH |= speed<<(Pin-8)*4;
		}
	}
  1. GPIO输出电平
    在这里插入图片描述
    ODR寄存器是端口输出数据寄存器,若是推挽输出模式就是输出的电平,若是输入模式就是上下拉。

  2. 在gpio.h文件中进行声明

void gpio_init(GPIO_Num pin, GPIO_Dir dir, uint8 dat, GPIO_Mode mode, GPIO_Speed speed);

到此GPIO初始化结束,完整代码如下:

/**
	* @name		gpio_init
 	* @brief  	GPIO初始化
	* @param  	pin	引脚编号		(P(A,B,C)0-15)
	* @param  	dir	引脚方向		GPO输出	GPI输入
	* @param  	dat	初始化电平	0为低电平	1为高电平
	* @param  	mode	引脚模式		在gpio.h文件中可选择
	* @param  	speed	输出速率		在gpio.h文件中可选择
	* @return 	void
	* @Sample 	gpio_init(PC13, GPO, 0, GPO_PUSH_PULL, GPIO_SPEED_50MHZ)
	* @Sample 	gpio_init(PC13, GPI, 1, GPI_PULL_UD	 , GPIO_SPEED_50MHZ)
  */
void gpio_init(GPIO_Num pin, GPIO_Dir dir, uint8 dat, GPIO_Mode mode, GPIO_Speed speed)
{
	uint8 Reg = Get_Region(pin);
	uint8 Pin = Get_Pin(pin);
	RCC->APB2ENR |= 0x01<<(2+Reg);
	if(dir == GPI)
	{
		if(Pin<8)
		{
			gpio_group[Reg]->CRL &= ~(0x0f<<Pin*4);
			gpio_group[Reg]->CRL |= mode<<Pin*4;
		}
		else
		{
			gpio_group[Reg]->CRH &= ~(0x0f<<(Pin-8)*4);
			gpio_group[Reg]->CRH |= mode<<(Pin-8)*4;
		}
	}
	if(dir == GPO)
	{
		if(Pin<8)
		{
			gpio_group[Reg]->CRL &= ~(0x0f<<Pin*4);
			gpio_group[Reg]->CRL |= mode<<Pin*4;
			gpio_group[Reg]->CRL |= speed<<Pin*4;
		}
		else
		{
			gpio_group[Reg]->CRH &= ~(0x0f<<(Pin-8)*4);
			gpio_group[Reg]->CRH |= mode<<(Pin-8)*4;
			gpio_group[Reg]->CRH |= speed<<(Pin-8)*4;
		}
	}
	if(dat)	gpio_group[Reg]->ODR |= 0x01<<Pin;
	else	gpio_group[Reg]->ODR &= ~(0x01<<Pin);
}

8.设置引脚电平函数

这个函数没什么说的,就是设置对应ODR寄存器中的值,代码如下:

/**
	* @name		gpio_set
  	* @brief  	GPIO设置引脚电平
	* @param  	pin		引脚编号		(P(A,B,C)0-15)
	* @param  	dat		初始化电平	0为低电平	1为高电平
	* @return 	void
	* @Sample 	gpio_set(PC13, 0)
  */
void gpio_set(GPIO_Num pin, uint8 dat)
{
	if(dat)	gpio_group[Get_Region(pin)]->ODR |= 0x01<<Get_Pin(pin);
	else	gpio_group[Get_Region(pin)]->ODR &= ~(0x01<<Get_Pin(pin));
}

9.读取引脚电平函数

在这里插入图片描述
只需要读取对应引脚的IDR寄存器中的值即可,代码如下:

/**
	* @name		gpio_get
  	* @brief  	GPIO获取引脚电平
	* @param  	pin		引脚编号		(P(A,B,C)0-15)
	* @return 	引脚电平	0为低电平 1为高电平
	* @Sample 	gpio_get(PA0)
  */
uint8 gpio_get(GPIO_Num pin)
{
	if(gpio_group[Get_Region(pin)]->IDR & 0x01<<Get_Pin(pin))		return 1;
	else															return 0;
}

10.设置引脚方向函数

该函数在软件模拟通信时序中常常用到,其实就是初始化函数,基础上去掉使能时钟和配置引脚电平,代码如下:

/**
	* @name		gpio_dir
	* @brief  	GPIO设置引脚方向
	* @param  	pin		引脚编号		(P(A,B,C)0-15)
	* @param  	dir		引脚方向		GPO输出	GPI输入
	* @param  	mode	引脚模式		在gpio.h文件中可选择
	* @param  	speed	输出速率		在gpio.h文件中可选择
	* @return 	void
	* @Sample 	gpio_dir(PC13, GPO, GPO_PUSH_PULL, GPIO_SPEED_50MHZ)
	* @Sample	gpio_dir(PC13, GPI, GPI_PULL_UD  , GPIO_SPEED_50MHZ)
  */
void gpio_dir(GPIO_Num pin, GPIO_Dir dir, GPIO_Mode mode, GPIO_Speed speed)
{
	uint8 Reg = Get_Region(pin);
	uint8 Pin = Get_Pin(pin);
	if(dir == GPI)
	{
		if(Pin<8)
		{
			gpio_group[Reg]->CRL &= ~(0x0f<<Pin*4);
			gpio_group[Reg]->CRL |= mode<<Pin*4;
		}
		else
		{
			gpio_group[Reg]->CRH &= ~(0x0f<<(Pin-8)*4);
			gpio_group[Reg]->CRH |= mode<<(Pin-8)*4;
		}
	}
	if(dir == GPO)
	{
		if(Pin<8)
		{
			gpio_group[Reg]->CRL &= ~(0x0f<<Pin*4);
			gpio_group[Reg]->CRL |= mode<<Pin*4;
			gpio_group[Reg]->CRL |= speed<<Pin*4;
		}
		else
		{
			gpio_group[Reg]->CRH &= ~(0x0f<<(Pin-8)*4);
			gpio_group[Reg]->CRH |= mode<<(Pin-8)*4;
			gpio_group[Reg]->CRH |= speed<<(Pin-8)*4;
		}
	}
}

11.引脚翻转电平函数

该函数就是将ODR寄存器中对应的位取反
这里我用了一个异或操作,将0x01与对应位进行异或,异或操作就是相同为0不同为1
真值表如下:

寄存器对应位0x01结果
110
011

代码如下:

/**
	* @name		gpio_reverse
  	* @brief  	GPIO引脚翻转电平
	* @param  	pin		引脚编号		(P(A,B,C)0-15)
	* @return 	void
	* @Sample 	gpio_reverse(PC13)
  */
void gpio_reverse(GPIO_Num pin)
{
	gpio_group[Get_Region(pin)]->ODR ^= 0x01<<Get_Pin(pin);
}

12.完整程序

完整gpio.c文件

/**
  ******************************************************************************
  * @file           : gpio.c
  * @brief          : GPIO驱动
	* @author					: 满心欢喜
	* @contact				: QQ:320388825 VX:LHD0617_
	* @Created				: 2021/12/30
  ******************************************************************************
  * @attention
  *
  * 本程序只供学习使用,未经作者许可,不得用于其它任何用途。
  *
  ******************************************************************************
  */

/* Includes ------------------------------------------------------------------*/
#include "gpio.h"

GPIO_TypeDef *gpio_group[4] = {GPIOA, GPIOB, GPIOC, GPIOD};


/**
	* @name		gpio_init
  * @brief  GPIO初始化
	* @param  pin		引脚编号		(P(A,B,C)0-15)
	* @param  dir		引脚方向		GPO输出	GPI输入
	* @param  dat		初始化电平	0为低电平	1为高电平
	* @param  mode	引脚模式		在gpio.h文件中可选择
	* @param  speed	输出速率		在gpio.h文件中可选择
	* @return void
	* @Sample gpio_init(PC13, GPO, 0, GPO_PUSH_PULL, GPIO_SPEED_50MHZ)
	* @Sample gpio_init(PC13, GPI, 1, GPI_PULL_UD	 , GPIO_SPEED_50MHZ)
  */
void gpio_init(GPIO_Num pin, GPIO_Dir dir, uint8 dat, GPIO_Mode mode, GPIO_Speed speed)
{
	uint8 Reg = Get_Region(pin);
	uint8 Pin = Get_Pin(pin);
	RCC->APB2ENR |= 0x01<<(2+Reg);
	if(dir == GPI)
	{
		if(Pin<8)
		{
			gpio_group[Reg]->CRL &= ~(0x0f<<Pin*4);
			gpio_group[Reg]->CRL |= mode<<Pin*4;
		}
		else
		{
			gpio_group[Reg]->CRH &= ~(0x0f<<(Pin-8)*4);
			gpio_group[Reg]->CRH |= mode<<(Pin-8)*4;
		}
	}
	if(dir == GPO)
	{
		if(Pin<8)
		{
			gpio_group[Reg]->CRL &= ~(0x0f<<Pin*4);
			gpio_group[Reg]->CRL |= mode<<Pin*4;
			gpio_group[Reg]->CRL |= speed<<Pin*4;
		}
		else
		{
			gpio_group[Reg]->CRH &= ~(0x0f<<(Pin-8)*4);
			gpio_group[Reg]->CRH |= mode<<(Pin-8)*4;
			gpio_group[Reg]->CRH |= speed<<(Pin-8)*4;
		}
	}
	if(dat)	gpio_group[Reg]->ODR |= 0x01<<Pin;
	else		gpio_group[Reg]->ODR &= ~(0x01<<Pin);
}

/**
	* @name		gpio_set
  * @brief  GPIO设置引脚电平
	* @param  pin		引脚编号		(P(A,B,C)0-15)
	* @param  dat		初始化电平	0为低电平	1为高电平
	* @return void
	* @Sample gpio_set(PC13, 0)
  */
void gpio_set(GPIO_Num pin, uint8 dat)
{
	if(dat)	gpio_group[Get_Region(pin)]->ODR |= 0x01<<Get_Pin(pin);
	else		gpio_group[Get_Region(pin)]->ODR &= ~(0x01<<Get_Pin(pin));
}

/**
	* @name		gpio_get
  * @brief  GPIO获取引脚电平
	* @param  pin		引脚编号		(P(A,B,C)0-15)
	* @return 引脚电平	0为低电平 1为高电平
	* @Sample gpio_get(PA0)
  */
uint8 gpio_get(GPIO_Num pin)
{
	if(gpio_group[Get_Region(pin)]->IDR & 0x01<<Get_Pin(pin))		return 1;
	else																												return 0;
}

/**
	* @name		gpio_dir
  * @brief  GPIO设置引脚方向
	* @param  pin		引脚编号		(P(A,B,C)0-15)
	* @param  dir		引脚方向		GPO输出	GPI输入
	* @param  mode	引脚模式		在gpio.h文件中可选择
	* @param  speed	输出速率		在gpio.h文件中可选择
	* @return void
	* @Sample gpio_dir(PC13, GPO, GPO_PUSH_PULL, GPIO_SPEED_50MHZ)
	* @Sample	gpio_dir(PC13, GPI, GPI_PULL_UD  , GPIO_SPEED_50MHZ)
  */
void gpio_dir(GPIO_Num pin, GPIO_Dir dir, GPIO_Mode mode, GPIO_Speed speed)
{
	uint8 Reg = Get_Region(pin);
	uint8 Pin = Get_Pin(pin);
	if(dir == GPI)
	{
		if(Pin<8)
		{
			gpio_group[Reg]->CRL &= ~(0x0f<<Pin*4);
			gpio_group[Reg]->CRL |= mode<<Pin*4;
		}
		else
		{
			gpio_group[Reg]->CRH &= ~(0x0f<<(Pin-8)*4);
			gpio_group[Reg]->CRH |= mode<<(Pin-8)*4;
		}
	}
	if(dir == GPO)
	{
		if(Pin<8)
		{
			gpio_group[Reg]->CRL &= ~(0x0f<<Pin*4);
			gpio_group[Reg]->CRL |= mode<<Pin*4;
			gpio_group[Reg]->CRL |= speed<<Pin*4;
		}
		else
		{
			gpio_group[Reg]->CRH &= ~(0x0f<<(Pin-8)*4);
			gpio_group[Reg]->CRH |= mode<<(Pin-8)*4;
			gpio_group[Reg]->CRH |= speed<<(Pin-8)*4;
		}
	}
}

/**
	* @name		gpio_reverse
  * @brief  GPIO引脚翻转电平
	* @param  pin		引脚编号		(P(A,B,C)0-15)
	* @return void
	* @Sample gpio_reverse(PC13)
  */
void gpio_reverse(GPIO_Num pin)
{
	gpio_group[Get_Region(pin)]->ODR ^= 0x01<<Get_Pin(pin);
}

完整gpio.h文件

/**
  ******************************************************************************
  * @file           : gpio.h
  * @brief          : GPIO驱动
	* @author					: 满心欢喜
	* @contact				: QQ:320388825 VX:LHD0617_
	* @Created				: 2021/12/30
  ******************************************************************************
  * @attention
  *
  * 本程序只供学习使用,未经作者许可,不得用于其它任何用途。
  *
  ******************************************************************************
  */
#ifndef _GPIO_H
#define _GPIO_H

/* Includes ------------------------------------------------------------------*/
#include "stm32f1xx.h"
#include "common.h"

/*设置引脚枚举类型*/
typedef enum
{
    PA0,  PA1,  PA2,  PA3,  PA4,  PA5,  PA6,  PA7,  PA8,  PA9,  PA10, PA11, PA12, PA13, PA14, PA15,
                 
    PB0,  PB1,  PB2,  PB3,  PB4,  PB5,  PB6,  PB7,  PB8,  PB9,  PB10, PB11, PB12, PB13, PB14, PB15,
                 
    PC0,  PC1,  PC2,  PC3,  PC4,  PC5,  PC6,  PC7,  PC8,  PC9,  PC10, PC11, PC12, PC13, PC14, PC15,
}GPIO_Num;

/*设置IO口方向枚举类型*/
typedef enum
{
	GPI,										// 定义管脚输入
	GPO,										// 定义管脚输出
}GPIO_Dir;

/*设置IO口模式枚举类型*/
typedef enum
{
	GPI_ANAOG_IN 				= 	0x00,						// 定义管脚模拟输入
	GPI_FLOATING_IN				= 	0x04,						// 定义管脚浮空输入
	GPI_PULL_UD					= 	0x08,						// 定义管脚上下拉输入

	GPO_PUSH_PULL				=	0x00,						// 定义管脚推挽输出
	GPO_OPEN_DTAIN				= 	0x04,						// 定义管脚开漏输出
	GPO_AF_PUSH_PULL			=	0x08,						// 定义管脚复用推挽输出
	GPO_AF_OPEN_DTAIN			= 	0x0C,						// 定义管脚复用开漏输出
	
}GPIO_Mode;

/*设置IO口速度枚举类型*/
typedef enum
{
		GPIO_SPEED_2MHZ		= 	0x02,
		GPIO_SPEED_10MHZ	=	0x01,
		GPIO_SPEED_50MHZ	=	0x03,
}GPIO_Speed;

/*获取引脚模块号(A,B,C)*/
#define Get_Region(pin)			(pin>>4)

/*获取引脚序号*/
#define Get_Pin(pin)			(pin&0x0f)


/*函数声明*/
void gpio_init(GPIO_Num pin, GPIO_Dir dir, uint8 dat, GPIO_Mode mode, GPIO_Speed speed);
void gpio_set(GPIO_Num pin, uint8 dat);
uint8 gpio_get(GPIO_Num pin);
void gpio_dir(GPIO_Num pin, GPIO_Dir dir, GPIO_Mode mode, GPIO_Speed speed);
void gpio_reverse(GPIO_Num pin);


#endif


Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐