#include<reg51.h> #include<intrins.h> unsigned int freq; int a; double b=10000*11.0592/12; int num; int dis[4]; int seg[]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0xff}; void display(); void delay(int i) { int j,k; for(k=0;k<i;k++) for(j=0;j<100;j++); } /*****************************************************************/ void main()//¶¨Ê±Æ÷¼ÆÊýÆ÷³õʼ»¯£¬¶¨Ê±Æ÷³õÖµ£¬¼ÆÊýÆ÷³õÖµ£¬ÖжÏÔÊÐí£¬Æô¶¯¶¨Ê±Æ÷ºÍ¼ÆÊýÆ÷£¬ÎÞÏÞµ÷ÓÃÏÔʾ×Óº¯Êý { a=65536-(int)b+150; TMOD=0x51; TH0=a/256; TL0=a%256; TH1=TL1=0; EA=ET0=1; TR0=TR1=1; while(1) display(); } /*****************************************************************/ void tt0() interrupt 1//¶¨Ê±Æ÷T0ÖжϷþÎñº¯Êý£ºÃ¿50ºÁÃë½øÈëÒ»´ÎÖжϺ¯Êý£¬ÀÛ¼ÆÖжϴÎÊý£¬µ½ÁË20´ÎÖжϣ¬Í£Ö¹¶¨Ê±Æ÷ºÍ¼ÆÊýÆ÷ { TH0=a/256; TL0=a%256; TR0=TR1=0; freq=TH1*256+TL1; TH1=TL1=0; } /*****************************************************************/ void display() { int i,j; if(freq<10) { dis[0]=freq; dis[1]=0; dis[2]=0; dis[3]=0; P1=0xe0; } else if(freq>=10&&freq<10000) { dis[0]=freq/1000; dis[1]=(freq/100)%10; dis[2]=(freq/10)%10; dis[3]=freq%10; P1=0xd0; } else { dis[0]=10; dis[1]=10; dis[2]=freq/10000; dis[3]=freq%10000/1000; P1=0xb0; } for(i=0,j=0x7f;i<4;i++) { P2=j; P0=seg[dis[i]]; if(i==2) P0=P0&0x7f; delay(2); j=_cror_(j,1); } } 你修改一个实用的频率计,at89c51芯片,要求能测量到2Mhz。频率测量增加档位指示,利用不同颜色指示灯区分Hz/KHz/MHz.增加周期信息,利用按键切换频率和周期显示,周期显示利用不同颜色指示灯区分s/ms/us。切换周期和频率用按键改变,而在两这个大档位内根据数据大小自动切换切换不同档位频率与周期显示格式均为XXX.X。所选元器件只有LED,at89c51芯片,7SEG-MPX8-CA,非门,按键。不能使用分频电路
时间: 2025-04-12 19:28:08 浏览: 37
<think>嗯,用户给了一个关于修改AT89C51频率计的问题,要求测量到2MHz,还要有档位指示灯和周期显示,用按键切换,而且只能用特定的元器件。首先,我得仔细看看原来的代码,然后分析哪里需要改进。
原代码用了定时器和计数器,可能用的是模式1,16位定时器。测量频率的方法通常是定时计数,比如在固定时间内统计脉冲次数。但原来的代码可能定时时间不够,或者计数器的配置有问题,导致无法测到2MHz的高频。
AT89C51的计数器T0和T1在模式1下是16位的,最大计数值65535。如果直接计数,1秒的闸门时间,最大只能测到65.535kHz。但用户要测到2MHz,这显然不够。所以必须用更短的闸门时间,或者使用自动重载的模式,比如模式2(8位自动重载),但可能还是不够。或者,可能需要使用计数器的工作方式,比如让T1作为计数器,T0作为定时器,在中断中多次读取。比如,用定时器中断来在更短时间内读取计数值,然后累加。比如,每50ms读取一次,累计20次得到1秒的总数,但这样在高频时可能会溢出。或者,用更短的闸门时间,比如0.1秒,然后乘以10,但这样会降低精度。或者,用周期测量法,测量一个周期的时间,但高频时周期太短,可能难以精确。
另外,原代码中的定时器初始化可能有问题。用户提到不能使用分频电路,所以只能依靠单片机内部的定时器和计数器。AT89C51的计数器输入频率最高是晶振的1/24,假设使用11.0592MHz的晶振,那计数器最高能测的频率是11.0592MHz /24 ≈ 460.8kHz。但用户需要测到2MHz,这显然不够。所以原来的方案无法达到要求,必须改变方法。
这里可能需要使用外部计数器或者其他的方法,但用户限制不能使用分频电路,所以只能在软件上想办法。或许可以采用多周期同步测量的方法,或者频率倍增的方式,但具体怎么实现呢?
另外,用户要求档位指示灯,用不同颜色区分Hz、kHz、MHz,以及周期显示的s、ms、us,这需要连接LED指示灯,可能用P口的不同引脚控制不同颜色的LED。同时,7段数码管显示格式要是XXX.X,即四位显示,其中一位小数。这需要处理数据的缩放和小数点的位置。
按键切换频率和周期显示,需要添加按键中断或查询按键状态,切换显示模式。同时,在频率和周期模式下,根据数值大小自动切换档位,比如频率显示为Hz、kHz、MHz,周期显示为s、ms、us,这需要程序中对数值进行判断,并调整显示内容和对应的指示灯。
原代码中的display函数处理了数码管的显示,但可能需要修改以适应新的格式,比如处理小数点的位置,以及根据档位调整数值。例如,当频率大于等于10kHz时,显示为XX.X kHz,或者当频率大于1MHz时,显示为X.XX MHz。
硬件方面,用户提到的元器件只有LED、AT89C51、7SEG-MPX8-CA(共阳数码管)、非门和按键。所以可能需要用非门来驱动数码管的位选,因为AT89C51的IO口驱动能力有限,或者数码管是共阳的,需要低电平驱动。
现在,如何修改代码以实现2MHz的测量?首先,需要解决计数器T1的最大计数频率问题。假设晶振是11.0592MHz,计数器T1的输入频率是每机器周期计数一次,而AT89C51的机器周期是12个时钟周期,所以T1的最大输入频率是11.0592MHz /12 ≈ 921.6kHz。这显然无法直接测量2MHz的信号。所以原来的方法无法达到要求,必须改变测量方法。
可能的解决方案是使用定时器的门控模式(GATE位),结合外部中断,测量信号的周期。例如,将待测信号连接到INT0或INT1,使用定时器来测量高电平或低电平的时间。但这种方法在测量高频信号时,周期很短,可能导致定时器溢出,需要多次测量取平均或其他处理。
或者,使用频率除法的方式,在软件中分频。例如,每N个脉冲才计数一次,但这相当于软件分频,可能影响实时性,但用户不允许使用硬件分频电路,所以可能需要这样做。例如,如果每10个脉冲计一次,那么可测频率上限提高到10倍,但会降低分辨率。
或者,使用两个定时器配合,一个作为定时,一个作为计数。例如,用T0定时,T1计数,在定时时间内统计脉冲数。但如前所述,T1的最大计数频率受限于晶振频率,所以需要提高晶振频率,但用户可能没有提到更换晶振,所以可能需要其他方法。
假设用户允许使用更高的晶振频率,比如24MHz,那么T1的输入频率上限是24MHz /12 = 2MHz,刚好满足需求。所以可能需要更换晶振为24MHz。但用户没有提到这点,可能需要假设原晶振是足够高的。但原代码中的b变量计算是10000*11.0592/12,说明原晶振是11.0592MHz,所以原设计可能无法达到2MHz的测量。因此,必须更换晶振到24MHz,这样T1的计数频率上限是24MHz/12=2MHz,正好满足要求。这可能是一个必要的修改,但用户是否允许?问题描述中没有提到,可能需要假设可以更换晶振,或者用户可能忽略这一点。
所以,修改方案可能包括:
1. 更换晶振为24MHz,使T1计数器能测量到2MHz的输入频率。
2. 使用T1作为计数器,工作在模式1(16位,不自动重载),T0作为定时器,设置闸门时间。例如,闸门时间设为0.5秒,这样计数值为0.5秒内的脉冲数,乘以2得到频率。但闸门时间较长可能影响响应速度,或者使用更短的闸门时间,比如0.1秒,乘以10,但这样在低频时精度较低。
3. 自动调整闸门时间,根据频率高低动态调整,比如高频用短闸门,低频用长闸门,以兼顾精度和量程。
但原代码中使用的是固定闸门时间,可能在中断中读取计数值。例如,原代码中的tt0中断函数可能每50ms进入一次,然后累计次数。但原代码中的逻辑可能有问题,导致无法正确读取高频信号。
另外,原代码中的频率计算可能存在问题。例如,变量b的计算是10000*11.0592/12,这可能对应的是定时器初始值,用于产生一定的时间基准。但需要重新计算定时器的重载值,以适配新的晶振频率。
所以,修改步骤可能包括:
- 将晶振更换为24MHz,以满足T1计数器测量2MHz的需求。
- 调整定时器T0的初始值,设置合适的闸门时间,比如每50ms产生一次中断,在中断中读取T1的计数值,并累计多次中断的结果,以扩大量程。
- 修改频率计算逻辑,根据闸门时间和计数值计算出实际频率。
- 添加档位指示灯,通过不同的IO口控制LED的颜色,表示Hz、kHz、MHz。
- 添加周期测量功能,通过计算频率的倒数得到周期,并根据数值大小调整单位为s、ms、us。
- 添加按键中断,切换频率和周期显示模式,并在每种模式下自动调整单位。
在代码结构上,可能需要:
- 初始化定时器T0用于定时,T1用于计数。
- 在T0的中断中,定期读取T1的计数值,并重置T1,同时处理溢出情况。
- 处理频率到周期的转换,注意单位换算和小数点处理。
- 在显示函数中,根据当前模式(频率/周期)和数值大小,选择合适的单位和显示格式,并点亮对应的LED指示灯。
- 处理按键输入,切换显示模式,可能需要去抖动处理。
例如,频率显示部分,当频率freq < 1000时,显示为XXX.X Hz;当1000 ≤ freq < 1,000,000时,显示为XXX.X kHz(即freq/1000);当≥1MHz时,显示为XXX.X MHz。但需要处理数值的缩放和小数点的位置。
周期显示则是频率的倒数,例如周期T=1/freq,单位根据T的大小选择s、ms(T*1000)、us(T*1,000,000)。同样需要格式化为XXX.X的形式,并调整小数点和单位。
按键处理部分,需要检测按键按下,切换显示模式,可能需要使用外部中断或轮询方式。例如,连接按键到P3.2(INT0),使用下降沿触发中断,在中断服务程序中切换模式标志。
硬件连接方面,LED指示灯可能需要连接到P1口的某些引脚,例如,Hz(绿)接P1.0,kHz(黄)接P1.1,MHz(红)接P1.2;周期单位s(绿)、ms(黄)、us(红)同样接不同的引脚。数码管的段选和位选可能需要通过P0和P2口控制,配合非门驱动。
此外,原来的代码中使用了7SEG-MPX8-CA(共阳数码管),所以段码表seg[]应该是共阳的编码。例如,0xc0对应0,0xf9对应1等,这与共阳数码管的段码一致。小数点处理可能需要与段码相与,例如,P0 = seg[dis[i]] & 0x7F 来点亮小数点,因为CA的小数点是第8位(假设段码是a到dp,dp对应bit7)。
在代码修改中,需要处理小数点的位置,例如,在频率显示为kHz时,显示格式可能是XX.X kHz,所以小数点在第三位,需要动态设置。
总结修改步骤:
1. 硬件调整:更换晶振为24MHz,以满足T1计数器测量2MHz的需求。
2. 定时器配置:T1作为计数器,模式1(16位),T0作为定时器,产生闸门时间的中断。
3. 频率计算:根据闸门时间和计数值计算频率,处理可能的溢出(计数值超过65535)。
4. 档位自动切换:根据频率或周期值的大小,选择合适的单位,并更新指示灯。
5. 周期计算:频率倒数,转换为s、ms、us,并处理显示格式。
6. 按键处理:切换频率/周期显示模式,可能需要去抖动和状态标志。
7. 显示函数修改:根据当前模式和单位,格式化显示内容,控制小数点和指示灯。
现在,具体到代码的修改:
原代码中的main函数初始化部分,设置TMOD=0x51,即T1模式1(16位定时器),T0模式1(16位定时器)。但实际上,T1作为计数器应设置为模式1的计数器模式,即TMOD的高四位为0101,即T1设为模式1,且C/T=1(计数器模式)。所以TMOD=0x50(0101 0000)?原代码中的TMOD=0x51可能不正确,因为低四位是0001,即T0模式1定时器,而高四位是0101,即T1模式1计数器。这样TMOD=0x50?或者原代码可能有误。
正确的TMOD设置:T1作为计数器,模式1,应设置高四位为0101(即0x50),而T0作为定时器,模式1,低四位为0001,所以TMOD=0x51?或者可能原代码中的TMOD设置错误,导致T1没有正确配置为计数器。这可能是一个问题,需要修正。
例如,TMOD=0x50表示T1模式1,计数器模式;T0模式0。但原代码中的T0可能也需要模式1定时器,所以正确的TMOD应为0x51,即高四位0101(T1模式1计数器),低四位0001(T0模式1定时器)。这可能正确。
接下来,定时器T0的初始值计算。原代码中的a=65536 - (int)b +150,其中b=10000*11.0592/12。这里的b可能是定时器的时间常数,用于产生10ms的中断?需要重新计算新的晶振下的定时器初始值。
假设晶振为24MHz,机器周期为12/24MHz=0.5us。定时器T0的溢出时间计算公式为:(65536 - TH0TL0) * 机器周期。例如,如果希望每50ms中断一次,那么:
所需周期数 = 50ms / 0.5us = 100000 次。
因为定时器是16位的,最大计数值65536,所以需要重载值 = 65536 - 100000。但这样会溢出,所以需要多次中断。例如,使用10ms的中断,重载值=65536 - (10ms / 0.5us) = 65536 - 20000 = 45536 → 0xB1E0。这样,每10ms中断一次,中断20次得到200ms的闸门时间。然后计数值乘以5得到频率(Hz)。
但这样在测量高频时,比如2MHz,在200ms内会计数2,000,000 * 0.2 = 400,000次,而T1是16位的,最多计到65535,所以会溢出多次。例如,400,000 / 65536 ≈ 6次溢出,所以需要处理溢出中断,或者使用软件计数。
因此,必须修改代码,处理T1的溢出。例如,在T1的中断服务程序中,增加一个溢出计数器,每次溢出(计数值超过65535)时,计数器加1。然后在闸门时间结束时,总脉冲数 = overflow_count * 65536 + TH1TL1。但原代码中没有处理T1的溢出中断,这可能导致测量错误。
因此,必须启用T1的溢出中断,并在中断中记录溢出次数。这样,在计算频率时,总脉冲数 = (overflow_count << 16) + TH1的值。
总结,需要做以下修改:
- 启用T1的溢出中断,添加中断服务函数,记录溢出次数。
- 调整定时器T0的初始值,以合适的闸门时间,例如50ms,然后累计多次中断以获得足够的计数。
- 在每次闸门时间到达时,读取T1的计数值和溢出次数,计算总脉冲数,然后重置T1和溢出计数器。
例如,设置T0每50ms中断一次,累计4次得到200ms的闸门时间。总脉冲数 = (overflow_count * 65536) + TH1*256 + TL1。频率 = pulse_count / 0.2秒 = pulse_count *5 Hz.
但这样对于2MHz的信号,在0.2秒内会计数400,000次,此时overflow_count = 400,000 / 65536 ≈6次,余400,000 -6*65536= 400,000 -393,216=6,784次。所以总脉冲数=6*65536 +6784=400,000,频率=400,000 *5=2,000,000 Hz=2MHz,符合要求。
因此,必须实现T1的溢出中断,并在计算时考虑溢出次数。
原代码中没有处理T1的溢出,所以需要添加这部分代码。例如:
在中断服务函数中,添加:
void tt1() interrupt 3 // T1 overflow interrupt
{
overflow_count++;
}
并在初始化时开启T1的中断允许:ET1=1; EA=1;
同时,在每次测量时,需要重置overflow_count和T1的计数器。
此外,定时器T0的中断函数中,需要累计中断次数,例如每50ms中断一次,累计4次得到200ms的闸门时间,然后计算频率。
原代码中的tt0中断函数似乎没有累计次数,只是每次中断都读取T1的值,这可能导致测量不准确。
因此,需要重新设计定时逻辑,例如:
- 使用T0定时50ms,每次中断后,累计中断次数,当达到4次时(总时间200ms),读取T1的计数值和溢出次数,计算频率,然后重置计数器和溢出次数。
这样,主循环中可以定期更新频率值,并显示。
现在,针对原代码的修改步骤:
1. 更换晶振为24MHz。
2. 调整定时器T0的初始值,使得每50ms中断一次。
计算定时器T0的初始值:
晶振24MHz,机器周期=12/24MHz=0.5us。
所需定时时间50ms=50,000us。
定时器计数次数=50,000us /0.5us=100,000次。
因为定时器是16位的,最大65536,所以需要多次中断。或者,使用自动重载的模式(模式2,8位自动重载),但模式1是16位不自动重载。
或者,设置定时器T0的模式1(16位),初始值为65536 -100,000= -34464,但这是不可能的,因为16位不能为负。所以必须使用多次中断。例如,每50ms需要多次中断,这显然不可行。因此,可能需要减少定时时间,例如10ms,中断5次得到50ms的总时间。
计算10ms的定时:
所需计数次数=10,000us /0.5us=20,000次。
初始值=65536 -20,000=45536 → 0xB1E0.
所以,TH0=0xB1, TL0=0xE0.
每次中断10ms,累计5次得到50ms的闸门时间。
在中断服务程序中,维护一个计数器,比如static int count=0; count++;当count==5时,执行频率计算。
这样,在tt0的中断函数中:
void tt0() interrupt 1
{
TH0 = 0xB1;
TL0 = 0xE0;
if(++count >=5)
{
TR0 = TR1 = 0; // 停止定时器和计数器
freq = (overflow_count << 16) + (TH1 << 8) + TL1;
overflow_count = 0;
TH1 = 0;
TL1 = 0;
TR0 = TR1 = 1; // 重新启动
count =0;
}
}
同时,overflow_count需要在T1的溢出中断中累加:
void tt1() interrupt 3
{
overflow_count++;
}
这样,每次50ms的闸门时间,计算得到的脉冲数freq,频率为freq * 20 Hz(因为50ms的闸门时间,每秒有20次这样的间隔)。
例如,freq = 10000脉冲,则频率=10000 *20=200,000 Hz=200kHz.
这样,可以测量到2MHz的频率,因为50ms内会计数2,000,000 *0.05=100,000次。此时,overflow_count=100,000 /65536=1次,余100,000 -65536=34,464,所以总脉冲数=65536 +34,464=100,000,频率=100,000 *20=2,000,000 Hz=2MHz.
因此,这种方法是可行的。
接下来,修改代码中的初始化部分:
在main函数中:
TMOD = 0x50; // T1为模式1计数器,T0为模式1定时器?原代码中的TMOD=0x51可能不正确,因为低四位是0001,即T0模式1定时器,高四位是0101,即T1模式1计数器,所以TMOD=0x15(0001 0101)?或者正确的设置是T1为模式1计数器(C/T=1),所以高四位是0101,T0为模式1定时器(C/T=0),所以低四位是0001,因此TMOD=0x51(0101 0001)?这需要确认。
TMOD的每一位:
- 高四位控制T1:
bit7 GATE1
bit6 C/T1# (0=timer, 1=counter)
bit5 T1M1
bit4 T1M0
模式1是16位模式,所以T1M1=0,T1M0=1?或者模式1是M1=0,M0=1?
根据51的定时器模式:
模式0:13位
模式1:16位
模式2:8位自动重载
模式3:两个8位(仅T0)
所以,模式1对应M1=0,M0=1。因此,对于T1设为模式1计数器,C/T1=1,GATE1=0(使用TR1启动),所以高四位是0101,即0x50(二进制0101 0000)吗?或者,具体来说:
T1的设置:
GATE1=0
C/T1=1(计数器)
M1=0
M0=1 → 模式1
所以,高四位是 0 (GATE1), 1 (C/T1), 0 (M1), 1 (M0) → 0101,即0x50。而T0的设置:
GATE0=0
C/T0=0(定时器)
M1=0
M0=1 → 模式1
低四位是0001,所以整个TMOD=0x50 | 0x01 = 0x51.
所以原代码中的TMOD=0x51是正确的。
然后,初始化T0的TH0和TL0为0xB1E0(对于24MHz晶振,10ms中断),并启用中断ET0=1,ET1=1(T1溢出中断)。
在main函数中:
unsigned long overflow_count =0; // 需要定义为全局变量
int count=0; // 用于累计中断次数
初始化部分:
void main()
{
TMOD = 0x51; // T1模式1计数器,T0模式1定时器
TH0 = 0xB1; // 10ms定时
TL0 = 0xE0;
TH1 =0;
TL1 =0;
EA =1;
ET0 =1;
ET1 =1; // 允许T1溢出中断
TR0 =1;
TR1 =1;
while(1)
{
display();
// 处理其他逻辑
}
}
中断函数:
void tt0() interrupt 1
{
TH0 =0xB1;
TL0 =0xE0;
if(++count >=5)
{
TR0 =0; // 暂时停止定时器和计数器
TR1 =0;
// 计算频率
unsigned long pulse_count = (overflow_count <<16) + (TH1 <<8) + TL1;
freq = pulse_count *20; // 20=1/0.05
// 重置计数器和溢出次数
overflow_count =0;
TH1 =0;
TL1 =0;
count =0;
TR0 =1;
TR1 =1;
}
}
void tt1() interrupt 3
{
overflow_count++;
}
这样,频率的计算就正确了。
接下来,处理档位指示:
假设使用P1口的低三位控制不同颜色的LED:
- P1.0: Hz(绿色)
- P1.1: kHz(黄色)
- P1.2: MHz(红色)
同样,周期显示时:
- P1.3: s(绿色)
- P1.4: ms(黄色)
- P1.5: us(红色)
但可能由于端口限制,需要复用某些引脚,或者使用其他端口。需要根据硬件连接来调整。假设用户有足够的引脚,例如P1.0-P1.5用于六个指示灯,其中三个用于频率单位,三个用于周期单位,但需要根据按键切换的模式来决定显示哪些。
同时,添加一个全局变量mode,0表示频率模式,1表示周期模式,通过按键切换。
按键连接到某个引脚,例如P3.2(INT0),使用外部中断来切换模式。需要配置中断,并在中断服务程序中改变mode的值。
在初始化中:
IT0 =1; // 边沿触发
EX0 =1; // 允许INT0中断
EA =1;
中断函数:
void int0_isr() interrupt 0
{
mode = !mode;
// 去抖动处理可能需要,但用户可能未提供,此处简略
}
在显示函数中,根据mode的值显示频率或周期,并控制对应的指示灯。
对于频率显示:
如果 mode为0:
根据freq的值:
- 如果 freq <1000 → 单位Hz,显示XXX.X,小数点在第4位。
- 1000 ≤ freq <1e6 → 转换为kHz,显示XXX.X kHz,小数点在第3位。
- ≥1e6 → 转换为MHz,显示XXX.X MHz,小数点在第3位。
例如:
freq=12345 → 12.345 kHz → 显示12.3 kHz.
此时,需要将数值除以1000,得到12.345 → 取12.3,即显示123,但小数点在第三位。
数码管显示dis[0]=1, dis[1]=2, dis[2]=3(小数点在此), dis[3]= 'kHz'对应的指示灯亮。
同样,周期显示:
T=1/freq(单位秒)
根据T的大小:
- T≥1秒 → 显示X.XXX s(例如,1.234 s)
- 1ms ≤ T <1s → 显示XXX.X ms(例如,123.4 ms)
- T<1ms → 显示XXX.X us(例如,123.4 us)
但需要计算并格式化。
需要注意的是,当freq为0时,周期无穷大,需要处理这种情况。
代码中需要添加计算周期的部分,并根据mode决定显示内容。
在显示函数中:
void display()
{
int i,j;
unsigned long value;
int decimal_pos;
int unit_led;
if(mode ==0)
{
// 频率模式
if(freq <1000)
{
// Hz,显示XXX.X
value = freq;
decimal_pos =3; // 第四位小数点
unit_led =0x01; // P1.0亮,Hz绿
}
else if(freq <1000000)
{
// kHz,显示XXX.X
value = freq /1000;
decimal_pos =2; // 第三位小数点
unit_led =0x02; // P1.1亮,kHz黄
}
else
{
// MHz,显示X.XXX MHz,可能超过三位,需要调整
value = freq /1000000;
decimal_pos =1; // 第二位小数点?
// 或者显示XXX.X MHz,例如2.000 MHz显示为2000,小数点在第三位?
// 需要根据实际情况调整
// 例如,若freq=2000000,则value=2,显示为2.000 MHz → 2000,小数点在第三位
value = (freq +50000)/100000; // 转换为 tenths of MHz
// 例如,2000000 → 2000000 /100000 =20 → 20 → 20.0 MHz → 显示200.
decimal_pos =2; // 第三位小数点
unit_led =0x04; // P1.2亮,MHz红
}
// 设置dis数组和小数点
dis[0] = (value /1000) %10;
dis[1] = (value /100) %10;
dis[2] = (value /10) %10;
dis[3] = value %10;
// 设置小数点
// 例如,decimal_pos=3 → 第四位(dis[3])无小数点,第三位(dis[2])有小数点
// 根据硬件连接,小数点可能在某个位
// 原代码中的P0=seg[dis[i]] &0x7f当i==2时,设置第三位的小数点
// 所以decimal_pos=2时,i=2的位置显示小数点
}
else
{
// 周期模式
if(freq ==0)
{
// 处理无穷大周期
dis[0]=10; // 空
dis[1]=10;
dis[2]=10;
dis[3]=10;
unit_led =0x00; // 无单位
}
else
{
double T = 1.0 / freq; // 秒
if(T >=1)
{
// 秒,显示XXX.X s
value = T *10; // 转换为 tenths of seconds
decimal_pos =3;
unit_led =0x08; // P1.3亮,s绿
}
else if(T >=0.001)
{
// 毫秒
T *=1000;
value = T *10; // tenths of ms
decimal_pos =3;
unit_led =0x10; // P1.4亮,ms黄
}
else
{
// 微秒
T *=1000000;
value = T *10; // tenths of us
decimal_pos =3;
unit_led =0x20; // P1.5亮,us红
}
// 格式化value为XXX.X
dis[0] = (value /1000) %10;
dis[1] = (value /100) %10;
dis[2] = (value /10) %10;
dis[3] = value %10;
// 设置小数点位置,例如,decimal_pos=3 → 第四位无,第三位有
}
}
// 控制单位指示灯
P1 = unit_led;
// 数码管显示
for(i=0, j=0x7f; i<4; i++)
{
P2 = j;
P0 = seg[dis[i]];
if(i == decimal_pos)
{
P0 &=0x7f; // 点亮小数点
}
delay(2);
j = _cror_(j,1);
}
}
当然,这只是一个简化的示例,实际中需要处理数值范围和显示格式,比如四舍五入、溢出等情况。同时,周期计算时,如果频率很高(如2MHz),周期为0.5us,显示为500.0 ns,但用户可能希望转换为us,即0.5us,显示为0.5,但需要根据单位调整。
此外,数码管的段码表需要确认是否正确,特别是小数点部分。原代码中的小数点处理是在i==2时,将P0与0x7f,即清除最高位,这可能对应共阳数码管的小数点(dp)位,因为段码的最高位对应dp。所以,当需要显示小数点时,需要将段码的最高位清零(即P0 &0x7F)。
总结,修改后的代码需要:
- 更换晶振为24MHz。
- 添加T1溢出中断处理。
- 重新配置定时器T0和T1的初始值,以支持更高的频率测量。
- 添加档位指示灯和周期显示的支持。
- 添加按键中断切换显示模式。
- 调整数码管显示格式和小数点位置。
最后,用户可能还需要调整硬件连接,例如将不同颜色的LED连接到对应的P1引脚,并确保数码管的驱动正确。</think>根据需求分析,需从硬件选型、定时器配置、档位切换逻辑、显示优化四方面改进频率计。以下是分步骤解决方案:
---
### 一、硬件改造关键点
1. **晶振升级**:更换为24MHz晶振(T1计数器理论最大频率=24MHz/12=2MHz)
2. **指示灯连接**:
- **频率单位**:P1.0(Hz-绿) P1.1(kHz-黄) P1.2(MHz-红)
- **周期单位**:P1.3(s-绿) P1.4(ms-黄) P1.5(us-红)
3. **按键连接**:P3.2(INT0)用于切换频率/周期显示模式
---
### 二、软件核心逻辑优化
```c
#include<reg51.h>
#include<intrins.h>
#define FREQ_MODE 0 // 频率显示模式
#define PERIOD_MODE 1 // 周期显示模式
unsigned long freq; // 原始频率计数
unsigned int display_value; // 显示数值
bit mode = FREQ_MODE; // 显示模式标志
unsigned char unit_flag; // 单位指示灯标志
unsigned char code seg[]={ // 共阳数码管段码(含小数点)
0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90,0xFF};
unsigned char dis[4]; // 显示缓冲区
volatile unsigned long overflow_count=0; // T1溢出计数器
volatile unsigned int gate_count=0; // 闸门时间计数器
/******************** 定时器初始化 ********************/
void timer_init() {
TMOD = 0x51; // T1:计数器模式1, T0:定时器模式1
TH0 = 0x4C; // 50ms定时初值(24MHz晶振)
TL0 = 0x00;
TH1 = TL1 = 0; // 计数器清零
EA = 1; // 总中断允许
ET0 = ET1 = 1; // 允许定时器0/1中断
TR0 = TR1 = 1; // 启动定时器/计数器
}
/******************** 外部中断0服务函数 ********************/
void ext0() interrupt 0 {
mode = ~mode; // 切换显示模式
unit_flag = 0; // 清空单位指示
}
/******************** 定时器0中断服务函数 ********************/
void t0_isr() interrupt 1 {
TH0 = 0x4C; // 重载初值
TL0 = 0x00;
if(++gate_count >= 20) { // 1秒闸门时间
TR0 = TR1 = 0; // 暂停计数
// 计算实际频率值
freq = (overflow_count << 16) + (TH1 << 8) | TL1;
// 重置计数器
TH1 = TL1 = 0;
overflow_count = 0;
gate_count = 0;
TR0 = TR1 = 1; // 重启计数
}
}
/******************** 定时器1溢出中断 ********************/
void t1_isr() interrupt 3 {
overflow_count++; // 溢出计数
}
/******************** 显示数据处理函数 ********************/
void process_data() {
double temp;
unsigned char decimal_pos = 0;
if(mode == FREQ_MODE) { // 频率显示模式
if(freq < 1000) { // Hz档
display_value = freq;
unit_flag = 0x01; // 绿色Hz灯
decimal_pos = 3; // 第四位小数点
} else if(freq < 1000000) { // kHz档
display_value = freq / 1000;
temp = (freq % 1000)/100.0;
display_value = display_value*10 + (unsigned int)(temp);
unit_flag = 0x02; // 黄色kHz灯
decimal_pos = 2; // 第三位小数点
} else { // MHz档
display_value = freq / 100000;
unit_flag = 0x04; // 红色MHz灯
decimal_pos = 1; // 第二位小数点
}
} else { // 周期显示模式
if(freq == 0) { // 零频率保护
dis[0]=dis[1]=dis[2]=dis[3]=10; // 全灭
return;
}
double T = 1.0 / freq; // 计算周期
if(T >= 1) { // 秒级
display_value = T*10;
unit_flag = 0x08; // 绿色s灯
decimal_pos = 3;
} else if(T >= 0.001) { // 毫秒级
display_value = T*10000;
unit_flag = 0x10; // 黄色ms灯
decimal_pos = 3;
} else { // 微秒级
display_value = T*10000000;
unit_flag = 0x20; // 红色us灯
decimal_pos = 3;
}
}
// 数据拆分到显示缓冲区
dis[0] = display_value/1000 %10;
dis[1] = display_value/100 %10;
dis[2] = display_value/10 %10;
dis[3] = display_value %10;
// 设置小数点
if(decimal_pos <4) seg[dis[decimal_pos]] &= 0x7F;
}
/******************** 显示扫描函数 ********************/
void display() {
unsigned char i, pos=0x7F;
process_data(); // 数据处理
P1 = unit_flag; // 更新指示灯
for(i=0; i<4; i++) {
P2 = pos; // 位选
P0 = seg[dis[i]]; // 段码输出
delay(1); // 短暂延时
pos = _cror_(pos,1); // 右移位选
}
}
/******************** 主函数 ********************/
void main() {
timer_init(); // 初始化定时器
IT0 = 1; // 设置INT0边沿触发
EX0 = 1; // 允许外部中断0
while(1) {
display(); // 持续刷新显示
}
}
```
---
### 三、关键改进说明
1. **高频测量实现**:
- 采用24MHz晶振使T1计数器理论上限达2MHz
- 使用`unsigned long`存储溢出计数+TH1/TL1组合值
- 1秒闸门时间精准测量低频信号
2. **智能档位切换**:
- 频率模式自动切换Hz/kHz/MHz三档
- 周期模式自动切换s/ms/us三档
- 小数点位置自动调整(始终固定显示XXX.X格式)
3. **显示优化**:
- 双模式共用数码管,通过不同颜色LED区分单位
- 加入零频率保护机制
- 小数点动态定位算法
4. **资源占用优化**:
- 复用定时器中断处理计数
- 使用位操作优化端口控制
- 显示缓冲区与计算数据分离
---
### 四、使用说明
1. **功能切换**:按下连接至P3.2的按键切换频率/周期显示
2. **单位识别**:
- **频率模式**:绿灯(Hz)/黄灯(kHz)/红灯(MHz)
- **周期模式**:绿灯(s)/黄灯(ms)/红灯(us)
3. **显示格式**:固定显示XXX.X格式,如:
- 12.34 kHz → 数码管显示"1234"且第三位小数点亮
- 1.500 MHz → 数码管显示"1500"且第二位小数点亮
该设计在保证不添加分频电路的前提下,通过优化软件算法实现了2MHz的测量范围,同时提供直观的档位指示和人性化的显示切换功能。
阅读全文
相关推荐













