stm32—GPIO

0. 引入

在单片机产品中,我们常常可以见到三种模块:LED灯、KEY按键、BEEP蜂鸣器


LED灯:

        一个比较常见的LED电路

LED0 ---------- 通过控制LED0引脚(电线)

        给它一个低电平(低电压),LED1灯就会亮

        给它一个高电平(高电压),LED1灯就会灭

        

        1  —> 高电平

        0  —> 低电平

        电流:从电势高的地方流向电势低的地方


        CPU  ===> 往LED0引脚去 写1,写0

                "output" 输出功能

KEY按键:

        一个比较常见的KEY电路


KEY0  ------  通过读取KEY0引脚的电平状态来知晓用户是否按下按键

        高电平(1) ---> 弹起

        低电平(0) ---> 按下

        CPU  ===> 读取KEY0引脚的电平状态

                "input" 输入功能

BEEP蜂鸣器:

        一个比较常见的BEEP电路

        
BEEP ----------- 通过控制BEEP引脚(电线)

        给它一个高电平(高电压),BEEP就会响

        给它一个低电平(低电压),BEEP就不响

        

        CPU  ===> 往BEEP引脚去 写1,写0

                "output" 输出功能


两种三极管:PNP   and   NPN   ===> P指向N

这些引脚最终是接入到MCU的某个引脚(gpio)上去的
控制LED灯、KEY按键、BEEP蜂鸣器等,可以在MCU上面写程序去控制这些引脚

1. GPIO到底是什么?

GPIO:General Purpose Input Output 通用功能的输入输出 线
 

GPIO就是从芯片( CPU + 总线 + 外设控制器)内部引出一根功能复用的口线("电线"),可以由CPU配置成不同的功能

        如:输入功能,输出功能,其他复用功能等

芯片或CPU控制整个世界就是通过这样的引脚(口线,GPIO)

STM32F4xx共有144个GPIO口线(引脚,pin),分为9组,记为GPIOA,GPIOB,GPIOC,GPIOD,GPIOE,GPIOF,GPIOG,GPIOH,GPIOI. 每组管理16个GPIO引脚,编号从0~15

        如:GPIOA这一组有16个引脚,分别记为GPIOA0,GPIOA1,GPIOA2,... GPIOA15
                其他组类似

                GPIOA0  -----> PA0

                GPIOB3  ------> PB3

                ......

这些GPIO引脚都是功能复用的,并且由GPIO控制器来控制它们的

        所有的外设都是由"外设控制器"来控制

GPIO控制器由不同的寄存器来配置或控制它们(GPIOx) 

每组GPIO地址分配如下:

GPIOA  0x4002 0000 ~ 0x4002 03ff
GPIOB  0x4002 0400 ~ 0X4002 07FF
GPIOC  0x4002 0800 ~ 0x4002 0BFF
GPIOD  0x4002 0C00 ~ 0x4002 0FFF
GPIOE  0x4002 1000 ~ 0x4002 13FF
GPIOF  0x4002 1400 ~ 0x4002 17FF
GPIOG  0x4002 1800 ~ 0x4002 1BFF
GPIOH  0x4002 1C00 ~ 0x4002 1FFF
GPIOI  0x4002 2000 ~ 0x4002 23FF

2. STM32F4xx GPIO内部结构原理


每个GPIO内部都可以配置成:

        1. 输入功能:input mode

                CPU可以获取该GPIO口的外部输入的一个电平状态

                输入功能有四种模式:

                        (1) 输入悬空(input floating):不接上拉和下拉电阻

                                输入引脚处于浮空状态,‌即没有特定电压状态,‌引脚悬浮在空中

                                IO引脚的电平状态完全是外部输入所决定的,这时CPU能够通过读取数据的操作知道状态

                        (2) 带上拉输入(input pull-up):内部接上拉电阻

                                该引脚被设置为上拉输入时,引脚悬空的状态下,CPU读取到的电平状态为高电平,因为内部有一个上拉电阻;唯有当被外部输入信号下拉时,CPU读取到的电平才为低电平

                        (3) 带下拉输入(input pull-down): 内部接下拉电阻

                               该引脚被设置为下拉输入时,引脚悬空的状态下,CPU读取到的电平状态为低电平。唯有当被外部输入信号上拉时,CPU读取到的电平状态才为高电平

        

                        (4) 模拟输入(Input Analog)

                                该引脚被设置为模拟输入时,能够获取外部的模拟信号,通过芯片内的ADC转换为数字量,如变化的电压值


        2. 输出功能:output mode

                CPU可以往该GPIO口输出一个电平状态

                输入功能有两种模式:

                        (1) 输出推挽(Push-Pull):可以输出高、低电平

                                可以往外部引脚输出一个高电平(1)或低电平(0)

                                1:MOS管上方导通,下方不导通,在此处数字量变成模拟量,输出高电平

                                0:MOS管下方导通,上方不导通,在此处数字量变成模拟量,输出低电平

                        (2) 输出开漏(Open-Drain):不输出电压

                                低电平接地,高电平不接地(悬空状态)

                                如果外部电路接上拉电阻,则在CPU输出1时会接到外部上拉电阻的电源电压上

                                0:ping  接地

                                1:ping  悬空    此时需要外部电路中设计上拉电阻

        3. 复用功能:Alternate Function 

                复用功能是指GPIO口用作其它的功能口线,如: I^2C,UART,SPI等

                每个GPIO口都可以配置成多达16种复用功能,记为: AF0,AF1,AF2 ...  AF15,具体哪个GPIO口可以配置成哪种复用功能,需要看原理图

                其实在芯片设计的,每个GPIO口能够复用的功能,就已经定了

                UART0 ---> TX0 是不是任意一个GPIO口都可以复用这个Tx0这个引脚呢?

                        肯定不是啦,这个在芯片设计的时候,就已经定了
                

