您现在的位置是:首页 >技术杂谈 >GPIO输出——LED闪烁、LED流水灯、蜂鸣器网站首页技术杂谈

GPIO输出——LED闪烁、LED流水灯、蜂鸣器

向阳菌 2023-07-05 08:00:02
简介GPIO输出——LED闪烁、LED流水灯、蜂鸣器

1、STM32F1 GPIO 简介

GPIO ( General Purpose Input Output )通用输入输出口
可配置为 8 种输入输出模式
引脚电平: 0V~3.3V ,部分引脚可容忍 5V
输出模式下可控制端口输出高低电平,用以驱动 LED 、控制蜂鸣器、模拟通信协议输出时序等
输入模式下可读取端口的高低电平或电压,用于读取按键输入、外接模块电平信号输入、 ADC 电压采集、模拟通信协议接收数据等

GPIO 是控制或者采集外部器件的信息的外设,即负责输入输出。它按组分配,每组 16 个

IO 口,组数视芯片而定。STM32F103ZET6 芯片是 144 脚的芯片,具有 GPIOA、GPIOB、GPIOC、

GPIOD、GPIOE、GPIOF 和 GPIOG 七组 GPIO 口,共有 112 个 IO 口可供我们编程使用。这里 重点说一下 STM32F103 的 IO 电平兼容性问题,STM32F103 的绝大部分 IO 口,都兼容 5V, 至于到底哪些是兼容 5V 的,请看 STM32F103xE 的数据手册(注意是数据手册,不是中文参考 手册),见表 5 大容量 STM32F103xx 引脚定义,凡是有 FT 标志的,都是兼容 5V 电平的 IO 口, 可以直接接 5V 的外设(注意:如果引脚设置的是模拟输入模式,则不能接 5V!),凡是不带 FT

标志的,就建议大家不要接 5V 了,可能烧坏 MCU。 

2、GPIO基本结构

 GPIO中寄存器有32位,而端口只有16位,故寄存器只有低16位对应的有端口

驱动器用来增加信号的驱动能力

寄存器只用于存放数据 

3、GPIO 功能模式

GPIO 有八种工作模式,分别是:

1、输入浮空

2、输入上拉

3、输入下拉

4、模拟功能

5、开漏输出

6、推挽输出

7、开漏式复用功能

8、推挽式复用功能

 4、GPIO 寄存器介绍

STM32F1 每组(这里是 A~D)通用 GPIO 口有 7 个 32 位寄存器控制,包括 :

2 个 32 位端口配置寄存器(CRL 和 CRH)

2 个 32 位端口数据寄存器(IDR 和 ODR)

1 个 32 位端口置位/复位寄存器 (BSRR)

1 个 16 位端口复位寄存器(BRR)

1 个 32 位端口锁定寄存器 (LCKR)

(1)端口配置低高寄存器(GPIOx_CRL/GPIOx_CRH)(x=A…E)

​ 端口配置寄存器共16位,但每4位数据表示1位,共需要64位,而STM32中每个寄存器都为32位,因此分为端口配置低寄存器和端口配置高寄存器。通过端口配置寄存器可以配置GPIO工作模式与端口输出速度。

注意:输出速度可以限制输出引脚的最大翻转速度,作用是降低功耗、提高稳定性,一般情况下配置为50MHz。

(2)端口输入数据寄存器(GPIOx_IDR)(x=A…E)
​ 输入数据共16位,但寄存器共32位,因此寄存器高16位为空。

(3)端口输出数据寄存器(GPIOx_ODR)(x=A…E)
​ 输出数据共16位,但寄存器共32位,因此寄存器高16位为空。

(4)端口位设置/清除寄存器(GPIOx_BERR)(x=A…E)
​ 高16位用于位清除,低16位用于位设置。

高16位:为0不影响;为1清0;
低16为:为0不影响;为1置1。

(5)端口位清除寄存器(GPIOx_BER)(x=A…E)
​ 高16位为空,低16位用于清除,方法同上。

(6)端口位配置锁定寄存器(GPIOx_LCKR)(x=A…E)
​ 高15位为空,低17位用于锁定,较少使用。

五、GPIO常用函数

(1)RCC常用函数

