中断与时钟

59 2025-08-24 09:45

下面把“中断”与“时钟”这两个概念放在一起做一个系统化梳理,并给出在 Arduino(以及更一般的 MCU)场景里它们是怎样配合、怎样配置、有哪些易踩的坑。你可以把它当成一份速查笔记。

  1. 中断的本质
  • 定义:打断当前 CPU 指令流,强制跳到固定入口(向量表)执行一段 ISR,再返回。
  • 触发源:
    – 外部引脚(attachInterrupt、CHANGE/RISING/FALLING/LOW)
    – 片上外设(ADC 完成、串口收到字节、定时器溢出、比较匹配、看门狗复位等)
    – 异常事件(地址越界、除零、NMI)
  • 关键寄存器:全局中断使能位(AVR 的 I 位、ARM 的 PRIMASK),以及每个外设自己的中断使能/标志位。
  1. 时钟在 MCU 中的角色
  • 时钟 = 驱动所有逻辑的“心跳”。
    – 源:内部 RC、外部晶振、PLL 倍频、内部 32 kHz RTC 时钟。
    – 分配:CPU 时钟、外设总线时钟、定时器计数时钟、ADC 时钟……
  • 时钟频率决定了:
    – 指令执行速度(1 Hz = 1 条指令 / 秒,8 MHz ≈ 8 MIPS)。
    – 定时器分辨率(16 位定时器在 8 MHz 下,1 tick = 0.125 µs)。
    – 串口波特率、SPI/I²C 速率等。
  1. “定时器中断”——把时钟变成中断
  • 工作流程:
    时钟 → 预分频器(1/8、1/64 …)→ 计数器 TCNTx → 溢出/比较匹配 → 置位中断标志 → CPU 若全局中断使能则跳转到 ISR。
  • 典型用法:
    – 产生周期任务(1 kHz PID 控制,每 1 ms 一次)。
    – 软件 PWM、Tone、Servo、IR 发射。
    – 时间戳/软件 RTC(millis()/micros() 在 AVR 上就是 T0 溢出中断)。
  1. Arduino 常见时钟与中断 API 速查 | 需求 | AVR 芯片 | ESP32 | 备注 | |---|---|---|---| | 取毫秒时间戳 | millis() | 同 | 内部 Tick 计数器,32 位 49 天回绕 | | 取微秒时间戳 | micros() | 同 | AVR 每 4 µs 一计,ESP32 每 1 µs | | 周期中断 | Timer1/TimerOne 库 | hw_timer_t* + timerAttachInterrupt | ESP32 有 4 组 64 位通用定时器 | | 外部引脚中断 | attachInterrupt(pin, ISR, mode) | 同 | ESP32 所有 GPIO 都可 | | 精确延时 | delayMicroseconds() | 同 | 忙等,关中断会失真 |
  2. 中断延迟 & 抖动
  • 进入延迟:CPU 完成当前指令 + 向量跳转,AVR 约 3–5 时钟周期;ESP32 带 cache/flash 抖动更大。
  • 抖动源:更高优先级中断抢占、ISR 内关中断、flash 擦写(ESP32)。
  • 对策:
    – ISR 里只做“标记/写队列”,重活放 loop()
    – 关键代码用 cli()/sei()portENTER_CRITICAL() 开关中断。
    – ESP32 把 ISR 放 IRAM:void IRAM_ATTR onTimer(){...}
  1. 时钟精度 & 温漂
  • 内部 RC:±1 %–5 %,随温度/电压漂移。
  • 外部晶振:±20 ppm(0.002 %),一年漂移约 10 分钟。
  • 若需长期计时:
    – 接 32.768 kHz 晶振 + RTC 模块(DS3231、PCF8523)。
    – 或用 NTP/ GPS 定期校时。
  1. 一个最小可运行示例(AVR Timer1 1 kHz CTC)
cpp
 
复制
#include <TimerOne.h>

volatile bool tick = false;

void onTimer() {      // 中断上下文
  tick = true;        // 只做标记
}

void setup() {
  Timer1.initialize(1000);      // 1000 µs 周期
  Timer1.attachInterrupt(onTimer);
  Serial.begin(115200);
}

void loop() {
  if (tick) {
    tick = false;
    static uint32_t cnt = 0;
    Serial.println(cnt++);
  }
}
 
  • 代码要点:
    volatile 保证编译器不优化掉跨上下文访问。
    loop() 里无阻塞操作,否则中断来了也来不及响应。

一句话总结
时钟 = 心跳源;中断 = 让 CPU 在特定“心跳点”或“外部事件”发生时停下手里活去干别的。
把时钟切成等长节拍,就能用“定时器中断”做出任何周期任务;把外部信号接到引脚,就能用“外部中断”立即响应。二者共同构成了 MCU 实时性的根基。

全部评论

·