STM32F4xx每个GPIO口内部都有一个上拉/下拉电阻,你可以enable/disable它,根据具体应用场景需要

每个GPIO口的操作都是通过GPIO的寄存器来控制的

3. STM32F4xx GPIO寄存器说明

每个通用 I/O 端口包括:

    4 个 32 位配置寄存器

        (GPIOx_MODER、GPIOx_OTYPER、GPIOx_OSPEEDR 和 GPIOx_PUPDR)
    2 个 32 位数据寄存器(GPIOx_IDR 和 GPIOx_ODR)

    1 个 32 位置位/复位寄存器 (GPIOx_BSRR)
    1 个 32 位锁定寄存器 (GPIOx_LCKR) 
    2 个 32 位复用功能选择寄存器(GPIOx_AFRL 和 GPIOx_AFRH)


(1) GPIOx_MODER
        模式选择寄存器,该寄存器的地址偏移为 0x00
                地址偏移:每一组GPIO寄存器都有一个地址范围

                        如:GPIOA :  0x4002 0000 ~ 0x4002 03FF

                        GPIOA这一组的寄存器的基地址是0x4002 0000

                因此, GPIOA_MODER 的地址就是:0x4002 0000 + 0x00

        模式寄存器用来配置GPIO的功能的(input / output / analog(模拟) / AF)

        该寄存器用来控制x(x=A,B,C...,I)组的16个GPIO引脚的模式,每个GPIO口占2bits(总共有四种模式,四种功能)

        编号为y(y=0,1,2...,15)的GPIO引脚在该寄存器的bit位为GPIOx_MODER[2y+1:2y]

        具体配置如下:

                GPIOx_MODER[2y+1:2y]                     模式

                                00                                         输入模式

                                01                                        通用输出模式

                                10                                    复用功能,Alternate Function

                                11                                          模拟输入

(2) GPIOx_OTYPER

        输出类型寄存器,该寄存器的地址偏移为 0x04
        该寄存器用来控制x(x=A,B,C,...,I)分组的16个GPIO的输出类型,每个GPIO口1bit

        编号为y(y=0,1,2,...,15)的GPIO在该寄存器的bit位置为GPIOx_OTYPER[y]
        具体输出类型如下:

                GPIOx_OTYPER[y]                     输出类型

                              0                                输出推挽(Push-Pull)

                              1                                输出开漏(Open-Drain)

        注:

                输出推挽(Push-Pull),不带上下拉电阻    

                        cpu写1  ----> (外部引脚)高电平

                                  0  -----> (外部引脚)低电平
                输出开漏(Open-Drain),不带上下拉电阻

                        cpu写0  ----> (外部引脚)接地

                                  1  ----> (外部引脚)悬空


(3) GPIOx_OSPEEDR

        端口输出速度寄存器,偏移地址:0x08
        该寄存器用来控制x(x=A,B,C,...,I)分组的16个GPIO的输出速度,每个GPIO口2bits

        编号为y(y=0,1,2,...,15)的GPIO在该寄存器中的bit位置为GPIOx_OSPEEDRGPIO

[2y+1:2y]

        具体输出速度如下:

                                GPIOx_OSPEEDRGPIO[2y+1:2y]                          输出速度

                                                        00                                                     2Mhz(低速)

                                                        01                                                     25Mhz(中速)

                                                        10                                                     50Mhz (高速)

                                                        11                                                   30pf 时为 100Mhz

                                                                                                               15pf 时为80Mhz

(4) GPIOx_PUPDR

        pu: pull up  上拉

        pd: pull down  下拉

        端口上拉/下拉寄存器,地址偏移为:0x0C

        该寄存器用来控制x(x=A,B,C,..,I)分组的16个GPIO内部上下拉电阻的选择,每个GPIO口2bits

        编号为y(y=0,1,2..., 15)的GPIO口在该寄存器中的bit 位置为 GPIOx_PUPDR[2y+1:2y]

        具体选择情况如下:

              GPIOx_PUPDR[2y+1:2y]                         上下拉情况

                       00                                  无上拉也无下拉 disable pull-up  disable pull_down

                       01                                               上拉   enable pull-up

                       10                                               下拉   enable pull-down

                       11                                                    保留,没用到


