新闻  |   论坛  |   博客  |   在线研讨会
PIC单片机引脚中断程序的设计技巧
tvb2058 | 2007-12-09 12:34:21    阅读:5036   发布文章

1  简述

  所有的中档系列PIC单片机,PORTB端口最高的4个引脚(RB7~RB4)在设为输入模式时,当输入电平由高到低或由低到高发生变化时,可以让单片机产生中断。这就是通常所说的引脚状态变化中断。

  在设计引脚中断程序时,有三个需要特别注意的地方。一是,在清除PORTB中断标志位RBIF之前,必须安排一条必不可少的,以PORTB端口数据寄存器PORTB为源寄存器的读操作指令。放置这一指令的目的有时并不只是为了读取有用的数据,而是为了取消状态变化的硬件信号,以便顺利清除RBIF标志位,为下一次中断做好准备。二是,由于端口PORTB是引脚电平变化中断,即无论引脚出现上升沿还是下降沿都会产生中断请求,所以必须处理好不需要的虚假中断。三是,一般都利用PIC单片机的引脚功能来检测按键,所以必须处理好按键消抖的问题。

2  引脚中断程序设计

  在主程序里先设置有关的寄存器。

  ◇ 设置TRISB寄存器,使RB7~RB4相关的引脚处于输入状态;
  ◇ 如果需要弱上拉,通过OPTION_REG的第7位设置;
  ◇ RBIF=0;
  ◇ RBIE=1;
  ◇ GIF=1。

  响应状态变化后的中断服务程序。

  ◇ 检查RBIF是否为1,为1则是引脚变化引起的中断;
  ◇ 调用延时程序,延时20~30 ms,目的是为了按键去抖;
  ◇ 判断是引脚出现上升沿还是下降沿引起的中断;
  ◇ 调用按键处理程序;
  ◇ 读PORTB口的值,取消状态变化的硬件信号;
  ◇ 清除RBIF标志。

  笔者认为上面程序设计最大的问题是在中断程序里调用延时程序。大家知道,中档PIC单片机只有8层深度的硬件堆栈,在中断里调用子程序出现极易堆栈溢出的情况。另外,PIC单片机中断程序入口只有一个,在响应中断的请求时,PIC单片机就会自动把全局中断的使能位(INTCON的第7位GIF)清除,这样其他中断就暂时不能被响应(此时,如果别的中断发出的中断请求,标志位将一直保留着),直到这个中断程序退出后才会得到响应。这就要求我们设计中断程序的时候必须尽量短,避免调用子程序,更不要在中断里进行复杂的运算。

  下面给出笔者设计程序时的思路。

  当引脚状态变化引起中断时,在中断子程序里首先判断引起中断的原因是不是我们需要的变化引起的中断。如果是,不要在这里延时,而是设置一个标志位,接着清除中断标志,退出中断。中断程序如下:

else if ( (RBIE & RBIF) == 1) {//如果引脚变化引起中断
  if (RB4==0) {//RB4上的按钮接地
  key=1;//按键标志位置位
  }
  RBIF=0;//清除引脚中断标志位
  }

  其中,if (RB4==0)语句相当于读取了PORTB端口数据寄存器,取消了状态变化的硬件信号。

  下面详细介绍怎么样进行按键去抖。

  首先,在定时器中断里设置一个1 ms的时间基准标志位“SYS1ms”,每到1ms,“SYS1ms”便置位。程序如下:

unsigned char count;
if ( (T0IE & T0IF) == 1) {//定时器中断
TMR0 += 0x09; //每250 μs中断一次
if(count==4) {
  count=0;
  SYS1ms=1;//系统时间标志
}
count++;
T0IF = 0;//清除时钟中断标志位
}

  有了这个时间基准,便可以在主程序里进行按键去抖处理了。为了更好地利用这个时间基准,定义一个消息标志SYSTime,笔者把它称作时间消息。为了让这个消息有自我发布和自我消失的功能,定义了如下一个宏:

bit SYSTime;
#define TimeEnable()SYSTime=0; if(SYS1ms){ SYSTime=1; SYS1ms=0; }

  可以把TimeEnable()放到主程序死循环的任何地方,每当程序执行这个宏,SYSTime就会清零,这就是标志位的自我消失。如果在定时器时间基准标志位SYS1ms已经置位的话,SYSTime就会置1,这样别的程序就可以利用这个时间消息了,这就是消息的自我发布。下面就是利用这个时间消息来进行按键延时去抖的,首先看一下按键扫描子程序:

void scankey() {
  unsigned char KeyTime,KeyTask; //定义任务时间参数、
  //任务参数
  switch(KeyTask) {
    case 0: if(key)  {
      KeyTime=30; //准备延时30 ms
      KeyTask++; //准备好下一个任务
      key=0;
      }
      break;
    case 1:KeyTime--;//延时30 ms
      if(KeyTime==0) KeyTask++;
      break;
    case 2:if(RB4==0)  {
      //调按键处理程序
      KeyTask=0;
      }
      else KeyTask=0; //退出任务
      break;
  }
}

  在主程序的死循环中这样用:

while(1) {
  TimeEnable();
  If(SYSTime==1) {scankey();}
  //在此可以添加其他程序
}

  只有有时间消息的时候才执行按键扫描程序。可以看到,进入扫描程序执行第一次的时候,程序首先判断按键标志位有没有置位,置位的话(也就是有按键按下的话),任务时间参数(KeyTime)赋值为30,这是延时30 ms去抖,当然你也可以设置为其他的时间值;同时任务参数(KeyTask)加1。1 ms后,再进入扫描程序,这个时候扫描程序执行case 1的语句,这样30次后(延时了30 ms),任务参数(KeyTask)加1,值为2。1 ms后,再进入扫描程序,将执行case 2的语句,首先在这里再次判断是不是按键还在按下,如果是就调按键的处理程序,如果不是,就退出按键扫描程序。在这里,还可以加入按键是否抬起的判断程序。

  这样设计的引脚变化程序,CPU开销小,效率高,不会出现堆浅溢出的问题,提高了系统的实时性。

*博客内容为网友个人发布,仅代表博主个人观点,如有侵权请联系工作人员删除。

参与讨论
登录后参与讨论
推荐文章
最近访客