下里巴人的笔记 https://passport2.21ic.com/?81022 [收藏] [复制] [RSS]

日志

利用定时器扫描键盘和数码管的"0"耗时支持长击和短击

已有 1449 次阅读2008-7-7 18:50 |系统分类:单片机

/*******************************************************************************
*程序共定义了6个键的功能:K1、K2、K3、K4以及K5、K8组成的一对复合键,其中K2,K3为
*连击键,K5为上档键。在正常工作模式下按K1则切换至状态,在设定模式下按K1键循环选
*择4个数码管中的某个,被选中的数码管闪烁,此时单按K2键显示数值加1;常按K2显示数
*值以一定速度递增,同时数码管停止闪烁,当K2松开,数码管恢复闪烁,显示数值停留在
*K2松开前的值上。K3完成的功能和K2类似。其完成减操作。这2个键只有在设定状态才有
*效,可以有效防止误操作。K4为确认键,按下该键回到正常显示状态,所有指示灯熄灭,
*数码管显示刚刚设定的数值。K5+K8这对复合键执行复位操作,任何情况下同时按下K5和K8
*或先按下K5再按下K8,所有数码管的显示全为0,指示灯全灭,进入正常显示状态。同时程
*序还对如下几个异常操作进行了处理:
* 2个或多个功能键同时按下
* 一个功能键按下未释放,又按另一个功能键,然后再松开其中一个功能键
* 先按下功能键再按下上档键
* 多个上档键和一个功能键同时按下,此时不做处理。等到松开其他上档键,只剩下一个上
* 档键和一个功能键时才执行这对复合键;或松开所有上档键,处理单一功能键。
*******************************************************************************/
#include <iom8v.h>
#include <macros.h>

#define uchar unsigned char
#define uint unsigned int


#define RCtrl  0x20              //定义上挡键 第5键
#define RConti 0xfe              //定义连击键 第6键

#define N 2                      //去抖年龄下限

#define MaxRate 50               //重复前的延迟值 600ms
#define MinRate 20               //重复速度 240ms

#define leddark 83               //闪烁时灭时间1s
#define ledshow 83               //闪烁时亮时间1s

#define decimal 0x80             //小数点的段数


#define KEY_DDR    DDRC
#define KEY_PORTO  PORTC
#define KEY_PORTI  PINC
#define OUT        0x3f
#define IN         0xc0    
#define KeyValue   0x3f

#define LEDD_DDR   DDRB
#define LEDD_PORTO PORTB

#define LEDS_DDR   DDRD
#define LEDS_PORTO PORTD
#define LEDS_MASK  0xfc
#define LEDS_NUM   0x06

#define TRUE       1
#define FALSE      0  


/*定义键盘扫描程序返回数据类型*/
typedef struct
{
  uchar shiftcnt; //上档键的个数值
  uchar funcnt; //功能键的个数值
  uchar shiftval; //最后扫描到的上档键的值
  uchar funval; //最后扫描到的功能键的值
} keyret;

/*定义显示字符段码*/
uchar const led_stroke[19] =
{
  //0,1,2,3,4,5,6,7,8,9
  0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F,

  //a,b,C,d,e,F,P,
  0x77, 0x7C, 0x39, 0x5E, 0x79, 0x71, 0x73,
  //all on all off
  0xff, 0x00
};

/*定义位选码*/
uchar const led_cs[LEDS_NUM] =
{
  0xfb,  //11111011
  0xf7,  //11110111
  0xef,  //11101111
  0xdf,  //11011111
  0xbf,  //10111111
  0x7f   //01111111
};

uchar led_buf[LEDS_NUM] =
{
  0x73, 0x81, 0x82, 0x83, 0x84,0x85
};
uchar *pb = &led_buf[1]; //定义指向数码管数据缓冲区的指针


/*定义全局变量*/

uchar task, state; //task:按键状态,0:去抖 1,重复的延迟 2,重复
//state:显示位置变量

