文章目录
- 系列文章目录
- 前言
- 1.创建文件
- 2.添加至工程
- 3.添加文件注释
- 4. 添加.h文件防止重复编译
- 5.新建通用文件common.h
- 6.gpio.h文件编写
- 7.GPIO初始化函数
- 8.设置引脚电平函数
- 9.读取引脚电平函数
- 10.设置引脚方向函数
- 11.引脚翻转电平函数
- 12.完整程序
前言
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文件当中主要是存放一些头文件、枚举类型、函数声明、宏定义
- 首先我们需要引入两个头文件
#include "stm32f1xx.h"
#include "common.h"
stm32f1xx.h就是stm32寄存器宏定义文件
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口方向枚举类型
/*设置IO口方向枚举类型*/
typedef enum
{
GPI, // 定义管脚输入
GPO, // 定义管脚输出
}GPIO_Dir;
- 定义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;
- 定义IO口速度枚举类型,枚举类型的数值就是将来要写入寄存器的值
/*设置IO口速度枚举类型*/
typedef enum
{
GPIO_SPEED_2MHZ = 0x02,
GPIO_SPEED_10MHZ = 0x01,
GPIO_SPEED_50MHZ = 0x03,
}GPIO_Speed;
- 定义IO口引脚模块号
我们知道C语言中的枚举类型本身也是数字也可以参与运算,那么也就是说PA0就是0,PA1就是1,PB0就是16。
因为每一个引脚模块有十六个引脚(0-15)所以只需要将引脚右移4位就可以获得引脚的模块号
/*获取引脚模块号(A,B,C)*/
#define Get_Region(pin) (pin>>4)
- 获取引脚编号
而引脚的低四位也就是引脚的序号所以我们只需要将引脚和0x0F按位与就可以得到引脚编号
/*获取引脚序号*/
#define Get_Pin(pin) (pin&0x0f)
7.GPIO初始化函数
- 首先我们需要定义一个GPIO寄存器数组,这样方便我们通过数组序号配置寄存器
GPIO_TypeDef *gpio_group[4] = {GPIOA, GPIOB, GPIOC, GPIOD};
- 定义函数
void gpio_init(GPIO_Num pin, GPIO_Dir dir, uint8 dat, GPIO_Mode mode, GPIO_Speed speed)
我定义函数名为gpio_init当然这个名称大家可以随便起。然后就是传入GPIO初始化所需要的参数:引脚、方向、初始的电平、GPIO模式、引脚速度。
- 获取引脚模块号和引脚号
uint8 Reg = Get_Region(pin);
uint8 Pin = Get_Pin(pin);
- 使能对应GPIO时钟
因为出于系统节能的考虑,芯片复位默认是关闭所有外设时钟的,用户要用到那个再自行开启那个时钟
由于GPIO的时钟全部挂载在APB2外设桥上,所以我们就只需要获取引脚编号,然后在GPIOA上偏移即可
RCC->APB2ENR |= 0x01<<(2+Reg);
- 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;
}
}
- GPIO输出电平
- ODR寄存器是端口输出数据寄存器,若是推挽输出模式就是输出的电平,若是输入模式就是上下拉。
- 在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 | 结果 |
1 | 1 | 0 |
0 | 1 | 1 |
代码如下:
/**
* @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
******************************************************************************
* @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