重要的内容写在前面:
- 该系列是以up主江协科技的STM32视频教程为基础写下去的,大部分内容都参考了老师的课件,对于一些个人认为比较重要但是老师仅口述的部分,笔者都有用文字的方式记录并标出了重点。
- 文中的图片基本都来源于老师的课件以及开发板和芯片的手册,粘贴过来是为了方便阅读。
- 如果有条件的可以先学习一些相关课程再去看STM32的教程,学起来会更加轻松(不太建议零基础开始直接STM32,听起来可能会有点困难,可以先学51单片机),相关课程有数字电路(强烈推荐先学数电,不然可能会有很多地方理解起来很困难)、模拟电路、计算机组成原理(像寄存器、存储器、中断等在这门课里有很详细的介绍)、计算机网络等。
- 如有错漏欢迎指出。
视频链接:[3-1] GPIO输出_哔哩哔哩_bilibili
一、GPIO外设介绍
1、概述
(1)GPIO(General Purpose Input Output)的全称是通用输入输出口。
①输出模式下可控制端口输出高低电平,用以驱动LED、控制蜂鸣器、模拟通信协议输出时序等。
②输入模式下可读取端口的高低电平或电压,用于读取按键输入、外接模块电平信号输入、ADC电压采集、模拟通信协议接收数据等。
(2)引脚电平:一般是0V~3.3V,部分引脚可容忍5V。
2、GPIO基本结构
在STM32中,所有GPIO都是挂载在APB2外设总线上的,每个GPIO外设各有16个引脚;GPIO的寄存器每一位对应一个引脚(寄存器有32位,只有低16位有对应引脚,高16位闲置),其中输出寄存器写1对应的引脚就会输出高电平,输出寄存器写0对应的引脚就会输出低电平,输入寄存器读取为1证明对应的端口目前是高电平,输入寄存器读取为0证明对应的端口目前是低电平;驱动器是用来增加信号的驱动能力的。
3、GPIO位结构
(1)如下图所示,整个结构可以分为输入部分(上)和输出部分(下),除了寄存器是8个I/O引脚共用一个之外,每一个I/O引脚都有自己的位结构。
(2)每一个I/O引脚都接了两个保护二极管,作用是对输入电压进行限幅,VDD为3.3V,VSS为0V,如果输入电压高于3.3V,那么上方的二极管将会导通,输入电压产生的电流就会直接往VDD流入,避免过高电压对内部的电路产生破坏;如果输入电压低于0V,那么下方的二极管将会导通,电流就会直接往VSS流出,而不会从内部电路汲取电流,也起到保护电路的作用。
(3)输入部分:见下图输入驱动器及其后续的电路。
①进入输入驱动器,可以看到电路连接了上拉电阻(和VDD)和下拉电阻(和VSS),两个开关均可以通过程序进行配置,如果上开关导通、下开关断开就是上拉输入模式,上开关断开、下开关导通就是下拉输入模式,上开关断开、下开关断开就是浮空输入模式。
②假如I/O引脚没有外部输入,但是它又不能“啥也不是”(用术语来讲就是高阻状态),如果将其配置为上拉输入模式,那么此时它是高电平,如果将其配置为下拉输入模式,那么此时它是低电平,如果将其配置为浮空模式,引脚的输入电平极易受到外界干扰而发生改变。上拉电阻和下拉电阻的阻值都比较大,所以两个输入模式的全名分别是弱上拉输入模式和弱下拉输入模式,如果I/O引脚有外部输入,那么VDD和VSS也不会影响I/O的正常使用(具体工作原理可根据基尔霍夫定律得出,另外51单片机中也介绍过弱上拉模式,不过51单片机不能通过程序对模式进行配置)。
③图中的“肖基特触发器”应该是“施密特触发器”,其作用是对输入电压进行整形,如果输入电压大于某一阈值,将会输出高电平,如果输入电压低于某一阈值,将会输出低电平(两个阈值不同,可以有效地避免因信号波动造成的输出抖动现象)。
④输入信号经过施密特触发器后就能有效输入输入数据寄存器了,用户要做的就是用程序读取输入数据寄存器对应的某一位数据,从而得知对应端口处于高电平还是低电平。
⑤模拟输入是连接到ADC上的,因为ADC需要接收模拟量,所以它接收的信号不需要施密特触发器进行处理;复用功能输入是连接到其它需要读取端口的外设上的,这根线接收的是数字量,所以它接收的信号需要施密特触发器进行处理。
(4)输出部分:见下图输出驱动器及其前继的电路。
①输出信号可以由输出数据寄存器或片上外设控制,两种控制方式通过数据选择器连接到了输出控制部分。如果选择通过输出数据寄存器(也就是普通的I/O口输出)进行控制,往数据寄存器中的某一位写数据就可以操作对应的某个端口,不过输出数据寄存器不能直接对某一位进行单独配置(&=、|=可以使用,但是效率低),这时可以借助位设置/清除寄存器:
[1]如果要对某一位进行置1操作,在位设置寄存器的对应位写1即可,剩下不需要操作的位写0,位设置寄存器会自动将输出数据寄存器中的对应位置为1,剩下写0的位则保持不变,这样就保证了只操作数据输出寄存器的某一位而不影响其它位。
[2]如果要对某一位进行置0操作,在位清除寄存器的对应位写1即可,剩下不需要改变的位写0,位清除寄存器会自动将输出数据寄存器中的对应位置为0,剩下写0的位则保持不变,这样就保证了只清除数据输出寄存器的某一位而不影响其它位。
②MOS管就是一种电子开关,其导通或关闭由输入信号控制,开关负责将I/O口接到VDD或VSS,在这里可以选择推挽、开漏或关闭三种输出方式。
[1]在推挽输出模式下,P-MOS和N-MOS均有效,数据寄存器相应位为1时,P-MOS导通、N-MOS断开,I/O引脚直接和VDD连接,也就是输出高电平,数据寄存器相应位为0时,P-MOS断开、N-MOS导通,I/O引脚直接和VSS连接,也就是输出低电平。在这种模式下高低电平均有较强的驱动能力,所以推挽输出模式也可以叫强推输出模式,STM32对I/O口具有绝对的控制权。
[2]在开漏输出模式下,P-MOS无效,数据寄存器相应位为1时,N-MOS断开,这时输出相当于断路,也就是高阻模式,数据寄存器相应位为0时,N-MOS导通,I/O引脚直接和VSS连接,也就是输出低电平。在这种模式下只有低电平有驱动能力,高电平没有驱动能力,该模式可以作为通信协议的驱动方式,在多机通信的情况下这个模式可以避免各个设备的相互干扰,另外该模式还可以用于输出5V的电平信号(在I/O引脚处接一个上拉电阻连接5V电源,数据寄存器相应位为1时N-MOS断开,I/O口输出5V电压)。
[3]在关闭模式下,两个MOS管均无效,端口的电平由外部信号控制。
4、端口模式配置
通过配置GPIO的端口配置寄存器,端口可以配置成以下8种模式:
模式名称 | 性质 | 特征 |
浮空输入 | 数字输入 | 可读取引脚电平,若引脚悬空,则电平不确定 |
上拉输入 | 数字输入 | 可读取引脚电平,内部连接上拉电阻,悬空时默认高电平 |
下拉输入 | 数字输入 | 可读取引脚电平,内部连接下拉电阻,悬空时默认低电平 |
模拟输入 | 模拟输入 | GPIO无效,引脚直接接入内部ADC |
开漏输出 | 数字输出 | 可输出引脚电平,高电平为高阻态,低电平接VSS |
推挽输出 | 数字输出 | 可输出引脚电平,高电平接VDD,低电平接VSS |
复用开漏输出 | 数字输出 | 由片上外设控制,高电平为高阻态,低电平接VSS |
复用推挽输出 | 数字输出 | 由片上外设控制,高电平接VDD,低电平接VSS |
①浮空/上拉/下拉输入:在输入模式下,输出驱动器断开,端口只能用于输入,否则输出部分产生的信号可能会影响输入。
②模拟输入:可以说是ADC模数转换器的专属配置。
③开漏/推挽输出:开漏输出的高电平呈现高阻态,没有驱动能力;推挽输出的高电平和低电平均有驱动能力。(配置成输出模式的时候,输入部分并没有断开,程序依然可以读取I/O引脚的电平,总之不影响I/O口的输出即可)
④复用开漏/推挽输出:引脚电平由片上外设控制。(配置成复用输出模式的时候,输入部分并没有断开,程序依然可以读取I/O引脚的电平,总之不影响I/O口的输出即可)
5、手册上有关GPIO寄存器的部分
二、LED和蜂鸣器简介
1、LED(发光二极管)
(1)LED的电路符号如下所示,左边是正极,右边是负极(正极的电压高于负极,电流从正极流向负极,即为有正向电流,LED在此条件下可以被点亮)
(2)LED一般需要接限流电阻,如果不加限流电阻,那么流过LED的电流会很大,可能会对LED造成破坏。
2、蜂鸣器
(1)有源蜂鸣器:内部自带振荡源,将正负极接上直流电压即可持续发声,频率固定。(开发板套件中提供的就是这种蜂鸣器)
(2)无源蜂鸣器:内部不带振荡源,需要控制器提供振荡脉冲才可发声,调整提供振荡脉冲的频率,可发出不同频率的声音。
三、示例程序(GPIO输出)
1、LED灯闪烁
(1)按照下图所示接好线路,并将之前建好的工程文件夹作为模板复制一份使用。
(2)操作STM32的GPIO总共需要3个步骤,第一步是使用RCC开启GPIO的时钟,第二步是使用GPIO_Init函数初始化GPIO,第三步是使用输出或者输入的函数控制GPIO口。
①如果需要找到RCC的相关函数,就到stm32f10x_rcc.h文件(在Library组中)的底部找,一般固件库提供的头文件底部都有所有函数的声明,且函数名称和其作用有很大关联,选中需要调用的函数后右键即可转到函数体,函数体上方有注释以及供用户使用的参数。(下图红框所框选的是RCC最常用的三个函数,用于使能/关闭时钟)
②如果需要找到GPIO的相关函数,就到stm32f10x_gpio.h文件(在Library组中)的底部找,下图红框所框选的是GPIO最常用的函数。
[1] GPIO_DeInit函数:将指定的GPIO外设复位。
[2] GPIO_AFIODeInit函数:将AFIO外设复位。
[3] GPIO_Init函数:用结构体的参数来初始化GPIO口,使用该函数时需要先定义一个结构体变量,然后再给结构体赋值,最后才能调用这个函数。
[4] GPIO_StructInit函数:这个函数可以给结构体变量赋一个默认值。
[5] GPIO_ReadInputDataBit、GPIO_ReadInputData、GPIO_ReadOutputDataBit、GPIO_ReadOutputData都是GPIO的读取函数。
[6] GPIO_SetBits(把指定的端口设置为高电平,可以使用按位或“|”同时操作多个引脚)、GPIO_ResetBits(把指定的端口设置为低电平,可以使用按位或“|”同时操作多个引脚)、GPIO_WriteBit(根据第三个参数的值设定指定端口的电平)、GPIO_Write(第一个参数是选择GPIO口,该函数可以同时对选中的GPIO口的16个端口同时进行写入操作,具体写入内容由第二个参数决定)都是GPIO的写入函数。
(3)使能GPIOA的时钟:
①首先去到stm32f10x_rcc.h底部,找到RCC_APB2PeriphClockCmd函数,它的作用是使能APB2上挂载外设的时钟。
②RCC_APB2PeriphClockCmd函数的使用方法如下,本例需要使能GPIOA的时钟,所以函数参数分别为RCC_APB2Periph_GPIOA、ENABLE。
(4)配置端口模式:
①首先去到stm32f10x_gpio.h底部,找到GPIO_Init函数(Init代表初始化)。
②GPIO_Init函数的第一个参数为需要初始化的GPIO口,第二个参数为一个结构体变量,这个结构体变量内有初始化GPIO口需要的相关参数。(为什么第二个参数是一个结构体指针?因为指针的运算速度比较快,且调用该函数时不用为结构体再拷贝一份形参)
③右键结构体类型名,找到定义结构体的地方,可以看到定义结构体变量需要三个参数,分别为引脚号、速度和模式:
[1]对于引脚号,可以选中注释中的“GPIO_pins_define”后按下Ctrl+F,点击“Find Next”,很快就能找到引脚相关的宏定义,这些就是可供选择的参数。(GPIO_Pin_ALL——一次选中所有引脚)
[2]对于速度,一般配置为50MHz即可,所以结构体关于速度的参数为GPIO_Speed_50MHz。
[3]对于模式,在该例中使用的是推挽输出(一般都采用推挽输出模式,特殊情况才可能会使用开漏输出),所以结构体关于模式的参数为GPIO_Mode_Out_PP。
(5)经过刚刚的步骤,main.c中应该有以下代码(点灯一步是否在死循环中目前不重要),点击编译,待程序编译完成后将它下载到开发板中,可以看到插在面包板上的LED灯亮起。
#include "stm32f10x.h" // Device headerCmd
int main()
{
//使能GPIOA的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
//配置端口模式
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //结构体参数之一(模式)
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //结构体参数之一(引脚)
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //结构体参数之一(速度)
GPIO_Init(GPIOA,&GPIO_InitStructure);
while(1)
{
GPIO_ResetBits(GPIOA,GPIO_Pin_0); //将PA0置为低电平(点灯)
}
}
(6)要完成LED闪烁的功能,仅靠反复将PA0置1置0是不够的,因为程序的执行速度非常快,人眼还没发现LED熄灭LED就重新亮起,这时需要一个延时函数,其作用是让程序暂时睡眠一段时间,首先从资料中找到Delay.c文件和Delay.h文件(找不到的下面有给出代码,直接复制即可),将它们复制到项目文件夹中新建的“System”文件夹中,然后按照下图所示步骤将它们添加到项目中。
①Delay.h文件:
#ifndef __DELAY_H
#define __DELAY_H
void Delay_us(uint32_t us);
void Delay_ms(uint32_t ms);
void Delay_s(uint32_t s);
#endif
②Delay.c文件:
#include "stm32f10x.h"
/**
* @brief 微秒级延时
* @param xus 延时时长,范围:0~233015
* @retval 无
*/
void Delay_us(uint32_t xus)
{
SysTick->LOAD = 72 * xus; //设置定时器重装值
SysTick->VAL = 0x00; //清空当前计数值
SysTick->CTRL = 0x00000005; //设置时钟源为HCLK,启动定时器
while(!(SysTick->CTRL & 0x00010000)); //等待计数到0
SysTick->CTRL = 0x00000004; //关闭定时器
}
/**
* @brief 毫秒级延时
* @param xms 延时时长,范围:0~4294967295
* @retval 无
*/
void Delay_ms(uint32_t xms)
{
while(xms--)
{
Delay_us(1000);
}
}
/**
* @brief 秒级延时
* @param xs 延时时长,范围:0~4294967295
* @retval 无
*/
void Delay_s(uint32_t xs)
{
while(xs--)
{
Delay_ms(1000);
}
}
(7)将下面这段代码复制到main.c文件中,然后编译、下载程序到开发板中验证。
#include "stm32f10x.h" // Device headerCmd
#include "Delay.h"
int main()
{
//使能GPIOA的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
//配置端口模式
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //结构体参数之一(模式)
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //结构体参数之一(引脚)
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //结构体参数之一(速度)
GPIO_Init(GPIOA,&GPIO_InitStructure);
while(1)
{
GPIO_ResetBits(GPIOA,GPIO_Pin_0); //将PA0置为低电平(亮灯)
Delay_ms(500); //睡眠500ms
GPIO_SetBits(GPIOA,GPIO_Pin_0); //将PA0置为高电平(灭灯)
Delay_ms(500); //睡眠500ms
/*下面这段代码有同样的效果
GPIO_WriteBits(GPIOA,GPIO_Pin_0,Bit_RESET); //将PA0置为低电平(亮灯)
Delay_ms(500);
GPIO_WriteBits(GPIOA,GPIO_Pin_0,Bit_SET); //将PA0置为高电平(灭灯)
Delay_ms(500);
*/
}
}
2、LED灯流水
(1)按照下图所示接好线路,并将上例的项目文件夹作为模板复制一份使用。
(2)将下面这段代码复制到main.c文件中,然后编译、下载程序到开发板中验证,可以看到8盏LED灯按照固定顺序轮流闪烁。
#include "stm32f10x.h" // Device headerCmd
#include "Delay.h"
int main()
{
//使能GPIOA的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
//配置端口模式
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2
| GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;
//可以使用按位或“|”同时选中多个引脚,也可以使用GPIO_Pin_ALL同时选择GPIOA的16个引脚
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
while(1)
{
static uint16_t i = 0x0001; //(高8位)+ 0000 0001
//高8位对应引脚PA8~PA15,这部分没有连接外设,不予理会
GPIO_Write(GPIOA,~i); //~i对应二进制数为(高8位)+1111 1110
//对16个端口同时进行写入操作
i = i << 1;
//第一盏灯亮后,下一盏灯应该是第二盏
if(i == 0x0100) //第一轮循环结束,下一盏亮灯是第一盏
i = 0x0001;
Delay_ms(500); //睡眠500ms
}
}
3、让蜂鸣器发出响声
(1)按照下图所示接好线路,并将LED灯闪烁的项目文件夹作为模板复制一份使用。(这次使用的蜂鸣器是低电平驱动,不是“交流电驱动”)
(2)将下面这段代码复制到main.c文件中,然后编译、下载程序到开发板中验证,可以听到蜂鸣器会发出断断续续的响声。
#include "stm32f10x.h" // Device headerCmd
#include "Delay.h"
int main()
{
//使能GPIOB的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
//配置端口模式
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);
while(1)
{
GPIO_ResetBits(GPIOB,GPIO_Pin_12); //将PB12置为低电平(响)
Delay_ms(100);
GPIO_SetBits(GPIOB,GPIO_Pin_12); //将PB12置为高电平(静)
Delay_ms(100);
GPIO_ResetBits(GPIOB,GPIO_Pin_12); //将PB12置为低电平(响)
Delay_ms(100);
GPIO_SetBits(GPIOB,GPIO_Pin_12); //将PB12置为高电平(静)
Delay_ms(700);
}
}
四、按键与传感器
1、按键
(1)按键是常见的输入设备,按下导通,松手断开。
(2)由于按键内部使用的是机械式弹簧片来进行通断的,所以在按下和松手的瞬间会伴随有一连串的抖动,这个抖动称为“按键抖动”,在软件中需要对抖动进行消除(在51单片机教程中对按键消抖有详细的介绍)。
2、传感器
传感器元件(光敏电阻/热敏电阻/红外接收管等)的电阻值会随外界模拟量的变化而变化,通过与定值电阻分压,传感器可得到模拟电压输出,再通过电压比较器对模拟电压进行二值化,即可得到数字电压输出(简单说,以光敏传感器为例,光线强度中间存在一个临界值,临界值上下传感器的输出引脚分别输出低电平和高电平)。
五、示例程序(GPIO输入)
1、按键控制LED
(1)按照下图所示接好线路,并将LED灯闪烁的项目文件夹作为模板复制一份使用。
(2)当代码量庞大时,需要将代码分模块放在不同的代码文件中进行管理,本例需要添加一个存放硬件驱动代码的组“Hardware”(具体步骤前面有提及,这里不再赘述),然后创建一个管理LED模块代码的源文件和头文件(记住选择文件路径为Hardware文件夹),再将两个文件都添加到Hardware组中。
(3)将LED模块的代码添加到LED.c文件中,再在LED.h文件中对函数进行声明,以便main.c调用。
①LED.h文件:
#ifndef __LED_H
#define __LED_H //防止头文件重复被包含
//函数声明
void LED_Init(void);
void LED1_ON(void);
void LED1_OFF(void);
void LED2_ON(void);
void LED2_OFF(void);
#endif
②LED.c文件:
#include "stm32f10x.h" // Device header(每个源文件基本都要包含它)
void LED_Init(void)
{
//使能GPIOA的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
//配置端口模式
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2; //同时配置PA1和PA2
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
//LED的初始状态为灭灯
GPIO_SetBits(GPIOA, GPIO_Pin_1 | GPIO_Pin_2);
}
void LED1_ON(void) //点亮第一盏LED灯(LED1负极接在PA1)
{
GPIO_ResetBits(GPIOA, GPIO_Pin_1); //PA1置为低电平
}
void LED1_OFF(void) //熄灭第一盏LED灯
{
GPIO_SetBits(GPIOA, GPIO_Pin_1); //PA1置为高电平
}
void LED2_ON(void) //点亮第二盏LED灯(LED2负极接在PA2)
{
GPIO_ResetBits(GPIOA, GPIO_Pin_2); //PA2置为低电平
}
void LED2_OFF(void) //熄灭第二盏LED灯
{
GPIO_SetBits(GPIOA, GPIO_Pin_2); //PA2置为高电平
}
(4)GPIO_ReadInputDataBit(用于读取输入数据寄存器某一位对应I/O引脚的输入值)、GPIO_ReadInputData(用于读取某个GPIO口对应输入数据寄存器的所有端口/引脚的值)、GPIO_ReadOutputDataBit(用于读取数据输出寄存器的某一个位)、GPIO_ReadOutputData(用于读取某个GPIO口对应输出数据寄存器的值)都是GPIO的读取函数。
(5)创建一个管理按键(key)模块代码的源文件和头文件(记住选择文件路径为Hardware文件夹),再将两个文件都添加到Hardware组中,然后将Key模块的代码添加到Key.c文件中,再在Key.h文件中对函数进行声明,以便main.c调用。
①Key.h文件:
#ifndef __Key_H
#define __Key_H
void Key_Init(void);
uint8_t Key_GetNum(void);
#endif
②Key.c文件:
#include "stm32f10x.h" // Device header
#include "Delay.h"
void Key_Init(void)
{
//使能GPIOB的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
//配置端口模式
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉输入(按键松开时引脚为高电平)
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_11; //同时配置PB1和PB11
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //输入模式下速度其实可以不用配置
GPIO_Init(GPIOB,&GPIO_InitStructure);
}
uint8_t Key_GetNum(void) //获取按键状态
{
uint8_t KeyNum = 0; //默认按键键码为0(无按键被按下)
if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0) //如果按键1被按下,PB1会被置为低电平
{
Delay_ms(20); //消除20ms的抖动
while(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0); //待按键松开,再往下执行
Delay_ms(20); //消除20ms的抖动
KeyNum = 1; //按键键码置为1
}
if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0) //如果按键2被按下,PB11会被置为低电平
{
Delay_ms(20); //消除20ms的抖动
while(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0); //待按键松开,再往下执行
Delay_ms(20); //消除20ms的抖动
KeyNum = 2; //按键键码置为2
}
return KeyNum; //返回按键键码
}
(6)本例需要实现的功能是按键1控制LED1的亮灭、按键2控制LED2的亮灭,为了使主函数的代码看起来更加简洁和清晰,需要在LED模块中再封装两个函数用于翻转两个LED灯的状态。
①LED.h文件需要添加两个函数声明:
void LED1_Turn(void);
void LED2_Turn(void);
②LED.c文件需要添加两个函数实现:
void LED1_Turn(void) //LED1状态翻转
{
if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_1) == 0) //读出PA1的电平并判断是否为低电平
{
GPIO_SetBits(GPIOA, GPIO_Pin_1); //如果读出引脚PA1为低电平,就将其置为高电平(亮->暗)
}
else
{
GPIO_ResetBits(GPIOA, GPIO_Pin_1); //如果读出引脚PA1为高电平,就将其置为低电平(暗->亮)
}
}
void LED2_Turn(void) //LED2状态翻转
{
if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_2) == 0) //读出PA2的电平并判断是否为低电平
{
GPIO_SetBits(GPIOA, GPIO_Pin_2); //如果读出引脚PA2为低电平,就将其置为高电平(亮->暗)
}
else
{
GPIO_ResetBits(GPIOA, GPIO_Pin_2); //如果读出引脚PA2为高电平,就将其置为低电平(暗->亮)
}
}
(7)主函数需要完成的任务有:①调用各模块的初始化函数,对各个模块进行初始化;②使用各模块已经封装好的函数进行代码的编写。
#include "stm32f10x.h" // Device headerCmd
#include "Delay.h"
#include "LED.h" //头文件包含
#include "Key.h"
uint8_t KeyNum;
int main()
{
LED_Init(); //LED模块初始化
Key_Init(); //按键模块初始化
while(1)
{
KeyNum = Key_GetNum();
if(KeyNum == 1) //按一次按键1,LED1状态翻转一次
{
//LED1_ON(); 打开LED1
//LED2_OFF(); 关闭LED2
LED1_Turn(); //LED1状态翻转
}
if(KeyNum == 2) //按一次按键2,LED2状态翻转一次
{
//LED2_ON(); 打开LED2
//LED1_OFF(); 关闭LED1
LED2_Turn(); //LED2状态翻转
}
}
}
2、光敏传感器控制蜂鸣器
(1)按照下图所示接好线路,并将按键控制LED的项目文件夹作为模板复制一份使用。
光敏传感器有4个引脚,其中两个引脚为VCC和GND,这是供电必须接上的,另外两个引脚为AO和DO,其中DO口输出数字信号,AO口输出模拟信号,一般情况下我们都是选择DO口,因为计算机处理数字信号会更加方便。
(2)创建一个管理蜂鸣器(Buzzer)模块代码的源文件和头文件(记住选择文件路径为Hardware文件夹),再将两个文件都添加到Hardware组中,然后将Buzzer模块的代码添加到Buzzer.c文件中,再在Buzzer.h文件中对函数进行声明,以便main.c调用。
①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
②Buzzer.c文件:
#include "stm32f10x.h" // Device header
void Buzzer_Init(void)
{
//使能GPIOB的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
//配置端口模式
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; //配置PB12
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStructure);
//蜂鸣器的初始状态为不响
GPIO_SetBits(GPIOB, GPIO_Pin_12);
}
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_ReadInputDataBit(GPIOB, GPIO_Pin_12) == 0) //读出PB12的电平并判断
{
GPIO_SetBits(GPIOB, GPIO_Pin_12); //如果读出引脚PB12为低电平,就将其置为高电平(响->静)
}
else
{
GPIO_ResetBits(GPIOB, GPIO_Pin_12); //如果读出引脚PB12为高电平,就将其置为低电平(静->响)
}
}
(3)创建一个管理光敏传感器器(LightSensor)模块代码的源文件和头文件(记住选择文件路径为Hardware文件夹),再将两个文件都添加到Hardware组中,然后将LightSensor模块的代码添加到LightSensor.c文件中,再在LightSensor.h文件中对函数进行声明,以便main.c调用。
①LightSensor.h文件:
#ifndef __LightSensor_H
#define __LightSensor_H
void LightSensor_Init(void);
uint8_t LightSensor_Get(void);
#endif
②LightSensor.c文件:
#include "stm32f10x.h" // Device header
void LightSensor_Init(void)
{
//使能GPIOB的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
//配置端口模式
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13; //配置PB13
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //输入模式下速度其实可以不用配置
GPIO_Init(GPIOB,&GPIO_InitStructure);
}
uint8_t LightSensor_Get(void) //获取传感器当前状态
{
return GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_13); //返回PB13引脚状态
//光线较暗时,光敏传感器模块的BO口为高电平,也就是PB13为高电平
//光线较亮时,光敏传感器模块的BO口为低电平,也就是PB13为低电平
}
(4)主函数需要完成的任务有:①调用各模块的初始化函数,对各个模块进行初始化;②使用各模块已经封装好的函数进行代码的编写。
#include "stm32f10x.h" // Device headerCmd
#include "Buzzer.h"
#include "LightSensor.h"
int main()
{
LightSensor_Init(); //传感器模块初始化
Buzzer_Init(); //蜂鸣器模块初始化
while(1)
{
if(LightSensor_Get() == 1)
{
Buzzer_ON(); //光线过暗,蜂鸣器启动
}
else
{
Buzzer_OFF(); //光线充足,蜂鸣器关闭
}
}
}