FOC 控制算法概述
FOC(Field-Oriented Control),直译为磁场定向控制,也被称为矢量控制。这是目前无刷直流电机(BLDC)和永磁同步电机(PMSM)高效控制的最优方案之一。其核心在于通过精确控制磁场的大小与方向,使电机转矩输出平稳、噪声低、效率高,并具备优异的高速动态响应能力。
无刷电机基础
无刷电机介绍
无刷电机(Brushless Motor)相比传统有刷电机,去除了机械换向器和电刷,采用电子换向技术。这使得它具备高效率、高功率密度、宽转速范围和高可靠性等显著优势。无刷电机由定子和转子组成,定子线圈通电产生旋转磁场,驱动转子上的永磁体转动。
BLDC 与 PMSM 的区别
无刷直流电机(BLDC)和永磁同步电机(PMSM)结构相似,主要区别在于反电动势波形:BLDC 接近梯形波,而 PMSM 接近正弦波。下图展示了两者反电动势波形的对比:

工作原理与驱动
理解电机原理前,先重温一下安培定则(右手螺旋定则)。当电流流过线圈时会产生磁场,通电线圈与永磁体之间的相互作用力驱动转子旋转。
六步换相驱动原理
传统的有感直流无刷电机通常采用六步换相法。驱动电路由三个半桥组成,通过控制上下桥臂的导通组合,将直流电逆变为交流电驱动三相绕组。由于绕组两两导通,共有 6 种通电状态,霍尔传感器检测转子位置后切换这 6 种状态,使转子跟随磁场旋转。
注意:同一半桥的上下管严禁同时导通,否则会导致电源短路烧毁器件。六步换相依赖霍尔传感器反馈位置,属于方波驱动。
FOC 控制原理
与六步换相不同,FOC 采用正弦波驱动,且三个半桥 MOS 管采用三三导通方式。这种方式能消除转矩脉动,运行更平滑,但算法复杂度更高。
无刷电机 FOC 控制算法
整体流程
FOC 控制的核心流程如下:
- 采集电机三相电流 Ia, Ib, Ic。
- 通过 Clarke 变换将三相静止坐标系转换为两相静止 α-β 坐标系。
- 通过 Park 变换将 α-β 坐标系转换为随转子旋转的 d-q 坐标系。
- 根据 Id, Iq 与目标值进行 PID 计算,得到电压指令 Ud, Uq。
- 通过反 Park 变换将 Ud, Uq 转换回 α-β 坐标系。
- 利用 SVPWM 调制生成三路 PWM 占空比输出。
Clarke 变换
Clarke 变换的作用是将三相电流映射到两相直角坐标系上。基于基尔霍夫电流定律(Ia + Ib + Ic = 0),我们只需采样两相电流即可推算第三相。
等幅值变换下,公式推导如下:

