3.4.STM32-按键控制LED&光敏传感器控制蜂鸣器

快捷键分享

C t r l + s − − − > 保存文件 Ctrl + s--->保存文件 Ctrl+s−−−>保存文件

C t r l + A l t + S p a c e − − − − > 弹出代码提示框 Ctrl + Alt + Space ---->弹出代码提示框 Ctrl+Alt+Space−−−−>弹出代码提示框


按键控制LED

我们按照视频教程,把按键控制LED的硬件电路接好

如图,按键一端接在GND,一端接在GPIO口,就是我们PPT中的第一种接法。LED一端接GPIO口,另一端接VCC,就是低电平点亮的接法

接下来我们就需要在这个工程上完成LED按键驱动代码

但是如果把这两部分驱动代码都混在主函数里面,显得很乱,不容易管理,也不容易移植

所以对于这种驱动代码,我们采取封装函数 的办法,单独放在另外的.c和.h文件里,这就是模块化编程的方式

后续我们跟随视频教程完成模块化编程

我们在添加文件时,这个LED.c用来存放驱动程序的主体代码 ,LED.h用来存放这个驱动程序可以对外提供的函数或变量的声明

注意.h文件要添加一个防止头文件重复包含 的代码,格式都是固定的

最后这个***#endif是和#ifndef***组成的括号,注意最后要以空行结尾

接着我们就来封装一下LED的代码,我们打开LED.c文件,首先写一个LED初始化函数,里面写的就是打开时钟,配置端口模式这些东西

  • 时钟相当于单片机的心脏,开启后才能实现相应的功能
  • 我们需要把初始化函数放在.h文件这里,记得加分号。
  • 这样就是对模块外部声明,这个函数是可以被外部调用的函数

如下图,配置成功。这说明我们得端口配置和模块化编程是没有问题的

这里因为GPIO配置好之后默认就是低电平,所以无需其他操作,LED就会亮起来,所以我们可以先用在3.2.STM32-LED闪烁&LED流水灯&蜂鸣器里面学到的GPIO_ResetBitsGPIO_SetBits输出函数手动配置高低电平

貌似可以折叠起来

然后复制,我们再粘贴到.h文件里面声明一下

这样LED的驱动函数模块就封装好了

接下来我们根据教程写一下按键初始化函数

注意输入模式要填GPIO_Mode_IPU(上拉输入) ,因为这几个按键都是接地的,浮空时输入低电平,那么输出高电平怎么实现?就需要上拉电阻输出高电平,我们可以在3.3.GPIO输入看到按键的硬件电路

接着我们再来写一个读取按键值的函数uint8_t Key_GetNum(),调用这个函数,就可以返回按下按键的键码,键码默认给0,松开按键时,返回0


uint8_t Key_GetNum()的返回值是uint8_t,我们在3.3.GPIO输入C语言的数据类型同样讲过,这就是unsigned char的意思

在中间这里,我们就需要用到读取GPIO端口的功能了。

我们打开库函数

按键读取函数

第一个

GPIO_ReadInputDataBit,这个函数是用来读取输入数据寄存器某一个端口的输入值 的,它的参数是GPIOxGPIO_Pin ,用来指定某一个端口,返回值uint8_t代表这个端口的高低电平。读取按键我们就需要用到这个函数

第二个

GPIO_ReadInputData,这个函数比上一个函数少了一个Bit,它是用来读取整个输入数据寄存器的,参数只有一个GPIOx,用来指定外设,返回值是uint16_t,是一个16位的数据,每一位代表一个端口值

第三个

GPIO_ReadOutputDataBit,这个函数是用来读取输出数据寄存器的某一个位,原则上来说,它并不是用来读取端口的输入数据的,这个函数一般用于输出模式,用来看一下自己输出的是什么

第四个

GPIO_ReadOutputData,这个函数也是少了一个Bit,意思也是一样,是用来读取整个输出寄存器的

为了解释这些函数的功能,我们回顾一下3.1.STM32-GPIO通用输入输出口中对GPIO位结构的介绍