uchar keydone, keyprocess; //keydone: 按键任务完成标志,为1表示已完成
//keyprocess: 按键有效标志,为1时表示对按键执行

uchar keypre[2] =
{
  0x00, 0x00
}; //存放上次功能键和上档键的键值
//keypre0存放功能键

uchar blink, ledtime; //blink:闪烁控制寄存器,某位为1时闪烁
                      //d7d6d5d4d3d2d1d0
                      //xxxx1111
                      //ledtime:累计闪烁时已点亮和已熄灭的时间
uchar ledtask; //ledtask: 当前的闪烁状态,0代表亮

uchar keymark; //keymark:只是当前工作状态,为1时处于设定状态,为0时正常工作
uchar enflash; //enflash:闪烁使能标志,1闪烁

#define shut_dis() LEDS_PORTO|=LEDS_MASK;  //shut display

/*******************************************************************************
 *函数原型: uchar _crol_(uchar data,uchar shiftbit);
 *功能:字节左移shiftbit
 *参数:
 *说明:
 *******************************************************************************/
uchar _crol_(uchar data,uchar shiftbit)
{
    data &=0xff;
    if(shiftbit>8)return 0;
 return ((~data)<<shiftbit);
}
/*******************************************************************************
 *函数原型: uchar _cror_(uchar data,uchar shiftbit);
 *功能:字节右移shiftbit
 *参数:
 *说明:
 *******************************************************************************/
uchar _cror_(uchar data,uchar shiftbit)
{
    data &=0xff;
    if(shiftbit>8)return 0;
 return ((~data)>>shiftbit);
}

/*******************************************************************************
 *函数原型: void send_shift(uchar d);
 *功能:     将显示数据由B口送出
 *******************************************************************************/
void send_shift(uchar data)
{
  LEDD_PORTO = data;
}
/*******************************************************************************
 *函数原型: void lflash();
 *功能:闪烁处理。ledtask指示当前的显示状态:亮或灭,ledtask为0时亮
 *参数:
 *说明:
 *******************************************************************************/
void lflash()
{
  if (enflash && (blink &0x3f))
  {
    if (ledtime-- == 0)
    {
      if (!ledtask)
        ledtime = leddark;
      else
        ledtime = ledshow;
      ledtask = ~ledtask; //显示状态翻转
    }
  }
  else
    ledtask = 0;
}

/*******************************************************************************
 *函数原型: void display(uchar pos);
 *功能:将pos对应的显示缓冲区的内容显示在第pos位数码管上
 *参数:
 *说明:
 *******************************************************************************/
void display(uchar pos)
{
  uchar sflag;
  shut_dis(); //关显示
  sflag = (blink >> (6-pos)) &0x01;      //取出pos对应的数码管的闪烁控制位
  if (pos == 0)
    send_shift(led_buf[0]);              //指示灯显示时直接将显示内容送显示端口
  else if (!sflag || (sflag &!ledtask))  //不闪烁或正处于闪烁的亮阶段
    if ((led_buf[pos] &0x80) == 0)       //不显示小数点,查表得到段码送显示端口

    send_shift(led_stroke[led_buf[pos] &0x7f] |decimal); //显示小数点,把查表得到段码与小数点的段码与的结果送显示端口
    else
    send_shift(led_stroke[led_buf[pos] &0x7f]);  
  else
    send_shift(0xff);                    //pos位数码管不显示
  LEDS_PORTO &= led_cs[pos];             //送出位选码
}

/*******************************************************************************
 *函数原型: void scankey(keyret *kpd);
 *功能:键盘扫描,返回上档键个数,上档键值,功能键个数,功能键值
 *参数:
 *说明:
 *******************************************************************************/
