【STM32库函数】GPIO详解

1. GPIO简介

GPIO是通用输入输出引脚,它是STM32芯片上的数字接口,可以被软件配置为输入或输出,在连接外部设备时发挥重要作用。它们可以使用寄存器级操作进行配置和控制,因此具有灵活性和可编程性。

STM32F103ZET6 芯片是 144 脚的芯片,具有 GPIOA、GPIOB、GPIOC、GPIOD、GPIOE、GPIOF 和 GPIOG 七组 GPIO 口,共有 112 个 IO 口可供我们编程使用,其中37个是普通输入/输出口,75个是复用输入/输出口(当然不同的芯片引脚个数不同,但功能一致,具体可查看芯片手册)。这些GPIO口都可以根据需要被配置为数字输入(input)或数字输出(output)模式。在输入模式下,GPIO口接收外部设备产生的电信号,并将其转换为数字信号传递给处理器。在输出模式下,GPIO口将处理器输出的数字信号转换为电信号,并向外部设备发送。

每个GPIO口还可以根据应用需求选择不同的工作模式,例如中断输入模式、定时器输入捕获模式、PWM输出模式、模拟输入模式等。这些不同的工作模式使得GPIO口可以更好地满足不同的应用需求。

在工程实践中,GPIO引脚也经常使用一些特殊的电气特性,例如上拉电阻、下拉电阻、推挽输出、开漏输出等。这些特性可以为系统提供更多的灵活性和稳定性。例如,使用上拉电阻可以防止输入引脚的漂移,而使用推挽输出则可以提供比开漏输出更强的驱动能力。

总之,STM32的GPIO是一种非常重要的数字接口,用于连接各种外部设备。它们具有可编程性、灵活性和可靠性,并且在工业自动化、智能家居、汽车电子、医疗设备等领域中广泛应用。

2. GPIO的八种工作模式

GPIO的八种模式又分为四种输入模式和四种输出模式,硬件实现这里不做详细介绍,主要介绍八种模式功能及其对应的适用场景。

2.1 输入浮空模式(Floating Input Mode)

在该模式下,GPIO引脚处于高阻态且未选通上拉或下拉电阻。这种模式适用于数字输入信号不需要精度要求的场合,例如控制系统中一些状态量检测、接近传感器等的信号输入。但是,在使用输入浮空模式时,需要注意防止静电干扰和误操作。
举例:在一个控制系统中,检测两个机械部件之间距离的传感器,只需输出0或1两个状态值,可以使用输入浮空模式。

2.2 输入上拉模式(Input with Pull-up Mode)

在该模式下,GPIO引脚通过一个上拉电阻与VDD相连。当外部电路未连接时,引脚被拉向高电平。这种模式适用于需要稳定输入信号的场合,如按键开关、震动传感器等的信号输入。此模式适用于数字输入信号需要精度要求的场合。
举例:在嵌入式设备中,如果需要使用按键来进行控制,可以将GPIO引脚配置为输入上拉模式,并将按键连接到引脚和地之间,在此模式下,按键不被按下时,引脚处于高电平状态,当按键被按下时,引脚变为低电平状态。

2.3 输入下拉模式(Input with Pull-down Mode)

在该模式下,GPIO引脚通过一个下拉电阻与GND相连。当外部电路未连接时,引脚被拉向低电平。这种模式同样适用于需要稳定输入信号的场合,如按键开关、震动传感器等的信号输入。此模式同样适用于数字输入信号需要精度要求的场合。
举例:在嵌入式设备中,如果需要使用按键来进行控制,可以将GPIO引脚配置为输入下拉模式,并将按键连接到引脚和VDD之间,在此模式下,当按键不被按下时,引脚处于低电平状态,当按键被按下时,引脚变为高电平状态。

2.4 模拟输入模式(Analog Input Mode)

在该模式下,GPIO引脚被配置为模拟信号输入。ADC模块可以将引脚读取到的模拟信号转换成数字信号进行处理。这种模式适用于需要精确模拟量信号输入的场合,如温度传感器、光传感器等。
举例:在一个机器人系统中,需要使用光传感器来检测周围环境的亮度。可以将GPIO引脚配置为模拟输入模式,并将光传感器连接到引脚和地之间,此时ADC模块可以将读取到的模拟信号转换成数字信号进行处理。

