ARTICLE DETAIL

资讯详情

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

双路FOC控制器源码解析:STM32定时器与ADC资源分配

双路FOC控制器源码解析:STM32定时器与ADC资源分配 简介面向嵌入式电机控制开发者的双路直流无刷电机FOC控制器工程源码基于C语言与STM32主控芯片实现重点解决多电机独立磁场定向控制问题适用于无人机、机器人或电动车辆等需要同时管理两套电机的场景。资源共154个文件包含77个.h头文件与49个.c源程序另有Markdown说明文档、原理图/PCB设计、工程配置文件及图片素材压缩包整体约28.56MB。源码覆盖电机参数初始化、电流采样与处理、位置传感器信号读取、电流空间矢量变换、FOC核心计算、PWM生成以及故障检测与保护等完整控制链路基于STM32F4系列HAL库工程涉及ADC采样、TIM定时器PWM、UART通信等外设便于在工程中直接跟踪与二次开发。项目采用双路独立控制环设计目录结构便于定位两套电机的控制逻辑与配置参数。已有1317人学习下载适合具备一定STM32与电机控制基础、希望深入理解FOC算法落地的工程师参考。1. 双路 FOC 控制器源码的工程切入点双路直流无刷电机控制器源码的工程难点从来不在 FOC 公式本身而在 STM32 的定时器、ADC 和中断资源怎么分给两套独立控制环路。单路 FOC 至少要占一个高级定时器的三组互补 PWM、两路电流采样通道和一个可靠的转子电角度来源双路意味着资源全部翻倍而电流环控制周期通常只有 50 到 100 微秒。这类 C 语言实现的双路 FOC 源码价值在两路电机的采样如何对齐各自的 PWM 载波以及无感启动、堵转保护在双路干扰下如何不误报。下面从控制原理、STM32 资源分配、C 语言实现到调试整定逐步展开适合正在做电机控制器或想读明白双路 BLDC 源码的工程师。2. FOC 双路控制的原理与 STM32 资源分配2.1 三个闭环电流环、速度环、位置环的分工FOC磁场定向控制的核心思路是把定子三相电流从静止的 ABC 坐标系变换到跟随转子旋转的 dq 坐标系。变换之后原本互相耦合、随时间变化的三相电流变成两个独立的直流分量id 控制励磁iq 控制转矩。直流无刷电机的转子是永磁体所以 id 通常给定为 0电流环全部输出都用来产生电磁转矩速度环处于电流环外层它的输出作为 iq 给定转速高时限制转矩电流上升斜率避免机械冲击如果上位机需要精确位置定位就在速度环外面再套一个位置环通常只有最外层的位置环放在低频任务里执行。双路控制器里两套电机的三个环完全独立但共用一颗 STM32 和一组母线电源所以源码必须把电机数据结构化让同一套 FOC 函数用指针分别驱动两路而不是把代码复制两份。实际调试时最容易犯的错误是只调电流环不管速度环导致两路电机速度竞争母线电压被拉低一路轻载一路满载时波形出现明显拍频。2.2 双路 BLDC 对 STM32 定时器与 ADC 的需求单路 FOC 至少需要一组三相互补 PWM六路输出、两个 ADC 通道采样相电流、一个电角度来源编码器、霍尔或反电动势观测器以及一个能与 PWM 同步的触发机制。双路把这些需求全部加倍工程上真正的约束不是 CPU 算力而是外设数量两个高级定时器必须能独立产生中心对齐 PWM两个 ADC 模块或一个 ADC 的两组注入通道必须分别跟随各自的 PWM 触发否则两路电流环的采样时刻无法对齐控制效果直接劣化。STM32F103 只有 TIM1 一个高级定时器虽然也能用普通定时器补出双路 PWM但死区、刹车和互补输出功能缺失不建议做真正意义上的双路 FOCF405 的 TIM1 和 TIM8 是标准组合双路入门选型最常见G431 是 ST 电机控制专用系列定时器分辨率更高还带可编程增益放大器PGA电流采样和反电动势检测电路可以省掉一部分外部运放。2.2.1 常用主控型号的资源对比主控型号高级定时器ADC 模块电流采样方案适合定位STM32F103C8T61 个 TIM12 个外部运放放大后进 ADC单路入门做双路需降级STM32F405RGT62 个TIM1/TIM83 个外部运放双路 FOC 最常见选型STM32G431CBT62 个TIM1/TIM84 个可差分输入内置 PGA可省外部运放双路无感 FOC 与反电动势检测2.3 电流采样时刻与 PWM 周期的对齐FOC 电流环的采样点必须避开功率管开关瞬间的电流尖峰。中心对齐 PWM 模式下载波顶点和谷底是开关振荡基本收敛的时刻工程上普遍用定时器更新事件触发 ADC 注入组采样采样值在 ADC 转换完成中断里直接参与电流环计算从采样到占空比更新只隔一个控制周期。以 STM32F405 标准外设库为例触发链路配置如下void adc_trigger_by_timer(void) { TIM_SelectOutputTrigger(TIM1, TIM_TRGOSource_Update); /* TIM1 更新事件做 TRGO */ ADC_ExternalTrigInjectedConvConfig(ADC1, ADC_ExternalTrigInjecConv_T1_TRGO); /* 接到 ADC1 注入组 */ ADC_ExternalTrigInjectedConvCmd(ADC1, ENABLE); }逻辑说明TIM1 中心对齐计数到顶点或谷底时产生更新事件TRGO 信号直接拉起 ADC1 注入组转换转换完成进中断执行 FOC。注入组优先级高于规则组可以随时打断主循环里的规则通道转换这一点对双路很关键。双路电机时两个定时器各自触发独立的 ADC 注入组采样互不干扰如果为了省成本共用一个 ADC 轮流采样两路电流环的采样时刻就会错开控制周期只能放宽否则第二路电流波形会出现周期性毛刺。判断一份双路 FOC 源码是否工程化优先看两件事ADC 触发源是不是定时器事件电流环是不是在中断上下文里执行如果看到主循环轮询 ADC 转换标志再算 FOC基本可判定是教学示例。HAL 库下函数命名有差异但触发链路的思路完全一致。3. 用 C 语言实现 FOC 核心变换与 SVPWM3.1 Clarke/Park 变换的 C 实现双路源码里坐标变换函数必须有清晰的输入输出接口最常见做法是定义静止两相坐标系和旋转 dq 坐标系两个结构体三相电流只采集两相第三相由基尔霍夫定律算出。下面把 Clarke 变换和 Park 变换合并成一条链路输入两相采样电流和电角度直接输出 dq 轴电流typedef struct { float alpha; float beta; } AlphaBeta_t; typedef struct { float d; float q; } DQ_t; void current_to_dq(float ia, float ib, float theta, DQ_t *out) { AlphaBeta_t ab; ab.alpha ia; ab.beta (ia 2.0f * ib) * 0.577350269f; /* 1/sqrt(3)ic 折算进 beta */ /* ic -(iaib)Clarke 变换系数已合并省一次减法 */ out-d ab.alpha * cosf(theta) ab.beta * sinf(theta); out-q -ab.alpha * sinf(theta) ab.beta * cosf(theta); }逻辑说明Clarke 变换把三相电流投影到 alpha-beta 静止坐标Park 变换再旋转到与转子磁场同步的 dq 坐标旋转角度 theta 就是电角度。直流无刷电机的电角度和机械角度相差极对数倍四极对电机转一圈对应四圈电角度换算错时 id/iq 互相耦合PI 参数怎么调都无法收敛。上面的 cosf/sinf 适合先在模拟环境验证数学放进 20kHz 的双路中断前要换成查表正弦或 CORDIC否则三角函数占掉大量 CPU 时间Cortex-M 上 double 运算远慢于 float全套源码统一用 float 是基本约定。3.2 SVPWM 的占空比计算与零序注入处理电压通道从 dq 反变换得到 alpha-beta 电压后进入 SVPWM生成三相定时器比较值。SVPWM 比 SPWM 母线电压利用率高约 15%还能让开关切换集中在最少的桥臂组合上是直流无刷电机控制器源码的标配。工程实现有两类一类是扇区判断加七段式时间分配另一类直接对三相中点电压做零序注入两种输出完全等价零序注入法不需要扇区查表代码更短双路场景更容易维护void svpwm_calc(AlphaBeta_t *v, float vdc, uint16_t arr, uint16_t *cmp) { float d_a, d_b, d_c; float u_max, u_min, u_offset; const float d_dt 0.02f; /* 死区折算的占空比下限按实际死区修改 */ /* 三相中点电压给定V由 alpha/beta 反投影得到 */ d_a v-alpha; d_b -0.5f * v-alpha 0.8660254f * v-beta; d_c -0.5f * v-alpha - 0.8660254f * v-beta; /* 零序分量把三相电压整体平移到母线中点附近 */ u_max fmaxf(d_a, fmaxf(d_b, d_c)); u_min fminf(d_a, fminf(d_b, d_c)); u_offset -0.5f * (u_max u_min); /* 归一化成 0..1 占空比再乘定时器周期得到比较值 */ d_a (d_a u_offset) / vdc 0.5f; d_b (d_b u_offset) / vdc 0.5f; d_c (d_c u_offset) / vdc 0.5f; /* 与死区做钳位防止互补 PWM 直通 */ if (d_a d_dt) d_a d_dt; if (d_b d_dt) d_b d_dt; if (d_c d_dt) d_c d_dt; d_a fminf(d_a, 1.0f - d_dt); d_b fminf(d_b, 1.0f - d_dt); d_c fminf(d_c, 1.0f - d_dt); cmp[0] (uint16_t)(d_a * arr); cmp[1] (uint16_t)(d_b * arr); cmp[2] (uint16_t)(d_c * arr); }逻辑说明三相中点电压就是 alpha-beta 往 ABC 三相的反投影u_offset 是叠加到三相的零序分量等效于七段式 SVPWM 里零矢量的分配省掉了扇区编码分支。母线电压 vdc 必须用实时采样值母线跌落时占空比自动抬高而不改变电压环输出双路共母线时尤其重要。死区钳位的上限和下限要对称否则三相电流波形出现直流分量低速时能听见明显的周期性噪声互补 PWM 上下管直通是硬件级事故保护交给定时器刹车功能而不是软件判断。3.3 双路控制的调度架构双路 FOC 的中断结构通常是TIM1 更新事件触发 ADC 注入组完成电机 A 电流采样采样完成中断里执行电机 A 电流环TIM8 对应电机 B两个中断占用同一优先级保证一路采样完成不会打断另一路正在进行的控制计算。电流环结束立刻更新比较值不要等两路算完再统一刷新否则其中一路输出滞后一整个控制周期。中断/任务频率内容TIM1 触发 ADC 注入完成10~20kHz电机 A 电流环采样→坐标变换→PI→SVPWMTIM8 触发 ADC 注入完成10~20kHz电机 B 电流环逻辑同 A速度环任务1~2kHz速度 PI 输出 iq 给定堵转/过流保护与电流环同频保护标志置位、PWM 刹车调度之外双路源码最常见的做法是把每路电机的状态封装成结构体FOC 核心函数只接收 Motor_t 指针传入 motor_a 或 motor_b 即可复用同一套计算靠 C 语言指针而不是宏复制区分两路电机typedef struct { DQ_t i_ref; /* 电流给定 */ DQ_t i_fb; /* 电流反馈 */ float speed_ref; float speed_fb; float theta; /* 电角度 */ PID_t pid_id; PID_t pid_iq; PID_t pid_speed; uint16_t adc_idx; /* 本路使用的 ADC 序号 */ TIM_TypeDef *timer; /* 本路 PWM 定时器句柄 */ } Motor_t; Motor_t motor_a; Motor_t motor_b;结构体里把定时器句柄、ADC 通道号、PI 参数全部放进来后续做双路一致性校准时只需轮询这两个实例改代码也不会出现改了 A 路忘了 B 路的问题。提示双路共母线时两路 PWM 载波频率最好保持一致否则拍频会通过母线电容耦合进对方的电流采样。4. 无感 FOC 的启动、反电动势检测与堵转处理4.1 开环强拖启动与切闭环判定无感 FOC 在零速时没有反电动势可供观测源码里最普遍的做法是开环强拖电流环仍然闭合但电角度由程序按设定频率递增让定子磁场拖着转子加速。启动一般分两段先施加固定的 d 轴电流把转子拉到已知电角度完成转子初始位置检测再以斜坡方式升高电角度频率和 iq 给定加速到观测器输出收敛后切闭环。启动状态机骨架如下typedef enum { ALIGN, RAMP, CLOSED_LOOP } StartState; void sensorless_start(Motor_t *m, float target_speed, float dt) { switch (m-start_state) { case ALIGN: m-theta 0.0f; /* 强制电角度定位转子初始位置 */ m-id_ref 0.3f; /* 定位电流单位 A */ if (m-align_timer 300) m-start_state RAMP; break; case RAMP: m-openloop_speed 0.5f; /* 电角度频率斜坡单位 Hz */ m-theta 6.2832f * m-openloop_speed * dt; m-iq_ref 0.2f 0.1f * m-openloop_speed; if (m-openloop_speed target_speed) m-start_state CLOSED_LOOP; break; case CLOSED_LOOP: /* theta 来源切到观测器切换前检查角度差 */ break; } }逻辑说明dt 是电流环周期秒数20kHz 时取 0.00005f。ALIGN 阶段的定位电流不能给太大否则转子越过平衡位置引起振荡负载惯量越大定位时间要越长RAMP 阶段的升频斜率取决于负载惯量斜坡过陡会失步出现啸叫过缓则启动时间拉长。切闭环之前要连续几个周期比较开环强制角度和观测器估计角度差值小于 10 度才允许切换再用一阶低通做几拍过渡否则 q 轴电流给定在切换瞬间跳变轻则转速波动重则直接触发过流保护。4.2 反电动势零点的检测方法无感 FOC 的反电动势检测有两条路线硬件过零比较和观测器估算。前一种在 PWM 关断窗口对不导通相电压做比较低速时反电动势幅值小信噪比差双路控制器要两套比较器电路很少采用多数源码走滑模观测器或龙伯格观测器路线把电机电压方程写进代码用电流误差重构反电动势再用锁相环提取转速和电角度。检测方式低速表现额外硬件源码工作量双路适用性反电动势过零比较差噪声大需要比较器电路小一般需两套比较器滑模观测器 PLL中无中好一路 CPU 可跑两套龙伯格观测器好无较大好依赖电机电感参数观测器增益是调试重点增益小收敛慢启动切闭环前等待时间长增益大高频噪声被放大电流环啸叫两路电机还会通过母线互相串扰。判断源码观测器实现是否规范看启动阶段是否对观测器输出做低通滤波以及是否保留反电动势幅值估计用于失速判断。直流无刷电机反电动势检测在低速段的另一个问题是信噪比低源码里常见的补偿是启动过程继续保留一小段 d 轴电流让反电动势更容易被观测器捕获而不是切到闭环瞬间立刻把 id 归零。4.3 堵转检测的三个参数无感 FOC 没有位置传感器转子堵住时观测器仍可能输出转速电流会一直加大直到烧毁所以堵转检测必须独立于观测器不能只看角度变化率。实用的堵转判定用三个参数电流阈值、持续时间和恢复策略。电流阈值取额定电流的 1.5 到 2 倍持续时间用电流环周期计数而不用毫秒定时器控制频率变化时保护时间自动跟随恢复策略禁止自动重启由上位机重新下达使能命令#define STALL_CURRENT_A 4.0f /* 堵转电流阈值额定 2A 的两倍 */ #define STALL_CYCLE 50 /* 连续 50 个控制周期10kHz 下约 5ms */ void stall_detect(Motor_t *m) { if (fabsf(m-iq_ref) STALL_CURRENT_A fabsf(m-iq_fb) STALL_CURRENT_A) { if (m-stall_cnt STALL_CYCLE) { pwm_brake(m-timer); /* 关六路 PWM 并拉低使能 */ m-state FAULT_STALL; } } else { m-stall_cnt 0; } }逻辑说明同时判断 iq 给定与反馈是因为只有反馈值超限可能只是短时过载给定和反馈同时超过阈值才能确认转子跟丢滞回逻辑要保留堵转标志一旦置位只能由外部命令清除防止故障状态下电机自行重启伤人。双路控制器还要把两路堵转检测的周期错开半个控制周期避免两路同时触发保护让母线电流冲击叠加。注意堵转保护触发后不要自动重启等上位机重新下发使能命令避免故障状态下电机自行转动伤人。5. 双路 FOC 的调试顺序与 PI 参数整定5.1 先调电流环再调速度环双路同时上电调试风险翻倍正确顺序是每路单独调。先把速度环断开iq 给阶跃给定观察反馈电流的上升时间和超调。电流环带宽取 500Hz 到 1kHz超调控制在 10% 以内超调大先降 Kp有稳态误差再动 Ki振荡周期短说明比例增益过高。电流环调通后再接速度环速度环带宽取电流环的五分之一以下否则两环会相互激励。5.2 用串口和上位机观察电流波形FOC 调试难点在于中间变量看不见最实用的手段是串口输出实时数据。调试阶段不要用 printf格式化转换在 20kHz 中断里是灾难按字节发送原始 float 是常见做法void trace_send(uint8_t ch, float v1, float v2) { uint8_t buf[12]; buf[0] 0xAA; /* 帧头 */ buf[1] ch; memcpy(buf[2], v1, 4); /* 原始 float 二进制 */ memcpy(buf[6], v2, 4); buf[10] 0x55; /* 帧尾 */ uart_dma_send(buf, 12); }每帧 12 字节460800 波特率下每秒约 4600 帧够看 1kHz 以内的动态必须用 DMA 发送。推荐先输出 iq 给定、iq 反馈、观测器电角度和三相占空比这四组量多半问题都能定位。5.3 双路一致性校准两路电流采样运放零漂不同上电后必须在 PWM 关闭时各自采集偏置闭环前把偏置从采样值里减掉void offset_calibrate(Motor_t *m) { float sum_ia 0.0f, sum_ib 0.0f; for (int i 0; i 128; i) { sum_ia adc_get_ia(m-adc_idx); sum_ib adc_get_ib(m-adc_idx); } m-offset_ia sum_ia / 128.0f; /* 零漂均值 */ m-offset_ib sum_ib / 128.0f; }零漂不校准电流环积分会一直和偏置对抗两路电机零速附近抖动差异明显。两路同型号电机时电流环 PI 可共用但堵转阈值要按路标定双路满载时母线电压跌落比单路严重SVPWM 归一化必须用实时采样到的 vdc否则一路负载突变会扰动另一路电流给定这也是双路比单路更容易出现噪声的根本原因。本文还有配套的精品资源点击获取
返回列表