2.1 多工程管理基础

多工程管理基础

(1)依照上节课讲解的方法新建一个工作空间,然后在工作空间中新建4个工程,分别是 led、key、ioConfig和interrupt。新建完成后,如图所示。单击Overview选项卡可以浏览整个工作空间,可以看到一个空间中包含了4个工程。右击led-Debug可以选中该工程,左上角也会显示当前被选中的工程。也可以单击底部的以工程名名称的选项卡来切换到对应的工程。
第2章:GPIO实验 - 图1

(2)为每个工程分别新建相应的组,新建完成后如下图所示。
第2章:GPIO实验 - 图2

(3)在工作空间的目录中新建一个code文件夹用来存放源代码,如图所示。工作空间的目录中含有一个以.eww结尾的工作空间文件。
第2章:GPIO实验 - 图3

(4)在code文件夹中新建4个与工程名称对应的子文件夹,用来存放相应的源代码,如图所示。
第2章:GPIO实验 - 图4

(5)分别在每个文件夹中新建以.c结尾源文件并添加到工程中,添加完成后如下图所示。
第2章:GPIO实验 - 图5

(6)接着读者还需要依照上节课讲解的方法配置这几个工程,配置完成后,即可进行开发。

2.2 GPIO输出实验——LED控制

接着上节课的内容,切换到LED工程,如图所示。
第2章:GPIO实验 - 图6

计算机的逻辑层与电路层

逻辑层

在计算机逻辑层中,一般是使用二进制数字(0或1)来控制对象或表示对象的状态,例如可以用1来表示开灯,用0来表示关灯,如图所示。
第2章:GPIO实验 - 图7

电路层

在计算机电路层中,一般是用高电平(3.3 v)来表示逻辑上的1,用低电平(0 v)来表示逻辑上的0。这个逻辑层和电路层的原理,初学者也许不好理解,但是可以继续往下学习,之后再逐步理解其中的含义。

芯片的引脚

芯片的引脚可以理解为从芯片里面引出来的导线,用于连接外部的元器件。
第2章:GPIO实验 - 图8
芯片的引脚一般可以划分为电源引脚、控制引脚还有GPIO。电源引脚一般用于给芯片供电;控制引脚一般和硬件设计有关,我们可以暂时忽略。

GPIO

GPIO的全称是General-Purpose Input / Output,中文意思是通用输入/输出,是我们常用的引脚。
一般来说,一个芯片会有多个GPIO,每个GPIO有两种工作模式,分别是输出信号模式和接收信号模式。

在输出信号模式时,我们可以通过代码来控制这个GPIO输出高电平(3.3 v)或者低电平(0 v)。在输入信号模式时,我们可以通过代码来检测这个GPIO是处于高电平还是低电平状态。

LED 简介

LED是Light Emitting Diode的缩写,中文意思是“发光二极管”,是一种能够把电转化为光的元器件。通俗地讲,LED就是我们常说的灯的一种。ZigBee开发板配备了一盏可以给开发者使用的LED,其原理图如图所示。
第2章:GPIO实验 - 图9

其中的D2表示LED,其右端接地(GND),因此右端的电压为0v。LED的左端依次连接着R12和IO_LED。IO_LED是CC2530的一个IO口,能够输出高电平(3.3v)或低电平(0v)。R12是一个稳压电阻,用于防止电路的电压过大而烧坏LED。

  • 当IO_LED输出高电平时,LED左端电压为3.3v,右端电压为0v,左端和右端形成了3.3v的电压差,因此LED被点亮;
  • 反之,当IO_LED输出低电平时,LED左端和右端电压均为0v,因此LED被熄灭。

如您因缺少硬件原理相关知识导致未能看懂电路图,需先补充相关知识。

CC2530的引脚简介

CC2530 配备了40个IO口,如图所示。
第2章:GPIO实验 - 图10

其中的P0_0~P0_7属于P0端口,P1_0~P1_7属于P1端口,P2_0~P2_5属于P2端口,这些IO口都可以通过编程的方式使用,接下来将会结合示例来讲解其使用方法。