2.5 开漏输出模式(Open-drain Output Mode)

在该模式下,GPIO引脚可以输出低电平信号,但输出高电平信号时变为高阻态。需搭配一个外部上拉电阻使引脚运作正确。开漏输出模式适用于驱动外部设备,如LED灯、数码管等。此模式适用于外部设备控制信号输出的场合。
举例:STM32使用I2C通讯时,SDA和SCL都要使用开漏输出模式。这是因为I2C通讯总线中,SDA和SCL线都可能出现多主机同时发送数据的情况,为避免数据冲突,需要将SDA和SCL配置成开漏输出模式,并且使用外部上拉电阻将它们拉升到高电平。

2.6 推挽输出模式(Push-pull Output Mode)

在该模式下,GPIO引脚可以输出高、低电平信号。推挽输出模式适用于直接驱动负载场合,如电机、继电器等的输出驱动。此模式适用于直接驱动负载的控制信号输出的场合。
举例:在一个机器人系统中,需要通过GPIO引脚来控制机器人的运动方向,例如前进、后退、左转、右转等。可以将GPIO引脚配置为推挽输出模式,并将引脚连接到电机驱动芯片,此时引脚可以直接驱动电机的正反转。

2.7 开漏式复用功能模式(Open-drain Alternate Function Mode)

在该模式下,GPIO引脚可以被配置为一些标准的或自定义的功能,例如I2C总线通讯的SCL时钟线。这种模式下,GPIO引脚被配置为开漏输出模式,适用于驱动外部设备或总线通讯。
举例:在一个机器人系统中,需要使用I2C总线来进行控制。可以将GPIO引脚配置为开漏式复用功能模式,并将SCL连接到引脚上。此时,引脚被配置为开漏输出模式,可以驱动I2C总线中的SCL时钟线。

2.8 推挽式复用功能模式(Push-pull Alternate Function Mode)

在该模式下,GPIO引脚可以被配置为一些标准的或自定义的功能,例如USART通信的TX、RX数据线。这种模式下,GPIO引脚被配置为推挽输出模式,适用于直接驱动负载或者数据通讯。
举例:在一个机器人系统中,需要使用串口通信来进行数据传输。可以将GPIO引脚配置为推挽式复用功能模式,并将TX、RX数据线连接到引脚上。此时,引脚被配置为推挽输出模式,可以直接驱动负载或进行数据通讯。

3. GPIO寄存器

GPIO对应的寄存器个数为7个,包括2 个 32位端口配置寄存器(CRL 和 CRH)、2 个 32 位端口数据寄存器(IDR 和 ODR)、1 个 32 位端口置位/复位寄存器(BSRR)、1 个 16 位端口复位寄存器(BRR)、1 个 32 位端口锁定寄存器 (LCKR)。下面将一一介绍。

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

作用:用来配置模式和端口的速度,可写可读。
CRH
在这里插入图片描述
CRL
在这里插入图片描述

每组GPIO有16个IO引脚,CRL 控制端口的低8个IO引脚, CRH 控制端口的高8个IO引脚,每个IO引脚由4个位控制。

  • 高2位为CNF配置IO口工作方式:
    在这里插入图片描述

  • 低2位为MODE配置IO口是输入/出和端口速度
    在这里插入图片描述

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

作用:用来控制IO口数据输出和检测IO口数据输入。
ODR(可写可读)
在这里插入图片描述

这里低16位输出为全1,对应IO口输出高电平;低16位输出为全0,对应IO口输出低电平。
IDR(只能读)
在这里插入图片描述

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

作用:用来控制IO口输出的高低电平,即ODR寄存器的低16位为全0还是全1,只能写不能读。
在这里插入图片描述

  • 高16位控制ODR寄存器低16位为全0还是保持:0为保持,1为设置为全0并且IO口输出低电平。
  • 低16位控制ODR寄存器低16位为全1还是保持:0为保持,1为设置为全1并且IO口输出高电平。

另外,当该寄存器为全1,那么就意味着置位和复位同时满足,是不成立的,规定此时置位满足。

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

作用:用来清除IO口的输出,高16位被保留,清除后低16位与BSRR低16位相同 (只能写)
在这里插入图片描述
将低16位全写1后,对应ODR寄存器的低16位变为0;将低16位全写1后,对对应的ODR寄存器的低16位不产生影响。

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

