分类 单片机 下的文章

从10月份开始做,中间插入做了一个抢答器,走走停停搞到如今总算满意收工。
主要功能:可设置系统参数,可保存调用系统参数。电机通电记录时长,每通电一次自动换一次电压档,检测微动开关状态,检测从微动开关状态,判定时长,通过数码管和LED灯显示,蜂鸣器警报。
原理图:
3edd751egb4adc5fda30c.jpg
程序:

//test.c*/
sfr  WDT = 0xA6;   // 89S52 Watchdog Timer */
#include <intrins.h>
#include <stdio.h>
#include<AT89X52.H>
#include<stdlib.H>
#include <AT24C16.h>
#define uchar  unsigned char
#define  WDTRST WDT=0x1E;WDT=0xE1;
 
#define LEDCode        P0
#define LEDdisplay     P2
#if 0
#define MENU_PIN  P1_4
#define PLUS_PIN  P1_3
#define REDUCE_PIN  P1_2
#define MASTER_PIN     P3_3
#define SLAVE_PIN      P1_2
#define BOARD_PIN      P1_4
#define L1_PIN         P1_0
#define L2_PIN         P1_1
#define L3_PIN         P1_3
#else
 
#define MASTER_PIN     P3_3 //定义通电端口
#define MENU_PIN  P1_7 //定义设置按钮
#define PLUS_PIN  P1_6 //定义加键按钮
#define REDUCE_PIN  P1_5 //定义减键按钮
#define SLAVE_PIN      P1_4 //定义从微动端口
#define BOARD_PIN      P1_3 //定义安装板端口
#define L3_PIN         P1_2   //定义档位端口
#define L2_PIN         P1_1 //定义档位端口
#define L1_PIN         P1_0 //定义档位端口
#endif
#define BELL         P3_7  //蜂鸣器端口
 
  
static uchar code  digital[]= {0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xd8,0x80,0x90,0xa0,0xa1,0xa3,0xc7,0x88,0x89,0x91}; //字库码"0,1,2,3,4,5,6,7,8,9,a,d,o,L,R,H,y"
static uchar code select[]={0xff,0xef,0xdf,0xbf,0x7f};  //选位字码
static uchar bod_led[]={0x00,0xfb};   //安装板指示灯 (开关[0],显示代码[1])
static uchar sla_led[]={0x01,0xf7};  //辅助触点指示灯(开关[0],显示代码[1])
static uchar relay[]={0x01,0xff,0xfc,0xfd,0x01};  //继电器三个档位(档位[0],档位代码[1]-[3],记忆上次档位)
static signed char SLa[]={0x01,0x01,0x0a,0x0d,0x05,0x00}; //定义从微动检测(闪动位[0],显示[1]-[4],小数点[5])
static signed char Bod[]={0x01,0x01,0x0b,0x0c,0x08,0x00}; //定义安装板检测
static signed char L1[]={0x03,0x05,0x09,0x00,0x0d,0x02};  //定义第1档最大容忍时间
static signed char L2[]={0x03,0x00,0x06,0x00,0x0e,0x02};  //定义第2档最大容忍时间
static signed char L3[]={0x03,0x00,0x04,0x00,0x0f,0x02};  //定义第3档最大容忍时间
static signed char dis[]={0x00,0x07,0x06,0x10,0x08,0x00};  //分配计时空间
static signed char led[]={0,0,0,0,0}; //计时数据临时存放
static signed char *p=dis; //显示指针
static bit write;//数据保存
static uchar index = 0x01; //起始显示位
static uchar delay;  //等待时间,用于主从微动切换
static bit menu;  //调用设置项
static bit changed;  //选项切换
static unsigned int belltime; //蜂鸣器时长控制
//延迟时长大约为3us*x*/
void delay3xus(int unsigned x)
{
 unsigned int i;
 for(i=0;i<x;i++){WDTRST};
}
//参数读取*/
void read()
{
 uchar i;
 READ_BYTE(5,&SLa[1]);
 READ_BYTE(10,&Bod[1]);
 for(i=1;i<4;i++)
 {
  READ_BYTE(i+15,&L1[i]);
  READ_BYTE(i+20,&L2[i]);
  READ_BYTE(i+25,&L3[i]);
 }
}
//参数保存*/
void save()
{
 uchar i;
 P3= 0xfe;  //设置存储器可写
 EA= 0x00; //禁止其他中断,优先处理此程序
 P2 =0xff;
 P0 = 0xff;
 P1= 0xff;
 WRITE_BYTE(5,SLa[1]);
 delay3xus(500);  //写操作需要等待存储器
 WRITE_BYTE(10,Bod[1]);
 delay3xus(500);
 for(i=1;i<4;i++)
 {
  WRITE_BYTE(i+15,L1[i]);
  delay3xus(500);
   WRITE_BYTE(i+20,L2[i]);
  delay3xus(500);
  WRITE_BYTE(i+25,L3[i]);
  delay3xus(500);
 }
 P3= 0xff;  //设置存储器只读
 EA= 0x01;
}
//初始化*/
void init()
{
 P0  = 0xff;
 P1  = 0xff;
 P2  = 0xff;
 P3  = 0xff;
 EX0  = 0x00;
 ET0  = 0x00;
 EX1  = 0x00;
 ES   = 0x00;
 TL0= 0xa0;  //定时器T0=30ms
 TH0= 0x15;
 TL1= 0xbf;   //定时器T1=4ms
 TH1= 0xe0;
 TMOD = 0x11;  //设置定时器模式
 ET1  = 0x01;   //允许定时器T1中断
 TR1= 0x01;   // 启用定时器T1
 
 TL2= 0xe0;  //10ms
 TH2= 0xb1;
 RCAP2H=TH2;   //中断溢出自动对TH2赋值(T2定时器为16位自动硬件赋值,故不必在中断产生后,再对寄存器再次赋值,但需要对TF2状态进行清零)
 RCAP2L=TL2;   //中断溢出自动对TL2赋值
    T2MOD= 0x00; //设置为定时器
 ET2  = 0x00;   // 允许T2中断
 TR2 = 0x00;    // 启动定时器
 
 
 
 EA = 0x01;  //中断总开关
}
bit shift_voltage();
void clear();
void voltage_led(uchar tmp);
//按键数值加减处理*/
void plus_reduce()
{
 if(!PLUS_PIN)
 {
  delay3xus(25000);
  p[1]+=1;
  if(p[1]>9)
  {
   p[2]+=1;
  if(p[2]>9)
   {
   p[2]=0;
   if(++p[3]>9) p[3]=0;
   }
   p[1]=0;
  }
  write =1;
 }
 else if(!REDUCE_PIN)
 {
  delay3xus(25000);
  p[1]-=1;
  if(p[1]<0)
  {
   p[2]-=1;
  if(p[2]<0)
   {
   p[2]=9;
   if(--p[3]<0) p[3]=9;
     
   }
   p[1]=9;
  }
  write =1;
 }
}
//调用参数设置页
 * menu、changed条件通过T1检测