(5) GPIOx_IDR

        Input Data Register  输入数据寄存器

        端口输入数据寄存器,偏移地址为: 0x10,复位值: 0x0000 XXXX (开机后高16位值为0,低16位的值不确定)

        该寄存器用来表示x(x=A,B,C,...,I)分组的16GPIO引脚输入的值,其中31:16保留
        高16bits,必须保持复位值
。bit 15:0 表示相应的GPIO引脚的输入电平的状态。这些bit,只能为只读,只能在字模式下访问

(6) GPIOx_ODR
        Output Data Register 输出数据寄存器

        端口输出数据寄存器,偏移地址为:0x14,复位值为: 0x0000 0000
        该寄存器用来表示x(x=A,B,C,...,I)分组的16个GPIO的输出值,其中31:16保留。
低位15:0 表示相应的GPIO口线的输出状态

        可以读取也可以写入


(7) GPIOx_BSRR

        BSR: Bit Set Reset  位置位复位操作
                Set ----> 把相应的bit位置1
                Reset -----> 把相应的bit位置0
        
端口置位/复位寄存器,偏移地址:0x18,复位值为:0x0000 0000
                位 31:16  BRx: Bit Reset 端口的复位bit位,这些位为只写形式,
只能在字、半字、或字节模式下访问

                        往相应的bit位写:

                                1: 对相应的ODR(x-16)位进行复位(0)

                                0: 不会对相应的ODR(x-16)执行任何操作

                位 15:0  BSx: Bit Set  端口x置位,这些位为只写形式,只能在字、半字、或字节模式下访问
                        往相应的bit位写:

                                1: 对相应的ODRx位进行置位(1)
                                0: 不会对相应的ODRx位进行操作


注意:如果同时对BSx和BRx置位,则BSx的优先级更高

(8) GPIOx_LCKR
        端口配置锁定寄存器,偏移地址为:0x1C,复位值为:0x0000 0000

        

        LOCK:

                当一个GPIO引脚的各配置寄存器设定好后,为了防止程序对这些已经配置好的寄存器的一些误操作,可以 LOCK一下

                如果是一个LOCK的状态,该GPIO引脚的配置就不能再修改了

                如果要修改就必须 UNLOCK

        

        位 31:17保留,必须保持复位值
        位 16 LCKK[16]:锁定键,可随时读取此位

    
(9) GPIOx_AFRL  偏移量  0x20

(10) GPIOx_AFRL   偏移量  0x24

        AFR:Alternate Function Register  复用功能寄存器
        

        每一个GPIO口可以复用多达16种复用功能,每一个GPIO口就需要4bits,一组GPIO口(16个GPIO口),就需要 16x4= 64bits

        所以复用功能寄存器就需要两个32bits的寄存器 GPIOx_AFRL,GPIOx_AFRH

        编号 0...7   -----> GPIOx_AFRL
        编号 8...15 -----> GPIOx_AFRH
                AF0     0000

                AF1     0001

                AF2     0010

                AF15   1111

                        AFx到底是复用何种功能,得看芯片手册        

4. GPIO寄存器点灯

注意:keil 的每个文件最后都必须空格一行

(1) 通过直接操作STM32f4xx的GPIO寄存器,点亮目标板上的LED1灯

        查看原理图:

                要把电路图的原理搞懂

                        LED0这个引脚

                                高电平 -----> led灯灭

                                低电平 -----> led灯亮

                看对应的引脚LED0对应的芯片(CPU)内部是哪个引脚:

                        PF9:GPIOF9

        步骤:

                (1) 使能GPIO分组的时钟   <-----  "时钟单元"  "上电"

                (2) 根据应用场景(原理图),配置GPIO

                        GPIOx_MODER

                        GPIOx_OTYPER

                        GPIOx_OSPEEDRGPIO

                        GPIOx_PUPDR

                (3) GPIOx_ODR

5. 使用stm32官方固件库来操作GPIO口

stm32固件库移植 -----> 具体移植操作参考文档

5.1 配置AHB1总线上的外设时钟   RCC_AHB1PeriphClockCmd

// 等同于RCC_AHB1ENR寄存器

void RCC_AHB1PeriphClockCmd(uint32_t RCC_AHB1Periph, 
                                FunctionalState NewState)

@RCC_AHB1Periph:指定AHB1总线外设,也就是要配置时钟的外设
    可以是以下任意一个宏:
        RCC_AHB1Periph_GPIOA
        RCC_AHB1Periph_GPIOB
        ...
        RCC_AHB1Periph_GPIOI
 
@NewState:指定该外设的时钟状态
    ENBALE 使能,为该外设提供时钟信号
     DISBALE 禁止,不提供
------------------------------------------------------------------
比如:使能GPIOF时钟 
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE);

