☰
ST32无刷电机FOC驱动全解析:原理、代码与实战
2026/10/19 13:56:06 网站建设 项目流程

对于刚接触无刷电机驱动的新手来说,FOC 这个缩写既熟悉又陌生。熟悉是因为只要搜索无刷电机、无人机、机器人关节,几乎处处能看到 FOC 的身影;陌生则是因为它背后的坐标变换、SVPWM、PID 整定、采样时序等概念,让人一开始很难形成完整认知。

分享一个“狗哥FOC”的最新作品,整套方案围绕无刷电机 FOC 驱动展开,覆盖了硬件板卡设计、STM32 侧驱动代码、电流采样、双电机控制以及无感 FOC 的估算思路。这篇文章就把这套作品拆开揉碎,从概念到代码,从原理到排错,完整梳理一遍。无论你是正在入门 FOC 的嵌入式新手,还是想把手上的电机项目升级为低噪声、高响应驱动方案的开发者,本文的内容都值得收藏备用。

1. 什么是 FOC,它解决了什么问题

1.1 从“方波驱动”到“矢量控制”

无刷电机(BLDC)和永磁同步电机(PMSM)的出现,让电机从“粗犷”走向了“精密”。早期驱动无刷电机最常用的方法是方波驱动,也就是按照霍尔信号或反电动势过零点,依次给三相通电,让电机转起来。

方波驱动的优点是简单,但缺点也很明显:换相瞬间电流突变,导致转矩波动大、噪声明显,高速运转时尤其明显。对于航模电机、风机这类对舒适度要求不高的场景,方波完全够用;可一旦进入机器人关节、电动工具、精密主轴这类场景,就必须让电流时刻保持平滑,让转矩输出恒定。

FOC,全称 Field Oriented Control,也就是磁场定向控制,正好解决了这个问题。它的核心思想是:把三相交流电流,通过数学变换,等效成两个正交的直流分量,然后像控制直流电机一样去控制转矩和磁通。

1.2 FOC 的三个核心收益

  • 转矩波动小:电流控制连续,输出转矩平稳,低速表现远超方波驱动。
  • 噪声低:电流正弦化,电磁噪声明显降低,适合对声音敏感的产品。
  • 动态响应快:电流环带宽可以做到很高,转矩响应达到毫秒级甚至更快。

1.3 为什么现在 FOC 越来越火

过去 FOC 主要用在高端伺服和电动汽车上,原因是算法复杂、MCU 算力要求高、成本昂贵。随着 ARM Cortex-M4/M7 内核 MCU 的普及,以及 STM32G4 等自带 CORDIC(坐标旋转数字计算机)硬件加速单元的芯片出现,FOC 的算力门槛被大幅拉低。

如今的 FOC 方案,一块几十元的 MCU 就能跑双电机甚至三电机控制,加上国产电机驱动芯片方案逐步成熟,FOC 已经从“高精尖”变成了“入门必备技能”。

2. 狗哥 FOC 作品的硬件架构概览

在讲解代码之前,先来看这套“狗哥FOC”作品的硬件组成。一套完整的 FOC 驱动系统,通常包含以下几个部分:

2.1 主控 MCU

作品选用的是 STM32 系列 MCU。具体型号可以根据项目需求选择:

  • STM32F103:入门首选,适合跑单电机 FOC,算力勉强够用,适合学习。
  • STM32G431/G474:推荐选择。内置 CORDIC 硬件加速单元,三角函数运算速度极快,专门为电机控制优化,双电机 FOC 无压力。
  • STM32H7:适合高端方案,主频高,可以跑复杂观测器算法或同时控制多台电机。

从“狗哥FOC”这套作品来看,采用带 CORDIC 的 STM32G4 系列是性价比最高的选择。

2.2 功率驱动部分

功率驱动板的核心是三相全桥逆变电路,也就是由 6 个 MOSFET 组成的 H 桥。MCU 输出 PWM 信号,通过栅极驱动芯片控制 MOSFET 的开关,从而给电机三相绕组施加电压。

常见驱动芯片包括:

  • 国产方案:FU6832、FU6812L 等,往往是驱动+MOS 一体化方案,适合小功率电机。
  • 分离方案:IR2101、EG2134、DRV8323 等,配合外部 MOSFET,适合大电流场合。

