-最基本的GPIO口详细配置过程及原理-
关于单片机的GPIO口的概念和使用
我们在学习单片机的过程中,每次学习一个新的外设,我们一定要去弄懂这个外设的概念,要彻底明白这个外设是用来干嘛的,这样也方便我们去理解记忆外设的配置流程,对于概念,字面意思我们要对适当的去记忆,不然别人问起,我们自己知道但不会表达,也不行哈,自己去适当的记忆也有利于我们对单片机的体系结构有个整体的把握,掌控全局,逐个击破。让我们先来认识一下GPIO口是干嘛的吧。
GPIO的概念
GPIO,英文全称为General-Purpose IO ports,也就是 通用IO口,单片机中常常有数量众多的GPIO口供用户使用,但是结构却比较简单的外部设备/电路,对这些电路有的需要CPU为之提供控制手段,有的则需要被CPU用作输入信号。而且,许多这样的设备/电路只要求一位,即只要有开/关两种状态就够了,比如灯亮与灭。对这些设备/电路的控制,使用传统的串行口或并行口都不合适。所以在微控制器芯片上一般都会提供一个“通用可编程IO接口”,即GPIO。
换种方法理解
换种话说GPIO是什么?GPIO就是芯片的引脚。单片机的外设都要通过GPIO口与芯片交互
除非它有规定的特定的用途,否则都可以叫做GPIO,具体得去看芯片手册怎么规定的。
特点是:可以被编程控制工作模式,也可以控制电压高低等。比如接上LED,就可以通过编程控制GPIO的模式和输入输出值来操控LED的亮和灭。
STM32芯片的GPIO引脚与外部设备连接起来,从而实现与外部通讯、控制以及数据采集的功能。STM32芯片的GPIO被分成很多组,每组有16个引脚,所有的GPIO引脚都有基本的输入输出功能。
最基本的输出功能是由STM32控制引脚输出高、低电平,实现开关控制,如把GPIO引脚接入LED灯,那就可以控制LED灯的亮灭,
最基本的输入功能是检测外部电平,如把GPIO引脚连接到按键,通过电平高低区分按键是否被按下。
GPIO的第二功能-复用
GPIO的复用功能(设置他的另外一种功能):
STM32F1 有很多的内置外设,这些外设的外部引脚都是与 GPIO 共用的。也就是说,一个引脚可以有很多作用,但是默认为IO口,如果想使用一个 GPIO内置外设的功能引脚,就需要GPIO的复用,那么当这个 GPIO 作为内置外设使用的时候,就叫做复用。 比如说串口 就是GPIO复用为串口。
STM32中GPIO的构造
每个GPI/O端口有两个32位配置寄存器(GPIOx_CRL,GPIOx_CRH),两个32位数据寄存器(GPIOx_IDR和GPIOx_ODR),一个32位置位/复位寄存器(GPIOx_BSRR),一个16位复位寄存器(GPIOx_BRR)和一个32位锁定寄存器(GPIOx_LCKR)。
对于寄存器的值,我们不多介绍,谁开发用寄存器啊。我们直接上手固件库!
GPIO的工作模式
根据数据手册中列出的每个I/O端口的特定硬件特征, GPIO端口的每个位可以由软件分别配置成多种模式。
─ 输入浮空 --用于对不确定高低电平的输入
─ 输入上拉 – 用于默认为上拉至高电平的输入
─ 输入下拉 --用于默认为下拉之低电平的输入
─ 模拟输入 --用于模拟量的输入
─ 开漏输出 --用于实现电平转换和线与功能的输出
─ 推挽式输出 --用于较大功率驱动的输出
─ 推挽式复用功能 --复用功能情况下的推挽输出
─ 开漏复用功能 --复用功能情况下的开漏输出
我们可以将这8中工作模式分成两大类:输入模式和输出,根据名字也能分辨哈。
GPIO的固件库函数
固件库中,对外设的初始化都是由外设对应的结构体开始的,还有一些封装好的函数。
GPIO的结构体变量
typedef struct
{
uint16_t GPIO_Pin; /*!< Specifies the GPIO pins to be configured.
This parameter can be any value of @ref GPIO_pins_define */
GPIOSpeed_TypeDef GPIO_Speed; /*!< Specifies the speed for the selected pins.
This parameter can be a value of @ref GPIOSpeed_TypeDef */
GPIOMode_TypeDef GPIO_Mode; /*!< Specifies the operating mode for the selected pins.
This parameter can be a value of @ref GPIOMode_TypeDef */
}GPIO_InitTypeDef;
GPIO结构体变量的取值
typedef enum
{ GPIO_Mode_AIN = 0x0,
GPIO_Mode_IN_FLOATING = 0x04,
GPIO_Mode_IPD = 0x28,
GPIO_Mode_IPU = 0x48,
GPIO_Mode_Out_OD = 0x14,
GPIO_Mode_Out_PP = 0x10,
GPIO_Mode_AF_OD = 0x1C,
GPIO_Mode_AF_PP = 0x18
}GPIOMode_TypeDef;
typedef enum
{
GPIO_Speed_10MHz = 1,
GPIO_Speed_2MHz,
GPIO_Speed_50MHz
}GPIOSpeed_TypeDef;
#define GPIO_Pin_0 ((uint16_t)0x0001) /*!< Pin 0 selected */
#define GPIO_Pin_1 ((uint16_t)0x0002) /*!< Pin 1 selected */
#define GPIO_Pin_2 ((uint16_t)0x0004) /*!< Pin 2 selected */
#define GPIO_Pin_3 ((uint16_t)0x0008) /*!< Pin 3 selected */
#define GPIO_Pin_4 ((uint16_t)0x0010) /*!< Pin 4 selected */
#define GPIO_Pin_5 ((uint16_t)0x0020) /*!< Pin 5 selected */
#define GPIO_Pin_6 ((uint16_t)0x0040) /*!< Pin 6 selected */
#define GPIO_Pin_7 ((uint16_t)0x0080) /*!< Pin 7 selected */
#define GPIO_Pin_8 ((uint16_t)0x0100) /*!< Pin 8 selected */
#define GPIO_Pin_9 ((uint16_t)0x0200) /*!< Pin 9 selected */
#define GPIO_Pin_10 ((uint16_t)0x0400) /*!< Pin 10 selected */
#define GPIO_Pin_11 ((uint16_t)0x0800) /*!< Pin 11 selected */
#define GPIO_Pin_12 ((uint16_t)0x1000) /*!< Pin 12 selected */
#define GPIO_Pin_13 ((uint16_t)0x2000) /*!< Pin 13 selected */
#define GPIO_Pin_14 ((uint16_t)0x4000) /*!< Pin 14 selected */
#define GPIO_Pin_15 ((uint16_t)0x8000) /*!< Pin 15 selected */
#define GPIO_Pin_All ((uint16_t)0xFFFF) /*!< All pins selected */
库函数:
1.void GPIO_DeInit(GPIO_TypeDef* GPIOx)
功能:将GPIOx外设寄存器初始化为默认值
2.void GPIO_AFIODeInit(void)
功能:将复用功能(重映射与EXTI设置)重设为默认值
注释:通过操作RCC_APB2RSTR(APB2外设复位寄存器)对其寄存器进行复位
3.void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)
功能:按照GPIO_InitStruct结构体变量所配置的参数来初始化GPIOx寄存器
例如:GPIO_Init(GPIOA,&GPIO_InitStructure);
4.void GPIO_StructInit(GPIO_InitTypeDef* GPIO_InitStruct)
功能:将GPIO结构体各个成员填充为默认值
注释:未涉及寄存器操作,直接向结构体成员赋默认值
5.uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
功能:读取指定端口引脚的输入电平,针对Pin
注释:通过访问GPIOx_IDR(端口输入数据寄存器)可获知某一引脚的输入电平
例如: ReadValue = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_7);
6.uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx)
功能:读取指定GPIO端口的输入电平,针对Port
注释:通过访问GPIOx_IDR(端口输入数据寄存器)可获知某一端口的输入电平
例如:ReadValue = GPIO_ReadInputData(GPIOC);
7.uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
功能:读取指定端口引脚的输出电平,针对Pin
注释:通过访问GPIOx_ODR(端口输出数据寄存器)可获知某一引脚的输出电平
8.uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx)
功能:读取指定GPIO端口的输出电平,针对Port
注释:通过访问GPIOx_ODR(端口输出数据寄存器可获知某一端口的输出电平
例如:ReadValue = GPIO_ReadOutputData(GPIOC);
9.void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
功能:设置选定的数据端口位,置“1”,一个或多个
注释:使用GPIOx_BSRR的端口位设置功能来达到设置引脚输出为“1”的目的,BSRR同样具备BRR功能
例如:GPIO_SetBits(GPIOA, GPIO_Pin_10 | GPIO_Pin_15);
10.void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
功能:清除指定的数据端口位,清“0”,一个或多个
注释:通过操作BRR寄存器来实现端口引脚的低电平输出
例如:GPIO_ResetBits(GPIOA, GPIO_Pin_10 | GPIO_Pin_15);
11.void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal)
功能:对某一个端口管脚实现清“0”或置“1”操作,一个
注释:判断电平,进而对应操作BRR或者BSRR
例如:GPIO_WriteBit(GPIOA, GPIO_Pin_15, 1);
12.void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal)
功能:向某一端口写入指定数据
注释:将数据写入GPIOx_ODR寄存器,ODR可读可写,但只能字操作
例如:GPIO_Write(GPIOA, 0x1234);
13.void GPIO_PinLockConfig(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
功能:锁定GPIO管脚的配置,下次系统复位之前将不能再更改端口位的配置,一个或多个
注释:根据键写入序列,通过操作LCKK与LCKx来进行设置
例如:GPIO_PinLockConfig(GPIOA, GPIO_Pin_0 | GPIO_Pin_1);
14.void GPIO_EventOutputConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource)
功能: 选择GPIO管脚用作事件输出
注释:对AFIO_EVCR的操作
例如:GPIO_EventOutputConfig(GPIO_PortSourceGPIOE, GPIO_PinSource5);
15.void GPIO_EventOutputCmd(FunctionalState NewState)
功能:使能或失能事件输出
16.void GPIO_PinRemapConfig(uint32_t GPIO_Remap, FunctionalState NewState)
功能:改变管脚映射
注释:没有重映像,部分重映像,完全重映像
例如:GPIO_PinRemapConfig(GPIO_Remap_I2C1, ENABLE);
17.void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource)
功能:选择GPIO管脚作为中断线路
注释:16个管脚占用了4个寄存器
例如:GPIO_EXTILineConfig(GPIO_PortSource_GPIOB, GPIO_PinSource8);
18.void GPIO_ETH_MediaInterfaceConfig(uint32_t GPIO_ETH_MediaInterface)
功能:选择以太网媒体接口
对于复用功能
为了优化64脚或100脚封装的外设数目,可以把一些复用功能重新映射到其他引脚上。设置复用重映射和调试I/O配置寄存器(AFIO_MAPR)实现引脚的重新映射。这时,复用功能不再映射到它们的原始分配上。
可以去看STM32官方提供的文档,要用到的时候直接去查阅就好啦,一些外设的复用,记得开时钟打开复用功能。
LED灯实验
我们看一个LED灯的实验,LED也是我们使用开发板上的一个外设,也是通过GPIO控制输出高低电平,实现亮与灭。
我们此处用的是stm32f103vet6开发板,
这些 LED 灯的阴极都是连接到 STM32 的 GPIO 引脚,只要我们控制 GPIO 引脚的电平
输出状态,即可控制 LED 灯的亮灭。若您使用的实验板 LED 灯的连接方式或引脚不一样,
只需根据我们的工程修改引脚即可,程序的控制原理相同
bsp_led.h
// R-红色
#define LED1_GPIO_PORT GPIOB
#define LED1_GPIO_CLK RCC_APB2Periph_GPIOB
#define LED1_GPIO_PIN GPIO_Pin_5
// G-绿色
#define LED2_GPIO_PORT GPIOB
#define LED2_GPIO_CLK RCC_APB2Periph_GPIOB
#define LED2_GPIO_PIN GPIO_Pin_0
// B-蓝色
#define LED3_GPIO_PORT GPIOB
#define LED3_GPIO_CLK RCC_APB2Periph_GPIOB
#define LED3_GPIO_PIN GPIO_Pin_1
上代码分别把控制 LED 灯的 GPIO 端口、G
/* 直接操作寄存器的方法控制 IO */
#define digitalHi(p,i) {p->BSRR=i;} //输出为高电平
#define digitalLo(p,i) {p->BRR=i;} //输出低电平
#define digitalToggle(p,i) {p->ODR ^=i;} //输出反转状态
/* 定义控制 IO 的宏 */
#define LED1_TOGGLE digitalToggle(LED1_GPIO_PORT,LED1_GPIO_PIN)
#define LED1_OFF digitalHi(LED1_GPIO_PORT,LED1_GPIO_PIN)
#define LED1_ON digitalLo(LED1_GPIO_PORT,LED1_GPIO_PIN)
#define LED2_TOGGLE digitalToggle(LED2_GPIO_PORT,LED2_GPIO_PIN)
#define LED2_OFF digitalHi(LED2_GPIO_PORT,LED2_GPIO_PIN)
#define LED2_ON digitalLo(LED2_GPIO_PORT,LED2_GPIO_PIN)
#define LED3_TOGGLE digitalToggle(LED2_GPIO_PORT,LED3_GPIO_PIN)
#define LED3_OFF digitalHi(LED2_GPIO_PORT,LED3_GPIO_PIN)
#define LED3_ON digitalLo(LED2_GPIO_PORT,LED3_GPIO_PIN)
/* 基本混色,后面高级用法使用 PWM 可混出全彩颜色,且效果更好 */
//红
#define LED_RED \
LED1_ON;\
LED2_OFF\
LED3_OFF
//绿
#define LED_GREEN \
LED1_OFF;\
LED2_ON\
LED3_OFF
//蓝
#define LED_BLUE \
LED1_OFF;\
LED2_OFF\
LED3_ON
//黄(红+绿)
#define LED_YELLOW \
LED1_ON;\
LED2_ON\
LED3_OFF
//紫(红+蓝)
#define LED_PURPLE \
LED1_ON;\
LED2_OFF\
LED3_ON
//青(绿+蓝)
#define LED_CYAN \
LED1_OFF;\
LED2_ON\
LED3_ON
//白(红+绿+蓝)
#define LED_WHITE \
LED1_ON;\
LED2_ON\
LED3_ON
//黑(全部关闭)
#define LED_RGBOFF \
LED1_OFF;\
LED2_OFF\
LED3_OFF
bsp_led.c
void LED_GPIO_Config(void)
{
/*定义一个 GPIO_InitTypeDef 类型的结构体*/
GPIO_InitTypeDef GPIO_InitStructure;
/*开启 LED 相关的 GPIO 外设时钟*/
RCC_APB2PeriphClockCmd( LED1_GPIO_CLK|
LED2_GPIO_CLK|
LED3_GPIO_CLK, ENABLE);
/*选择要控制的 GPIO 引脚*/
GPIO_InitStructure.GPIO_Pin = LED1_GPIO_PIN;
/*设置引脚模式为通用推挽输出*/
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
/*设置引脚速率为 50MHz */
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
/*调用库函数,初始化 GPIO*/
GPIO_Init(LED1_GPIO_PORT, &GPIO_InitStructure);
/*选择要控制的 GPIO 引脚*/
GPIO_InitStructure.GPIO_Pin = LED2_GPIO_PIN;
/*调用库函数,初始化 GPIO*/
GPIO_Init(LED2_GPIO_PORT, &GPIO_InitStructure);
/*选择要控制的 GPIO 引脚*/
GPIO_InitStructure.GPIO_Pin = LED3_GPIO_PIN;
/*调用库函数,初始化 GPIOF*/
GPIO_Init(LED3_GPIO_PORT, &GPIO_InitStructure);
/* 关闭所有 led 灯 */
GPIO_SetBits(LED1_GPIO_PORT, LED1_GPIO_PIN);
/* 关闭所有 led 灯 */
GPIO_SetBits(LED2_GPIO_PORT, LED2_GPIO_PIN);
/* 关闭所有 led 灯 */
GPIO_SetBits(LED3_GPIO_PORT, LED3_GPIO_PIN);
}
主函数
#include "stm32f10x.h"
#include "./led/bsp_led.h"
#define SOFT_DELAY Delay(0x0FFFFF);
void Delay(__IO u32 nCount);
/**
* @brief 主函数
* @param 无
* @retval 无
*/
int main(void)
{
/* LED 端口初始化 */
LED_GPIO_Config();
while (1)
{
LED1_ON; // 亮
SOFT_DELAY;
LED1_OFF; // 灭
LED2_ON; // 亮
SOFT_DELAY;
LED2_OFF; // 灭
LED3_ON; // 亮
SOFT_DELAY;
LED3_OFF; // 灭
/*轮流显示 红绿蓝黄紫青白 颜色*/
LED_RED;
SOFT_DELAY;
LED_GREEN;
SOFT_DELAY;
LED_BLUE;
SOFT_DELAY;
LED_YELLOW;
SOFT_DELAY;
LED_PURPLE;
SOFT_DELAY;
LED_CYAN;
SOFT_DELAY;
LED_WHITE;
SOFT_DELAY;
LED_RGBOFF;
SOFT_DELAY;
}
}
void Delay(__IO uint32_t nCount) //简单的延时函数
{
for (; nCount != 0; nCount--);
}
把编译好的程序下载到开发板并复位,可看到 RGB 彩灯轮流显示不同的颜色。