*/
void setting(void)
{
 if(menu)
 {  
  P3= 0xff;
  delay3xus(0xffff);
  while(menu)
  {
    WDTRST
  p=SLa;   //从微动设置项
   if(!PLUS_PIN)
   {
    delay3xus(25000);
    p[1]=!p[1];
    write =1;   //数据改变,需要更新
   }
   else if(!REDUCE_PIN)
   {
    delay3xus(25000);
    p[1]=!p[1];
    write =1;
   }
   if(changed)
   {
   changed=0;
   if(menu)
    {
    p=Bod;     //安装板设置项
    delay3xus(33333);
    }
   while(menu)
    {
     WDTRST
    if(!PLUS_PIN)
     {
      delay3xus(25000);
      p[1]=!p[1];
     write =1;
     }
    else if(!REDUCE_PIN)
     {
      delay3xus(25000);
      p[1]=!p[1];
     write =1;
     }
    if(changed)
    {
     changed=0;
     break;
    }
    }
   if(menu)
    {
    p=L1;    //第一档时间设置
    delay3xus(33333);
    }
   while(menu)
    {
       WDTRST
      plus_reduce();
    if(changed)
    {
     changed=0;
     break;
    }    
    }
   if(menu)
    {
    p=L2;   //第二档时间设置
    delay3xus(33333);
    }
   while(menu)
    {
     WDTRST
    plus_reduce();
    if(changed)
    {
     changed=0;
     break;
    }    
    }
   if(menu)
    {
    p=L3;    //第三档时间设置
    delay3xus(33333);
    }
   while(menu)
    {
     WDTRST
    plus_reduce();
    if(changed)
    {
     changed=0;
     break;
    }    
    }
    }
    }
   clear();
   voltage_led(relay[0]);
 }
 p=dis;   //退出设置菜单,回到计时状态
 if(write)
 {
   save();
  write=0;   //已保存
  relay[0]=3;  //初始化继电器
 shift_voltage();   //初始化继电器
 clear();   //初始化计时器
 voltage_led(relay[0]);   //初始化继电器
 
 }
 
 
}
//用于主从微动切换等待延迟*/
void start_t0(uchar t)
{
 ET0  = 0x01;
 TR0= 0x01;
 delay=t;
}
//用于主从微动切换等待延迟*/
void stop_t0()
{
 TL0= 0xa0;
 TH0= 0x15;
 TMOD = 0x11;
 ET0  = 0x00;
 TR0= 0x00;
}
//开始计时*/
void start_t2()
{
 ET2  = 0x01;
 TR2 = 0x01; 
}
//停止计时*/
void stop_t2()
{
 TL2= 0xe0;
 TH2= 0xb1;
 ET2  = 0x00;
 TR2 = 0x00;
 
}
//主微动检测,去抖动*/
bit master()   
{
 uchar sum[]={100,300};
 while(sum[0]&&sum[1])  //300*20us
 {
  MASTER_PIN=1;
  _nop_();
  if(MASTER_PIN)
  {
   sum[0]--;
  }
  else
  {
   sum[1]--;
  }
   WDTRST
 }
  return  (bit)sum[0];
}
//从微动检测,去抖动*/
bit slave()
{
 uchar sum[]={100,300};
  while(sum[0]&&sum[1]) // 300*20us
 {
   SLAVE_PIN=1;
  _nop_();
   if(SLAVE_PIN)
  {
   sum[0]--;
  }
  else
  {
   sum[1]--;
  }
    WDTRST
 }
 
 return  (bit)sum[0];
}
//报警蜂鸣*/
void bell()
{
 if(!belltime)
 {
  BELL=0;
  belltime=500; //蜂鸣时长控制
 }
}
//清空计时*/
void clear()
{
 dis[0]=0; //闪动位
 dis[1]=0; //数值1
 dis[2]=0; //数值2
 dis[3]=0; //数值3
 dis[4]=0; //数值4
 dis[5]=3; //标点符号位
}
//处理时长警报*/
void time_chk()
{
 bit overtime=0;
 //超时报警处理 */
 switch(relay[0]) 
 {
  case 0x01:
  if(L1[3]>dis[4]) break;
   else if((L1[3]==dis[4]))
   {
   if(L1[2]>dis[3]) break;
    else if(L1[2]==dis[3])
    {
    if(L1[1]>dis[2]) break;
    else if(L1[1]==dis[2]) break;
    else overtime=1; break;
    }else overtime=1; break;
   }else overtime=1; break;
  case 0x02:
  if(L2[3]>dis[4]) break;
   else if((L2[3]==dis[4]))
   {
   if(L2[2]>dis[3]) break;
    else if(L2[2]==dis[3])
    {
    if(L2[1]>dis[2]) break;
    else if(L2[1]==dis[2]) break;
    else overtime=1; break;
    }else overtime=1; break;
   }else overtime=1; break;
  case 0x03:
  if(L3[3]>dis[4]) break;
   else if((L3[3]==dis[4]))
   {
   if(L3[2]>dis[3]) break;
    else if(L3[2]==dis[3])
    {
    if(L3[1]>dis[2]) break;
    else if(L3[1]==dis[2]) break;
    else overtime=1; break;
    }else overtime=1; break;
   }else overtime=1; break;
    default:
    ;
 }
 if(overtime)
 {
  dis[0]=4;  //计时器闪动
  bell();
 }
}
//换电压档位*/
bit shift_voltage()
{
 char tmp=relay[0]; //程序过程不对继电器状态直接操作,避免继电器抖动
 if(!(L1_PIN&&L2_PIN)) return 0;   //人工操作电压档,不进行换挡操作,直接返回
 if(++tmp>3) tmp=1;   //换挡
 //空档跳过*/
 switch(tmp)
 {
  case 1:
  if(!L1[1]&&!L1[2]&&!L1[3])
   {
    tmp++;
   if(!L2[1]&&!L2[2]&&!L2[3])
    {
    tmp++;
    if(!L3[1]&&!L3[2]&&!L3[3])
    {
     tmp=1;     
    }
    }
   }
   break;
  case 2:
  if(!L2[1]&&!L2[2]&&!L2[3])
   {
    tmp++;
   if(!L3[1]&&!L3[2]&&!L3[3])
    {
    tmp=1;
//    if(!L1[1]&&!L1[2]&&!L1[3])
//    {
//     tmp=1;     
//    }
    }
   }
   break;
  case 3:
  if(!L3[1]&&!L3[2]&&!L3[3])
   {
    tmp=1;
   if(!L1[1]&&!L1[2]&&!L1[3])
    {
    tmp++;
    if(!L2[1]&&!L2[2]&&!L2[3])
    {
     tmp=1;
    }
    }
   }
   break;
  default:;
 }
 relay[4]=relay[0];
 relay[0]=tmp;   //处理换挡完毕,将档位数值写入继电器档位,换挡完成
 return 1;
}
//档位指示灯*/
void  voltage_led(uchar tmp)
{
 switch(tmp)
 {
  case 1:
  P3_4=1;P3_5=0;P3_6=1;break;
  case 2:
  P3_4=0;P3_5=1;P3_6=0;break;
 default:
  P3_4=0;P3_5=0;P3_6=0;break;
 }
 
}
/
bit  bod_open=0;//需要重新调节电压档,临时解除“电机拿出测试台,恢复到首档” ,计时过程开始恢复正常
void  renew()
{
  if(!MENU_PIN) bod_open=0;   //按设置按钮解除“电机拿出测试台,恢复到首档”
 if(Bod[1]&&BOARD_PIN&&L1_PIN&&L2_PIN&&L3_PIN&&bod_open)
  {
   if(L1[1]||L1[2]||L1[3])
    {
    relay[4]=relay[0]=1;
    }
    else if(L2[1]||L2[2]||L2[3])
    {
    relay[4]=relay[0]=2;
    }
    else if(L3[1]||L3[2]||L3[3])
    {
    relay[4]=relay[0]=3;
    }
   else  relay[4]=relay[0]=1;
   voltage_led(relay[0]) ;
   
  }
}
// 人工操作电压档位*/
void manual_control()
{
 char tmp=relay[0];
 if(!L3_PIN)  //档位端口3,设置为第三档
 {
  tmp=3;
  relay[4]=relay[0]=tmp;
  voltage_led(relay[0]) ;
 }
 else if(!L2_PIN)  //档位端口2,设置为第二档
 {
  tmp=2;
  relay[4]=relay[0]=tmp;
  voltage_led(relay[0]) ;
 }
 else if(!L1_PIN)  //档位端口1,设置为第一档
 {
  tmp=1;
  relay[4]=relay[0]=tmp;
  voltage_led(relay[0]) ;
 }
 else if(!PLUS_PIN)
 {
  delay3xus(25000);
  shift_voltage();
  tmp=relay[4]=relay[0];
  voltage_led(relay[0]) ;
 }
 else if(!REDUCE_PIN)
 {
  delay3xus(25000);
  if(--tmp<1) tmp=3;
  switch(tmp)
  {
   case 1:
   if(!(L1[1]||L1[2]||L1[3]))
    {
    tmp=3;
    if(!(L3[1]||L3[2]||L3[3]))
    {
     tmp--;
     if(!(L2[1]||L2[2]||L2[3]))
     {
      tmp=1;
     }
    }
    }
    break;
   case 2:
   if(!(L2[1]||L2[2]||L2[3]))
    {
    tmp--;
    if(!(L1[1]||L1[2]||L1[3]))
    {
     tmp=3;
     if(!(L3[1]||L3[2]||L3[3]))
     {
      tmp=1;
     }
    }
    }
    break;
   case 3:
   if(!(L3[1]||L3[2]||L3[3]))
    {
    tmp--;
    if(!(L2[1]||L2[2]||L2[3]))
    {
     tmp--;
     if(!(L1[1]||L1[2]||L1[3]))
     {
      tmp=1;
     }
    }
    }
    break;
   default:;
  }
  relay[4]=relay[0]=tmp; 
  voltage_led(relay[0]) ;
 }
 
}
//主函数入口,主要过程为控制计时和状态检测*/
void main()
{
 unsigned int i;
 init();  //初始化寄存器
 read();  //读取设置参数
 for(i=2500;i!=0;i--) delay3xus(0xff); //冷开机,比较器输入电容预充电,需要等待
 relay[0]=3;     //初始化继电器参数
 shift_voltage();   //使继电器恢复到首参数档位
 clear();
 voltage_led(relay[0]) ;
 while(1)
 {
  setting() ;  //设置参数
  WDTRST
  renew(); //电机拿出测试台,恢复到首档
    manual_control();  //手工操作电压档
  //主微动闭合,通电开始*/
  if(master())
  {
   start_t2(); //启动计时
  
   voltage_led(relay[0]) ;
   start_t0(35);
  while(delay&&master())
   {
   if(!slave())
    {
    stop_t0();
    break;
    }
    WDTRST
   }
   stop_t0();
     if(!delay&&SLa[1])
   {
   bell();
   }
  
   //主微动闭合,从微动断开,通电过程*/
   while(master())
   {
   if(slave())
    {
    start_t0(35);
    while(delay)
    {
     if(!master())
     {
      break;
     }
      WDTRST
    }
    if(!delay&&SLa[1])
    {
     bell();
     continue;
    }
    }
   }
   stop_t2();
  if(!(led[2]||led[3]||led[4]))
   {
   for(i=1;i<5;i++) led[i]=0;
   voltage_led(relay[4]);
   continue;
   }
   //主微动断开,通电过程结束*/
   start_t0(35);
   while(delay)
   {
    WDTRST
   if(slave())
    {
    start_t0(13);
    while(delay)
    {
     if(!slave()||master())
     {
      stop_t0();
      break;
     }
      WDTRST
    }
    if(master())
    {
     delay=1;
     break;
    }
    else if(delay)
    {
     delay=0;
     break;
    }
    else
    {
     delay=1;
     break;
    }
    }
    else if(master())
    {
      stop_t0();
      break;
    }
   }
  if(!delay&&SLa[1])
   {
    bell();
    }
  
   time_chk();
   shift_voltage();
  for(i=1;i<5;i++) led[i]=0;   //清空临时计时
  bod_open=1;   //通电一次恢复“电机拿出测试台,恢复到首档 ”
  if(Bod[1])   //安装板检测
   {
   if(BOARD_PIN) bell();
   }
  }
   }
}
//定时器T0主要用于主从微动开关延迟等待用*/
void t0() interrupt 1  //30ms
{
 TL0+= 0xa0;
 TH0+= 0x15;
 if(delay>0)
 {
  delay--;
 }
 else
 {
  ET0=0;
  TR0=0;
  TL0= 0xa0;
  TH0= 0x15;
 }
}
/
static uchar skip,count;
void t1() interrupt 3 //4ms
{
 TL1+= 0xbf;
 TH1+= 0xe0;
 sla_led[0]=!SLAVE_PIN;
 bod_led[0]=!BOARD_PIN;
 if(belltime)
 {
  belltime--;
 
 }else BELL=1;
 if(index>p[0])
 {
  LEDCode = digital[p[index]]&(p[5]==index?0x7f:0xff);
  LEDdisplay = select[index]&(bod_led[0]?bod_led[1]:0xff)&(sla_led[0]?sla_led[1]:0xff)&relay[relay[0]];
 
 }
 else if( skip >4 )
 {
  LEDCode = digital[p[index]]&(p[5]==index?0x7f:0xff);
  LEDdisplay = select[index]&(bod_led[0]?bod_led[1]:0xff)&(sla_led[0]?sla_led[1]:0xff)&relay[relay[0]];
  if(index==p[0]) skip=0;
 }
 else
 {
  LEDCode = p[5]==index?0x7f:0xff;
  LEDdisplay = select[index]&(bod_led[0]?bod_led[1]:0xff)&(sla_led[0]?sla_led[1]:0xff)&relay[relay[0]];
 }
 if(++index>4)
 {
  index=0x01;
  skip++;
 }
 if(!MENU_PIN)
 {
  if(count<200) count++;
 }
 else
 {
  if(count==200)
  {
   menu=!menu;
   count=0;
  }
  else if(count>10)
  {
   changed=1;
  }
  count=0;
 }
}
//AT89S52特有的16位中断自动载入TH2、TL2计时器,用于通电时长计时*/
void t2() interrupt 5  //10ms
{
 uchar i;
 TF2 = 0; // 溢出标志必须由软件清零
 EXF2 = 0; // 捕获标志必须由软件清零
 menu=0;//测试过程不可设置
 if(++led[1]>9)
 {
  led[1]=0;
  if(++led[2]>9)
  {
   led[2]=0;
  if(++led[3]>9)
   {
   led[3]=0;
   if(++led[4]>9)
    {
    led[4]=0;
    }
   }
  }
 }
 if(led[4]||led[2]||led[3])
 {
  dis[0]=0;
  dis[5]=3;
  for(i=1;i<5;i++)
  {
   dis[i]=led[i];
  }
 }
}
 
