概述
该设计用51单片机做控制器,显示采用LCD1602液晶屏。可以测量频率、脉宽、占空比、周期。外部晶振采用12M,所以单片机的机器周期为1us。51单片机采样周期是2个机器周期,所以理论上测量的频率范围是1-500KHz。实际应用时,发现被测频率值越大那么测得频率的误差也越大。
误差原因分析:
1.定时器工作于方式1时在中断服务程序中需要重新给定时器赋装载值,赋装载值得过程需要占用一定的时间,所以达不到精准的1S定时。
2.根据香农定理:采样频率需要大于被测连续信号频率中含有的最大频率值的2倍。当被测频率值大于250KHz时,则不满足香农定理了。实际测试中当被测频率大于250KHz时,误差很大甚至达到了几百Hz。
1.测量频率的原理
频率的定义是在1s内完成周期性变化的次数。故可在外部信号来临时,开启定时/计数器0定时1s,同时也开启定时/计数器1计数,这样在1s内所计的次数,即是所测得的信号频率。因为是12MHz的晶振,能定时最长时间是,小于1s,故可以利用定时器计数器0定时,同时利用定时/计数器1计数,使定时器的溢出4000次后,即得1s后的频率值。如下公式所示。
2. 测量周期的原理
因被测信号的频率越高,测量的误差越大。为避免在高频时测量的误差过大,故在低频(<100Hz)的情况下,利用定时计数器在外部信号第一次高电平来临时从0开始计时,在相邻的下一个高电平来临时定时/计数器关闭,则定时的值即为所测的周期。但倘若在高频(>100Hz)的情况下,则利用上述的测量方法测量10组周期值求得其平均值来作为所测的周期值,这样做的目的是减少在高频时因频率太快周期太短,定时计数器速率跟不上而带来的偶然误差。
利用STC89C52RC单片机内部定时器1的定时功能,测量信号周期的大小。信号从P3.5口引入,检测P3.5口信号的第一个上升沿到来,定时器1以工作方式1开始定时,定时器1定时溢出,标志位count加一,直至第二个上升沿到来,定时器1停止工作。则该信号的周期为如下公式所示。
3.测量脉宽的原理
正、负脉宽的定义是在高电平、低电平所持续的时间。则可利用测量周期的方法类似的测量出脉宽值。在检测到外部输入信号的上升沿时则打开计数器,为下降沿时则关闭计数器,此时计时器所测得的值即为正脉宽值。在检测到外部输入信号的下降沿时开启计数器,为上升沿时关闭计数器,则此时记录的数值为负脉宽值。
信号从P3.5口引入,检测P3.5口信号的第一个上升沿到来,定时器1以工作方式1开始定时,定时器1定时溢出,标志位count加一,直至第一个下降沿,定时器1停止工作。则该信号的脉宽如下公式所示。
4. 测量占空比原理
占空比的定义是在一个周期内高电平所持续的时间即为占空比,故可利用上述测量周期和脉宽的方法来实现占空比的测量。设测得周期为、正脉宽为,则占空比为正脉宽除以周期的值。
信号从P3.5口引入,检测P3.5口信号的第一个上升沿到来,定时器1以工作方式1开始定时,定时器1定时溢出,标志位count和m加一,直至第一个下降沿,得出脉宽的大小,如公式(4)所示。定时器1继续以工作方式1工作,标志位count加一,直至第二个上升沿,定时器1停止工作。得出周期的大小,如公式(2)所示。则该信号的占空比如下公式所示。
5. 实物图
6.LCD1602电路原理图
7.单片机程序
/****************************************************
程 序 名:数字频率计
编 写 者:ZuoYouPaPa
时 间:2019.12
功 能:(1)上电显示提示符“P.”。
(2)定义4个功能键:
按下第一个键开始测量频率;
按下第二个键开始测量周期;
按下第三个键开始测量脉宽;
按下第四个键开始测量占空比;
*********/
#include “reg52.h”
#define LCD1602_DATAPINS P0
sbit EN=P2^7;
sbit RW=P2^5;
sbit RS=P2^6;
sbit InputPin=P3^5;
#define RS_CLR RS=0
#define RS_SET RS=1
#define RW_CLR RW=0
#define RW_SET RW=1
#define EN_CLR EN=0
#define EN_SET EN=1
#define Time0HIGHT 0x3C //50ms定时
#define Time0LOW 0xAF
#define Time1HIGHT 0x00 //从0开始计数
#define Time1LOW 0x00
typedef unsigned int uint; //对数据类型进行声明定义
typedef unsigned char uchar;
typedef unsigned long ulong;
uchar CountValue=0,KeyValue=0;
ulong Frequency,Cycle,PulseWidth,DutyCycle,Low;
uint TimeValue=0;
void nop(void); //空指令
/
- 函 数 名 : delay_ms
- 函数功能 : ms延时,
- 输 入 : nms:要延时的ms数 0~65535
- 输 出 : 无
*************************************************/
void Delay1ms(uint c) //延时函数
{
uchar a,b;
for (; c>0; c–)
{
for (b=199;b>0;b–)
{
for(a=1;a>0;a–);
}
}
}
/*************************************************
-
函 数 名 : LcdWriteCom
-
函数功能 : 写入命令
-
输 入 : com:需要写入的指令码
-
输 出 : 无
*************************************************/
void LcdWriteCom(uchar Com)
{
EN_CLR;//使能
RS_CLR; //选择发送命令
RW_CLR; //选择写入LCD1602_DATAPINS = Com; //放入命令
Delay1ms(1); //等待数据稳定EN_SET; //写入时序
Delay1ms(5); //保持时间
EN_CLR;
}
/************************************************* -
函 数 名 : LcdWriteData
-
函数功能 : 写入数据
-
输 入 : dat:需要写入的数据
-
输 出 : 无
*************************************************/
void LcdWriteData(uchar Data)
{
EN_CLR; //使能清零
RS_SET; //选择输入数据
RW_CLR; //选择写入LCD1602_DATAPINS = Data; //写入数据
Delay1ms(1);EN_SET; //写入时序
Delay1ms(5); //保持时间
EN_CLR;
}
/************************************************* -
函 数 名 : LcdWriteChar
-
函数功能 : 写入单个字符
-
输 入 :
Column,Row,Data:坐标列,坐标行,需要写入的字符 -
输 出 : 无
/
void LcdWriteChar(uchar Column,uchar Row,uchar Data) //显示字符
{
if (Row == 0)
{
LcdWriteCom(0x80 + Column);
}
else
{
LcdWriteCom(0xC0 + Column);
}
LcdWriteData( Data);
}
/ -
函 数 名 : LcdWriteString
-
函数功能 : 写入字符串
-
输 入 :
Column,Row,*s:坐标列,坐标行,需要写入的字符串 -
输 出 : 无
***/
void LcdWriteString(uchar Column,uchar Row,uchar s)
{
while(s)
{
LcdWriteChar(Column,Row,s);
s++;
Column++;
}
}
/ -
函 数 名 : LcdInit
-
函数功能 : LCD上电初始化
-
输 入 : 无
-
输 出 : 无
/
void LcdInit()
{
LcdWriteCom(0x38); //开显示
LcdWriteCom(0x0c); //开显示不显示光标
LcdWriteCom(0x06); //写一个指针加1
LcdWriteCom(0x01); //清屏
LcdWriteCom(0x80); //设置数据指针起点
LcdWriteString(0,0,“P.”); //开机提示
}
/ -
函 数 名 : LcdClear
-
函数功能 : LCD清屏
-
输 入 : 无
-
输 出 : 无
/
void LcdClear()
{
LcdWriteCom(0x01);
Delay1ms(5);
}
/ -
函 数 名 : KeyScan
-
函数功能 : 按键扫描,判断有无按键按下
-
输 入 : 无
-
输 出 : 键值
/
uchar KeyScan()
{
uchar KeyValue;
if((P1&0x0f)!=0x0f)
{
Delay1ms(10); //延时10ms
if((P1&0x0f)!=0x0f)
{
KeyValue=P1;
KeyValue=~KeyValue;
LcdClear();
return KeyValue;
}
}
return 0;
}
/ -
函 数 名 : TimerInit
-
函数功能 : 定时器初始化
-
输 入 : 无
-
输 出 : 无
/
void TimerInit()
{
TMOD=0X51; //定时器工作方式设置
TH1=0x00; //从0开始计数
TL1=0x00; //T1计数
TH0=0x3C; //50ms
TL0=0xAF;
EA=1; //开总中断
ET0=1; //开定时器0中断
ET1=1;
PT1=1;
}
/ -
函 数 名 : Timer0_Interrupt
-
函数功能 : 定时器0中断服务
-
输 入 : 无
-
输 出 : 无
/
void Timer0_Interrupt() interrupt 1 using 2
{
TH0=0x3C;
TL0=0xAF;
TimeValue++;
}
/ -
函 数 名 : Count1_Interrupt()
-
函数功能 : 计数器1中断服务
-
输 入 : 无
-
输 出 : 无
/
void Count1_Interrupt() interrupt 3 using 2
{
TH1=0x00; //计数值清0
TL1=0x00;
CountValue++;
}
/ -
函 数 名 : Display
-
函数功能 : 将频率、周期、占空比、脉宽显示
-
输 入 :
*Content,Parameter 显示内容,数据 -
输 出 : 无
*/
void Display(uchar Content,ulong Data)
{
LcdWriteString(0,0,Content);
LcdWriteChar(0,1,0x30+(Data/1000000)); //取百万位写入
LcdWriteChar(1,1,0x30+(Data/100000%10));//取十万位写入
LcdWriteChar(2,1,0x30+(Data/10000%10)); //取万位写入
LcdWriteChar(3,1,0x30+(Data/1000%10)); //取千位写入
LcdWriteChar(4,1,0x30+(Data/100%10)); //取百位写入
LcdWriteChar(5,1,0x30+(Data/10%10)); //取十位写入
LcdWriteChar(6,1,0x30+(Data%10)); //取个位写入
if(Content==“Frequency:”)
{
LcdWriteChar(7,1,‘H’);
LcdWriteChar(8,1,‘z’);
}
if(Content==“PulseWidth:”||Content==“Cycle:”)
{
LcdWriteChar(7,1,‘u’);
LcdWriteChar(8,1,‘s’);
}
}
/ -
函 数 名 : MeasureFrequency
-
函数功能 : 测量频率
-
输 入 : 无
-
输 出 : 无
/
void MeasureFrequency()
{
while(TimeValue<20); //1s定时到达
TR0=0;
TR1=0;
Frequency=(CountValue65536+TH1256+TL1); //频率计算
TH1=0x00; //重新赋值
TL1=0x00;
TH0=0x3C; //重新赋值
TL0=0xAF;
TimeValue=0;
CountValue=0;
Display(“Frequency:”,Frequency); //显示频率
Delay1ms(300);
}
/** -
函 数 名 : MeasureCycl
-
函数功能 : 测量周期
-
输 入 : 无
-
输 出 : 无
/
void MeasureCycle()
{
while(InputPin); //等待一个高电平到来
while(!InputPin); //等待低电平过去
TR0=1; //高电平,开定时器
while(InputPin); //高电平时间
TR0=0; //高电平过去则关闭定时器
PulseWidth=TimeValue50000+(TH0256+TL0-15536); //高电平时间
TimeValue=0;
TH0=0x3C; //定时50ms
TL0=0xAF;
Delay1ms(1);
while(!InputPin); //等待一个低电平
while(InputPin); //等待高电平过去
TR0=1; //低电平,开定时器
while(!InputPin); //等待低电平过去
TR0=0;
Low=TimeValue50000+(TH0256+TL0-15536);//低电平时间
TH0=0x3C; //定时50ms
TL0=0xAF;
TimeValue=0;
Delay1ms(1);
Cycle=PulseWidth+Low; //周期等于高电平时间加上低电平时间
}
/**** -
函 数 名 : MeasurePulseWidth
-
函数功能 : 测量脉宽
-
输 入 : 无
-
输 出 : 无
/
void MeasurePulseWidth()
{
while(InputPin); //等待高电平过去
while(!InputPin);//等待低电平过去
TR0=1; //高电平,开定时器
while(InputPin); //等待高电平过去
TR0=0;
PulseWidth=TimeValue50000+(TH0256+TL0-15536);//高电平时间
TH0=0x3C;
TL0=0xAF;
TimeValue=0;
Delay1ms(1);
Display(“PulseWidth:”,PulseWidth);
Delay1ms(300);
}
/** -
函 数 名 : MeasureDutyCycle
-
函数功能 : 测量占空比
-
输 入 : 无
-
输 出 : 无
************************************************/
void MeasureDutyCycle()
{
MeasureCycle(); //测周期
PulseWidth=PulseWidth100; //乘以100,防止精度丢失太多
DutyCycle=PulseWidth/Cycle; //计算占空比
Display(“DutyCycle:”,DutyCycle);
LcdWriteChar(7,1,’%’);
Delay1ms(300);
}
void main(void)
{
LcdInit(); //LCD1602初始化
TimerInit(); //定时器初始化
while(1)
{
switch(KeyScan())
{
case 0x01:
while(InputPin);
TR0=1;
TR1=1;
MeasureFrequency(); //测频率
KeyValue=0;
break;
case 0x02:
TR0=0;
TR1=0;
MeasureCycle(); //测周期
Display(“Cycle:”,Cycle);
Delay1ms(10);
KeyValue=0;
break;
case 0x04:
TR0=0;
TR1=0;
MeasurePulseWidth(); //测脉宽
KeyValue=0;
break;
case 0x08:
TR0=0;
TR1=0;
MeasureDutyCycle(); //测占空比
KeyValue=0;
break;
default:
break;
}
}
}