MSP430FR6989中断 第八章

 

4.5 中断函数练习
 

下面我们以P1端口的中断为例,练习一下如何编写一个完整的带有中断的程序。练习中我们要实现的功能是使用I/O口中断来读取按键状态,并根据按键状态来控制LED灯。在这个练习中涉及到的中断相关知识点包括:

 

  • 设置中断向量
  • 使能中断
  • 创建中断服 务函数(ISR)


 

下面我们来进入具体的练习。

 

练习目标:利用中断读取MSP430FR698X LaunchPad上按键S2的状态,并根据该状态控制红色LED灯。每按下S1一次,LED改变一次亮灭状态。

 

在第二章中,我们曾经介绍过MSP430FR698x LaunchPad的引脚定义。在本练习中,我们需要检测按键S1的状态,S1与MSP430FR698x 的P1.1端口相连;另外我们还要控制绿色的LED1,它与P1.0端口相连。

 

MSP430FR698x 上的按键S1是低电平有效,也就是说当按键被按下时,P1.1为低电平;而当按键没有被按下时,P1.1为高电平。我们要检测按键按下的动作,实际上就是检测P1.1端口上的电平变化。当P1.1端口上出现一个下降沿(即电平从高变低)时,就可以认为出现了按键被按下的动作。

 

本练习的程序可以分为3个部分,首先需要对GPIO模块做初始化,以配置相应的输入输出功能,并打开中断使能位;其次在主程序中还是要写一个无限循环,不过由于现在按键的检测是通过中断完成的,因此循环中CPU其实什么事情也不用做;最后还要写一个中断服 务函数,在里面设定中断事件发生后要执行的任务。


 

1)     初始化

 

初始化时,我们需要完成下列步骤:

 

        i)      将P1.0设为输出,P1.1设为输入;

        ii)     使能P1.1的内部上拉电阻;

        iii)    使能P1.1引脚中断;

        iv)    将P1.1中断设为下降沿中断;

        v)     复位P1.1的中断标志位

 

其中第ii步是为了实现按键S1的低电平有效,GPIO的初始化和上拉电阻设置在第2章都有介绍过,可以复习第二章的对应部分。

 

完成GPIO初始化后,请不要忘记使用_BIS_SR(GIE)指令来使能全局中断,这样才能使CPU对中断信号做出反应。

 

初始化的具体代码如下:

    WDTCTL = WDTPW | WDTHOLD;               // Stop WDT

    // Configure GPIO
    P1DIR |= BIT0;                          // Clear P1.0 output latch for a defined power-on state
    P1DIR &= ~BIT1;                        //配置BIT1为输入
    P1OUT|=BIT1;                            //配置 BIT1为1
    /*当GPIO作为输入时,PxREN和PxOUT一起配合设置内部的上/下拉电阻,
     *PxREN置1时使能上/下拉电阻,PxOUT置1时上拉,置0时下拉*******/
    P1REN |= BIT1;                          //配置BIT1上拉
    P1IE |= BIT1;                                                // P1.1 interrupt enabled
    P1IES |= BIT1;                                              // P1.1 Hi-low edge
    P1IFG &= ~BIT1;                                         // P1.1 IFG cleared

    _BIS_SR(GIE);                                               // Enable global interrupt

    PM5CTL0 &= ~LOCKLPM5;                   // Disable the GPIO power-on default high-impedance mode


 

2)     while循环

 

初始化完成后,需要写一个while循环,由于现在按键检测是通过中断功能自动完成的,因此在这个循环中CPU其实没有做任何事情,只是简单地等待中断事件的发生。当按键按下的一刹那(即P1.3的下降沿),CPU将检测到中断事件,并自动进入对应的中断服 务函数。

  1. int main(void)
  2. {
  3.   ......
  4.   while (1) {
  5.     __no_operation();                                 // Do nothing
  6.   }
  7. }

复制代码


 

3)     中断服 务函数

 

下面我们要编写P1.1对应的中断服 务函数,来告诉CPU中断事件(即按键被按下)发生后需要执行的任务。

 

编写中断服 务函数时,首先要找到对应的中断向量。本练习中我们使用的是P1端口的中断,其中断向量名为PORT1_VECTOR。接下来按照4.4节中的说明编写中断服 务函数。

 

中断服 务函数里具体要做的事情包括:

 

        i)       翻转P1.0的电平;

        ii)      清除P1.1中断标志位。

 

这样,当每一次按下S1按键时,LaunchPad上的红色色LED灯就会改变状态。

  1. #pragma vector=PORT1_VECTOR
  2. __interrupt void Port_1(void)
  3. {
  4.   P1OUT ^= BIT0;                            // P1.0 toggled
  5.   P1IFG &= ~BIT1;                           // P1.1 IFG cleared
  6. }