查阅TI官方数据手册

(1)可以在本课程配套资源中找到CC2530数据手册,如图所示。
第2章:GPIO实验 - 图11

下载地址:https://gitee.com/study-j/zigbee/tree/master/%E8%AF%BE%E5%A4%96%E8%B5%84%E6%96%99%E5%8F%82%E8%80%83/CC2530%E6%95%B0%E6%8D%AE%E6%89%8B%E5%86%8C

(2)该数据手册的目录如下。
第2章:GPIO实验 - 图12

(3)展开《I/O端口》章节,其目录如下。
第2章:GPIO实验 - 图13

(4)此数据手册可以用作查阅工具书,供我们随时查阅CC2530的各个细节,例如使用GPIO时就可以找到《I/O 端口》这一小节,使用定时器时就可以找到定时器章节。

IO口的使用方法

相关寄存器简介

可以通过配置相关寄存器的方式来使用IO口,例如配置指定的IO口为输出信号模式并控制其输出高/低电平。CC2530中与IO口配置相关的寄存器如下。

  • P0:端口0配置寄存器
  • P1:端口1配置寄存器
  • P2 :端口2配置寄存器
  • PERCFG:外设控制寄存器
  • APCFG:模拟外设 I/O 配置寄存器
  • P0SEL :端口 0 功能选择寄存器
  • P1SEL :端口 1 功能选择寄存器
  • P2SEL :端口 2 功能选择寄存器
  • P0DIR :端口 0 方向寄存器
  • P1DIR :端口 1 方向寄存器
  • P2DIR :端口 2 方向寄存器
  • P0INP :端口 0 输入模式寄存器
  • P1INP :端口 1 输入模式寄存器
  • P2INP :端口 2 输入模式寄存器
  • P0IFG :端口 0 中断状态标志寄存器
  • P1IFG :端口 1 中断状态标志寄存器
  • P2IFG :端口 2 中断状态标志寄存器
  • PICTL :中断边缘寄存器
  • P0IEN :端口 0 中断掩码寄存器
  • P1IEN :端口 1 中断掩码寄存器
  • P2IEN :端口 2 中断掩码寄存器
  • PMUX :掉电信号 Mux 寄存器
  • OBSSEL0 :观察输出控制寄存器 0
  • OBSSEL1 :观察输出控制寄存器 1
  • OBSSEL2 :观察输出控制寄存器 2
  • OBSSEL3 :观察输出控制寄存器 3
  • OBSSEL4 :观察输出控制寄存器 4
  • OBSSEL5 :观察输出控制寄存器 5
    使用P0_4 IO口
    在ZigBee开发板中,CC2530的 P0_4与LED连接,因此可以通过CC2530的 P0_4来控制LED的亮灭。可以对P0_4相关的寄存器进行配置来控制P0_4 的状态和行为。P0_4相关的寄存器及相应说明见表。
寄存器 说明
P0 8位寄存器,8个位分别与P0_0~P0_7一一对应,分别用于设置或读取这8个IO口的电平状态
P0SEL 8位寄存器,8个位分别与P0_0~P0_7一一对应,分别配置这8个IO口的功能。如果IO口对应的位为0,表示该IO口用于通用输入/输出;如果为1,表示用于特定的功能
P0DIR 8位寄存器,8个位分别与P0_0~P0_7一一对应,分别配置这8个IO口的通信方向。如果IO口对应的位为0,表示该IO口处于输入信号模式;如果为1,表示处于输出信号模式

根据表格说明,可以按如下方式配置P0_4的相关寄存器和控制其输出电平,代码如下:

  1. // 把P0_4配置为通用输出IO口
  2. P0SEL &= ~(1<<4); // 把P0SEL寄存器的第4位设置为0,表示把P0_4配置为通用IO口
  3. P0DIR |= (1<<4); // 把P0DIR寄存器的第4位设置为1,表示把P0_4配置为输出信号模式
  4. P0_4=0//输出低电平
  5. P0_4=1//输出高电平

