stm32学习笔记---GPIO输入(代码部分)按键控制LED/光敏传感器控制蜂鸣器

目录

第一个代码:按键控制LED

模块化程序

LED驱动程序

GPIO的四个读取函数

GPIO_ReadInputDataBit

GPIO_ReadInputData

GPIO_ReadOutputDataBit

GPIO_ReadOutputData

Key驱动程序

第二个代码:光敏传感器控制蜂鸣器

蜂鸣器驱动代码

光敏传感器驱动代码

总结GPIO的使用方法


声明:本专栏是本人跟着B站江科大的视频的学习过程中记录下来的笔记,我之所以记录下来是为了方便自己日后复习。如果你也是跟着江科大的视频学习的,可以配套本专栏食用,如有问题可以QQ交流群:963138186

上一篇讲了GPIO输入部分的理论知识,接下来我们来写一下GPIO输入部分的代码。

第一个代码:按键控制LED

先看一下第一个代码的接线图

这里接了两个按键和两个 LED,其中两个按键分别接在了PB1和PB11两个口上,按键一端接GPIO口,另一端接GND,因为这是引脚上拉输入模式,就是上一篇讲到的第一种接法

两个LED分别接在了PA1和PA2两个口上。LED一端接GPIO另一端接VCC,就是低电平电量的接法。

按键、LED的数量和连接的端口都是随意的。具体接多少个,接到哪些端口,就看你的需求。

接好电路后,打开工程文件夹,复制一下之前蜂鸣器工程的代码,改个名字,叫3-4 按键控制LED。

然后点进去打开工程,接下来我们就需要在这个工程上完成LED和按键的驱动代码。

模块化程序

在本节代码演示之前演示一下程序模块化,方便我们以后直接调用或者移植。

首先我们打开工程文件夹,再建一个文件夹,叫Hardware,以后就用来存放我们所有的硬件驱动代码。

然后回到keil,还是一样的步骤,点击三个箱子的按钮,打开工程管理,新建一个组也叫hardware。挪个位置,ok。

然后再点击魔术棒按钮,打开工程选项,选择C/C++,点击这里三个点的按钮。把刚才新建的hardware文件夹添加到头文件路径列表中,ok。

这样我们就又添加了一个Hardware文件夹。

LED驱动程序

然后在Hardware这里右键添加新的文件,选择c文件,起个名字叫LED这个文件,就用来分装LED的驱动程序,

然后路径也别忘了。

选择hardware文件夹,存在这个文件夹里,add。

接着继续在Hardware处右键添加新的文件,选择h文件,起个名字也叫LED。下面的路径也是一样,选择hardware文件夹,这里可以直接在这里写,反斜杠hardware,然后Add。

这样我们就建好了LED.c和LED.h两个文件,用来分装LED的驱动程序。

这两个文件建好之后,还得添加一些必要的代码。

首先.c文件的第一行,右键,include的一个头文件

.h文件里要添加一个防止头文件重复包含的代码

最后注意这个文件要以空行结尾,这样就完成了。

接着我们就编辑一下LED的的代码

我们打开LED.c文件,首先写一个LED初始化函数。

这个函数是用来初始化LED,里面写的就是打开时钟配置端口模式这些东西,这就是我们之前学的代码,也就是RCC_APB2外设时钟控制。

我们的LED都是接在GPIOA上的,所以第一个参数是RCC_APB2Periph外设GPIOA,然后第二个参数写enable开启时钟

接着配置端口模式,先定义一个结构体变量。

首先输入GPIO_Init这里如果不显示代码提示的话,可以按一下快捷键CTRL+ALT+空格,这样就可以弹出代码提示框了。我们选择GPIO_InitTypeDef

变量名叫GPIO_InitStructure,

然后引出结构体成员并赋值,最后取结构体的地址传参和GPIO初始化函数完成端口配置。这里过程在之前几篇博客中都详细讲过了,这里不再赘述。不懂的话可以去看看之前的博客。

这样LED初始化的代码就写完了。

在.h文件中声明一下这个函数是可以被外部调用的函数

然后在main.c中包含LED模块的头文件

在主函数里直接调用LED_Init就完成了LED的初始化

编译下载可以看到两个LED都亮起来了,

这说明我们的端口配置和模块化编程是没有问题的。

这里因为GPIO配置好了之后,默认就是低电平,所以我们还没操作LED,LED就亮起来了。我们可以在LED_Init函数的最后写上一行代码:

这样初始化之后,如果不操作LED就是熄灭的了。

重新编译下载,LED默认是关闭状态的。

GPIO的四个读取函数

