
做电机控制这几年我统计过一个有意思的数据同样跑FOC电流环20kHz的开关频率下如果每个PWM周期都靠中断读取ADC转换结果CPU在电流环上大概要烧掉30%的算力如果改成PWMADCDMA联动这个数字能压到5%以下。差别不在于算力高低而在于“采样这件事到底由谁来完成”。TC3xx是英飞凌AURIX 2G系列的当家花旦TC375、TC377这些型号在车载电机控制器、数字电源、变频器里非常常见。PWMADCDMA联动说白了就是让PWM模块在特定相位点自动触发ADC采样转换完成后由DMA把结果自动搬进内存数组整个链路不需要CPU逐周期参与CPU只在数据就绪后做真正的控制运算。这个方案适合所有对采样时刻有严格要求的场合FOC电流环采样、图腾柱PFC的电压电流闭环、相电流重构、PWM整流器以及任何“采样点必须卡在PWM波形固定位置”的工况。下面我会从设计思路讲到模块细节再给出可直接照抄的配置步骤和调试方法。如果你手上正好有TC3xx的开发板跟着走一遍就能把链路跑通。1. 为什么要把PWM、ADC、DMA串成一条链1.1 软件触发采样的问题很多人一开始做电流采样都是这么干的PWM周期中断里置一个标志位主循环或者在某个中断里去启动ADC等转换完成后在ADC中断里把结果读出来。这套逻辑在原理上没毛病但放到实际项目中就会暴露三个问题。第一个是采样时刻不精确。软件从“PWM周期开始”到“真正执行ADC启动代码”之间存在不确定的中断延迟。只要系统里还有CAN、UART、故障处理这些中断在跑延迟就会抖动。对于电机控制来说采样点抖动意味着电流反馈中叠加了开关纹波控制性能会下降。第二个是CPU占用率。20kHz开关频率下每个周期50微秒如果这50微秒里有三分之一的时间在处理ADC启动、等待转换、读取结果、计算偏移这些琐碎事留给FOC核心计算的时间就被压缩得很厉害。我见过有些项目为了省时间把开关频率降到8kHz结果电流谐波变大电机噪声和温升都上去了。第三个是数据一致性。你需要采U相电流、V相电流、母线电压如果这三个量是在三个不同时间段采回来的它们对应的其实是电机在不同电角度下的状态做Clarke变换和Park变换时就会引入相位误差。频率越高误差越明显。1.2 硬件联动是如何工作的PWMADCDMA链路把这三件事全部硬件化。PWM模块在计数器运行的特定相位点比如计数归零的时刻或者比较匹配的时刻输出一个触发事件这个事件不经过CPU直接作为EVADC的外部触发信号。ADC收到触发后按照预设好的队列顺序开始转换转换完成后结果存入ADC的结果寄存器。与此同时ADC会产生一个服务请求事件这个事件被路由到DMA模块DMA自动把结果寄存器里的数据搬到内存地址——通常是一个全局数组或环形缓冲区。整个过程中CPU能做的事情只有两件事前配置好ADC通道和DMA搬运地址事后在DMA完成中断里取走数据做控制运算。你可以把这条链路理解成一条流水线PWM是产线的节拍器每个周期响一次铃ADC是称重工位听到铃声就把当前电流值“称”一次DMA是传送带称好的数据自动送到仓库。仓库里的数据什么时候被取走去包装做控制算法那是线长CPU的事。1.3 为什么选TC3xx做这套联动ST的STM32、GD32这些芯片也能做类似的PWM触发采样但TC3xx有几个明显的优势。首先是EVADC的触发源非常灵活。CCU6的T12比较事件、GTM的某个TIM通道事件、外部引脚触发都能作为ADC的触发源。这意味着你可以在PWM周期的任意相位点触发采样甚至可以在一个周期内触发多次。其次是DMA的请求源映射很丰富ADC转换完成、串口收发、SPI、CAN都是可选请求源配置自由度很高。最后是芯片本身的汽车级可靠性PWM模块的故障保护输入比如CTRAP可以直接接硬件过流信号故障响应时间是纳秒级不依赖软件。这套硬件链路选对了后续做功能安全或者电磁兼容整改会省很多事。2. TC3xx联动设计的核心模块拆解2.1 CCU6PWM生成与触发输出CCU6是Capture/Compare Unit 6的缩写是英飞凌在电机控制领域非常经典的定时器模块。它内部有两个定时器组T12是16位定时器带三对比较通道适合生成三相PWMT13是另一个16位定时器通常用于生成载波同步的辅助信号或者PWM的同步基础。在TC3xx里CCU6依然沿用这个架构虽然GTMGeneric Timer Module功能更强、更灵活但CCU6在电机驱动里的地位依然稳固原因是它对电机控制场景的优化很到位T12支持中心对齐和边沿对齐两种模式死区发生器内置紧急停止输入可以直接封锁六路PWM输出不需要CPU干预。用T12做中心对齐PWM时计数器先递增再递减比较事件在递增和递减两个方向都会发生。比较通道0的匹配事件可以作为触发信号输出这个触发信号经过内部互联矩阵送到EVADC的触发输入。我画一下它的工作节拍假设开关频率20kHz中心对齐模式下T12的周期值设为2500计数器从0增到2500再减到0一个完整周期就是50微秒。计数归零的时刻恰好对应PWM载波的谷底这时候三相下桥臂的开关管刚好处于开关状态附近的特定相位母线电流纹波相对可控是采样的黄金位置。触发点选在计数归零时刻还有一个好处此时PWM的占空比信息已经由比较寄存器决定了触发事件天然和PWM周期同步不会出现相位偏移。2.2 EVADC外部触发采样与结果存储EVADC是Enhanced Versatile ADCTC3xx内置的ADC单元。它比传统MCU的ADC强大在多组并行和灵活触发上。每个ADC组有自己的请求控制逻辑可以工作在队列模式或者扫描模式。队列模式特别适合联动场景你预先在队列里排好一串通道号比如第1个采样U相电流第2个采样V相电流第3个采样母线电压。收到一次外部触发后ADC按顺序把这几个通道全部转换一遍结果依次存入相应的结果寄存器。这里有个容易搞混的点EVADC的结果寄存器是按照通道组编号的比如Group 0的结果寄存器是G0RES0到G0RES11每个结果寄存器对应一个通道。队列里排了三个通道转换完成后数据会在三个不同的结果寄存器里而不是塞进同一个寄存器。后续DMA搬运时你要把源地址对应到这三个寄存器上。转换时间上也值得展开算一下。TC3xx的ADC单次转换时间由采样时间和转换时间两部分组成。采样时间由采样通道的SHSSample and Hold Stage配置决定可选的采样时间有几十纳秒到几微秒不等。转换时间则和分辨率有关12位模式下典型值在1微秒以内。如果是三个通道连续转换总时间大约在3到4微秒。这个时间非常短完全不影响20kHz周期里剩下的时间裕量。2.3 DMA从结果寄存器到内存的数据搬运DMA模块在TC3xx里的地位相当于CPU的“搬运工”可以把数据从外设寄存器搬到内存也可以从内存搬到内存还能在内存区域之间做拷贝。它和CPU的DMA没什么本质区别但TC3xx的DMA支持多通道并行、环形缓冲、链式传输以及非常灵活的请求源映射。联动方案里DMA要做的事情是ADC组转换完成后把G0RESx这几个寄存器里的数据搬到内存数组。DMA的请求源要选成对应ADC组的“转换完成事件”这样ADC每完成一组转换DMA就会被触发一次。DMA还可以配置成连续请求模式也就是一次请求后自动连续搬运多次。这个模式如果用得好可以一次触发搬空所有结果寄存器如果用不好很容易造成数据错位后面我会在常见坑里详细说。3. 保姆级实操三步打通硬触发链路3.1 第一步配置PWM定时器并输出触发事件先定参数。以常见的电机控制器为例PWM开关频率20kHz中心对齐模式T12输入时钟100MHz。中心对齐模式下T12的周期值由下面的公式决定 [ T12PR \frac{f_{CCU6}}{2 \times f_{PWM}} ] 代入数据 [ T12PR \frac{100\text{MHz}}{2 \times 20\text{kHz}} 2500 ] 所以周期寄存器写入2499寄存器值从0开始计数。比较值如果设1250占空比就是50%。T12周期配置好之后重点来了光有PWM输出还不够你得把“T12比较事件”作为触发信号释放出去。很多人在这一步卡住PWM波形是好的但ADC死活不触发原因就是触发信号被关了。在TC3xx里触发信号的选择和使能通常在CCU6的事件选择和比较控制寄存器里配置。以下代码是寄存器级示意不同型号位域名可能略有差异实际以芯片User Manual为准/* CCU6 T12周期配置100MHz时钟中心对齐20kHz */ CCU6_T12PR 2499u; // 周期值2500 CCU6_T12CMP0 1250u; // 比较值1250占空比50% /* 关键把T12比较0匹配事件接成触发信号 */ CCU6_T12MSEL 0x02u; // 选择T12比较0作为触发源A CCU6_CMCON | (1u 6); // 使能触发源A输出配置完之后用示波器或逻辑分析仪观察CCU6的触发输出引脚应该能看到和PWM周期同步的脉冲。这个脉冲的相位就是你后面ADC采样的时刻。触发点还可以再优化。默认情况下T12归零时产生触发但如果你想在PWM波的中间点采样可以选择上升沿比较匹配时触发。这取决于采样点想避开哪种开关噪声源原则是尽量让采样点远离噪声最剧烈的开关切换沿。3.2 第二步配置ADC外部触发与采样通道ADC这边要做两件事把触发源选成CCU6的触发信号以及把需要采样的通道按顺序排进队列。以EVADC Group 0为例先把通道0、通道1、通道2依次排进队列G0REQ寄存器写入0x07表示队列里有通道0、1、2三个通道。对于每个通道在G0CHCTR寄存器里配置采样时间和分辨率。在G0RQCON和G0REQTR寄存器里配置外部触发模式以及触发源的具体选择。外部触发模式一般有三种门控、边沿触发、单次触发。联动方案里用边沿触发即可收到CCU6的上升沿就转换一轮队列。采样时间的选择要根据源阻抗来定。如果电流传感器的输出经过运放源阻抗比较高采样时间就要适当加长否则采样电容没充满精度会受影响。我习惯把采样时间配置成1微秒左右既能保证精度又不会占用太多转换周期。ADC的转换结果默认可能是右对齐的12位数据存放在G0RESx寄存器里。这个对齐方式要保持和DMA搬运时的数据宽度一致后面如果发现数据比预期翻了好几倍多半是左对齐右对齐的问题。代码层面大致长这样/* EVADC Group 0 队列配置 */ EVADC_G0CHCTR (EVADC_G0CHCTR ~0x3Fu) | 0x30u; // 配置通道0采样时间 EVADC_G0REQ 0x07u; // 队列通道0/1/2 EVADC_G0RQCON | (1u 0); // 使能外部触发 EVADC_G0REQTR 0x01u; // 触发源选择CCU6配置完可以先不开DMA直接让ADC在触发下工作在调试器里看G0RES寄存器的值是否随PWM变化。这样能把变量范围缩小。3.3 第三步配置DMA通道搬运结果DMA配置是这条链路里最需要细心的部分。先确定搬运关系源地址EVADC Group 0的结果寄存器G0RES0、G0RES1、G0RES2三个寄存器的地址是连续的。目的地址一个全局数组比如int32_t adcResults[3]或者uint16_t adcResults[3]。传输长度3个半字每个结果寄存器是12位半字即可。请求源EVADC Group 0的转换完成事件。一个容易被忽略的细节是DMA的访问宽度。ADC结果寄存器是32位宽的寄存器但你只需要低16位或低12位的数据。如果DMA按32位搬运目的数组必须是32位数据类型如果按16位搬运目的数组就用16位。混着用会导致地址错位后面排查会很头大。DMA通道配置完后一定要使能它对应的中断因为你需要知道“这一轮数据已经搬完了”。在DMA完成中断里可以置一个全局标志位控制算法只在标志位置位时才读取数据。中断里不要做太多事置位标志就够了。我见过有人在DMA中断里跑滤波和坐标变换结果中断处理时间过长反而拖慢了整个控制周期这违背了硬件联动的初衷。一个简单可行的DMA配置示意/* DMA请求源对应EVADC Group0转换完成事件 */ DMA_CH_CSR /* 请求源选择、传输大小等 */; DMA_CH_SADR (uint32_t)EVADC_G0RES0; // 源地址 DMA_CH_DADR (uint32_t)adcResults[0]; // 目的地址 DMA_CH_TCR 3u; // 搬运3个数据单元 DMA_CH_IRQV | (1u 0); // 使能完成中断3.4 联动调试顺序与验证方法很多人喜欢一次性把三个模块全部配完再调试结果出了问题根本定位不了。我的做法是分三步每走一步都验证一下。第一步先单独验证PWM。关闭ADC触发和DMA用示波器看PWM波形确认频率、占空比、死区都正确。同时用逻辑分析仪抓CCU6的触发输出确认触发脉冲存在且和PWM周期严格同步。第二步使能ADC触发但不开DMA。在调试器里手动暂停程序查看G0RES0到G0RES2的值。如果三个通道没接传感器可以给一个已知电压比如1.25V看转换结果是否接近理论值。这一步能验证触发链路和通道配置是否正确。第三步使能DMA。让程序跑起来在调试器的内存窗口里观察adcResults数组看它是否在持续更新。还可以人为改变输入电压看数组值是否跟着变化。如果数组值一直不变要么是DMA没被触发要么是源地址配置错了。最后一步才是接入真正的控制算法把DMA中断标志位拿来作为电流环的触发信号。这一步跑通整个联动就算真正落地了。4. 常见坑与排查技巧实录4.1 PWM有输出但ADC不触发这个现象非常典型示波器看PWM完全正常但是G0RES寄存器的值一直不变或者一直等于某个固定值不刷新。排查顺序按下面来先确认CCU6的触发事件使能了没有。很多人只配了T12周期和比较值没有开比较事件输出到触发信号导致内部信号根本没送出来。这个容易漏先查。再确认EVADC的触发源配置。TC3xx的EVADC触发来源很丰富可能是CCU6的某个通道信号也可能是GTM信号还可能是外部引脚。你在G0REQTR里选的是不是和CCU6输出对应的那一路选错了一丁点信号都进不来。第三查队列。如果G0REQ寄存器是0队列是空的触发来了也没有通道可以转换。还有一个比较隐蔽的问题有些TC3xx型号的EVADC组有各自独立的触发源映射不是所有组都能接收任意CCU6信号。查User Manual里的Trigger Source Mapping表确认你用的这一组ADC确实支持你要用的那个触发信号。4.2 DMA搬移的数据错位、不对齐开机后数据看起来在更新但值经常“跳变”或者通道顺序乱了。比如我明明队列里先排的是U相电流结果数组第0个元素显示的却是V相电流。这种错位多半是DMA搬运长度和ADC队列长度不一致。ADC队列里有3个通道DMA只搬了2个半字那么下一次触发时DMA接着从第3个结果寄存器开始搬整体往后错一个位置。解决办法就是保证DMA搬运的起始源地址 队列第一个通道的结果寄存器地址搬运长度 队列通道数量。另外还有一个对齐问题如果DMA用16位搬运源地址必须半字对齐用32位搬运源地址必须字对齐。EVADC的结果寄存器地址通常是按32位对齐的源地址没问题但目的数组如果定义成uint16_t且编译器没做对齐优化就可能出问题。稳妥起见把目的数组定义成uint32_t或者加aligned属性。4.3 数据新旧覆盖与一致性高速运行时你会遇到另一个头疼的问题这一轮的数据还没被CPU读完下一轮DMA已经把数据覆盖了。如果控制算法读数组读到一半DMA写入了新数据读出来的数据就是“拼凑的”——前半部分是旧数据后半部分是新数据。这在多通道采样里尤其危险因为各相电流必须在同一时刻对齐拼凑出来的数据做坐标变换算出来的角度电流完全不对。解决思路有这么几种用双缓冲。定义两个数组DMA轮流往A数组和B数组写。DMA写A时CPU读BDMA写B时CPU读A。DMA的“半传输”和“传输完成”两个事件正好可以用来切换缓冲。或者利用DMA完成中断的时机。只在DMA完成中断里拉一个标志控制算法等到下一轮开始时才读数据。虽然实际上数据已经更新了但因为你保证“读的时候DMA不写”也不会出问题。如果是安全相关功能还可以在数组里加一个时间戳或者序列号。每次DMA搬完序列号加一控制算法读取前检查序列号是否有更新这种方式能检测出“数据没刷新”的情况。4.4 故障保护为什么必须单独接线联动的核心是PWM触发ADCADC触发DMA但别忘了PWM还有一个独立的紧急停止功能。CCU6的CTRAPCapture/Compare Trap输入和T12HRBHall/Trigger模式输入可以直接封锁PWM输出优先级高于任何软件配置。我在实际项目里栽过跟头一开始过流保护走软件ADC采到过流值后CPU再去关PWM。结果是每次过流都晚那么几个微秒开关管撑不住连续炸了好几个驱动模块。后来把硬件过流比较器的输出直接接到CCU6的CTRAP引脚过流瞬间硬件封波PWM被立刻拉低再让CPU去处理故障状态。这个改动之后过流保护就再也没炸过管。所以联动的设计思路是正常工况下让PWM、ADC、DMA全自动跑故障工况下让硬件直接接管软件只负责善后。千万别把所有安全兜底都放在软件上那等于把最后一道防线交给了中断延迟。故障现象可能原因排查手段PWM正常但ADC数据不更新触发事件未使能/触发源选错查CCU6触发输出和G0REQTRDMA数据顺序错乱搬移长度与队列长度不一致核对源地址、长度、队列通道数数据读取时被覆盖单缓冲无互斥机制改用双缓冲或DMA完成中断同步过流偶尔烧管软件保护响应太慢硬件故障信号接CTRAP硬件封波ADC数据偏大/偏小且异常稳定左右对齐配置错误检查结果对齐模式和DMA数据宽度5. 从联动到闭环的一点扩展心得5.1 双缓冲与半满全满我把双缓冲展开再说一点。DMA的中断事件里通常有“半满事件”和“全满事件”。如果你开了一个比较大的缓冲区比如能存64次转换结果DMA搬完32次触发一次半满中断搬完64次触发一次全满中断。CPU可以在半满时处理前半段数据在全满时处理后半段数据这样就把固定周期处理和批量搬运结合起来了。这种方式特别适合数据采集卡、振动监测、电能质量分析这类需要连续高通量采样的场景。对电机控制这种逐周期实时控制来说一般用不到这么大缓冲区但思路是一样的用缓冲换时间。5.2 多通道采样顺序设计与电流重构如果是三相电流采样很多方案不是用三个电流传感器而是用两个传感器第三相电流由基尔霍夫定律算出来。这时候ADC队列里只需要排两相电流加一路母线电压DMA搬运的长度可以相应减少。采样顺序也有讲究。如果两个电流传感器是同一个ADC模块的不同通道转换本身是串行的存在微小的时间差。对于低速电机这个时间差可以忽略但高速电机在高电频率下这个时间差对应的电角度差可能很明显。这时候可以把两个电流通道分给不同的ADC组利用TC3xx多组ADC并行转换的能力让两相电流几乎同时采样。具体怎么做呢用两组ADC分别配置成相同的触发源各自只采样一相电流。这样两路ADC在触发沿同时启动转换采样时刻差只有ADC内部的启动延迟远小于串行转换的时间间隔。5.3 一点个人体会这套PWMADCDMA联动方案我从最开始的半信半疑到后来彻底离不开中间大概用了一两个月的时间。最开始总觉得“CPU亲自读一次数据也不慢”但真到了系统复杂度上来之后每多一个中断嵌套每多一段临界区保护都会侵蚀系统的确定性。硬件链路的好处是它不带任何感情色彩不关心当前CPU在干什么到点就采采完就搬搬完才叫你。调试这类联动最重要的习惯就是“分模块验证”。我见过太多人拿着一堆寄存器配置到处问为什么没反应其实只要耐下心把PWM、ADC、DMA三个模块逐个验证过去问题几乎都能自己浮出来。这也是我写这篇教程的初衷把该注意的坑先说出来让大家少走弯路。