其中的P0_4是在头文件ioCC2530.h中定义的,在配置好相关寄存器后,给其赋0或1即可控制其输出电平状态。这里简单讲解一下向左位移运算符<<、取反运算符~、按位与运算符&和按位或运算符|,以便加深读者对代码的理解。
(1)向左位移运算符 <<
把所有二进制数字向左移动指定的位数,高位的数字移出(舍弃),低位的空位补0。例如“1<<4”表示把0000 0001中的所有数字向左移动4位,并且高位的0移出舍弃,低位的空位补0,所以运算结果为0001 0000。
(2)取返运算符 ~
把所有二进制数字取反,例如对0001 0000进行取反运算后的结果为1110 1111。
(3)按位与运算符&
按位与运算规则是把两边的数转换为二进值数,然后把两个数的对应位逐位进行与运算,与运算的规则为1&1=1、1&0=0、0&1=0和0&0=0,例如1101 1101和1110 1111进行按位与运算后的结果为1100 1101。
(4)按位或运算符|
按位或运算规则是把两边的数转换为二进值数,然后把两个数的对应位逐位进行或运算,或运算的规则为1&1=1、1&0=1、0&1=1和0&0=0,例如1101 1101和0001 0000进行按位与运算后的结果为1101 1101。

编写LED控制代码

学习了相关原理后,可以编写代码来控制LED,示例代码如下:

  1. //51单片机入门/2.GPIO实验/Workspace/code/led/led.c
  2. #include "ioCC2530.h"
  3. #include <stdio.h>
  4. #include <stdint.h>
  5. //P0_4由头文件ioCC2530.h定义
  6. #define LED P0_4
  7. //定义LED的开关状态和对应的值
  8. #define LED_ON 1
  9. #define LED_OFF 0
  10. static void delayMs(uint16_t nMs);
  11. static void initLed(void);
  12. void main()
  13. {
  14. initLed();//初始化LED
  15. while(1) {
  16. printf("Set led to on!\r\n");
  17. LED = LED_ON;//开启LED
  18. delayMs(500);//延时0.5s后才继续往下执行程序
  19. printf("Set led to off!\r\n");
  20. LED = LED_OFF;//关闭LED
  21. delayMs(500);//延时0.5s后才继续往下执行程序
  22. } /* while */
  23. }
  24. /**
  25. * @fn delayMs
  26. *
  27. * @brief 让程序延后指定的时间才接着运行
  28. *
  29. * @param nMs - 时间长度,以毫秒为单位,值范围:165535
  30. *
  31. * @return none
  32. */
  33. static void delayMs(uint16_t nMs)
  34. {
  35. uint16_t i,j;
  36. for (i = 0; i < nMs; i++)
  37. //经由实际测试可以得出执行535次循环耗时最接近1ms
  38. for (j = 0; j < 535; j++);
  39. }
  40. /**
  41. * @fn initLed
  42. *
  43. * @brief 初始化LED,完成P0_4相关寄存器的配置
  44. */
  45. static void initLed()
  46. {
  47. P0SEL &= ~(1<<4);
  48. P0DIR |= (1<<4);
  49. }

以上代码实现了闪烁LED的功能。程序首先通过
initLed对寄存器P0SEL和P0DIR进行初始化,然后对LED值进行定时反转,从而实现了闪烁LED的效果。

仿真调试

注意:
在学习本节课前,需要先掌握基本的程序下载及仿真操作,参考《第二部分:51单片机入门——基于CC2530》→《第1章:CC2530 开发基础实验》→《程序下载及仿真》。
(1)把开发板通过仿真器连接到电脑上。
(2)按一下仿真器的复位按键,如图所示。
第2章:GPIO实验 - 图14

(3)打开本实验代码,编译链接通过后,点击“下载仿真”按钮,如图所示。
第2章:GPIO实验 - 图15

(4)点击Go按钮,全速运行程序。可以观察到LED闪烁的效果,并且Terminal I/O不断输出对应的状态信息,如图所示。
第2章:GPIO实验 - 图16

(5)点击如图所示按钮可以停止运行程序,如图所示。。
第2章:GPIO实验 - 图17

