ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

STM32 FOC电流采样实战:ADC+DMA+TIM8链路解析与Odrive方案拆解

STM32 FOC电流采样实战:ADC+DMA+TIM8链路解析与Odrive方案拆解 搞过FOC调电机的人应该都经历过这种晚上看着示波器上的相电流波形明明SVPWM扇区切换是正常的电机转起来却一卡一卡的声音也不对。最后查来查去问题多半出在电流采样上。做高精度电流采样这件事绕不开Odrive这类开源方案里沉淀下来的那套思路——用STM32的ADCDMATIM8组合把触发时刻、数据搬运、CPU开销这三件事一次解决。这篇文章就围绕这个组合把从硬件选型、CubeMX配置到代码链路的完整过程拆开讲一遍适合正在调FOC电流环、或者想把手里的板子采样精度再往上提一档的朋友参考。1. 电流采样到底卡了FOC的哪根脖子精度与同步缺一不可很多人在FOC上栽跟头第一反应是PID调得不好或者是观测器参数不对。但实际上FOC的内环是电流环电流环吃的是相电流反馈。如果ADC采出来的电流本身就带着毛刺、漂移或者相位滞后后面不管是PI调节器还是SVPWM计算全都是在用一个失真信号做控制怎么调都白搭。1.1 相电流不准SVPWM等于在盲飞先想一个最简单的场景电机工作在10kHz电流环频率下每个PWM周期里你需要知道此刻流入电机三相绕组的电流是多少。电流经过采样电阻变成电压经过运放放大后进入STM32的ADC引脚ADC转换的结果再通过DMA搬到内存最后在电流环中断里参与Clarke变换和Park变换。这一条链路里任何一环出了问题表现都差不多q轴电流噪声大、力矩波动明显、电机温度偏高严重的时候甚至直接过流保护。我自己一开始用最简单的裸采集方式——就是每次进PWM中断后手动触发ADC转换再阻塞等待转换完成——低速空载看起来还行一带负载马上露馅电流波形上全是高频毛刺跑到额定转速的30%就开始啸叫。这里面的根本原因不是ADC分辨率不够而是两点采样时刻不对以及三相电流不在同一时刻被捕获。三相电流是时刻变化的如果你在T0时刻采了A相在T03us才采B相这个时间差对应的电流变化在低速时可能无所谓但在高速电机上电流斜率很陡哪怕几个微秒的偏差也会让Clarke变换出来的Iα、Iβ产生周期性脉动。1.2 采样窗口三电阻采样方案的天然限制Odrive最常用的方案是三电阻采样也就是在逆变器下桥臂的三个MOS管下面各放一个采样电阻。它的工作原理是当某一相的下桥臂MOS管导通时该相电流会完整流过采样电阻此时采样到电压就能换算出该相电流。但这个方案有一个物理约束一个PWM周期里并不是所有时刻三相下桥都正好导通。在中心对齐PWM模式下三角载波的顶点和底点附近三个下桥的导通状态相对稳定这时候才适合触发ADC。如果某一相的占空比接近0%或者100%它的下桥导通时间就变得极短采样窗口不够ADC还没来得及完成转换该相电流就已经换向了。所以高精度电流采样的第一步不是去调ADC参数而是先在脑子里面把采样窗口这件事想明白。只有理解了PWM状态和采样时刻的绑定关系后面配置TIM8触发源的时候你才知道自己到底在调什么。2. 对照Odrive的采样硬件从电阻选型到运放增益推导软件配置之前先把采样电路这一层说清楚。因为ADC采到的每一个数值最终都要通过硬件参数换算回实际电流——采样电阻阻值、运放增益、基准电压这三个数字如果对应不上软件写得再漂亮也是白搭。2.1 采样电阻的值不是越小越好Odrive的高功率版本上采样电阻一般选在0.5mΩ到2mΩ之间。这个数值是经过权衡的电阻越小功耗越低发热越小但电阻越小相同电流下产生的压降也越小对运放和ADC的噪声余量要求就越高。你可以自己算一笔账。假设电机相电流峰值是50A选用1mΩ采样电阻那么满载时电阻上的压降是U I × R 50A × 0.001Ω 50mV这个50mV就是运放需要放大的原始信号。如果选0.5mΩ压降减半到25mV运放增益就得翻倍电路噪声也会被同步放大。而如果选2mΩ满载时压降达到100mV功耗是P I²×R 50×50×0.002 5W这个热量已经很难处理了。所以选采样电阻的本质是在信号幅度够大和发热可以接受之间找平衡。我个人的习惯是对于额定30A以内的电机驱动器1mΩ是个很舒服的起点。2.2 单电源运放的偏置与增益计算另一个容易被新手忽略的问题是运放的供电。STM32的ADC只能接受0到3.3V的正电压但电机相电流是有正有负的采样电阻上的压降自然也有正负。如果用双电源运放负压输出直接进ADC引脚就烧了。Odrive这类方案普遍采用的办法是中点偏置用基准电压常见2.048V或1.65V把零电流对应的输出顶到ADC量程的中间。这样正电流让输出电压高于中点负电流让输出电压低于中点ADC采样后减去偏移量再除以增益就能得到带符号的电流值。增益的选择可以按这个公式推增益 可用的ADC输入摆幅 ÷ 采样电阻最大压降假设ADC参考电压3.3V中点偏置1.65V为保证线性留出20%余量那么可用摆幅约为0.33V到2.97V单向摆幅1.32V。采样电阻最大压降50mV那么增益应该在1.32÷0.05 ≈ 26倍左右。市面上常见的INA240系列有20倍、50倍、100倍档位选20倍留的余量更大选50倍分辨率更高这个就要根据你的最大电流和控制裕量来取舍。2.3 布局布线的噪声控制开尔文连接不是玄学采样电路是模拟前端但它工作在一个满是开关噪声的环境里。我不止一次遇到过硬件原理图完全正确运放选型也没问题但采样出来的波形就是反复跳。原因基本都是PCB布局问题。最典型的一个坑采样电阻的两个引脚大电流路径和控制信号路径共用了同一段走线。采样电阻本身的阻值只有1mΩ如果走线上多出来0.5mΩ的寄生电阻而大电流恰好流过这段寄生电阻那么ADC读到的电压实际是采样电阻压降加上寄生电阻压降完全失真。正确的做法是开尔文(Kelvin)连接从采样电阻的两个独立焊盘单独拉两条细线到运放输入端大电流路径不要经过这两条细线。这样运放采到的电压就是采样电阻两个端点之间的真实压降不包含大电流路径上的附加压降。另外运放输出到MCU引脚的RC滤波电容需要靠近MCU引脚放置并且模拟地要单独铺设一小块在单点位置与功率地汇合。3. 为什么必须是TIM8加ADC硬触发加DMA一条不需要CPU的采样流水线硬件这一层理清之后回到STM32内部的配合机制。这个方案里有三个主角TIM8、ADC、DMA。它们各自负责的事情很清楚TIM8决定什么时候采ADC负责把模拟量变成数字量DMA负责把结果搬到内存并且不打扰CPU。3.1 同步永远是采样第一诉求中断触发为什么被抛弃一开始做FOC很多人会想着在PWM中断里手动启动ADC转换也就是软件触发方式。确实这个方式最简单进入中断HAL_ADC_Start等待转换完成读结果。但它有一个致命问题——中断响应时间不稳定。当你的程序还有其他中断源尤其是通信中断、编码器中断时PWM中断本身可能被延迟几个微秒执行。即使PWM中断按时触发中断里起始ADC转换的命令执行也需要若干个CPU周期。这样ADC真正开始采样的时刻与PWM理想采样点之间出现了随机抖动。电流环频率越高这个抖动带来的误差越明显。而TIM8硬触发方案是纯硬件链路TIM8的计数器到达某个特定值时硬件电路自动发出触发信号给ADCADC收到后自动开始转换。整个过程不经过CPU延迟是固定的、可预测的。这对高精度电流采样至关重要。3.2 三种触发时机对应不同的采样策略TIM8触发的时机通常有三种选择更新事件、通道比较匹配、TRGO输出。在实际FOC工程里最常见的是前两种。更新事件触发也就是计数器溢出时触发ADC。中心对齐PWM模式下更新事件发生在三角载波的顶点或底点这个时刻所有开关管处于死区附近开关噪声较小适合采集续流阶段的电流。选择这种触发时采样点相对稳定但采到的电流是PWM周期的平均电流不是瞬态峰值。比较匹配触发可以设定在PWM周期的某个特定占空比位置触发ADC。比如让TIM8的第4个比较通道作为触发源把比较值设置为计数周期中点附近这时候触发采样能得到更接近电流纹波中心点的值。我个人建议刚上手的时候先用更新事件触发先把整条链路跑通再去优化采样点位置。因为比较匹配触发往往还需要考虑死区时间对窗口的影响调试复杂度更高。3.3 DMA循环模式让最近一次转换结果永远在内存里ADC每次转换完成都会产生一个数据寄存器值如果每个通道转换完都靠中断来读那么10kHz电流环×3个通道就是每秒三万次中断CPU负担太重。DMA的引入就是让ADC在转换完成后自动把结果写入内存数组比如adc_buf[3]。你只需要在电流环主循环里直接读这个数组。这里有个很重要的配置细节DMA要工作在循环模式(Circular Mode)。这样ADC每完成一轮三通道扫描DMA就会把数据依次覆盖写回adc_buf[0]、adc_buf[1]、adc_buf[2]永远保持最近一次采样结果在内存里。如果不选循环模式DMA搬运完一轮就会停止之后ADC的数据没人搬运adc_buf里永远是老数据电流环读到的电流自然就不对了。这个错误非常隐蔽因为程序不会报错电机低速时甚至察觉不到但一旦高速运转电流反馈延迟带来的问题就全部暴露出来。4. CubeMX逐项配置与关键参数推算照着填就能跑聊完了原理到动手环节。下面这套配置以STM32F405为例工程采用HAL库加CubeMX初始化。整个配置过程关键是把每个参数为什么会这么填搞清楚而不是机械地照抄。4.1 ADC时钟与采样周期的换算逻辑ADC的参数配置首先要确定ADC时钟频率。在STM32F4上ADC挂载在APB2总线上时钟最高允许36MHz。如果APB2是84MHz需要配置ADC预分频器为4分频得到21MHz的ADC时钟。这个21MHz就是后续所有时间计算的基础。ADC转换时间由两部分组成采样时间加固定转换时间。固定转换时间是12个ADC时钟周期。采样时间则可以在CubeMX里选择常见选项有3、15、28、56、84、112、144、480个周期。以采样时间28周期为例单通道总转换时间 (2812)÷21MHz ≈ 1.90us。三通道扫描一轮总耗时约5.7us。对于10kHz电流环周期100us来说这个时间完全够用。如果你把采样时间调到480周期单通道就要23.4us三通道就是70us这已经逼近一个PWM周期的极限基本不可用。所以采样时间的选择不是越大越好。采样时间长一点确实能降低等效输入阻抗的影响但也会拖慢整体转换节奏。我自己的参数取舍是常规电机控制用15周期或28周期对噪声敏感的场合提到56周期同时把电流环频率适当降低。4.2 TIM8的中心对齐计数模式与触发信号输出TIM8在FOC里的角色首先是产生六路互补PWM驱动逆变器其次才是作为ADC触发源。所以在CubeMX里配置TIM8时要先把PWM模式和频率设定好。假设目标PWM频率为20kHz定时器时钟来自APB2的84MHz。选择中心对齐模式(Center-Aligned Mode 1)那么定时器计数先从0增加到ARR再从ARR减小到0一个完整周期产生一个PWM周期。ARR的计算公式为ARR (84MHz ÷ PWM频率) ÷ 2 - 1代入20kHz得到ARR (84,000,000 ÷ 20,000) ÷ 2 - 1 2100 - 1 2099。这个值就是计数的峰值点。PWM占空比由比较寄存器CCR决定在中心对齐模式下输出电平在计数器上升段计数小于CCR时导通下降段大于CCR时关断具体取决于你选择的PWM模式。六个通道中CH1/CH2/CH3分别对应A/B/C三相上桥CH1N/CH2N/CH3N对应下桥死区时间在Break and Dead Time配置里统一设置。触发源选择方面在TIM8的Trigger Output(TRGO)选项中要选择Update Event。这样每次计数器更新事件发生时TIM8会通过TRGO引脚内部信号不占用外部引脚向ADC发出触发信号。4.3 DMA、中断优先级和初始化顺序DMA配置相对简单在CubeMX里选择ADC的DMA设置添加一条DMA请求方向是PeripheralToMemory模式必须选Circular数据宽度半字(Half Word)因为ADC数据寄存器是16位的。内存地址递增开启这样三次转换结果会依次写入数组的三个元素。中断优先级这一块务必想清楚。FOC电流环主中断通常由TIM8更新事件触发它的优先级必须是最高的比如抢占优先级0子优先级0。ADC的DMA半传输完成中断和传输完成中断用于提示主程序当前这一轮采样结果已经齐了优先级可以设为0抢占下的子优先级1。其余外设——串口、CAN、编码器——优先级都应该排在它们之后。一个容易忽略的点是初始化顺序启动顺序应当是先初始化并启动ADC和DMA然后再启动TIM8的PWM输出。如果反过来PWM已经开始开关了ADC还没准备好上电瞬间会产生不完整的采样窗口第一次电流环计算用的是垃圾数据轻则电流尖峰重则直接触发过流保护。5. 从ADC裸值到FOC相电流的完整代码链路CubeMX生成的初始化代码只解决了外设配置的问题。真正让电流数据变成FOC能用的相电流还需要在应用层补齐几个关键步骤。这里直接通过代码说明。5.1 零电流校准上电第一步必须先做的事电流采样链路里运放有失调电压ADC有偏移误差采样电阻阻值也有容差。这些误差合在一起会导致0A输入时ADC读到的不是理想的中点偏置值而是一个偏离值。这就是零电流误差。处理方式是在电机静止、逆变器不输出、电流确实为0的时候采集足够多的样本取平均得到一个零点偏移量。示例代码如下#define ADC_BUF_SIZE 3 volatile uint16_t adc_buf[ADC_BUF_SIZE]; volatile uint8_t adc_dma_complete 0; float current_offset[3] {0.0f, 0.0f, 0.0f}; void current_calibration_zero(void) { uint32_t sum[3] {0, 0, 0}; const uint16_t sample_count 1024; for (uint16_t i 0; i sample_count; i) { HAL_ADC_Start_DMA(hadc1, (uint32_t *)adc_buf, ADC_BUF_SIZE); while (!adc_dma_complete) { } adc_dma_complete 0; sum[0] adc_buf[0]; sum[1] adc_buf[1]; sum[2] adc_buf[2]; } for (uint8_t ch 0; ch 3; ch) { current_offset[ch] (float)sum[ch] / sample_count; } }这段代码在电机每次上电启动前执行一次。校准完成后电流换算公式就固定下来了。5.2 电流换算与坐标变换前的数据对齐拿到ADC裸值后电流换算公式为#define ADC_FULL_SCALE 4096.0f // 12位ADC #define VREF 3.3f // 参考电压 #define V_CENTER 1.65f // 中点偏置 #define AMP_GAIN 20.0f // 运放增益 #define SHUNT_RESISTOR 0.001f // 采样电阻 1mΩ void current_read(float iabc[3]) { for (uint8_t ch 0; ch 3; ch) { float v_meas (adc_buf[ch] / ADC_FULL_SCALE) * VREF; float v_signal v_meas - (current_offset[ch] / ADC_FULL_SCALE) * VREF; iabc[ch] v_signal / AMP_GAIN / SHUNT_RESISTOR; } }注意这里有个隐藏的问题如果使用的是单ADC扫描三通道那么三个通道的转换不是同时完成的而是依次进行。A相在T0时刻转换B相在T01.9us转换C相在T03.8us转换。对于常规BLDC应用这个时间差引起的误差通常可以忽略。但对于高转速、低电感电机这个误差会变得可观。解决办法有两个一是使用STM32F4/F7系列的双ADC同步模式让ADC1和ADC2同时采集两相电流第三相通过基尔霍夫定律计算得出二是在代码里对采样时间差做逐通道相位补偿。前者的硬件布线复杂一些但效果更直接。Odrive在部分设计里也采用了类似的双ADC同步采样思路。5.3 过流保护与滤波的取舍电流数据同步进电流环之前还有两个细节要处理滤波和保护。滤波方面我的经验是电流环内尽量少加滤波或者只加截止频率很高的低通滤波比如截止频率在电流环带宽5倍以上。因为滤波会引入相位滞后相位滞后在高增益的PI调节器里很容易变成振荡源。如果确实觉得电流噪声大优先去查采样电阻布局和运放电源去耦而不是一味在软件里加滤波。过流保护方面软件过流检测可以作为硬件比较器保护的第二道防线。做法很简单在电流环中断里换算完iabc后判断三相信电流绝对值是否超过阈值如果超限立即封锁PWM输出if ((fabsf(iabc[0]) CURRENT_LIMIT) || (fabsf(iabc[1]) CURRENT_LIMIT) || (fabsf(iabc[2]) CURRENT_LIMIT)) { __HAL_TIM_MOE_DISABLE(htim8); // 关闭主输出桥臂全部关断 }调用__HAL_TIM_MOE_DISABLE而不是直接停止定时器是因为它能在微秒级切断所有PWM输出同时保留定时器计数状态复位后可以快速恢复。6. 实测排错两次采样故障的完整排查链路代码能跑不算完真正折磨人的是电机转起来之后的那些诡异现象。这里分享两个我实际调试过程中遇到的采样故障完整的排查过程可能对正在调板子的人更有帮助。6.1 现象电流波形高频抖动查了一圈发现是DMA配置少了循环第一次遇到这个问题是在一块自制驱动板上。低速空载运行示波器看相电流波形发现波形整体形状是对的但上面叠了一层明显的高频抖动频率在几kHz级别不是开关频率本身更像是一种节拍性的干扰。我先怀疑的是运放电源纹波把示波器探头点到运放供电引脚用交流耦合看纹波只有几十毫伏不算干净但也不至于引起这么明显的抖动。接着查采样电阻两端波形发现采样时刻确实存在但波形包络不稳定。排查到ADC配置时我才注意到问题所在DMA模式被Config成了Normal而不是Circular。这意味着DMA搬完一轮三通道数据后就停了之后ADC虽然还在被TIM8触发转换但转换结果不会更新到adc_buf里。而电流环主循环读的还是旧数据。为什么低速时感觉不明显因为电机转角变化慢旧数据与新数据之间的电流差异本来就小。随着转速上升旧数据与当前真实电流的偏差越来越大波形就越来越扭曲。解决办法很简单把DMA模式改成Circular后波形立刻恢复正常。6.2 现象转速一高就出现电流尖峰问题根源在采样时刻落到了开关噪声里另一个问题更隐蔽电机在额定转速附近运转时相电流波形上偶尔出现明显的尖峰持续时间很短像针一样。用示波器触发抓了很久才抓到。后来我把PWM的Vgs波形和ADC的触发信号同时接到示波器上发现ADC触发时刻正好落在某个桥臂开关动作后的几百纳秒内。这个时刻由于MOS管的寄生电容和二极管反向恢复采样电阻上的电压存在严重的振铃。虽然RC滤波器能削弱一部分但振铃幅度太大还是会污染ADC采样结果。排查之后确认这个触发时刻是TIM8的更新事件而更新事件在中心对齐PWM模式下与某些扇区的开关动作时刻重合了。解决方案有几个方向一是换用比较匹配触发把采样点挪到PWM周期的中间位置远离开关动作二是优化RC滤波器的截止频率让振铃在采样前衰减干净三是使用ADC硬件过采样功能对同一通道连续采样4次取平均等效提高信噪比。我实际用的是方案一加方案三的组合把触发源从Update Event改为TIM8的第4比较通道触发并开启ADC的过采样。改动之后电流尖峰彻底消失波形干净了很多。6.3 零点漂移不是运放坏了而是基准电压走线太长还有一次是零漂问题。静态校准的时候记录的三相零点偏移量一致但运行一段时间后发现q轴电流在小负载下出现了缓慢的偏移导致电机在零速指令下停不住会缓慢爬行。这次排查的重点放在了基准电压源上。我用的基准芯片输出1.65V作为运放中点偏置但PCB上基准电压到三路运放的走线很长并且中间经过了一枚过孔。大电流路径切换时PCB上微弱的电位差变化通过过孔影响了基准电压造成三路运放的偏置电压同步波动ADC读到的零点也随之漂移。处理方式一是把基准电压源就近放置在运放阵列旁边缩短走线二是在每一路运放的基准输入引脚都加一个100nF的去耦电容三是把零电流校准从上电只做一次改成每次电机停机时自动重新校准。经过这三步调整零漂问题彻底解决而且长时间运行后的一致性也好了很多。7. 最后分享两个调试技巧如果上面的链路你都走通了再分享两个实际项目中很好用的小技巧。第一个调试阶段可以在DMA中断回调里放置一个计数器用IO翻转的方式让示波器看到电流环的实际执行频率。这个方法比我见过很多人直接在代码里printf日志靠谱得多零侵入也不影响实时性。比如在DMA传输完成回调里翻转一个空闲IO就能直观看到采样和搬运是否稳定也能快速确认DMA是否真的在循环工作。第二个如果你在电流闭环之前想把采样链路单独验证建议在DMA搬运完后的数据上做一次已知信号注入。比如读到一个临时数组里手动填入一个人工正弦波然后送进坐标变换和SVPWM看电机是否按预期出力。如果人工信号下电机出力正常说明问题在物理采样链路如果人工信号下电机出力都不正常那就要去查坐标变换、PI参数这些软件逻辑了。这个二分定位方法能帮你节省大量的猜测时间。我在做Odrive类控制器的时候很长一段时间里都把注意力放在SVPWM和观测器算法上后来才意识到整个FOC系统里电流采样才是真正决定控制带宽和稳定性的那根地基。把ADCDMATIM8这条链路吃透把采样时刻、数据搬运、零点校准这些事情做到位后面调那些高级算法会顺手得多。
返回列表