MSP430按键消抖

在介绍呼吸灯的时候,CPU的典型工作方式就是一个死循环,不停的查看是否应该点亮LED。但是,一直忙同一件事情也很无聊,可以停下来干干别的,这就是中断的工作方式。

1、效果展示

buttonjitter

上图是一个采用中断方式工作的按键示例,单片机正常工作期间可以处理其他任务,当按下按键时,CPU被打断触发在红绿灯之间切换,然后返回继续处理任务。

2、中断的正经介绍

在我最开始学习中断的时候,印象最深的一个例子就是中断就像一个工作的人,他正在忙碌的处理手里的文件,但是突然桌上的电话响起来了,他不得不停下手头的工作去接电话。接电话的过程中,领导刚好叫他处理一个重要的事情,他只好简短的结束电话以后去忙领导交待的事情。圆满完成后,他回到办公室,找到手里文件夹书签的那一页,继续工作。

  • 这个例子十分的形象,中断就像是给单片机附于了更加灵活的工作能力,不在是一个死板的工作人员一样按部就班的按照日程表工作,不懂的灵活变通处理紧急任务。
  • 这些紧急任务中,有电话这种不紧急、不重要的任务,有关键人员要求的紧急任务,这样的机制在单片机中断中就叫做中断优先级
  • 为了忙完了这乱如麻的突发任务,回过头来还能轻松准确的找到手头看到那一页文件,我们需要夹个书签、做个记号,这就叫做中断里的保存现场

下面我们试试以简单易懂的讲解和例子分别看看怎么把单片机训练成一个熟练工。

2.1、430的中断

前面讲了什么是中断,中断有什么好处,那MSP430这个芯片有哪些中断呢?让我们来看看430中到底有多少人会来骚扰CPU的工作。

到这里我们要开始看芯片的datasheet了,datasheet就是一款芯片的使用说明书。由于MSP430家族有太多型号了,所以不可能每一款芯片说明书都把所有特性写全。所以430 x2xx系列有一个总的说明书,链接在此:MSP430x2xx 系列
用户指南
。然后具体到每一个芯片,其硬件的指标内如内存大小什么的不尽相同,这些指标就会有一个专门的说明书,链接在此:MSP430G2x53、MSP430G2x13 混合信号微控制器 datasheet (Rev. G) 。这些文档,都是在官网找到的,直接搜索具体的芯片型号,然后点击下面的几个地方可以找到,有中文也有英文的,一般来说英文的是最新的文档版本。
2553datasheet

从2553的datasheet中,第12也官方帮我们整理了430G2553上的中断源表,如下:
2553intsource

可以看到,芯片连通电源、复位、定时器、比较器、串口、引脚IO等都能够触发中断,本例中的按键,实际上就是利用的P1.3引脚IO中断。表格中还有一项需要我们注意的是是否可屏蔽属性,简单理解就是有些中断,你可以设置为不理它,但是有些中断,人在江湖,身不由己,它要是来了,必须得理它。

2.2、430的中断优先级

上面的表格,看到最优一列的优先级没有,从上到下,优(存)先(在)级(感)依次降低。这个表格完全没有必要记住,我们只需要知道有这么个事情存在,在编写代码的时候,如果涉及到使用了多个中断,注意合理安排各个中断之间的时机不要互相大家就好,这个后面有机会再介绍,现在先忘掉这些吧,记得我讲的书签那个比喻就OK了。

2.3、中断中的现场保护

下图是430的一个典型流程图,在430系列文档里有介绍(2553 datasheet不会写)。是不是看着巨麻烦,不用担心,现在没必要搞懂,等到后面有机会讲门电路和寄存器级的代码,我在细细描述下通用的单片机中断处理的机制。讲讲下面这段话究竟在做啥事情。现在先忘掉这些吧,记住我讲的那个书签的比喻就OK啦

  1. 任何当前执行的指令完成。
  2. 指向下一条指令的PC 被压入堆栈。
  3. SR 被压入堆栈。
  4. 如果在最后一个指令执行期间由多个中断出现,那么具有最高优先级的中断被选中并等待被处理。
  5. 在单一源标志上,中断请求标志自动复位。对于软件处理,多个源标志保持被设定。
  6. SR 被清除。这将终止任何低功耗模式。由于GIE 位被清除,之后的中断被禁用。
  7. 中断矢量的内容被载入到PC:程序继续在中断处理例程所处的地址上执行。

430intflow

3、中断的不正经操作

介绍说了这么多,都快忘记了那个开头的图片,那个是怎么做到的,我们来说说。

3.1、代码展示