2.3 GPIO输入实验——机械按键

接着上节课的内容,切换到按键的工程,如图所示。
第2章:GPIO实验 - 图18

按钮原理简介

配套的ZigBee开发板均配备有按键,其原理图如图所示。
第2章:GPIO实验 - 图19
如您缺少硬件原理相关知识导致未能看懂本图,需先补充相关知识

图中K5是一个按钮,R11是一枚100KΩ的上拉电阻,P0_1是CC2530的一个IO口,相关原理如下:
(1)当按钮没有按下时,P0_1通过上拉电阻R11接到3v3,所以P0_1的输入电平为高电平。
(2)当按键按下时,P0_1接地,所以P0_1的输入电平为低电平。
由以上分析可知,可以检测P0_1的输入电平状态来检测按钮是否被按下。

P0_1相关寄存器

与LED实验类似,要检测P0_1的输入电平状态,就必须要先配置一下相关寄存器。P0_1的相关寄存器见下表。

寄存器 说明
P0 8位寄存器,8个位分别与P0_0~P0_7一一对应,分别用于设置或读取这8个IO口的电平状态
P0SEL 8位寄存器,8个位分别与P0_0~P0_7一一对应,分别配置这8个IO口的功能。如果IO口对应的位为0,表示该IO口用于通用输入/输出;如果为1,表示用于特定的功能
P0DIR 8位寄存器,8个位分别与P0_0~P0_7一一对应,分别配置这8个IO口的通信方向。如果IO口对应的位为0,表示该IO口处于输入信号模式;如果为1,表示处于输出信号模式
P0INP 8位寄存器,8个位分别与P0_0~P0_7一一对应,分别配置这8个IO口的输入模式。如果IO口对应的位为0,表示该IO口为上拉/下拉输入模式;如果为1,表示三态模式
P2INP[7:5] P2INP寄存器的第5、6和7位分别用于配置端0、1和2的上拉或下拉模式,如果为0,表示上拉模式;如果为1,表示下拉模式

上拉与下拉输入

相关寄存器中涉及到上拉和下拉输入,对其简单讲解一下。通俗地讲,上拉是指在默认的状态下给IO口输入高电平,与之相反,下拉就是低电平。根据上文的按键原理图可知,这是一个上拉输入模式。按键原理示意图如图所示。
第2章:GPIO实验 - 图20

当按键没有被按下时,CC2530与3.3v电源连接,其引脚的输入电平为高电平(对应信号1)。当按钮被按下时,CC2530与GND连接,其引脚的输入电平为低电平(对应信号0)。

寄存器配置

通过以上分析,相关寄存器的配置代码如下:

  1. P0SEL &= ~(1<<1);//把P0SEL寄存器的第1位设置为0,即让P0_1用作通用IO口
  2. P0DIR &= ~(1<<1);//把P0DIR寄存器的第1位设置为0,即让让P0_1处于输入信号模式
  3. P0INP &= ~(1<<1);//把P0INP寄存器的第1位设置为0,即让P0_1处于上拉/下拉输入模式
  4. P2INP &= ~(1<<5);//把P2INP寄存器的第5位设置为0,即让端口0处于上拉输入模式

编写按键代码