GPIO_ReadInputDataBit,就是读取这里输入数据寄存器的每一位
GPIO_ReadInputData,就是读取这整个输入数据寄存器
GPIO_ReadOutputDataBit,就是读取这里输出数据寄存器的某一位

GPIO_ReadOutputData,就是读取这整个输出数据寄存器

所以说,如果你想读取GPIO口 的话,需要用ReadInput 的这两个函数

如果在输出模式下,想要看现在输出了什么,才需要用到ReadOutput这两个函数,这就是这四个函数的用途

我们回到Key.c这里来,在这里我们需要读取外部输入的一个端口值

我们刚刚加入的这个函数

GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1);
它会返回 GPIOB 端口第 1 号引脚当前的电平,是 0 还是 1。

逐字解释

"输入寄存器某一位的值"

其实就是说:

STM32 每个 GPIO 端口都有一个 输入数据寄存器 IDR

它里面有 16 个 bit(0~15),每个 bit 对应一个引脚现在的电平情况。

  • bit = 1 → 说明这个引脚现在是 高电平
  • bit = 0 → 说明这个引脚现在是 低电平

GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1)

干的事情就是:

  • 去看 GPIOB->IDR 这个寄存器
  • 取出 第 1 位(Pin1)
  • 把这个 0 或 1 返回给你

所以我们需要加上一个if语句,如果读取PB1端口值为0 (== 0),就代表按键按下

开始进入if内操作,这时按键刚按下,会有个抖动,所以需要Delay一段时间(要用Delay函数需要声明一下头文件),然后再这里Delay_ms(20)消除一下抖动。接着我们还需要检测一下按键松手的情况

  • 因为我们的按键一般是松手之后才有动作的,所以在这里加上一个while循环 ,判断条件还是读取PB1 == 0这个,如果按键一直按下,就卡在这里,直到松手。松手之后,再Delay_ms(20);消除一下按键松手的抖动,接着我们赋值KeyNum = 1,把键码1传递出去,这就是PB1按键的检测

  • PB11按键也是一样的

  • 这是一个阻塞式的延时去抖按键检测程序:
    按键按下 → 等 20ms → 等你松手 → 再等 20ms → 返回按键编号。

  • 虽然能用,但:

    • Delay_ms(20) 会让 CPU 卡死
    • while(...) 也会让 CPU 卡死
    • 多任务下会导致其他功能失效(串口丢数据、定时器错乱)
    • 不适合复杂项目
      ·

接着我们在主函数声明一下Key.h文件,再定义一个全局变量KeyNum,用来存一下键码的返回值

注意这个KeyNum是全局变量,函数内的KeyNum是局部变量,两者的作用域不一样。

局部变量只能在本函数里使用,在外面的是全局变量,每个函数都可以使用。在函数里优先使用自己的局部变量,如果没有,才会使用外部的全局变量

那我就用这个全局变量来接收返回值。接着在while循环写上这个
KeyNum = Key_GetNum();,不断读取键码值,放在KeyNum变量里

接着在主函数写下这一段代码,就可以实现按按键1,LED1点亮,按按键2,LED1熄灭

但是我们演示的程序是按一下熄灭,再按一下点亮。也就是按键按下,LED的状态取反,这个怎么实现呢?

这就需要用到GPIO_ReadOutput函数了

我们可以在LED的驱动代码里,再加一个函数void LED1_Turn()

这整个逻辑就是,调用GPIO_ReadOutputDataBit函数,读取当前的端口输出状态,如果当前输出0,就给他置1,否则就置0

这也是GPIO_ReadOutput函数的用途,一般用于输出模式下

把LED2也加上翻转的功能,然后把函数名复制到.h文件声明一下即可。这样我们就可以在主函数使用这两个功能了

接下来我们展示一下最终效果

按键控制LED

因为博主的面包板质量不太好,总是接触不良,所以只演示一个按键控制一个LED的情况