2.3 电流采样电路

FOC 控制质量高度依赖电流采样的精度。常见采样方式有三种:

  • 三电阻采样:三相下桥臂各串一个采样电阻,精度最高,成本也最高。
  • 双电阻采样:采集两相电流,第三相通过基尔霍夫定律计算得出。这是目前最常用的方案。
  • 单电阻采样:只用一个采样电阻,在 PWM 周期内多次触发 ADC,成本最低,但采样窗口较窄,对时序要求苛刻。

这套作品采用双电阻采样方案,兼顾了成本、精度和实现难度。

2.4 电机

控制对象是无刷电机(BLDC 或 PMSM)。对于 FOC 控制而言,PMSM(永磁同步电机)是天然最佳对象,因为它的反电动势是正弦波,与 FOC 的正弦电流控制天然匹配。

3. FOC 核心算法拆解:从三相电流到转矩控制

FOC 算法的本质,是把三相静止坐标系下的交流量,变换到两相旋转坐标系下的直流量。整个过程分为三步:

  1. Clarke 变换:将三相电流 (i_a, i_b, i_c) 变换到两相静止坐标系 (i_\alpha, i_\beta)。
  2. Park 变换:将两相静止坐标变换到两相旋转坐标 (i_d, i_q)。
  3. PID 控制:在旋转坐标系下分别控制 (i_d) 和 (i_q),输出 (V_d, V_q)。
  4. 逆 Park 变换与 SVPWM:将 (V_d, V_q) 变换回三相占空比,输出 PWM 波。

3.1 Clarke 变换

Clarke 变换的公式如下:

[ \begin{cases} i_\alpha = i_a \ i_\beta = \frac{i_a + 2i_b}{\sqrt{3}} \end{cases} ]

在实际代码中,为了减少浮点运算,通常会做等幅值变换并直接展开。C 语言实现如下:

// 文件路径:src/algorithm/clarke.c #include "foc_math.h" void clarke_transform(float ia, float ib, float *i_alpha, float *i_beta) { // 等幅值 Clarke 变换 // 注意:ic = -ia - ib,无需额外采样 *i_alpha = ia; *i_beta = (ia + 2.0f * ib) * 0.577350269f; // 1/sqrt(3) }

这里的0.577350269f就是 (1/\sqrt{3})。由于三相平衡时 (i_a + i_b + i_c = 0),所以只需要采集两相电流即可得到第三相电流。

3.2 Park 变换

Park 变换把静止坐标系旋转到与转子磁场同步的旋转坐标系,公式如下:

[ \begin{cases} i_d = i_\alpha \cos\theta + i_\beta \sin\theta \ i_q = -i_\alpha \sin\theta + i_\beta \cos\theta \end{cases} ]

在带 CORDIC 的 STM32G4 上,sin和cos可以直接调用硬件加速单元,大幅缩短运算时间。

// 文件路径:src/algorithm/park.c #include "foc_math.h" void park_transform(float i_alpha, float i_beta, float theta, float *i_d, float *i_q) { float sin_t = fast_sin(theta); // STM32G4 CORDIC 硬件加速 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; }

变换完成后,i_d代表磁通分量,i_q代表转矩分量。FOC 的核心控制逻辑就是:让i_d = 0,只控制i_q来调节转矩。

3.3 PID 控制器

有了i_d和i_q,接下来就是两个电流环 PID。外层的速度环输出i_q的给定值,内层的电流环控制实际电流跟踪给定。

// 文件路径:src/algorithm/pid.c typedef struct { float kp; float ki; float kd; float integral; float integral_limit; float output_limit; float prev_error; } pid_controller_t; float pid_update(pid_controller_t *pid, float target, float current) { float error = target - current; float output; // 积分项带限幅,防止积分饱和 pid->integral += error; if (pid->integral > pid->integral_limit) pid->integral = pid->integral_limit; if (pid->integral < -pid->integral_limit) pid->integral = -pid->integral_limit; output = pid->kp * error + pid->ki * pid->integral + pid->kd * (error - pid->prev_error); pid->prev_error = error; // 输出限幅 if (output > pid->output_limit) output = pid->output_limit; if (output < -pid->output_limit) output = -pid->output_limit; return output; }