编写代码实现每按一下按键就翻转一下LED的开关状态,打开Key文件夹中的key.c文件,可以看到如下示例代码:

  1. //2. 51单片机入门/2. GPIO实验/Workspace/code/key/key.c
  2. #include "ioCC2530.h"
  3. #include <stdio.h>
  4. #include <stdint.h>
  5. #define DEBUG
  6. //#define xDEBUG
  7. #ifdef DEBUG
  8. #define DEBUG_LOG(...) printf(__VA_ARGS__)
  9. #else
  10. #define DEBUG_LOG(...)
  11. #endif
  12. #define LED P0_4
  13. #define LED_ON 1
  14. #define LED_OFF 0
  15. /**
  16. * @brief 按钮及其状态的定义。其中,P0_1是在头文件ioCC2530.h,可以检测其值来判断P0_1引脚的电平状态
  17. */
  18. #define BUTTON P0_1
  19. #define BUTTON_NORMAL 1//按钮的默认状态
  20. #define BUTTON_DOWN 0//按钮被按下
  21. static void delayMs(uint16_t nMs);
  22. static void initLed(void);
  23. static void initButton(void);
  24. void main() {
  25. initLed();//初始化LED
  26. initButton();//初始化按键
  27. while(1) {
  28. if (BUTTON != BUTTON_DOWN)//如果按键没有被按下
  29. continue;//结束本次while循环
  30. else {
  31. /* 以下为对按键的机械抖动的处理处理代码 */
  32. delayMs(10);//延后10毫秒
  33. if (BUTTON != BUTTON_DOWN)//再次检测按钮的状态,如果按键没有被按下
  34. continue;//结束本次while循环
  35. }
  36. while (BUTTON == BUTTON_DOWN);//等待BUTTON != BUTTON_DOWN,即等待按键松开
  37. DEBUG_LOG("Key Pressed!\r\n");
  38. LED = (LED == LED_ON)? LED_OFF : LED_ON;//翻转LED的状态
  39. }
  40. }
  41. static void delayMs(uint16_t nMs)
  42. {
  43. uint16_t i,j;
  44. for (i = 0; i < nMs; i++) for (j = 0; j < 535; j++);
  45. }
  46. static void initLed()
  47. {
  48. P0SEL &= ~(1<<4);
  49. P0DIR |= (1<<4);
  50. }
  51. /*
  52. * @fn initButton
  53. *
  54. * @brief 初始化Button,完成P0_1相关寄存器的配置
  55. */
  56. static void initButton()
  57. {
  58. P0SEL &= ~(1<<1);//把P0SEL寄存器的第1位设置为0,即让P0_1用作通用IO口
  59. P0DIR &= ~(1<<1);//把P0DIR寄存器的第1位设置为0,即让让P0_1处于输入信号模式
  60. P0INP &= ~(1<<1);//把P0INP寄存器的第1位设置为0,即让P0_1处于上拉/下拉输入模式
  61. P2INP &= ~(1<<5);//把P2INP寄存器的第5位设置为0,即让端口0处于上拉输入模式
  62. }

处理机械按键抖动

上述代码包含了按键抖动的处理,对其简单讲解一下。由于按键内部采用了弹簧,所以当按键被按下或松开的时候,会产生一定的震动,这种震动可以称为机械抖动。这种机械抖动会导致电平的抖动,如图所示。
第2章:GPIO实验 - 图21

图中的中的横坐标是电平,纵坐标是时间,展示了按钮从按下到松开这个过程的电平变化。按键被按下时产生的抖动称为前沿抖动,松开时产生的是后沿抖动。这个抖动时间一般持续5~10ms。因此在代码上当检测到按钮被按下后,需要延后10ms后再检测一次按钮是否真的被按下。

使用调试模式

在程序开发调试过程中,可设置一个调试模式。在调试模式下可以输出相关的调试信息来了解程序运行状态。然而在程序开发完成后,可以关闭调试模式,停止输出调试信息。

DEBUG_LOG是一个宏定义,其定义在上述代码的开端处。

  • 当代码定义了 DEBUG 这个宏时,表示当前模式是调试模式,此时DEBUG_LOG和printf是一致的。
  • 而没有定义DEBUG 这个宏时,表示关闭调试模式,此时DEBUG_LOG什么都不做。所以,当需要关闭调试模式时,可以把DEBUG更改为xDEBUG,表示关闭调试模式。

    仿真调试

    注意:
    在学习本节课前,需要先掌握基本的程序下载及仿真操作,参考《第二部分:51单片机入门——基于CC2530》→《第1章:CC2530 开发基础实验》→《程序下载及仿真》。
    (1)把开发板通过仿真器连接到电脑上。
    (2)按一下仿真器的复位按键。
    第2章:GPIO实验 - 图22