void scankey(keyret *kpd)
{
  uchar i,j;
  shut_dis();                //关显示
  KEY_DDR    = OUT;          //定义数据端口为输出
  KEY_PORTO |= KeyValue;     //输出数据(输出检测码)
  KEY_DDR    = IN;           //定义数据端口为输入
  i = (KEY_PORTI &KeyValue); //取出键盘位
  if (i != KeyValue)         
                             //检测是否有键按下
  {
    i |= ~KeyValue;          //组成完整的字节
    if (~i &RCtrl)
                             //该键是上档键
    {
      kpd->shiftcnt++;       //上档键个数加1   ??
      //这里不对
      for(j=0;j<6;j++)
      {
          if((0x01<<j)==RCtrl)
          {
              kpd->shiftval = j+1;     //上档键值存入上档键值缓冲  ??
          }
      }
    }
    else
    {
      for(j=0;j<6;j++)
      {
          if((0x01<<j)==~i)
          {
              kpd->funcnt++;           //功能键个数加1   ??
            kpd->funval =j+1;        //上档键值存入上档键值缓冲  ??
          }
      }
    }
  }
  KEY_DDR = OUT;             //数据端口还原为输出  
}

/*******************************************************************************
 *函数原型: void key();
 *功能:键盘扫描,返回上档键个数,上档键值,功能键个数,功能键值
 *参数:
 *说明:
 *******************************************************************************/
void key()
{
  uchar krpt = RConti;
  static uchar keycnt;
  static uchar keyesc;
  keyret keytemp =
  {
    0x00, 0x00, 0x00, 0x00
  };
  if (keydone)                                   //上次按键任务已完成
  {
    scankey(&keytemp);                           //扫描键盘
    if (!keytemp.funcnt || keytemp.shiftcnt > 1) //功能键计数器为0或上档键个数多于1
    {
      enflash = 1;                               //允许闪烁
     // USART0_TransmitString("shiftcnt!\r\n");
      keypre[0] = 0x00;                          //清零上次功能键和上档键缓冲
      keypre[1] = 0x00;
      //清零
      keyesc = 0x00;                             //键释放标志 清零
      task   = 0x00;                             //按键状态 标志为去抖阶段
      keycnt = 0x00;                             //年龄计数器 清零
    }
    else if (!keyesc)                            //键已释放 按下上挡键后按键释放标志请零
    {
        // USART0_TransmitString("keyesc!\r\n");
      if (keytemp.funcnt > 1)                    //有多于一个的功能键按下
        keyesc = 1;                              //置位按键释放标志
        //本次功能键值和上次不同或本次上档键值和上次不同且上次有上档键
      else if (keytemp.funval != keypre[0] || keytemp.shiftval != keypre[1] &&keypre[1])
      {
        keypre[0] = keytemp.funval;              //用本次键值更新上次键值
        keypre[1] = keytemp.shiftval;

        keycnt = 0x00;                           //年龄计数器复位
      }
      else if (!keypre[1] && keytemp.shiftval)   //2次功能键相同,上次无上档键本次有
        keyesc = 1;//置位keyesc,屏蔽先按下功能键,再按下上档键这种按键组合
      else
      {
        keycnt++; //2次功能键和上档键都相同
        switch (task)
        {
          case 0:
            if (keycnt == N)
            {
              enflash = 1;
              keyprocess = 1; //键值处理
              if ((keytemp.shiftcnt == 1) || !(krpt >> (keytemp.funval - 1)
                  &0x01))
                keyesc = 1;//是一对复合键或该键不允许连击
              else
              {
                task++; //按键状态转入重复的延缓阶段
                keycnt = 0; //按键年龄清0
              }
            }
            break;
          case 1://重复的延缓阶段
            if (keycnt == MaxRate)//按键年龄等于重复的延缓阶段
            {
              enflash = 0; //不允许闪烁

              keyprocess = 1; //进行键值处理
              keycnt = 0; //按键年龄清0
              task++; //按键转入重复状态
            }
            break;
          case 2://重复阶段
            if (keycnt == MinRate)//重复时间到
            {
              enflash = 0; //不允许闪烁

              keyprocess = 1; //进行键值处理
              keycnt = 0; //按键年龄清0
            }
            break;
        }
      }
    }
  }
}