实际工程中,PID 参数不是越大越好。Kp 过大会导致电流震荡,Ki 过大会导致超调。推荐从较小的 Kp 开始,逐步增大,待电流波形稳定后再加入 Ki。

3.4 有效磁链的计算问题

有一个经常被问到的问题:FOC 控制中有效磁链怎么计算?

有效磁链的概念在无感 FOC 中非常重要。对于表贴式永磁同步电机(SPMSM),有效磁链约等于永磁体磁链 (\psi_f),可以从电机铭牌或反电动势常数推算:

[ \psi_f = \frac{k_e}{\sqrt{3} \cdot \omega} ]

其中 (k_e) 是反电动势常数,单位通常是 V/krpm。在实际控制中,也可以通过观测器在线估算有效磁链,用于无感角度估算。

对于内置式永磁同步电机(IPMSM),由于存在凸极效应,有效磁链还包含电感不对称带来的分量,计算会更复杂。初学者如果使用表贴式电机,可以直接使用永磁磁链近似。

4. SVPWM 调制实现

4.1 什么是 SVPWM

SVPWM(Space Vector Pulse Width Modulation)即空间矢量脉宽调制。它的目标不是简单地生成三相互差 120° 的正弦电压,而是直接控制定子磁链矢量的运行轨迹,使其逼近圆形。

SVPWM 相比 SPWM 的优点是:

  • 母线电压利用率更高。SVPWM 的线性调制范围可以达到 (V_{dc}/\sqrt{3}),比 SPWM 提高约 15%。
  • 谐波含量更低,电机电流波形更平滑。

4.2 SVPWM 的 C 语言实现

SVPWM 的核心是扇区判断、作用时间计算和占空比生成。这里给出一个最常用的七段式 SVPWM 实现框架。

// 文件路径:src/algorithm/svpwm.c #include "foc_math.h" #include <math.h> typedef struct { float ta; float tb; float tc; } svpwm_output_t; svpwm_output_t svpwm_calc(float v_alpha, float v_beta, float v_dc) { svpwm_output_t out; float v1, v2, v3; float t1, t2; int sector; float ts = 1.0f; // 归一化周期,实际需要乘 PWM 周期计数 v1 = v_beta; v2 = 0.5f * (sqrtf(3.0f) * v_alpha - v_beta); v3 = -0.5f * (sqrtf(3.0f) * v_alpha + v_beta); // 扇区判断 if (v1 > 0) sector = 1; else sector = 4; if (v2 > 0) sector = (sector == 1) ? 5 : 3; if (v3 > 0) sector = (sector == 3) ? 2 : (sector == 5 ? 6 : sector); // 根据扇区计算相邻矢量的作用时间 switch (sector) { case 1: t1 = v2; t2 = v1; break; case 2: t1 = -v3; t2 = -v1; break; case 3: t1 = -v1; t2 = v3; break; case 4: t1 = -v2; t2 = -v1; break; case 5: t1 = v1; t2 = -v3; break; case 6: t1 = -v1; t2 = -v2; break; default: t1 = 0; t2 = 0; break; } // 七段式插入零矢量 float tsum = t1 + t2; if (tsum > 1.0f) { // 过调制处理:按比例缩小 float scale = 1.0f / tsum; t1 *= scale; t2 *= scale; tsum = 1.0f; } float t0 = (1.0f - tsum) * 0.5f; float t1_2 = t1 + t0; float t2_2 = t2 + t1_2; // 三相占空比输出,需根据扇区映射到 A/B/C switch (sector) { case 1: out.ta = t1_2; out.tb = t2_2; out.tc = t0; break; case 2: out.ta = t2_2; out.tb = t1_2; out.tc = t0; break; case 3: out.ta = t0; out.tb = t1_2; out.tc = t2_2; break; case 4: out.ta = t0; out.tb = t2_2; out.tc = t1_2; break; case 5: out.ta = t2_2; out.tb = t0; out.tc = t1_2; break; case 6: out.ta = t1_2; out.tb = t0; out.tc = t2_2; break; default: out.ta = 0; out.tb = 0; out.tc = 0; break; } return out; }