5.2 初始化配置GPIO(完成GPIO的4个配置寄存器的赋值)     GPIO_init

void GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_InitStruct)

@GPIOx:指定要初始化的GPIO分组
            GPIOA
            GPIOB
            ...
            GPIOI
 
@GPIO_InitStruct:指向GPIO初始化信息结构体
    该结构体原型如下所示:(已经定义在头文件中,直接使用就好)
        typedef struct {
            uint32_t GPIO_Pin;         
            GPIOMode_TypeDef GPIO_Mode;     
            GPIOSpeed_TypeDef GPIO_Speed;  
            GPIOOType_TypeDef GPIO_OType;   
            GPIOPuPd_TypeDef GPIO_PuPd;   
       } GPIO_InitTypeDef;

        @GPIO_Pin:指定要配置的GPIO引脚(可以位或多个,表示配置同一组的多个引脚为相
同模式,如:GPIO_Pin_0 | GPIO_Pin_1) 
                GPIO_Pin_0
                GPIO_Pin_1
                ...
                GPIO_Pin_15

         @GPIO_Mode:指定要配置的GPIO引脚的功能模式
                GPIO_Mode_IN   输入模式
                GPIO_Mode_OUT  输出模式
                GPIO_Mode_AF   复用功能模式
                GPIO_Mode_AN   模拟模式
 
        @GPIO_Speed:指定引脚速率 
                GPIO_Speed_2MHz    2M低速
                GPIO_Speed_25MHz   25M中速
                GPIO_Speed_50MHz   50M快速
                GPIO_Speed_100MHz  100M高速
 
        @GPIO_OType:指定输出类型 
                GPIO_OType_PP   输出推挽
                GPIO_OType_OD   输出开漏
        
        @GPIO_PuPd:指定上下拉选择
                GPIO_PuPd_NOPULL   无上拉,也无下拉
                GPIO_PuPd_UP       上拉
                GPIO_PuPd_DOWN     下拉
---------------------------------------------------------------
比如:配置PF9为带下拉的推挽输出模式

// 定义GPIO初始化信息结构体
GPIO_InitTypeDef g;

// 根据配置需要,对结构体成员赋值
g.GPIO_Pin = GPIO_Pin_9; // 9号引脚
g.GPIO_Mode = GPIO_Mode_OUT; // 输出模式
g.GPIO_Speed = GPIO_Speed_50MHz; // 50MHz
g.GPIO_OType = GPIO_OType_PP; // 输出推挽
g.GPIO_PuPd = GPIO_PuPd_DOWN; // 下拉

// 根据结构体信息,完成GPIO配置
GPIO_Init(GPIOF, &g);                

5.3 获取指定GPIO分组输入数据寄存器中的值,并通过返回值返回  GPIO_ReadInputData

uint16_t GPIO_ReadInputData(GPIO_TypeDef *GPIOx)

@GPIOx:指定GPIO分组
        GPIOA
        GPIOB
        ...
        GPIOI
        
返回值:
    返回获取到的GPIO分组的输入数据寄存器中数据
    
要注意的是该返回值是uint16_t类型,具有16bits,分别对应该GPIO分组的16个GPIO引脚的输入
电平状态
    bit0  --> 0号引脚
    ...
    bit15 --> 15号引脚

5.4 获取指定GPIO引脚的输入值   GPIO_ReadInputDataBit

uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin)
    
@GPIOx:指定GPIO分组
            GPIOA
            GPIOB
            ...
            GPIOI
 
@GPIO_Pin:指定GPIO引脚
        GPIO_Pin_0
        ...
        GPIO_Pin_15

返回值:
    返回指定GPIO引脚的电平状态(1个引脚的状态)
        Bit_SET     表示该GPIO引脚输入为高电平
        Bit_RESET   表示该GPIO引脚输入为低电平

5.5 GPIO_SetBits / GPIO_ResetBits

GPIO_SetBits:用来将指定的GPIO引脚输出为高电平(GPIO_BSRR)
 
void GPIO_SetBits(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin)
-----------------------------------------------------------------
GPIO_ResetBits用来将指定的GPIO引脚输出低电平(GPIO_BSRR)

void GPIO_ResetBits(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin)

6. 固件库操作GPIO

BSP_led.h

#ifndef __BSP_LED_H__
#define __BSP_LED_H__

#include "stm32f4xx.h"

// 哪组寄存器
#define LED1_GPIO GPIOF
#define LED2_GPIO GPIOF
#define LED3_GPIO GPIOE
#define LED4_GPIO GPIOE

// 哪个引脚
#define LED1_Pin  GPIO_Pin_9
#define LED2_Pin  GPIO_Pin_10
#define LED3_Pin  GPIO_Pin_13
#define LED4_Pin  GPIO_Pin_14