/*******************************************************************************
 *函数原型: void key1_handler();
 *功能:按键1处理
 *参数:
 *说明:正常工作模式下,按下按键1即进入设定状态,此时第一个数码管闪烁,同时对应led1
 *     的指示灯亮。设定模式下,按下按键1,在led1和led4之间循环切换选择被选中的数码
 *     管闪烁,相应的指示灯亮
 *******************************************************************************/
void key1_handler()
{
  if (!keymark)        //进入设定状态
  {
    led_buf[0] = 0xfe; //点亮第一个led
    pb = &led_buf[1];  //pb指向第一个数码管
    //blink = 0x80;      //第一个数码管闪烁
     blink = 0x20;      //第一个数码管闪烁
    keymark = 1;
    enflash = 1;       //允许闪烁
  }
  else
  {
    pb++;              //pb指向下一个数码管
    blink >>= 1;       //该数码管闪烁
   // led_buf[0] = _crol_(led_buf[0], 2); //调整指示灯
    if (pb >= (uchar*)(led_buf + 6))    //pb指针越限,重新初始化
    {
      pb = &led_buf[1];
      //blink = 0x80;                     //第一个数码管闪烁
       blink = 0x20;      //第一个数码管闪烁
    }
  }
}


/*******************************************************************************
 *函数原型: void key2_handler();
 *功能:按键2处理
 *参数:
 *说明:正常工作模式下,按下按键2无效。设定模式下,按下按键2,每执行一次选中的数码
 *     管值加一,常按,数值以一定速度连续递增,但数值大于9时又回到0,
 *******************************************************************************/
void key2_handler()
{
  if (keymark)
  {
    *pb += 1;
    if (*pb >= 10)
      *pb = 0;

  }
}

/*******************************************************************************
 *函数原型: void key3_handler();
 *功能:按键3处理
 *参数:
 *说明:正常工作模式下,按下按键3无效。设定模式下,按下按键2,每执行一次选中的数码
 *     管值减一,常按,数值以一定速度连续递减,但数值大于0时又回到9,
 *******************************************************************************/
void key3_handler()
{
  if (keymark)
  {
    if (*pb <= 0)
      *pb = 9;
    else
      *pb -= 1;
  }
}

/*******************************************************************************
 *函数原型: void key4_handler();
 *功能:按键4处理
 *参数:
 *说明:此键为确认键,按下后熄灭指示灯,不闪烁,进入工作状态
 *******************************************************************************/
void key4_handler()
{
  blink = 0x00;
  led_buf[0] = 0x73;
  keymark = 0;
}

/*******************************************************************************
 *函数原型: void keymix_handler();
 *功能:复合按键(上档键5和功能键8)处理
 *参数:
 *说明:执行复位功能,4个数码管全显示0,不闪烁。8个指示灯全灭,进入正常工作状态
 *******************************************************************************/
void keymix_handler()
{
  led_buf[0] = 0x73;
  led_buf[1] = 0;
  led_buf[2] = 0;
  led_buf[3] = 0;
  led_buf[4] = 0;
  led_buf[5] = 0;
  keymark = 0;
  blink = 0;
}

/*******************************************************************************
 *函数原型: void key_processing();
 *功能:键值处理
 *参数:keyprocess,keypre[1],keypre[0],
 *说明:根据键值执行相应的按键功能
 *******************************************************************************/
void key_processing()
{
  if (keyprocess)
  {
    keydone = 0;
    keyprocess = 0;
    switch (keypre[1])
    {
      case 0x00:
        switch (keypre[0])
        {
        case 0x01:
          key1_handler();
          break;
        case 0x02:
          key2_handler();
          break;
        case 0x03:
          key3_handler();
          break;
        case 0x04:
          key4_handler();
          break;
        }
        break;
      case 0x05:
        if (keypre[0] == 8)
          keymix_handler();
        break;
    }
    keydone = 1;
  }
}


