(STM32学习笔记)GPIO通用输入输出

目录

一、GPIO 简介

二、GPIO的基本结构

三、GPIO的八种工作模式

3.1 浮空输入:GPIO_Mode_IN_FLOATING

3.2 上拉输入:GPIO_Mode_IPU

3.3 下拉输入:GPIO_Mode_IPD

 3.4 模拟输入:GPIO_Mode_AIN

3.5 开漏输出:GPIO_Mode_Out_OD 

3.6 推挽输出:GPIO_Mode_Out_PP

3.7 复用开漏输出:GPIO_Mode_AF_OD

3.8 复用推挽输出:GPIO_Mode_AF_PP

四、GPIO的使用 

4.1 常用函数

4.1.1 常用的RCC库函数

4.1.2 常用的GPIO库函数

4.2 操作步骤

4.3 GPIO输出函数和GPIO输入函数的使用

4.3.1 GPIO输出函数的应用 - LED闪烁

4.3.2 GPIO输入函数的应用 - 按键读取LED亮灭

五、个人疑惑 & 个人理解


一、GPIO 简介

GPIO (General Purpose Input Output) ,意为通用输入输出口,可配置为8种输入输出模式,引脚电平:0V~3.3V,部分引脚可容忍5V(容忍的意思是可以在这个端口输入5V 的电压,也认为是高电平,具体哪些端口可以容忍 5 V 需要查找STM32的引脚定义。在引脚定义中带FT,意为Five Tolerate,就是可以输入 5 V 的端口)

输出模式下可控制端口输出高低电平,用以驱动LED、控制蜂鸣器模拟通信协议输出时序等;

输入模式下可读取端口的高低电平或电压,用于读取按键输入、外接模块电平信号输入、ADC电压采集、模拟通信协议接收数据等。

二、GPIO的基本结构

      在所有的STM32中,所有的GPIO外设都挂载在APB2外设总线上。其中GPIO外设是按照GPIOA、GPIOB、GPIOC等命名的。
  每个GPIO外设有16个引脚。以GPIOA为例,它的引脚编号分别为PA0,PA1,PA2 … PA15。其他GPIO的引脚也是这样命名的。
  在GPIO模块内部,主要包含了寄存器驱动器两个模块。寄存器就是一段特殊的存储器,内核可以通过APB2总线对寄存器进行读写,以完成输出电平读取电平的功能。
  STM32是32位的单片机,其中的寄存器都是32位的。而端口只有16个,故寄存器只有低16位有对应的端口,高16位没有用到。
  驱动器的作用是增加信号的驱动能力。寄存器只负责存储数据,需要用驱动器来增大驱动能力。

三、GPIO的八种工作模式

通过配置GPIO的端口配置寄存器,端口可以配置成以下8种模式:

3.1 浮空输入:GPIO_Mode_IN_FLOATING

浮空输入(Floating Input):浮空输入模式是一种高阻抗输入模式。在该模式下,引脚不连接到外部电路,处于高阻抗状态。可以通过读取引脚电平来检测外部信号。

  • 特点:引脚处于高阻抗状态,未连接到外部电路,测量外部信号电平。
  • 应用场景:接收外部信号的状态,如按键输入、传感器输入等
3.2 上拉输入:GPIO_Mode_IPU

上拉输入(Pull-up Input):上拉输入模式是一种具有内部上拉电阻的GPIO输入模式。在该模式下,引脚连接到外部电路,通过内部上拉电阻来维持默认电平为高电平。

  • 特点:具有内部上拉电阻,引脚的默认电平为高电平。
  • 应用场景:检测外部信号为低电平时,例如按键按下。

3.3 下拉输入:GPIO_Mode_IPD

下拉输入(Pull-down Input):下拉输入模式是一种具有内部下拉电阻的GPIO输入模式。在该模式下,引脚连接到外部电路,通过内部下拉电阻来维持默认电平为低电平。

  • 特点:具有内部下拉电阻,引脚的默认电平为低电平。
  • 应用场景:检测外部信号为高电平时,例如按键抬起。
 
3.4 模拟输入:GPIO_Mode_AIN

模拟输入(Analog Input):模拟输入模式是一种用于ADC(模数转换器)输入的特殊模式。在该模式下,引脚可以接收连续变化的模拟信号。

  • 特点:用于接收连续变化的模拟信号,通常与ADC(模数转换器)配合使用。
  • 应用场景:测量传感器信号、音频输入等模拟信号的变化

3.5 开漏输出:GPIO_Mode_Out_OD 

开漏输出(Open-Drain Output):开漏输出模式是一种能够输出低电平和高阻抗的GPIO模式。在该模式下,引脚只能输出低电平,要输出高电平需要通过外部上拉电阻或其他方式。通常用于与外部器件连接,例如与开漏输出的I2C总线器件进行通信。

  • 特点:只能输出低电平,需要外部上拉电阻将引脚拉高;具有一定的驱动能力。
  • 应用场景:与外部器件连接时,如I2C总线,用于与其他设备进行通信。

3.6 推挽输出:GPIO_Mode_Out_PP

