如何验证AVR ATmega328P的TIMER2溢出频率?实测异常求助
ATmega328P Timer2溢出频率验证问题解决
问题背景
已将ATmega328P的Timer2配置为普通模式,预期溢出频率10000Hz,通过溢出中断翻转PB5引脚验证频率。初始示波器测得频率约159Hz,调整后DSO显示4.9407kHz(对应溢出频率9.881kHz),与预期10kHz不符。
原代码
#define F_CPU 16000000 #define TIMER2_PRESCALER 8 // RV8A12, added 2022-11-08 #define TIMER2_CLOCK (F_CPU / TIMER2_PRESCALER) uint8_t Timer2_count = 0; unsigned int sample_freq; ISR(TIMER2_OVF_vect) { TCNT2 = Timer2_count; PORTB ^= (1<<PORTB5); } void timer2_init() { TCCR2A = 0x00; // Normal mode TCNT2 = Timer2_count; TIMSK2 = 0x01; TCCR2B = 0x02; // Set prescaler 8 or divide by 8 } void timer2_stop() { TCCR2B = 0x00; } void setup() { // Put your setup code here, to run once: sei(); Serial.begin(115200); Serial.print("\nSTARTING..."); delay(5000); DDRB |= (1<<PORTB5); sample_freq = 10000; Timer2_count = 256 - (TIMER2_CLOCK / sample_freq); timer2_init(); } void loop() { // Put your main code here, to run repeatedly: }
问题分析
1. 初始159Hz异常原因
大概率是预分频器配置错误或Timer2_count赋值顺序问题:
- 若预分频器误设为1024(
TCCR2B=0x07),溢出频率为16MHz/1024/256≈61Hz,翻转引脚后为30Hz,与159Hz不符;更可能是前期代码中先调用timer2_init再计算Timer2_count,导致TCNT2被设为0,溢出频率为16MHz/8/256≈7812Hz,翻转后3906Hz,仍不符。需排查代码执行顺序和寄存器配置。
2. 9.88kHz偏差原因
原代码中ISR的软件操作存在延迟:
- 理论计算:
TIMER2_CLOCK=16MHz/8=2MHz,2MHz/10kHz=200,Timer2_count=256-200=56,溢出频率应为10kHz。但ISR中先重载TCNT2再翻转引脚,这两个操作会占用数个时钟周期,导致实际溢出周期变长,频率略低于预期,翻转后的引脚频率(溢出频率的一半)因此接近4.94kHz。
正确验证方法
方法一:用CTC模式减少ISR开销(精度最高)
改用CTC(比较匹配清零定时器)模式,硬件自动清零TCNT2,无需在ISR中重载寄存器,彻底消除软件延迟:
#define F_CPU 16000000 #define TIMER2_PRESCALER 8 #define TIMER2_CLOCK (F_CPU / TIMER2_PRESCALER) unsigned int sample_freq = 10000; ISR(TIMER2_COMPA_vect) { PORTB ^= (1<<PORTB5); // 仅保留翻转操作,无寄存器重载 } void timer2_init() { TCCR2A = (1<<WGM21); // 启用CTC模式 OCR2A = (TIMER2_CLOCK / sample_freq) - 1; // CTC模式下,比较值为周期数-1 TIMSK2 = (1<<OCIE2A); // 使能比较匹配A中断 TCCR2B = (1<<CS21); // 设置预分频器为8 } void setup() { sei(); DDRB |= (1<<PORTB5); timer2_init(); } void loop() { }
此时溢出频率理论值为10kHz,引脚翻转频率为5kHz,精度大幅提升。
方法二:软件统计中断次数(排除示波器误差)
通过串口统计1秒内的溢出中断次数,直接计算实际频率,避免引脚翻转的软件延迟和示波器测量误差:
#define F_CPU 16000000 #define TIMER2_PRESCALER 8 #define TIMER2_CLOCK (F_CPU / TIMER2_PRESCALER) uint8_t Timer2_count = 0; volatile uint32_t overflow_count = 0; unsigned int sample_freq = 10000; ISR(TIMER2_OVF_vect) { TCNT2 = Timer2_count; overflow_count++; } void timer2_init() { TCCR2A = 0x00; TCNT2 = Timer2_count; TIMSK2 = 0x01; TCCR2B = 0x02; } void setup() { sei(); Serial.begin(115200); DDRB |= (1<<PORTB5); Timer2_count = 256 - (TIMER2_CLOCK / sample_freq); timer2_init(); } void loop() { static unsigned long last_time = 0; if (millis() - last_time >= 1000) { cli(); // 关中断避免统计时被打断 uint32_t count = overflow_count; overflow_count = 0; sei(); // 恢复中断 Serial.print("实际溢出频率:"); Serial.println(count); last_time = millis(); } }
串口输出的数值即为1秒内的溢出次数,直接对应溢出频率。
方法三:检查时钟精度
若上述方法仍有偏差,需验证ATmega328P的系统时钟是否为精确16MHz。可通过测量已知频率的引脚(如串口波特率对应的时钟)确认时钟准确性。
总结
- 优先使用CTC模式提升频率精度,消除ISR软件延迟;
- 软件统计中断次数可直接获取溢出频率,不受引脚翻转操作影响;
- 初始异常频率需重点排查预分频器配置和变量赋值顺序。
内容的提问来源于stack exchange,提问作者vishnu m c
相关产品推荐
相关产品推荐