// LED灯需要配置的时钟外设
#define LED1_RCC_AHB1Periph   RCC_AHB1Periph_GPIOF
#define LED2_RCC_AHB1Periph   RCC_AHB1Periph_GPIOF
#define LED3_RCC_AHB1Periph   RCC_AHB1Periph_GPIOE
#define LED4_RCC_AHB1Periph   RCC_AHB1Periph_GPIOE

// 使能时钟
#define LED1_Clock()   RCC_AHB1PeriphClockCmd(LED1_RCC_AHB1Periph, ENABLE)
#define LED2_Clock()   RCC_AHB1PeriphClockCmd(LED2_RCC_AHB1Periph, ENABLE)
#define LED3_Clock()   RCC_AHB1PeriphClockCmd(LED3_RCC_AHB1Periph, ENABLE)
#define LED4_Clock()   RCC_AHB1PeriphClockCmd(LED4_RCC_AHB1Periph, ENABLE)

// 开
#define LED1_ON()       GPIO_ResetBits(LED1_GPIO, LED1_Pin)
// 关
#define LED1_OFF()      GPIO_SetBits(LED1_GPIO, LED1_Pin)

#define LED2_ON()      GPIO_ResetBits(LED2_GPIO, LED2_Pin)
#define LED2_OFF()      GPIO_SetBits(LED2_GPIO, LED2_Pin)

#define LED3_ON()       GPIO_ResetBits(LED3_GPIO, LED3_Pin)
#define LED3_OFF()      GPIO_SetBits(LED3_GPIO, LED3_Pin)

#define LED4_ON()       GPIO_ResetBits(LED4_GPIO, LED4_Pin)
#define LED4_OFF()      GPIO_SetBits(LED4_GPIO, LED4_Pin)

void LED_Init(GPIO_TypeDef *LEDx, uint16_t GPIO_Pin_x);

#endif

BSP_led.c

#include "BSP_led.h"

void LED_Init(GPIO_TypeDef *LEDx, uint16_t GPIO_Pin_x) {
	
	// 2.初始化配置GPIO(完成GPIO的4个配置寄存器的赋值)

	// 2.1 定义GPIO初始化信息结构体
	GPIO_InitTypeDef g;
	
	// 2.2 根据配置需要,对结构体成员赋值
	g.GPIO_Pin = GPIO_Pin_x; // ????
	g.GPIO_Mode = GPIO_Mode_OUT; // ????
	g.GPIO_Speed = GPIO_Speed_2MHz; // 2MHz
	g.GPIO_OType = GPIO_OType_PP; // ????
	g.GPIO_PuPd = GPIO_PuPd_NOPULL; // ???????
	
	// 2.3 根据结构体信息,完成GPIO配置
	GPIO_Init(LEDx, &g);
}

(1) 点亮LED灯

#include "led.h"

int main() {

	/* 点亮LED1灯 */

	// 1. 使能时钟
	LED1_Clock();
	
	// 2. 初始化配置GPIO(完成GPIO的4个配置寄存器的赋值)
	LED_Init(LED1_GPIO, LED1_Pin);
	
	// 3. 将指定的GPIO引脚输出低电平(GPIO_BSRR) 开
	LED1_ON();

// --------------------------------------------------------
	
	/* 点亮LED2灯 */

	// 1. 使能时钟
	LED2_Clock();
	
	// 2. 初始化配置GPIO(完成GPIO的4个配置寄存器的赋值)
	LED_Init(LED2_GPIO, LED2_Pin);
	
	// 3. 将指定的GPIO引脚输出低电平(GPIO_BSRR) 开
	LED2_ON();
	
// ---------------------------------------------------------
	
	/* 点亮LED3灯 */

	// 1. 使能时钟
	LED3_Clock();
	
	// 2. 初始化配置GPIO(完成GPIO的4个配置寄存器的赋值)
	LED_Init(LED3_GPIO, LED3_Pin);
	
	// 3. 将指定的GPIO引脚输出低电平(GPIO_BSRR) 开
	LED3_ON();
	
// ---------------------------------------------------------

	/* 点亮LED4灯 */

	// 1. 使能时钟
	LED4_Clock();
	
	// 2. 初始化配置GPIO(完成GPIO的4个配置寄存器的赋值)
	LED_Init(LED4_GPIO, LED4_Pin);

	// 3. 将指定的GPIO引脚输出低电平(GPIO_BSRR) 开
	LED4_ON();

	while (1);
	
	return 0;
}

(2) 一个按键控制一个灯,按下就亮,松开就灭

BSP_key.h

#ifndef __BSP_KEY_H__
#define __BSP_KEY_H__

#include "stm32f4xx.h"

/*
	key1:PA0
	key2:PE2
	key3:PE3
	key3:PE4
*/
	

// 哪组寄存器
#define KEY1_GPIO  GPIOA
#define KEY2_GPIO  GPIOE
#define KEY3_GPIO  GPIOE
#define KEY4_GPIO  GPIOE

