零死角玩转stm32-初级篇之KEY(Polling)

作者:
上传时间为: 2013-07-16 05:29 PM
2013-07-16
阅读:

    7、KEY(Polling)

 在LED灯例程中我们已经简单体验了GPIO的强大之处。更强大的还在后头,野火开发板使用的芯片型号是STM32F103VET6,具有100个管脚,除去晶振输入、电源输入、Boot引脚,剩下的80个引脚均为GPIO。它们分布在GPIOA~GPIOE的5个端口组之中,每个小组有16个引脚,所有的GPIO引脚都可以用作外部中断源的输入,每个GPIO引脚可配置为8种模式,不同的引脚还有相应的复用功能,复用功能重映射 等,足以满足应用需求,也足以把初学者弄得晕头转向。

本章以按键工程为例,着重分析GPIO的模式配置。

  7.1 GPIO的8种工作模式

在初始化GPIO的时候,根据我们的使用要求,必须把GPIO设置为相应的模式。如LED例程中的GPIO引脚如果配置为模拟输入模式是必然会导致错误的。

我们配合GPIO结构图,来看看GPIO的8种模式及其应用场合:

图 71 GPIO结构图

图的最右端为I/O引脚,左端的器件位于芯片内部。I/O引脚并联了两个用于保护的二极管。

7.1.1四种输入模式

结构图的上半部分为输入模式结构。

接下来就遇到了两个开关和电阻,与VDD相连的为上拉电阻,与VSS相连的为下拉电阻。再连接到施密特触发器就把电压信号转化为0、1的数字信号存储在输入数据寄存器(IDR)。我们可以通过设置配置寄存器(CRL、CRH),控制这两个开关,于是就可以得到GPIO的上拉输入(GPIO_Mode_IPU )下拉输入模式(GPIO_Mode_IPD )了。

从它的结构我们就可以理解,若GPIO引脚配置为上拉输入模式,在默认状态下(GPIO引脚无输入),读取得的GPIO引脚数据为1,高电平。而下拉模式则相反,在默认状态下其引脚数据为0,低电平

而STM32的浮空输入模式(GPIO_Mode_IN_FLOATING)在芯片内部既没有接上拉,也没有接下拉电阻,经由触发器输入。配置成这个模式直接用电压表测量其引脚电压为1点几伏,这是个不确定值。由于其输入阻抗较大,一般把这种模式用于标准的通讯协议如I2C、USART的接收端。

模拟输入模式(GPIO_Mode_AIN )则关闭了施密特触发器,不接上、下拉电阻,经由另一线路把电压信号传送到片上外设模块。如传送至给ADC模块,由ADC采集电压信号。所以使用ADC外设的时候,必须设置为模拟输入模式。

7.1.2四种输出模式

结构图的下半部分为输出模式结构。

线路经过一个由P-MOS和N-MOS管组成的单元电路。而所谓推挽输出模式,则是根据其工作方式来命名的。在输出高电平时,P-MOS导通,低电平时,N-MOS管导通。两个管子轮流导通,一个负责灌电流,一个负责拉电流,使其负载能力和开关速度都比普通的方式有很大的提高。推挽输出的供电平为0伏,高电平为3.3伏。

开漏输出模式时,如果我们控制输出为0,低电平,则使N-MOS管导通,使输出接地,若控制输出为1 (无法直接输出高电平),则既不输出高电平,也不输出低电平,为高阻态。为正常使用时必须在外部接上一个上拉电阻。它具“线与”特性,即很多个开漏模式 引脚连接到一起时,只有当所有引脚都输出高阻态,才由上拉电阻提供高电平,此高电平的电压为外部上拉电阻所接的电源的电压。若其中一个引脚为低电平,那线路就相当于短路接地,使得整条线路都为低电平,0伏。

STM32的GPIO输出模式就分为普通推挽输出(GPIO_Mode_Out_PP )、普通开漏输出 (GPIO_Mode_Out_OD)及复用推挽输出(GPIO_Mode_AF_PP )、复用开漏输出(GPIO_Mode_AF_OD )。

普通推挽输出模式一般应用在输出电平为0和3.3伏的场合。而普通开漏输出一般应用在电平不匹配的场合,如需要输出5伏的高电平,就需要在外部接一个上拉电阻,电源为5伏,把GPIO设置为开漏模式,当输出高阻态时,由上拉电阻和电源向外输出5伏的电平。

对于相应的复用模式,则是根据GPIO的复用功能来选择的,如GPIO的引脚用作串口的输出,则使用复用推挽输出模式。如果用在IC、SMBUS这些需要线与功能的复用场合,就使用复用开漏模式。其它不同的复用场合的复用模式引脚配置将在具体的例子中讲解。