这段代码将电压矢量在 αβ 坐标系下的分量转换成三相占空比,输出范围在 0~1 之间。实际写入定时器比较寄存器前,需要乘以定时器的 ARR 值。

5. 电流采样与定时器触发 ADC

5.1 为什么电流采样必须和 PWM 同步

FOC 电流环的质量,很大程度上取决于“什么时候采样”。PWM 波形为高频开关信号,电流纹波呈现三角波形状。如果在 MOSFET 开关瞬间采样,噪声极大;如果采样点不在电流纹波的中心,采到的电流也偏离平均值。

正确的做法是:在 PWM 中心对齐模式下,将 ADC 触发点安排在定时器计数器的峰值点(也就是上桥臂导通中点),此时电流纹波对称,采到的电流最能代表一个周期内的平均电流。

5.2 STM32 定时器触发 ADC 配置

在 STM32 中,通常使用高级定时器 TIM1 或 TIM8 的通道输出来产生 PWM,并通过 TRGO(触发输出)事件来触发 ADC 注入组采样。

以下是基于 STM32CubeMX 生成的配置思路,关键代码片段如下:

// 文件路径:src/hal/tim_adc_trigger.c // 定时器时基配置:中心对齐模式 1 TIM_HandleTypeDef htim1; htim1.Instance = TIM1; htim1.Init.Prescaler = 72 - 1; // 72MHz / 72 = 1MHz htim1.Init.CounterMode = TIM_COUNTERMODE_CENTERALIGNED1; htim1.Init.Period = 1000 - 1; // PWM 频率 = 1MHz / 1000 = 1kHz htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; htim1.Init.RepetitionCounter = 0; HAL_TIM_PWM_Init(&htim1); // 配置 TRGO:更新事件触发 ADC TIM_MasterConfigTypeDef sMasterConfig = {0}; sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig); // ADC 注入组配置:由外部触发启动 ADC_InjectionConfTypeDef sConfigInjected = {0}; sConfigInjected.InjectedChannel = ADC_CHANNEL_0; sConfigInjected.InjectedRank = 1; sConfigInjected.InjectedNbrOfConversion = 2; sConfigInjected.InjectedSamplingTime = ADC_SAMPLETIME_71CYCLES_5; sConfigInjected.ExternalTrigInjecConv = ADC_EXTERNALTRIGINJECCONV_T1_TRGO; sConfigInjected.AutoInjectedConv = DISABLE; sConfigInjected.InjectedDiscontinuousConvMode = DISABLE; sConfigInjected.InjectedOffset = 0; HAL_ADCEx_InjectedConfigChannel(&hadc1, &sConfigInjected, 0);

这段配置的本质是:定时器计数到周期中心时触发 ADC,采集电流的瞬间正好避开 PWM 开关噪声,确保电流环每次计算用的都是“干净”的电流值。

5.3 双电阻采样与双电机采样组

双电阻采样的挑战在于:不同扇区下 PWM 占空比各不相同,某一相上桥臂导通时间过短时,对应的采样窗口可能不够 ADC 完成转换。

“狗哥FOC”作品采用了“采样组扫描双触发”的方式,也就是在一个 PWM 周期内选择两个不同的触发点,分别采样两相电流,保证任意扇区下都能获得足够宽的采样窗口。

对于双电机应用,由于两路 PWM 的相位需要彼此独立,通常使用 TIM1 和 TIM8 两路高级定时器,分别触发两路 ADC(或者同一 ADC 的注入组轮询),从而实现双电机 FOC 的同步采样。

6. 无感 FOC:如何用反电动势估算角度

6.1 为什么需要无感控制

有感 FOC 依赖编码器或霍尔传感器获得转子位置。编码器精度高,但成本高、安装难度大;霍尔传感器成本低,但分辨率低,低速性能有限。

在很多量产场景(如风机、泵、无人机),空间和成本不允许安装编码器,这时候就需要无感 FOC。无感 FOC 的核心问题只有一个:如何在不安装传感器的情况下,实时获取转子角度。

6.2 反电动势估算角度的基本原理

电机旋转时,定子绕组中会产生反电动势(BEMF)。反电动势的相位与转子位置直接相关。

理想情况下,可以通过检测反电动势过零点来获得换相时机,但这是方波驱动的思路。FOC 需要连续的角度信息,所以通常采用状态观测器来估算反电动势,再从中提取角度。