作用:用来控制0-15IO口锁定状态(可读可写)
在这里插入图片描述

高15位保留,始终为0,第16位控制某个IO口是否被锁,低16位为选定0-15的IO口,第16位实际上是控制CRL和CRH对应某个IO口对应的4个位是否能被修改。
第16位具体使用方式如下:
当LCKR寄存器的第16位被设置为1时,表示将要锁定相应的GPIO端口,此时需要向LCKR寄存器写入两次特定的数据来最终锁定该端口。具体步骤如下:

  • 将控制寄存器(CR寄存器)中的相应引脚配置为输入模式或输出模式,并设置所需的速度、推挽/开漏等其他参数。
  • 将LCKR寄存器的第16位设置为1以锁定该端口。
    将LCKR寄存器的第16位(即LCKK位)设置为1表示准备锁定相应的GPIO端口。此时GPIO端口还未被锁定,但已经被预备锁定。如果在锁定状态下尝试修改GPIO端口的配置,则会导致错误发生。
  • 向LCKR寄存器中写入任意值。 向LCKR寄存器中写入任意值,以便进行第4步的确认操作。
  • 将LCKR寄存器中的值反转后再次写入相同的值,确认锁定操作。
    在确认操作之前,需要反转LCKR寄存器中的值(即按位取反),然后再次向LCKR寄存器中写入相同的值。如果确认操作成功,GPIO端口将被锁定并且无法再被修改。

如果需要解除GPIO端口的锁定状态,则需要通过复位整个芯片或者读取LCKR寄存器来检查其状态,并执行相应的解锁操作。例如,可以使用特殊的序列来解锁GPIO端口:先将LCKR寄存器的第16位(LCKK位)清零,然后再向LCKR寄存器中写入任意值,最后再次向LCKR寄存器中写入相同的值即可解锁GPIO端口。

需要注意的是,在使用GPIO端口锁定功能时,只有部分引脚支持GPIO端口锁定功能,具体请参考芯片的数据手册或相关技术文档。

3.5 GPIO寄存器地址映象

在这里插入图片描述

4. GPIO库函数

GPIO的库函数声明都在"stm32f10x_gpio.h"文件中。

4.1 初始化函数

4.1.1 初始化GPIO_InitTypeDef结构体为默认值

函数声明如下:

void GPIO_StructInit(GPIO_InitTypeDef* GPIO_InitStruct);
  • GPIO_InitStruct:结构体的指针参数,用于对该结构体进行初始化。在调用该函数之前,必须先定义并分配内存空间给
    GPIO_InitTypeDef 结构体变量。该结构体是用于配置 GPIO
    端口的一种数据类型,包含了各种控制引脚模式、输入/输出方向、输出速度和上拉/下拉等属性的成员变量。

默认值包括:

  • 引脚GPIO_Pin:0
  • 速度GPIO_Speed: GPIO_Speed_2MHz。
  • 模式GPIO_Mode: GPIO_Mode_IN_FLOATING,浮空输入。

使用方式:将一个结构体设置为默认值:

// 定义一个GPIO_InitTypeDef类型的结构体变量gpio_init_structure
GPIO_InitTypeDef gpio_init_structure;

// 使用GPIO_StructInit()函数对gpio_init_structure结构体进行初始化
GPIO_StructInit(&gpio_init_structure);

将该结构体配置为默认值后,就可以用GPIO_Init函数对这个结构体中参数设置为指定值并映射到指定端口。

4.1.2 将指定GPIO端口配置为指定参数

函数声明如下:

void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);
  • GPIOx :一个指向 GPIO_TypeDef 类型结构体的指针,表示要被初始化的GPIO端口(如GPIOA、GPIOB等)。
  • GPIO_InitStruct:一个指向 GPIO_InitTypeDef类型结构体的指针,表示要应用于指定GPIO端口的配置信息。该结构体成员变量有GPIO引脚号,GPIO速度结构体,GPIO模式结构体。

