我需要测量可以在 0 到 1MHz 之间变化的方波频率,分辨率为 0.25Hz。
我还没有决定使用哪个控制器,但它很可能是 20pin Attiny 的一个。
通常,我如何测量低频信号是使用两个定时器,一个配置为定时器捕获模式,在外部信号的上升沿中断,另一个定时器设置为每秒中断一次,因此前一个定时器在 1 秒后计数寄存器值将等于信号的频率。
然而,这种方法显然不适用于以 0.25Hz 的分辨率捕获 0 到 1MHz 之间的信号,为此我需要一个 22 位计数器(AFAIK 8 位微控制器只有 8/16 位计数器)。
我的一个想法是在将信号应用于微机之前对其进行划分,但这不切实际,因为信号必须除以 61,因此频率只能每 61 秒更新一次,而我希望它每隔几秒更新一次.
是否有另一种方法可以让频率每 4 秒更新一次?
更新:
最简单的解决方案是使用外部中断或定时器捕捉在信号的上升沿中断,并让isr增量类型为变量long int。每 4 秒读取一次变量(以允许测量低至 0.25Hz 的频率)。
更新 2:
正如 JustJeff 所指出的,一个 8 位 MCU 将无法跟上 1MHz 信号,因此排除了在每个上升沿中断并增加一个long int...
我选择了 timororr 建议的方法。一旦我开始实施它,我会发回并分享结果。感谢大家的建议。
进度报告:
我已经开始测试这里提出的一些想法。首先我尝试了vicatcu的代码。有一个明显的问题是TCNT1在计算频率后没有被清除——没什么大不了的……
然后我注意到,在调试代码时,大约每 2 到 7 次计算一次频率,计时器 1(配置为计数外部事件的计时器)溢出计数会减少 2。我将其归结为 Timer 0 ISR 的延迟,并决定将 if 语句块从 ISR 移至 main(见下面的片段),并在 ISR 中设置一个标志。一些调试表明第一次测量是可以的,但随后每次读取计时器 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 中断之间大约 15ms),我必须级联两个 8 位定时器,因为 Atmega16 上唯一的 16 位定时器用于捕获外部信号的上升沿。
我认为这个解决方案会起作用并且效率更高,因为大部分开销都转移到了计时器上,并且只剩下一个短的 isr 供 cpu 处理。然而它并不像我希望的那样准确,测量值来回移动了大约 70Hz,我不介意在高频下,但在低频下绝对不能接受。我没有花太多时间分析问题,但我猜测定时器级联安排并不那么准确,因为我已经在一个速度慢得多的 8051 控制器上实现了与 timrorrs 建议类似的安排,该控制器具有 2 个 16 位定时器,结果非常准确。
我现在回到了 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% 的分辨率,我目前的准确度水平似乎没有多大意义.