最经典的实现是滑模观测器(SMO):

  • 利用电流误差构造滑模面。
  • 通过滑模控制律估算反电动势分量 (e_\alpha, e_\beta)。
  • 用反正切函数或锁相环提取转子角度。

6.3 反电动势估算角度的代码框架

下面给出一个基于滑模观测器的角度估算核心逻辑,简化了滤波部分,但总体结构完整。

// 文件路径:src/observer/smo_observer.c typedef struct { float Rs; // 定子电阻 float Ls; // 定子电感 float k_slide; // 滑模增益 float e_alpha; // 估算反电动势 α 轴分量 float e_beta; // 估算反电动势 β 轴分量 float theta; // 估算角度 float omega; // 估算电角速度 } smo_observer_t; void smo_update(smo_observer_t *smo, float i_alpha, float i_beta, float v_alpha, float v_beta, float dt) { // 电流误差 float err_alpha = i_alpha - smo->i_alpha_est; float err_beta = i_beta - smo->i_beta_est; // 滑模控制律 float z_alpha = smo->k_slide * sign(err_alpha); float z_beta = smo->k_slide * sign(err_beta); // 电流观测器更新 smo->i_alpha_est += (1.0f / smo->Ls) * (v_alpha - smo->Rs * smo->i_alpha_est - z_alpha) * dt; smo->i_beta_est += (1.0f / smo->Ls) * (v_beta - smo->Rs * smo->i_beta_est - z_beta) * dt; // 反电动势通过低通滤波器提取 smo->e_alpha += (z_alpha - smo->e_alpha) * smo->lp_fc * dt; smo->e_beta += (z_beta - smo->e_beta) * smo->lp_fc * dt; // 角度估算:atan2 方式 smo->theta = atan2f(-smo->e_alpha, smo->e_beta); }

无感 FOC 的启动是另一个难点。零速和低速时反电动势几乎为零,观测器无法提供有效角度,所以通常需要“强拉启动”或“高频注入”策略:

  • 强拉启动(I/F 启动):先以固定电流幅值和递增频率开环拖动电机,等到反电动势足够大后再切入闭环。
  • 高频注入:适合内置式电机,利用凸极效应在低速时辨识角度,复杂度较高。

对于“狗哥FOC”这类以学习和通用电机控制为主的作品,采用 I/F 强拉启动 + 中高速切入滑模观测器的方案比较可靠。

7. 完整实战:双电机 FOC 代码框架

7.1 项目结构

双电机 FOC 项目的代码结构,建议按以下方式组织,便于维护和移植:

MotorFOC/ ├── Core/ │ ├── Inc/ │ └── Src/ ├── Drivers/ │ ├── CMSIS/ │ └── STM32G4xx_HAL_Driver/ ├── FOC/ │ ├── math/ │ │ ├── clarke.c │ │ ├── park.c │ │ ├── svpwm.c │ │ └── pid.c │ ├── observer/ │ │ └── smo_observer.c │ ├── sensor/ │ │ ├── encoder.c │ │ └── hall.c │ └── control/ │ ├── foc_control.c │ └── speed_control.c └── User/ └── main.c

7.2 双电机 FOC 控制核心代码

双电机控制的关键在于两个电机控制任务的调度。最常用的方式是利用定时器中断,以固定的频率(如 10kHz)执行电流环。