复制代码


 

完整代码

 

把上述步骤连起来,就完成了一个完整的GPIO中断程序。完整代码如下:

  1. #include
  2. int main(void)
    {
        WDTCTL = WDTPW | WDTHOLD;               // Stop WDT

        // Configure GPIO
        P1DIR |= BIT0;                          // Clear P1.0 output latch for a defined power-on state
        P1DIR &= ~BIT1;                        //配置BIT1为输入
        P1OUT|=BIT1;                            //配置 BIT1为1
        /*当GPIO作为输入时,PxREN和PxOUT一起配合设置内部的上/下拉电阻,
         *PxREN置1时使能上/下拉电阻,PxOUT置1时上拉,置0时下拉*******/
        P1REN |= BIT1;                          //配置BIT1上拉
        P1IE |= BIT1;                                                // P1.1 interrupt enabled
        P1IES |= BIT1;                                              // P1.1 Hi-low edge
        P1IFG &= ~BIT1;                                         // P1.1 IFG cleared

        _BIS_SR(GIE);                                               // Enable global interrupt

        PM5CTL0 &= ~LOCKLPM5;                   // Disable the GPIO power-on default high-impedance mode

        while (1) {
          __no_operation();                                 // Do nothing
        }
    }

    #pragma vector=PORT1_VECTOR
    __interrupt void Port_1(void)//中断服务函数名在fr6989.h里面
    {
      P1OUT ^= BIT0;                            // P1.6 toggled
      P1IFG &= ~BIT1;                           // P1.3 IFG cleared
    }

复制代码

 

 

 

4.1 什么是中断
 

中断是单片机内非常重要的一个功能。在单片机内部,CPU就像是主心骨,能力强责任大,最重要的任务都需要它来完成。同时正是因为CPU性能较强,因此CPU运行时要消耗的功耗也是很大的。而中断就像是CPU的一个助理,当出现一些特定事件时助理会主动提醒CPU,让CPU及时知道并做出反应。有了中断,CPU不仅可以一心多用,同时处理多个任务,而且在不是必须工作时CPU可以解放出来,这样可以大大节省单片机的功耗。

 

以下是几个常见的中断应用场景:

 

  • GPIO输入状态的检测:如果没有GPIO中断,CPU必须不断通过读取GPIO引脚状态来知道目前的输入是什么。有了GPIO中断,CPU就可以解放出来,执行其他更重要的任务,或是进入休眠状态,直到GPIO中断信号产生时再唤醒CPU去对GPIO输入状态的改变做出响应;
  • 利用定时器中断实现延时:之前章节中我们需要延时的话,都是通过执行一个空循环来实现的,这种方式无疑是对CPU资源的“浪费”。而有了定时器中断,我们就可以给定时器设定一个固定的时间间隔,在到达设定时间之后通知CPU进入定时器中断。这就像一个倒计时的闹钟一样,在闹钟响起之前,CPU无需消耗资源。

 

总之中断起到的作用就是提醒,就像接电话的过程一样,正常情况下CPU按照程序一步一步的执行,当有电话进来时(中断事件发生时),CPU会暂时放下手里的工作,处理电话中的事情,处理完之后再继续投入之前的工作。



轮询 vs 中断
 

要让CPU检测到一件事情发生有2种方法,一种叫“轮询”,一种叫“中断”。就好像收快递,如果没有短信或电话提醒,那只能不断的到快递点去询问“快递到了吗?”,这种情况就是轮询。而中断就是提醒短信,只要收到短信后再到快递点去取件就可以了。        MSP430FR6989中断 第八章_第1张图片                 

这样一对比就发现,中断比轮询要省力得多。对于单片机也是一样,有了中断之后平时单片机可以去处理其他任务,甚至进入休眠状态,只要等中断事件发生时再醒来处理就可以了,处理完了还可以继续睡大觉。


一个例子就是我们第二章学过的按键状态检测,当时我们用的就是轮询的方式,CPU反复的读取I/O口的状态,判断按键是否被按下。这种方式下CPU一直被占用。而本章中我们就要学会使用中断来处理按键,CPU占用率会大大降低。

MSP430FR6989中断 第八章_第2张图片

 

上图左边就是轮询方式检测按键,右边则是中断方式来实现。请注意右边的中断服 务函数,中断服 务函数里面就是单片机收到中断信号后具体要做的事情。MSP430的中断服 务函数有固定的格式。

  • #pragma关键字后面是中断向量,中断向量决定了这个中断函数响应的是哪一个中断源。例如这里的PORT1_VECTOR就代表来自P1 I/O口的中断。
  • __interrupt(注意有两个下划线)关键字会告诉编译器这个函数是一个中断服 务函数,后面的函数名(rx)是编程者可以自行定义的。