//AT24C16.H*/
#ifndef __AT24C16_H__
#define __AT24C16_H__
#include <intrins.h>
#define uchar unsigned char
#define Delay1us() _nop_();_nop_()
#define Delay2us() _nop_();_nop_();_nop_();_nop_()
#define Delay5us() _nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_()
sbit SDA=P3^2; //模拟数据线
sbit SCL=P3^1; //模拟时钟线
//sbit WC=P3^0; //读写控制线
//开始信号
void START_I2C(void)
{
 SCL=1; //空闲状态
 Delay5us();
 SDA=0; // 1->0跳变,开始
 Delay5us();
 SCL=0; //准备发送数据
 Delay1us();
}
//结束信号
void STOP_I2C(void)
{
 SDA=0;
 SCL=1;
 Delay5us();
 SDA=1; // 0->1跳变,结束
 Delay2us();
}
//
//应答信号
void ACK_I2C(bit ack)
{
 SDA=ack?1:0; //发出应答或非应答
 Delay2us();
 SCL=1; //保持一个高电平周期
 Delay5us();
 SCL=0;
 Delay2us();
}
*/
//发送一个字节
bit SEND_BYTE(uchar byte)
{
 uchar i;
 bit ack;
 SDA = 1;
 for(i=0;i<8;i++)
 {
 SDA=((byte<<i)&0x80)?1:0; //赋值发送位
  Delay1us();
  SCL=1; //保持一个高电平周期
  Delay5us();
  SCL=0;
  Delay2us();
 }
 Delay2us();
 SDA=1; //准备接收应答位
 Delay2us();
 SCL=1;
 Delay2us();
 ack=SDA?1:0; //判断应答位
 SCL=0;
 return(ack); //返回应答位
}
//接收一个字节
uchar RECEIVE_BYTE(void)
{
 uchar i;
 uchar byte=0;
 SDA=1; //输入方式,等待接收
 for(i=0;i<8;i++)
 {
  SCL=1;
  Delay2us();
 byte=byte<<1;
  if(SDA) //读数据位,存入byte
  byte+=1;
  Delay5us();
  SCL=0; //提供一个低电平周期
 }
 
 Delay2us();
 return(byte); //返回接收字节
}
//向从机写一个字节
bit WRITE_BYTE(uchar address, uchar byte)
{
 START_I2C();
 if(SEND_BYTE(0xa0))
 return 1; //非应答,返回
 if(SEND_BYTE(address))
 return 1; //非应答,返回
 if(SEND_BYTE(byte))
 return 1; //非应答,返回
 STOP_I2C();
 return 0;
}
//读取从机一个字节
uchar READ_BYTE(uchar address, uchar *byte)
{
 
 START_I2C();
 if(SEND_BYTE(0xa0))
 return 1; //非应答,返回
 if(SEND_BYTE(address))
 return 1; //非应答,返回
 START_I2C();
 if(SEND_BYTE(0xa1))
 return 1; //非应答,返回
 *byte=RECEIVE_BYTE();
// ACK_I2C(0); //应答信号
 STOP_I2C();
 return 0;
}