// 文件路径:User/main.c(双电机 FOC 主循环框架) #include "foc_control.h" // 电机控制句柄 foc_handle_t motor1; foc_handle_t motor2; // TIM1 中断:电机1 电流环 10kHz void TIM1_UP_TIM16_IRQHandler(void) { if (__HAL_TIM_GET_FLAG(&htim1, TIM_FLAG_UPDATE)) { __HAL_TIM_CLEAR_FLAG(&htim1, TIM_FLAG_UPDATE); // 电流采样已在 ADC 中断中完成,这里直接执行 FOC 运算 foc_current_loop(&motor1); } } // TIM8 中断:电机2 电流环 10kHz void TIM8_UP_TIM13_IRQHandler(void) { if (__HAL_TIM_GET_FLAG(&htim8, TIM_FLAG_UPDATE)) { __HAL_TIM_CLEAR_FLAG(&htim8, TIM_FLAG_UPDATE); foc_current_loop(&motor2); } } int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_TIM1_Init(); MX_TIM8_Init(); MX_ADC1_Init(); MX_ADC2_Init(); // 初始化电机参数 foc_init(&motor1, &motor1_param); foc_init(&motor2, &motor2_param); // 使能定时器中断 HAL_TIM_Base_Start_IT(&htim1); HAL_TIM_Base_Start_IT(&htim8); // 启动 PWM 输出 HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1); HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_2); HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_3); HAL_TIM_PWM_Start(&htim8, TIM_CHANNEL_1); HAL_TIM_PWM_Start(&htim8, TIM_CHANNEL_2); HAL_TIM_PWM_Start(&htim8, TIM_CHANNEL_3); while (1) { // 速度环任务,可降低频率到 1kHz speed_loop_task(); HAL_Delay(1); } }

这里需要提醒的是:两个电机的定时器中断不要共用同一优先级,避免同时进入中断导致资源冲突。建议将 TIM1 中断优先级设置比 TIM8 高,或者使用互不嵌套的独立优先级组配置。

7.3 电流环核心函数

电流环任务内部执行完整的 FOC 算法链路:

// 文件路径:src/control/foc_control.c void foc_current_loop(foc_handle_t *h) { float ia, ib, ic; float i_alpha, i_beta; float i_d, i_q; float v_d, v_q; float theta = h->theta; // 1. 读取两相电流 ia = h->adc.ia; ib = h->adc.ib; ic = -ia - ib; // 2. Clarke 变换 clarke_transform(ia, ib, &i_alpha, &i_beta); // 3. Park 变换 park_transform(i_alpha, i_beta, theta, &i_d, &i_q); // 4. 电流环 PID v_d = pid_update(&h->pid_d, h->id_ref, i_d); v_q = pid_update(&h->pid_q, h->iq_ref, i_q); // 5. 逆 Park 变换得 alpha/beta 轴电压 float sin_t = fast_sin(theta); float cos_t = fast_cos(theta); float v_alpha = v_d * cos_t - v_q * sin_t; float v_beta = v_d * sin_t + v_q * cos_t; // 6. SVPWM 输出 svpwm_output_t out = svpwm_calc(v_alpha, v_beta, h->v_dc); // 7. 写入定时器比较寄存器 write_duty(&h->tim, out.ta, out.tb, out.tc); }

这个循环就是 FOC 的全部核心。所谓“从零实现 STM32 无刷电机 FOC”,本质上就是把这一条链路跑通、跑稳。

8. STM32G4 CORDIC 硬件加速的价值

8.1 CORDIC 能加速什么

CORDIC(Coordinate Rotation Digital Computer)是一种通过加法和移位实现三角函数、双曲函数、开方等运算的硬件单元。在 FOC 算法中,Park 变换、逆 Park 变换和角度估算需要大量使用sin、cos和atan2函数。

纯软件实现sinf在 M4 内核上大约需要几百个周期,而 CORDIC 硬件加速只需几十个周期。对于 10kHz 电流环、双电机场景,省下的算力非常可观。

8.2 STM32G4 的 CORDIC 配置示例

使用 STM32G4 的 CORDIC 计算正余弦,可以通过寄存器直接操作。以下是一个精简示例:

// 文件路径:src/hal/cordic_helper.c #include "stm32g4xx_hal.h" // 计算 cos(theta) 和 sin(theta) void cordic_sin_cos(float theta, float *sin_out, float *cos_out) { // 将角度转换为 Q1.15 或 Q1.31 格式,这里使用 Q1.31 int32_t angle_q31 = (int32_t)(theta * 2147483648.0f / 3.14159265358979f); // 配置 CORDIC 参数:正弦余弦模式 CORDIC->CSR = (CORDIC_FUNCTION_COSINE | CORDIC_PRECISION_6CYCLES | CORDIC_SCALE_0 | CORDIC_NBWRITE_1 | CORDIC_NBREAD_2); // 写入角度 CORDIC->WDATA = angle_q31; // 等待运算完成 while ((CORDIC->CSR & CORDIC_CSR_RRDY) == 0); // 读取结果 int32_t cos_q31 = CORDIC->RDATA; int32_t sin_q31 = CORDIC->RDATA; // 转回浮点 *cos_out = (float)cos_q31 / 2147483648.0f; *sin_out = (float)sin_q31 / 2147483648.0f; }

