基于51单片机的数字频率计

时间:2024-03-26 16:25:08

概述

该设计用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后的频率值。如下公式所示。

基于51单片机的数字频率计

2. 测量周期的原理

因被测信号的频率越高,测量的误差越大。为避免在高频时测量的误差过大,故在低频(<100Hz)的情况下,利用定时计数器在外部信号第一次高电平来临时从0开始计时,在相邻的下一个高电平来临时定时/计数器关闭,则定时的值即为所测的周期。但倘若在高频(>100Hz)的情况下,则利用上述的测量方法测量10组周期值求得其平均值来作为所测的周期值,这样做的目的是减少在高频时因频率太快周期太短,定时计数器速率跟不上而带来的偶然误差。
利用STC89C52RC单片机内部定时器1的定时功能,测量信号周期的大小。信号从P3.5口引入,检测P3.5口信号的第一个上升沿到来,定时器1以工作方式1开始定时,定时器1定时溢出,标志位count加一,直至第二个上升沿到来,定时器1停止工作。则该信号的周期为如下公式所示。
基于51单片机的数字频率计

3.测量脉宽的原理

正、负脉宽的定义是在高电平、低电平所持续的时间。则可利用测量周期的方法类似的测量出脉宽值。在检测到外部输入信号的上升沿时则打开计数器,为下降沿时则关闭计数器,此时计时器所测得的值即为正脉宽值。在检测到外部输入信号的下降沿时开启计数器,为上升沿时关闭计数器,则此时记录的数值为负脉宽值。
信号从P3.5口引入,检测P3.5口信号的第一个上升沿到来,定时器1以工作方式1开始定时,定时器1定时溢出,标志位count加一,直至第一个下降沿,定时器1停止工作。则该信号的脉宽如下公式所示。
基于51单片机的数字频率计

4. 测量占空比原理

占空比的定义是在一个周期内高电平所持续的时间即为占空比,故可利用上述测量周期和脉宽的方法来实现占空比的测量。设测得周期为、正脉宽为,则占空比为正脉宽除以周期的值。
信号从P3.5口引入,检测P3.5口信号的第一个上升沿到来,定时器1以工作方式1开始定时,定时器1定时溢出,标志位count和m加一,直至第一个下降沿,得出脉宽的大小,如公式(4)所示。定时器1继续以工作方式1工作,标志位count加一,直至第二个上升沿,定时器1停止工作。得出周期的大小,如公式(2)所示。则该信号的占空比如下公式所示。
基于51单片机的数字频率计

5. 实物图

基于51单片机的数字频率计
基于51单片机的数字频率计
基于51单片机的数字频率计
基于51单片机的数字频率计

6.LCD1602电路原理图

基于51单片机的数字频率计

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=(CountValue
    65536+TH1
    256+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=TimeValue
    50000+(TH0
    256+TL0-15536); //高电平时间
    TimeValue=0;
    TH0=0x3C; //定时50ms
    TL0=0xAF;
    Delay1ms(1);
    while(!InputPin); //等待一个低电平
    while(InputPin); //等待高电平过去
    TR0=1; //低电平,开定时器
    while(!InputPin); //等待低电平过去
    TR0=0;
    Low=TimeValue
    50000+(TH0
    256+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=TimeValue
    50000+(TH0
    256+TL0-15536);//高电平时间
    TH0=0x3C;
    TL0=0xAF;
    TimeValue=0;
    Delay1ms(1);
    Display(“PulseWidth:”,PulseWidth);
    Delay1ms(300);
    }
    /
    **

  • 函 数 名 : MeasureDutyCycle

  • 函数功能 : 测量占空比

  • 输 入 : 无

  • 输 出 : 无
    ************************************************/
    void MeasureDutyCycle()
    {
    MeasureCycle(); //测周期
    PulseWidth=PulseWidth
    100; //乘以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;
    }
    }
    }