成品:
3edd751egb4adcc7fae5a.jpg
3edd751egb4adcdacb540.jpg
3edd751eg7877c7e24e6a.jpg
3edd751egb4adcfec40c2.jpg
3edd751egb4add10d031c.jpg
3edd751egb4add1b168ee.jpg
3edd751egb4add29bb6bc.jpg
3edd751egb4add342dd56.jpg
3edd751egb4add3e6350a.jpg
3edd751egb4add48ff9c2.jpg
3edd751egb4add54e9404.jpg
3edd751egb4add5ebbfc0.jpg
3edd751egb4add69732ff.jpg
3edd751egb4add744cfcd.jpg

1、流程
裁判按下复位键,抢答器进入抢答模式,各组人员才可以通过抢答按钮进行抢答,抢答结果显示在抢答器上。

2、实现
使用单片机对输入的电平信号处理(低电平有效):1路为复位,8路为抢答。复位前对8路抢答状态进行判断,防止作弊。一次性对8路抢答器状态进行采样,将结果存储在num数组内,最大可存储4路同时抢答结果。LED显示部分由T0定时器每3ms动态将num数组的结果点亮。蜂鸣器的时长由T0定时器控制

3、原理图
3edd751exb1376dbafc0a.jpg
4、代码

    #include<AT89X51.H>
    #include<stdio.H>
    #include<stdlib.H>
    sfr WDTRST = 0xA6;  
     
    #define OUTPORT1  P0
    #define OUTPORT2  P2
    #define BELL     P3_5
    #define CLR           P3_4
    #define INPORT    P1
     
    Static unsigned char code  digital[]= {0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xd8,0x80,0xe1,0x91,0x88,0x86,0xa0,0xa1,0x40,0x79,0x24,0x30,0x19,0x12,0x02,0x58,0x00}; //字库码"0,1,2,3,4,5,6,7,8,j,y,r,e,a,d,0.,1.,2.,3.,4.,5.,6.,7.,8."
    static unsigned char code select[]={0xff,0xfb,0xf7,0xef,0xdf};     //选位字码
     
    static unsigned char  rycle=0x00; //当前数码管显示位控制
    static unsigned char data num[5]={0x4,0x0e,0x09,0x0A,0x09};//检测结果
    static unsigned char data i,j;
    static unsigned char data  count;//蜂鸣器警报时长
    static unsigned char bdata temp;  //8位取样数据
    static bit state=0x00 ;//复位状态保存
    static unsigned int sum[]={0x00,0x00};   //复位键检测时间/自动复位的时长
    static bit timeCLR=0x00;//自动复位状态
    static bit stateCLR=0x00; //首次状态保留
     
    void delay(char t);
    void ready();
     
     
    void init()
    {
          P0 = 0xff;
          P1 = 0xff;
          P2 = 0xff;
          P3 = 0xFF;
          EX0 = 0x00;
     
          EX1 = 0x00;
          ET1 = 0x00;
          ES = 0x00;
          ET2 = 0x00;
          WDTRST = 0x1E;
          WDTRST = 0xE1;
     
          BELL = 0;
     
          TMOD=0x11;
          EA=0x01;
     
          TR0=0x00;
          ET0=0x00;
          TL0=0x18;      //T0=130ms
          TH0=0x02;
     
          count=0;
     
          TL1=0x24;    //T1=4.5ms
          TH1=0xfa;
          TR1=0x01;
          ET1=0x01;
     
    }
    void belltime() interrupt 1
    {
          TL0+=0x18;
          TH0+=0x02;
          if(!count--)
          {
                  BELL=0x00;
                  ET0=0x00;
                  TR0=0x00;
          }
     
     
    }
     
     
    void display() interrupt 3
    {
          TL1+=0x24;
          TH1+=0xFA;
          if(rycle<=num[0])
          {
                 OUTPORT1 = digital[num[rycle]];
                 OUTPORT2 = select[rycle];
                 ++rycle;
          }else  rycle=0x00;
     
     
     
          if(!CLR)
          {
                 sum[0]++;
               if(sum[0]>666&&stateCLR)
                 {
                        timeCLR=!timeCLR;
                      stateCLR=0;
                        state=!state;   //避免按复位键超时,导致状态重复反转
                 }
                 
          }else
          {
                 sum[0]=0;
                 stateCLR=1;                //复位键退出,将状态设置为可复位状态
          }
     
     
          if(timeCLR&&!state)
          {
                 if(sum[1]++>2333)                    //在有抢答结果的情况下,等待复位
                 {
                        if(temp==0xff)                    //防作弊
                        {
                               ready();
                        }else              
                        {    
                               sum[1]=2167-(int)666*rand();        //延迟0.5-2.5S
                        }
                 }
     
          }else sum[1]=0;
     
     
     
     
    }
    void delay(char t)
    {
          count=t;
          TL0=0x18;
          TH0=0x02;
          ET0=0x01;
          TR0=0x01;
    }
     
    void main()
    {
          init();
         while(1){
                 WDTRST=0x1E;
                 WDTRST=0xE1;
                 temp = INPORT;            //对8个连接器同时采样
                 if(!CLR&&temp==0xff)
                 {
                    ready();
                 }
                 for(i=1,j=0;i<=8&&state;i++)         //8个连接器状态检测
                 {
                        if(!(temp & 0x01))
                        {
                               j++;
                               num[0]=j; //num[0]存放首批按下的连接器个数
                               num[j]=(j>1?i+0x0f:i); //存放按下状态的连接器序列
     
                               
                               BELL=0x01;
                               delay(1);
                        }
                        temp >>= 1;
                 }
     
     
                 if(j>0) state=0; //判断是否抢答成功,成功就禁止再次抢答
          
     
     
          }
    }
     
     
    void ready()
    {
          num[0]=0x04;
          num[4]=0x0b;
          num[3]=0x0c;
          num[2]=0x0e;
          num[1]=0x0a;
     
          state=0x01;
     
          BELL=0x01;
          delay(1);
    }