在使用任何一种开漏模式,都需要接上拉电阻。

7.2 按键实验分析

了解了GPIO的8种工作模式之后,立即进行一下小测。如果采用以下的电路,我们的按键GPIO端口应该如何进行配置?

有两个方案可以选择,一是采用上拉输入模式,因为按键在没按下的时候,是默认为高电平的,采且内部上拉模式正好符合这个要求。

第二个方案是直接采用浮空输入模式,因为按照这个硬件电路图,在芯片外部接了上拉电阻,其实就没必要再配置成内部上拉输入模式了,因为在外部上拉与内部上拉效果都是一样的。

野火STM32开发板 按键 硬件原理图(GPIO端口相对第一版有小改动)

图 72 野火STM32开发板按键硬件图

7.3按键代码分析

7.3.1实验描述及工程文件清单

7.3.2配置工程环境

本按键实验中用到了GPIO和RCC片上外设,所以要把外设函数库文件FWlib/stm32f10x_gpio.cFWlib/stm32f10x_rcc.c文件添加到工程模板之中。实验中还使用了LED灯,为了重用代码,我们把在前面写好的led.cled.h用户文件复制到USER目录下,并添加到工程之中。配置工程环境最重要的一步就是别忘记在stm32f10x_conf.h文件中把使用到的外设头文件包含进来。

/**

********************************************************

* @file    Project/STM32F10x_StdPeriph_Template/stm32f10x_conf.h

* @author  MCD Application Team

* @version V3.5.0

* @date    08-April-2011

* @brief   Library configuration file.

*****************************************************/

#include "stm32f10x_gpio.h"

#include "stm32f10x_rcc.h"

7.3.3 main文件

顺着代码的执行流程,从main函数开始分析,这样阅读和分析别人写的代码更有条理。

/*

* 函数名:main

* 描述  :主函数

* 输入  :无

* 输出  :无

*/

int main(void)

{

/* config the led */

LED_GPIO_Config();

LED1( ON );

/*config key*/

Key_GPIO_Config();

while(1)

{

if( Key_Scan(GPIOE,GPIO_Pin_5) == KEY_ON  )

{

/*LED1反转*/

GPIO_WriteBit(GPIOC, GPIO_Pin_3,

(BitAction)((1-GPIO_ReadOutputDataBit(GPIOC, GPIO_Pin_3))));

}

}

}

由于采用的为3.5版本的库,上电后,启动文件已经调用了SystemInit() 将我们的系统时钟SYSCLK配置为72MHz。接着进入到main函数,第一步先调用了在LED灯例程中编写的LED_GPIO_Config(),配置LED用到的I/O。再使用LED1( ON ) 宏,把LED设置为点亮状态。为了使用LED这部分代码,我们只要把前面写的led.cled.h文件复制一份,放到本工程目录下,把led.c添加到工程就可以了,这样重用代码,变得非常方便。关于这部分的具体分析可参考LED代码讲解部分。

7.3.4 GPIO初始化配置

现在我们分析一下紧接下来调用到的Key_GPIO_Config()函数。

/*

* 函数名:Key_GPIO_Config

* 描述  :配置按键用到的I/O口

* 输入  :无

* 输出  :无

*/

void Key_GPIO_Config(void)

{

GPIO_InitTypeDef GPIO_InitStructure;

/*开启按键端口(PE5)的时钟*/

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE,ENABLE);

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;

//  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;

GPIO_Init(GPIOE, &GPIO_InitStructure);

}

Key_GPIO_Config() 跟LED的GPIO初始化函数LED_GPIO_Config()是很类似的 ,区别只是在这个函数中,要开启的GPIO外设时钟为GPIOE的时钟,并且把检测按键用的引脚PE5的模式,设置为适合按键应用的上拉输入模式(由于接了外部上拉电阻,也可以使用浮空输入,读者可自行修改代码做实验)。在这个函数的第15行,读者注意到这行代码是被注释 掉的,若GPIO被设置为输入模式,是不需要设置GPIO端口的最大输出速度的,当然,如果配置了这个速度也没关系,GPIO_Init()函数会自动忽略它的。

RCC_APB2PeriphClockCmd()GPIO_InitStructure.GPIO_Pin 的输入参数设置之中,我们可以用符号“|”,同时配置多个参数。如:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE|RCC_APB2Periph_GPIOC,ENABLE);

输入参数为RCC_APB2Periph_GPIOE| RCC_APB2Periph_GPIOC ,这样调用之后,就把GPIOE和GPIOC的时钟都开启了。

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5|GPIO_Pin_6;

以上代码则表示将要同时配置GPIO端口的Pin5和Pin6。

7.3.5利用库的数据类型

