原理图和代码见评论区网盘
第一种方法通过时间求频率
定时器测频有很多种方法,其中一种就是对输入的脉冲信号的高电平或低电平时间进行测量得到一个半周期的时长后经过运算就能得到待测频率的值。
所以有频率的计算公式为
这种测量方法有其局限性,要求被测量的脉冲占空比为50%,否则测量得到的半周期数据不准确。
第二种方法在固定时间内记录电平翻转的次数
另外一种测频方法就是规定定时器计时一段固定的时间,在固定的时间内记录高低电平跳变的次数。
测得频率计算公式
这种方法规避了测量的脉冲占空比不为50%时的会出现的问题,但是在最后一个脉冲的计数上可能会出现误差,所以应该用于较高频率的测量,同时定时器计时的时间也应该尽量长一些。
单片机测频功能的仿真实现
利用Proteus建立仿真原理图,利用T1计时器进行计时,在P15端口产生固定频率的波,利用T0定时器对产生波的半周期时间进行测量。其中通过按原理图中的按键可以使单片机产生的波频率发生变化(会产生两种波,一种频率大于500Hz,一种频率小于500Hz)。单片机的P14端口作为对频率的检测端口,检测到频率大于500Hz时发光二极管就会被点亮。
在原理图中引入了LCD1602,检测到的波形的周期数据与频率数据都将会显示在上面
原理图 |
|
第一种测频计的实现
本次实验的程序编写中,尝试在keil中将代码放在两个.C文件中。在LCD1602.C中定义了液晶显示相关的函数,在main.C中对这些函数进行调用。
程序框图
main.c
#include<reg52.h>
#include<intrins.h>
#define uchar unsigned char
#define uint unsigned int
#define out P0
sbit RS=P1^0;
sbit RW=P1^1;
sbit E=P2^5;
sbit send=P3^5;
sbit recieve=P3^4;
sbit button=P3^2;
sbit led=P1^7;
void delay(uint j);
void check_busy(void);
void write_command(uchar com);
void write_data(uchar dat);
void initial(void);
void string(uchar ad,uchar *s);
uchar code num[10]={0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39};//数字0-9对应的CGROM位置
uint numf[4]={0,0,0,0};
uint numfp[4]={0,0,0,0};
uchar i,t;
uint f,fp;
bit flag;
void main() //主函数
{
initial(); //初始化
delay(100);
TMOD=0x11;//设置两个定时器为16位计时器
//定时器0用于计时,定时器1用于发生波形
TL0=0;
TH0=0;
EA=1; //开中断
ET1=1;
TL1=0x0c;
TH1=0Xfe;
send=1;
recieve=1;
flag=1;
EX0=1;
TR1=1;
led=1;
while(1)
{
while(recieve==1);
while(recieve==0);
TR0=1;
while(recieve==1);
TR0=0;
f=TH0*256+TL0;
TH0=0x00;
TL0=0x00;
if(f>1000) //判断频率是否大于500Hz
{
led=1;
}
if(f<=1000)
{
led=0;
}
f=2*f; //进行数据转换
fp=(1000000)/f;
numf[0]=f/1000;
f=f-1000*numf[0];
numf[1]=f/100;
f=f-100*numf[1];
numf[2]=f/10;
f=f-10*numf[2];
numf[3]=f;
numfp[0]=fp/1000;
fp=fp-1000*numfp[0];
numfp[1]=fp/100;
fp=fp-100*numfp[1];
numfp[2]=fp/10;
fp=fp-10*numfp[2];
numfp[3]=fp;
string(0x80,"T:"); //显示数据
delay(1);
write_command(0x82);
write_data(num[numf[0]]);
write_command(0x83);
write_data(num[numf[1]]);
write_command(0x84);
write_data(num[numf[2]]);
write_command(0x85);
write_data(num[numf[3]]);
string(0x86,"us");
string(0xc0,"f:");
delay(1);
write_command(0xc2);
write_data(num[numfp[0]]);
write_command(0xc3);
write_data(num[numfp[1]]);
write_command(0xc4);
write_data(num[numfp[2]]);
write_command(0xc5);
write_data(num[numfp[3]]);
string(0xc6,"Hz");
}
}
void time1() interrupt 3 //重置定时器的预置数
{
if(flag==0)
{
TL1=0x0c;
TH1=0Xfe;
}
if(flag==1)
{
TL1=0x42;
TH1=0Xfa;
}
send=~send;
}
void zhongguan() interrupt 0
{
flag=~flag;
delay(500);
}
1602.c
#include<reg52.h>
#include<intrins.h>
#define uchar unsigned char
#define uint unsigned int
#define out P0
sbit RS=P1^0;
sbit RW=P1^1;
sbit E=P2^5;
void delay(uint j) //延时函数
{
uchar i=250;
for (;j>0;j--)
{
while(--i);
i=249;
while(--i);
i=250;
}
}
void check_busy(void) //读忙函数
{
uchar dt;
do
{
dt=0xff;
E=0;
RS=0;
RW=1;
E=1;
dt=out;
}while(dt&0x80);
E=0;
}
void write_command(uchar com) //写命令函数
{
check_busy();
E=0;
RS=0;
RW=0;
out=com;
E=1;
delay(1);
E=0;
delay(1);
}
void write_data(uchar dat) //写数据函数
{
check_busy();
E=0;
RS=1;
RW=0;
out=dat;
E=1;
delay(1);
}
void initial(void) //初始化函数
{
write_command(0x3f);
delay(1);
write_command(0x0c);
delay(1);
write_command(0x06);
delay(1);
write_command(0x01);
delay(1);
}
void string(uchar ad,uchar *s) //写字符函数
{
write_command(ad);
while(*s>0)
{
write_data(*s++);
delay(1);
}
}
实验效果
频率小于500Hz 灯灭 |
|
频率大于500Hz 灯亮 |
|
设计的电路很好的实现了预期的功能,设计时产生的脉冲波频率分别为1000Hz与333Hz,由于单片机自身运算速率的原因存在一定误差。
第二种测频计的实现
原理图如上,对代码进行了修改
main.c
#include<reg52.h>
#include<intrins.h>
#define uchar unsigned char
#define uint unsigned int
#define out P0
sbit RS=P1^0;
sbit RW=P1^1;
sbit E=P2^5;
sbit send=P3^5;
sbit recieve=P3^4;
sbit button=P3^2;
sbit led=P1^7;
void delay(uint j);
void check_busy(void);
void write_command(uchar com);
void write_data(uchar dat);
void initial(void);
void string(uchar ad,uchar *s);
uchar code num[10]={0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39};//数字0-9对应的CGROM位置
uint numf[4]={0,0,0,0};
uint numfp[4]={0,0,0,0};
uchar i,t;
uint f,fp;
bit flag;
bit jiou;
void main() //主函数
{
initial(); //初始化
delay(100);
TMOD=0x11;//设置两个定时器为16位计时器
//定时器0用于计时,定时器1用于发生波形
TL0=0xb0;
TH0=0x3c;
TF0=0;
EA=1; //开中断
ET1=1;
TL1=0x48;
TH1=0Xf4;
send=1;
recieve=1;
flag=1;
EX0=1;
TR1=1;
led=1;
jiou=0;
while(1)
{
f=0;
while(recieve==0);
TR0=1;
while(TF0==0)
{
while(recieve==1);
while(recieve==0);
f=f+1; //f为单位周期内计脉冲的个数
}
TR0=0;
TF0=0;
TL0=0xb0;
TH0=0x3c;
if(f<=25) //判断频率是否大于500Hz
{
led=1;
}
if(f>25)
{
led=0;
}
//进行数据转换
fp=20*f;
f=50000/f;
numf[0]=f/1000;
f=f-1000*numf[0];
numf[1]=f/100;
f=f-100*numf[1];
numf[2]=f/10;
f=f-10*numf[2];
numf[3]=f;
numfp[0]=fp/1000;
fp=fp-1000*numfp[0];
numfp[1]=fp/100;
fp=fp-100*numfp[1];
numfp[2]=fp/10;
fp=fp-10*numfp[2];
numfp[3]=fp;
string(0x80,"T:"); //显示数据
delay(1);
write_command(0x82);
write_data(num[numf[0]]);
write_command(0x83);
write_data(num[numf[1]]);
write_command(0x84);
write_data(num[numf[2]]);
write_command(0x85);
write_data(num[numf[3]]);
string(0x86,"us");
string(0xc0,"f:");
delay(1);
write_command(0xc2);
write_data(num[numfp[0]]);
write_command(0xc3);
write_data(num[numfp[1]]);
write_command(0xc4);
write_data(num[numfp[2]]);
write_command(0xc5);
write_data(num[numfp[3]]);
string(0xc6,"Hz");
}
}
void time1() interrupt 3
{
if(jiou==0) //jiou这一位的0 1来调占空比
{
if(flag==0)
{
TL1=0x48;
TH1=0Xf4;
}
if(flag==1)
{
TL1=0x38;
TH1=0Xff;
}
}
if(jiou==1)
{
if(flag==0)
{
TL1=0x18;
TH1=0Xfc;
}
if(flag==1)
{
TL1=0xdc;
TH1=0Xfe;
}
}
send=~send;
jiou=~jiou;
}
void zhongguan() interrupt 0
{
flag=~flag;
delay(500);
}
实验效果
频率小于500Hz 灯灭 |
|
频率大于500Hz 灯亮 |
|
在图中可以看到,这次单片机端口产生的脉冲占空比不再为50%,但是由于改变了测频的方式设计的电路依旧达到了预期的效果。设计时产生的脉冲频率分别为2000Hz与250Hz,由于这种方法在单位时间内最后一个脉冲上可能会出现计数偏差,所以存在一定误差。