STM32入门教程(GPIO篇)

重要的内容写在前面:

  1. 该系列是以up主江协科技的STM32视频教程为基础写下去的,大部分内容都参考了老师的课件,对于一些个人认为比较重要但是老师仅口述的部分,笔者都有用文字的方式记录并标出了重点。
  2. 文中的图片基本都来源于老师的课件以及开发板和芯片的手册,粘贴过来是为了方便阅读。
  3. 如果有条件的可以先学习一些相关课程再去看STM32的教程,学起来会更加轻松(不太建议零基础开始直接STM32,听起来可能会有点困难,可以先学51单片机),相关课程有数字电路(强烈推荐先学数电,不然可能会有很多地方理解起来很困难)、模拟电路、计算机组成原理(像寄存器、存储器、中断等在这门课里有很详细的介绍)、计算机网络等。
  4. 如有错漏欢迎指出。

视频链接:[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();   //光线充足,蜂鸣器关闭
		}
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Zevalin爱灰灰

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

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

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

打赏作者

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

抵扣说明:

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

余额充值