// 哪个引脚
#define KEY1_Pin  GPIO_Pin_0
#define KEY2_Pin  GPIO_Pin_2
#define KEY3_Pin  GPIO_Pin_3
#define KEY4_Pin  GPIO_Pin_4

// KEY按键需要配置的时钟外设
#define KEY1_RCC_AHB1Periph   RCC_AHB1Periph_GPIOA
#define KEY2_RCC_AHB1Periph   RCC_AHB1Periph_GPIOE
#define KEY3_RCC_AHB1Periph   RCC_AHB1Periph_GPIOE
#define KEY4_RCC_AHB1Periph   RCC_AHB1Periph_GPIOE

// 使能时钟
#define KEY1_Clock()   RCC_AHB1PeriphClockCmd(KEY1_RCC_AHB1Periph, ENABLE)
#define KEY2_Clock()   RCC_AHB1PeriphClockCmd(KEY2_RCC_AHB1Periph, ENABLE)
#define KEY3_Clock()   RCC_AHB1PeriphClockCmd(KEY3_RCC_AHB1Periph, ENABLE)
#define KEY4_Clock()   RCC_AHB1PeriphClockCmd(KEY4_RCC_AHB1Periph, ENABLE)

void KEY_Init(GPIO_TypeDef *KEYx, uint16_t GPIO_Pin_x);

/*
	高电平(1) ---> 弹起
	低电平(0) ---> 按下
*/
int KEY_ReadInputData(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin_x);

#endif

BSP_key.c

#include "BSP_key.h"

void KEY_Init(GPIO_TypeDef *LEDx, uint16_t GPIO_Pin_x) {
	
	// 2. 初始化配置GPIO(完成GPIO的4个配置寄存器的赋值)

	// 2.1 定义GPIO初始化信息结构体
	GPIO_InitTypeDef g;
	
	// 2.2 根据配置需要,对结构体成员赋值
	g.GPIO_Pin = GPIO_Pin_x; // 几号引脚
	g.GPIO_Mode = GPIO_Mode_IN; // 输入模式
	g.GPIO_PuPd = GPIO_PuPd_NOPULL; // 无上拉也无下拉
	
	// 2.3 根据结构体信息,完成GPIO配置
	GPIO_Init(LEDx, &g);
}

/*
	高电平(1) ---> 弹起
	低电平(0) ---> 按下
*/
int KEY_ReadInputData(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin_x) {
	
	if (GPIO_ReadInputDataBit(GPIOx, GPIO_Pin_x) == Bit_SET) {
		return 1;
	}
	
	return 0;
}
#include "stm32f4xx.h"

#include "BSP_led.h"
#include "BSP_key.h"

int main(void) {
	
	/* LED1灯 */
	// 1. 使能时钟
	LED1_Clock();
	
	// 2. 初始化配置GPIO(完成GPIO的4个配置寄存器的赋值)
	LED_Init(LED1_GPIO, LED1_Pin);
	
	// 3. 将指定的GPIO引脚输出高电平(GPIO_BSRR) 关
	LED1_OFF();
// --------------------------------------------------------
	/* LED2灯 */
	// 1. 使能时钟
	LED2_Clock();
	
	// 2. 初始化配置GPIO(完成GPIO的4个配置寄存器的赋值)
	LED_Init(LED2_GPIO, LED2_Pin);
	
	// 3. 将指定的GPIO引脚输出高电平(GPIO_BSRR) 关
	LED2_OFF();
// ---------------------------------------------------------
	/* LED3灯 */
	// 1. 使能时钟
	LED3_Clock();
	
	// 2. 初始化配置GPIO(完成GPIO的4个配置寄存器的赋值)
	LED_Init(LED3_GPIO, LED3_Pin);
	
	// 3. 将指定的GPIO引脚输出高电平(GPIO_BSRR) 关
	LED3_OFF();
// ---------------------------------------------------------
	/* LED4灯 */
	// 1. 使能时钟
	LED4_Clock();
	
	// 2. 初始化配置GPIO(完成GPIO的4个配置寄存器的赋值)
	LED_Init(LED4_GPIO, LED4_Pin);
	
	// 3. 将指定的GPIO引脚输出高电平(GPIO_BSRR) 关
	LED4_OFF();
// ---------------------------------------------------------
	/* KEY1 */
	// 1. 使能时钟
	KEY1_Clock();
	
	// 2. 初始化配置GPIO(完成GPIO的4个配置寄存器的赋值)
	KEY_Init(KEY1_GPIO, KEY1_Pin);
// ---------------------------------------------------------
	/* KEY2 */
	// 1. 使能时钟
	KEY2_Clock();
	
	// 2. 初始化配置GPIO(完成GPIO的4个配置寄存器的赋值)
	KEY_Init(KEY2_GPIO, KEY2_Pin);
// ---------------------------------------------------------
	/* KEY3 */
	// 1. 使能时钟
	KEY3_Clock();
	
	// 2. 初始化配置GPIO(完成GPIO的4个配置寄存器的赋值)
	KEY_Init(KEY3_GPIO, KEY3_Pin);
// ---------------------------------------------------------
	/* KEY4 */
	// 1. 使能时钟
	KEY4_Clock();
	
	// 2. 初始化配置GPIO(完成GPIO的4个配置寄存器的赋值)
	KEY_Init(KEY4_GPIO, KEY4_Pin);

	while (1) {
		// 1:弹起  0:按下
	
		// KEY1控制LED1
		if (KEY_ReadInputData(KEY1_GPIO, KEY1_Pin)) {
			// 弹起
			LED1_OFF();
		} else {
			// 按下
			LED1_ON();
		}
		
		// KEY2控制LED2
		if (KEY_ReadInputData(KEY2_GPIO, KEY2_Pin)) {
			// 弹起
			LED2_OFF();
		} else {
			// 按下
			LED2_ON();
		}
		
		// KEY3控制LED3
		if (KEY_ReadInputData(KEY3_GPIO, KEY3_Pin)) {
			// 弹起
			LED3_OFF();
		} else {
			// 按下
			LED3_ON();
		}
			
		// KEY4控制LED4
		if (KEY_ReadInputData(KEY4_GPIO, KEY4_Pin)) {
			// 弹起
			LED4_OFF();
		} else {
			// 按下
			LED4_ON();
		}
	}
}

