影子寄存器(Shadow Register)

STM32 影子寄存器(Shadow Register)是定时器硬件内部真正生效的寄存器,作用是确保时序参数(周期、占空比、分频)在更新时不产生毛刺、不破坏当前周期完整性,实现同步、无抖动的参数切换。

基本结构:预装载 + 影子

STM32 定时器里带 “阴影” 标记的寄存器(ARR、CCR、PSC、RCR)都由两部分组成:

  • 预装载寄存器(Preload Register)
    • 软件直接读写的对象(如 TIMx->ARR、TIMx->CCR1)
    • 你写的值先存在这里,不一定立即生效
  • 影子寄存器(Shadow Register)
    • 硬件内部、用户不可直接访问
    • 真正参与计数、比较、输出的寄存器
    • 只有它的值决定当前定时器行为

核心作用(为什么需要影子)

  1. 保证当前计数周期完整,不被中途截断
    • 计数器正在跑时,你改 ARR/CCR:
      • 无影子:立即改硬件 → 周期突然变短 / 变长、PWM 出现毛刺、尖峰、丢脉冲
      • 有影子:新值暂存预装载,等当前周期结束(更新事件 UEV)才加载到影子 → 平滑切换
  2. 多通道参数同步更新
    • 同时改多个 CCR(多通道 PWM):
      • 软件不可能同一时刻写完所有寄存器
      • 开启预装载 → 所有新值先缓存
      • 一次更新事件 → 所有影子寄存器同时刷新 → 多通道严格同步
  3. 防止运行中修改导致逻辑错乱
    • 避免在计数中途突变阈值,造成:
      • 意外中断 / 溢出
      • 输出波形失真、电机异响、采样错位
      • 时序逻辑不可预知

常见带影子的寄存器(定时器)

  • ARR(自动重装载):决定周期
    • 控制位:TIMx_CR1.ARPE(ARR 预装载使能)
  • CCR(捕获 / 比较):决定 PWM 占空比、比较阈值
    • 控制位:TIMx_CCMRx.OCxPE(通道 x 预装载使能)
  • PSC(预分频器):计数时钟分频
    • 始终带影子,无使能位 → 必须等更新事件才生效
  • RCR(重复计数器):高级定时器,更新事件分频

两种工作模式(以 ARR 为例)

模式 1:立即生效(ARPE=0)

  • 预装载 ↔ 影子直通
  • 写 TIMx->ARR → 立即更新影子
  • 缺点:中途改值易产生波形异常、周期错乱

模式 2:预装载缓冲(ARPE=1,推荐)

  • 写 TIMx->ARR → 只改预装载
  • 更新事件(UEV)到来时:
    • 预装载 → 影子(一次性拷贝)
    • 新值从下一个周期开始生效
      更新事件何时发生
  • 计数器溢出 / 下溢(计数到 0 或 ARR)
  • 软件触发:TIMx->EGR = TIM_EGR_UG

典型应用场景(PWM 动态调参)

  • 电机调速、LED 调光:随时改频率 / 占空比
  • 无影子:改 CCR/ARR → 瞬间突变 → 波形毛刺、电机抖响
  • 有影子(ARPE=1、OCxPE=1):
    • 软件写新值到 TIMx->ARR / TIMx->CCR
    • 等待当前周期结束(UEV)
    • 影子自动更新 → 下一个周期完美切换

一句话总结

影子寄存器 = 硬件级双缓冲:让你在定时器运行时安全修改参数,保证时序干净、周期完整、多通道同步,是 PWM、电机控制、高精度定时必须理解的底层机制。

事件、中断与触发

概念关系总览

1
2
3
4
5
6
7
8
硬件信号变化(如定时器溢出、EXTI 边沿)


触发(Trigger)—— 信号检测方式:边沿触发?电平触发?软件触发?

├─→ 事件(Event) ──→ 触发 DMA / 触发其他外设(无需 CPU 介入)

└─→ 中断(Interrupt) ──→ CPU 执行 ISR(需要 CPU 介入)

三者从不同维度描述同一个物理过程:

  • 触发:描述”什么条件引起了注意”
  • 事件:描述”发生了什么事
  • 中断:描述”CPU 如何响应这件事

1. 三个核心概念的区别

