ARTICLE DETAIL

资讯详情

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

FOC电流采集优化:从阻塞中断到DMA搬运的完整实践

FOC电流采集优化:从阻塞中断到DMA搬运的完整实践 做嵌入式性能优化做到第11篇终于轮到FOC电机控制里最容易看似没问题、实则拖后腿的一段代码——电流采集。做过无感FOC或者伺服驱动的小伙伴应该都有体会电流环跑得好不好一半看算法另一半看采样链路。而采样链路一旦写得糙不管你是10 kHz还是20 kHz的电流环实际带宽都会被打折甚至出现高速转起来之后相电流畸形、扭矩抖动这类莫名其妙的问题。这篇拿一段真实项目里的FOC电流采集代码开刀完整走一遍从阻塞式ADC转换中断读结果优化到定时器同步触发DMA搬运定点化变换的过程。文章不追求把FOC数学从头推一遍重点放在嵌入式侧怎么把这些环节做快、做稳。适合正在调FOC驱动、想把电流环周期再往上压的嵌入式工程师也适合准备电机控制方向面试、需要把电流采样为什么放PWM下桥为什么只用两相电流这类问题讲透的同学。1. 先从根子上说清楚一段FOC电流采集代码到底在忙什么1.1 为什么FOC的性能优化往往先拿电流采集开刀很多刚接触FOC的人会有个误解觉得FOC的性能瓶颈在SVPWM在PID调参甚至在那个看起来最吓人的MTPA弱磁。其实做过几轮驱动板就会明白SVPWM和PID在MCU上的计算量非常可控真正容易失控的是电流采样链路。说句直白点的话FOC的电流环每隔一个PWM周期就要更新一次电压矢量输出这个周期一般是50us到125us对应8 kHz到20 kHz电流环。每个周期里你需要拿到至少两相电流而且要保证这两相电流在时间上尽量对齐然后算Clark变换、Park变换、两个PI调节器再算逆Park变换和SVPWM。时间预算就摆在那采样环节如果吃掉了三分之一甚至一半的CPU时间留给控制算法的余量就小得可怜。更严重的是采样时序如果和PWM开关动作没有精确对齐采回来的电流根本不能代表这个周期的平均值后面的PI跑到飞起也是在瞎忙。所以FOC性能优化的第一步就是把电流采集这条链路打造成尽量不占用CPU、每次都在对的时间点拿到对的数据的流水线。这是所有后续优化工作的基础。1.2 采样点选择为什么电流要专门放在下桥导通时采集网络热词里有一个foc电流采集为什么要设置在下桥这确实是新手最容易卡住的点。要理解这个得先看电机驱动器的硬件结构——三相全桥逆变器每相上下两个MOS管两个MOS管的中间点接电机相线下桥MOS管下方接采样电阻到地。电流采样电阻接法是低边采样因为我们采样的是流经下桥MOS管的电流。问题来了下桥MOS管不是一直导通的。在SVPWM的每个开关周期里某相下桥MOS管的导通时间占空比可大可小有时候接近0有时候接近100%。如果在它关断的时间点去采这个采样电阻上的电压采到的就是0或者是一堆开关噪声如果恰好在上下桥切换的死区时间内去采那更是毛刺重灾区。所以行业里通行的做法是选定每个PWM周期内一个固定的、下桥导通状态最稳定的时刻去触发ADC。工程上一般选在PWM计数器上溢或下溢位置也就是载波周期的边界。这个位置在中心对齐PWM模式下通常对应所有下桥管子都处在确定的导通状态且母线电流流向恰好能反映三相合成电流矢量。踩在这个点上做一次转换得到的就是当前周期的理想电流采样值。1.3 两相还是三相基尔霍夫定律把工作量砍掉三分之一另一个高频问题clarke变换为什么只需要两相电流就可以了。这个从物理上非常好解释电机三相绕组的中性点没有引出星形连接且中点悬空那么根据基尔霍夫电流定律流入三个相绕组的电流瞬时值之和必须为0。换句话说知道了任意两相电流第三相电流就是负的这两相之和。所以硬件上我们只需要两个电流传感器或者两个采样电阻加两路ADC通道软件上也只需要对两个通道做ADC转换。这是FOC电流采样天然的三分之一工作量减负来源。不过有个细节需要注意硬件上可以只采样两相但第三相电流不是完全不关心的。在做故障诊断、堵转检测的时候重构出来的那一相电流仍然有参考价值只是计算上不需要额外的ADC开销。2. 优化前的那段教科书代码看着能用其实遍地是坑2.1 第一版代码ADC阻塞转换加中断处理还是先来看一段典型的第一版能跑就行的电流采集代码。很多从别的MCU转过来的工程师或者跟着网上教程把小电机转起来之后代码写出来基本上是这个样子。// 伪代码PWM中断里做完整电流采样与FOC运算 void pwm_isr(void) { // 1. 触发两路ADC转换假设ADC1/ADC2各自一个通道 start_adc_conversion(ADC1, CHANNEL_PHASE_U); start_adc_conversion(ADC2, CHANNEL_PHASE_V); // 2. 阻塞等待转换完成 while (!adc_conversion_done(ADC1)); int16_t iu_raw read_adc_value(ADC1); while (!adc_conversion_done(ADC2)); int16_t iv_raw read_adc_value(ADC2); // 3. 转换成实际电流值 float iu (float)iu_raw * gain_u - offset_u; float iv (float)iv_raw * gain_v - offset_v; // 4. 后续FOC计算 clarke_transform(iu, iv); park_transform(...); pi_controller(...); inverse_park_transform(...); svpwm_update(...); }这段代码在电机空载、低速、不追求性能的项目里能跑电流环频率有个8 kHz、10 kHz也能凑合。但你只要把负载加上去把速度提起来问题就全暴露了。2.2 逐行拆解循环等待转换是在浪费时间我们来给这段代码做一次性能审计逐行算算它的时间开销。首先是start_adc_conversion这一步。如果MCU的ADC是软件触发模式那么你调用这个函数后还得等ADC完成一个完整的采样保持和逐次逼近过程。假设ADC时钟是18 MHz这是很多主流MCU的ADC最大时钟12位分辨率需要约12个到15个ADC时钟周期完成转换另外采样保持阶段通常要配置1.5个到15个周期。为了信号稳定FOC电流采样一般粒子采样保持时间不放太短假设典型配置选5.5个ADC时钟周期用于采样再加上12.5个周期用于逐次逼近一次转换大约需要18个ADC时钟周期折算下来大约是1us。两路ADC就是2us。听起来不多对吧但注意这2us是CPU阻塞在while循环里等出来的。如果电流环是10 kHz一个周期的总预算是100us2us占比2%。你觉得不痛不痒且慢这只是纯转换等待时间还没算中断进出开销、软件触发命令下发时间、读数判断时间、数据换算时间。随着电流环频率提高比如想做到30 kHz甚至50 kHz周期预算被压缩到33us甚至20us这两三个微秒的阻塞等待就开始变得扎眼了。更要命的是阻塞等待具有累积效应在while循环等转换完成的这段时间里CPU不能做任何其他事情如果这时候来了另一个更高优先级的中断整个采样时序就全乱了。2.3 性能记账一次完整采集需要多少CPU周期不妨用MCU主频120 MHz来算笔账。120 MHz下每个机器周期约8.3ns一条普通指令大约1到2个周期。阻塞式等两路ADC转换大概花2us等于大约240个机器周期。加上进出PWM中断的现场保护恢复这个在ARM Cortex-M上要做到30到50个周期再加上软件触发ADC的命令执行和结果读取粗算下来这部分链路消耗大约在300个机器周期上下。看着还行问题是这只是纯采集链路。后面紧跟着的电流换算缩放、偏移校准如果全部用浮点Cortex-M4有FPU还好Cortex-M0或者不带FPU的内核一次float乘加就要调用软浮点库几十个周期打底。两路电流换算下来又是上百个周期进去了。Clark变换里面涉及到sqrt(2/3)系数如果没提前算好而是每次现场计算又是一笔开销。这些开销单个拎出来都小捆在一起就会发现一个10 kHz的电流环CPU在采样和预处理上就耗掉接近10%的算力而且这些算力消耗是零产出——没有做任何控制决策只是把数据拿进来而已。这就是为什么这一节要重点讲优化的原因。3. 优化实操从同步触发到DMA搬运把CPU从采集流水线上解放出来3.1 第一步把ADC触发权交还给定时器采样点由硬件保证优化的核心思路其实一句话能用硬件做的事情不要让CPU干。第一件事就是把软件触发ADC改成定时器事件硬件触发ADC。现在主流MCU比如STM32/GD32/AT32系列的定时器以及NXP、 TI的部分型号都支持这样的链路高级定时器的更新事件或比较事件输出触发信号这个信号直接连到ADC的注入组或者规则组的触发源。配好之后每到PWM载波周期的指定位置ADC自动开始转换CPU全程零参与。你不再需要写start_adc_conversion函数也不用担心软件触发延迟和抖动。这个优化有一个直接收益采样时间点的精确度从软件上下文不确定的几百纳秒提升到硬件信号触发的几个纳秒级别。FOC电流采样对时序精度要求极高因为开关管导通瞬间的电流在快速变化几十纳秒的偏差都会导致采样点偏离理想位置尤其在高转速下反应更明显。一个实用的配置参考以STM32的定时器加ADC为例PWM载波15 kHz周期约66.7us高级定时器比如TIM1工作在中心对齐模式。将TIM1的更新事件作为ADC1和ADC2的注入触发源。这样每个载波周期PWM计数器在溢出时对应中心对齐模式下的载波边界ADC自动触发一次注入组转换。这里有个小技巧常被忽略ADC触发事件并不一定要用更新事件也可以专门配置一个比较通道让它的比较值对应你想要的采样点。这样做的好处是采样点可以微调比如你想让采样点比载波边界晚那么一点点避开开关管的振铃区就比较随心所欲了。3.2 第二步双ADC同步注入两相电流要同一时刻拿到解决什么时候采样之后还要解决两相电流是否是同一时刻采到的问题。之前那段阻塞式代码是分别触发两路ADC一路一路等这会导致两相电流的采样时刻相差好几微秒。在电机转速低的时候电流变化慢这点时间差可以容忍转速一高电流正弦周期变短几微秒的时间差意味着两相电流不是同一个瞬间的物理量拿去做Clark变换算出来的电流矢量就会带上误差。这里要请出双ADC同步模式。还是以STM32为例它支持ADC1和ADC2组成双ADC配置成交替模式或同步注入模式。同步注入模式下两个ADC会在同一个触发信号的作用下同时开始各自的注入组转换一个采U相电流另一个采V相电流。采样时刻的一致性由硬件保证两路结果的完成时间差基本可以忽略。这对硬件设计也有一个要求两个采样电阻的端子要分别连接到ADC1和ADC2的不同通道。如果你的板子上把两相电流都接到了同一个ADC的不同通道那就没法用双ADC同步注入只能用单ADC规则组连续转换。这样两相之间的时间差就没办法做得极致除非你降低对指标的要求或者用采样保持电路外扩。所以在设计驱动板的时候如果性能目标偏高把电流采样通道分开接到两个ADC上是一个更有利的硬件布局。3.3 第三步让DMA把转换结果搬走中断里只做数据处理触发和同步问题解决后CPU还是有可能参与等待只不过从软件触发等待变成了在中断里读数据。这时候引入DMA就是最后的关键一步。DMA扮演的角色类比一下就是专门的搬运工。你把ADC转换结果寄存器的地址配成DMA的源地址把内存里的一个数组配成目的地然后在ADC每次转换完成后自动发出DMA请求DMA就把结果搬到数组里。整个过程中CPU完全不用在把数据搬回来这件事上动手可以继续干别的活或者进入休眠待机直到需要处理数据时被唤醒。具体配置要点选择ADCx的DMA请求在双ADC同步模式下一般由主ADC产生DMA请求搬运的数据是双ADC的结果高16位和低16位各保存一路。DMA传输模式选择循环模式这样每个PWM周期产生的转换结果会自动覆盖到固定数组里不需要每次重新初始化DMA。中断选择上不要用ADC中断而是用DMA传输完成中断。这样中断频率是每周期一次而不是每通道转换完成一次中断次数减半。优化后的中断服务函数会清爽很多// DMA传输完成中断数据已经自动放在 s_adc_buf[2] 里 void dma_transfer_complete_isr(void) { // 直接从DMA搬运好的缓冲区拿数不需要等待转换 int16_t iu_raw (s_adc_buf[0] 0xFFFF); int16_t iv_raw (s_adc_buf[0] 16); // 后续换算与FOC运算 float iu (float)iu_raw * gain_u - offset_u; float iv (float)iv_raw * gain_v - offset_v; // ... 后续计算 }这样做的好处非常明显中断里第一行指令执行的时候电流数据已经在内存里了不需要任何等待。中断服务函数的时间开销大幅缩短而且时间是可预估的这对实时系统非常关键。3.4 第四步Clark变换定点化与查表把浮点开销压下来数据拿到之后还有一个经常被忽略的性能优化点电流换算和Clark变换的计算路径。如果MCU没有硬件浮点单元FPU你在中断里做float转换和运算效率会非常感人。即使有FPU大量的浮点乘法也是可以优化的。FOC代码里有一段固定高频执行的数学操作两个电流值从ADC LSB换算到实际安培值然后做Clark变换。Clark变换公式等幅值变换长这样ialpha iu ibeta (iu 2*iv) * (1/sqrt(3))这里有个平时不太注意的优化点变换里的系数1/sqrt(3)是固定的不需要每次实时计算。很多教科书代码喜欢现场写乘法计算这个系数性能上其实没有必要。正确做法是在编译期用常量定义或者直接预先算好使用浮点常量。更进一步如果ADC量程和增益是确定的可以把从ADC原始值到ialpha/ibeta的整个线性变换直接合并成两个系数也就是说你不需要先把ADC值换算成电流值再去做变换而是可以直接从ADC原始值线性变换到ialpha和ibeta。这样省掉一两个中间乘法。// 预先算好的综合增益包含ADC缩放、偏置消除和Clark变换系数 const float k_alpha_u 1.0f * adc_to_current_gain; // ialpha 中 iu 的系数 const float k_alpha_v 0.0f; const float k_beta_u 0.57735f * adc_to_current_gain; // 1/sqrt(3) 预计算 const float k_beta_v 1.1547f * adc_to_current_gain; // 2/sqrt(3) 预计算 float ialpha k_alpha_u * iu_raw k_alpha_v * iv_raw offset_alpha; float ibeta k_beta_u * iu_raw k_beta_v * iv_raw offset_beta;如果你用的是不带FPU的MCU比如某些M0内核的驱动芯片建议把所有电流换算全部调整为定点数实现。ADC结果是12位整数系数用Q15格式表示乘完右移15位全程没有软浮点调用。实测下来这一处改动可以省掉约60%到70%的电流预处理时间。3.5 整套配置串起来的效果数字背后的优化空间把前面四步全部落实之后一个比较理想的实现流程长这样TIM1中心对齐PWM输出载波频率15 kHz。ADC1和ADC2配置为同步注入模式注入触发源选择TIM1更新事件。ADC1注入通道采U相电流ADC2注入通道采V相电流。ADC转换完成后自动触发DMA搬运结果存到s_adc_buf数组DMA传输完成产生中断。中断服务函数里只做从数组取值、线性变换得到ialpha和ibeta、后续Park变换和PI运算。对比一下优化前后的时间账主频120 MHz15 kHz电流环环节优化前软件触发 阻塞读取优化后硬件触发 DMA搬运两路ADC启动与等待约2.0us 阻塞0由硬件自动完成中断读取结果约0.5us读写寄存器约0.1us内存数组读取电流换算 浮点运算约1.2us多次软浮点约0.4us预乘系数到位合计采集链路开销约3.7us约0.5us这么一比单看采集链路就省下来超过3us。对于15 kHz的电流环一个周期预算66.7us这个节省比例接近5%。如果再算上中断里少了反复判断ADC状态、读取寄存器的分支开销整体CPU占用率大概能往下降个8到10个百分点。别小看这几个百分点在电流环和其他功能模块抢CPU的项目里这往往决定了你能不能把电流环频率从15 kHz再提到20 kHz或者能不能多塞一个通信协议栈进去。4. 常见问题与排查技巧实录4.1 采到的电流噪声很大波形毛刺一堆这是做完定时器触发之后最常见的现象。代码优化到位了、DMA也上了但示波器看电流波形还是一团糟毛刺频率和PWM开关频率一致。这种问题十有八九不是代码问题而是采样点没有避开开关动作的振铃区。我在实际板子上调试时遇到过类似情况。排查步骤是这样来的先把PWM触发ADC的位置从更新事件换成某一个比较通道的输出事件然后把比较值一点一点往后调。调整过程中肉眼观察电流波形的毛刺幅度当采样点落在振铃区波形上去噪强烈往后退个几百纳秒到躲开振铃衰减尾部毛刺会明显收敛。最终找到一个大约滞后载波边界0.5us到1us的位置噪声从数百毫安峰峰降到几十毫安。另一种噪声来源是采样保持时间太短。ADC内部采样电容需要时间充电到外部电阻网络的电压值如果采样保持时间不够采样值就会偏且抖动。FOC电流采样电路的等效源阻抗通常包含采样电阻、走线电阻和滤波电容源阻抗越高需要的采样时间越长。建议在MCU资源允许的情况下把采样保持时间从最短档往上加一两个档位实测对噪声改善很大。4.2 一次采样是准的但三相重构出来的电流不对称驱动板上电之后电机转起来通过上位机看U相和V相电流正常重构出来的W相电流却总觉得不对称。排查了一圈发现不是算法错误是两路ADC通道的增益有偏差。FOC里面两相电流到第三相电流的重构本质上是做iw -iu - iv这个计算非常依赖iu和iv的精度一致性。如果两路ADC通道的参考电压、偏置、增益校准没做或者两路硬件采样电路的电阻误差不同那么重构出来的W相电流自然就带着歪斜。解决办法分两层第一层是在硬件上尽量保持两路采样电路参数对称第二层是在软件里做增益和偏置校准。校准方法不复杂在电机不转的情况下给驱动器一个固定的占空比比如让某一相下桥完全导通这时采样电阻上流过的是一个已知电流或者在零电流状态记录ADC值反推出偏置再用一个外部电流源注入标准电流记录ADC值反推出增益。把两组系数存下来在代码里用矩阵或者简单的乘加把两路通道修正到一致的标度。4.3 优化之后电流环频率反而跑不上去优先检查DMA和中断优先级有一种情况容易气人DMA配了定时器也触发正常但电流环周期一缩短就出现偶发的电流畸变。经验来看这类问题十有八九出在中断优先级和DMA优先级配置上。电流环中断DMA传输完成中断的优先级必须高于任何可能阻塞的中断而且最好设为FOC链路专用的抢占优先级。有些默认工程里把定时器更新中断、串口中断、外部中断混在一起DMA传输完成中断优先级偏低一旦串口在传输大批数据时频繁占用CPU电流环中断就会被延迟导致某个周期没有及时更新PWM波形电流波形上就会出现偶发台阶。另外要留意DMA的循环模式配置。如果DMA配置成了普通模式而不是循环模式那么每次ADC转换完之后DMA传输就停了s_adc_buf不再更新电机电流会突然失控。这个问题在优化过程中遇到过一次花了半天才定位到是DMA模式配置错误而不是算法问题。建议初始化完DMA之后加一个状态位的运行时检查防止DMA停摆而不自知。4.4 面试向这些知识点在嵌入式面试里怎么答这项优化做得多了有朋友问我去面嵌入式岗位时FOC相关题目应该怎么准备。这里顺带整理一下高频题目和回答思路。问题一FOC为什么只用采集两相电流回答思路中性点不引出基尔霍夫电流定律保证三相电流和为0知道任意两相就能推导出第三相。这里要点出硬件成本和ADC通道数的节省最好能补充一句第三相并不是完全不关注软件里会重构用于诊断。问题二电流采样时序怎么保证回答思路先说PWM开关噪声对采样的影响所以采样点不能放在开关边沿再说用定时器事件硬件触发ADC保证采样点相对PWM载波位置是固定的再补充双ADC同步注入保证两相同时采样DMA搬运保证不阻塞CPU。层层推进面试官基本不会再追问。问题三电流环运行频率怎么提升回答思路不要只答算法优化要从采样链路整体谈触发方式从软件改硬件、双ADC同步、DMA搬移数据、计算量定点化、中断优先级保证实时性。这种多维度回答在面试里会明显和其他候选人拉开差距。5. 一些踩过坑之后的个人心得这段优化做下来最大的一个感触是FOC性能优化很少是单点突破它更像是一条链路的系统性整理。ADC触发方式、采样点位置、DMA交互、中断配置、计算路径哪一环松了整个电流环的动态性能都会跟着掉。很多时候你以为是PI参数没调好折腾半天最后发现是采样链路里某个微小的时序偏差在作祟。还有一个想重点提醒的细节做DMA和定时器触发ADC这类配置改动后一定要在改动完成后立刻检查一遍外设时钟使能情况。这份配置里涉及TIM、ADC、DMA三个外设在低功耗模式下任何一个外设的时钟没有同步开启都会导致触发链路静默失效且这种失效非常隐蔽——代码看着都在跑但就是没有新的电流数据进来。最后给个实操建议优化完采集链路后建议在代码里专门加一个诊断计数器统计每次电流环周期的实际耗时超过预算就置一个警告标志位。有了这个计数器后面再加功能模块、调高PWM频率、或者换芯片平台的时候都能快速判断采集链路还有没有余量不用靠肉眼观察波形去猜。这套方法在我们的多个电机项目上验证过省下来的调试时间远比写这几行诊断代码的时间多。
返回列表