在使用GPIO_Init函数时,我们需要先定义一个GPIO_InitTypeDef结构体并对其中的各个参数进行赋值或者使用GPIO_StructInit函数将定义的结构体设置为默认值,然后将其作为参数传递给GPIO_Init函数,以完成对GPIO口的初始化配置。
使用方法:初始化PB5口为推挽输出模式,速度为50MHz,无上下拉电阻,代码如下:

// 声明一个 GPIO_InitTypeDef 类型的结构体变量 GPIO_InitStruct
GPIO_InitTypeDef GPIO_InitStruct;

// 设置 GPIO_InitStruct 结构体中的 GPIO_Pin 成员变量为 GPIOB 的第 5 个引脚
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_5;

// 设置 GPIO_InitStruct 结构体中的 GPIO_Mode 成员变量为推挽输出模式
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;

// 设置 GPIO_InitStruct 结构体中的 GPIO_Speed 成员变量为 50MHz
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;

// 设置 GPIO_InitStruct 结构体中的 GPIO_OType 成员变量为推挽输出
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;

// 设置 GPIO_InitStruct 结构体中的 GPIO_PuPd 成员变量为无上下拉电阻
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;

// 调用 GPIO_Init 函数,将 GPIOB 第 5 个引脚的配置设置为 GPIO_InitStruct 中的值
GPIO_Init(GPIOB, &GPIO_InitStruct);

注意:在调用该函数之前,需要先使能所需的 GPIO 端口时钟。
在配置完 GPIO 引脚后,可以调用其他函数(如GPIO_SetBits()、GPIO_ResetBits()和GPIO_WriteBit())来设置或重置 GPIO 引脚的输出状态。

4.1.3 将指定的GPIO外设寄存器初始化为默认值

函数声明如下:

void GPIO_DeInit(GPIO_TypeDef* GPIOx)
  • GPIOx:要操作的GPIO端口

这个函数适用于需要对一个已经被初始化过的GPIO端口进行重置或释放的情况。
使用方法:将已经具体初始化的PB5口初始化恢复到默认状态

//
// 声明一个 GPIO_InitTypeDef 类型的结构体变量 GPIO_InitStruct
GPIO_InitTypeDef GPIO_InitStruct;

// 设置 GPIO_InitStruct 结构体中的 GPIO_Pin 成员变量为 GPIOB 的第 5 个引脚
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_5;

// 设置 GPIO_InitStruct 结构体中的 GPIO_Mode 成员变量为推挽输出模式
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;

// 设置 GPIO_InitStruct 结构体中的 GPIO_Speed 成员变量为 50MHz
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;

// 设置 GPIO_InitStruct 结构体中的 GPIO_OType 成员变量为推挽输出
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;

// 设置 GPIO_InitStruct 结构体中的 GPIO_PuPd 成员变量为无上下拉电阻
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;

// 调用 GPIO_Init 函数,将 GPIOB 第 5 个引脚的配置设置为 GPIO_InitStruct 中的值
GPIO_Init(GPIOB, &GPIO_InitStruct);

//恢复默认值
GPIO_DeInit(GPIOB);

4.2 设置输出电平函数

4.2.1 将指定引脚设置为指定电平

函数声明如下:

void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal);
  • GPIOx:GPIO 寄存器组,例如 GPIOA、GPIOB 等。
  • GPIO_Pin:要操作的 GPIO 引脚编号,可以使用宏定义 GPIO_Pin_x(x 表示引脚编号)来指定某个具体的引脚。
  • BitVal:要设置的 GPIO 引脚的输出值,可以是 Bit_SET 或 Bit_RESET。

使用方法:将 GPIOB 的第 5 个引脚设置为高电平:

GPIO_WriteBit(GPIOB, GPIO_Pin_5, Bit_SET);

注意:使用该函数前必须先初始化对应的 GPIO 引脚,通常使用 GPIO_Init 函数完成初始化。

4.2.2 将指定引脚设置为高电平

函数声明如下:

void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
  • GPIOx:要操作的GPIO端口,可以是GPIOA、GPIOB、GPIOC、GPIOD、GPIOE、GPIOF或GPIOG。
  • GPIO_Pin:要设置为高电平的引脚编号,取值范围为0-15。

使用方法:将PA5引脚设置为高电平:

GPIO_SetBits(GPIOA, GPIO_Pin_5);

4.2.3 将指定引脚设置为低电平

函数声明如下:

