# 引言 在嵌入式开发中,精确的定时信号至关重要。Arduino 板载的 16MHz 晶振通常精度较高,但许多低成本克隆板或裸片方案使用内部 RC 振荡器,其精度受温度、电压影响,误差可达 ±10%。本文介绍一种利用 AVR 定时器 CTC 模式生成 1Hz 方波的方法,并展示如何通过测量实际输出频率来校准内部 RC 振荡器,从而在无外部晶振的情况下获得接近晶振的精度。 # CTC 模式原理 CTC(Clear Timer on Compare Match)模式是 AVR 定时器的一种工作模式。当计数器 TCNT 与比较寄存器 OCR 相等时,计数器清零,同时可触发中断或翻转输出引脚。通过设置比较值,可以精确控制输出波形的周期。 对于 1Hz 方波,我们需要每 0.5 秒翻转一次输出。若使用 16MHz 时钟,预分频为 256,则计数器频率为 62.5kHz。要得到 0.5 秒周期,比较值应为 31250(62.5k × 0.5)。但注意,CTC 模式计数到比较值后清零,因此实际周期为 (OCR+1) 个计数周期,所以 OCR = 31249。 # 硬件与软件准备 - 硬件:Arduino Uno 或兼容板(ATmega328P),示波器或频率计,USB 转 TTL 串口模块(用于校准)。 - 软件:Arduino IDE,或直接使用 AVR-GCC 编程。 # 配置步骤 ## 1. 定时器/计数器选择 ATmega328P 有 Timer0(8位)、Timer1(16位)、Timer2(8位)。Timer1 适合生成低频信号,因为 16 位计数器可容纳较大比较值。 ## 2. 寄存器配置 - 设置 TCCR1A:选择 CTC 模式(WGM12=1),并配置输出引脚 OC1A(PB1,Arduino 数字 9)为翻转模式(COM1A0=1)。 - 设置 TCCR1B:设置预分频为 256(CS12=1,CS11=0,CS10=0),并保持 WGM13=0。 - 设置 OCR1A = 31249。 - 启用比较匹配中断(TIMSK1 的 OCIE1A=1),用于校准计数。 ## 3. 中断处理 在中断服务程序中,可以增加一个计数器,用于测量实际频率。例如,每产生一次中断,变量 `pulse_count` 加 1。 # 完整代码示例 以下代码在 Arduino 上生成 1Hz 方波,并通过串口输出实际频率测量结果。 ```c // 1Hz 方波生成与 RC 校准示例 #include #include volatile unsigned long pulse_count = 0; void setup() { pinMode(9, OUTPUT); // OC1A 引脚 Serial.begin(115200); // 配置 Timer1 为 CTC 模式 TCCR1A = (1 << COM1A0); // 翻转 OC1A TCCR1B = (1 << WGM12) | (1 << CS12); // CTC 模式,预分频 256 OCR1A = 31249; // 0.5s 周期 TIMSK1 = (1 << OCIE1A); // 使能比较匹配中断 sei(); // 全局中断使能 } ISR(TIMER1_COMPA_vect) { pulse_count++; // 每次翻转计数 } void loop() { static unsigned long last_count = 0; static unsigned long last_time = 0; if (millis() - last_time >= 1000) { unsigned long current_count = pulse_count; unsigned long diff = current_count - last_count; // 每秒中断次数约为 2(因为 1Hz 方波,每周期两次翻转) // 实际频率 = diff / 2 Hz float freq = diff / 2.0; Serial.print("Measured frequency: "); Serial.print(freq, 3); Serial.println(" Hz"); last_count = current_count; last_time = millis(); } } ``` # 校准内部 RC 振荡器 测量实际频率后,可以调整 OSCCAL 寄存器(仅部分 AVR 芯片,如 ATmega328P 没有 OSCCAL,但 ATtiny 有)。对于 ATmega328P,内部 RC 振荡器不可校准,但我们可以通过软件调整定时器预分频或比较值来补偿误差。 方法:根据测量频率与实际目标频率的偏差,调整 OCR1A 值。例如,若测得频率为 0.98Hz,则实际周期偏长,应减小 OCR1A 值。公式: `new_OCR = (int)(old_OCR * (measured_freq / target_freq))` 在代码中,可以动态调整 OCR1A,实现闭环校准。 ```c // 校准函数:根据测量频率调整 OCR1A void calibrate(float measured_freq) { float target_freq = 1.0; float ratio = measured_freq / target_freq; unsigned int new_ocr = (unsigned int)(OCR1A * ratio); OCR1A = new_ocr; } ``` # 注意事项 - 预分频选择:预分频越大,分辨率越低,但可生成更低频率。对于 1Hz,256 预分频合适。 - 中断开销:中断服务程序应尽量简短,避免影响计时精度。 - 测量窗口:测量频率时,采样时间应足够长(如 10 秒),以减少计数误差。 - 温度漂移:内部 RC 受温度影响明显,校准后若环境变化,可能需重新校准。 - 引脚冲突:OC1A 默认映射到 PB1(Arduino 数字 9),确保该引脚未被其他功能占用。 # 总结 通过 CTC 模式,我们可以在 Arduino 上生成精确的 1Hz 方波,并利用测量结果进行校准。虽然 ATmega328P 的内部 RC 不可直接调节,但通过软件补偿,仍能获得较高精度。此方法适用于对成本敏感、对精度要求不极高的应用,如时钟、定时器等。掌握这一技术,有助于深入理解 AVR 定时器与嵌入式系统时序设计。