概念 是什么 CPU 参与? 典型用途
事件(Event) 硬件产生的信号,用于外设间协作 可选,看配置 DMA 传输、外部信号同步
中断(Interrupt) CPU 对事件的响应机制 是,CPU 必须介入 定时器溢出、串口收到数据、外设需要处理
触发(Trigger) 引起反应的”条件”,如边沿、电平、软件 配置外设时的触发沿选择

简单来说:事件是一种信号,中断是 CPU 对这个信号的响应,触发是产生信号的机制


2. 中断(Interrupt)

2.1 什么是中断

中断是一种机制:CPU 正在执行主程序(main loop),某个外设(比如定时器)发生了一个特定事件,这个事件通知 CPU:「我需要你暂停一下,去处理一下我这边的事」。CPU 停下当前工作,跳转到对应的中断服务函数(ISR)去执行,处理完后再回到原来的位置继续执行。

2.2 中断向量表

STM32 有一个中断向量表,每个外设对应一个或多个中断入口地址。当中断发生时,硬件自动根据向量表跳转到对应的 ISR。开发者要做的就是:给每个外设的中断写一个 ISR 函数,并正确配置 NVIC(嵌套向量中断控制器)。

2.3 NVIC 优先级

STM32 用 NVIC 管理所有中断的优先级。优先级分为:

  • 抢占优先级(Preemption Priority):高可以打断低的
  • 子优先级(Sub Priority):当两个中断同时到达,按子优先级决定先后

数字越小优先级越高。优先级配置直接影响系统实时性,配置错误可能导致低优先级中断被长期阻塞。

2.4 常见中断源

  • 定时器更新中断(TIMx_IRQn)
  • 外部中断(EXTIx)
  • 串口接收/发送中断(USARTx_IRQn)
  • ADC 转换完成中断
  • DMA 完成/错误中断

3. 事件(Event)

3.1 什么是事件

事件是硬件之间的”内部通知信号”。一个外设可以产生事件,这个事件可以:

  1. 触发 DMA 传输(不需要 CPU 参与)
  2. 触发另一个外设的操作(比如定时器输出比较匹配后触发 ADC 启动)
  3. 也可以触发中断(这实际上是在事件基础上加了一层 CPU 介入)

3.2 事件与中断的关键区别

事件不需要 CPU 介入,是纯硬件的协作机制。例如:

  • DMA 触发:串口收到数据后产生事件,事件触发 DMA 把数据从 DR 寄存器搬到你预设的内存里,整个过程 CPU 完全不参与。
  • 如果同一个场景用中断实现:串口接收中断触发,CPU 在 ISR 里一个个字节地复制数据 — 这就占用了 CPU 时间和中断嵌套开销。

3.3 EXTI 外部中断/事件线

EXTI(External Interrupt/Event Controller)是一个重要的模块,它管理的实际上是:

  • 外部中断:线路上的信号变化触发 CPU 中断
  • 外部事件:同一个信号变化触发 DMA 或其他外设

两者共享同一组物理线路(0-15),但通过配置区分开。配置为事件时,信号绕过 CPU,直接传递给其他外设。

3.4 事件的本质:硬件电路

事件不是软件里的”标志位”,而是芯片内部物理布好的硬件通路

每个外设在硅片上已经布好了线:定时器溢出→内部事件线→连接到 DMA 控制器、EXTI、其他外设。这些线路在你买回来的时候就已经固定了,你只能通过配置寄存器来选择它的走向,但不能改变它的物理存在

3.5 事件与标志位的关系

类型 有标志位吗 说明
外设→CPU 的事件 TIM 的更新标志位在 SR 寄存器,软件可见
外设→DMA 的事件 没有 DMA 请求信号是内部硬连线,软件看不到
外设→外设的事件 没有 TIM 触发 ADC 这种内部连接,没有软件标志位

事件和标志位是同一事件的两面:硬件在产生事件的同时会在寄存器里设置标志位,但纯硬件传递的事件(如 DMA 请求)没有软件可见的标志位。

3.6 软件触发事件

软件触发不是去”写事件标志位”,而是去写触发寄存器

1
2
3
4
5
6
7
8
// 定时器:手动产生更新事件
TIM2->EGR |= TIM_EGR_UG; // 写 EGR 触发,不是写 SR

// ADC:软件触发转换
ADC1->CR |= ADC_CR_ADSTART;

// EXTI:软触发外部中断
EXTI->SWIER |= (1 << 13); // 软件触发 EXTI13 的事件