void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
  • GPIOx:要操作的GPIO端口,可以是GPIOA、GPIOB、GPIOC、GPIOD、GPIOE、GPIOF或GPIOG。
  • GPIO_Pin:要设置为低电平的引脚编号,取值范围为0-15。

使用方法:将PA5引脚设置为低电平:

GPIO_ResetBits(GPIOA, GPIO_Pin_5);

4.2.4 将指定端口所有引脚设置为指定值

函数声明如下:

void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal);
  • GPIOx:要操作的GPIO端口,可以是GPIOA、GPIOB、GPIOC、GPIOD、GPIOE、GPIOF或GPIOG。
  • PortVal:要输出的值,取值范围为0到FFFF(16位)。

使用方法:将PA0~PA7引脚全部设置为高电平:

GPIO_Write(GPIOA, 0xFF);

4.3 读取输入输出状态函数

4.3.1 读取指定GPIO端口和引脚号对应的输入状态

函数声明如下:

uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
  • GPIOx:GPIO端口号(例如GPIOA、GPIOB等)。
  • GPIO_Pin:引脚号(例如GPIO_Pin_0、GPIO_Pin_1等)。
  • 函数返回值为uint8_t类型。

实际使用时,要先对GPIO对应的端口和引脚配置为输入模式,然后才能正确读取其输入状态。
使用方法:读取PB2的输入状态:

// 配置PB2为输入模式
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOB, &GPIO_InitStructure);

// 读取PB2的输入状态
uint8_t input_status = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_2);

首先将PB2配置为上拉输入模式,然后调用GPIO_ReadInputDataBit函数读取PB2的输入状态,并将结果存储在input_status变量中。如果PB2为高电平,则input_status为1;如果PB2为低电平,则input_status为0。

4.3.2 读取指定GPIO端口的输入数据

函数声明如下:

uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx);
  • GPIOx:可以是A、B、C、D、E、F或G端口之一。
  • 返回值是一个16位的无符号整数,表示指定GPIO端口上各个引脚的电平状态(1为高电平,0为低电平)。

使用方法:读取GPIOB端口的输入数据,保存在16位变量input_data中:

GPIO_InitTypeDef GPIO_InitStructure; //定义GPIO初始化结构体
uint16_t input_data; //定义用于存储读取到的GPIO输入数据的变量

//配置GPIOB.5为输入引脚
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
  GPIO_Init(GPIOB, &GPIO_InitStructure);

  //读取GPIOB端口的输入数据
  input_data = GPIO_ReadInputData(GPIOB);

此时通过观察input_data变量的第五位二进制数可以得到PB5的引脚状态。

4.3.3 读取指定GPIO端口的指定引脚的输出状态

函数声明如下:

uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
  • GPIOx:要访问的GPIO端口。
  • GPIO_Pin:要读取状态的引脚。
  • 函数返回值为一个无符号8位整数(uint8_t),表示指定引脚的状态,如果该引脚为高电平,则返回1,否则返回0。

使用方法:读取PA0引脚的状态:

uint8_t PA0_state;

    // 将PA0配置为输入引脚
    GPIO_InitTypeDef GPIO_InitStruct;
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU; // 设置上拉输入模式
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz; // 速度设置为2MHz
    GPIO_Init(GPIOA, &GPIO_InitStruct);

    // 读取PA0的状态
    PA0_state = GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_0);

4.3.4 读取指定GPIO端口的输出数据

函数声明如下:

uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx);
  • GPIOx :指定要读取的GPIO端口。
  • 返回值是一个16位的无符号整数,表示指定GPIO端口上各个引脚的电平状态(1为高电平,0为低电平)。

使用方法:读取GPIOB端口的输出数据,保存在16位变量output_data中:

// 初始化GPIO模块并配置引脚
    GPIO_InitTypeDef GPIO_InitStructure;
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // 使能GPIOB时钟
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6; // 配置第5和第6引脚
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 配置为推挽输出模式
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 输出速度设置为50MHz
    GPIO_Init(GPIOB, &GPIO_InitStructure); // 应用上述配置

    // 写入输出数据
    GPIO_WriteBit(GPIOB, GPIO_Pin_5, Bit_SET); // 设置第5引脚输出高电平
    GPIO_WriteBit(GPIOB, GPIO_Pin_6, Bit_RESET); // 设置第6引脚输出低电平

    // 读取输出数据
    uint16_t output_data;
    output_data = GPIO_ReadOutputData(GPIOB); // 将GPIOB端口的输出数据读取到 output_data 变量中

