STM32微控制器中的GPIO通用输入输出口详解

3 GPIO通用输入输出口

3.1 GPIO简介

  • GPIO(General Purpose Input Output)通用输入输出口;
  • 可配置为8种输入输出模式;
  • 引脚电平:0V~3.3V,部分引脚可容忍5V;
  • 但是输出最大只能输出3.3V,因为供电就只有3.3V;
  • 具体哪些端口可以容忍5V,可以参考STM32的引脚定义,带FT的就可以容忍5V;
  • 输出模式下可控制端口输出高低电平,用以驱动LED、控制蜂鸣器、模拟通信协议输出时序等;
  • 输入模式下可读取端口的高低电平或电压,用于读取按键输入、外接模块电平信号输入、ADC电压采集、模拟通信协议接收数据等。
  • 3.2 GPIO基本结构

  • 最左边的是APB2外设总线,在STM32中,所有的GPIO都是挂载在APB2外设总线上的;
  • GPIO的外设是按照GPIOA、GPIOB、GPIOC……这样的方式来命名的;
  • 每个GPIO外设有16个引脚,编号是0~15,第0号引脚称作PA0,1号引脚称作PA1……;
  • 在每个GPIO模块内,主要包含了寄存器和驱动器;
  • 寄存器:
  • 寄存器就是一段特殊的存储器,内核可以通过APB2总线对寄存器进行读写,这样就可以完成读取电平和输出电平的功能;
  • 寄存器的每一位对应一个引脚;
  • 输出寄存器写1,对应的引脚就会输出高电平,写0则输出低电平;输入寄存器读取为1,说明对应的端口目前是高电平,读取为0说明是低电平;
  • 因为STM32是32位的单片机,所以STM32内部的寄存器都是32位的。但是此处的端口(引脚)只有16位,所以这个寄存器只有低16位对应的有端口,高16位是没有用到的;
  • 驱动器:用来增强信号和驱动能力。
  • 3.3 GPIO位结构

  • 左边三个是寄存器,中间部分是驱动器,右边就是某个I/O口的引脚;
  • 整体结构可以分为两部分,上面是输入部分,下面是输出部分;
  • 输入部分:
  • I/O引脚处接了两个保护二极管,是对输入电压进行限幅的,上面的二极管接VDD,即3.3V,下面的二极管接VSS,即0V;
  • 若输入电压比3.3V还要高,那么上方的二极管就会导通,输入电压产生的电流就会直接流入VDD而不会流入内部电路,这样就可以避免过高的电压对内部电路造成损伤;
  • 若输入电压低于0V,这个电压是相当于VSS的电压的,所以是可以有负电压的,此时下面的二极管就会导通,电流会从VSS流出去,而不会从内部电路汲取电流,也是用于保护电路;
  • 若输入电压在0~3.3V之间,那么两个二极管均不会导通;
  • I/O引脚连接到了一个上拉电阻和下拉电阻,上拉电阻至VDD,下拉电阻至VSS,此处的两个开关是可以通过程序配置的;
  • 若上面导通,下面断开,就是上拉输入模式;
  • 若下面导通,上面断开,就是下拉输入模式;
  • 若两个都断开,就是浮空输入模式;
  • 上拉和下拉的作用就是为了给输入提供一个默认的输入电平。因为对于一个输入的端口,输入不是高电平就是低电平。那么如果输入引脚什么都不接,此时这种情况算是低电平还是高电平呢?这就不好说了。实际情况是:如果输入什么都不接,这时输入就会处于一种浮空的状态,引脚的输入电平极易受外界干扰而改变。为了避免引脚悬空导致的输入数据不稳定,就要给其加上上拉电阻或者下拉电阻;
  • 如果接入上拉电阻,当引脚悬空时,还有上拉电阻来保证引脚的高电平,所以上拉输入又可以称作是默认为高电平的输入模式。下拉同理,默认为低电平的输入模式;
  • 上拉电阻和下拉电阻的阻值是比较大的,是一种弱上拉和弱下拉,目的是尽量不影响正常的输入操作;
  • 继续往左是一个肖特基触发器(应该是斯密特触发器),作用是对输入电压进行整型,它的执行逻辑是:如果输入电压大于某一阈值,输出就会瞬间升为高电平;如果输出电压小于某一阈值,输出就会瞬间降为低电平;
  • 经过斯密特触发器整形的波形就可以直接写入输入数据寄存器了,我们再用程序读取输入数据寄存器对应某一位的数据,就可以知道端口的输入电平了;
  • 上面还有两路线路,是连接到片上外设的一些端口;
  • 模拟输入,连接到ADC上,因为ADC需要接收模拟量,所以这根线是接到斯密特触发器前面的;
  • 复用功能输入,连接到其它需要读取端口的外设,比如串口的输入引脚等,这根线接收的是数字量,所以接到斯密特触发器后面;
  • 输出部分:
  • 输出部分由输出数据寄存器或片上外设控制,两种控制方式通过数据选择器接到了输出控制部分;
  • 如果选择通过输出数据寄存器控制,就是普通的I/O口输出,写这个数据寄存器的某一位就可以操作对应的某一个端口的;
  • 左边还有一个位设置/清除寄存器,可以用来单独操作输出数据寄存器的某一位,而不影响其它位。因为这个输出数据寄存器同时控制16个端口,并且这个寄存器只能整体读写,所以如果想单独控制其中某一个端口而不影响其它端口的话,就需要一些特殊的操作方式;
  • 第一种方式:先读出这个寄存器,然后用按位与或按位或的方式更改某一位,再将更改后的数据写回去;
  • 麻烦,效率不高;
  • 第二种方法:通过设置这个位设置/清除寄存器;
  • 如果我们要对某一位进行置1的操作,在位设置寄存器的对应位写1即可,剩下不需要操作的位写0,这样它内部就会有电路自动将输出数据寄存器中对应位置1,而剩下写0的位则保持不变,这样就保证了只操作某一位而不影响其它位;
  • 如果我们要对某一位进行置0的操作,在位清除寄存器的对应位写1即可,这样内部电路就会把这一位清零;
  • 第三种方法:读写STM32中的“位带”区域
  • 这个“位带”的作用和51单片机中位寻址的作用差不多。在STM32中,有一段专门分配的地址区域,这段地址映射了RAM和外设寄存器所有的位,读写这段地址中的数据,就相当于读写锁映射地址的某一位;
  • 输出控制的右边接到了两个MOS管,上面是P-MOS,下面是N-MOS;
  • MOS管就是一种电子开关,我们的信号来控制开关的导通和关闭,开关负责将I/O口接到VDD或VSS;
  • 此处可以选择推挽、开漏或关闭三种输出方式;
  • 推挽模式下,P-MOS和N-MOS均有效;
  • 数据寄存器为1时,上管导通,下管断开,输出直接接到VDD,就是输出高电平;
  • 数据寄存器为0时,上管断开,下管导通,输出直接接到VSS,就是输出低电平;
  • 这种模式下,高低电平均有较强的驱动能力,所以推挽输出模式也称作强推输出模式;
  • 在推挽输出模式下,STM32对I/O口具有绝对的控制权,高低电平都由STM32说了算;
  • 开漏模式下,P-MO无效,只有N-MOS在工作;
  • 数据寄存器为1时,下管断开,这是输出相当于断开,也就是高阻模式;
  • 数据寄存器为0时,下管导通,输出直接接到VSS,也就是输出低电平;
  • 这个模式下,只有低电平有驱动能力,高电平无驱动能力;
  • 开漏模式可以作为通信协议的驱动方式,比如I2C总线的引脚,就是使用的开漏模式;
  • 在多机通信的情况下,这种模式可以避免各个设备的互相干扰;
  • 开漏模式还可以用于输出5V的电平信号。比如在I/O口外接一个上拉电阻到5V的电源,当输出低电平时,由内部的N-MOS直接接VSS;当输出高电平时,由外部的上拉电阻拉高至5V,这样就可以输出5V的电平信号,用于兼容一些5V电平的设备;
  • 关闭模式,当引脚配置为输入模式的时候,两个MOS管都无效,也就是输出关闭,端口的电平由外部信号控制。
  • 3.4 GPIO模式

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

    模式名称 性质 特征
    浮空输入(IN_FLOATING) 数字输入 可读取引脚电平,若引脚悬空,则电平不确定
    上拉输入(IPU,In Pull Up) 数字输入 可读取引脚电平,内部连接上拉电阻,悬空时默认高电平
    下拉输入(IPD,In Pull Down) 数字输入 可读取引脚电平,内部连接下拉电阻,悬空时默认低电平
    模拟输入(AIN,Analog IN) 模拟输入 GPIO无效,引脚直接接入内部ADC
    开漏输出(Out_OD,Out Open Drain) 数字输出 可输出引脚电平,高电平为高阻态,低电平接VSS
    推挽输出(Out_PP,Out Push Pull) 数字输出 可输出引脚电平,高电平接VDD,低电平接VSS
    复用开漏输出(AF_OD,Atl Open Drain) 数字输出 由片上外设控制,高电平为高阻态,低电平接VSS
    复用推挽输出(AF_PP,Atl Push Pull) 数字输出 由片上外设控制,高电平接VDD,低电平接VSS
  • 浮空/上拉/下拉输入:

  • 模拟输入:

  • 开漏/推挽输出:

  • 复用开漏/推挽输出:

  • 3.5 LED和蜂鸣器简介

  • LED:发光二极管,正向通电点亮,反向通电不亮;
  • 有源蜂鸣器:内部自带振荡源,将正负极接上直流电压即可持续发声,频率固定;
  • 无源蜂鸣器:内部不带振荡源,需要控制器提供振荡脉冲才可发声,调整提供振荡脉冲的频率,可发出不同频率的声音;
  • 硬件电路:
  • 左上是LED的低电平驱动电路,左下是LED的高电平驱动电路;
  • GPIO在推挽输出模式下,高低电平均有比较强的驱动能力,所以两种接法均可;
  • 但是在单片机的电路中,一般倾向于低电平驱动的接法。因为很多的单片机和芯片,都采用的是高电平弱驱动、低电平强驱动的规则,这样在一定程度上可以避免高低电平打架;
  • 右上是PNP三极管的驱动电路,三极管的左边是基极,带箭头的是发射极,剩下的是集电极。基极给低电平,三极管就会导通,蜂鸣器就有电流;基极给低电平,三极管截止,蜂鸣器就没有电流;
  • 右下是NPN三极管的驱动电路,驱动逻辑与上面的相反;
  • PNP三极管最好接在上面,NPN三极管最好接在下面。因为三极管的通断,是需要发射极和基极直接产生一定的开启电压的。如果把负载接在发射极这一边,可能会导致三极管不能开启;
  • 3.6 面包板

    3.7 LED闪烁

  • 新建System文件夹存放Delay函数相关代码;
  • Delay.h
    #ifndef __DELAY_H
    #define __DELAY_H
    
    void Delay_us(uint32_t us);
    void Delay_ms(uint32_t ms);
    void Delay_s(uint32_t s);
    
    #endif
    
  • Delay.c
    #include "stm32f10x.h"
    
    /**
      * @brief  微秒级延时
      * @param  xus 延时时长,范围:0~233015
      * @retval 无
      */
    void Delay_us(uint32_t xus)
    {
    	SysTick->LOAD = 72 * xus;				//设置定时器重装值
    	SysTick->VAL = 0x00;					//清空当前计数值
    	SysTick->CTRL = 0x00000005;				//设置时钟源为HCLK,启动定时器
    	while(!(SysTick->CTRL & 0x00010000));	//等待计数到0
    	SysTick->CTRL = 0x00000004;				//关闭定时器
    }
    
    /**
      * @brief  毫秒级延时
      * @param  xms 延时时长,范围:0~4294967295
      * @retval 无
      */
    void Delay_ms(uint32_t xms)
    {
    	while(xms--)
    	{
    		Delay_us(1000);
    	}
    }
     
    /**
      * @brief  秒级延时
      * @param  xs 延时时长,范围:0~4294967295
      * @retval 无
      */
    void Delay_s(uint32_t xs)
    {
    	while(xs--)
    	{
    		Delay_ms(1000);
    	}
    } 
    
  • mian.c
    #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_Mode_Out_OD,开漏输出模式,高电平无驱动能力,LED的接线方式就不可以短脚接负,长脚接引脚
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //输出引脚
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //输出速度
    	//第一个参数是选择哪一个GPIO,第二个参数是一个指向结构体的指针
    	GPIO_Init(GPIOA, &GPIO_InitStructure);
    	
    	while(1)
    	{
            //以下几种写法均可
            
    		//点亮LED
    		GPIO_ResetBits(GPIOA, GPIO_Pin_0);
    		Delay_ms(500);
    		//熄灭LED
    		GPIO_SetBits(GPIOA, GPIO_Pin_0);
    		Delay_ms(500);
    		
    		//点亮LED
    		GPIO_WriteBit(GPIOA, GPIO_Pin_0, Bit_RESET);
    		Delay_ms(500);
    		//熄灭LED
    		GPIO_WriteBit(GPIOA, GPIO_Pin_0, Bit_SET);
    		Delay_ms(500);
    		
    		//点亮LED
    		GPIO_WriteBit(GPIOA, GPIO_Pin_0, (BitAction)1);
    		Delay_ms(500);
    		//熄灭LED
    		GPIO_WriteBit(GPIOA, GPIO_Pin_0, (BitAction)0);
    		Delay_ms(500);
    	}
    }
    
  • 3.8 LED流水灯

  • mian.c
    #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; //输出引脚,此处配置成所有的总共16个引脚,也可以按照GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2的方式来选择多个引脚
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //输出速度
    	//第一个参数是选择哪一个GPIO,第二个参数是一个指向结构体的指针
    	GPIO_Init(GPIOA, &GPIO_InitStructure);
    	
    	while(1)
    	{
    		//第二个参数是指定写到输出数据寄存器(GPIO里的ODR寄存器)的值
    		GPIO_Write(GPIOA, ~0x0001); //0000 0000 0000 0001,对应PA0~PA15共16个端口。因为是低电平点亮,所以还要加一个按位取反的符号
    		Delay_ms(500);
    		
    		GPIO_Write(GPIOA, ~0x0002); //0000 0000 0000 0010
    		Delay_ms(500);
    		
    		GPIO_Write(GPIOA, ~0x0004); //0000 0000 0000 0100
    		Delay_ms(500);
    		
    		GPIO_Write(GPIOA, ~0x0008); //0000 0000 0000 1000
    		Delay_ms(500);
    		
    		GPIO_Write(GPIOA, ~0x0010); //0000 0000 0001 0000
    		Delay_ms(500);
    		
    		GPIO_Write(GPIOA, ~0x0020); //0000 0000 0010 0000
    		Delay_ms(500);
    		
    		GPIO_Write(GPIOA, ~0x0040); //0000 0000 0100 0000
    		Delay_ms(500);
    		
    		GPIO_Write(GPIOA, ~0x0080); //0000 0000 1000 0000
    		Delay_ms(500);
    	}
    }
    
  • 3.9 蜂鸣器

  • main.c
    #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,第二个参数是一个指向结构体的指针
    	GPIO_Init(GPIOB, &GPIO_InitStructure);
    	
    	while(1)
    	{
    		GPIO_ResetBits(GPIOB, GPIO_Pin_12);
    		Delay_ms(500);
    		GPIO_SetBits(GPIOB, GPIO_Pin_12);
    		Delay_ms(500);
    	}
    }
    
  • 3.10 按键简介

  • 按键:常见的输入设备,按下导通,松手断开;
  • 按键抖动:由于按键内部使用的是机械式弹簧片来进行通断的,所以在按下和松手的瞬间会伴随有一连串的抖动;
  • 3.11 传感器模块简介

  • 传感器模块:传感器元件(光敏电阻/热敏电阻/红外接收管等)的电阻会随外界模拟量的变化而变化,通过与定值电阻分压即可得到模拟电压输出,再通过电压比较器进行二值化即可得到数字电压输出;

  • 按键和传感器的硬件电路:
  • 左边4幅图是按键的硬件电路;上两图是下接按键的方式(常用),下两图是上接按键的方式;
  • 左上图。是按键的最常用接法。随便选择一个GPIO口,此处选择的是PA0,然后通过K1接地。当按键按下时,PA0直接被下拉到GND,此处读取PA0口的电压就是低电平。当按键松手时,PA0被悬空,就会导致引脚的电压不确定,为了避免出现这种情况,必须要求PA0是上拉输入的模式。如果PA0是上拉输入的模式,引脚悬空时,PA0就是高电平。所以这种方式下,按下按键引脚为低电平,松开按键引脚为高电平;
  • 右上图。相比较第一个图,多了一个上拉电阻。当按键松手时,引脚由于上拉作用,保持高电平。当按键按下时,引脚直接接到GND,引脚就为低电平。这种情况下,引脚不会出现悬空状态,所以PA0引脚配置为浮空输入或上拉输入。如果是上拉输入,那么就是和STM32内部的上拉电阻共同作用了,这时高电平就会更强一些,对应高电平就更加稳定,当然这样的话,当引脚被强行拉到低时,损耗也就会大一些;
  • 左下图。PA0通过按键接到3.3V,这种接线要求PA0配置成下拉输入的模式。当按键按下时,引脚为高电平,松手时,引脚默认低电平。这要求低电平的引脚可以配置成下拉输入的模式,一般单片机不一定有下拉输入的模式,所以最好还是用上面的接法;
  • 右下图。在左下图的基础上,外接一个下拉电阻,这个接法PA0需要配置成下拉输入模式或者浮空输入模式;
  • 总结:
  • 上面的接法是按键按下时引脚是低电平,松手是高电平;下面的接法是按键按下时是高电平,松手是低电平;
  • 左边的接法要求引脚上上拉或者下拉输入模式,右边的接法允许引脚是浮空输入的模式;
  • 右边的图是传感器的硬件电路;
  • 3.12 按键控制LED

  • 新建Hardware文件夹来存放硬件驱动相关代码;
  • 项目目录结构:
  • LED.h
    #ifndef __LED_H
    #define __LED_H
    
    void LED_Init(void);
    void LED1_ON(void);
    void LED1_OFF(void);
    void LED1_Turn(void);
    void LED2_ON(void);
    void LED2_OFF(void);
    void LED2_Turn(void);
    
    #endif
    
  • LED.c
    #include "stm32f10x.h"                  // Device header
    
    /**
      * 函    数:LED初始化
      * 参    数:无
      * 返 回 值:无
      */
    void LED_Init(void)
    {
    	/*开启时钟*/
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);		//开启GPIOA的时钟
    	
    	/*GPIO初始化*/
    	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);						//将PA1和PA2引脚初始化为推挽输出
    	
    	/*设置GPIO初始化后的默认电平,使LED灯熄灭*/
    	GPIO_SetBits(GPIOA, GPIO_Pin_1 | GPIO_Pin_2);				//设置PA1和PA2引脚为高电平
    }
    
    /**
      * 函    数:LED1开启
      * 参    数:无
      * 返 回 值:无
      */
    void LED1_ON(void)
    {
    	GPIO_ResetBits(GPIOA, GPIO_Pin_1);		//设置PA1引脚为低电平
    }
    
    /**
      * 函    数:LED1关闭
      * 参    数:无
      * 返 回 值:无
      */
    void LED1_OFF(void)
    {
    	GPIO_SetBits(GPIOA, GPIO_Pin_1);		//设置PA1引脚为高电平
    }
    
    /**
      * 函    数:LED1状态翻转
      * 参    数:无
      * 返 回 值:无
      */
    void LED1_Turn(void)
    {
    	if (GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_1) == 0)		//获取输出寄存器的状态,如果当前引脚输出低电平
    	{
    		GPIO_SetBits(GPIOA, GPIO_Pin_1);					//则设置PA1引脚为高电平
    	}
    	else													//否则,即当前引脚输出高电平
    	{
    		GPIO_ResetBits(GPIOA, GPIO_Pin_1);					//则设置PA1引脚为低电平
    	}
    }
    
    /**
      * 函    数:LED2开启
      * 参    数:无
      * 返 回 值:无
      */
    void LED2_ON(void)
    {
    	GPIO_ResetBits(GPIOA, GPIO_Pin_2);		//设置PA2引脚为低电平
    }
    
    /**
      * 函    数:LED2关闭
      * 参    数:无
      * 返 回 值:无
      */
    void LED2_OFF(void)
    {
    	GPIO_SetBits(GPIOA, GPIO_Pin_2);		//设置PA2引脚为高电平
    }
    
    /**
      * 函    数:LED2状态翻转
      * 参    数:无
      * 返 回 值:无
      */
    void LED2_Turn(void)
    {
    	if (GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_2) == 0)		//获取输出寄存器的状态,如果当前引脚输出低电平
    	{                                                  
    		GPIO_SetBits(GPIOA, GPIO_Pin_2);               		//则设置PA2引脚为高电平
    	}                                                  
    	else                                               		//否则,即当前引脚输出高电平
    	{                                                  
    		GPIO_ResetBits(GPIOA, GPIO_Pin_2);             		//则设置PA2引脚为低电平
    	}
    }
    
  • 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"
    
    /**
      * 函    数:按键初始化
      * 参    数:无
      * 返 回 值:无
      */
    void Key_Init(void)
    {
    	/*开启时钟*/
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);		//开启GPIOB的时钟
    	
    	/*GPIO初始化*/
    	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_Init(GPIOB, &GPIO_InitStructure);						//将PB1和PB11引脚初始化为上拉输入
    }
    
    /**
      * 函    数:按键获取键码
      * 参    数:无
      * 返 回 值:按下按键的键码值,范围:0~2,返回0代表没有按键按下
      * 注意事项:此函数是阻塞式操作,当按键按住不放时,函数会卡住,直到按键松手
      */
    uint8_t Key_GetNum(void)
    {
    	uint8_t KeyNum = 0;		//定义变量,默认键码值为0
    	
    	if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0)			//读PB1输入寄存器的状态,如果为0,则代表按键1按下
    	{
    		Delay_ms(20);											//延时消抖
    		while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0);	//等待按键松手
    		Delay_ms(20);											//延时消抖
    		KeyNum = 1;												//置键码为1
    	}
    	
    	if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0)			//读PB11输入寄存器的状态,如果为0,则代表按键2按下
    	{
    		Delay_ms(20);											//延时消抖
    		while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0);	//等待按键松手
    		Delay_ms(20);											//延时消抖
    		KeyNum = 2;												//置键码为2
    	}
    	
    	return KeyNum;			//返回键码值,如果没有按键按下,所有if都不成立,则键码为默认值0
    }
    
  • main.c
    #include "stm32f10x.h"                  // Device header
    #include "Delay.h"
    #include "LED.h"
    #include "Key.h"
    
    uint8_t KeyNum;		//定义用于接收按键键码的变量
    
    int main(void)
    {
    	/*模块初始化*/
    	LED_Init();		//LED初始化
    	Key_Init();		//按键初始化
    	
    	while (1)
    	{
    		KeyNum = Key_GetNum();		//获取按键键码
    		
    		if (KeyNum == 1)			//按键1按下
    		{
    			LED1_Turn();			//LED1翻转
    		}
    		
    		if (KeyNum == 2)			//按键2按下
    		{
    			LED2_Turn();			//LED2翻转
    		}
    	}
    }
    
    
  • 3.13 光敏传感器控制蜂鸣器

  • 项目结构:
  • Buzzer.h
    #ifndef __BUZZER_H
    #define __BUZZER_H
    
    void Buzzer_Init(void);
    void Buzzer_ON(void);
    void Buzzer_OFF(void);
    void Buzzer_Turn(void);
    
    #endif
    
  • Buzzer.C
    #include "stm32f10x.h"                  // Device header
    
    /**
      * 函    数:蜂鸣器初始化
      * 参    数:无
      * 返 回 值:无
      */
    void Buzzer_Init(void)
    {
    	/*开启时钟*/
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);		//开启GPIOB的时钟
    	
    	/*GPIO初始化*/
    	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);						//将PB12引脚初始化为推挽输出
    	
    	/*设置GPIO初始化后的默认电平*/
    	GPIO_SetBits(GPIOB, GPIO_Pin_12);							//设置PB12引脚为高电平
    }
    
    /**
      * 函    数:蜂鸣器开启
      * 参    数:无
      * 返 回 值:无
      */
    void Buzzer_ON(void)
    {
    	GPIO_ResetBits(GPIOB, GPIO_Pin_12);		//设置PB12引脚为低电平
    }
    
    /**
      * 函    数:蜂鸣器关闭
      * 参    数:无
      * 返 回 值:无
      */
    void Buzzer_OFF(void)
    {
    	GPIO_SetBits(GPIOB, GPIO_Pin_12);		//设置PB12引脚为高电平
    }
    
    /**
      * 函    数:蜂鸣器状态翻转
      * 参    数:无
      * 返 回 值:无
      */
    void Buzzer_Turn(void)
    {
    	if (GPIO_ReadOutputDataBit(GPIOB, GPIO_Pin_12) == 0)		//获取输出寄存器的状态,如果当前引脚输出低电平
    	{
    		GPIO_SetBits(GPIOB, GPIO_Pin_12);						//则设置PB12引脚为高电平
    	}
    	else														//否则,即当前引脚输出高电平
    	{
    		GPIO_ResetBits(GPIOB, GPIO_Pin_12);						//则设置PB12引脚为低电平
    	}
    }
    
  • LightSensor.h
    #ifndef __LIGHT_SENSOR_H
    #define __LIGHT_SENSOR_H
    
    void LightSensor_Init(void);
    uint8_t LightSensor_Get(void);
    
    #endif
    
  • LightSensor.c
    #include "stm32f10x.h"                  // Device header
    
    /**
      * 函    数:光敏传感器初始化
      * 参    数:无
      * 返 回 值:无
      */
    void LightSensor_Init(void)
    {
    	/*开启时钟*/
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);		//开启GPIOB的时钟
    	
    	/*GPIO初始化*/
    	GPIO_InitTypeDef GPIO_InitStructure;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_Init(GPIOB, &GPIO_InitStructure);						//将PB13引脚初始化为上拉输入
    }
    
    /**
      * 函    数:获取当前光敏传感器输出的高低电平
      * 参    数:无
      * 返 回 值:光敏传感器输出的高低电平,范围:0/1
      */
    uint8_t LightSensor_Get(void)
    {
    	return GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_13);			//返回PB13输入寄存器的状态
    }
    
  • main.c
    #include "stm32f10x.h"                  // Device header
    #include "Delay.h"
    #include "Buzzer.h"
    #include "LightSensor.h"
    
    int main(void)
    {
    	/*模块初始化*/
    	Buzzer_Init();			//蜂鸣器初始化
    	LightSensor_Init();		//光敏传感器初始化
    	
    	while (1)
    	{
    		if (LightSensor_Get() == 1)		//如果当前光敏输出1
    		{
    			Buzzer_ON();				//蜂鸣器开启
    		}
    		else							//否则
    		{
    			Buzzer_OFF();				//蜂鸣器关闭
    		}
    	}
    }
    
  • 作者:木木慕慕

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32微控制器中的GPIO通用输入输出口详解

    发表回复