//TIMER0 initialize - prescale:256
// desired value: 2mSec
// actual value:  1.991mSec (0.5%)
void timer0_init(void)
{
  TCCR0 = 0x00; //stop
  TCNT0 = 0xAA; //set count
  TCCR0 = 0x04; //start timer
}

/*******************************************************************************
 *函数原型: void timer0_ovf_isr(void);
 *功能:定时器0中断服务程序
 *参数:
 *说明:键盘和led一起构成1/6扫描,每6次中断进行一次键盘处理
 *******************************************************************************/
#pragma interrupt_handler timer0_ovf_isr:10
void timer0_ovf_isr(void)
{

  TCNT0 = 0xAA; //set count
  if (state <= 5)
  {
    lflash();
    display(state);
    state++;

  }
  else
  {
    key();
    state = 0;
  }
}

void USART0_Init(void)
{
 UCSRB = 0x00; //disable while setting baud rate
 UCSRA = 0x00;
 UCSRC = BIT(URSEL) | 0x06;
 UBRRL = 0x47; //set baud rate lo
 UBRRH = 0x00; //set baud rate hi
 UCSRB = 0x18;
}

//-------------------------------------------------------------------------

void USART0_Transmit(unsigned char data)
{
  while (!(UCSRA &(1 << UDRE)))
    ;
  UDR = data;
}

//-------------------------------------------------------------------------

void USART0_TransmitString(char *data)
{
  while (*data)
    USART0_Transmit(*data++);
}

//-------------------------------------------------------------------------

unsigned char USART0_Receive(void)
{
  while (!(UCSRA &(1 << RXC)))
    ;
  return UDR;
}



/*******************************************************************************
 *函数原型: void port_init(void);
 *功能:端口初始化
 *参数:
 *说明:
 *******************************************************************************/
void port_init(void) //端口初始化
{
  DDRB = 0xff; //led数据口    11111111
  DDRC = 0xff; //键盘      00111111
  DDRD = 0xfc; //段码选择  11111100

  PORTD = 0xff;
  PORTB = 0xff;
  PORTC = 0xff;
}
/*******************************************************************************
 *函数原型: void init_devices(void);
 *功能:m8初始化
 *参数:
 *说明:
 *******************************************************************************/
//call this routine to initialize all peripherals
void init_devices(void)
{
  //stop errant interrupts until set up
  CLI(); //disable all interrupts
  port_init();
  timer0_init();
  USART0_Init();
  MCUCR = 0x00;
  GICR = 0x00;
  TIMSK = 0x01; //timer interrupt sources
  SEI(); //re-enable interrupts
  //all peripherals are now initialized
}

/*******************************************************************************
 *函数原型: void main() ;
 *功能:主程序,执行初始化操作,循环等待案件事件
 *参数:
 *说明:
 *******************************************************************************/
void main()
{
  init_devices();
  //标志和常量初始化
  task = 0x00;       //按键状态
  state = 0x00;      //显示位置变量
  keyprocess = 0;    //按键有效标志
  keydone = 1;       //按键任务完成标志
  ledtime = ledshow; //累计闪烁时已点亮或已熄灭时间
  ledtask = 0;       //当前的闪烁状态 0 代表亮
  keymark = 0;       //指示当前工作状态为正常工作状态
  USART0_TransmitString("OK!\r\n");
  while (1)
  {
    key_processing();
  }
}


路过

鸡蛋

鲜花

握手

雷人

发表评论 评论 (1 个评论)

回复 phz0008 2008-7-7 19:05
看到hotpower的贴后在proteus用mega8玩了一把0耗时的键盘扫描,顺便试一把怎么保证键盘扫描时数码管不闪。呵呵