5、成品效果
3edd751exb1376cc6fa63&690.jpg
3edd751exb137729e5330&690.jpg
3edd751exb13777447693&690.jpg
3edd751exb1377d3a5419.jpg

月底CO开关就要送样,上个月底答应小郑要把非独立的检测方式改为独立的方式,总不能食言。利用周末两天时间在家搞这个电路:12个微动开关(24根线)、12组LED,24+12超出89S51总的32个IO口,故增加一片74LS138进行IO扩展。通过对IO口动态扫描电平状态,检测其相对应的微动开关的状态,再将检测的结果写到相对应的LED,LED使用ULN2803进行扩流,弥补单片机驱动能力不足的弱点。

原理图
3edd751ex9e586829ee35.jpg
焊接完成的PCB板
3edd751exa6ec5bcc6206.jpg
3edd751exa6ec5f8db1da.jpg
由于只是扫描和写出,故程序没啥难度,这也是我写的最快的一个程序

#include "AT89X51.H"
sfr WDTRST = 0xA6;  
static unsigned char code write_data[12]={0x0f,0x8f,0x4f,0xcf,0x2f,0xaf,0x6f,0xef,0xfe,0xfd,0xf9,0xf7};
unsigned char i;


void init()
{
 P0  = 0x00;
 P1  = 0xff;
 P2  = 0xff;
 P3  = 0x0f;
 EX0  = 0x00;
 ET0  = 0x00;
 EX1  = 0x00;
 ET1  = 0x00;
 ES   = 0x00;
 ET2  = 0x00; 
 WDTRST = 0x1E;
 WDTRST = 0xE1;

}