设置GPIOB端口的第5引脚输出高电平,第6引脚输出低电平,此时output_data变量的第五位和第六位分别为1和0。

5. GPIO实验

代码实现在注释中已详细解释。

5.1 跑马灯实验

实现开发板上两个LED灯实现跑马灯的效果。
LED硬件电路如下:
在这里插入图片描述
根据硬件电路图知,两个LED灯对应的IO引脚分别为PortB5,PortB5,当两个引脚为低电平时,二极管导通;当两个引脚为高电平时,二极管截止,因此对这两个IO引脚设置高低电平变化即可。
led.h文件内容如下:

#ifndef __LED_H
#define __LED_H	 
#include "sys.h"

#define LED0_OFF GPIO_SetBits(GPIOB, GPIO_Pin_5)		// PB5灭
#define LED0_ON GPIO_ResetBits(GPIOB, GPIO_Pin_5)		// PB5亮

#define LED1_OFF GPIO_SetBits(GPIOE, GPIO_Pin_5)		// PE5灭	
#define LED1_ON GPIO_ResetBits(GPIOE, GPIO_Pin_5)		// PE5亮	

void LED_Init(void);//初始化
		 				    
#endif

led.c文件内容如下:

#include "led.h"

//初始化PB5和PE5为输出口.并使能这两个口的时钟		    
//LED IO初始化
void LED_Init(void)
{
 
 GPIO_InitTypeDef  GPIO_InitStructure;
 	
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOE, ENABLE);	 //使能PB,PE端口时钟
	
 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;				 //LED0-->PB.5 端口配置
 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 		 //推挽输出
 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		 //IO口速度为50MHz
 GPIO_Init(GPIOB, &GPIO_InitStructure);					 //根据设定参数初始化GPIOB.5
 GPIO_SetBits(GPIOB,GPIO_Pin_5);						 //PB.5 输出高

 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;	    		 //LED1-->PE.5 端口配置, 推挽输出
 GPIO_Init(GPIOE, &GPIO_InitStructure);	  				 //推挽输出 ,IO口速度为50MHz
 GPIO_SetBits(GPIOE,GPIO_Pin_5); 						 //PE.5 输出高 
}

main.c文件内容如下:

#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"

 int main(void)
 {	
	delay_init();	    //延时函数初始化	  
	LED_Init();		  	//初始化与LED连接的硬件接口
	while(1)
	{
		LED0_OFF;
		LED1_ON;
		delay_ms(300);	 //延时300ms
		LED0_ON;
		LED1_OFF;
		delay_ms(300);	//延时300ms
	}
 }

5.2 蜂鸣器实验

实现开发板上蜂鸣器发声,并用一个指示灯LED0指示蜂鸣器正在发声。
蜂鸣器硬件电路如下:
在这里插入图片描述
根据硬件电路图可知,当 Port8 输出高电平的时候,蜂鸣器将发声, 当 Port8 输出低电平的时候,蜂鸣器停止发声。因此对该IO引脚设置高低电平变化即可。
beep.h文件内容如下:

#ifndef __BEEP_H
#define __BEEP_H	 
#include "sys.h"

#define LED0_OFF GPIO_SetBits(GPIOB, GPIO_Pin_5)            // PB5灭
#define LED0_ON GPIO_ResetBits(GPIOB, GPIO_Pin_5)           // PB5亮

//蜂鸣器端口定义
#define BEEP_ON GPIO_SetBits(GPIOB, GPIO_Pin_8)	            // BEEP发声		   
#define BEEP_OFF GPIO_ResetBits(GPIOB, GPIO_Pin_8)	        // BEEP停止发声		   

void BEEP_Init(void);	//蜂鸣器初始化
void LED_Init(void);    //LEDO初始化
		 				    
#endif

led.c文件内容如下:

#include "beep.h"
//初始化PB8为输出口.并使能这个口的时钟		    
//蜂鸣器初始化
void BEEP_Init(void)
{
 
 GPIO_InitTypeDef  GPIO_InitStructure;
 	
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);	 //使能GPIOB端口时钟
 
 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;				 //BEEP-->PB.8 端口配置
 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 		 //推挽输出
 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;	 //速度为50MHz
 GPIO_Init(GPIOB, &GPIO_InitStructure);	 //根据参数初始化GPIOB.8
 
 GPIO_ResetBits(GPIOB,GPIO_Pin_8);//输出0,关闭蜂鸣器输出

}

void LED_Init(void)
{
 
 GPIO_InitTypeDef  GPIO_InitStructure;
 	
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);	 //使能PB端口时钟
	
 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;				 //LED0-->PB.5 端口配置
 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 		 //推挽输出
 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		 //IO口速度为50MHz
 GPIO_Init(GPIOB, &GPIO_InitStructure);					 //根据设定参数初始化GPIOB.5
 GPIO_SetBits(GPIOB,GPIO_Pin_5);						 //PB.5 输出高
}

main.c文件内容如下:

#include "sys.h"	
#include "delay.h"	
#include "beep.h" 

 int main(void)
 {
	delay_init();	    	 //延时函数初始化	  
	LED_Init();		  	 	 //初始化与LED连接的硬件接口
	BEEP_Init();         	 //初始化蜂鸣器端口
	while(1)
	{
		LED0_ON;
		BEEP_ON;		  
		delay_ms(300);//延时300ms
		LED0_OFF;	  
		BEEP_OFF;  
		delay_ms(300);//延时300ms
	}
 }

5.3 按键输入实验

使用开发板上三个按键控制两LED灯亮灭和一个蜂鸣器发声。
按键硬件电路如下:
在这里插入图片描述
根据硬件电路图可知,当WK_UP为高电平时,按键按下才有效;当KEY0和KEY1为低电平时,按键按下才有效。并且外部都没有上下拉电阻,所以,需要在内部设置上下拉。
led.h文件内容如下:

#ifndef __LED_H
#define __LED_H	 
#include "sys.h"
//LED电平翻转
//这里Bit_RESET实际值为0,类型为BitAction结构体,只需用1-GPIO_ReadOutputDataBit函数读到的电平值即可
//当GPIO_ReadOutputDataBit读到高电平返回值为1,那么1-GPIO_ReadOutputDataBit为0对应即为Bit_RESET,再用GPIO_WriteBit即可实现电平翻转
//当GPIO_ReadOutputDataBit读到低电平返回值为0,那么1-GPIO_ReadOutputDataBit为1对应即为Bit_SET,再用GPIO_WriteBit即可实现电平翻转
#define _LED0_ GPIO_WriteBit(GPIOB, GPIO_Pin_5, (BitAction)(1 - GPIO_ReadOutputDataBit(GPIOB, GPIO_Pin_5)))// PB5
#define _LED1_ GPIO_WriteBit(GPIOE, GPIO_Pin_5, (BitAction)(1 - GPIO_ReadOutputDataBit(GPIOE, GPIO_Pin_5)))// PE5	

void LED_Init(void);//初始化
		 				    
#endif

led.c文件内容如下:

#include "led.h"  

//初始化PB5和PE5为输出口.并使能这两个口的时钟		    
//LED IO初始化
void LED_Init(void)
{
 
 GPIO_InitTypeDef  GPIO_InitStructure;
 	
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOE, ENABLE);	 //使能PB,PE端口时钟
	
 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;				 //LED0-->PB.5 端口配置
 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 		 //推挽输出
 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		 //IO口速度为50MHz
 GPIO_Init(GPIOB, &GPIO_InitStructure);					 //根据设定参数初始化GPIOB.5
 GPIO_SetBits(GPIOB,GPIO_Pin_5);						 //PB.5 输出高

 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;	    		 //LED1-->PE.5 端口配置, 推挽输出
 GPIO_Init(GPIOE, &GPIO_InitStructure);	  				 //推挽输出 ,IO口速度为50MHz
 GPIO_SetBits(GPIOE,GPIO_Pin_5); 						 //PE.5 输出高 
}

beep.h文件内容如下:

#ifndef __BEEP_H
#define __BEEP_H	 
#include "sys.h"
//蜂鸣器电平翻转,与led电平翻转相同
#define _beep_ GPIO_WriteBit(GPIOB, GPIO_Pin_8, (BitAction)(1 - GPIO_ReadOutputDataBit(GPIOB, GPIO_Pin_8)))