main.c代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include "MSP430G2553.h"
#include "common.h"

int main(void)
{
// Stop watchdog timer to prevent time out reset
WDTCTL = WDTPW + WDTHOLD;
// P1.0和P1.6设置为输出,点灯用
P1DIR |= BIT6 + BIT0;
// 启动时6亮
P1OUT |= BIT6;
// 0灭
P1OUT &= ~BIT0;
// P1.3作为输入,接收按键的输入电压
P1DIR &= ~BIT3;
// P1SEL功能选择,0表示IO功能
P1SEL &= ~BIT3;
// 上拉电阻使能,作用我们后面讲模拟部分再说吧
P1REN |= BIT3;
// P1.3允许中断,默认是关的,不开不行
P1IE |= BIT3;
// 中断边沿选择为下降沿,因为上拉电阻存在,按下按键的时候电压从高变成接地的低,所以是下降沿
P1IES |= BIT3;
// 清零中断标记位,IFG(interrupt flag),一个字节,8位,分别对应P1.0 ~1.7
P1IFG = 0;
// 使能全局中断,相当于中断的总开关,所有中断都受这个总开关的限制
_EINT();
while (1)
{
// 这里随便干点啥
}
}

#pragma vector=PORT1_VECTOR
__interrupt void key_inte(void)
{
// 看下中断标志位,是不是1.3产生的,其他引脚产生的也会进来
// 当然这里我们只开了1.3的中断,不会有其他引脚触发进来
if (P1IFG & BIT3)
{
// 延时20毫秒消抖
delay_ms(20);
// 按照消抖原理,延时结束后,再判断P1.3的输入是否还是0电压
if ((P1IN & BIT3) == 0)
{
// 确实是按键按下,交换两等的亮暗状态
P1OUT ^= (BIT6 + BIT0);
}
}

// 不管是哪个脚触发的中断,本次全部清除标记,继续等待下一次中断
P1IFG = 0;
}

common.h代码,使用的时候在工程中新建一个common.h文件,并添加到工程中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/*
* common.h
*
* Created on: 2015年6月22日
* Author: xiaoyao
*/

#ifndef COMMON_H_
#define COMMON_H_

#include <msp430g2553.h>

/********************延时*******************************/
#define CPU_F ((double)1000000) //时钟频率0
#define delay_us(x) __delay_cycles((long)(CPU_F*(double)x/1000000.0))
#define delay_ms(x) __delay_cycles((long)(CPU_F*(double)x/1000.0))

#endif /* COMMON_H_ */
3.1、代码解释

1、为什么中断后要等20毫秒再判断?
jitter
如上图所示,机械的按键在按下和弹起时,电压的波动不是理想的立即变低然后变高,而是在变化时伴随着很多抖动和毛刺(有示波器的同学可以看下波形,没有的可以去掉延时这一行然后感受一下效果差异)。这也是本文标题按键消抖的来源。

2、common.h里面的是什么?
个人习惯喜欢在common.h里面封装一些公共的函数和宏定义,这样方便使用,每个工程直接引用就可以了。这里的延时就是。CPU_F是CPU主频率,430默认是1M,所以填1000000,后面如果做其他功能的时候将时钟频率调到16M或者32M或者32768HZ,都记得改一下这里的值,否则延时就不准了。delay_us和delay_ms分别是延时对应的微秒和毫秒。

3、上面的代码中,下面这两行的写法,第一行是固定的,这行告诉编译器,下面的函数是给P1引脚准备的,P1中断后,如果中断打开,让CPU直接跳到下面的函数执行。至于第二行函数名是什么,怎么起就随意了,只要前面加上__interrupt就行了。

#pragma vector=PORT1_VECTOR

__interrupt void key_inte(void)

4、上面有个地方讲现场保护,是不是发现为什么这里都没有?这是因为如果使用C语言而不是汇编,这些东西编译器都可以自动帮我们生成,按照上面第一点说的写法就可以了。

5、上面中断判断的代码中,有个坑新手容易踩进去,造成按键怎么按就没反应,就是下面这一行:

if ((P1IN & BIT3) == 0)

这一行很容易有人顺手写成if (P1IN & BIT3 == 0),这个结果是完全不一样的,因为&的优先级比==低,编译器会先判断BIT3==0,然后再把结果和P1IN与,BIT3是0x00001000,肯定不等于0,这样按键处理的函数怎么都走不到。附C语言运算符优先级表如下,平时写代码有一个诀窍就是如果记不住优先级,那么就把每一个你不确定的操作都像上面一样加上括号,这是万金油操作。

coperatorpri

s