目录
0. 《STM32单片机自学教程》专栏
本文作为专栏《STM32单片机自学教程》专栏其中的一部分,返回专栏总纲,阅读所有文章,点击Link:
本节我们学习一下GPIO的输入检测,按键检测是最常用最简单的输入检测场景。掌握了按键输入检测之后,其他的不同设备外设检测都是大同小异,触类旁通,我们更重要的是要学习这些工程开发思想和技巧。
15.1.硬件设计
15.1.1 硬件设计示意图
图15.1-1 硬件连接示意图
我们的硬件连接非常简单,PA1口接一个按键到GND,PB1控制LED灯的亮灭。我们要实现的程序现象就是,按下按键后LED灯状态进行反转,实现亮-灭控制。
15.1.2 机械按键问题
按键是机械的,在按下和松手的瞬间会伴随有一定时间的抖动,按键开关不会马上稳定接通或一下断开,使用按键时会产生图 15.1-2 中所示波浪信号,我们写程序的时候需要用软件消抖处理滤波。
图15.1-2 按键抖动说明
当然也可以设计硬件进行消抖,如果有硬件消抖,软件就不用再设计消抖处理。硬件消抖一般是利用RC电路的电容充放电特性来对抖动产生的电压毛刺进行滤波,简单示意图如图15.1-3.
图15.1-3 硬件消抖
15.2 软件设计
15.2.1 工程文件创建
目前设计的软件,要更加注重的是可移植性,所以很多软件设计里有很多封装,有很多“层层套娃”等,目的是为了软件的可移植性可重用性。一些硬件的改动,仅仅需要软件配置上做一定适配,软件就能用,减少开发周期和成本。
我们利用以前的工程模版,再新建2个文件夹,“BSP”(即板级支持包)和“SrcLibrary”,分别存放板子的驱动函数和工程服务函数(这个可以按自己习惯来,文件夹命名不同人有不同的风格),工程里也对应新建2个组。如图15.2-1。
图15.2-1 工程创建示意图
我们一个开发板可能有很多外设,一般每个外设我们都需要写驱动服务函数,即2个文件,.h和.C。.h一般是.C文件需要被外部调用的变量和函数的声明,以及软件和硬件配置相关内容;.C文件主要是一些和外设相关的过程实现函数。如上图,我们本次需要用到LED和按键Key两个外设,在BSP中我们对应建立了2个外设对应的.c和.h文件。工程文件的建立过程前面多次讲过,这里就略过。
“SrcLibrary”文件夹里我们放了“Delay”函数,因为本次编程中用到延时相关的内容,这里我们直接拿过来用即可。后面我们还会介绍。这里也提一下,我们开发时要善于用别人已经开发好的函数,这样可以大大提高开发速度,有些函数我们知道怎么去构建,但有时候确实非常耗费时间。也有些函数我们也没必要去深究,我一直秉承一个思想,当今世界知识爆炸,需要你去学的东西太多了,每一项你都要去深究学习,是不对的,要有的放矢,该放下的就放下,“学海无涯,人生苦短,点到即止”。当然对于你从事的专业工作,那肯定还是要深入研究,这个不要太死板。
15.2.2 LED相关程序设计
为方便以后移植,我们建立LED.h文件,对LED使用的端口,是高电平点亮还是低电平点亮等方式进行配置。这样即便后续硬件有更改,那么我们只用修改.h文件里的配置信息即可。当项目非常大时,这些差异化的配置,我们也可以统一放到一个.h文件中,如建立一个user_config.h,把所有需要特殊配置的内容都放在这里面。现在开发,这些配置都是可以通过工具自动生成的,都无需人工代码,现在项目开发有完备的工具链,你只需要在开发界面里选择好你的设置内容,代码自动生成。
LED.h的代码如下:
#ifndef __LED_H
#define __LED_H
#include "stm32f10x.h"
/* LED连接的GPIO端口配置, 用户只需要修改下面的代码即可改变控制的LED引脚 */
#define LED_GPIO_PORT GPIOB /* GPIO端口 */
#define LED_GPIO_CLK RCC_APB2Periph_GPIOB /* GPIO端口时钟 */
#define LED_GPIO_PIN GPIO_Pin_1 /* GPIO -pin*/
/** 定义LED亮灭对应的高低电平,根据硬件设计可修改
* 1 - off
* 0 - on
*/
#define LED_ON 0
#define LED_OFF 1
void LED_Init(void);//LED端口初始化
void LED_Toggle(void);//LED灯反转
#endif
代码中对LED的亮灭也进行了定义,这样做有两个好处:一是更加形象,编程的时候一看便知是什么意思;二是方便程序移植,如果其他硬件配置的高低电平点亮方式和软件设计的不同,那么我们只需要修改一下这个配置即可,无需再修改主代码。这段代码除了LED相关的硬件配置,还声明了2个函数,分别是LED端口的初始化函数和LED灯反转的函数,这2个函数需要在LED.c文件中实现。LED.c文件代码如下:
#include "stm32f10x.h" // Device header
#include "LED.h"
//LED端口初始化函数
void LED_Init(void)
{
/*定义一个GPIO_InitTypeDef类型的结构体*/
GPIO_InitTypeDef GPIO_InitStructure;
/*开启LED相关的GPIO外设时钟*/
RCC_APB2PeriphClockCmd(LED_GPIO_CLK, ENABLE);
/*选择要控制的GPIO引脚*/
GPIO_InitStructure.GPIO_Pin = LED_GPIO_PIN;
/*设置引脚模式为通用推挽输出*/
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽输出
/*设置引脚速率为50MHz */
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
/*调用库函数,初始化GPIO*/
GPIO_Init(LED_GPIO_PORT, &GPIO_InitStructure);
}
//LED端口反转函数
void LED_Toggle(void)
{
if (GPIO_ReadOutputDataBit(LED_GPIO_PORT, LED_GPIO_PIN) == LED_ON)
{
GPIO_SetBits(LED_GPIO_PORT, LED_GPIO_PIN);
}
else
{
GPIO_ResetBits(LED_GPIO_PORT, LED_GPIO_PIN);
}
}
/*注:LED的端口反转函数,还可以这样写,就是靠操作端口输出寄存器ODR的值,异或位运算^=可以反转IO口
void LED_Toggle(void)
{
LED_GPIO_PORT->ODR ^=LED_GPIO_PIN;//输出反转状态
}
*/
初始化函数我们就不讲了,步骤都是固定的,开启时钟,配置模式和速率,这里模式我们选择推挽输出 。
LED的反转函数LED_Toggle,我们用到了标准库函数GPIO_ReadOutputDataBit,这个函数是读取端口指定pin的输出状态。不清楚的可以再翻看前面《第14章GPIO简介》中库函数的介绍。我们采用的实现方式是最直观的好理解的一种方式,同时我们也提供了另外一个方式,就是通过对GPIO数据输出寄存器ODR的指定位进行异或操作,将对应PIn反转:
LED_GPIO_PORT->ODR ^=LED_GPIO_PIN
这个在《第2章.STM32开发C语言常用知识点》中有介绍,有需要的可以再翻看一下。
15.2.3 按键检测程序设计
和前面LED类似,在.h文件中进行硬件相关配置和实现函数的声明。
#ifndef __KEY_H
#define __KEY_H
#include "stm32f10x.h"
// 引脚定义
#define KEY_GPIO_CLK RCC_APB2Periph_GPIOA
#define KEY_GPIO_PORT GPIOA
#define KEY_GPIO_PIN GPIO_Pin_1
/** 按键按下标置宏
* 按键按下为高电平,设置 KEY_ON=1, KEY_OFF=0
* 若按键按下为低电平,把宏设置成KEY_ON=0 ,KEY_OFF=1
*/
#define KEY_ON 0
#define KEY_OFF 1
void Key_Init(void);
uint8_t Key_State(void);
#endif
Key.c需要中需要设计2个函数,分别是初始化函数和按键是否按下的检测函数,代码如下:
#include "stm32f10x.h" // Device header
#include "Key.h"
#include "Delay.h"
//按键端口初始化
void Key_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(KEY_GPIO_CLK, ENABLE);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//上拉输入
GPIO_InitStructure.GPIO_Pin = KEY_GPIO_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(KEY_GPIO_PORT, &GPIO_InitStructure);
}
//获取按键的状态,是否被按下
uint8_t Key_State(void)
{
/*检测是否有按键按下 */
if(GPIO_ReadInputDataBit(KEY_GPIO_PORT,KEY_GPIO_PIN) == KEY_ON )
{
Delay_ms(20);//消抖
/*等待按键释放 */
while(GPIO_ReadInputDataBit(KEY_GPIO_PORT,KEY_GPIO_PIN) == KEY_ON);
Delay_ms(20);//消抖
return KEY_ON;
}
else
{
return KEY_OFF;
}
}
初始化函数我们这里设置为上拉输入模式。按键检测函数Key_State函数中用到了对GPIO数据输入寄存器IDR指定pin口的数据读取函数GPIO_ReadInputDataBit,用法和前面得GPIO_ReadOutputDataBit一样,有2个参数,分别是端口GPIOX和对应pin口。按键检测中需要Delay函数延时20ms进行消抖处理,两处消抖对应我们上节画的那个示意图。
15.2.4 主函数设计
主函数main.c的设计就非常简单了,首先是对硬件LED和KEY对应GPIO口初始化,然后就是检测按键是否按下,如果按下就反转LED灯的状态,代码如下:
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "LED.h"
#include "Key.h"
int main(void)
{
/* LED端口初始化 */
LED_Init();
/* 按键端口初始化 */
Key_Init();
/* 轮询按键状态,若按键按下则反转LED */
while(1)
{
if( Key_State()== KEY_ON)
{
/*LED反转*/
LED_Toggle();
}
}
}
资源已经上传:
15.3 触类旁通
前面是最简单的一个输入检测和输出控制的实例,掌握了这个实例,我们用STM32可做的事就非常多了,无非是输入的硬件和输出控制的硬件的变化,但操作方式是一模一样的。比如输入硬件可以换成光敏元器件,根据亮度的不同会反馈高低电平,我们可以根据这个高低电平控制灯的亮灭,例如我们常见的日控灯。也可以将输入器件换成烟雾检测器件,输出变成蜂鸣器,检测到烟雾检测器件输入的信号,就给蜂鸣器高低电平实现鸣叫。在后面时钟章节,我们可以做一个类似的实验。
参考资料:
【1】哔站江协科技STM32入门教程
【2】《STM32单片机原理与项目实战》刘龙、高照玲、田华著
【3】《ARM Cortex-M3嵌入式原理及应用》黄可亚著
【4】《STM32嵌入式微控制器快速上手》陈志旺著
【5】《STM32单片机应用与全案例实践》沈红卫等著
【6】《野火STM32开发指南》
【7】《正点原子STM32开发指南》