另外,要想使用中断,必须开启中断使能位,同时在对应外设中设置好中断功能(例如将GPIO引脚设为中断输入引脚)。

 

 

4.2 中断的工作过程
 

要想学会使用中断,需要先了解一下中断的工作过程。一个中断过程可以分为4个阶段。


 

1)   中断事件发生

 

首先,中断事件的发生是一个中断的起点。中断事件指的是可以导致CPU进入中断的外部事件,MSP430中有许多外设可以产生中断事件,例如串口、GPIO、定时器、ADC等等。

 

MSP430FR6989中断 第八章_第3张图片

以GPIO为例,I/O口上的电平变化就可以是一个中断事件,如果I/O口外接按键,那么按键按下或抬起所造成的电平变化就可以导致中断事件发生。MSP430系列大部分型号允许两组I/O口(P1和P2)作为中断输入。


 

2)   中断标志位被置位

 

当中断事件被单片机探测到之后,对应的中断标志位(interrupt flag,简称IFG)就会被自动置位(置为1)。置位之后除非进行复位,否则中断标志位会一直保持置位状态。这样做的好处是当有些中断事件发生的时间很短时,中断信号不会被CPU所忽略。MSP430的外设中有该外设中断所对应的IFG寄存器,中断事件发生时这些寄存器就会被改写。

 

MSP430FR6989中断 第八章_第4张图片

 

中断信号是如何到达CPU的呢?中断事件发生以后,对应的外设IFG寄存器被置位,但这并不意味着CPU能够接收到中断信号,要想CPU能接收到中断信号,必须将中断使能位(Interruptenable bits,简称IE)置为1。

 

MSP430FR6989中断 第八章_第5张图片

 

中断使能位的存在是使得CPU能够自由选择对哪个中断事件作出响应。在单片机工作过程中,可能会产生多个不同外设的中断事件,例如定时器溢出、串口接到数据、I/O口电平变化等,但单片机CPU并不一定想对所有这些中断事件作出响应。有了中断使能位,就像有了开关,CPU就可选择只响应那些需要响应的中断事件。

 

默认情况下,所有中断使能位都是关闭的(除了看门狗以外)。我们需要通过编程来打开需要的中断使能位。例如想要检测P1.3口所连接的按键,就要将P1端口的中断使能寄存器(P1IE)中的BIT3置为1。

 

除了上述中断使能位以外,CPU中还有一个全局中断使能位,它相当于一个总开关。MSP430的全局中断使能位称为GIE,它在SR寄存器中。


 

3)   CPU响应中断

 

现在我们假设一个中断事件已经发生,IFG也已被置位,对应的中断使能位也已经打开,这样我们确保中断信号能够顺利到达CPU。那么CPU接收到中断信号之后如何做出反应呢?

 

CPU接收到中断信号之后,会暂停正在执行的工作,并执行中断服 务函数(interrupt ser-vice routine,简称ISR)。这句话说起来简单,但实际上CPU为了保证执行完中断服 务函数之后能够顺利的回到之前的工作,需要做一系列的准备工作,如下图中第3项所述。

 

MSP430FR6989中断 第八章_第6张图片

 

在执行完当前一条指令后,CPU会保存当前程序计数器(PC)的值,这样就记录下现在程序执行到的位置,以便中断结束后能返回主程序继续执行。同时状态寄存器SR的值也会被保存起来。接下来CPU会将SR寄存器清零,这将使单片机退出所有低功耗状态以便执行中断程序。另外SR寄存器中的全局中断使能位GIE也将被禁用,这样就避免了在执行中断服 务函数时CPU再响应其他中断。退出中断后CPU会重新取回SR寄存器的值,程序回到进入中断之前的位置继续执行。

 

接下来中断向量的地址会被加载到程序计数器(PC)中,这样就会进入到中断服 务函数的入口开始执行中断程序。CPU之所以能识别出中断的源头,是因为每一个中断标志位(IFG)是与一个中断服 务函数(ISR)一一对应的,这个对应关系存在一个表里面,这个表叫做中断向量表。例如P1端口的中断就与中断向量PORT1_VECTOR对应,因此检测到P1口的中断信号后就会自动进入PORT1_VECTOR对应的中断服 务函数。更多关于中断向量表的知识将在后续章节中详细介绍。


 

4)  执行中断服 务函数

 

中断服 务函数(ISR)里的内容是由用户编程的,想要单片机在中断时执行什么任务,就在中断服 务函数中编写相应指令。例如想要实现按键按下时LED灯亮起,那么就在GPIO中断函数中加入LED控制的语句。

MSP430FR6989中断 第八章_第7张图片

 

