
做电机控制的兄弟应该都清楚电流采样是整个FOC矢量控制的眼睛采样精度和实时性直接决定转矩脉动、噪声和效率。最近在折腾Odrive方案时我把STM32的ADCDMATIM8这条采样链路彻底捋了一遍从CubeMX配置到寄存器级调试都有涉及。这篇就专门聊聊怎么把这套组合玩明白解决采样抖动、相位延迟和数据错位这些让人头大的问题。Odrive这类高性能电机控制器对电流采样的要求核心就两条一是精度要够二是延迟要低。精度不够闭环算出来的力矩就是歪的电机跑起来嗡嗡响延迟太高高速转起来电流相位就对不上轻则效率暴跌重则直接过流炸管。用STM32的ADCDMATIM8组合本质上就是让硬件自己把采样时刻控制、数据搬运、PWM同步这三件事做完CPU只负责在DMA中断里取结果算FOC把延迟压到微秒级。这个方案不只是Odrive能用凡是自己画板子做FOC驱动器的场景都能复用适合正在调板子、写FOC库或者被采样噪声折磨的DIY玩家和嵌入式工程师。1. 电流采样链路怎么设计才对先搞清楚整套逻辑1.1 电机电流采样的核心痛点先说痛点。三相永磁同步电机或者无刷直流电机的相电流不是你想什么时候采就什么时候采的。逆变器是PWM方波驱动的电流在PWM周期内是波动的尤其在下桥臂续流阶段电流波形会有一个明显的纹波。Omron、TI的参考设计里都会强调采样点必须放在PWM波形的特定位置通常是PWM计数器的峰值或者谷值也就是下桥臂导通的中点附近。这个时刻电流纹波最小采样值最接近真实平均值。如果采样时刻不对或者采样窗口太短采到的电流就等于准确的信号上叠加了一个很大的纹波噪声。这个噪声进了电流环PI调节器会把它当成真实误差来响应结果就是电流环输出抖动电机噪声变大甚至引发振荡。1.2 三种采样方式的对比选择电流采样有三种常见方式低端采样、高端采样和inline采样。低端采样最简单在逆变器下桥臂MOS管的源极和地之间串采样电阻成本低、共模电压小但问题是只能在下桥臂导通时采样而且无法感知相电流过零附近的真实电流。高端采样在母线正极和上桥臂之间采样能覆盖整个PWM周期但共模电压接近母线电压放大器选型麻烦。inline采样直接在电机相线上采样理论上最准但对放大器的共模抑制比要求极高。Odrive方案和大部分DIY FOC板子用的都是低端采样搭配三电阻或者单电阻采样。三电阻能同时采三相电流适合母线电压较高的场景单电阻省成本但需要在一个PWM周期内做两次采样然后重构三相电流算法复杂点。我这次用的是双电阻加母线重构的方案后面细说。1.3 TIM8ADCDMA的经典配合逻辑STM32的ADCDMATIM8这套组合本质上就是个硬件级的定时触发-采样-搬运流水线。TIM8在这里不只是产生PWM更关键的是它的更新事件Update Event和比较匹配事件Compare Match可以作为ADC的触发源。比如配置TIM8的TRGO事件连接到ADC的触发输入当PWM计数到峰值或者谷值时硬件自动触发一次ADC转换不需要CPU干预。这样采样点就和PWM中心严格对齐相位误差基本为零。ADC转换完成后结果通过DMA直接搬到内存缓冲区DMA可以用循环模式Circular Mode每次转换完自动搬一次不需要软件反复配置外设。CPU只用在DMA传输完成中断里读取最新的三个采样值然后立刻丢给Clarke变换和Park变换去算电流环。整个过程从采样到数据就绪延迟大概在几百纳秒到一两个微秒之间对于常见的20kHz电流环来说绰绰有余。2. 关键就是这几步分阶段讲解STM32实现细节2.1 第一步确定定时器配置TIM8的PWM模式与触发点选择TIM8是一个高级定时器带互补PWM输出和刹车功能非常适合驱动三相逆变器。我们要做的是先让TIM8输出6路互补PWM带死区用来控制三相全桥的6个MOS管。与此同时TIM8的TRGO事件要配置为Update Event或者OC4REF。这里有个细节如果TRGO选择的是Update Event那ADC触发点就是PWM周期开始计数器上溢或下溢对应的采样时刻在PWM的边沿实际上是在死区附近这时候下桥臂续流二极管还在导通采样电流有一定误差。如果想更精细可以选择OC4REF作为TRGO然后把TIM8的通道4配置为PWM模式2比较值设为PWM周期的一半这样ADC触发点就能精确落在PWM的中间点也就是电流纹波最小的时刻。从代码上看在CubeMX里配置TIM8时预分频器Prescaler 0时钟源直接接APB2定时器时钟通常是72MHzF103或168MHzF407。Counter Period PWM频率对应的计数值比如20kHz PWM72MHz时钟下period 3600。通道1到通道3配置为PWM Generation CH1/CH2/CH3通道4配置为PWM Generation CH4或者不输出。主从模式选择Enable Timer Master/Slave Mode触发输出TRGO选择Update Event或OC4REF。我用的是OC4REF对齐到PWM中间点的方式实测下来电流波形上的毛刺比Update Event触发小不少。如果你的应用对成本不敏感建议优先用这个。2.2 第二步ADC配置采样时钟、通道、采样时间与对齐方式ADC的精度不只是看分辨率还得看采样时钟和采样时间。STM32的ADC是逐次逼近型SAR采样时间越长采样电容充电越充分精度越高但采样窗口太长会占用PWM周期时间。以F103为例ADC时钟最高14MHz采样时间可以配置为1.5、7.5、13.5、28.5、41.5、55.5、71.5、239.5周期。我一般用28.5或55.5对于电流采样这种中高速场景28.5周期已经够用精度在12位下能做到±2个LSB以内。ADC通道选择上三相电流采样一般用注入组或者规则组。规则组按顺序扫描采样适合同步采集多个通道但要配合DMA连续传输。关键参数ADC模式独立模式即可。ADC时钟配置为12MHz左右不要超过14MHz否则线性度变差。采样时间28.5周期总转换时间约12.5 28.5 41个周期约3.4微秒。对齐方式右对齐。采样通道数3个对应三路电流如果你用的是单电阻重构则是1到2个通道。触发方式外部触发选择TIM8触发。这里有个容易忽略的点ADC的采样通道顺序要和DMA缓冲区里的顺序一致。我习惯把A相、B相、母线电压的顺序固定然后DMA缓冲区里依次排这样取数据时不用再重排。2.3 第三步DMA配置循环模式、数据宽度与中断DMA是整个链路里最省心也最容易出bug的地方。配置时主要关注几个参数数据方向是外设到内存外设地址是ADC的数据寄存器地址内存地址是你的缓冲区首地址数据宽度是半字16位内存地址自增外设地址固定传输模式是循环模式。循环模式非常关键。规则组扫描3个通道转换完后会产生一个DMA传输请求每次请求传输3个半字到一个数组里循环模式会自动重复这一过程。CPU只用在DMA传输完成中断中处理数据不用管下半场怎么重新启用DMA。中断方面我习惯打开DMA传输完成中断Transfer Complete用它的回调函数里做FOC电流环的ADC数据就绪标志位。这样主循环跑速度环和位置环PWM中断或者DMA中断里跑电流环时序清晰。// DMA配置示例初始化后开启 static uint16_t adc_buffer[3]; // 存放A/B相电流和母线电压 HAL_DMA_Start_IT(hdma_adc1, (uint32_t)ADC1-DR, (uint32_t)adc_buffer, 3); HAL_ADC_Start_DMA(hadc1, (uint32_t *)adc_buffer, 3);缓冲区数组的大小和DMA传输次数必须严格等于通道数多一个少一个都会导致数据错位。这个坑我在初调的时候踩过后面会细说。3. 实际操作中我是怎么配置的完整CubeMX参数表与代码3.1 CubeMX引脚分配与时钟树规划先搭工程。把TIM8的PWM引脚对应到MOS驱动芯片的输入上例如PA6、PA7、PB0作为上桥臂的CH1/CH2/CH3PC6、PC7、PC8作为下桥臂的互补输出CH1N/CH2N/CH3NPB1作为HALL或者编码器接口PC0-PC3作为ADC输入。在CubeMX的时钟树里让APB2定时器时钟跑满这样PWM频率可以尽量高。F103的话就是72MHzF407可以168MHz。ADC时钟独立设置压到12MHz左右。3.2 TIM8与ADC/DMA联动参数清单以下是我实测稳定运行的配置表可直接参考参数项配置值说明PWM频率20kHz周期80us电流环频率20kHzTIM8计数周期359972MHz / 20kHz - 1死区时间500ns根据MOS管Qg和驱动芯片调整CH4比较值1800对齐到PWM中间点触发ADCADC触发源TIM8 TRGO (OC4REF)采样点居中ADC采样时间28.5周期精度/速度折中ADC通道数3A相/B相/母线电压DMA模式循环模式自动重新装载数据宽度半字(16位)ADC数据寄存器16位有效3.3 核心代码实现ADC回调里读取电流代码部分放一个最核心的回调函数。DMA传输完成中断会自动调用这个函数里面读ADC值并对原始数据进行处理。#define ADC_12BIT_MAX 4095 #define ADC_VREF 3.3f #define CURRENT_SHUNT_RESISTOR 0.005f #define OPAMP_GAIN 50.0f float phase_a_current, phase_b_current, bus_voltage; void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) { if (hadc-Instance ADC1) { // 原始值转换ADC值 - 电压 - 电流 float adc_voltage_a (float)adc_buffer[0] / ADC_12BIT_MAX * ADC_VREF; float adc_voltage_b (float)adc_buffer[1] / ADC_12BIT_MAX * ADC_VREF; float adc_voltage_bus (float)adc_buffer[2] / ADC_12BIT_MAX * ADC_VREF; // 中点偏置校准一般是放大器输出在零电流时的电压约1.65V phase_a_current (adc_voltage_a - vref_offset) / (CURRENT_SHUNT_RESISTOR * OPAMP_GAIN); phase_b_current (adc_voltage_b - vref_offset) / (CURRENT_SHUNT_RESISTOR * OPAMP_GAIN); // C相利用基尔霍夫电流定律重构 float phase_c_current -phase_a_current - phase_b_current; // 这里已经拿到三相电流可以接Clarke/Park变换和电流PI环 motor_foc_current_loop(phase_a_current, phase_b_current, phase_c_current, bus_voltage); } }这里有个非常重要的点运算放大器的输出不等于相电流直接对应值因为存在一个共模电压和偏置。零电流时运放输出通常是电源电压的一半比如1.65V采样回来的ADC值需要先减去这个偏置再除以增益和采样电阻的乘积才能得到真实的有符号电流值。偏置值不是固定的会随温度和电源电压漂移所以最好每次上电做一次自校准。3.4 上下电与自检逻辑上电时先给TIM8 PWM输出使能但占空比置为50%死区时间内让电机不转。此时相电流为零采样电路输出就是偏置电压。利用这段等待时间读取ADC数据计算偏置的平均值作为vref_offset。这个逻辑非常实用我每次上电都会跑一遍能明显减少零漂对电流环的影响。4. 调参路上我遇到的坑常见问题与排查技巧4.1 采回来的数据疯狂跳变甚至满量程现象是电流波形全是毛刺或者偶发性地突然跳到一个很大的值。这个问题大部分情况不是电路的问题而是采样时刻不对。排查思路先用示波器看PWM输出和ADC触发信号的时序确认采样点是不是真落在PWM中间。最简单的方法是把ADC触发信号引到一个空闲的引脚上比如用TIM8的TRGO直接映射到MCO引脚或者用一个GPIO来toggle并在中断里抓然后和PWM波形对比相位。检查死区时间内要不要加消隐。下桥臂MOS管开关瞬间采样电阻上会有很大的di/dt尖峰这个尖峰如果不避开ADC会采到完全错误的值。我的做法是通过TIM8的通道4比较值来调整触发点避开死区和开关沿。4.2 DMA数据错位A相电流串到了B相现象是电机低速时抖动用上位机读取电流数据发现A/B相位差不对甚至两个通道波形一样。这个问题的根源是DMA传输次数和ADC转换通道数不匹配。通道数设为3但DMA传输次数却写了4多出来的那次会把下一个通道的值也搬进来。通道数设为3但DMA只启动了2次那就只会更新前两个通道的值。检查时先看缓冲区里数据是否正确按照通道顺序存放。还有一个隐蔽的问题ADC触发的间隔和DMA搬运的速度不匹配。如果PWM频率是20kHz每个PWM周期触发一次ADC每次转换3个通道那DMA每秒需要搬运20k * 3 60k次数据量很小不会造成总线拥塞。但如果和其他外设共用DMA1/DMA2通道优先级配置不当可能会偶发丢数据。我给ADC的DMA通道设置了最高优先级能给其他外设让路的就让。4.3 电流环一使能电机就啸叫啸叫的声音来源于电流环振荡通常是PI参数不合适但也有可能是采样链路延迟太大。采样链路延迟分两部分一是ADC转换本身的延迟配置好的采样时间转换时间二是DMA搬运到CPU读到数据之间的延迟。如果电流环频率太高而采样延迟又大相位裕度就会被吃光系统就振荡。解决思路降低电流环频率从20kHz降到10kHz试试如果啸叫消失就说明延迟占比太大。优化采样时刻让ADC尽量在PWM中间触发但也要注意留出足够时间让运放稳定。检查运放带宽。有些便宜的运放增益带宽积不够在几十纳秒内建立不了足够的精度这时采样到的信号本身就失真换运放是唯一的出路。4.4 ADC参考电压不干净电流采样精度还和ADC参考电压有关。如果你用VDDA作为参考电压而VDDA没有做滤波那么电机启停时母线电压波动会耦合到VDDA上直接导致采样结果抖动。解决办法在VDDA引脚附近加一个10uF钽电容100nF陶瓷电容滤波PCB布局时让电容尽量靠近MCU引脚。对精度要求更高的场景用外部基准芯片比如REF3030提供2.5V或3.0V参考用VREF引脚接入ADC。5. 从Odrive移植到自研板卡时的注意事情5.1 Odrive原版方案的参考价值Odrive原版用的是高端的浮点DSP或者专用栅极驱动芯片它的电流采样链路设计非常讲究。我在移植到STM32平台时并没有全部照搬而是保留了硬件上的抗干扰设计替换了软件层的实现。比如Odrive在模拟前端用了专门的电流检测放大器输出的是以某个参考电压为中心的双极性信号。面对这块需要我们明确设置好ADC的采样范围确保不超出量程。5.2 PCB布局的常见坑软件调通了板子却不行这种情况大多是布局问题。电流采样电路走线要遵循开尔文连接原则采样电阻的两端不要走信号线而要单独引线到运放输入避免地线上的压降影响采样精度。驱动部分的功率地和小信号地要单点连接最好在母线电容负极处汇合。还有一个细节采样电阻要用低感电阻最好是金属合金电阻贴片封装的话2512以上尺寸才合适电感量小很多。5.3 与编码器、HALL的时序配合电流采样除了要和PWM同步还要和编码器零位对齐。虽然电流环本身不依赖编码器读数但电流环算出的力矩最终要通过Park变换作用到转子上如果编码器零位不准力矩方向就是歪的电机会抖动甚至过流。建议在每次上电做一次零位找正让编码器Z相脉冲对应的电角度和A相电流的对应关系被准确地记录下来。6. 进阶玩法软件层面的进一步优化6.1 多重采样平均Multiple Sampling有些高级方案会在一个PWM周期内多次触发ADC然后求平均这样能进一步减少纹波噪声。代价是CPU负担增加。用STM32的注入组功能可以做到类似的效果但实现复杂度会提高。如果电机运行时的噪声在可接受范围内我建议先别上这个。6.2 线性校准两点校准刚才提到零漂校准那只是单点校准。如果电流范围比较大运放的非线性也会引入误差这时可以做两点校准正方向通一个已知电流和反方向通一个已知电流分别记录ADC值然后算出斜率和截距。这个方法我在实验室校准过一版驱动器电流误差从±5%降到了±1%以内对高精度应用很有效。6.3 断开DMA减少CPU负载用PWM中断触发电流环先别急着优化先把基础版本跑通。我刚开始是DMA传输完成中断里直接调FOC电流环后来发现主循环里还要跑速度环、通信、状态机堆在一起优先级很乱。后来我把速度环和位置环放在主循环里跑电流环在DMA中断里跑用信号量同步流程清晰了很多。如果你的MCU资源紧张还可以把电流环频率降到10kHz大多数使用场景其实够用。Odrive商用版本能跑几十kHz是因为用了强得多的处理芯片我们STM32F103这种级别的20kHz电流环已经是比较极限了。7. 最后分享两个小细节一个是采样数据的状态锁定问题。调试时如果发现DMA在传输但读取到的数组有一半是旧数据一半是新数据这是因为没有锁存机制。解决方法是DMA用双缓冲区或者在前半部分传输完成中断里读取数据而不是在处理完成中断里读。另一个是ADC值的符号转换。STM32的ADC只有单极性输入设计时要注意运放的输出范围。如果电流方向是双向的那运放的输出必须偏置到AD转换范围的中点半否则反向电流会直接被削掉。相关内容前面已经展开过这里提醒一句零漂校准的可靠性直接影响双向电流的测量准确度别偷懒跳过。这套ADCDMATIM8的采样方案我先后在无刷云台、小型机械臂关节和两轮自平衡机器人的驱动板上验证过稳定性非常不错。如果你正在调试自己的FOC驱动器建议先按这个链路把采样部分跑通再去做复杂的速度环和位置环。毕竟电流数据不准上层控制做得再好看也都是海市蜃楼。