接下来的程序我们需要用到读取GPIO端口的功能,先找一下GPIO的库函数文件,这四个就是GPIO的读取函数

GPIO_ReadInputDataBit

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

GPIO_ReadInputData

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

GPIO_ReadOutputDataBit

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

GPIO_ReadOutputData

最后一个函数也是少了一个bit,意思也是一样,是用来读取整个输出寄存器的

对照一下这个图,应该就好理解了。

GPIO_ReadInputDataBit就是读取这里输入数据寄存器的某一位。

GPIO_ReadInputData就是读取整个输入数据寄存器。

GPIO_ReadOutputDataBit就是读取这里输出数据寄存器的某一位。

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

所以如果你想读取GPIO口的话,需要用read input的两个函数。如果在输出模式下,想要看一下现在输出了什么才需要用到read output的两个函数。

接着我们还得完善一下LED驱动程序模块,除了初始化,我们还需要点亮和熄灭LED的函数和按下按键后LED状态取反的函数

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初始化后的默认电平*/
	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引脚为低电平
	}
}

调用六个函数来实现两个灯的打开和关闭以及状态取反。

当然这里定义4个函数比较繁琐了一点,我们后面还会讲如果用一个函数还实现关和开的操作,这里LED还是比较少,就直接这样写了。

最后在头文件里面声明一下这几个函数。

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的驱动函数模块就分装好了。

Key驱动程序

现在开始写按键部分的代码,同样封装在一个驱动函数模块里。一样的过程就省略了,直接看写好的结果:

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
}

别忘了在头文件中声明一下这两个函数

Key.h

#ifndef __KEY_H
#define __KEY_H

void Key_Init(void);
uint8_t Key_GetNum(void);

#endif

然后在主函数中调用这些函数来实现按键控制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翻转
		}
	}
}

运行效果:

STM32-按键控制LED

第二个代码:光敏传感器控制蜂鸣器

接着我们来写一下第二个代码:光敏传感器控制蜂鸣器

先看一下接线图

接好线后上电可以看到光敏传感器的灯亮了,当我们遮住光线时,输出指示灯灭,代表输出高电平,松手时,输出指示灯亮,代表输出低电平。

这个传感器上面的电位器可以调节高低电平的判断阈值。

接着我们复制上一个工程改名,然后打开工程。

我们的主要步骤还是驱动程序的封装,给各模块封装好的程序都放在这个我们的hardware文件夹中

说明:关于程序的文件建立,添加路径步骤我前面的博文都写得很清楚了,以后不再赘述。

蜂鸣器的代码逻辑和LED的代码逻辑很相似就不再赘述了,接下来的代码逻辑可以看我的注释,如果看不懂可以在评论区留言或者后台私信,私信时请带问题处的截图。

蜂鸣器驱动代码

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

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

接着我们封装一下光敏传感器的程序

光敏传感器驱动代码

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

LightSensor.h

#ifndef __LIGHT_SENSOR_H
#define __LIGHT_SENSOR_H

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

#endif

最后我们在主函数中实现一下

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

运行效果:

STM32-光敏传感器控制蜂鸣器

本节课的程序演示到这里就差不多了。

总结GPIO的使用方法

最后我们来总结一下GPIO的使用方法。

总体上来说还是比较简单的:

首先初始化时钟;

然后定义结构体,赋值结构体,GPIO_Mode可以选择我们讲的八种输入输出模式;GPIO_Pin选择引脚可以用按位或的方式,同时选中多个引脚;

GPIO_Speed选择输出速度这个不是很重要,要求不高的话,直接选50MHz就行了;

最后使用GPIO_Init函数将指定的GPIO外设初始化好.

然后这里有八个读取和写入的函数,我们读写GPIO口主要就用这些函数就行了

之后我们学习了模块化编程的方法,一般我们自己做一个产品的话,外围硬件比较多。这时候就要尽量把每个硬件的驱动函数单独提取出来,封装在.c和点h文件里,这样有利于简化主函数的逻辑。在主函数中,我们还有更重要的任务要完成,不要让这些驱动函数混在主函数里影响我们。另外把硬件驱动提取出来,也有利于我们移植程序,还有利于我们进行分工合作。

比如让别人来写驱动函数,你的主要精力就可以集中在主程序的逻辑上了。最后既然要做分装,函数的注释就需要写清楚,这样可以方便使用你这个模块的人快速上手这些函数。

以上就是本节的全部内容了,下篇继续。

QQ交流群:963138186

本篇就到这里,下篇继续!欢迎点击下方订阅本专栏↓↓↓

  • 38
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Vera工程师养成记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值