引言
在嵌入式开发中,精确的定时信号至关重要。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 方波,并通过串口输出实际频率测量结果。
// 1Hz 方波生成与 RC 校准示例
#include <avr/io.h>
#include <avr/interrupt.h>
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,实现闭环校准。
// 校准函数:根据测量频率调整 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 定时器与嵌入式系统时序设计。