(3)打开本实验代码,打开调试模式,编译链接通过后,点击“下载仿真”按钮全速运行程序,每当有按键按下时Terminal I/O中就会输出相应的信息,如图所示。
第2章:GPIO实验 - 图23

2.4 GPIO输入输出通用配置实验

接着上节课的内容,切换到ioConfig工程,如图所示。
第2章:GPIO实验 - 图24

GPIO输入输出通用配置API

本小节实验将要实现的效果和上节课的是一样的,即通过按键控制LED翻转。不同的是,本节课使用了GPIO输入输出通用配置API CC2530_IOCTL,开发者不再需要自己查找和配置GPIO的相关寄存器了。

打开头文件cc2530_ioctl.h,找到CC2530_IOCTL定义,代码如下:

  1. //2. 51单片机入门/4. 串口通信实验/Workspace/code/common/cc2530_ioctl.h
  2. /**
  3. * @brief 配置GPIO模式
  4. *
  5. * @param port - CC2530引脚port号
  6. * @param pin - CC2530引脚pin号
  7. * @param mode - 该GPIO的模式
  8. *
  9. * @warning P1_0和P1_1不能配置为输入模式
  10. */
  11. #define CC2530_IOCTL(port, pin, mode) do {
  12. if (port > 2 || pin > 7) break;
  13. if (mode == CC2530_OUTPUT) CC2530_IO_OUTPUT(port, pin);
  14. else CC2530_IO_INPUT(port, pin, mode);
  15. } while(0)

其中的mode参数可以传入下列参数,表示不同的模式:

  1. //2. 51单片机入门/4. 串口通信实验/Workspace/code/common/cc2530_ioctl.h
  2. /** @brief CC2530 GPIO mode. */
  3. #define CC2530_OUTPUT 0 //输出模式
  4. #define CC2530_INPUT_PULLUP 1 //上拉输入模式
  5. #define CC2530_INPUT_PULLDOWN 2 //下拉输入模式
  6. #define CC2530_INPUT_TRISTATE 3 //3态模式

如果需要把CC2530的P0_4引脚配置为输出信号模式,只需按如下方式调用:

  1. CC2530\_IOCTL(0, 4, CC2530\_OUTPUT);

可见,在使用CC2530_IOCTL后,GPIO口的配置变得非常简单,不再需要另外查找配置P0_4的相关寄存器了!

使用CC2530_IOCTL

使用CC2530_IOCTL前,需要分别定义LED和按键与CC2530引脚的连接。在头文件cc2530_ioctl.h中,可以找到LED和按键与CC2530的引脚连接定义,代码如下:

  1. //2. 51单片机入门/4. 串口通信实验/Workspace/code/common/cc2530_ioctl.h
  2. // LED与GPIO的连接定义
  3. #define LED_PORT 0 //Led port.
  4. #define LED_PIN 4 //Led pin.
  5. #define LED P0_4 //Led GPIO.
  6. // Button与GPIO的连接定义
  7. #define BUTTON_PORT 0 //Button port.
  8. #define BUTTON_PIN 1 //Button pin.
  9. #define BUTTON P0_1 //Button GPIO.

以上代码定义了LED与CC2530的P0_4引脚连接、按键与CC2530的P0_1引脚连接。

这样做的优点在于,如果硬件电路发生改变,例如LED接的不是P0_4、按键接的不是P0_1,那么开发者只需要在这里修改配置,而不需要修改代码。

以下两个初始化函数的效果分别与前面章节的LED和按键初始化函数的效果是相同的。

  1. //2. 51单片机入门/2. GPIO实验/Workspace/code/ioConfig/ioConfig.c
  2. /*
  3. * 初始化LED
  4. */
  5. static void initLed()
  6. {
  7. CC2530_IOCTL(LED_PORT,//LED的port
  8. LED_PIN,//LED的pin
  9. CC2530_OUTPUT);//配置为输出
  10. LED = LED_OFF;
  11. }
  12. /*
  13. * 初始化按键
  14. */
  15. static void initButton()
  16. {
  17. CC2530_IOCTL(BUTTON_PORT,//按键的port
  18. BUTTON_PIN,//按键的pin
  19. CC2530_INPUT_PULLUP);//配置为上拉输入
  20. }