最终结果为: $$ \begin{bmatrix} i_\alpha \ i_\beta \end{bmatrix} = \begin{bmatrix} 1 & -1/2 & -1/2 \ 0 & \sqrt{3}/2 & -\sqrt{3}/2 \end{bmatrix} \begin{bmatrix} i_a \ i_b \ i_c \end{bmatrix} $$
逆变换同理,用于将两相结果还原为三相参考值。
Park 变换
Park 变换将静止的 α-β 坐标系旋转到与转子磁链同步旋转的 d-q 坐标系中。其中 d 轴与转子磁链重合,q 轴垂直于 d 轴。
经过这一步变换,原本随时间变化的交流量变成了直流量,便于使用常规 PI 控制器调节。角度 θ 是 d 轴与 α 轴的夹角,即电角度。
SVPWM 空间矢量脉宽调制
SVPWM 通过逆变器开关状态的组合,合成近似圆形的磁链轨迹。本设计采用七段式 SVPWM,相比五段式效率更高,谐波失真更低。
核心步骤:
- 扇区判断:根据合成电压矢量的位置,将 360°分为六个扇区。
- 作用时间计算:计算相邻两个基本矢量在一个周期内的作用时间。
- 占空比计算:根据作用时间分配三路 PWM 的 CCR 值。
以扇区一为例,占空比计算公式为:
value_a = (tpwm - Ta - Tb) / 4;
value_b = value_a + Ta / 2;
value_c = value_b + Tb / 2;
其他扇区需根据波形对称性调整 A、B、C 相的对应关系。
代码实现与 STM32 配置
STM32CubeMX 配置要点
- 时钟配置:确保系统时钟稳定,满足定时器频率要求。
- 高级定时器:配置为互补输出模式,用于产生三路 PWM,注意死区设置以防直通。
- ADC 配置:开启多通道扫描模式,用于同步采样三相电流。
核心算法代码
以下是 FOC 算法的关键 C 语言实现,包含反 Park 变换、扇区判断及 SVPWM 计算逻辑。
#include "foc.h"
#include <math.h>
#define SQRT3 1.7320508f
// 反 Park 变换:将 dq 坐标转换回 alpha-beta 坐标
void RevParkOperate(float vd, float vq, float theta, float *valpha, float *vbeta) {
*valpha = vd * cos(theta) - vq * sin(theta);
*vbeta = vd * sin(theta) + vq * cos(theta);
}
// 扇区判断函数:返回 1~6
unsigned char SectorJude(float valpha, float vbeta) {
float A = vbeta;
float B = valpha * SQRT3 / 2.0f - vbeta / 2.0f;
float C = -valpha * SQRT3 / 2.0f - vbeta / 2.0f;
unsigned char N = 0;
if (A > 0) N += 1;
if (B > 0) N += 2;
if (C > 0) N += 4;
return N; // 映射关系:3->1, 1->2, 5->3, 4->4, 6->5, 2->6
}
// 矢量作用时间计算
void VectorActionTime(uint8_t n, float valpha, float vbeta, uint32_t udc, uint32_t tpwm, float *ta, float *tb) {
float X = (SQRT3 * tpwm) / udc * vbeta;
float Y = (SQRT3 * tpwm) / udc * ((SQRT3 / 2.0f) * valpha - vbeta / 2.0f);
float Z = (SQRT3 * tpwm) / udc * (-(SQRT3 / 2.0f) * valpha - vbeta / 2.0f);
switch (n) {
case 3: *ta = Y; *tb = X; break; // 扇区 1
case 1: *ta = -Y; *tb = -Z; break; // 扇区 2
case 5: *ta = X; *tb = Z; break; // 扇区 3
case 4: *ta = -X; *tb = -Y; break; // 扇区 4
case 6: *ta = Z; *tb = Y; break; // 扇区 5
case 2: *ta = -Z; *tb = -X; break; // 扇区 6
}
}
// CCR 时间计算
void CCRCalculate(uint8_t n, float ta, float tb, uint32_t tpwm, uint32_t *ccr1, uint32_t *ccr2, uint32_t *ccr3) {
float temp = ta + tb;
if (temp > tpwm) { // 过调制保护
ta = ta / temp * tpwm;
tb = tb / temp * tpwm;
}
float value1 = (tpwm - ta - tb) / 4.0f;
float value2 = value1 + ta / 2.0f;
float value3 = value2 + tb / 2.0f;
switch (n) {
case 3: *ccr1 = value1; *ccr2 = value2; *ccr3 = value3; break;
case 1: *ccr1 = value2; *ccr2 = value1; *ccr3 = value3; break;
case 5: *ccr1 = value3; *ccr2 = value1; *ccr3 = value2; break;
case 4: *ccr1 = value3; *ccr2 = value2; *ccr3 = value1; break;
case 6: *ccr1 = value2; *ccr2 = value3; *ccr3 = value1; break;
case 2: *ccr1 = value1; *ccr2 = value3; *ccr3 = value2; break;
}
}
// SVPWM 主算法入口
void SvpwmAlgorithm(float vd, float vq, float theta, uint32_t udc, uint32_t tpwm) {
float valpha = 0, vbeta = 0;
RevParkOperate(vd, vq, theta, &valpha, &vbeta);
unsigned char n = SectorJude(valpha, vbeta);
float ta = 0, tb = 0;
VectorActionTime(n, valpha, vbeta, udc, tpwm, &ta, &tb);
uint32_t ccr1 = 0, ccr2 = 0, ccr3 = 0;
CCRCalculate(n, ta, tb, tpwm, &ccr1, &ccr2, &ccr3);
__HAL_TIM_SET_COMPARE(&htim8, TIM_CHANNEL_1, ccr1);
__HAL_TIM_SET_COMPARE(&htim8, TIM_CHANNEL_2, ccr2);
__HAL_TIM_SET_COMPARE(&htim8, TIM_CHANNEL_3, ccr3);
}
// Clark_Park 变换与电流环处理
void Clark_Park(int APhaseCurrent, int BPhaseCurrent, float Electronic_Angle, FOC_CURRENT *Current) {
float I_alfa = APhaseCurrent;
float I_beta = (2.0f * BPhaseCurrent + APhaseCurrent) / sqrt(3.0f);
Current->Id = I_alfa * cos(Electronic_Angle) + I_beta * sin(Electronic_Angle);
Current->Iq = -(I_alfa * sin(Electronic_Angle)) + I_beta * cos(Electronic_Angle);
// 归一化处理,根据实际 ADC 量程调整
Current->Id /= 1600.0;
Current->Iq /= 1600.0;
}
主循环逻辑
在定时器中断或主循环中,需按顺序执行位置环、速度环、电流环的控制逻辑。以下是一个典型的 1ms 中断回调示例:
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
if (htim->Instance == TIM3) {
if (motor_state == 1) {
Get_Pos(); // 获取编码器数据
Encoder_TypeDef.angle = Get_Elec_Angle();
// 角度补偿与限制
if (Encoder_TypeDef.angle >= Encoder_TypeDef.angle_rad_offset) {
Encoder_TypeDef.angle -= Encoder_TypeDef.angle_rad_offset;
} else {
Encoder_TypeDef.angle = 6.2831852f - Encoder_TypeDef.angle_rad_offset + Encoder_TypeDef.angle;
}
Encoder_TypeDef.electronic_angle = fmod(Encoder_TypeDef.angle * 7, 6.2831852f);
// 位置环与速度环
PID_Position_Out = PID_Function(&pid_position_TypeDef, target_location, encoder.rad_comulative);
vel_LPF = LPF_velocity(MT6701_GetVelocity());
PID_Speed_Out = PID_Function(&pid_speed_TypeDef, PID_Position_Out, vel_LPF);
// 电流环与 FOC
Clark_Park(PhaseACurrent, PhaseBCurrent, Encoder_TypeDef.electronic_angle, &foc_current);
PID_Out_Value = PID_Function(&pid_current_Typedef, PID_Speed_Out, foc_current.Iq);
// 输出 SVPWM
SvpwmAlgorithm(0, PID_Out_Value, Encoder_TypeDef.electronic_angle, 12, 3600);
}
}
}
在实际调试中,建议先开环测试 PWM 输出是否正常,再逐步闭环调试电流环参数,最后加入速度环和位置环。PID 参数整定需要耐心,观察波形变化微调 Kp 和 Ki。