推挽输出(Push-Pull Output):推挽输出模式是最常见的GPIO输出模式。在该模式下,引脚可以输出高电平或低电平,同时具有一定的驱动能力。引脚在输出低电平时形成低阻抗,输出高电平时形成高阻抗,可以驱动外部电路。

  •  特点:可以输出高电平和低电平,具有一定的驱动能力。
  •   应用场景:用于驱动外部电路,如控制LED灯、驱动其他逻辑电路等。

3.7 复用开漏输出:GPIO_Mode_AF_OD

复用开漏输出(AF Open-Drain Output):复用开漏输出模式允许将GPIO引脚用作特定外设功能。在该模式下,引脚只能输出低电平,要输出高电平需要通过外部上拉电阻或其他方式。

  • 特点:具有开漏输出的特性,可用于将GPIO引脚用作特定外设的功能。
  • 应用场景:连接到外设的特殊功能引脚,如I2C总线通信引脚、故障信号输出等。

3.8 复用推挽输出:GPIO_Mode_AF_PP

复用推挽输出(AF Push-Pull Output):复用推挽输出模式允许将GPIO引脚用作特定外设功能。在该模式下,引脚可以输出高电平或低电平,并具有一定的驱动能力。

  • 特点:具有推挽输出的特性,可用于将GPIO引脚用作特定外设的功能。
  • 应用场景:连接到外设的特殊功能引脚,如UART串口通信引脚、PWM输出等。

四、GPIO的使用 

4.1 常用函数
4.1.1 常用的RCC库函数
void RCC_AHBPeriphClockCmd(uint32_t RCC_AHBPeriph, FunctionalState NewState);
void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState);
void RCC_APB1PeriphClockCmd(uint32_t RCC_APB1Periph, FunctionalState NewState);
4.1.2 常用的GPIO库函数

 GPIO外设有很多库函数,以下几个函数比较常用(GPIO_Init和8个读写函数最重要):

// 使指定的GPIO外设复位
void GPIO_DeInit(GPIO_TypeDef* GPIOx);

// 使指定的AFIO外设复位	
void GPIO_AFIODeInit(void);

// 用结构体的参数初始化GPIO口
// 基本上STM32的所有外设都使用Init函数进行初始化
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);

// 给结构体变量赋一个默认值
void GPIO_StructInit(GPIO_InitTypeDef* GPIO_InitStruct);

// 下面四个是GPIO的写入函数
uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx);
uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx);

// 下面四个是GPIO的输出函数
void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal);
void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal);	// 这个函数可以对16个端口同时进行写入操作
4.2 操作步骤

 操作STM32的GPIO输出一共需要三个步骤(下面步骤中设计RCC和GPIO两个外设):

  1. 使用RCC开启GPIO的时钟;
  2. 使用GPIO_Init函数初始化GPIO
  3. 使用输出或者输入函数控制GPIO口
4.3 GPIO输出函数和GPIO输入函数的使用
4.3.1 GPIO输出函数的应用 - LED闪烁

接线图和代码如下所示:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"

int main()
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	// 定义结构体变量,它是一个局部变量,有些过时的编译器不支持在程序中间定义局部变量,需要在最前面声明定义
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	
	// GPIO_SetBits(GPIOA, GPIO_Pin_0);		// 给相应端口设置高电平
	// GPIO_ResetBits(GPIOA, GPIO_Pin_0);	// 给相应端口设置低电平
	// GPIO_WriteBit(GPIOA, GPIO_Pin_0, Bit_RESET);	// 在相应端口写入数据 (Bit_RESET是一个枚举,它就是写入的数据,意为写入低电平)
	GPIO_WriteBit(GPIOA, GPIO_Pin_0, Bit_SET);	// 在相应端口写入数据,意为写入高电平
	while(1)
	{
		// 下面三种方式都可以实现LED闪烁
		GPIO_ResetBits(GPIOA, GPIO_Pin_0);
		Delay_ms(500);
		GPIO_SetBits(GPIOA, GPIO_Pin_0);
		Delay_ms(500);
		
		GPIO_WriteBit(GPIOA, GPIO_Pin_0, Bit_RESET);
		Delay_ms(500);
		GPIO_WriteBit(GPIOA, GPIO_Pin_0, Bit_SET);
		Delay_ms(500);
		
		GPIO_WriteBit(GPIOA, GPIO_Pin_0, (BitAction)0);	// 第三个参数直接写1,或者写0会报出一个警告,原因是1和0不是BitAction这个枚举类型,所以需要用到强制类型转换
		Delay_ms(500);
		GPIO_WriteBit(GPIOA, GPIO_Pin_0, (BitAction)1);
		Delay_ms(500);
	}
}

4.3.2 GPIO输入函数的应用 - 按键读取LED亮灭

接线图和代码如下所示:

  • LED.h
#ifndef __LED_H__
#define __LED_H__

void LED_Init(void);
void LED1_ON(void);
void LED1_OFF(void);
void LED2_ON(void);
void LED2_OFF(void);
void LED1_Turn(void);
void LED2_Turn(void);