​ 在RCC时钟控制的函数库中,我们最经常用到的是以下三个函数:

在函数的后面是三个总线可以设置的外设

//AHB系统总线时钟控制
void RCC_AHBPeriphClockCmd(uint32_t RCC_AHBPeriph, FunctionalState NewState);
//APB2总线时钟控制
void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState);
//APB1总线时钟控制
void RCC_APB1PeriphClockCmd(uint32_t RCC_APB1Periph, FunctionalState NewState);
/*第一个参数为外设选择,与STM32互联型的设备在下列列表中选择:
 *	   @arg RCC_AHBPeriph_DMA1
 *     @arg RCC_AHBPeriph_DMA2
 *     @arg RCC_AHBPeriph_SRAM
 *     @arg RCC_AHBPeriph_FLITF
 *     @arg RCC_AHBPeriph_CRC
 *     @arg RCC_AHBPeriph_OTG_FS    
 *     @arg RCC_AHBPeriph_ETH_MAC   
 *     @arg RCC_AHBPeriph_ETH_MAC_Tx
 *     @arg RCC_AHBPeriph_ETH_MAC_Rx
 *     

  
 *     @arg RCC_APB2Periph_AFIO, RCC_APB2Periph_GPIOA, RCC_APB2Periph_GPIOB,
 *          RCC_APB2Periph_GPIOC, RCC_APB2Periph_GPIOD, RCC_APB2Periph_GPIOE,
 *          RCC_APB2Periph_GPIOF, RCC_APB2Periph_GPIOG, RCC_APB2Periph_ADC1,
 *          RCC_APB2Periph_ADC2, RCC_APB2Periph_TIM1, RCC_APB2Periph_SPI1,
 *          RCC_APB2Periph_TIM8, RCC_APB2Periph_USART1, RCC_APB2Periph_ADC3,
 *          RCC_APB2Periph_TIM15, RCC_APB2Periph_TIM16, RCC_APB2Periph_TIM17,
 *          RCC_APB2Periph_TIM9, RCC_APB2Periph_TIM10, RCC_APB2Periph_TIM11  
 *
 *     @arg RCC_APB1Periph_TIM2, RCC_APB1Periph_TIM3, RCC_APB1Periph_TIM4,
 *          RCC_APB1Periph_TIM5, RCC_APB1Periph_TIM6, RCC_APB1Periph_TIM7,
 *          RCC_APB1Periph_WWDG, RCC_APB1Periph_SPI2, RCC_APB1Periph_SPI3,
 *          RCC_APB1Periph_USART2, RCC_APB1Periph_USART3, RCC_APB1Periph_USART4, 
 *          RCC_APB1Periph_USART5, RCC_APB1Periph_I2C1, RCC_APB1Periph_I2C2,
 *          RCC_APB1Periph_USB, RCC_APB1Periph_CAN1, RCC_APB1Periph_BKP,
 *          RCC_APB1Periph_PWR, RCC_APB1Periph_DAC, RCC_APB1Periph_CEC,
 *          RCC_APB1Periph_TIM12, RCC_APB1Periph_TIM13, RCC_APB1Periph_TIM14
 *
 *	   其他设备在下列列表中选择:
 *     @arg RCC_AHBPeriph_DMA1
 *     @arg RCC_AHBPeriph_DMA2
 *     @arg RCC_AHBPeriph_SRAM
 *     @arg RCC_AHBPeriph_FLITF
 *     @arg RCC_AHBPeriph_CRC
 *     @arg RCC_AHBPeriph_FSMC
 *     @arg RCC_AHBPeriph_SDIO
 *
 *可用按位或(|)来选择多个外设
 *
 *第二个参数为选择使能或失能,选择:ENABLE or DISABLE
 */

(2)通用IO口的函数库 

​ 在GPIO通用IO口的函数库中,我们最经常用到的是以下函数:

//复位函数,调用这个函数后,所指定的GPIO外设就会被复位
void GPIO_DeInit(GPIO_TypeDef* GPIOx);
//复位函数,可以复位AFIO外设
void GPIO_AFIODeInit(void);
//初始化函数,功能:用结构体的参数来初始化GPIO口
//初始化时,我们需要先定义一个结构体变量,然后给结构体赋值,最后调用初始化函数,函数内部会自动读取结构体的值,然后把外设的各个参数配置好。
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);
//功能:同时对16个端口进行写入操作
void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal);
/*参数注释:
 *①GPIO_TypeDef* GPIOx,GPIO端口选择,值为GPIOx(x=A~G);
 *可用按位或(|)来选择多个端口
 *
 *②uint16_t GPIO_Pin,IO口引脚选择,一个GPIO端口有16个引脚,所以其值为GPIO_Pin_x(x=0~15)
 *可用按位或(|)来选择多个引脚
 *
 *③BitAction BitVal,指定写入的数据值,这个参数可以是BitAction枚举中的一个值,值为:
 *	@arg Bit_RESET:清除端口值,即置低电平
 *  @arg Bit_SET:设置端口值,即置高电平
 *
 *④uint16_t PortVal,指定要写入端口输出数据寄存器的值
 *
 *⑤GPIO_InitTypeDef* GPIO_InitStruct,GPIO初始化结构体的地址
 */

/*初始化结构体定义:
 *①定义一个结构体变量
 *GPIO_InitTypeDef GPIO_InitStructure;
 *结构体如下:
 *typedef struct
 *{
 *  uint16_t GPIO_Pin;             
 *  GPIOSpeed_TypeDef GPIO_Speed;  
 *  GPIOMode_TypeDef GPIO_Mode;    
 *}GPIO_InitTypeDef;
 *由此可知该结构体有三个成员,分别为GPIO_Pin、GPIO_Speed和GPIO_Mode,下一步就是分别赋值
 *
 *②给结构体赋值:
 *GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //设置推挽输出
 *GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;		   //使用GPIO的0号引脚
 *GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//速度为50MHz
 *
 *GPIO_Mode,用于设置工作模式,其枚举如下:
 *typedef enum
 *{ GPIO_Mode_AIN = 0x0,			//模拟输入模式(Analog IN)
 *  GPIO_Mode_IN_FLOATING = 0x04,	//浮空输入模式
 *  GPIO_Mode_IPD = 0x28,			//下拉输入模式(In Pull Down)
 *  GPIO_Mode_IPU = 0x48,			//上拉输入模式(In Pull Up)
 *  GPIO_Mode_Out_OD = 0x14,		//开漏输出模式(Out Open Drain)
 *  GPIO_Mode_Out_PP = 0x10,		//推挽输出模式(Out Push Pull)
 *  GPIO_Mode_AF_OD = 0x1C,			//复用开漏模式(Atl Open Drain)
 *  GPIO_Mode_AF_PP = 0x18			//复用推挽模式(Atl Push Pull)
 *}GPIOMode_TypeDef;
 *
 *GPIO_Pin,选择引脚,值为GPIO_Pin_x(x=0~15和All)
 *
 *GPIO_Speed,选择输出速度,其枚举如下:
 *typedef enum
 *{ 
 *  GPIO_Speed_10MHz = 1,
 *  GPIO_Speed_2MHz, 
 *  GPIO_Speed_50MHz
 *}GPIOSpeed_TypeDef;
 *常用速度为50MHz
 */

六、主要程序

1、第一步:使用RCC初始化GPIO时钟

  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

GPIOA是位于APB2总线上的

请添加图片描述

 2、 第二步:使用GPIO_Init函数初始化GPIO

    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;//50兆hz的速度
    //用结构体的函数初始化GPIO口
    GPIO_Init(GPIOA, &GPIO_InitStructure);//第二个参数为指向结构体的指针,故传地址

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;

若要使用多个端口GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;

若要使用A/B/C/D的全部端口GPIO_InitStructure.GPIO_Pin = GPIO_Pin_All

(3)使用输出或输入的函数控制GPIO口

​ 调用输入的函数,GPIO_SetBits()、GPIO_ResetBits()和GPIO_WriteBit()均可单独设置引脚低电平,代码如下:

GPIO_ResetBits(GPIOA,GPIO_Pin_0);
Delay_ms(250);
GPIO_SetBits(GPIOA,GPIO_Pin_0);
Delay_ms(250);
		