void BEEP_Init(void);	//初始化
		 				    
#endif

beep.c文件内容如下:

#include "beep.h"
//初始化PB8为输出口.并使能这个口的时钟		    
//蜂鸣器初始化
void BEEP_Init(void)
{
 
 GPIO_InitTypeDef  GPIO_InitStructure;
 	
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);	 //使能GPIOB端口时钟
 
 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;				 //BEEP-->PB.8 端口配置
 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 		 //推挽输出
 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;	 //速度为50MHz
 GPIO_Init(GPIOB, &GPIO_InitStructure);	 //根据参数初始化GPIOB.8
 
 GPIO_ResetBits(GPIOB,GPIO_Pin_8);//输出0,关闭蜂鸣器输出
}

key.h文件内容如下:

#ifndef __KEY_H
#define __KEY_H	 
#include "sys.h"

#define KEY0  GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_4)//读取按键0
#define KEY1  GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_3)//读取按键1
#define WK_UP   GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)//读取按键3(WK_UP) 

#define KEY0_PRES 	1	//KEY0按下
#define KEY1_PRES	2	//KEY1按下
#define WKUP_PRES   3	//KEY_UP按下(即WK_UP/KEY_UP)

void KEY_Init(void);//IO初始化
u8 KEY_Scan(u8);  	//按键扫描函数					    
#endif

key.c文件内容如下:

#include "stm32f10x.h"
#include "key.h"
#include "sys.h" 
#include "delay.h"
								    
//按键初始化函数
void KEY_Init(void) //IO初始化
{ 
 	GPIO_InitTypeDef GPIO_InitStructure;
 	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOE,ENABLE);//使能PORTA,PORTE时钟

	GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_4|GPIO_Pin_3;//KEY0-KEY1
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //设置成上拉输入
 	GPIO_Init(GPIOE, &GPIO_InitStructure);//初始化GPIOE4,3

	//初始化 WK_UP-->GPIOA.0	  下拉输入
	GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_0;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //PA0设置成输入,默认下拉	  
	GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.0

}
//按键处理函数
//返回按键值
//mode:0,不支持连续按;1,支持连续按;
//0,没有任何按键按下
//1,KEY0按下
//2,KEY1按下
//3,KEY3按下 WK_UP
//注意此函数有响应优先级,KEY0>KEY1>KEY_UP!!
u8 KEY_Scan(u8 mode)
{	 
	static u8 key_up=1;//按键按松开标志
	if(mode)key_up=1;  //支持连按		  
	if(key_up&&(KEY0==0||KEY1==0||WK_UP==1))
	{
		delay_ms(10);//去抖动 
		key_up=0;
		if(KEY0==0)return KEY0_PRES;
		else if(KEY1==0)return KEY1_PRES;
		else if(WK_UP==1)return WKUP_PRES;
	}else if(KEY0==1&&KEY1==1&&WK_UP==0)key_up=1; 	    
 	return 0;// 无按键按下
}

main.c文件内容如下:

#include "led.h"
#include "delay.h"
#include "key.h"
#include "sys.h"
#include "beep.h"

 int main(void)
 {
 	vu8 key=0;	
	delay_init();	    	 //延时函数初始化	  
	LED_Init();		  		//初始化与LED连接的硬件接口
	BEEP_Init();         	//初始化蜂鸣器端口
	KEY_Init();         	//初始化与按键连接的硬件接口
	while(1)
	{
 		key=KEY_Scan(0);	//得到键值
	   	if(key)
		{						   
			switch(key)
			{				 
				case WKUP_PRES:	//控制蜂鸣器
					_beep_;     //蜂鸣器翻转
					break; 
				case KEY1_PRES:	//控制LED1翻转	 
					_LED1_;     //LED1翻转
					break;
				case KEY0_PRES:	//同时控制LED0,LED1翻转 
					_LED0_;     //LED0翻转
					_LED1_;     //LED1翻转
					break;
			}
		}else delay_ms(10); 
	}	 
}
posted @ 2023-04-30 14:17  码上芯路人  阅读(1282)  评论(0)    收藏  举报