回到main函数中的应用代码,初始化了按键的GPIO之后,就在死循环里不断调用一个函数Key_Scan(),用于扫描按键是否被按下。我们使用keil使用技巧中介绍的“GO ToDefinition of ”功能追踪它的定义:

/*

* 函数名:Key_Scan(GPIO_TypeDef* GPIOx,u16 GPIO_Pin)

* 描述  :检测是否有按键按下

* 输入  :GPIOx:x 可以是 A,B,C,D或者 E

GPIO_Pin:待读取的端口位

* 输出  :KEY_OFF(没按下按键)、KEY_ON(按下按键)

*/

u8 Key_Scan(GPIO_TypeDef* GPIOx,u16 GPIO_Pin)

{

/*检测是否有按键按下 */

if(GPIO_ReadInputDataBit(GPIOx,GPIO_Pin) == KEY_ON )

{

/*延时消抖*/

Delay(10000);

if(GPIO_ReadInputDataBit(GPIOx,GPIO_Pin) == KEY_ON )

{

/*等待按键释放 */

while(GPIO_ReadInputDataBit(GPIOx,GPIO_Pin) == KEY_ON);

return  KEY_ON;

}

else

return KEY_OFF;

}

else

return KEY_OFF;

}

相信延时消抖的原理大家在学习其它单片机的时候是非常了解了,本函数的功能就是扫描输入参数中指定的引脚,检测其电平变化,并作延时消抖处理,最终对按键消息进行确认。

1.利用GPIO_ReadInputDataBit() 读取输入数据,若从相应引脚读取得的数据等于0 (KEY_ON),低电平,表明可能有按键按下,调用延时函数。否则返回KEY_OFF,表示按键没有被按下。

2.延时之后再次利用GPIO_ReadInputDataBit() 读取输入数据,若依然为低电平,表明确实有按键被按下了。否则返回KEY_OFF,表示按键没有被按下。

3.循环调用GPIO_ReadInputDataBit()一直检测按键的电平,直至按键被释放,被释放后,返回表示按键被按下的标志KEY_ON。

以上是按键消抖的流程,调用了一个库函数GPIO_ReadInputDataBit()。输入参数为要读取的端口、引脚,返回引脚的输入电平状态,高电平为1,低电平为0;

图 03 GPIO输入数据读取函数

但按键消抖并不是本小节的重点,而且这样的消抖在实际的工程应用中并无价值。重点是教会大家如何利用ST库定义的新数据类型来编写用户函数。

Key_Scan()函数的形参,其实跟GPIO_ReadInputDataBit()的形参是一样的,都是(GPIO_TypeDef* GPIOx,u16 GPIO_Pin) ,如果再在Key_Scan()的定义中加入断言 ,用于输入参数检查,看起来是不是很像ST 官方的库函数?其实这是野火写的一个用户函数。

这个例子告诉大家,在stm32f10x.h文件中的新数据类型,我们不但可以利用它们来定义变量,还应善于利用这些数据类型来编写用户函数。如这个Key_Scan()函数,由于使用了这些引脚类型形参,在其它不同的工程之中,我们就可以在调用时,通过输入不同的实参,来检测其它按键的引脚了。

如在调用Key_Scan()函数时,把实参改成(GPIOE,GPIO_Pin_6),就可以用Key-2来控制LED1啦(当然,GPIO_Pin_6要在Key_GPIO_Config()中初始化)。是不是很方便呢,利用官方的库,我们可以很方便地开发出这一类用户函数,这就是库的魅力呀!

7.3.6 实现LED反转

在main函数中,检测到有按键被按下之后,就开始执行LED反转的操作。

GPIO_WriteBit(GPIOC, GPIO_Pin_3,

(BitAction)((1-GPIO_ReadOutputDataBit(GPIOC, GPIO_Pin_3))));

这一段代码首先调用了GPIO_ReadOutputDataBit()函数,读取PC3的当前输出电平,然后再用1减去读取得的电平数据状态,相当于获取一个也当前输出相反的状态,再把这个相反的状态利用GPIO_WriteBit()函数写入到PC3,从而实现了输出状态取反的功能。大家会发现,这实在太复杂了,我们只不过是要取反输出,在51单片机可以直接PA0=~PA0就可以完成了,而在这里使用库的时候,我们竟然要先读取状态,再计算出反状态,最后再写入新状态。能不能也像单片机那样使用呢?答案是肯定的,我们可以采用Cortex-M3的位带操作方式,实现同样的功能。

7.3.7实验现象

将野火STM32开发板供电(DC5V),插上JLINK,将编译好的程序下载到开发板,LED1亮,按下按键时LED1灭,再按下按键时LED1灭,如此循环。

全部评论 ()
条评论
写评论

创建讨论帖子

登录 后参与评论
系统提示