☰
ODrive 8kHz控制环时基设计:从时钟树到TIM8中断的完整解析
2026/9/29 16:34:38 网站建设 项目流程

ODrive 的固件源码里,最容易被忽略、但又最不该被忽略的一块,就是它的时基系统。很多人第一次翻源码,注意力全被 FOC 算法、电流环、编码器校准这些"明星模块"吸走了,结果看到Axis::run_control_loop()里那一堆if (count % ...)就懵了——为什么控制环是 8 kHz?为什么不是 10 kHz 或者 20 kHz?这个 8 kHz 到底是从哪个定时器、哪个分频系数、哪个中断里长出来的?我当初也是带着这个疑问一路从main.cpp追到TIM8的初始化,中间踩了不少"想当然"的坑。这篇就把这条链路完整拆一遍,从时钟树到定时器配置,再到中断里怎么把 8 kHz 的节拍分发给电流环、位置环和 PWM 更新,尽量把每一步的"为什么"讲清楚。适合已经能编译 ODrive 固件、想深入理解实时控制时基设计的嵌入式开发者,也适合做电机控制、想把"控制频率"这件事从玄学变成工程的人。

1. 为什么 ODrive 把控制环钉在 8 kHz 这个数字上

1.1 8 kHz 不是拍脑袋定的,它和 PWM 频率是绑定关系

先把结论摆出来:ODrive 的 8 kHz 控制环,本质上是PWM 开关频率 24 kHz 的三分之一。这个"三分之一"不是随便取的,而是由"中心对齐 PWM + 每周期采样一次电流"这套经典组合推出来的。

在电机控制里,电流环的采样时刻必须和 PWM 的载波对齐,否则采样到的电流里会混进开关噪声。ODrive 用的是中心对齐(center-aligned)PWM,计数器从 0 涨到 ARR 再降回 0,一个完整周期里电流在"波峰"和"波谷"各稳定一次。理论上你可以在每个波峰和波谷都采一次,那就是 2 倍 PWM 频率;也可以只在波峰采,那就是 1 倍。ODrive 选的是在波峰采样、并且把控制环频率设成 PWM 的 1/3,这样每个控制周期里 PWM 正好走完 3 个完整周期,采样点始终落在同一个相位上,电流纹波的相位是固定的,PI 调节器的相位裕度就好算。

如果你把控制环设成 24 kHz(和 PWM 同频),采样点会一直在波峰附近抖动,电流环的噪声会明显变大;设成 12 kHz 也能跑,但 ODrive 官方在文档里明确说过 8 kHz 是"性能和 CPU 占用的平衡点"。我实测过把CURRENT_MEASURE_PERIOD相关的宏改大改小,8 kHz 确实是最稳的,再往上 CPU 负载会吃掉太多,再往下电流环带宽就不够用了。

1.2 从 8 kHz 反推:一个控制周期里到底发生了什么

8 kHz 意味着每个控制周期是 125 微秒。这 125 微秒里,ODrive 要干完这些事:

  • 读取三相电流采样值(ADC 已经由定时器触发采好了,这里只是搬运)
  • 读取编码器位置(SPI 或 ABZ,取决于编码器类型)
  • 跑电流环 PI,算出 Vd、Vq
  • 跑位置环/速度环(这两个不是每个周期都跑,后面会讲)
  • 做反 Park 变换,算出三相占空比
  • 把占空比写进定时器的 CCR 寄存器

125 微秒对 STM32F405(ODrive 用的主控)来说,168 MHz 主频,大概 21000 个时钟周期。听起来很多,但电流环里涉及浮点运算、三角函数(反 Park 要用 sin/cos),如果不用查表或者 CORDIC,光算三角函数就能吃掉一大半。所以 ODrive 在时基设计上做了很多"省时间"的取舍,这些取舍直接决定了它的定时器配置方式。

提示:如果你自己写电机控制固件,别一上来就追求 20 kHz 控制环。先把 8 kHz 跑稳,把电流采样和 PWM 的相位对齐做对,比盲目提高频率有用得多。

1.3 定时器时基在整个固件里的位置

ODrive 固件里其实有不止一个"时间源",容易混淆,我列一下:

时间源用途频率/精度
TIM8高级定时器,产生 PWM + 触发 ADC + 触发控制环中断24 kHz PWM,8 kHz 中断
SysTick系统滴答,给 HAL 库做延时、超时1 kHz
TIM2/TIM5通用定时器,做编码器接口或辅助计时视配置而定
DWT数据观察点,做微秒级性能测量CPU 周期级

真正驱动控制环的是 TIM8,不是 SysTick。SysTick 只负责"慢速"的事情,比如通信超时、状态机轮询。很多人第一次看源码会以为控制环挂在 SysTick 上,那是完全跑不动的——SysTick 默认 1 kHz,连电流环的边都摸不到。

2. 从时钟树一路追到 TIM8 的寄存器配置

2.1 先搞清楚 TIM8 挂在哪条总线上、时钟是多少

STM32F405 的时钟树里,TIM8 挂在 APB2 总线上。ODrive 的SystemClock_Config()里把系统时钟配到 168 MHz,APB2 预分频器设成 2,所以 APB2 的时钟是 84 MHz。但这里有个 STM32 的"坑":当 APB 预分频系数不为 1 时,定时器的时钟会自动倍频。也就是说,TIM8 的实际计数时钟不是 84 MHz,而是168 MHz。

这个倍频规则写在参考手册的时钟树章节里,但很多人配定时器的时候会忘掉,导致算出来的 PWM 频率差一倍。我第一次算的时候就是按 84 MHz 算的,结果示波器一测,PWM 频率只有 12 kHz,排查了半天才发现是倍频没算进去。

所以 TIM8 的输入时钟是 168 MHz,这是后面所有分频计算的基准。

2.2 中心对齐模式下,ARR 和 PWM 频率的关系

ODrive 要的是 24 kHz 中心对齐 PWM。中心对齐模式下,计数器先向上数到 ARR,再向下数回 0,一个完整周期是2 * ARR个计数。所以:

PWM 频率 = TIM8_CLK / (2 * ARR * (PSC + 1))

ODrive 里 PSC 设成 0(不分频),那么:

24000 = 168000000 / (2 * ARR) ARR = 168000000 / (2 * 24000) = 3500

所以TIM8->ARR = 3500。这个数字你在源码里能直接找到,对应的是PWM_ARR或者类似的宏。我建议你自己拿计算器按一遍,比死记硬背强。

2.3 8 kHz 中断是怎么从 24 kHz PWM 里"分"出来的

关键来了:PWM 是 24 kHz,但控制环中断是 8 kHz,正好是三分之一。ODrive 的做法不是再开一个定时器,而是在 TIM8 的更新中断里做计数分频。

TIM8 中心对齐模式下,计数器到 0 和到 ARR 都会产生更新事件(如果开了对应的中断源)。ODrive 只在一个方向上触发中断,所以中断频率是 24 kHz。然后在中断服务函数里,用一个静态计数器:

static uint32_t control_loop_count = 0; if (++control_loop_count >= 3) { control_loop_count = 0; // 这里才真正跑控制环 axis->run_control_loop(); }

这样每 3 次中断跑一次控制环,24 kHz / 3 = 8 kHz。这个"3"就是 PWM 和控制环之间的分频比。

注意:这个分频计数器必须是静态的或者全局的,不能是局部变量,否则每次进中断都被重置,控制环就变成 24 kHz 了。我见过有人抄代码的时候把static漏了,结果电机一上电就啸叫,查了两天才发现是这个问题。

2.4 ADC 触发和中断触发的时序配合

TIM8 不只是产生中断,它还要触发 ADC 采样。ODrive 用的是 TIM8 的 TRGO(Trigger Output)去触发 ADC,触发点选在计数器等于某个比较值的时候,保证采样时刻正好落在电流稳定的窗口里。

具体来说,TIM8 的CCR4或者某个比较通道被用来做 ADC 触发源,触发事件选在"计数器向上计数到 CCR 时"。这个时刻对应的是 PWM 波形的中间点,也就是电流最稳的时候。ADC 采完之后产生转换完成中断,或者用 DMA 把数据搬走,控制环中断里直接读结果。

这里有个时序细节:ADC 采样和转换需要时间,STM32F405 的 ADC 在 12 位精度下大概几微秒。如果控制环中断来得太早,ADC 还没转换完,读到的就是旧数据。ODrive 的做法是让 ADC 触发稍微提前于控制环中断,给转换留出时间。这个提前量在源码里是通过比较值来调的,不是固定的。

3. 中断服务函数里那点事:谁先跑、谁后跑

3.1 中断优先级:为什么电流环必须最高

ODrive 固件里,TIM8 的中断优先级是最高的(数值最小)。这不是随便设的,因为电流环对抖动极其敏感。如果电流环中断被别的中断打断,哪怕只延迟几微秒,电流波形就会畸变,严重的时候会触发过流保护。

STM32F405 的 NVIC 支持抢占优先级和子优先级。ODrive 把 TIM8 设成抢占优先级 0(最高),其他中断比如 UART、USB、CAN 都设得比它低。这样即使通信中断正在处理,电流环中断也能立刻抢占。

我实测过把 TIM8 优先级调低,电机在低速的时候会有明显的"咔咔"声,就是电流环被延迟导致的。所以这个优先级设置千万别乱改。

3.2 控制环里的"分频"逻辑:不是所有环都跑 8 kHz

虽然中断是 8 kHz,但 ODrive 里的控制环是分层的:

  • 电流环:每个 8 kHz 周期都跑,这是最内环,必须最快
  • 速度环:通常跑 1 kHz 左右,通过count % 8来分频
  • 位置环:更低,可能 100 Hz 到 1 kHz,看配置

源码里能看到类似这样的结构:

if (count % current_meas_period == 0) { // 电流环 } if (count % (current_meas_period * 8) == 0) { // 速度环 } if (count % (current_meas_period * 80) == 0) { // 位置环 }

这里的current_meas_period就是那个"3"的倒数关系对应的周期数。这种分层设计的好处是:内环跑得快保证响应,外环跑得慢减少 CPU 占用,而且外环的采样周期是内环的整数倍,相位关系固定,不会出现"拍频"。

3.3 中断里的浮点运算和 FPU 使用

STM32F405 有硬件 FPU,单精度浮点。ODrive 的电流环里大量用 float,编译的时候必须开-mfpu=fpv4-sp-fp和-mfloat-abi=hard,否则浮点运算会走软件模拟,慢几十倍。

但即使有 FPU,中断里也不能随便用sinf()、cosf()这种库函数,因为它们内部可能有分支、查表、甚至调用慢速路径。ODrive 的做法是预先算好 sin/cos 表,或者用快速近似算法。反 Park 变换需要角度,角度来自编码器,每个周期都在变,所以要么实时算,要么用查表加插值。

我自己的经验是:如果控制环里发现某个函数耗时异常,先用 DWT 或者 GPIO 翻转法测一下,别猜。ODrive 源码里就有用 GPIO 翻转做性能标记的地方,示波器一挂就知道哪个环节慢。

4. 实测中容易踩的时基相关坑

4.1 定时器时钟倍频算错,PWM 频率差一倍

前面提过,APB 预分频不为 1 时定时器时钟倍频。这个坑我踩过两次,第一次是算 PWM 频率,第二次是算死区时间。死区时间也是基于定时器时钟算的,如果时钟算错,死区要么太大(效率低、波形畸变),要么太小(上下管直通,炸管)。

正确的做法是:配完时钟树之后,先写个测试代码,让定时器输出一个已知频率的方波,用示波器或者逻辑分析仪测一下,确认时钟频率和计算一致,再去配 PWM 和死区。

4.2 中断里做浮点除法,耗时爆炸

浮点除法在 FPU 上虽然比软件快,但仍然比乘法慢很多。ODrive 的电流环里,PI 调节器的积分项涉及除法(除以采样周期),如果每个周期都做一次除法,累积起来很可观。优化方法是把除法提前算成乘法:1/Ts预先算好,中断里只做乘法。

这个技巧在写任何实时控制固件时都适用。我见过有人把1.0f / 8000.0f写在中断里,编译器如果没优化,每次都要做一次除法,白白浪费几十个周期。

4.3 编码器读取和电流环的时序冲突

如果编码器走 SPI,SPI 传输需要时间。ODrive 里编码器读取不是在电流环中断里同步做的,而是用 DMA 或者异步方式,中断里只读已经准备好的结果。如果强行在 8 kHz 中断里同步读 SPI,SPI 时钟不够快的话,一个周期根本读不完,控制环就会被拖慢。

这个坑在换用高分辨率编码器的时候特别明显。分辨率越高,SPI 要传的位数越多,时间越长。解决办法要么提高 SPI 时钟,要么降低编码器读取频率(位置环本来就不需要 8 kHz),要么用 ABZ 接口让定时器硬件解码。

4.4 中断嵌套和栈溢出

TIM8 中断优先级最高,如果它里面调用的函数又触发了更低优先级的中断,就会嵌套。嵌套层数多了,栈会不够用。ODrive 的栈大小是调过的,如果你自己加功能,比如在中断里调用 printf(千万别这么干),栈很容易爆。

排查栈溢出可以用填充法:启动时把栈区域填成特定 pattern,跑一段时间后看还有多少没被覆盖,就知道峰值用量了。

5. 把 8 kHz 时基迁移到自己的项目里

5.1 最小可用的定时器 + 中断骨架

如果你想在自己的 STM32 项目里复现这套 8 kHz 时基,核心步骤是:

  1. 配时钟树,确认 TIMx 的输入时钟(注意倍频)
  2. 配定时器为中心对齐 PWM 模式,算 ARR 和 PSC
  3. 开更新中断,在中断里做 3 分频
  4. 配 ADC 用定时器 TRGO 触发,采样点对齐 PWM 波峰
  5. 设中断优先级为最高
  6. 在分频后的中断里跑控制逻辑

代码骨架大概长这样:

void TIM8_UP_TIM13_IRQHandler(void) { if (TIM8->SR & TIM_SR_UIF) { TIM8->SR = ~TIM_SR_UIF; // 清标志 static uint8_t div = 0; if (++div >= 3) { div = 0; control_loop_isr(); // 8 kHz 控制环 } } }

注意清标志的顺序,先读 SR 再写 SR,这是 STM32 的标准操作,别搞反了。

5.2 控制频率不是越高越好:算一笔 CPU 账

假设你的电流环一次执行需要 5 微秒(含 ADC 读取、PI、反 Park、写 CCR),那么:

  • 8 kHz:每周期 125 微秒,占用 4%
  • 16 kHz:每周期 62.5 微秒,占用 8%
  • 32 kHz:每周期 31.25 微秒,占用 16%

看起来占用不高,但别忘了还有通信、状态机、编码器处理。而且中断本身有进出开销,频率越高开销越大。更重要的是,PWM 频率如果不变,控制环频率提高并不会让电流更平滑,反而可能因为采样点相位变化引入噪声。

所以选控制频率的正确姿势是:先定 PWM 频率(受功率管和电感限制),再根据"每个 PWM 周期采样几次"来定控制频率。ODrive 选 1/3 是一个经过验证的平衡点。

5.3 用 GPIO 翻转法验证时基是否准确

最后分享一个我常用的调试技巧:在控制环中断的入口和出口各翻转一个 GPIO,用示波器看这个 GPIO 的波形。波形的频率就是控制环频率,高电平时间就是中断执行时间。这个方法比用调试器打点直观得多,而且不影响实时性。

如果发现频率不是 8 kHz,先查分频计数器;如果发现高电平时间忽长忽短,说明有更高优先级的中断在抢占,或者中断里有不确定的分支(比如 cache miss,虽然 F4 没有 cache,但有 flash 等待周期)。

这套时基系统看起来简单,但它是整个 ODrive 固件能稳定跑 8 kHz 控制环的地基。把定时器时钟、分频、中断优先级、ADC 触发这几件事理清楚,后面看 FOC 算法和状态机就会顺很多。我在实际项目里迁移这套结构的时候,最大的体会是:先把时基跑通、用示波器验证过,再去写控制逻辑,否则后面出了问题你根本分不清是算法错了还是时基歪了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询