目录
第一个代码:按键控制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
本篇就到这里,下篇继续!欢迎点击下方订阅本专栏↓↓↓