☰
STM32 ADC+DMA+TIM8实现FOC电流采样:从原理到调参全解析
2026/9/28 16:38:40 网站建设 项目流程

做电机控制的兄弟应该都清楚,电流采样是整个FOC矢量控制的"眼睛",采样精度和实时性直接决定转矩脉动、噪声和效率。最近在折腾Odrive方案时,我把STM32的ADC+DMA+TIM8这条采样链路彻底捋了一遍,从CubeMX配置到寄存器级调试都有涉及。这篇就专门聊聊怎么把这套组合玩明白,解决采样抖动、相位延迟和数据错位这些让人头大的问题。

Odrive这类高性能电机控制器对电流采样的要求,核心就两条:一是精度要够,二是延迟要低。精度不够,闭环算出来的力矩就是歪的,电机跑起来嗡嗡响;延迟太高,高速转起来电流相位就对不上,轻则效率暴跌,重则直接过流炸管。用STM32的ADC+DMA+TIM8组合,本质上就是让硬件自己把"采样时刻控制、数据搬运、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 TIM8+ADC+DMA的经典配合逻辑

STM32的ADC+DMA+TIM8这套组合,本质上就是个硬件级的"定时触发-采样-搬运"流水线。

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定时器时钟,通常是72MHz(F103)或168MHz(F407)。
  • Counter Period = PWM频率对应的计数值,比如20kHz PWM,72MHz时钟下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/CH3,PC6、PC7、PC8作为下桥臂的互补输出CH1N/CH2N/CH3N,PB1作为HALL或者编码器接口,PC0-PC3作为ADC输入。

在CubeMX的时钟树里,让APB2定时器时钟跑满,这样PWM频率可以尽量高。F103的话就是72MHz,F407可以168MHz。ADC时钟独立设置,压到12MHz左右。

3.2 TIM8与ADC/DMA联动参数清单

以下是我实测稳定运行的配置表,可直接参考:

参数项配置值说明
PWM频率20kHz周期80us,电流环频率20kHz
TIM8计数周期359972MHz / 20kHz - 1
死区时间500ns根据MOS管Qg和驱动芯片调整
CH4比较值1800对齐到PWM中间点,触发ADC
ADC触发源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 采回来的数据疯狂跳变,甚至满量程

现象是电流波形全是毛刺,或者偶发性地突然跳到一个很大的值。这个问题大部分情况不是电路的问题,而是采样时刻不对。

排查思路:

  1. 先用示波器看PWM输出和ADC触发信号的时序,确认采样点是不是真落在PWM中间。最简单的方法是把ADC触发信号引到一个空闲的引脚上(比如用TIM8的TRGO直接映射到MCO引脚,或者用一个GPIO来toggle并在中断里抓),然后和PWM波形对比相位。
  2. 检查死区时间内要不要加消隐。下桥臂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读到数据之间的延迟。如果电流环频率太高,而采样延迟又大,相位裕度就会被吃光,系统就振荡。

解决思路:

  1. 降低电流环频率,从20kHz降到10kHz试试,如果啸叫消失,就说明延迟占比太大。
  2. 优化采样时刻,让ADC尽量在PWM中间触发,但也要注意留出足够时间让运放稳定。
  3. 检查运放带宽。有些便宜的运放增益带宽积不够,在几十纳秒内建立不了足够的精度,这时采样到的信号本身就失真,换运放是唯一的出路。

4.4 ADC参考电压不干净

电流采样精度还和ADC参考电压有关。如果你用VDDA作为参考电压,而VDDA没有做滤波,那么电机启停时母线电压波动会耦合到VDDA上,直接导致采样结果抖动。

解决办法:

  1. 在VDDA引脚附近加一个10uF钽电容+100nF陶瓷电容滤波,PCB布局时让电容尽量靠近MCU引脚。
  2. 对精度要求更高的场景,用外部基准芯片(比如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转换范围的中点半,否则反向电流会直接被削掉。相关内容前面已经展开过,这里提醒一句:零漂校准的可靠性直接影响双向电流的测量准确度,别偷懒跳过。

这套ADC+DMA+TIM8的采样方案,我先后在无刷云台、小型机械臂关节和两轮自平衡机器人的驱动板上验证过,稳定性非常不错。如果你正在调试自己的FOC驱动器,建议先按这个链路把采样部分跑通,再去做复杂的速度环和位置环。毕竟电流数据不准,上层控制做得再好看,也都是海市蜃楼。

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

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

立即咨询