Arduino 上利用 AVR CTC 模式生成无抖动 1Hz 方波并同步校准内部 RC 振荡器
👁 2 阅读 · 2026-08-27 · 嵌入式
本文深入探讨如何在 Arduino(ATmega328P)上利用定时器 CTC 模式生成精确的 1Hz 方波,并以此为基础,通过测量实际频率来校准芯片内部 RC 振荡器。文章涵盖 CTC 模式原理、寄存器配置、中断处理、误差分析及校准方法,提供完整代码示例,帮助开发者摆脱外部晶振,实现低成本高精度的定时应用。
# 引言
在嵌入式开发中,精确的定时信号至关重要。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 定时器与嵌入式系统时序设计。