1. 电流检测在 FOC 里到底卡在哪一步
搞过 simpleFOC 的人多半有过这种体验:电机能转,速度环也能跑,但一开电流环就抖得厉害,或者干脆原地嗡嗡响不转。十有八九,问题不在 PID 参数,而在电流检测这一环没打通。simpleFOC 的电流环对采样时机极其敏感——它需要在 PWM 的特定时刻采到相电流,早了是开关噪声,晚了电流已经换向,采到的值根本不能用。而这一切的起点,就是定时器触发 ADC 采样这条链路。
这篇文章要拆的就是这条链路:从 STM32 的定时器怎么产生触发信号,到 ADC 怎么在正确的时刻完成采样,再到 simpleFOC 代码里这些配置是怎么落地的。关键词里的 simpleFOC、电流检测、定时器、ADC 采样、STM32 这几个词,基本就是整条链路的骨架。适合已经能让电机开环转起来、准备上电流环的玩家,也适合想搞清楚"为什么我的电流采样总是不对"的调试者。如果你还在纠结 PWM 怎么输出,那建议先把开环跑通再来看这篇。
我先把结论摆前面:simpleFOC 的电流检测能不能用,80% 取决于采样时刻是否落在 PWM 中心对齐模式下的计数器谷底(或峰值),以及ADC 触发源是否真的接到了那个时刻。剩下的 20% 才是运放电路和校准。很多人一上来就调电路、换采样电阻,其实方向就错了。
2. 为什么偏偏要用定时器去触发 ADC
2.1 软件触发采样为什么在 FOC 里行不通
最直觉的做法是在代码里直接调analogRead(),或者手动启动一次 ADC 转换。开环测试时这么干没问题,因为电流值准不准无所谓。但一旦进电流环,问题就来了:FOC 的电流环跑在几 kHz 到几十 kHz 的频率上,每次 PWM 周期都要采一次电流。如果你在中断里用软件触发,采样时刻完全取决于中断什么时候进、代码执行到哪一行,抖动可能有好几微秒。
几微秒听起来不多,但电机 PWM 周期可能只有 50 微秒(20kHz),电流在这段时间里变化剧烈。采样点漂移几微秒,采到的电流相位就偏了,反馈给 PID 的就是错误信息,环路自然震荡。更麻烦的是,软件触发无法保证每次都在 PWM 的同一个相位点采样,采样值里混入了开关噪声,信噪比一塌糊涂。
2.2 硬件触发解决的是"确定性"问题
定时器触发 ADC 的本质,是把"什么时候采样"这件事从软件手里夺过来,交给硬件自动完成。STM32 的定时器可以在计数器到达特定值时,自动产生一个触发信号(TRGO),这个信号直接连到 ADC 的外部触发输入。整个过程不需要 CPU 参与,采样时刻由硬件保证,每个 PWM 周期都精确落在同一个位置。
这就是确定性。对 FOC 来说,确定性比精度还重要。因为电流环是个闭环,采样时刻的抖动会直接变成反馈噪声,而反馈噪声在闭环里会被放大。硬件触发把抖动压到了纳秒级,这是软件触发永远做不到的。
2.3 中心对齐模式下"谷底采样"的物理意义
这里要讲一个关键概念。STM32 的高级定时器(比如 TIM1)支持中心对齐模式,计数器先向上数到 ARR,再向下数回 0,形成对称的三角波。simpleFOC 默认就用这个模式驱动 PWM,因为它生成的 PWM 波形对称,谐波特性好。
在中心对齐模式下,计数器在谷底(=0)和峰值(=ARR)时,PWM 输出处于一个特殊状态。以谷底为例,此时所有下桥臂都导通(或者根据互补配置),相电流正处于一个"稳定窗口"——开关动作刚结束,电流已经稳定下来,但还没开始下一次换向。这个窗口就是采样的黄金时刻。
提示:谷底采样和峰值采样在 simpleFOC 里都能用,但要注意你的电流采样电路是放在下桥臂还是相线上。下桥臂采样电阻方案通常配谷底采样,因为此时下桥臂导通,采样电阻上有电流流过。
理解这一点,你就明白为什么"定时器触发"不是随便找个时刻触发就行——触发点必须和你的采样电路拓扑匹配。配错了,采到的永远是噪声或者零。
3. STM32 定时器到 ADC 的触发链路拆解
3.1 TRGO 信号是怎么产生的
STM32 的定时器有一个叫 TRGO(Trigger Output)的输出,它可以被配置成多种事件源:更新事件、比较匹配、使能信号等。在 simpleFOC 的场景里,我们通常把 TRGO 配置成更新事件(Update Event)。中心对齐模式下,更新事件发生在计数器到达谷底(下溢)和峰值(上溢)时,具体取决于配置。
配置代码大概长这样(以 TIM1 为例,HAL 库风格):
// 定时器基础配置:中心对齐,20kHz htim1.Init.CounterMode = TIM_COUNTERMODE_CENTERALIGNED1; htim1.Init.Period = 4200 - 1; // 假设 168MHz 时钟,中心对齐下 PWM 频率 = 168M/(2*4200) = 20kHz htim1.Init.Prescaler = 0; // 关键:配置 TRGO 源为更新事件 TIM_MasterConfigTypeDef sMasterConfig = {0}; sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig);TIM_TRGO_UPDATE这一行就是整条链路的起点。它告诉定时器:"每次更新事件发生时,往 TRGO 线上发一个脉冲。"这个脉冲会一路走到 ADC。
3.2 ADC 的外部触发怎么接
ADC 这边要做的,是把自己的启动转换源从"软件"改成"外部触发",并且指定触发源是哪个定时器。STM32 的 ADC 外部触发是通过EXTSEL位域选择的,不同定时器对应不同的编码。
// ADC 配置:外部触发,上升沿 hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T1_TRGO; hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING;ADC_EXTERNALTRIGCONV_T1_TRGO就是"TIM1 的 TRGO"这个触发源。注意这里有个容易踩的坑:不同 STM32 系列(F1、F4、G4、H7)的触发源编码不一样,F4 上叫ADC_EXTERNALTRIGCONV_T1_TRGO,G4 上可能叫ADC_EXTERNALTRIG_T1_TRGO,移植代码时一定要查对应型号的参考手册,别直接抄。
3.3 从触发到转换完成的时间账
触发信号发出后,ADC 并不是瞬间就出结果。中间有几段延迟要算清楚:
| 阶段 | 典型耗时 | 说明 |
|---|---|---|
| 触发信号传播 | 几个时钟周期 | 硬件内部,可忽略 |
| ADC 采样时间 | 可配置,1.5~480 周期 | 采样电容充电,电阻大就要设长 |
| 逐次逼近转换 | 12 位约 12 个 ADC 时钟 | 固定开销 |
| 结果写入 DR | 1~2 周期 | 可忽略 |
假设 ADC 时钟 42MHz,采样时间设为 15 周期,转换 12 周期,总共约 27 个 ADC 时钟,约 0.64 微秒。这个时间必须远小于 PWM 周期,否则转换还没完成,下一次触发又来了。20kHz PWM 周期 50 微秒,0.64 微秒完全够用。但如果你的采样电阻很大、采样时间设到 480 周期,那就要重新算账了。
注意:采样时间不是越长越好。采样时间太长,虽然充电更充分,但转换总时间变长,可能错过下一个触发。一般 1k 以下的采样电阻配 15~28 周期就够了。
4. simpleFOC 代码里这条链路是怎么落地的
4.1 电流检测的三种模式与触发的关系
simpleFOC 支持三种电流检测方式:低侧采样(下桥臂电阻)、高侧采样、相线采样。这三种方式对触发时刻的要求完全不同,这是很多人配置失败的根源。
低侧采样:采样电阻在三个下桥臂到地之间。只有下桥臂导通时,电阻上才有电流。中心对齐谷底时,如果配置成下桥臂全导通,三个电阻上都有电流,可以一次采三个。但要注意死区时间——如果谷底时上下桥臂正在换向,采样窗口可能被死区吃掉。
相线采样:电阻串在电机相线上,任何时候都有电流。这种方案对触发时刻不敏感,但需要双向采样电路(因为电流有正负),运放要加偏置。
simpleFOC 的InlineCurrentSense和LowSideCurrentSense两个类,内部对触发时机的假设是不一样的。用错类,采样值就是错的。
4.2 定时器配置在 simpleFOC 里的实际写法
simpleFOC 的BLDCMotor和BLDCDriver6PWM会接管定时器配置。以 STM32F405 为例,驱动初始化时会设置中心对齐和 TRGO:
// simpleFOC 驱动内部大致逻辑(简化) // 配置 TIM1 为中心对齐,设置 TRGO 为更新事件 // 配置 ADC 为外部触发,源为 T1_TRGO // 使能 ADC 的 DMA,把转换结果搬到内存关键点在于:simpleFOC 默认假设你的 ADC 触发源已经正确接到了驱动用的定时器。如果你用的是 TIM1 驱动 PWM,但 ADC 触发源配成了 TIM2,那采样时刻就完全错位了。这个错误在代码里不会报,但电流环一定跑不起来。
4.3 DMA 搬运与采样数据的读取
ADC 转换完成后,结果放在 DR 寄存器里。如果不用 DMA,就得在中断里读,增加 CPU 负担。simpleFOC 推荐用 DMA,让 ADC 每次转换完自动把数据搬到指定数组。
// ADC DMA 配置 HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buffer, 3); // 三个通道三个通道对应三相电流。DMA 配置成循环模式,每个 PWM 周期自动更新一次adc_buffer。simpleFOC 的电流检测类会从这个 buffer 里读值,减去偏置,乘以增益,得到实际电流。
这里有个细节:DMA 搬运和 ADC 转换是流水线关系。第一次触发时 buffer 里是旧值,要等一两个周期才稳定。所以初始化后不要立刻进电流环,先空转几个周期让数据稳定。
5. 采样时刻配错会看到什么现象
5.1 电流波形上的典型症状
采样时刻配错,最直接的表现是电流波形异常。用示波器看采样值(可以通过 DAC 输出或者串口打印),正常应该是平滑的正弦波。如果看到:
- 波形削顶或畸变:采样点落在了开关瞬间,采到了尖峰噪声。
- 波形有周期性凹陷:采样窗口被死区时间吃掉了,某些时刻采到零。
- 波形相位偏移:触发时刻和电流相位不匹配,反馈滞后。
- 波形完全乱掉:触发源接错了定时器,采样和 PWM 根本不同步。
我遇到过最坑的一次,是 ADC 触发源配成了软件触发,但代码里又开了定时器中断去启动转换。结果采样时刻跟着中断走,中断又被其他任务延迟,电流环抖得像筛糠。查了两天才发现是触发源没改。
5.2 用调试手段定位触发问题
定位这类问题,有几个实用手段:
第一,把 TRGO 信号引到一个空闲 GPIO 上,用示波器看它和 PWM 波形的相对位置。TRGO 应该在谷底(或峰值)出现,如果位置不对,说明定时器配置有问题。
第二,在 ADC 转换完成中断里翻转一个 GPIO,看这个翻转点和 PWM 的关系。如果翻转点漂移,说明触发不稳定。
第三,把采样值通过 DAC 输出,直接看波形。这是最直观的,能一眼看出采样质量。
提示:STM32 的 DAC 输出配合示波器,是调试电流采样的神器。把采样值实时输出,比串口打印快得多,能看到真实的波形细节。
5.3 一个容易被忽略的坑:ADC 时钟分频
STM32 的 ADC 时钟来自 APB2 分频,分频系数配错会导致 ADC 时钟超频或太慢。超频会转换出错,太慢会导致转换时间超过 PWM 周期。F4 系列 ADC 时钟最高 36MHz,G4 可以到 60MHz,配置时一定要查手册。这个坑很隐蔽,因为代码能跑,但采样值就是不对。
6. 从零配通这条链路的实操顺序
6.1 先让定时器单独跑起来
不要一上来就接 ADC。先把定时器配好,用示波器确认 PWM 波形正常,中心对齐模式生效,频率对得上。然后配置 TRGO,把 TRGO 引到 GPIO,确认它在正确的位置出现。这一步做扎实,后面省一半时间。
6.2 再单独验证 ADC 外部触发
定时器确认无误后,配 ADC 外部触发,先不接 DMA,在转换完成中断里翻转 GPIO。用示波器看翻转频率是否等于 PWM 频率。如果频率不对,说明触发没接上,回去查EXTSEL配置。
6.3 最后接上 DMA 和 simpleFOC
前两步都通了,再接 DMA,把三个通道的数据搬出来。用串口打印原始值,手动转动电机,看数值是否随转动变化。确认无误后,再初始化 simpleFOC 的电流检测类,进电流环。
这个顺序的核心逻辑是:每次只验证一个环节。定时器、触发、ADC、DMA、simpleFOC,五个环节串在一起,一次全配好,出问题根本不知道是哪一环。分开验证,每步都有明确的成功标准。
6.4 校准偏置和增益不能省
链路通了不代表数值准。ADC 有偏置,运放有失调,采样电阻有误差。simpleFOC 提供了校准流程:电机不转时采一批值算零偏,然后通已知电流算增益。这一步不做,电流环的反馈就是错的,PID 再调也没用。
校准时的经验:零偏要在电机静止、PWM 关闭或占空比为零时采,避免开关噪声污染。增益校准如果有条件,用外部电流表对比,比理论计算靠谱。
7. 几个实际调试中攒下的经验
第一个经验:采样电阻的选型要和采样时间匹配。电阻越大,信号越强,但 RC 时间常数越大,需要的采样时间越长。我见过有人用 0.1 欧的电阻配 1.5 周期采样时间,结果采样电容根本没充满,采到的值偏小。一般 0.01~0.05 欧配 15~28 周期比较稳妥。
第二个经验:死区时间会吃掉采样窗口。中心对齐谷底时,如果死区时间设得太大,上下桥臂换向还没完成,采样窗口就被占了。死区时间要尽量小(够用就行),给采样留出空间。
第三个经验:DMA 缓冲区要放在非缓存区或者配置好缓存一致性。在 H7 这类带缓存的芯片上,DMA 写的数据可能还在缓存里没落到内存,CPU 读到的就是旧值。这个坑在 F4 上不明显,换到 H7 就冒出来了。
第四个经验:电流环的带宽不要超过采样频率的十分之一。采样 20kHz,电流环带宽最多 2kHz。设太高,采样延迟会直接导致震荡。这是控制理论的基本约束,但实际调的时候很容易忘。
第五个经验:先用开环验证采样链路,再进闭环。开环时给固定电压,电流应该是稳定的。如果开环下采样值就乱跳,那肯定是链路问题,别急着调 PID。
8. 关于触发时刻选择的一点个人看法
谷底采样和峰值采样之争,其实没有绝对答案,关键看你的硬件拓扑。低侧采样配谷底,是因为谷底时下桥臂导通,采样电阻有电流。但如果你用的是相线采样,谷底峰值都行,因为相线电流连续。simpleFOC 默认的配置偏向低侧谷底,但代码里留了调整空间。
我个人的习惯是:先用默认配置跑通,确认链路无误后,再根据实际波形微调触发时刻。有时候把触发点稍微提前或延后几个计数周期,采样质量会有明显改善。这个微调没有公式,靠示波器看波形,看哪个位置采到的值最干净、最稳定。
还有一点,不同 STM32 系列的定时器和 ADC 联动细节有差异。F1 的 ADC 触发源选择比 F4 少,G4 又多了些新特性。移植 simpleFOC 到新芯片时,这部分一定要重新查手册,别指望代码直接能跑。我移植到 G4 时就因为触发源编码不同,卡了半天。
最后说个心态问题:电流检测这条链路,配通一次之后,后面就顺了。但第一次配,尤其是没有示波器的时候,会非常痛苦。建议至少借一台示波器,或者用 DAC 输出配合声卡输入这种土办法,也要能看到波形。盲调这条链路,效率太低。