void main()
{
 init();
  while(1){
  WDTRST=0x1E;
  WDTRST=0xE1;
   for (i=0;i<12;i++)
  {
   P2=write_data[i] ;
   switch(i)
   { 
    case 0: P0_0=!P1_0; break;
    case 1: P0_1=!P1_1; break;
    case 2: P0_2=!P1_2; break;
    case 3: P0_3=!P1_3; break;
    case 4: P0_4=!P1_4; break;
    case 5: P0_5=!P1_5; break;
    case 6: P0_6=!P1_6; break;
    case 7: P0_7=!P1_7; break;
    case 8: P3_4=!P3_0; break;
    case 9: P3_5=!P3_1; break;
    case 10: P3_6=!P3_2; break;
    case 11: P3_7=!P3_3; break;
    default: break;

   }

  }

 }
}

把程序编译成HEX格式,烧录到89S51中即可,有了躯干和灵魂的PCB板就跑了起来。

组装到箱子里,替换原电路板
3edd751exa6ec4b2b2794.jpg
密麻麻的线,其实很多都是相同信号线(不想再去大费周折的精简重焊,就按原本的电气特性,重新做的这个PCB兼容电路)
3edd751exa6ec4d31f9d2.jpg
3edd751exa6ec4f75c221.jpg
3edd751exa6ec53dee883.jpg
3edd751exa6ec573582b5.jpg
原电路板为立林做的,就留着做应急备用
3edd751exa6eceefa87b7.jpg

经过N次的软硬件调试,终于是完美了。
过程中遇到的问题:①信号的误动作②程序跑飞的硬件复位③光耦电路的电阻选取④连续重复测试过程,程序的健壮性
3edd751exa454f18949f5.jpg
3edd751exa454f714443c.jpg
实物图
3edd751exa454f76092f2.jpg
原理图,Port1.0和Port1.1串接到主微动,Port2.0和Port2.1直接连接辅微动。当主微动导通时,由于电阻分压使得光耦导通,P3.3为低电平;辅微动闭合,P3.2为低电平。主微动信号和辅微动信号为互斥的两个信号量,当主微动为闭合状态,辅微动为断开状态,反之亦然。

#include "AT892051.H"
 
#define uint  unsigned int
#define Master_switch P3_3
#define Slave_switch  P3_2
#define Yellowlight   P3_0
#define Redlight     P3_1
#define Bell         P3_7
#define rst         P1_7
#define delaytime    20 //20*50ms
#define watchtime    40 //40*50ms
#define count     6  //6*50ms
#define chknum     48 //48*20us

static uint dtime;
static uint wait;
static uint dstate;
static uint dcheck;
static uint belltime;



bit checkstate(bit p)   //周期(21+chknum*20)us
{
 int sum[]={chknum,chknum};
 if(p)        //检测主微动为0,从微动为1
 {
  while(sum[0]&&sum[1])  // chknum*20us
   {
   Slave_switch=1;
   if(Slave_switch)
   {
    sum[0]--;
   }
   else
   {
    sum[1]--;
   }
   }
 }
 else
 {
  while(sum[0]&&sum[1])
   {
   Master_switch=1;
   if(Master_switch)
   {
    sum[0]--;
   }
   else
   {
    sum[1]--;
   }
   }
 }
  return  (bit)sum[0];
}

//看门狗T0监测主程序
void watchdog() interrupt 1  using 3
{
 
 if(!--dtime)
 {
   rst=0;     //主程序跑飞,系统复位
  }
 if(!--belltime)
 {
  Bell=1;
 }

}

//看门狗T1检测看门狗T0
void int50ms() interrupt 3  using 1
{
 if(wait) wait--;
 if(dcheck==dtime)  
 {
  if(++dstate>watchtime) rst=0;   //看门狗T0停止,系统复位
 }
 else
 {
  dcheck=dtime;
  dstate=0;
 }
}

