我需要测量方波的频率,该频率可以在0到1MHz之间变化,分辨率为0.25Hz。
我还没有决定使用哪个控制器,但是它很可能是20pin Attiny的控制器之一。
通常,我如何测量低频信号将通过使用两个定时器来配置,其中一个配置为定时器捕获模式以在外部信号的上升沿中断,另一个定时器设置为每秒中断一次,因此前一个定时器在1秒后计数寄存器值将等于信号的频率。
但是,这种方法显然无法以0.25Hz的分辨率捕获0到1MHz范围内的信号,为此,我需要一个22Bit计数器(AFAIK 8bit micros仅具有8 / 16bit计数器)。
我曾经想过的一个想法是在将信号应用于微控制器之前先对其进行分频,但这是不切实际的,因为必须将信号除以61,因此频率只能每61秒更新一次,而我希望每隔几秒更新一次。
还有另一种方法可以让频率每4秒更新一次吗?
更新:
最简单的解决方案是使用外部中断或定时器捕获在信号的上升沿中断,并使isr
增量为type变量long int
。每4秒钟读取一次变量(以允许测量低至0.25Hz的频率)。
更新2:
正如JustJeff所指出的,一个8位MCU将无法跟上1MHz的信号,从而排除了在每个上升沿中断并增加long int
...
我选择了timororr建议的方法。一旦我实现了它,我会回发并分享结果。感谢大家的建议。
进度报告:
Iv'e开始测试此处提出的一些想法。首先,我尝试了vicatcu的代码。存在一个明显的问题,即TCNT1在计算频率后仍未清除-没什么大不了的...
然后,我在调试代码时注意到,大约每2到7倍的频率被计算出来,定时器1(配置为对外部事件进行计数的定时器)的溢出计数将减少2。我将其归结为定时器0 ISR的等待时间,并决定将ISR的if语句块移至主要(请参见下面的代码段),并在ISR中设置一个标志。一些调试显示,第一次测量是可以的,但随后的每次读取都将使Timer 1的溢出计数超过2。这我无法解释-我希望它不会超过...。
int main()
{
while(1)
{
if(global_task_timer_ms > 0 && (T0_overflow == 1))
{
global_task_timer_ms--;
T0_overflow = 0;
}
.....
}
}
接下来,我决定尝试实施timrorrs建议。要生成必要的间隔(每个timer_isr中断之间大约15毫秒),我将必须将两个8位定时器级联,因为Atmega16上仅有的16位定时器被用来捕获外部信号的上升沿。
我认为此解决方案将行之有效,并且效率会更高,因为大部分开销都转移到了计时器上,并且仅剩一个简短的isr供CPU处理。但是它不如我所希望的那样准确,测量值来回移动了约70Hz,这在高频下我不介意,但在低频下绝对不能接受。我没有花太多时间来分析问题,但是我猜想计时器的级联设置不太准确,因为我在一个速度较慢的具有2个16位计时器的8051控制器上实现了与调速建议类似的安排,结果相当准确。
现在,我又回到了vicatcu的建议,但是将频率计算移到了Timer 0 isr (请参见下面的代码段)中,此代码产生了一致且合理的测量结果。稍加校准后,精度应约为+/- 10Hz。
ISR(TIMER0_OVF_vect)
{
TCNT0 = TIMER0_PRELOAD; //Reload timer for 1KHz overflow rate
if(task_timer_ms > 0)
{
task_timer_ms--;
}
else
{
frequency_hz = 1.0 * TCNT1;
TCNT1 = 0;
frequency_hz += global_num_overflows * 65536.0;
global_num_overflows = 0;
frequency_hz /= (TASK_PERIOD_MS / 1000.0);
task_timer_ms = TASK_PERIOD_MS;
}
}
如果有人有其他建议,尽管我可以向他们提出,但我宁可不必使用范围...我也不再打算获得0.25%的分辨率,我目前的准确性水平似乎没有多大意义。