最后注意一下,这个驱动函数模块 写好之后,尽量在函数上面加上一些注释,说明一下函数的用途、参数的取值和返回值的意思这些东西

下面给大家配上图,演示一下

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翻转
		}
	}
}

LED.c文件

图片版





代码版

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初始化后的默认电平*/
	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.c文件

图片版

代码版

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
}

按键控制LED 的代码到这里就结束了,接着我们来到光敏传感器控制蜂鸣器的代码编写

光敏传感器控制蜂鸣器

接线图

我们先看一下接线图

这里接了两个模块

左边的蜂鸣器同之前的接法。

VCC,GND 接电源,控制脚接PB12号口

右边的光敏传感器,VCC,GND同样都是接电源,DO(数字输出端)接PB13号口

当我们遮住光线时,输出指示灯灭,代表输出高电平,松手时,输出指示灯亮,代表输出低电平

其上的电位器可以调节高低电平的判断阈值

代码设计

主要步骤还是驱动程序的封装

蜂鸣器+光敏传感器

main.c文件

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();				//蜂鸣器关闭
		}
	}
}

蜂鸣器.c

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引脚为低电平
	}
}

蜂鸣器.h

c 复制代码
#ifndef __BUZZER_H
#define __BUZZER_H

void Buzzer_Init(void);
void Buzzer_ON(void);
void Buzzer_OFF(void);
void Buzzer_Turn(void);

#endif

光敏传感器.c

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输入寄存器的状态
}

光敏传感器.h

c 复制代码
#ifndef __LIGHT_SENSOR_H
#define __LIGHT_SENSOR_H

void LightSensor_Init(void);
uint8_t LightSensor_Get(void);

#endif

最后总结一下

GPIO的使用方法

  • 初始化时钟
  • 定义结构体
  • 赋值结构体
    • GPIO_Mode可以选择我们之前讲的那8种输入输出模式
    • GPIO_Pin选择引脚,可以用按位或的方式同时选中多种引脚
    • GPIO_Speed没啥要求
  • 最后使用GPIO_Init函数,将指定的GPIO外设初始化好
  • 然后有8个读取和写入的函数,我们读写GPIO口主要就用这些函数就行
  • 之后我们学习了模块化编程的方法,一般我们自己要做一个产品的话,外围硬件比较多
  • 这时候就要尽量把每个硬件的驱动函数单独提取出来,封装在.c和.h文件里
  • 这样有利于简化主函数的逻辑,也有利于移植程序和分工合作
  • 做封装时,把函数的注释写清楚,示例已经给出来了
相关推荐
点灯小铭3 小时前
基于单片机的程控放大器设计与实现
单片机·嵌入式硬件·毕业设计·课程设计·期末大作业
范纹杉想快点毕业3 小时前
《嵌入式硬件从入门到精通:电源 / 模电 / 数电 / 通信核心全解析》
java·开发语言·数据库·单片机·嵌入式硬件
打酱油程序员3 小时前
舵机工作原理与控制详解
单片机·嵌入式硬件
Wave8454 小时前
FreeRTOS的常用函数和剪切
单片机·嵌入式硬件
dlwlrma_5165 小时前
STM32使用HAL库通过中断实现非阻塞I2C通讯 解决实际发送错误数据的问题 HAL_I2C_Mem_Write_IT
stm32
C.咖.5 小时前
STM32 ——嵌入式 存储系统、时钟系统(F407 系列)
stm32·单片机·嵌入式硬件
llilian_165 小时前
晶振有什么好用的检测仪器?石英晶振测试仪 晶体测试仪
服务器·单片机·嵌入式硬件·其他
FreakStudio6 小时前
串口协议解析实战:以 R60ABD1 雷达为例,详解 MicroPython 驱动中数据与业务逻辑的分离设计
python·单片机·pycharm·嵌入式·面向对象·硬件·电子diy
brave and determined6 小时前
可编程逻辑器件学习(day24):异构计算:突破算力瓶颈的未来之路
人工智能·嵌入式硬件·深度学习·学习·算法·fpga·asic