//初始化
void init()
{
 EA = 1;
 ET1 = 1;
 ET0 = 1;
 PT1 = 0;
 PT0 = 0;
 TMOD = 0X11;
 TL1 = 0X00;
 TH1 = 0X00;  
 TL0 = 0XFF;
 TH0 = 0X00;
 TR1 = 1;
 TR0 = 1;
 P3 = 0xff;
 P1 = 0xff;
 dstate = 0;
 dcheck = 0;
 Bell = 0;
 belltime=5;
}

//指示灯状态
void LED()
{
 if(checkstate(1)) Redlight=0;
 else Redlight=1;
}

void main() using 0
{
 init();
 for(dtime=watchtime;1;dtime=watchtime)
 {
  LED();

  
  if(checkstate(0))
  {
   Yellowlight=0;
   wait=delaytime;  //在规定时间内,检测从微动断开
   while(wait)
   { 
    if(checkstate(1))
    {
     Redlight=0;
    }
    else
    {
     Redlight=1;
     wait=delaytime;
     break;
    }
   }
   if(!wait)     //超时,触发蜂鸣器
   {
    Bell=0;
    belltime=20;
    continue;
   }


     
   for(dtime=watchtime;checkstate(0);dtime=watchtime)
   {
    if(checkstate(1)) //若从微动闭合,则在规定时间段内检测主微动为断开状态
    { 
     Redlight=0;
     wait=delaytime;
     while(wait)
     {
      if(!checkstate(0))
      {
       Yellowlight=1;
       wait=delaytime;
       break;
      }
      LED();
     }
     if(!wait)   //超时,触发蜂鸣器
     {
      Bell=0;
      belltime=20;
     }

    }else Redlight=1;
    }

  
   
    Yellowlight=1;
   LED();
   wait=delaytime;
   
   while(wait)  //在规定时间内,检测从微动为闭合状态
   {
    if(checkstate(1))  //若从微动为闭合状态 
    {
     Redlight=0;
     wait=count;
     while(wait)   //检测持续时间是否满足count*50ms
     {
      if(!checkstate(1)) //在count*50ms时间内,若从微动断开,则中断退出
      {
       Redlight=1;
       wait=delaytime;
       break;  
     
      }


     }
     if(!wait)  //检测时间满足count*50ms,退出
     {
      wait=delaytime;
      break;
     }
     else   //未能持续count*50ms,在规定时间段内检测主微动为闭合状态
     {
      while(wait)
      {
       if(checkstate(0))
       {
        Yellowlight=0;
        wait=delaytime;
        break;
       }       
      }
     }

    }
    else  //检测不到从微动闭合的信号量,但在规定时间段内检测到主微动再次为闭合状态,中断退出
    {
     Redlight=1;
     if(checkstate(0))
     {
      Yellowlight=0;
      wait=delaytime;
      break;
     }
    }
   }
   if(!wait)   //超时,触发蜂鸣器
   {
    Bell=0;
    belltime=20;
   }
  }else
   Yellowlight=1;
 }
}

主控程序,看门狗T0检测主程序,看门狗T1检测看门狗T0,看门狗T1又作为主辅微动切换时间的定时器,当程序跑飞,触发硬件复位。主辅微动切换最长时间为1秒,超时触发蜂鸣器。

这是我的第一个51项目(很久没写过程序了,顺便活动下大脑,不然要生锈了)。
原理:变化的电场产生变化的磁场,变化的磁场切割线圈产生感应电流。经过全波整流,直接将电平信号送入AT89C2051,利用芯片自带的比较器(P1.0、P1.1)和存放比较结果P3.6。通过轮询P3.6的状态,启动或停止计时中断,每次计时清空计时器,计时时间小于0.1S不显示结果(屏蔽了部分由于干扰而产生的误动作)
电路设计:
3edd751ex9e58693c974a.jpg
焊接完成的电路板:
3edd751ex9e5868ff9385.jpg
51汇编代码:

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; AT89C2051通断计时程序 ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 定时器T0溢出周期为10MS,P1.0(同相端)、P1.1(反相端)作为电压比较器
; P3.6为比较器的输出结果,P1.2-P1.7和P3.0、P3.1口(A-DP)为字符输出口,采用共阳显示管。
; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 中断入口程序 ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
ORG 0000H ;程序执行开始地址
LJMP MAIN ;跳到标号MAIN执行
ORG 0003H ;外中断0中断程序入口
RETI ;外中断0中断返回
ORG 000BH ;定时器T0中断程序入口
LJMP INTT0 ;跳至INTTO执行
ORG 0013H ;外中断1中断程序入口
RETI ;外中断1中断返回
ORG 001BH ;定时器T1中断程序入口
RETI ;定时器T1中断返回
ORG 0023H ;串行中断程序入口地址
RETI ;串行中断程序返回
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 主 程 序 ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
MAIN:

MOV R0,#70H ;清70H-77H共8个内存单元
MOV R7,#08H ;
CLEARDISP: MOV @R0,#00H ;
INC R0 ;
DJNZ R7,CLEARDISP ;
MOV 70H,#07H;初始化信息显示
MOV 71H,#06H
MOV 72H,#0AH
MOV 73H,#0BH

MOV TMOD,#01H ;设T0为16位定时器
CLR C ;清进位标志

MAIN1:
LCALL DISPLAY ;调用显示子程序
LCALL DS8MS;调用延迟减少误动作
JB P3.6,START ;P3.6口为1时开启定时器

JC STOP;据进位标志位判断是否关停T0
SJMP MAIN1
STOP:
CLR C;清除标志位
CLR TR0 ;关闭T0定时器
CLR ET0 ;关闭T0中断
CLR EA ;关闭总中断
SJMP MAIN1

START:
JC MAIN1;据标志位情况判断是否需清零重新开启T0
SETB C
MOV R0,#75H
ACALL CLR0 ;计时单元清0
MOV R0,#77H
ACALL CLR0 ;计时单元清0
MOV TL0,#0f0H ;10MS定时初值(T0计时用)
MOV TH0,#0D8H ;10MS定时初值
SETB EA ;总中断开放
SETB ET0 ;允许T0中断
SETB TR0 ;开启T0定时器
SJMP MAIN1