(3) 一个按键控制一个灯, 按一下就亮,再按一下就灭

#include "stm32f4xx.h"

#include "BSP_led.h"
#include "BSP_key.h"

int main(void) {
	
	/* LED1灯 */
	// 1. 使能时钟
	LED1_Clock();
	
	// 2. 初始化配置GPIO(完成GPIO的4个配置寄存器的赋值)
	LED_Init(LED1_GPIO, LED1_Pin);
	
	// 3. 将指定的GPIO引脚输出高电平(GPIO_BSRR) 关
	LED1_OFF();
// --------------------------------------------------------
	/* LED2灯 */
	// 1. 使能时钟
	LED2_Clock();
	
	// 2. 初始化配置GPIO(完成GPIO的4个配置寄存器的赋值)
	LED_Init(LED2_GPIO, LED2_Pin);
	
	// 3. 将指定的GPIO引脚输出高电平(GPIO_BSRR) 关
	LED2_OFF();
// ---------------------------------------------------------
	/* LED3灯 */
	// 1. 使能时钟
	LED3_Clock();
	
	// 2. 初始化配置GPIO(完成GPIO的4个配置寄存器的赋值)
	LED_Init(LED3_GPIO, LED3_Pin);
	
	// 3. 将指定的GPIO引脚输出高电平(GPIO_BSRR) 关
	LED3_OFF();
// ---------------------------------------------------------
	/* LED4灯 */
	// 1. 使能时钟
	LED4_Clock();
	
	// 2. 初始化配置GPIO(完成GPIO的4个配置寄存器的赋值)
	LED_Init(LED4_GPIO, LED4_Pin);
	
	// 3. 将指定的GPIO引脚输出高电平(GPIO_BSRR) 关
	LED4_OFF();
// ---------------------------------------------------------
	/* KEY1 */
	// 1. 使能时钟
	KEY1_Clock();
	
	// 2. 初始化配置GPIO(完成GPIO的4个配置寄存器的赋值)
	KEY_Init(KEY1_GPIO, KEY1_Pin);
// ---------------------------------------------------------
	/* KEY2 */
	// 1. 使能时钟
	KEY2_Clock();
	
	// 2. 初始化配置GPIO(完成GPIO的4个配置寄存器的赋值)
	KEY_Init(KEY2_GPIO, KEY2_Pin);
// ---------------------------------------------------------
	/* KEY3 */
	// 1. 使能时钟
	KEY3_Clock();
	
	// 2. 初始化配置GPIO(完成GPIO的4个配置寄存器的赋值)
	KEY_Init(KEY3_GPIO, KEY3_Pin);
// ---------------------------------------------------------
	/* KEY4 */
	// 1. 使能时钟
	KEY4_Clock();
	
	// 2. 初始化配置GPIO(完成GPIO的4个配置寄存器的赋值)
	KEY_Init(KEY4_GPIO, KEY4_Pin);
	
	unsigned short KEY1_flag = 0;
	unsigned short KEY2_flag = 0;
	unsigned short KEY3_flag = 0;
	unsigned short KEY4_flag = 0;

	while (1) {
		// 1:弹起  0:按下

		// KEY1控制LED1
//		if (KEY_ReadInputData(KEY1_GPIO, KEY1_POS) == 0) {
//			// 消抖
//			while (KEY_ReadInputData(KEY1_GPIO, KEY1_POS) == 0);
//			KEY1_flag = ~KEY1_flag;
//			if (KEY1_flag != 0) {
//				LED1_ON();
//			} else {
//				LED1_OFF();
//			}
//		}
		
		if (KEY_ReadInputData(KEY1_GPIO, KEY1_Pin) == 0) {
			// 消抖
			while (KEY_ReadInputData(KEY1_GPIO, KEY1_Pin) == 0);
			KEY1_flag++;
		}
		if (KEY1_flag == 2) {
			KEY1_flag = 0;
			LED1_OFF();
		} else if (KEY1_flag == 1) {
			LED1_ON();
		}

		// KEY2控制LED2
		if (KEY_ReadInputData(KEY2_GPIO, KEY2_Pin) == 0) {
			// 消抖
			while (KEY_ReadInputData(KEY2_GPIO, KEY2_Pin) == 0);
			KEY2_flag++;
		}
		if (KEY2_flag == 2) {
			KEY2_flag = 0;
			LED2_OFF();
		} else if (KEY2_flag == 1) {
			LED2_ON();
		}
		
		// KEY3控制LED3
		if (KEY_ReadInputData(KEY3_GPIO, KEY3_Pin) == 0) {
			// 消抖
			while (KEY_ReadInputData(KEY3_GPIO, KEY3_Pin) == 0);
			KEY3_flag++;
		}
		if (KEY3_flag == 2) {
			KEY3_flag = 0;
			LED3_OFF();
		} else if (KEY3_flag == 1) {
			LED3_ON();
		}
			
		// KEY4控制LED4
		if (KEY_ReadInputData(KEY4_GPIO, KEY4_Pin) == 0) {
			// 消抖
			while (KEY_ReadInputData(KEY4_GPIO, KEY4_Pin) == 0);
			KEY4_flag++;
		}
		if (KEY4_flag == 2) {
			KEY4_flag = 0;
			LED4_OFF();
		} else if (KEY4_flag == 1) {
			LED4_ON();
		}
	}
}

