查看: 1752|回复: 0

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

[复制链接]
  • TA的每日心情
    慵懒
    2015-5-29 12:01
  • 签到天数: 11 天

    连续签到: 1 天

    [LV.3]偶尔看看II

    发表于 2013-7-16 17:29:13 | 显示全部楼层 |阅读模式
    分享到:
        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灭,如此循环。
    回复

    使用道具 举报

    您需要登录后才可以回帖 注册/登录

    本版积分规则

    关闭

    站长推荐上一条 /2 下一条

    手机版|小黑屋|与非网

    GMT+8, 2024-4-20 06:59 , Processed in 0.120454 second(s), 18 queries , MemCache On.

    ICP经营许可证 苏B2-20140176  苏ICP备14012660号-2   苏州灵动帧格网络科技有限公司 版权所有.

    苏公网安备 32059002001037号

    Powered by Discuz! X3.4

    Copyright © 2001-2020, Tencent Cloud.