#endif

  • LED.c
#include "stm32f10x.h"                  // Device header

/**
  * @brief  LED的初始化函数,将GPIOA配置为推挽输出模式
  * @param  无
  * @retval 无
  */
void LED_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	// 如果不加入以下操作,GPIO配置完成后默认为低电平
	
	GPIO_SetBits(GPIOA, GPIO_Pin_1 | GPIO_Pin_2);	// 默认将GPIO设置为高电平
}

/**
  * @brief  点亮LED1,将PA1置低电平
  * @param  无
  * @retval 无
  */
void LED1_ON(void)
{
	GPIO_ResetBits(GPIOA, GPIO_Pin_1);
}

/**
  * @brief  熄灭LED1,将PA1置高电平
  * @param  无
  * @retval 无
  */
void LED1_OFF(void)
{
	GPIO_SetBits(GPIOA, GPIO_Pin_1);
}

/**
  * @brief  点亮LED2,将PA2置低电平
  * @param  无
  * @retval 无
  */
void LED2_ON(void)
{
	GPIO_ResetBits(GPIOA, GPIO_Pin_2);
}

/**
  * @brief  熄灭LED2,将PA2置高电平
  * @param  无
  * @retval 无
  */
void LED2_OFF(void)
{
	GPIO_SetBits(GPIOA, GPIO_Pin_2);
}

/**
  * @brief  LED1的翻转函数,实现按一下点亮,再按一下熄灭
  * @param  无
  * @retval 无
  */
void LED1_Turn(void)
{
	if (GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_1) == 0)	// GPIOA处于输出模式,如果此时GPIOA_Pin_1输出0(LED1亮)
	{
		GPIO_SetBits(GPIOA, GPIO_Pin_1);	// 将灯熄灭
	}
	else	// 如果此时GPIOA_Pin_1输出为1(LED1灭)
	{
		GPIO_ResetBits(GPIOA, GPIO_Pin_1);	// 将灯点亮
	}
}

/**
  * @brief  LED2的翻转函数,实现按一下点亮,再按一下熄灭
  * @param  无
  * @retval 无
  */
void LED2_Turn(void)	// LED2的翻转函数,实现按一下点亮,再按一下熄灭
{
	if (GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_2) == 0)	// GPIOA处于输出模式,如果此时GPIOA_Pin_2输出0(LED2亮)
	{
		GPIO_SetBits(GPIOA, GPIO_Pin_2);	// 将灯熄灭
	}
	else	// 如果此时GPIOA_Pin_2输出为1(LED2灭)
	{
		GPIO_ResetBits(GPIOA, GPIO_Pin_2);	// 将灯点亮
	}
}

  • Key.h
#ifndef __KEY_H
#define __KEY_H

void Key_Init(void);
uint8_t Key_GetNum(void);

#endif
  • Key.c
#include "stm32f10x.h"                  // Device header
#include "Delay.h"

/**
  * @brief  按键初始化函数
  * @param  无
  * @retval 无
  */
void Key_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_11;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;	// 这里的速度是GPIO的输出速度,在输入模式下这个参数选择没有用处
	
	GPIO_Init(GPIOB, &GPIO_InitStructure);
}

/**
  * @brief  返回按下按键的值,若不按下按键默认返回0
  * @param  无
  * @retval KeyNum 按键对应的值
  */
uint8_t Key_GetNum(void)
{
	uint8_t KeyNum = 0;
	
	if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0)	// 读取1端口的值
	{
		Delay_ms(20);
		while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0);	// 如果不松手,程序将在此等待
		Delay_ms(20);
		KeyNum = 1;
	}
	if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0)
	{
		Delay_ms(20);
		while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0);	// 如果不松手,程序将在此等待
		Delay_ms(20);
		KeyNum = 2;
	}
	
	return KeyNum;
}

  • main.c
#include "stm32f10x.h"                  // Device header
#include "LED.h"
#include "Key.h"

uint8_t KeyNum;	// 全局变量,用于存储按键对应的值

int main()
{
	LED_Init();
	Key_Init();
	
	while(1)
	{
		KeyNum = Key_GetNum();
		if (KeyNum == 1)
		{
			LED1_Turn();
		}
		if (KeyNum == 2)
		{
			LED2_Turn();
		}
	}
}

五、个人疑惑 & 个人理解

问题:

为什么点亮LED灯用GPIO的输出函数读取按键用GPIO的输入函数

个人理解:

点亮LED灯,引脚接的是VCC(正极),给引脚输出高电平就可以点亮LED灯,所以GPIO口用的工作模式是推挽输出,所以输出控制LED亮灭的函数就用输出函数;

读取按键,按键的其中一个引脚接的是GND(负极),按键被按下时属于低电平状态,想要松开按键之后是高电平,就必须在芯片内部设置上拉输入模式,这样在按键松开时就是高电平状态,既然用的是上拉输入模式,那读取按键,用的函数就是ReadInput输入函数。

 推挽输出模式  --> 向连接的引脚输出高或低电平 。

 上拉输入模式 --> 将输入到引脚的电平读取出来 。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值