GPIO_WriteBit(GPIOA,GPIO_Pin_0,Bit_RESET);
Delay_ms(250);
GPIO_WriteBit(GPIOA,GPIO_Pin_0,Bit_SET);
Delay_ms(250);
//通过强制转换为BitAction的枚举类型来直接输入高低电平
GPIO_WriteBit(GPIOA,GPIO_Pin_0,(BitAction)0);
Delay_ms(250);
GPIO_WriteBit(GPIOA,GPIO_Pin_0,(BitAction)1);
Delay_ms(250);

GPIO_WriteBit直接写1或0来设置电平的时候,需要将将数字强制转换成枚举类型BitAction

Bit_RESET和Bit_SET也是枚举类型BitAction

GPIO_Write(GPIOA, ~0x0040)表示将整个GPIOA寄存器进行配置,16位的数据

七、完整程序

1、点亮一个灯(低电平亮)(LED连接PA0口)

 

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
 
int main(void)
{
	//使用RCC初始化GPIO时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	//使用GPIO_Init函数初始化GPIO
	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;//50兆hz的速度
	//用结构体的函数初始化GPIO口
	GPIO_Init(GPIOA, &GPIO_InitStructure);//第二个参数为指向结构体的指针,故传地址
	
	while (1)
	{
		//使用输出或输入函数控制GPIO口
		GPIO_ResetBits(GPIOA, GPIO_Pin_0);//0
		Delay_ms(500);
		GPIO_SetBits(GPIOA, GPIO_Pin_0);//1
		Delay_ms(500);
		
		GPIO_WriteBit(GPIOA, GPIO_Pin_0, Bit_RESET);//0
		Delay_ms(500);
		GPIO_WriteBit(GPIOA, GPIO_Pin_0, Bit_SET);//1
		Delay_ms(500);
		
		
		GPIO_WriteBit(GPIOA, GPIO_Pin_0, (BitAction)0);
		Delay_ms(500);
		GPIO_WriteBit(GPIOA, GPIO_Pin_0, (BitAction)1);
		Delay_ms(500);
	}
}

2、流水灯(低电平亮)(LED连接PA0~PA6)

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
 
int main(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_All;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	while (1)
	{
		GPIO_Write(GPIOA, ~0x0001);	//0000 0000 0000 0001 低电平点亮
		Delay_ms(100);
		GPIO_Write(GPIOA, ~0x0002);	//0000 0000 0000 0010
		Delay_ms(100);
		GPIO_Write(GPIOA, ~0x0004);	//0000 0000 0000 0100
		Delay_ms(100);
		GPIO_Write(GPIOA, ~0x0008);	//0000 0000 0000 1000
		Delay_ms(100);
		GPIO_Write(GPIOA, ~0x0010);	//0000 0000 0001 0000
		Delay_ms(100);
		GPIO_Write(GPIOA, ~0x0020);	//0000 0000 0010 0000
		Delay_ms(100);
		GPIO_Write(GPIOA, ~0x0040);	//0000 0000 0100 0000
		Delay_ms(100);
		GPIO_Write(GPIOA, ~0x0080);	//0000 0000 1000 0000
		Delay_ms(100);
	}
}

3、蜂鸣器(低电平的时候响)(蜂鸣器接口为PB12

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
 
int main(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	while (1)
	{
		GPIO_ResetBits(GPIOB, GPIO_Pin_12);
		Delay_ms(100);
		GPIO_SetBits(GPIOB, GPIO_Pin_12);
		Delay_ms(100);
		GPIO_ResetBits(GPIOB, GPIO_Pin_12);
		Delay_ms(100);
		GPIO_SetBits(GPIOB, GPIO_Pin_12);
		Delay_ms(700);
	}
}

八、其他基础知识

1、A15、B3、B4三个端口默认是JTAG调试端口,如果要当作普通端口,要进行一些相关的配置

2、有源蜂鸣器:内部自带振荡器,频率固定

     无源蜂鸣器:不带振荡器,要提供震荡脉冲才能发生,可以发出不同频率的声音

3、推挽输出高低电平都有驱动能力

     开漏输出高电平相当于高阻态,没有驱动能力,低电平有驱动能力
 

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。