电机控制一直是嵌入式领域里门槛较高、也最容易劝退新手的方向。国内大部分学习资料要么停留在六步换相方波控制,要么一上来就是复杂的 Clarke/Park 变换、SVPWM、PID 整定,公式推导铺满屏幕,真正能跑起来的开源工程反而少见。最近看到一个很有意思的开源项目:一位初中生基于 STM32F103C8T6 手搓了一个简易极坐标 FOC 控制器,目标驱动的是拆机洗衣机永磁同步电机。这个选题本身就很有信息量。
如果只看表面,很多人会以为这只是一个“学生作品”,技术含量有限。但深入看硬件选型和算法实现,会发现这个项目真正值得学习的地方,是在极其有限的 MCU 资源里,用极坐标变换替代传统查表 SVPWM,把 FOC 的门槛用工程手段降了下来。STM32F103C8T6 这颗芯片只有 64KB Flash 和 20KB RAM,主频 72MHz,没有硬件浮点单元,用它在 20kHz 甚至更高的 PWM 频率下跑闭环电流环,本身就是一件需要认真考虑资源分配的事。
这篇文章不打算复读 FOC 的教科书推导,而是从工程实战角度拆解这个项目:为什么极坐标 FOC 适合 F103 这类低配 MCU,硬件上怎么搭采样和驱动电路,核心代码的扇区判断和占空比计算怎么做,以及调试时最容易踩的坑是什么。如果你正在学习电机控制,或者在低成本 MCU 上想跑通 FOC 但一直觉得无从下手,这篇文章应该能帮你省下不少时间。
1. 这个项目真正值得关注的点在哪里
很多人听到“初中生手搓 FOC”,第一反应是怀疑:FOC 不是需要 DSP 或者高端 ARM 吗?F103C8T6 这种几块钱的芯片真的能跑?这种怀疑在工程上是有道理的,但也正是这个项目的价值所在——它证明了 FOC 在一个低成本最小系统板上是可以跑通的,而且驱动对象不是玩具电机,而是洗衣机上拆下来的永磁同步电机。
从实际工程角度看,这个项目解决了几类真实的痛点:
第一类是学习成本问题。传统 FOC 学习路径很长,要先懂电机模型,再懂坐标变换,还要看得懂 SVPWM 扇区推导。很多初学者在“为什么需要 Park 变换”这一步就被劝退了。这个项目用极坐标的方式简化了中间环节,让控制逻辑更直观。
第二类是硬件成本问题。STM32F103C8T6 最小系统板的价格非常低,配套的驱动板也可以用常见分立元件搭建。相比于动辄几百上千的 FOC 开发套件,这个方案几乎是零门槛起步。
第三类是资源受限下的工程取舍。F103C8T6 没有 FPU,Flash 和 RAM 都小得可怜,要在这种条件下跑闭环控制,代码层面的每一处优化都很关键。这个项目的价值不仅在于“能转”,更在于它展示了在资源受限条件下如何做数学计算和时序调度的取舍。
从开发者的角度看,无论你是准备入门电机控制,还是已经做过一些方波控制想进阶到 FOC,这个项目的开源代码和硬件方案都值得花时间拆解一遍。
2. FOC 基础概念与极坐标控制的切入点
2.1 什么是 FOC,为什么要用 FOC
FOC(Field-Oriented Control,磁场定向控制)本质上是把三相交流电机的控制问题变换成类似直流电机的控制问题。三相定子电流在物理上是三组互差 120 度的交流量,直接控制滞后明显、动态响应差。FOC 通过坐标变换,先把三相静止坐标系下的电流变换到两相静止坐标系,再变换到随转子磁极同步旋转的坐标系,这样交流量就变成了直流量,控制目标变成了“励磁电流分量”和“转矩电流分量”两个直流回路,可以用常规 PID 精确控制。
对于永磁同步电机(PMSM),转子是永磁体,不需要励磁电流,控制目标主要落在转矩电流上。相比六步换相方波控制,FOC 的力矩波动小、噪音低、效率高,这也是洗衣机、空调压缩机等家电越来越倾向于使用 PMSM 加 FOC 方案的原因。
2.2 为什么选择 STM32F103C8T6
STM32F103C8T6 属于 STM32F1 系列,Cortex-M3 内核,主频 72MHz,Flash 64KB,SRAM 20KB。这颗芯片在国内嵌入式开发中极其常见,国产替代型号丰富,最小系统板价格低廉,资料多、生态成熟。
但放在 FOC 场景下,它的劣势很明显:不支持硬件浮点运算,所有浮点数学都要靠软件模拟;ADC 是 12 位,采样速度和精度都有上限;高级定时器数量有限,生成互补 PWM 需要精心配置。选择这颗芯片本身就有“用低配硬件做到够用性能”的意味,这也是整个项目最有价值的工程思路。
2.3 极坐标 FOC 和传统 SVPWM 的区别
传统 FOC 在生成 PWM 时,通常使用 SVPWM(空间矢量脉宽调制),需要把电压矢量分解到扇区,计算相邻基本矢量的作用时间,再根据需要切换零矢量。这个过程中包含大量三角函数运算和扇区判断逻辑。
极坐标 FOC 的思路更接近于直接用角度和幅值描述电压矢量。通过对反 Park 变换得到的两相电压分量,直接计算合成矢量的模长和角度,再由角度判断当前处于哪个扇区,最后换算成三相占空比。这种方式省去了一部分 SVPWM 扇区边界推导和矢量作用时间的细分计算,代码更直观,也更容易在低性能 MCU 上运行。
这种简化方案在性能上会带来一些代价,比如调制比利用率略低、谐波含量偏大、极低速下表现一般。但对于风扇、水泵、洗衣机滚筒这类对低速精度要求不高的应用,完全够用。
3. 系统硬件架构与电机选型分析
3.1 整体硬件框架
一套最简 FOC 驱动系统通常包含以下几部分:
- 控制板:STM32F103C8T6 最小系统板,负责执行 FOC 算法、电流采样处理和 PWM 输出。
- 驱动板:三相全桥逆变电路,常见方案是使用栅极驱动芯片加 MOSFET 功率管。
- 电流采样电阻:通常在逆变器下桥臂串联采样电阻,通过运放放大后送入 MCU ADC。
- 母线电压采样:用于过压保护和无感算法的电压前馈计算。
- 电机:永磁同步电机。
这个开源项目中,控制板与驱动板通过排针连接,控制板负责算法,驱动板负责功率变换,结构清晰,两级分开的好处是调试时可以单独供电,避免电机功率部分干扰 MCU 逻辑部分。
3.2 洗衣机永磁同步电机的特殊性
洗衣机使用的 PMSM 属于低压大扭矩类型,常见的有 24V、36V、310V 等不同规格。拆机电机和全新电机差异很大,关键是查看铭牌参数,重点是额定电压、额定电流和极对数。
极对数对 FOC 算法影响极大。如果极对数设置错误,角度反馈就会出现倍数偏差,电机轻则抖动,重则无法启动。很多初学者拿着拆机电机不知道参数,这里有一个简单方法:低速用手转动电机轴,用示波器或万用表交流档测量任意两相之间的反电动势波形,一个完整电周期内包含的波形周期数乘以电机机械转过的圈数,可以大致推算出极对数,或者直接查阅同型号电机资料。
需要特别提醒的是,如果是 310V 高压驱动的洗衣机电机,请务必先把安全事项搞清楚,确认驱动板和电机额定电压匹配后再上电,不要拿着低压控制板去驱动高压电机。
3.3 电流采样方案对比
FOC 闭环控制必须获取相电流反馈。常见的采样方案有三种:
- 三电阻采样:在三相下桥臂各串一个采样电阻,还原三相电流,精度较高,对硬件要求也高。
- 双电阻采样:采集两相电流,第三相通过基尔霍夫电流定律算出,成本适中,是大多数中低端 FOC 控制器的选择。
- 单电阻采样:在母线回路上放一个电阻,利用 PWM 不同开关状态还原相电流,成本最低,但算法复杂,精度受 PWM 占空比影响大。
这个项目从实现简单性出发,使用了下桥臂采样加运放放大的方式。实际调试时要注意采样时刻点必须落在下桥臂 MOSFET 导通且电流稳定的时间段内,否则采到的电流包含开关尖峰,控制环会震荡。
3.4 定时器与 PWM 互补输出配置建议
F103C8T6 的高级定时器 TIM1 和 TIM8 支持互补 PWM 输出,并能插入死区时间。FOC 驱动必须使用互补 PWM 驱动半桥电路,避免上下桥臂直通。
配置时建议:
- 使用 TIM1 的 CH1、CH2、CH3 生成三相互补 PWM。
- 设置合理的死区时间,例如 1us 左右,具体取决于 MOSFET 栅极驱动速度和开关频率。
- PWM 频率建议设置在 15kHz 到 20kHz 之间,高于人耳可听范围,同时不超出 MOSFET 开关能力。
- ADC 触发源设置为 TIM1 的更新事件或中心对齐计数的特定点,保证电流采样与 PWM 周期同步。
4. 软件算法核心:极坐标变换与扇区重构
4.1 Clarke 变换与 Park 变换
传统 FOC 的第一步是 Clarke 变换,将三相电流变换到两相静止坐标系。考虑到三相电流之和为零,通常只采样两相电流即可:
// 文件路径: src/foc_transform.c // 注意: 无中性线引出时 ic = -ia - ib void clarke_transform(float ia, float ib, float *i_alpha, float *i_beta) { *i_alpha = ia; *i_beta = (ia + 2.0f * ib) * 0.577350269f; // 1/sqrt(3) }Park 变换将静止两相坐标系旋转到转子同步坐标系:
void park_transform(float i_alpha, float i_beta, float theta, float *i_d, float *i_q) { float sin_t = fast_sin(theta); float cos_t = fast_cos(theta); *i_d = i_alpha * cos_t + i_beta * sin_t; *i_q = -i_alpha * sin_t + i_beta * cos_t; }经过这两步变换,id 和 iq 成为直流量,可以分别设计 PI 控制器。对于表贴式 PMSM,d 轴参考电流通常设置为 0,q 轴参考电流由速度环输出决定。
4.2 极坐标方式生成电压矢量
反 Park 变换后得到的是 dq 坐标系下的参考电压 vd 和 vq,需要变换到静止坐标系:
void inverse_park_transform(float vd, float vq, float theta, float *v_alpha, float *v_beta) { float sin_t = fast_sin(theta); float cos_t = fast_cos(theta); *v_alpha = vd * cos_t - vq * sin_t; *v_beta = vd * sin_t + vq * cos_t; }传统 SVPWM 此时会判断扇区并计算各矢量作用时间。极坐标 FOC 的做法是直接把 v_alpha 和 v_beta 合成一个目标电压矢量,用极坐标描述:
typedef struct { float magnitude; // 电压矢量幅值 float angle; // 电压矢量角度, 单位: 弧度 } polar_vector_t; polar_vector_t calculate_polar_vector(float v_alpha, float v_beta) { polar_vector_t result; result.magnitude = sqrt_fast(v_alpha * v_alpha + v_beta * v_beta); result.angle = atan2_fast(v_beta, v_alpha); return result; }在低成本 MCU 上,sqrt 和 atan2 都是开销较大的运算。实际工程中可以使用查表法、近似多项式或者简化反正切算法。角度精度不需要特别高,控制在电角度几度以内的误差对电流环影响是可以接受的。
4.3 三相差分占空比计算与扇区判断
拿到目标电压矢量的极坐标后,需要把它的投影转换为三相 PWM 占空比。这里的关键是根据角度判断当前矢量落在哪个扇区,然后调整占空比组合,使得线电压满足需求。
常见的实现方式是直接对目标电压矢量角度进行 60 度一组的扇区划分,然后在每个扇区内计算两相之间的差分占空比:
// 文件路径: src/svpwm_polar.c // 输入: 电压矢量的极坐标表示 // 输出: 三相占空比 duty_a, duty_b, duty_c (范围 0.0 ~ 1.0) void svpwm_polar_to_duty(polar_vector_t v, float vbus, float *duty_a, float *duty_b, float *duty_c) { float vd = v.magnitude / vbus; float angle = v.angle; // 将角度归一化到 [0, 2*pi) while (angle < 0.0f) angle += 6.2831853f; while (angle >= 6.2831853f) angle -= 6.2831853f; int sector = (int)(angle / 1.04719755f); // 60 度 = pi/3 // 根据扇区计算基础占空比 float x = vd * fast_cos(angle); float y = vd * fast_sin(angle); float d1, d2, d3; switch (sector) { case 0: d1 = 0.5f + 0.5f * ( x - 0.577350269f * y); d2 = 0.5f + 1.0f * (0.577350269f * y); d3 = 0.5f + 0.5f * (-x - 0.577350269f * y); break; case 1: d1 = 0.5f + 0.5f * (-x + 0.577350269f * y); d2 = 0.5f + 1.0f * (0.577350269f * y); d3 = 0.5f + 0.5f * ( x - 0.577350269f * y); break; // 其余扇区类似处理, 只涉及符号和顺序变化 default: d1 = 0.5f; d2 = 0.5f; d3 = 0.5f; break; } *duty_a = d1; *duty_b = d2; *duty_c = d3; }这段代码展示的是扇区 0 和扇区 1 的处理方式,其余 4 个扇区逻辑相同,只是正负号和顺序变化。这种做法的好处是不需要复杂的空间矢量作用时间计算,代码结构紧凑,适合在 F103 上运行。
需要注意,vbus 是母线电压采样值。如果母线电压波动较大,必须实时测量并参与计算,否则电压输出会实际偏离目标值,导致电流环性能下降。
4.4 为什么要关注扇区重构
从调试验证的角度看,极坐标方式和传统 SVPWM 的一个关键差异在于扇区边界的连续性问题。传统 SVPWM 在扇区切换时会重新计算矢量作用时间,如果实现代码中有符号判断错误,会在扇区边界处产生明显抖动。极坐标方式直接对角度做区间划分,边界是连续变化的,但三角函数近似误差在边界处可能被放大。
调试时如果发现电机在某个角度区间内噪音异常增大,优先怀疑扇区判断代码的边界条件,尤其是角度接近 0 和 2pi 时的环绕问题。
5. 环境准备与代码编译烧录
5.1 硬件清单
手头有这些材料就能开始:
| 硬件 | 说明 |
|---|---|
| STM32F103C8T6 最小系统板 | 蓝板或国产兼容板均可 |
| 三相全桥驱动板 | 自行焊接或购买成品驱动模块 |
| 电流采样运放电路 | 常见方案为 0.05 欧采样电阻加差分运放 |
| 永磁同步电机 | 洗衣机拆机电机或小型航模电机 |
| 直流稳压电源 | 电压范围根据电机额定电压选择 |
| USB 转 TTL 模块 | 用于串口调试输出 |
| 逻辑分析仪或示波器 | 非必需但强烈推荐,排查 PWM 和电流波形非常有用 |
5.2 软件工具链
软件工具推荐使用 Keil MDK 或 STM32CubeIDE,两者都能编译 STM32F1 工程。这个开源项目是基于标准外设库或 HAL 库实现的,具体以仓库代码为准。
需要准备的软件:
- ARM Compiler 或 GCC ARM 工具链
- STM32CubeProgrammer 或 ST-Link 烧录工具
- 串口调试助手
- 如果有上位机需求,可以准备简单的串口波形显示工具
5.3 编译前需要修改的关键参数
拿到开源代码后,第一件事不是直接编译烧录,而是确认几个和你的硬件匹配的核心参数:
// 文件路径: src/config.h // 根据自己电机和硬件修改以下参数 #define PWM_FREQUENCY 20000 // PWM 频率 20kHz #define DEADTIME_NS 800 // 死区时间 800ns, 根据 MOSFET 驱动调整 #define MOTOR_POLE_PAIRS 4 // 电机极对数, 务必确认 #define CURRENT_SENSE_GAIN 50.0f // 电流采样放大倍数 #define SHUNT_RESISTOR_OHM 0.05f // 采样电阻阻值 #define VBUS_NOMINAL_VOLTAGE 36.0f // 母线额定电压极对数设置错误是最常见的问题。电机转动时如果出现明显的顿挫或正反转抖动,优先怀疑这里。
5.4 烧录与首次启动流程
首次烧录前建议做以下检查:
- 确认驱动板母线电容放电完毕再接线。
- 先不连接电机,单独给控制板上电,确认 MCU 在跑。
- 用示波器查看三路 PWM 输出波形,确认频率和死区正常。
- 连接电机前,先用烧录器把程序烧进去,让 MCU 输出低占空比预定位信号,观察电机是否有轻微“咔哒”声。
- 确保电机电流限制配置合理,避免首次上电电流过大。
预定位是 FOC 从零开始的第一步,通过给定一个固定的 d 轴电流方向,让转子对齐到已知位置,这也是启动前必须经过的阶段。
6. 完整实现:从电流环到速度环的代码走读
6.1 电流采样与零漂校准
电流采样是所有闭环控制的基础,采不准后面全部白搭。电流采样电路存在零点漂移问题,放大器输入为零时 ADC 读数并不为零。
启动阶段必须先执行零漂校准:
// 文件路径: src/current_sense.c // 上电时调用, 在逆变桥未输出的情况下采集 ADC 零漂 #define ADC_OFFSET_SAMPLES 64 float current_offset_a = 0.0f; float current_offset_b = 0.0f; void current_sense_calib_zero_offset(void) { uint32_t sum_a = 0; uint32_t sum_b = 0; for (int i = 0; i < ADC_OFFSET_SAMPLES; i++) { sum_a += adc_read_channel(ADC_CH_A); sum_b += adc_read_channel(ADC_CH_B); } current_offset_a = (float)sum_a / ADC_OFFSET_SAMPLES; current_offset_b = (float)sum_b / ADC_OFFSET_SAMPLES; } float current_sense_read_phase_a(void) { float raw = (float)adc_read_channel(ADC_CH_A); float voltage = (raw - current_offset_a) * 3.3f / 4096.0f; return voltage / (CURRENT_SENSE_GAIN * SHUNT_RESISTOR_OHM); }零漂校准需要在电机不转、功率桥不输出的情况下做,否则采样数据不纯。校准之后还需要做电流信号滤波,常用的是一阶低通滤波,但要注意滤波带来的相位延迟对控制环的影响,滤波系数不宜过大。
6.2 PI 控制器与抗积分饱和
FOC 的电流环和速度环都需要 PI 控制器。针对 MCU 环境,PI 实现要考虑几个问题:
- 积分饱和。当误差长期存在,积分项累积到很大值,一旦误差反转,系统响应变慢。
- 计算频率。电流环通常在 PWM 中断里执行,速度环可以降低频率。
- 限幅。输出限幅是必须的,防止占空比超过允许范围。
一个带抗积分饱和的 PI 实现:
// 文件路径: src/pi_controller.c typedef struct { float kp; float ki; float integral; float out_min; float out_max; } pi_controller_t; float pi_controller_update(pi_controller_t *pid, float error, float dt) { float proportional = pid->kp * error; pid->integral += pid->ki * error * dt; if (pid->integral > pid->out_max) pid->integral = pid->out_max; else if (pid->integral < pid->out_min) pid->integral = pid->out_min; float output = proportional + pid->integral; if (output > pid->out_max) output = pid->out_max; else if (output < pid->out_min) output = pid->out_min; return output; }初调时建议先把积分系数设得很小,只调比例项,等系统稳定后再缓慢增加积分系数。
6.3 无感位置观测的简易实现
如果这个开源项目走的是无感 FOC 路线,代码中会包含位置估算模块。无感方案通常使用滑模观测器或反电动势观测器。
实现里可以借鉴的简化思路是:把定子电压和电流代入电机电压方程,反解出反电动势,再从反电动势中提取转子角度。实际实现时,需要低通滤波器滤除高频噪声,还需要锁相环或反正切函数提取连续角度。
滑模观测器的符号函数会带来高频抖动,实际工程中一般用饱和函数或连续函数替代符号函数,减小抖振。
6.4 主循环与中断优先级设计
FOC 对实时性要求极高,电流环必须放在 PWM 中断里执行,不能被其他代码阻塞。建议的结构是:
// 文件路径: src/main.c // 简化版主循环与中断结构 volatile uint32_t speed_loop_tick = 0; void TIM1_UP_IRQHandler(void) { if (TIM_GetITStatus(TIM1, TIM_IT_Update) != RESET) { TIM_ClearITPendingBit(TIM1, TIM_IT_Update); // 电流采样 ia = current_sense_read_phase_a(); ib = current_sense_read_phase_b(); // 坐标变换 clarke_transform(ia, ib, &i_alpha, &i_beta); park_transform(i_alpha, i_beta, theta_elec, &i_d, &i_q); // 电流环 PI vd = pi_controller_update(&pid_d, id_ref - i_d, dt); vq = pi_controller_update(&pid_q, iq_ref - i_q, dt); // 反 Park + 极坐标 + PWM 占空比 inverse_park_transform(vd, vq, theta_elec, &v_alpha, &v_beta); polar = calculate_polar_vector(v_alpha, v_beta); svpwm_polar_to_duty(polar, vbus, &duty_a, &duty_b, &duty_c); // 更新定时器比较寄存器 tim_update_duty(duty_a, duty_b, duty_c); speed_loop_tick++; } } int main(void) { // 外设初始化、零漂校准、PWM 启动等代码略 while (1) { // 速度环和位置环可以放这里低速运行 if (speed_loop_tick >= 10) // 每 10 个电流环周期执行一次速度环 { speed_loop_tick = 0; rotor_speed = estimate_speed(); iq_ref = pi_controller_update(&pid_speed, speed_ref - rotor_speed, dt_speed); } } }这种结构下,电流环的执行周期是固定且高优先级的,速度环可以慢一些,非实时任务在主循环里做。
7. 运行验证与波形调试方法
7.1 判断 FOC 是否正常的基本方法
电机能平稳转起来只是最基础的验证。真正的 FOC 正常运行表现为:
- 电机运行噪音较低,没有明显的“滋滋”高频尖叫声。
- 加减速响应平滑,没有抖振。
- 空载电流较小,且随转速变化不明显。
- 用手捏住电机轴,能感受到持续的堵转力矩,而不是间歇性顿挫。
如果电机一上电就剧烈抖动,或者转起来一顿一顿的,说明 FOC 算法中的角度信息是错的。检查顺序是:极对数、相序、编码器或观测器角度方向。
7.2 用示波器观察的关键节点
有示波器的条件下,重点看三个信号:
第一是 PWM 输出波形。确认互补 PWM 之间存在死区,死区时间内上下桥臂都关断,否则可能出现直通炸管。
第二是电流采样点。在 PWM 周期内固定时刻采样不稳定,通常是在 PWM 中心对齐时采样,此时电流纹波最小。如果采样值带有大量尖峰,需要用 RC 滤波或在代码里做多次采样取平均。
第三是电流波形。FOC 正常时,相电流波形应接近正弦波。如果波形畸变严重,可能是扇区切换逻辑有问题或者 PI 参数过激。
7.3 串口输出辅助调试
没有示波器时,串口是最方便的调试手段。把 id、iq、电角度、占空比等内部变量定时输出,在电脑上就能看到控制状态。
常见做法是定义一个结构体,统一通过串口发送:
// 文件路径: src/debug_uart.c // 简单串口调试输出, 使用二进制协议 typedef struct { float i_alpha; float i_beta; float i_d; float i_q; float speed_rpm; float theta_elec; } debug_data_t; debug_data_t debug_data; void debug_send_frame(void) { uint8_t *p = (uint8_t *)&debug_data; uart_send_byte(0xAA); // 帧头 for (int i = 0; i < sizeof(debug_data); i++) { uart_send_byte(*p++); } uart_send_byte(0x55); // 帧尾 }串口输出频率建议 100Hz 到 1kHz,避免占用太多 MCU 时间。上位机端可以用现成的串口波形软件解析,也可以用 Python 脚本绘制曲线。
8. 常见问题与排查思路
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 电机完全不动 | 使能信号未拉高 / PWM 未输出 / 母线电压没上 | 用示波器查看 PWM 引脚,测量母线电压 | 检查使能 GPIO 状态,确认功率桥供电 |
| 电机剧烈抖动 | 极对数配置错误 | 确认电机铭牌或反电动势波形 | 修改 MOTOR_POLE_PAIRS 参数 |
| 电机正转方向不对 | 三相相序接反 | 交换任意两相电机线 | 重新接线后在软件中修正方向 |
| 电机噪音大 | 电流环 PI 参数过激 / 采样点时机不对 | 降低 P 值,观察电流波形 | 重调 PI 参数,调整 ADC 采样触发点 |
| 低速时力矩不足 | 位置观测器角度不准确 | 用编码器对照观测器角度 | 调整观测器增益或改用带编码器的方案 |
| 芯片发热 | PWM 频率过高 / 死区设置不当 | 测量门极驱动波形 | 降低 PWM 频率或调大死区 |
| 电流采样读数为负 | 运放供电负压不存在 / 采样电阻方向不对 | 检查运放供电和采样参考地 | 确保采样参考点连接正确,添加电平抬升电路 |
| 速度环震荡 | 速度环增益过大 | 降低速度环 P,增加滤波 | 单独调试速度环,去掉电流环激励 |
每一个问题都可以进一步向下切分。比如电机不动,要先区分是控制板问题还是驱动板问题:控制板看 PWM 引脚是否有输出,驱动板看 MOS 管栅极波形是否正常,再往下一层是看电流反馈是否正常。定位问题的顺序应该是先信号后功率,先软件后硬件。
9. 最佳实践与工程建议
9.1 从低速启动开始
FOC 调试不要一上来就追求高速高载。先用开环电压驱动电机低速旋转,确认相序和角度方向正确后,再切入闭环电流控制,最后才加速度环。这个顺序能避免很多让人困惑的问题。
9.2 关注电流采样的时序
电流采样是 FOC 的命门。采样时刻不正确,PID 控制就是无源之水。PWM 中心对齐模式下,ADC 触发点要设置在中心点附近,确保下桥臂导通且电流稳定。使用定时器通道触发 ADC 而不是软件延时,能显著提高采样一致性。
9.3 安全边界必须设置
给电机供电之前,先确认代码中设置了母线电流上限、母线电压上下限和堵转保护。开发阶段电机堵转很常见,如果没有保护,电流持续增大会烧毁 MOSFET。在上位机或串口指令里加一个应急停机命令,关键时刻能少烧几块板子。
9.4 版本管理别偷懒
电机控制算法参数没调好的时候,人会忍不住频繁改代码。建议每次改动前记录原始参数,把调好的 Kp、Ki 保存到配置文件里,或者用 Git 管理固件代码。不要相信自己的记忆力,电机控制现场一忙,很容易改乱参数然后忘记回滚。
9.5 理解简化的代价
极坐标 FOC 是一种工程简化,它在资源受限 MCU 上做了一定的性能和精度妥协。了解它之后,再去看传统 SVPWM 的推导,往往会更容易理解 SVPWM 中的零矢量和非零矢量选择究竟在解决什么问题。简化的方案学会之后,还是值得再往标准 FOC 深入一步。
10. 总结与后续学习方向
这篇博客梳理了基于 STM32F103C8T6 的极坐标 FOC 开源项目从硬件到算法的核心内容。相比标准 FOC,极坐标方案用电压矢量的幅值和角度描述控制目标,简化了 SVPWM 的扇区判断和占空比生成过程,让 FOC 在一个几块钱的 MCU 上就能跑起来。项目的核心价值不仅是“能转”,而是如何在资源受限的情况下做硬件选型、算法简化和调试取舍。
想继续深入的话,可以按下面的路径走:
先把标准 SVPWM 的扇区判断和七段式发波完整推一遍,搞懂零矢量和基本矢量的时间分配。然后研究滑模观测器或者龙伯格观测器,了解无感 FOC 中位置估算的原理,这部分是电机控制真正的难点。有条件的话,在 Simulink 里搭建 PMSM FOC 仿真模型,先离线验证算法,再下载到板子上实测对比。最后可以尝试移植到 STM32F405 或 F407 这类带 FPU 的芯片上,你会发现资源和性能提升后,算法可以做得更精细,控制效果也会好很多。
如果你正准备开始自己的第一个 FOC 项目,建议先把文中的电源安全和工作原理搞清楚,再对照开源代码逐步验证波形和数据。电机控制是一门很依赖实践的学科,理论看得再多,不如自己调通一个带载的电流环学到的东西多。期待你也能从零开始跑起自己的电机。