标志位是”查”的(只读),触发寄存器是”写”的(可写)。 标志位是果,触发寄存器是因。


4. 触发(Trigger)

4.1 什么是触发

触发不是独立的概念,而是指外设检测信号的方式。每种外设在启动时都需要知道「什么情况下我才开始工作」,这个判断条件就是触发条件。

4.2 常见的触发类型

4.2.1 边沿触发(Edge Trigger)

  • 上升沿触发:信号从低变高那一刻触发
  • 下降沿触发:信号从高变低那一刻触发
  • 双边沿触发:上升沿和下降沿都触发

典型应用:按键检测(下降沿触发表示按下)、编码器计数(双边沿都有计数值变化)。

4.2.2 电平触发(Level Trigger)

  • 高电平触发:信号维持高电平期间持续工作
  • 低电平触发:信号维持低电平期间持续工作

典型应用:某些通信协议需要维持一段电平来表示数据。

4.2.3 软件触发(Software Trigger)

手动通过代码触发某个动作,如:

  • TIMx->EGR |= TIM_EGR_UG 手动产生更新事件
  • ADC1->CR |= ADC_CR_ADSTART 手动启动 ADC 转换

4.3 触发源配置示例

以定时器为例:

1
2
3
TIMx->SMCR 寄存器控制触发源:
- SMS[2:0] = 010:外部时钟模式1,TRGI 引脚上升沿驱动计数器
- SMS[2:0] = 100:复位模式,TRGI 上升沿触发计数器复位

以 ADC 为例:

1
2
3
ADC1->CR |= ADC_CR_ADSTART  // 软件触发开始转换
或者:
EXTI->EMR 某个事件触发(硬件触发)

5. 中断与事件的典型应用场景

场景 选择中断还是事件 原因
串口收到数据,需要 CPU 处理 中断 数据需要 CPU 分析、决策
串口收到数据,直接 DMA 搬走 事件 纯数据搬运,CPU 不需要介入
定时器计时,到了就做固定动作 中断 到了时间点需要 CPU 操作
定时器触发 DMA,自动传输波形数据 事件 周期性搬运不需要 CPU
按键按下,切换状态 中断 需要 CPU 判断和处理
两个外设需要精确同步 事件 硬件信号比中断更精确,无软件延迟

6. 中断处理的注意事项

6.1 ISR 编写原则

  • 快进快出:ISR 执行时间要尽量短,不要在 ISR 里做复杂运算、printf、大量数据处理
  • 避免重入:STM32 的 ISR 是不能被同一外设打断的(除非配置了嵌套),但可能被更高优先级打断
  • 清除标志位:进入 ISR 后通常需要手动清除外设的中断标志位,否则退出后会立即再次进入

6.2 中断使能与禁能

1
2
3
4
5
6
7
8
// 使能定时器中断
NVIC_EnableIRQ(TIM2_IRQn);

// 禁能
NVIC_DisableIRQ(TIM2_IRQn);

// 设置优先级
NVIC_SetPriority(TIM2_IRQn, 2);

6.3 常见错误

  1. 在 ISR 里使用阻塞操作(如等待标志位)
  2. 忘记清除中断标志位导致卡死在 ISR
  3. 优先级配置错误导致低优先级中断饿死
  4. 在 main 里和 ISR 里同时操作同一个全局变量(需要关中断或加锁)

7. 总结:三者关系梳理

1
2
3
4
5
6
7
8
9
10
     触发条件满足(如上升沿到来)


触发(Trigger)

┌───────────┴───────────┐
▼ ▼
事件(Event) 中断(Interrupt)
└─→ DMA 传输 └─→ NVIC 优先级判断
└─→ 触发其他外设 └─→ CPU 执行 ISR

关键关系:

  1. 触发 → 事件:触发是条件,事件是触发后的”信号本身”
  2. 事件 → 中断:事件可以触发中断(需配置),但也可以不触发中断
  3. 触发 ≠ 中断:中断只是触发的一种响应方式,不是所有触发都会导致中断

理解的核心:把整个过程想象成”报警系统”:

  • 触发:报警的条件(烟雾、温度超标、敲门)
  • 事件:报警发出的信号(声光、通知)
  • 中断:安保人员(CPU)收到信号后的行动

实际开发中,能用事件 + DMA 解决的不用中断,让 CPU 专注核心业务。