功能:按键8个,测频计数,交流电压 ,直流电压,及相应保护判断亮灯指示,用ST7920-12864液晶显示
硬件:测频计数用到 单运放迟滞比较器、555波形陡处理、 CD4013做的D触发器;交流电压用到
LM358单运放半波整流,输出加一级RC滤波.调节运放可以调节电压、AD转换芯片TLC1543(0-5V)。
直流电压就没嘛说的,电阻分压就行了。
按键用74HCT245双向数据芯片,蛮好。
继电器控制,液晶并口数据线,保护灯,按键都是用P0口,用74HC573锁存切换,AD转换口用P1口。计数用T0 INT0 INT1 ,其他的控制脚接P2口,所以没有做单片机口扩展,那样到更麻烦些
补充:灯光显示和继电器的74HC573控制脚接到反向器74HC04,加大驱动能力。
备注:由于硬件电路做的简单,所以,程序繁琐,待优化中。。。。。。。。。。。。。
以下是部分程序预览,完整的源代码下载:http://www.51hei.com/ziliao/file/cepin1.rar
/********关于多周期测频法,精度与晶振和闸门时间有关。晶振越大,闸门时间越长,都可以提高精度。**********/ #include <stc89c52.h> #include <intrins.h> #define uchar unsigned char #define uint unsigned int #define LCD12864_IO P0 #define CLERADISPLAY LCD12864_command(0x01); int a1=0,a2=0,a3=0,a4=0,a5=0,a6=0;//存储电压值的每一位,设计a4,a5是小数位. const uchar num[]="0123456789. "; //AD转换控制脚 sbit CLK = P1^0; //TLC1543 18P sbit ADDRESS = P1^1; //17P sbit SDATA = P1^2; //16P sbit CS = P1^3; //15P /********************************************************************/ sbit LCD12864_RS=P2^5; // 12864-st7920 4P RS sbit LCD12864_RW=P2^6; //RW(5P) sbit LCD12864_EN=P2^7; //E(6P) /********************************************************************/ void LCD12864_busy(void); void LCD12864_command(unsigned char command); void LCD12864_data(unsigned char dat); void LCD12864_address(unsigned char row,unsigned char line); void LCD12864_string(unsigned char row,unsigned char line,unsigned char *s); void LCD12864_picture(unsigned char *gImage); void LCD12864_init(void); void LCD12864_char (unsigned char row,unsigned char line,unsigned char a); unsigned char LCD12864_ReadData(); void LCD12864_Drawpoint(uchar X,uchar Y); void LCD12864_LineX(unsigned char X0, unsigned char X1, unsigned char Y); void LCD12864_LineY( unsigned char X, unsigned char Y0, unsigned char Y1); void LCD12864_DrawPicture( unsigned char code *pic); void clrgdram(); /********************************************************************/ // double DAT[7]={115.0, 115.1, 115.2, 285.0,1500.0, 400.0,3100.0}; // U V W E N F CINT // 控制脚 sbit CONTRL=P1^7; //测频控制脚 sbit SCANF =P2^0; //键盘扫描控制脚 sbit LED1 =P2^1; //状态显示控制脚1 sbit LED2 =P2^2; //状态显示控制脚2 sbit SHUCHU=P2^3; //继电器输出控制脚 // bit FLAG; //测频标志位 bit xunhuanflag;//显示方式标志位 bit outflag; //允许输出标志 bit eding;//额定状态标志 // 按键存储 bdata uchar key; //键值存储 sbit key0=key^0;//停机 sbit key1=key^1;//启动按钮 sbit key2=key^2; //显示方式(高循环) sbit key3=key^3; //自检 sbit key4=key^4;//灯检 sbit key5=key^5;//复位 sbit key6=key^6;//应急 (高应急) sbit key7=key^7;//油压(高有油压) uint t1h,t1l,t2h,t2l;//测频变量 double cnt1,cnt2; double AD; //定义为float 类型,可以防止下面做四则运算时每一步的值超出 范围 unsigned long int X; /****************************************************************/ uint rd1543(uchar address);//AD转换程序 void voltage(); //电压检测 void init(); //初始化 void zhuansu(); //计算转速 void reset();//测频计数定时复位 uchar scanf(); //键盘扫描 void keychuli(); //按键处理程序 void baohu(); void display(unsigned long int sx); //显示函数 void displayFX(unsigned long int sx); /*************************************************************/ void delayus(uint); void delayms(uint); void delays(uint m); //延时秒 unsigned char code Bmp019[]= { /*------------------------------------------------------------------------------ ; 若数据乱码,请检查字模格式设置,注意选择正确的取模方向和字节位顺序。 ; 源文件 / 文字 : C:\Documents and Settings\Administrator\桌面\888.bmp字模 ; 宽×高(像素): 128×64 ; 字模格式/大小 : 单色点阵液晶字模,横向取模,字节正序/1024字节 ; 数据转换日期 : 2010-7-26 20:46:48 ------------------------------------------------------------------------------*/ /**************************** 0x80 0x81 0x82 0x83 0x84 0x85 0x86 0x87 上半屏行坐标,表示的是多少列 0x88 0x89 0x8a 0x8b 0x8c 0x8d 0x8e 0x8f 下半屏行坐标,每组8列,每列16位,共128位, 0x80 0x81 0x82 0x83 ........ 0x9f //列坐标,共32个,表示的是行数 ,分两个半屏,每个32行,共64行 //Y坐标 只是用来确定具体坐标,在哪一行 功能:图形模式下,显示(X,Y)点 输入:X(0~127) Y(0~63) 相对屏幕坐标 输出:无 点亮某一点的操作步骤: 1.求出水平坐标X对应的地址和是哪一位 0x80-----0x8f (范围0-15) X/16求地址 X%16求该地址哪一位 2.求垂直坐标Y对应的地址和上下半屏 0x80------0x9f(范围0-63) Y本身就是8位地址,Y=63-Y ,Y--(0-31) 3.写入行列地址(Y是行X是列),0x80+Y ,0X80+X/16 4.读要显示的数据 DAT 5.区分上下半屏(X%16<=7&&X%16>=0是上半屏)写入数据每一位 DAT|0x80 ;DAT<<1 注意:这个函数显示某一点时,可能会把上次显示的处于同一地址的其他位的点擦掉,所以先保存所有数据,最后显示,就连贯起来了 *******************************/ /*******************************************************************/ /**************************************************************/ //------------------清整个GDRAM空间---------------------------- /**************************************************************/ void clrgdram() { unsigned char x,y ; for(y=0;y<64;y++) for(x=0;x<16;x++) { LCD12864_command(0x34); LCD12864_command(y+0x80); LCD12864_command(x+0x80); LCD12864_command(0x30); LCD12864_data(0x00); LCD12864_data(0x00); } } /******************************************/ /*******8========================================================================== 功能:图形模式下,显示(X,Y)点 输入:X(0~127) Y(0~63) 输出:无 0x80 0x81 0x82 0x83 0x84 0x85 0x86 0x87 上半屏行坐标,表示的是多少列,X地址 0x88 0x89 0x8a 0x8b 0x8c 0x8d 0x8e 0x8f 下半屏行坐标,X地址 (水平地址 ) 0x80 (垂直地址) 0x81 0x82 0x83 ........ 0x9f //列坐标,共32个,表示的是行数 ,分两个半屏,每个32行,共64行 ====================================================================**********/ void LCD12864_Drawpoint(uchar X,uchar Y) { uchar i= 0, j = 0,ok=0; uchar temp1 = 0x00,temp2 = 0x00; LCD12864_command(0x34); //8位,扩充指令,绘图关 LCD12864_command(0x36); //8位,扩充指令,绘图开 i = X/16; //计算出X字节地址(0X80-0X8F) j = X%16; //计算出该字节的具体位(0-15) //Y = 63 - Y; if(Y>=0 && Y<=31)//判断上下半屏 { ok=1; } else if(Y>=32 && Y<=63)//下半屏 { Y = Y - 32;//Y只有0-31共32个 地址 i = i + 8;//X地址进入下半屏 (0X88-0X8F) ok=1; } if(ok) { //读数据操作 LCD12864_command(0x80+Y); //第一步:设置Y坐标,读数据先写地址,写GDRAM时先写垂直地址(0X80-0X9F) LCD12864_command(0x80+i); //第二步:设置X坐标 LCD12864_ReadData(); //第三步:空读取一次 temp1 =LCD12864_ReadData(); //第四步:读取高字节,先读高字节 temp2 =LCD12864_ReadData(); //第五步:读取低字节 //图形模式下的写数据操作 LCD12864_command(0x80+Y); //第一步:设置Y坐标 LCD12864_command(0x80+i); //第二步:设置X坐标 if(j>=0 && j<=7) //判断是高字节 { LCD12864_data(temp1|(0x80>>j)); //第三步:写高字节数据 LCD12864_data(temp2); //第四步:写低字节数据 } else if(j>7 && j<=15) //判断是低字节 { j = j - 8; LCD12864_data(temp1); LCD12864_data(temp2|(0x80>>j)); //改变字节里的位 } } } /******************************************/ //画线 /********************************************/ //画水平线 void LCD12864_LineX(unsigned char X0, unsigned char X1, unsigned char Y) { unsigned char Temp ; if( X0 > X1 ) { Temp = X1 ; //交换X0 X1值 X1 = X0 ; //大数存入X1 X0 = Temp; //小数存入X0 } for( ; X0 <= X1 ; X0++ ) LCD12864_Drawpoint(X0,Y); } //画垂直线 void LCD12864_LineY( unsigned char X, unsigned char Y0, unsigned char Y1) { unsigned char Temp ; if( Y0 > Y1 )//交换大小值 { Temp = Y1 ; Y1 = Y0 ; Y0 = Temp ; } for(; Y0 <= Y1 ; Y0++) LCD12864_Drawpoint( X, Y0) ; } /******************************************/ /**************画图************************/ void LCD12864_DrawPicture( unsigned char code *pic) { unsigned char i, j, k ; LCD12864_command(0x34);//开扩充指令 LCD12864_command(0x36);//开绘图功能 for( i = 0 ; i < 2 ; i++ )//分上下两屏写 { for( j = 0 ; j < 32 ; j++)//垂直地址递加 ,行扫方式 { LCD12864_command( 0x80 + j ) ;//写Y坐标(Y的范围: 0X80-0X9F ) if( i == 0 ) //写X坐标 { LCD12864_command(0x80);//上半屏 } else { LCD12864_command(0x88);//下半屏开始地址 } for( k = 0 ; k < 16 ; k++ ) //写一整行数据 { LCD12864_data( *pic++ );//前面只写入首地址,后面依次写入数据,地址会自动递增 } } } LCD12864_command( 0x30);//恢复到一般模式 } // // 初始化设置 /*******************************************************************/ void LCD12864_init(void) { CLERADISPLAY // clear DDRAM LCD12864_command(0x30); // 8 bits unsigned interface,basic instrument LCD12864_command(0x02); // cursor return LCD12864_command(0x0c); // display,cursor on LCD12864_command(0x03); LCD12864_command(0x06); CLERADISPLAY // clear DDRAM } // //显示函数 void display(unsigned long int sx) { a1=sx/100000; a2=sx%100000/10000; a3=sx%10000/1000;//千位 a4=sx%1000/100;//百位 a5=sx%100/10;//十位 a6=sx%10;//个位 LCD12864_command(0x90); //指定显示位置 LCD12864_data(num[a1]); //从最高位开始显示 LCD12864_data(num[a2]); LCD12864_data(num[a3]); LCD12864_data(num[a4]); LCD12864_data(num[a5]); LCD12864_data(num[10]);//小数点 LCD12864_data(num[a6]); } void displayFX(unsigned long int sx) { a1=sx/100000; a2=sx%100000/10000; a3=sx%10000/1000; a4=sx%1000/100; a5=sx%100/10; a6=sx%10; LCD12864_command(0x98); //指定显示位置 LCD12864_data(num[a1]); //从最高位开始显示 LCD12864_data(num[a2]); LCD12864_data(num[a3]); LCD12864_data(num[a4]); LCD12864_data(num[a5]); LCD12864_data(num[a6]); } /********************************* 主函数入口 /********************************************/ //主函数入口 // main() { uchar ii=0; CONTRL=0; Mcu_init(); CLERADISPLAY delays(1); LCD12864_char(1,2,'A'); LCD12864_string(1,4,"shaozhanyu"); delays(2); clrgdram(); CLERADISPLAY delays(1); clrgdram(); delays(1); LCD12864_LineX(8,113,25); clrgdram(); CLERADISPLAY delays(2); clrgdram(); LCD12864_LineY(55,29,57); delays(2); clrgdram(); delays(1); LCD12864_DrawPicture(Bmp019); delays(3); Mcu_init(); CLERADISPLAY LCD12864_string(3,1,"N F U V W E"); LCD12864_char(2,1,'D'); LCD12864_string(4,1,"计数"); LCD12864_string(1,3,"少占鱼做"); display(123456); /***************8888 P0=0x10;//怠速,准备打开K5继电器,关闭其他输出继电器 SHUCHU=0;//打开输出闸门 SHUCHU=1;//关闭闸门 outflag=0;//输出标志位 ******************/ AD=0.0; keychuli(); //扫描并处理按键 delayms(5200); ///初始化 init(); while(1) { while(!FLAG); //等待频率测出 keychuli(); //扫描并处理按键 LCD12864_string(1,3,"开始转换"); delayms(300); voltage(); //采样电压 LCD12864_string(1,3,"转换完成"); delayms(400); baohu();//判断采样数据保护 for(;ii<6;ii++) { display(DAT[ii]); LCD12864_string(1,3,"显示下组"); delayms(1200); // keychuli(); if(xunhuanflag==0) //定点 {display(DAT[ii]); break; } } if(ii==6) ii=0; display(DAT[ii]); displayFX(DAT[6]); LCD12864_string(1,3,"下轮复位"); delayms(1000); init(); } } /*************************************/ //初始化函数 void init() { /******T1定时器模式,外部INT1控制开启,T0计数器不允许中断,外部控制 INTO开启,外部中断0允许(EX0=1), 定时器T2中断允许 (ET2=1) ************/ CONTRL=0; FLAG=0; EX0=0; ET2=0; //三个定时器方式设置 TMOD=0x9d; //T0T1方式控制 T2MOD=0x00; T2CON=0x00;//定时器2,16位定时方式,自动重装。 TH0= 0x00; // T0高8位 TL0= 0x00; // T0低8位 TH1= 0x00; // T1高8位 TL1= 0x00; // T1低8位 EXEN2=0; TH2=256/256; TL2=256%256; RCAP2H=256/256; RCAP2L=256%256; //中断设置 5 EX0=1;//允许外部0输入中断(INT0引脚) ET2=1; //开定时中断2 IT0=1; //外部中断0边沿触发,下降沿到来触发 //优先级设置 PX0=1; //预置T0,T1 TR1=1;//先允许T1定时,因T1的GATE=1,还要等外部INT1高电平才计数 TR0=1;//先允许T0计数 ,同T1一样,等待INTO高电平 TR2=1;//启动T2定时,不用外部控制,直接启动 EA=1; //开全局中断 CONTRL=1; //初始化完成...... } /********************************** void reset() { CONTRL=0; FLAG=0; TL0=0x00; TH0=0x00; TL1=0x00; TH1=0x00; TF2=0; TH2=256/256; TL2=256%256; TR1=1;//先允许T1定时,因T1的GATE=1,还要等外部INT1高电平才计数 TR0=1;//先允许T0计数 ,同T1一样,等待INTO高电平 TR2=1;//启动T2定时,不用外部控制,直接启动 EA=1; //开全局中断 CONTRL=1; } *******************************/ //键盘扫描 uchar scanf() { uchar value; P0=0xff; delayms(1); SCANF=0;//打开键扫闸门 value=P0; delayms(2); value=P0; SCANF=1; return value; } /********************************/ //键处理 void keychuli() { uchar k; //关于指示灯,交流和频率是一组,直流和其他项是一组 /***************************************/ /******************/ key=scanf(); /******************/ //启动键判断 if(key1==0&&key7==0&&DAT[4]==0)//启动位 ,油压和转速为0 { for(k=0;k<3;k++)//三次启动循环 { P0=0x04;//准备 输出K6继电器 SHUCHU=0;//打开闸门 delays(3);//隔三秒响应一次停机键 //响应停机键 key=scanf(); if(key0==0&&key7==1&&DAT[4]>500)//停机开关状态,油压高,转速达到500,其他继电器都停止工作 ,所以 ,P0=0x08 { P0=0x03;//准备 输出K0继电器 SHUCHU=0;//打开输出闸门 SHUCHU=1;//关闭闸门 outflag=0;//输出标志位 } if(key1==0&&key7==1&&DAT[4]>430) //每启动一次都判断是否成功,成功直接跳出 break ; //判断启动成功,立即跳出启动for 循环 delays(3);//没有启动成功,继续启动3秒 key=scanf(); if(key1==0&&key0==0&&key7==1&&DAT[4]>500)//停机,油压高,转速达到500,其他继电器都停止工作 ,所以 ,P0=0x08 { P0=0x03;//准备 输出K0继电器 SHUCHU=0;//打开输出闸门 SHUCHU=1;//关闭闸门 outflag=0;//输出标志位 } if(key1==0&&key7==1&&DAT[4]>430) //每启动一次都判断是否成功,成功直接跳出 break ; delays(3); key=scanf(); if(key0==0&&key7==1&&DAT[4]>500)//停机,油压高,转速达到500,其他继电器都停止工作 ,所以 ,P0=0x08 { P0=0x03;//准备 输出K0继电器 SHUCHU=0;//打开输出闸门 SHUCHU=1;//关闭闸门 outflag=0;//输出标志位 } if(key1==0&&key7==1&&DAT[4]>430) //每启动一次都判断是否成功,成功直接跳出 break ; P0=0x00;//停止启动 SHUCHU=0; SHUCHU=1; delays(9); }//for启动循环结束 SHUCHU=1;//关闭闸门 /**********************/ //启动失败判断 if(key7==0) //油压低 if(DAT[4]<440) { P0=0x01;//点亮启动失败灯 LED1=0;//开启573输入 LED1=1;//关闭使能,74HC573锁定状态 outflag=0;//输出标志位清0,表示输出未允许 } } //停机键判断 /********************/ if(key0==0&&key7==1&&DAT[4]>500)//停机,油压高,转速达到500,其他继电器都停止工作 ,所以 ,P0=0x08 { P0=0x03;//准备 输出K0继电器 SHUCHU=0;//打开输出闸门 SHUCHU=1;//关闭闸门 outflag=0;//输出标志位 } /*****************/ //应急键判断 if(key6==1)//应急/正常,高电位应急 { eding=1;//应急标志 } else if(key6==0) //进入怠速 { eding=0;//怠速标志 } if(eding==0)//对怠速的处理 { P0=0x08;//怠速,准备打开K5继电器,关闭其他输出继电器 SHUCHU=0;//打开输出闸门 SHUCHU=1;//关闭闸门 outflag=0; } if(eding==1&&outflag==0)//对额定的处理 { P0=0x00;//额定,准备关闭K5继电器,进入额定,其他继电器都停止工作 ,所以 ,P0=0x00 SHUCHU=0;//打开输出闸门 SHUCHU=1;//关闭闸门 delayms(2); if(DAT[0]>110&&DAT[1]>110&&DAT[2]>110&&DAT[5]>390)//三相交流大于110 ,频率大于390 { P0=0x30;//准备合闸(交流,直流 内控制 ) SHUCHU=0; SHUCHU=1; outflag=1;//合闸后,输出标志位置1 } } /******************************/ //循环键 if(key2==1) //处理循环定点显示标志位 xunhuanflag=1; else if(key2==0) xunhuanflag=0; /**********保护函数*******************/ void baohu() { if(outflag==1) { //立即保护值 if((DAT[0]<80||DAT[1]<80||DAT[2]<80) || (DAT[0]>180||DAT[1]>180||DAT[2]>180) || DAT[5]>445||DAT[5]<320)//立即断闸保护 { { P0=0x00;//准备断闸(交流,直流 内控制 ) SHUCHU=0; SHUCHU=1; outflag=1;//合闸后,输出标志位清0 } if((DAT[0]>180||DAT[1]>180||DAT[2]>180)&&DAT[5]>445) {P0=0x05; /*******************************/ //转速计算 ,频率计算 void zhuansu() { t1h=TH0; t1l=TL0; t2h=TH1; t2l=TL1; cnt1=t1l+(t1h<<8); cnt2=t2l+(t2h<<8); DAT[6]=cnt1/cnt2*1000000.200;//计数值 DAT[4]=cnt1/cnt2*1000000.200/124.00*600.00; //计算转速,信号频率就是单片机计数频率的整数倍 ,这里这样写是怕cnt1 cnt2 超出范围 //注意这里: cnt1 cnt2 的类型不能是 uint 否则第一步计算除法会得0 , 如果你要先乘1000000.0,也不行。因为超出了uint 范围 DAT[5]=DAT[4]*16.00/60.000;//电压频率计算 } /******************************/ //外部中断0,调用转速计算 void interint0() interrupt 0 //using ** //外部中断0处理 { EA=0; zhuansu();//调用转速函数 FLAG=1; /***注意:因为TO T1是外部引脚控制的,所以,这时外部低电平,自动停止。不用软件停止**/ } void intertimer2() interrupt 5 //using ** //T2定时中断处理 { TR2=0; CONTRL=0;//关闭闸门信号 } /*******************************/ //AD转换程序 /************************************************/ //常测数据函数 void voltage() //电压测量 { uchar i; AD=0.00; X=0; for(i=0;i<20;i++) { AD+=rd1543(0x08); //读取AD值 } // AD=AD/20; X=AD*46.0001*50.00/1023.00;//转换成电压值 ,分辨率是10位 DAT[0]=X; AD=0.00; delayus(2); // for(i=0;i<20;i++) { AD+=rd1543(0x08); //读取AD值 } // AD=AD/20; X=AD*46.0001*50.00/1023.00;//转换成电压值 ,分辨率是10位 DAT[1]=X; // AD=0.00; delayus(2); // for(i=0;i<20;i++) { AD+=rd1543(0x08); //读取AD值 } // AD=AD/20; X=AD*46.0001*50.00/1023.00;//转换成电压值 ,分辨率是10位 DAT[2]=X; // AD=0.00; delayus(2); // /********* for(i=0;i<20;i++) { AD+=rd1543(0x07); //读取AD值 } // AD=AD/20; X=AD*50.000/1023.000*28.500/4.000; DAT[3]=X; // **********/ } /***********************************************/ //TLC1543输入模拟电压范围: 0-4.9152 V //TLC1543AD 10个时钟方式 方式1 。 LC1543 有四位精度 。 输入 0.0024v 时,AD值为0000000001 //0.0048时,还为1。 0.0072为 2 也就是0.0048 为一个 宽度 0.0048*AD 就是电压值 . uint rd1543(uchar addr) { uint date_out=0; uchar k; // uchar j; CLK=0; CS=0; ADDRESS=(bit)(addr&0x08); //用这种愚蠢的方法比用FOR循环快的多 。 CLK=1; CLK=0; addr=addr*2; //用乘法比用左移快 ADDRESS=(bit)(addr&0x08); CLK=1; CLK=0; addr=addr*2; //用乘法比用左移快 ADDRESS=(bit)(addr&0x08); CLK=1; CLK=0; addr=addr*2; //用乘法比用左移快 ADDRESS=(bit)(addr&0x08); CLK=1; CLK=0; addr=addr*2; //用乘法比用左移快 // for (j=0;j<6;j++) //填充6 个CLOCK // { CLK=1;CLK=0; //这里不用循环,省时间 CLK=1;CLK=0; CLK=1;CLK=0; CLK=1;CLK=0; CLK=1;CLK=0; CLK=1;CLK=0; CLK=0; // } CS=1; delayus(8); //等待AD 转换 CS=0; for(k=0;k<10;k++) { SDATA=1; //非P0口作为数据总线使用时,读入数据前要赋值1,特别 CLK = 1; //是既用于写有用于读的情况下. date_out<<=1; if(SDATA) date_out += 1; //这样写法比下面的方法速度快(5us) // date_out=date_out|SDATA;//用时6US CLK = 0; } return(date_out); }