中断服 务函数所做的事情由上图第4项描述,其中最关键的就是红色的“Runyour interrupt’s code”。不过需要注意在此之前,还需要判断是否为“grouped interrupt”。由于MSP430的中断资源是很宝贵的,很多中断会共用一个中断向量入口。例如前面提到的P1端口中断,当P1口的8个I/O口中的任何一个检测到中断事件时,都会进入中断服 务函数。这时就需要在中断服 务函数中先查询中断标志位,以判断究竟是哪个I/O口“出事”了。

 

4.3 中断优先级和中断向量表
 

MSP430的很多外设都能触发中断,那么如果多个中断同时被触发会怎么样呢?之前在介绍中断工作过程时提到过,MSP430在进入中断时会自动关闭全局中断使能位(GIE),所以如果MSP430已经在一个中断里,是不能再进入另一个中断的,这时另一个(或多个)中断排队等候,等到当前中断结束后再进入下一个中断。

 

如果有多个中断同时在等候,需要注意它们的优先级并不是由时间顺序决定的,而是由MSP430本身规定的中断优先级来决定,这个优先级在中断向量表里可以看到。所以在当前中断结束之后,CPU会自动按照中断优先级来依次执行。

 

说了这么多,下面我们就来见一见中断向量表的“真身”,中断向量表在MSP430G2553 datasheet的“Interrupt VectorAddresses”部分可以找到。

 

MSP430FR6989中断 第八章_第8张图片

 

表中第一列是中断事件来源的名称;第二列是对应的中断标志位;第三列表示中断的性质,其中non-maskable是无法关掉的中断,即不受GIE控制的中断,maskable是可以被GIE关掉的中断;第四列是中断向量在存储器中的位置;最后一列就是中断优先级。

 

中断向量表中大多数中断属于“grouped interrupt”,即多个中断源共用一个中断向量入口。例如P1.0-P1.7中断都使用P1中断这一个入口。要识别中断具体来自哪一个I/O口,需要查看P1IFG寄存器,P1IFG共8位,分别对应P1.0-P1.7,当某个引脚出现中断事件时对应的P1IFG.x就会被置位,在退出中断之前一定不要忘记手动将P1IFG.x复位。另外P1IE是P1口的中断使能寄存器,也有8位,可以分别控制每个I/O口的中断使能。

 

 

 

 

4.4 如何写一个中断函数
 

要写一个带有中断的程序,需要做下面几件事情:

 

1)     配置外设中和中断有关的寄存器,例如I/O口中断是上升沿触发还是下降沿触发,定时器中断的计数方式和定时值等等。

2)     依照中断服 务函数的模板写中断服 务函数,添加中断后要干什么的代码。

3)     使能外设的中断,使能全局中断(GIE)

4)     一旦中断发生,CPU停下主函数中的任务,并标记位置,进入中断服 务函数,执行完中断服 务函数之后回到主函数标记位置处继续运行。




中断服 务函数
 

我们想要单片机在中断里执行什么任务,都是由中断服 务函数决定的。中断服 务函数的编写和一般的函数略有不同,我们以P1端口的中断服 务函数来做一个说明:

  1. #pragma vector=PORT1_VECTOR//中断服务函数名在fr6989.h里面
    __interrupt void Port_1(void)
    {
      P1OUT ^= BIT0;                            // P1.6 toggled
      P1IFG &= ~BIT1;                           // P1.3 IFG cleared
    }

复制代码

 

1) #pragma vector=PORT1_VECTOR

 

#Pragma是编译器指令,是告诉编译器将函数与中断向量连接起来。“vector=”后面是中断向量地址的宏定义,例如P1口中断就是PORT1_VECTOR,定时器中断就是TIMER0_A1_VECTOR。

 

不同外设的中断向量名在哪里找呢?打开CCS可以跳转到一个名为msp430.h的头文件。在里面在找到"msp430fr6989.h"这个头文件,搜索Interrupt Vectors, 这个下面包含了所有寄存器位的宏定义,包括中断向量的宏定义(如下图)。其中包含了所有中断向量的名称。例如P1端口的中断向量名就是PORT1_VECTOR。

 

MSP430FR6989中断 第八章_第9张图片


 

2) __interrupt void Port_1(void)

 

__interrupt关键字表明这是一个中断服 务函数,CPU见到这个关键字以后就会去做中断之前的准备工作。Port_1是用户自己取的函数名称,这个名称可以任意命名。


 

3)  中断服 务函数的具体内容

 

中断服 务函数的内容依据中断的不同种类有所差别。退出中断前一定不要忘记将中断标志位复位。

本文参考:https://e2echina.ti.com/group/universityprogram/students/f/11/p/149721/424157#424157 

你可能感兴趣的:(MSP430FR6989)