FOC电流采集代码性能优化:从ADC等待到DMA与查表

发布时间:2026/9/6 8:53:07
FOC电流采集代码性能优化:从ADC等待到DMA与查表 1. 优化前先看清楚一段典型 FOC 电流采集代码的性能瓶颈做 FOC 控制的工程师基本都经历过这样一个阶段算法在仿真里跑得挺好波形也很漂亮一上板子就发现电流环跑不了太高频率中断里干的事太多CPU 占用率报警甚至偶尔还出现采样数据跳变、电机噪音大。我最近在优化一套 PMSM 无感 FOC 方案时就把电流采集这一段代码从头到尾捋了一遍结论是很多性能问题其实不是芯片算力不够而是代码写法把算力浪费在了不该花的地方。先说结论FOC 电流采集代码的性能优化核心目标不是把代码“写快”而是把中断里的时间预算留给真正需要实时计算的部分。电流环中断是要在极短时间内完成电流采样、Clarke 变换、Park 变换、PI 调节、反 Park 变换、SVPWM 更新的任何一个环节多耗几十个周期整个控制频率就可能从 20kHz 掉到 16kHz系统的动态响应和高转速性能都会受牵连。这篇文章适合谁看正在做 PMSM 无感 FOC、BLDC 方波/正弦波驱动或者对电机控制代码做实时性优化的嵌入式工程师。我会把我实际优化过的一段电流采集代码拿出来从采样触发方式、ADC 配置、数据搬运、数学计算四个层面拆解性能瓶颈再给出每一步的优化做法和实测数据。以后你再看自己项目里的电流采集代码应该能一眼看出哪些地方在浪费 CPU。2. 优化前一段典型的 FOC 电流采集代码到底慢在哪2.1 原始代码长什么样先贴一段我早期项目里的 FOC 电流采集代码这段代码的结构在很多入门级的电机控制工程里都能看到。它跑在 PWM 定时器更新中断里中断频率就是电流环频率我当时的配置是 10kHz。void TIM1_UP_IRQHandler(void) { if (TIM_GetITStatus(TIM1, TIM_IT_Update) ! RESET) { TIM_ClearITPendingBit(TIM1, TIM_IT_Update); // 软件触发ADC转换 ADC_SoftwareStartConvCmd(ADC1, ENABLE); // 等待转换完成 while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) RESET); int32_t ia ADC_GetConversionValue(ADC1); ADC_SoftwareStartConvCmd(ADC1, ENABLE); while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) RESET); int32_t ib ADC_GetConversionValue(ADC1); ADC_SoftwareStartConvCmd(ADC1, ENABLE); while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) RESET); int32_t ic ADC_GetConversionValue(ADC1); // 减去零点偏置 ia - ia_offset; ib - ib_offset; ic - ic_offset; // Clarke变换 float i_alpha ia; float i_beta (ia 2 * ib) * 0.57735f; // Park变换 float cos_theta cosf(theta); float sin_theta sinf(theta); float id i_alpha * cos_theta i_beta * sin_theta; float iq -i_alpha * sin_theta i_beta * cos_theta; // PI调节、反Park、SVPWM... } }这段代码功能上完全正确但性能上到处都是坑。我当时测了一下在 72MHz 主频的 Cortex-M3 上从进中断到完成电流采集和坐标变换大概花了 22 到 25 微秒。听着不多但 10kHz 中断周期就是 100 微秒光采集和变换就占了接近四分之一的时间预算后面还要跑 PI、SVPWM、无感观测器完全不够用。2.2 三个明显的性能黑洞第一个黑洞是软件触发 ADC 后的死等。ADC 转换虽然快但每次转换都要等 EOC 标志位置位这个 while 循环就在那空转。而且 ADC 每次启动还要重新配置触发源、等待稳定三段采样串行执行中间的时间全是浪费。更糟糕的是这种写法把 ADC 转换时间完全暴露在中断关键路径上中断里啥正事没干就光等硬件干活了。第二个黑洞是三角函数实时计算。代码里直接调cosf和sinf这两个函数在带 FPU 的芯片上还能凑合如果是没有 FPU 的 M3/M4F一次cosf可能要几百个周期。我当时的 MCU 是带 FPU 的但即便如此两个三角函数也要 1 到 2 微秒。如果换成定点查表这个开销能压到几百纳秒。第三个黑洞是ADC 结果读取方式。直接用ADC_GetConversionValue()读数据寄存器在部分芯片上会插入总线等待周期而且三次读取之间完全没有流水线优化空间CPU 就这么被拖住了。注意这三个问题本质上不是“代码风格差”而是“架构设计没有考虑到控制周期的实时性预算”。在 FOC 这种高实时性场景下中断里的每一微秒都是算过的不是能跑就行。2.3 用性能剖析工具确认瓶颈在我开始动手优化之前先做了一件事用 GPIO 翻转法测出中断里各段代码的实际耗时。方法很简单在关键代码段前后分别拉高拉低一个 GPIO然后用示波器看脉冲宽度。我测得的结果是这样的代码段耗时微秒占比ADC 软件触发 等待三段完成12.5约52%cosf/sinf 计算2.8约12%Clarke Park 浮点运算1.2约5%PI 反Park SVPWM6.5约27%其他变量处理、函数调用等1.0约4%这个表格说明了一个重要事实ADC 采样与等待相关的时间占了一半以上。如果不把 ADC 转换时间从关键路径上摘掉后面再怎么优化数学计算收益都很有限。所以我的优化顺序是先动 ADC 触发和转换方式再动数据搬运方式最后才优化数学计算。这个顺序建议你也按着来先解决大头再做精细化调整。3. 采样时机才是命根子FOC 电流采样为什么必须和 PWM 同步3.1 为什么 FOC 电流采样要设置在下桥相关热搜词里出现频率特别高的一句话是“foc 电流采集为什么要设置在下桥”。这个问题正好是采样时机优化的引子值得先讲透。FOC 电流采样的常用方案是电阻采样也就是在逆变器桥臂上串联采样电阻通过测量电阻两端的压降来推算相电流。采样电阻放的位置不同采样策略也不同。下桥采样是指采样电阻放在下桥 MOSFET 的源极和地之间利用下桥导通时电流流过采样电阻的原理来获取相电流。为什么要在下桥采样而不是上桥因为上桥导通时相电流确实在流动但采样电阻上的电压是浮地的需要差分放大器把电压降提取出来电路复杂且成本高。而下桥采样时下桥 MOSFET 导通电流从电机相线流过下桥、采样电阻回到地采样电阻的一端直接接地另一端对地的电压就是电流乘以电阻值用普通的运放就能放大简单可靠。所以工程上绝大多数低压电机驱动板都用下桥采样。但下桥采样有个前提条件必须在下桥导通的时候才能采到正确的电流。如果下桥关闭、上桥导通电流走的是上桥采样电阻上没有电流信号这时候采集的数据毫无意义。所以 FOC 电流采样必须和 PWM 开关状态严格同步要在下桥导通的窗口期内启动 ADC 转换。3.2 三段式 PWM 和七段式 PWM 的采样窗口差异SVPWM 的调制方式会影响采样窗口。三段式 PWM也称中心对齐、对称 PWM在一个载波周期内三相桥臂的开关状态会经历一个完整的“从全部下桥导通到部分上桥导通再到全部下桥导通”的过程。在 PWM 计数器的特定时刻三相下桥全部导通此时三个采样电阻上都有电流信号是理想的采样时机。七段式 PWM 的每个 PWM 周期被分成七个时间段其中首尾两个时间段是零矢量此时三相下桥全部导通或全部关断。在零矢量期间三相电流仍然存在续流所以也可以采样。但要注意零矢量期间采样到的电流方向和有效矢量期间相反吗不电流方向是一样的只是电机相电流在这个阶段流经的是续流二极管和下桥采样电阻上的信号依然有效。实际操作中最常用的采样触发点是PWM 计数器计数到 0 的时刻也就是载波周期的起始点。在中心对齐模式下此刻三相下桥全部导通ADC 可以采到三路完整的相电流。也有工程师选择在计数到峰值时触发那对应的是三相上桥全部导通的状态下桥采样在这种时刻采不到信号所以大多数用下桥采样的设计都会把采样点设在计数到 0 的地方。我用一个真实的波形说明这个问题。示波器同时抓 PWM 输出和采样电阻上的电压能看到在下桥导通窗口内采样电阻电压是一个相对平缓的平台而在开关切换瞬态电压会有剧烈震荡。如果 ADC 的采样时刻落在开关切换的震荡区采到的电流值就是毛刺PI 调节器会被这些毛刺干扰轻则电流噪音大重则控制器发散。所以定时器触发 ADC 的时刻必须避开开关切换瞬态选择在平台期的中间位置。3.3 优化第一步从软件触发改为硬件触发理解了采样时机的重要性之后第一项优化就顺理成章了把 ADC 触发方式从软件触发改为定时器硬件触发。原来的代码是在中断里启动 ADC 转换然后死等。改法是把定时器的触发事件配置为 ADC 的触发源让硬件在 PWM 计数到 0 时自动启动 ADC 转换。这样 ADC 转换和数据到达事件与 PWM 精确同步不需要中断参与中断里只需要处理已经转换完成的数据。以 STM32 为例配置思路是这样的具体寄存器操作因型号而异// 配置ADC为定时器触发 ADC_ExternalTrigConvCmd(ADC1, ENABLE); ADC_ExternalTrigConv ADC_ExternalTrigConv_T1_CC2; // 由TIM1捕获比较2触发 // 配置TIM1产生触发信号 TIM_SelectOutputTrigger(TIM1, TIM_TRGOSource_Update); // 更新事件作为触发输出 // 或者更精确地使用某个通道的比较事件 TIM_OC2Init(...); // 配置比较值使得PWM计数到特定值时产生触发这里的核心思想是让硬件完成“采样时刻控制”和“转换启动”两件事CPU 完全不需要参与。ADC 转换完成后数据会存放在 ADC 数据寄存器里或者通过 DMA 搬运到内存。CPU 只在下一次中断到来时取走已经准备好的数据。注意 硬件触发 ADC 时要留意触发信号和 ADC 采样开始之间是有延时的。一般芯片手册会给出触发延迟时间例如 STM32F4 系列的触发延迟大概是几个 ADC 时钟周期。在配置采样时刻时要把这个延迟算进去确保采样点仍然落在下桥导通的稳定窗口内。优化做完这一步中断里的轮询等待代码就完全删掉了。测了一下中断关键路径从原来的 25 微秒直接降到 12 微秒左右省下来的一半时间全在 ADC 等待上。4. ADC 数据搬运优化DMA 双缓冲把采集开销从关键路径上彻底摘掉4.1 ADC 转换结果为什么要用 DMA硬件触发 ADC 解决了“什么时候启动转换”的问题但还有一个问题没解决转换完成之后CPU 怎么拿到数据。最原始的做法是中断里查询 EOC 标志位然后读寄存器这种方式在上一节已经否掉了。另一种做法是 ADC 转换完成触发 DMA 请求由 DMA 硬件把数据从 ADC 数据寄存器搬到内存变量里全程不需要 CPU 参与。对于 FOC 电流采集这种周期性很强的场景DMA 几乎是标配。而且我建议用DMA 循环模式 双缓冲的设计而不是简单的单缓冲。单缓冲的问题是DMA 搬完一次数据后要重新配置或者要在中断里同步“DMA 已经搬完了”和“CPU 正在使用数据”这两个事件搞不好就会产生数据覆盖冲突。双缓冲的含义是 DMA 在搬当前数据到缓冲区 A 的同时CPU 在处理缓冲区 B 里的上一帧数据两个缓冲区交替使用互不干扰。4.2 结合 FOC 场景的双缓冲设计我在双电阻 FOC 方案里的具体做法是用 ADC 的两个通道分别采集两相电流第三相通过基尔霍夫定律计算或者用三个通道采集三相电流。把 ADC 配置成扫描模式一次触发依次转换多个通道。ADC 转换完成后DMA 按顺序把数据搬运到数组adc_buf[2][3]中。DMA 传输完成中断只在“半传输”和“全传输”时触发实际上每个 PWM 周期触发两次一次对应前 3 个数据缓冲区 A一次对应后 3 个数据缓冲区 B。伪代码示意volatile int32_t adc_buf[2][3]; volatile uint8_t dma_idx 0; // 当前DMA正在填充的缓冲区索引 void DMA1_Channel1_IRQHandler(void) { if (DMA_GetITStatus(DMA1_IT_TC1)) { DMA_ClearITPendingBit(DMA1_IT_TC1); // 上半段传输完成 - 当前使用缓冲区0的数据 process_foc_current((int32_t *)adc_buf[0]); } else if (DMA_GetITStatus(DMA1_IT_HT1)) { DMA_ClearITPendingBit(DMA1_IT_HT1); // 下半段传输完成 - 当前使用缓冲区1的数据 process_foc_current((int32_t *)adc_buf[1]); } }这里有个重要的同步问题DMA 搬运到缓冲区 A 完成时CPU 要处理的应该是缓冲区 B 里上一帧的数据而不是缓冲区 A 刚搬完的数据。在 FOC 控制中电流环的采样和控制存在一个 PWM 周期的延迟是可以接受的甚至很多方案故意引入一拍延迟来换取计算的确定性和稳定性。所以 DMA 中断服务函数里不要直接使用刚写完的缓冲区而是使用上一次 DMA 周期写入的另一块缓冲区数据。上面的伪代码里我是直接用了“当前 DMA 刚完成”的缓冲区这个在实际项目中要注意根据你的控制周期和 DMA 周期对齐情况设计。更稳妥的办法是把 DMA 中断当作“数据就绪信号”然后置一个标志位真正的 FOC 计算放在更高优先级的控制中断里处理。控制中断去读“上一次 DMA 准备好的缓冲区”。这个思路实质上是解耦了电流采样数据的产生和消费避免了在 DMA 中断里做复杂运算。4.3 优化前后的中断负载实测我把这一步做完后再测了一轮中断里的耗时变化如下项目优化前软件触发轮询优化后硬件触发DMAADC 触发方式中断内软件启动三次定时器触发自动转换数据获取方式CPU 等待并读取三次DMA 自动搬运电流环中断耗时约 25 微秒约 5 微秒CPU 占用率10kHz 电流环约 25%约 5%数据说明问题。光把 ADC 采集和数据搬运从关键路径上摘掉就把电流环中断的耗时降到了原来的五分之一。这时候中断里剩下的时间全部留给坐标变换、PI 运算和 SVPWM 生成控制频率从 10kHz 提升到 20kHz 甚至更高都有了空间。5. 数学计算层优化从浮点到定点查表还能再抠出几个微秒5.1 浮点运算在 FOC 中是主要开销吗硬件触发和 DMA 已经解决了采样采集的大头接下来就是数学计算。很多工程师会觉得 MCU 带了 FPU浮点运算应该不慢没必要做定点化。这个观点部分正确但要看具体芯片和具体运算。如果用的是带单精度 FPU 的 M4/M7浮点加减乘除确实很快几条指令就完事。但在 FOC 电流环里有两个东西再快的 FPU 也救不了一个是cosf/sin这类超越函数库函数内部可能包含多项式展开、迭代逼近等另一个是当代码用了double类型时会强制调用软浮点库慢到没法看。我的建议是在 FOC 电流环和速度环的热路径上全部改用单精度float并且把三角函数替换成查表或者定点整数运算。float的精度对于电流环 PI 控制器完全够用电流采样本身最多 12 位 ADC换算成物理量后浮点误差远小于硬件噪声。5.2 Clarke/Park 变换的定点化改造Clarke 变换和 Park 变换本身只是加减乘除用浮点算没问题。如果芯片性能紧张或者想彻底去掉 FPU 的依赖可以改成定点 Q 格式。我在一个使用无 FPU 的 Cortex-M0 的电机控制项目里试过定点化直接把整段坐标变换从浮点改成了 Q15 格式配合查表得到正余弦值。效果非常明显坐标变换的时间从浮点模拟的 10 多微秒降到了 3 微秒以内。定点化的核心是确定 Q 格式。电流采样值可以先做归一化处理把 ADC 原始值比如 0 到 4095映射到 Q15 的有符号范围-32768 到 32767也就是把零点偏置减掉之后满量程电流对应0x7FFF。反正切角度用查表得到 sin/cos 的 Q15 值再做乘法累加时用 Q15 乘法最后左移或右移恢复标度。这里有个细节要注意定点乘法是很容易溢出的。两个 Q15 数相乘结果是 Q30必须右移 15 位才能回到 Q15。如果中间累加需要更高精度可以先用 Q31 或 Q32 做中间变量最后归一到 Q15。这些在定点代码里都要提前设计好不能随手乱算。对于绝大多数用 M4/M7 的 FOC 项目我建议不必强求全定点化但把cosf/sin换成查表是无论如何都值得做的。5.3 查表法替换三角函数的实现细节角度查表的基本思路是把 0 到 360 度分成 N 个点预先把每个点的 cos 和 sin 值存到数组里运行时根据当前角度索引直接取值。N 选多大我常用 256 或者 512。256 点意味着角度分辨率是 360/256 1.40625 度对于 FOC 电流环来说查到的 sin/cos 精度是 8 位左右可能会有轻微谐波512 点更好一些内存也就多几百字节我的实际项目里通常用 512 点。索引计算要小心。电角度theta是浮点数比如 0 到 2π。查表前先归一化到表长范围然后转成整数索引。如果不做任何保护浮点转整数的时候有截断误差表边界处可能出现索引越界。我建议用下面的方式#define SIN_TABLE_SIZE 512 static const int16_t sin_table[SIN_TABLE_SIZE] { /* 预先生成的正弦值 */ }; int16_t fast_sin(int32_t angle_q15) // angle_q15范围 0~32767 对应 0~2π { int32_t idx (angle_q15 * SIN_TABLE_SIZE) 15; // 乘表长后右移15位 int32_t frac (angle_q15 * SIN_TABLE_SIZE) 0x7FFF; // 取小数部分 int16_t y0 sin_table[idx]; int16_t y1 sin_table[(idx 1) (SIN_TABLE_SIZE - 1)]; int16_t result y0 ((int32_t)(y1 - y0) * frac 15); return result; }这段代码用了线性插值比直接查表精度更高而且表长取 2 的幂时取模操作可以用代替%非常高效。angle_q15可以是角度累加器的直接输出完全避开浮点角度运算。实测下来一次带插值的查表大约在 20 到 50 个周期比cosf快一个数量级。查表对 FOC 代码的实时性提升非常大而且实现简单强烈建议优先做这一步。注意 查表法的精度在高转速下尤其重要。转速越高电角度变化越快如果查表分辨率不够电流环里会出现明显的量化噪声听感上就是电机高频啸叫。所以表长宁大勿小512 点是最低建议有内存余量可以用 1024 点。5.4 预计算常量减少重复除法还有一个不起眼但确实有效的优化在 Clarke 变换里2 * 0.57735f是常量每次中断都在算同样的乘法。同样的还有 Park 变换里的系数组合、PI 控制器里的积分系数乘以控制周期等。把这些常量在初始化时预先算好存成全局变量中断里直接引用能省掉一小部分浮点乘法。单看一次没啥感觉但在 20kHz 的电流环里跑一整天这些重复计算累积的功耗和指令周期就是实打实的。我在优化时甚至会把这几个变换的中间变量尽量复用避免频繁申请栈上变量。嵌入式编译器对局部变量的处理通常没问题但在中断里调用一个大的函数栈的使用也要控制不能把栈顶推得太高否则容易踩掉其他任务的栈空间。6. 优化后的完整代码结构硬件帮你采样CPU 只做核心控制优化到这里整个电流采集代码的结构已经和初始版本完全不同了。我把优化后的代码主干贴出来供参考。// 初始化部分 void foc_current_sampling_init(void) { // 1. 配置ADC扫描模式通道序列为 A相、B相、C相 // 2. 配置ADC触发源TIM1更新事件或比较事件 // 3. 配置DMA循环模式搬运3个数据到adc_buf // 4. 使能DMA半传输/全传输中断 } // DMA中断只做标记 volatile uint8_t current_frame_ready 0; volatile int32_t *current_data_ptr NULL; void DMA_IRQHandler(void) { if (半传输完成) { current_frame_ready 1; current_data_ptr adc_buf[1]; // 上一帧数据在另一个缓冲 } else if (全传输完成) { current_frame_ready 1; current_data_ptr adc_buf[0]; } } // 控制中断真正的FOC计算 void TIM1_UP_IRQHandler(void) { // 使用上一次DMA准备好的数据 if (current_frame_ready) { int32_t ia current_data_ptr[0] - ia_offset; int32_t ib current_data_ptr[1] - ib_offset; int32_t ic 0 - ia - ib; // 三相电流和为零 int16_t sin_t fast_sin(angle_q15); int16_t cos_t fast_cos(angle_q15); // 定点Clarke Park int32_t i_alpha ia; int32_t i_beta (ia 2 * ib) * 0.57735f; // 实际使用定点系数 int32_t id (i_alpha * cos_t i_beta * sin_t) 15; int32_t iq (-i_alpha * sin_t i_beta * cos_t) 15; // PI调节、反Park、SVPWM... } }这个代码结构把“数据采集”“数据就绪通知”“核心计算”完全解耦了。ADC 和 DMA 负责采数据DMA 中断只负责设置一个标志位真正吃 CPU 时间的只有坐标变换和 PI 运算。中断里不再有任何等待操作所有数据都是“上一帧已经准备好了”的。实测优化后的 20kHz 电流环中断耗时总共大约 7 到 8 微秒包括 PI、SVPWM比原始代码的 25 微秒10kHz 贵一倍频率还要快两倍以上。CPU 占用率大幅下降系统还余出算力跑无感观测器、速度环和上位机通信。7. 调试过程中踩过的坑给后来者提个醒优化过程中最容易出问题的点往往不在“改代码”本身而在“改完之后系统行为变了”。我把自己踩过的几个坑整理成表格希望你不用再走一遍。现象原因排查与解决方案电机在低速时电流波形毛刺多采样点落在开关切换震荡区调整定时器触发时刻用示波器对比 PWM 和采样电阻波形把触发点移到平台期中间DMA 搬来的数据偶发错位DMA 缓冲区索引与控制中断不同步用“两个缓冲区 半/全传输中断”方案控制中断只消费当前帧完成对应的缓冲区电流零点偏置漂移导致 id 不为 0采样电阻、运放偏置电压随温度变化在电机启动前做一次 ADC 零点校准把各通道偏置存下来运行时周期更新零点提高电流环频率后电机啸叫PI 参数未随控制频率调整电流环积分系数和时间常数要按新频率重新设计不能直接沿用旧参数用了查表 sin/cos 后电流波形出现尖角查表点数太少或没有插值改用 512 点以上加上线性插值必要时对角度累加器溢出做规范处理电机高转速运行时采样电流跳零下桥导通时间太短采样窗口不足优化 SVPWM 调制策略限制最大调制比保证最小采样窗口考虑改用三电阻采样加移位算法这六个问题里最坑的是第二个。DMA 双缓冲的同步逻辑如果没想清楚“这一帧”和“上一帧”的关系调试起来会非常痛苦。我的经验是先在 DMA 中断里用 GPIO 翻转做标记用示波器同时看控制中断和 DMA 中断的时序确认数据消费和数据生产的顺序再开始调 FOC 参数。不要一上来就指望电机动起来先把数据流捋顺了后面就是顺理成章的事。还有一个实用的小技巧调试采样波形时不要只看 DA 输出的电流波形要直接读 ADC 原始值看一下「电流采样原始数据 零点偏置」的散点图。如果散点的分布在噪声水平内说明采样链路没问题如果散点有成团、跳变、平台缺失等现象优先查采样时刻和 DMA 同步别先去调 PI。这个排查顺序能省下大量定位时间。8. 写在最后优化 FOC 电流采集代码的通用思路这套优化流程并不局限于某一款芯片。无论你用的是 STM32、国民技术、GD32还是其他主流 MCU思路都是通用的先做性能剖析定位热点再把等待型操作从关键路径摘掉接着用 DMA 卸载数据搬运最后优化数学计算。每一步落地后都应该用 GPIO 翻转和示波器重新测一遍耗时用数据确认优化有效再进入下一步。我个人在实际操作中的体会是嵌入式性能优化最关键的不是某个技巧而是建立“时间预算”的意识。FOC 控制里 20kHz 的电流环意味着每个周期只有 50 微秒你必须清楚地知道这 50 微秒里每一项操作花了多少哪些占了预算的大头哪些是可以通过架构设计省掉的。只要把这种意识带进项目里不用刻意追求花哨的优化手段代码的性能也能一步步逼近硬件的极限。最后再分享一个我在多个项目里验证过的经验优化完电流采集代码后记得把电流环的中断优先级和 DMA 中断优先级放在一个合理的层级。我一般会把 DMA 数据就绪中断设为最低实时性需求只要保证它在下一个控制周期到来前置位就行而控制中断保持最高优先级。这样哪怕 DMA 中断有点延迟也不会错过电流环的实时计算窗口。优先级设置得好系统在极端负载下也不会丢帧。

相关新闻