;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 10毫秒计时程序 ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;T0中断服务程序
INTT0: PUSH ACC ;累加器入栈保护
PUSH PSW ;状态字入栈保护
CLR ET0 ;关T0中断允许
CLR TR0 ;关闭定时器T0
MOV A,#0F7H ;中断响应时间同步修正
ADD A,TL0 ;低8位初值修正
MOV TL0,A ;重装初值(低8位修正值)
MOV A,#0d8H ;中断响应时间同步修正
ADDC A,TH0 ;高8位初值修正
MOV TH0,A ;重装初值(高8位修正值)
SETB TR0 ;开启定时器T0
MOV R0,#75H ;指向毫秒计时单元(74H-75H)
ACALL ADD1 ;调用加1程序
CLR C ;清进位标志
CJNE R4,#9Ah,ADDMM ;判断是否等于100毫秒
ADDMM: JC OUTT0 ;小于100毫秒时中断退出
ACALL CLR0 ;大于或等于100毫秒时对毫秒计时单元清0
MOV R0,#77H ;指向秒计时单元(76H-77H)
ACALL ADD1 ;秒计时单元加1秒
CLR C ;清进位标志
CJNE R4,#9Ah,ADDSS ;
ADDSS: JC OUTT0 ;小于100秒时中断退出
ACALL CLR0 ;大于或等于100秒计时单元清0
OUTT0:
MOV R0,#75H
MOV A,@R0
MOV R3,#02H
FOR:INC R0
ORL A,@R0
DJNZ R3,FOR ;小于0.1秒不做输出显示
JZ DIS
MOV 70H,74H
MOV 71H,75H
MOV 72H,76H
MOV 73H,77H
DIS:
POP PSW ;恢复状态字(出栈)
POP ACC ;恢复累加器
SETB ET0 ;开放T0中断
RETI ;中断返回

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 加1子程序 ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
ADD1: MOV A,@R0 ;取当前计时单元数据到A
DEC R0 ;指向前一地址
SWAP A ;A中数据高四位与低四位交换
ORL A,@R0 ;前一地址中数据放入A中低四位
ADD A,#01H ;A加1操作
MOV R4,A;保留用于判断是否进位
DA A ;十进制调整
MOV R3,A ;移入R3寄存器
ANL A,#0FH ;高四位变0
MOV @R0,A ;放回前一地址单元
MOV A,R3 ;取回R3中暂存数据
INC R0 ;指向当前地址单元
SWAP A ;A中数据高四位与低四位交换
ANL A,#0FH ;高四位变0
MOV @R0,A ;数据放入当削地址单元中
RET ;子程序返回
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 清零程序 ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;对计时单元复零用
CLR0: CLR A ;清累加器
MOV @R0,A ;清当前地址单元
DEC R0 ;指向前一地址
MOV @R0,A ;前一地址单元清0
RET ;子程序返回

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 显示程序(70个机器周期+4*20*25*2=4070us) ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 显示数据在70H-73H单元内,用4位LED共阳数码管显示,P1.2-P1.7和P3.0、P3.1口(A-DP)输出段码数据,P3.3-P3.5、P3.7口(10ms|100ms|1S|10S)作
; 扫描控制,每个LED数码管亮1MS时间再逐位循环。
DISPLAY: MOV R1,#70H ;指向显示数据首址

;P3.3扫描显示
MOV A,@R1 ;取显示数据到A
MOV DPTR,#TABP3;取段码表地址
MOVC A,@A+DPTR ;查显示数据对应段码
ANL A,#0F7H;显示端口
MOV P3,A;段码和端口放入P3口
MOV A,@R1 ;取显示数据到A
MOV DPTR,#TABP1 ;取段码表地址
MOVC A,@A+DPTR
MOV P1,A ;段码放入P1口
LCALL DL1MS ;显示1MS
INC R1 ;指向下一地址
;P3.4扫描显示
MOV A,@R1
MOV DPTR,#TABP3
MOVC A,@A+DPTR
ANL A,#0EFH
MOV P3,A
MOV A,@R1
MOV DPTR,#TABP1
MOVC A,@A+DPTR
MOV P1,A
LCALL DL1MS
INC R1
;P3.5扫描显示
MOV A,@R1
MOV DPTR,#TABP3
MOVC A,@A+DPTR
ANL A,#0DEH
MOV P3,A
MOV A,@R1
MOV DPTR,#TABP1
MOVC A,@A+DPTR
MOV P1,A
LCALL DL1MS
INC R1
;P3.7扫描显示
MOV A,@R1
MOV DPTR,#TABP3
MOVC A,@A+DPTR
ANL A,#07FH
MOV P3,A
MOV A,@R1
MOV DPTR,#TABP1
MOVC A,@A+DPTR
MOV P1,A
LCALL DL1MS

MOV P3,#0FFH  ;一次显示结束,P3口复位
MOV P1,#0FFH ;P1口复位
RET ;子程序返回
TABP3:  DB 0FFH,0FFH,0FDH,0FDH,0FDH,0FDH,0FDH,0FFH,0FDH,0FDH,0FDH,0FDH
TABP1:  DB 003H,0E7H,093H,0C3H,067H,04BH,00BH,0E3H,003H,043H,047H,00FH
;共阳段码表 "0"  "1"  "2" "3"  "4"  "5" "6"  "7"  "8" "9"  "y"  "b"
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 延时程序 ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
;1MS延时程序,LED显示程序用
DL1MS: MOV R6,#14H
DL1: MOV R7,#19H
DL2: DJNZ R7,DL2
DJNZ R6,DL1
RET
;8MS延时程序
DS8MS: ACALL DISPLAY
ACALL DISPLAY
RET

;
END ;程序结束

将以上代码编译链接成.HEX文件,烧入AT89C2051,上机
通电初始化信息:“BY 67”
3edd751ex9e586829ee35.jpg
测试通电时间(显示范围0.1S-99.99S):
3edd751ex9e5867544eb5.jpg
感觉很HAPPY,一直在前进,学无止尽。。。。