(4) 通过按键 KEY1 控制蜂鸣器响或不响(BEEP)

BSP_beep.h

#ifndef __BSP_BEEP_H__
#define __BSP_BEEP_H__

#include "stm32f4xx.h"

// 哪组寄存器
#define BEEP_GPIO GPIOF

// 哪个引脚
#define BEEP_Pin  GPIO_Pin_8

// BEEP需要配置的时钟外设
#define BEEP_RCC_AHB1Periph   RCC_AHB1Periph_GPIOF

// 使能时钟
#define BEEP_Clock()   RCC_AHB1PeriphClockCmd(BEEP_RCC_AHB1Periph, ENABLE)

// 响
#define BEEP_ON()       GPIO_SetBits(BEEP_GPIO, BEEP_Pin)
// 不响
#define BEEP_OFF()      GPIO_ResetBits(BEEP_GPIO, BEEP_Pin)

void BEEP_Init(GPIO_TypeDef *BEEP, uint16_t GPIO_Pin_x);

#endif

BSP_beep.c

#include "BSP_beep.h"

void BEEP_Init(GPIO_TypeDef *BEEP, uint16_t GPIO_Pin_x) {
	
	// 2.初始化配置GPIO(完成GPIO的4个配置寄存器的赋值)

	// 2.1 定义GPIO初始化信息结构体
	GPIO_InitTypeDef g;
	
	// 2.2 根据配置需要,对结构体成员赋值
	g.GPIO_Pin = GPIO_Pin_x; // 哪个引脚
	g.GPIO_Mode = GPIO_Mode_OUT; // 输出模式
	g.GPIO_Speed = GPIO_Speed_2MHz; // 2MHz
	g.GPIO_OType = GPIO_OType_PP; // ????
	g.GPIO_PuPd = GPIO_PuPd_NOPULL; // ???????
	
	// 2.3 根据结构体信息,完成GPIO配置
	GPIO_Init(BEEP, &g);
}

main.c

#include "stm32f4xx.h"

#include "BSP_key.h"
#include "BSP_beep.h"

int main(void) {

	/* KEY1 */
	// 1. 使能时钟
	KEY1_Clock();
	
	// 2. 初始化配置GPIO(完成GPIO的4个配置寄存器的赋值)
	KEY_Init(KEY1_GPIO, KEY1_Pin);
	
	/* BEEP */
	// 1. 使能时钟
	BEEP_Clock();
	
	// 2. 初始化配置GPIO(完成GPIO的4个配置寄存器的赋值)
	BEEP_Init(BEEP_GPIO, BEEP_Pin);
	
	// 3. 将指定的GPIO引脚输出低电平(GPIO_BSRR) 不响
	BEEP_OFF();
	
	unsigned short BEEP_flag = 0;

	while (1) {
		// 1:弹起  0:按下

		// KEY1控制LED1
		if (KEY_ReadInputData(KEY1_GPIO, KEY1_Pin) == 0) {
			// 消抖
			while (KEY_ReadInputData(KEY1_GPIO, KEY1_Pin) == 0);
			BEEP_flag = ~BEEP_flag;
			if (BEEP_flag != 0) {
				BEEP_ON();
			} else {
				BEEP_OFF();
			}
		}
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值