在 STM32CubeF4/G4 固件库更新版本中,HAL 也提供了HAL_CORDIC_Calculate这类封装接口,直接使用即可。CORDIC 的引入可以让电流环从 20kHz 提升到 40kHz,对抑制电流噪声和提升刚度有明显帮助。

9. 常见问题与排查思路

FOC 开发过程中,最容易遇到的问题,这里整理成一张排查表。

问题现象常见原因解决思路
电机不转,PWM 有输出启动方式错误或占空比过小确认是否已执行开环强拉启动,检查给定电压是否足够
电机抖动、啸叫角度传感器方向或零位不对检查编码器/观测器的角度方向是否与电机旋转方向一致
电流波形正弦度差采样点不在 PWM 中心调整定时器 TRGO 触发点,确保采样时刻在计数峰值
母线电流异常大电流环 PID 参数不合适先调 Kp 调稳波形,再加 Ki,注意输出限幅
运行中偶发失步采样窗口不够,扇区切换时电流失真改用双电阻采样组或调整采样触发策略
无感启动失败反电动势信号太弱,观测器发散提高强拉电流,确认滑模增益足够,检查低通滤波器截止频率
双电机互相干扰两个定时器中断优先级冲突或共用地线干扰分开供电,提升采样隔离,配置合理中断优先级
开环转但闭环卡死电角度初始值错误标定编码器零位,或在无感方案里用 I/F 启动预定位

10. 最佳实践与工程建议

10.1 采样与保护的工程细节

  • 电流采样电阻要使用高精度低感电阻,推荐 1% 精度、低温度系数。
  • 运放电路要做偏置电压标定,上电初始化时采集一次零电流偏置并保存。
  • 过流保护不能只依赖软件,硬件比较器直接封锁 PWM 输出才是兜底方案。
  • 主控的 ADC 参考电压建议使用独立基准源,避免电源波动影响采样精度。

10.2 代码工程建议

  • 电流环中断函数里只做 FOC 计算和 PWM 更新,不要做日志打印、浮点格式化等耗时操作。
  • 速度环和位置环放在主循环里执行,与电流环剥离,降低耦合度。
  • PID 参数使用结构体统一管理,方便调试时在线修改和固化。
  • 电机参数(电阻、电感、极对数)做成独立结构体,换电机时只改参数不改代码。

10.3 安全边界

电机驱动是强电和功率电子的结合,调试过程中必须注意:

  • 先使用小功率电源,串联限流电阻或电流表,避免炸管。
  • 第一次上电不要直接带载,先空载验证电流环。
  • 修改 PID 参数前,确认输出限幅和过流保护阈值已设置。
  • 不要带电插拔电机线,感性负载瞬间高压容易击穿驱动芯片。

11. 总结与下一步学习路线

这套“狗哥FOC”作品把无刷电机 FOC 驱动从硬件到软件完整落地,涵盖了 FOC 数学变换、电流采样时序设计、STM32G4 硬件加速、双电机架构以及无感观测器的核心思路。如果能把本文中的 Clarke/Park 变换、SVPWM、电流环 PID 和 ADC 触发配置完全吃透,再配合一套实际的开发板跑起来,FOC 就算是真正入门了。

接下来可以按这个顺序继续深入:

  • 先跑通单电机开环:验证 PWM、ADC 采样和电流波形。
  • 再实现单电机闭环:接入编码器,调通电流环和速度环。
  • 然后挑战无感 FOC:从 I/F 强拉启动开始,逐步切换到滑模观测器闭环。
  • 最后扩展双电机:重点解决采样隔离、中断调度和资源竞争问题。

FOC 是一个理论和工程结合得非常紧密的方向。理论公式虽然看起来复杂,但只要按 Clarke → Park → PID → SVPWM 这条主线一步步走,每步用波形和数据验证,很快就能建立起完整的调试直觉。

如果本文对你有帮助,不妨收藏备用。下次调试 FOC 遇到问题时,再回来对照排查,应该能少走不少弯路。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询