仿真调试

注意:
在学习本节课前,需要先掌握基本的程序下载及仿真操作,参考《第二部分:51单片机入门——基于CC2530》→《第1章:CC2530 开发基础实验》→《程序下载及仿真》。
(1)把开发板通过仿真器连接到电脑上。
(2)按一下仿真器的复位按键,如图所示。
第2章:GPIO实验 - 图25

(3)打开本实验代码,打开调试模式,编译链接通过后,点击“下载仿真”按钮全速运行程序,每当有按键按下时Terminal I/O中就会输出相应的信息,如图所示。
第2章:GPIO实验 - 图26

2.5 GPIO外部中断实验

接着上节课的内容,切换到中断实验对应的工程,如图所示。
第2章:GPIO实验 - 图27

什么是中断?

通俗地讲,中断是指系统在正常运行的时候突然遇到了一个紧急事件,例如用户突然按下一个按键、火灾传感器监测到起火或者计时结束等,需要先放下当前的工作去处理这个紧急事件。我们通过一张逻辑图来理解一下这个过程。
第2章:GPIO实验 - 图28
如图所示,系统在执行主程序的过程中,在某一个时间点(也可以称为断点)遇到了一个紧急事件,这时候通常会发生以下3件事:
(1)中断请求
这个紧急事件请求系统暂停主程序,转而去处理这个紧急事件。
(2)中断响应
系统会执行一个叫中断处理的程序来处理这个紧急事件。
(3)中断返回
处理完这个紧急事件后,系统会返回到刚才的主程序中并且从暂停的位置继续执行主程序。

中断优先级

当有多个中断同时发生时,应该优先处理哪个中断呢?我们可以给每个中断定义一个优先级,告诉系统优先处理优先级较高的中断,延后处理优先级较低的中断。

CC2530的中断体系原理

CC2530的中断体系原理如图所示。
第2章:GPIO实验 - 图29

这个中断体系原理图还是比较复杂的,这里以P0端口的中断原理为例讲解一下。上图的最右边是P0端口的中断过程,其相关寄存器依次为P0、PICTL.P0ICON、P0IFG、P0IEN、P0IF、EA和IEN1.P0IE,相关说明见表。

寄存器 说明
PICTL.P0ICON PICTL是8位的端口中断控制寄存器。P0ICON是PICTL的第0个位,用于配置端口P0中断引发方式。如果P0ICON的值为:0,输入的上升沿引起中断;1,输入的下降沿引起中断
P0IFG 8位寄存器,8个位分别与P0_0~P0_7一一对应,分别用于表示P0_7到P0_0引脚的输入中断状态标志。如果产生了中断,相应标志位将置 1
P0IEN 8位寄存器,8个位分别与P0_0~P0_7一一对应,分别用于配置P0_7到P0_0引脚是否允许中断。如果对应位的值为:0,禁用中断;1,允许中断。
P0IF 如果端口0产生了中断,那么值被置1
EA 中断总开关,如果值为:0,禁用所有中断;1:允许所有中断
IEN1.P0IE IEN1是8位的中断使能1寄存器,用于配置是否允许端口中断、定时器中断或DMA中断。P0IE是IEN1的第5个位,用于配置是否允许端口0引起终端,如果值为:0,禁止中断;1,允许中断

结合相关寄存器说明,如果需要实现有P0端口引发CPU中断,那么配置步骤如下:
(1)通过PICTL的P0ICON位指定端口0的中断引发方式。
(2)清零P0IFG寄存器,表示默认状态下端口0中的所有IO口均没有引发中断。
(3)把P0IEN的对应位置1,表示对应的IO口被允许引发中断,例如如果需要允许P0_0引发中断,那么把P0IEN的第0位置1即可。
(4)清零P0IF寄存器,表示默认状态下端口0没有引发中断。
(5)EA寄存器置1,表示允许所有中断。
(6)IEN1寄存器的P0IE位置1,表示允许端口0引起中断。

当端口0引发中断后,这个中断便由中断向量IP0_5或IP1_5分发给指定的中断处理函数处理,下文将结合具体的例子讲解。

按键中断的配置

ZigBee开发板的按键与P0_1相连接,并且使用上拉输入的方式,如果需要显示按下按键后引发中断,那么相应的配置代码如下:

  1. PICTL |= 0x01;//P0ICON=1,指定端口0在输入下降沿信号时触发中断,即输入电平由高电平转为低电平时触发中断
  2. P0IFG = 0x00;//清零P0IFG寄存器
  3. P0IEN |= (1<<1);// 把P0\_1设置为允许引发中断
  4. P0IF = 0x00;//清零P0IF寄存器
  5. EA = 1;//允许所有中断
  6. IEN1 |= 0x20;//P0IE=1,允许端口0引起中断

编写按键中断代码

打开本实验代码,main函数的代码如下:

  1. //2. 51单片机入门/2. GPIO实验/Workspace/code/interrupt/interrupt.c
  2. void main()
  3. {
  4. initLed();//初始化LED
  5. initButton();//初始化按键
  6. while(1) {//每隔100ms计数一次
  7. counter_g++;//计数1次
  8. delayMs(100);//延迟100ms
  9. }
  10. }

上述代码中的counter_g是一个计算器,每隔100ms计数一次。在按键初始化函数initButton中把P0_1配置为中断输入,代码如下:

  1. //2. 51单片机入门/2. GPIO实验/Workspace/code/interrupt/interrupt.c
  2. static void initButton(void)
  3. {
  4. PICTL |= 0x01;//P0ICON=1,指定端口0在输入下降沿信号时触发中断,即输入电平由高电平转为低电平时触发中断
  5. P0IFG = 0x00;//清零P0IFG寄存器
  6. P0IEN |= (1<<1);// 把P0\_1设置为允许引发中断
  7. P0IF = 0x00;//清零P0IF寄存器
  8. EA = 1;//允许所有中断
  9. IEN1 |= 0x20;//P0IE=1,允许端口0引起中断
  10. }

中断处理函数的定义如下:

  1. //2. 51单片机入门/2. GPIO实验/Workspace/code/interrupt/interrupt.c
  2. #pragma vector = P0INT_VECTOR
  3. __interrupt void buttonISR(void)
  4. {
  5. delayMs(10); //处理机械按键抖动
  6. if (BUTTON == BUTTON_DOWN) {
  7. DEBUG_LOG("counter_g: %d\r\n", counter_g);//输出计数器的值
  8. LED = (LED == LED_ON)? LED_OFF : LED_ON;//翻转LED灯的状态
  9. }
  10. //处理完中断后,需要清零相关寄存器
  11. P0IFG = 0;
  12. P0IF = 0;
  13. }

pragma是一个编译器预处理指令,其在此处的作用是告诉编译器vector是一个仅编译阶段有效的变量。vector表示一个中断向量,被赋值为P0INT_VECTOR,并且下面紧跟以__interrupt关键字开发头的函数buttonISR,表示该函数为P0引发的中断的处理函数,即P0引发中断后,由此函数处理该中断。在中断处理函数中,必须要清0中断标志位,否则当再次遇到这个中断时,CPU不会再执行中断处理函数。

仿真调试

注意:
在学习本节课前,需要先掌握基本的程序下载及仿真操作,参考《第二部分:51单片机入门——基于CC2530》→《第1章:CC2530 开发基础实验》→《程序下载及仿真》。

利用仿真器与ZigBee开发板,可以观察在ZigBee开发板上的运行结果。仿真调试步骤如下:
(1)把开发板通过仿真器连接到电脑上。
(2)按一下仿真器的复位按键,如图所示。
第2章:GPIO实验 - 图30

(3)打开本实验代码,在编译链接通过后,全速运行,如图所示。
第2章:GPIO实验 - 图31

当按下按键后,Terminal I/O中会显示计数器的值,并且翻转LED灯的状态。如果需要停止仿真调试,可以点击图中的红色交叉按钮。