电机驱动开发学习19. 霍尔 BLDC 三段式 FOC 启动算法
- [一、为什么需要三段式 FOC 启动](#一、为什么需要三段式 FOC 启动)
-
- [1.1 三段式启动是什么](#1.1 三段式启动是什么)
- [1.2 与六步「三段启动」的区别](#1.2 与六步「三段启动」的区别)
- 二、实验简介
-
- [2.1 本章目标](#2.1 本章目标)
- [2.2 硬件](#2.2 硬件)
- 三、程序设计
-
- [3.1 目录结构(相对 lesson18 新增/变更)](#3.1 目录结构(相对 lesson18 新增/变更))
- [3.2 状态机流程](#3.2 状态机流程)
-
- [阶段 1:定位(Align)](#阶段 1:定位(Align))
- [阶段 2:拖转(Drag)](#阶段 2:拖转(Drag))
- [阶段 3:运行(Run)](#阶段 3:运行(Run))
- [3.3 关键代码](#3.3 关键代码)
- 四、调参与串口命令
-
- [4.1 串口(与 lesson18 相同,启动自动化)](#4.1 串口(与 lesson18 相同,启动自动化))
- [4.2 FreeMASTER 建议通道](#4.2 FreeMASTER 建议通道)
- [4.3 现场整定参数(与 `bsp_foc_boot.c` 一致)](#4.3 现场整定参数(与
bsp_foc_boot.c一致))
- 五、实验现象
-
- [5.1 预期波形](#5.1 预期波形)
- [5.2 VOFA+ 三通道](#5.2 VOFA+ 三通道)
- 六、本章小结

一、为什么需要三段式 FOC 启动
lesson17/18 已实现 速度外环 + DQ 电流内环 + SVPWM ,电角度 θ 来自 霍尔 6 态 + TIM5 扇区内插值 。但在 转子静止或极低速 时,存在两个根本矛盾:
| 问题 | 原因 | 后果 |
|---|---|---|
| θ 不更新 | 霍尔只在转子转过 60° 电角度时才跳变 | Park 变换用的 θ 与实际转子位置脱节 |
| 无法直接闭环 | 电流环需要准确 θ 才能解耦 Id/Iq | 一上电就 s 1 / w 1,电机抖动、堵转或倒转 |
lesson16 已说明:不是 PI 公式错,而是 零速时霍尔给不了连续 θ 。
本章在 lesson18 工程基础上,增加工业界常用的 三段式启动 ,解决「从静止到稳定闭环」的衔接。
硬件输出 :Align/Drag 与 Run 均走 六步桥单占空比 (接口 2);SVPWM 仅在 Run 中作 FreeMASTER 扇区监视,不驱动三相 CCR。
1.1 三段式启动是什么
借鉴无感 BLDC「预定位 → 外同步加速 → 自同步切换」思路,结合 霍尔 FOC 特点,本章划分为:

| 阶段 | 英文名 | θ 来源 | 执行方式 | 目标 |
|---|---|---|---|---|
| 1 定位 | Align | 当前霍尔扇区中心 | 六步固定占空比(lesson4 开环,不用 FOC) | 转子对齐到已知扇区 |
| 2 拖转 | Drag | 软件插值 θ(扇区中心 + 步内比例) | 六步定时/边沿换相 + duty 斜坡 | 加速到霍尔边沿可靠 |
| 3 运行 | Run | 霍尔 + TIM5 插值(lesson17) | FOC 速度/电流双环 + |V|→单占空比(六步板) | 稳定闭环调速 |
说明 :Align/Drag 在野火六步桥上刻意不用 InvPark/SVPWM(与真实导通相不匹配);Run 阶段才是完整 FOC 双环。SVPWM 模块仍参与 扇区监视(FreeMASTER),硬件输出走 lesson17 的 |V| 映射------与 lesson11 同级 PWM 利用率,
1.2 与六步「三段启动」的区别
野火官方 六步换相 例程也有「定位 → 升频换相 → 霍尔闭环」,本章在此基础上叠加 FOC 运行阶段:
- Align/Drag :与 lesson4 相同六步开环(定位 duty + 拖转换相序),但维护 开环 θ 供监视与交接;
- Drag 期间
motor_set_openloop_comm()强制换相步,霍尔已跟上时可提前对齐; - Run :θ 切回霍尔插值,启用 速度→Iq→电流 PI→InvPark 双环,输出 |V|→单占空比;
- 切换条件 (
bsp_foc_boot.c,拖转满FOC_BOOT_DRAG_MIN_MS=600后,满足任一):- 霍尔边沿 ≥ 2 且 RPM ∈ **60**, target+120(下限至少到 200);
- 或强拖步数 ≥ 18 且边沿 ≥ 2 且 RPM 同上。
二、实验简介
2.1 本章目标
在 lesson18 工程基础上新增 bsp_foc_boot.c 三段式启动状态机:
w 1/s 1时 自动 执行 Align → Drag → Run,无需手动开环v命令;- FreeMASTER 监视
g_fm_boot_stage(0 idle / 1 align / 2 drag / 3 run / 4 fail); - 拖转失败(10 s 超时)触发 fault=3 并关断;
- 运行阶段保留 lesson17 速度环 + DQ 电流环;六步板上 Run 输出为 |V|→单占空比(lesson18 SVPWM 作扇区监视)。
2.2 硬件
与 lesson4~18 相同:野火骄阳 F407 + 无刷驱动板 + 霍尔 BLDC ,「无刷电机驱动接口 2」排线。
三、程序设计
3.1 目录结构(相对 lesson18 新增/变更)
User/
├── foc/
│ ├── bsp_foc_boot.c/h ★ 三段式启动状态机(本章核心)
│ ├── bsp_foc.c/h ★ θ 在 Align/Drag 改读开环角
│ └── bsp_svpwm.c/h (lesson18 沿用,Run 仅监视)
├── pid/
│ └── bsp_pid_app.c/h ★ 启停时触发 foc_boot_request();Run 用 |V|→duty
├── tim/
│ └── bsp_motor_tim.c/h ★ motor_set_openloop_comm() 强制换相
└── main.c
3.2 状态机流程
#mermaid-svg-pchS6kx0kucR2lcX{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-pchS6kx0kucR2lcX .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-pchS6kx0kucR2lcX .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-pchS6kx0kucR2lcX .error-icon{fill:#552222;}#mermaid-svg-pchS6kx0kucR2lcX .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-pchS6kx0kucR2lcX .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-pchS6kx0kucR2lcX .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-pchS6kx0kucR2lcX .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-pchS6kx0kucR2lcX .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-pchS6kx0kucR2lcX .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-pchS6kx0kucR2lcX .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-pchS6kx0kucR2lcX .marker{fill:#333333;stroke:#333333;}#mermaid-svg-pchS6kx0kucR2lcX .marker.cross{stroke:#333333;}#mermaid-svg-pchS6kx0kucR2lcX svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-pchS6kx0kucR2lcX p{margin:0;}#mermaid-svg-pchS6kx0kucR2lcX defs #statediagram-barbEnd{fill:#333333;stroke:#333333;}#mermaid-svg-pchS6kx0kucR2lcX g.stateGroup text{fill:#9370DB;stroke:none;font-size:10px;}#mermaid-svg-pchS6kx0kucR2lcX g.stateGroup text{fill:#333;stroke:none;font-size:10px;}#mermaid-svg-pchS6kx0kucR2lcX g.stateGroup .state-title{font-weight:bolder;fill:#131300;}#mermaid-svg-pchS6kx0kucR2lcX g.stateGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-pchS6kx0kucR2lcX g.stateGroup line{stroke:#333333;stroke-width:1;}#mermaid-svg-pchS6kx0kucR2lcX .transition{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-pchS6kx0kucR2lcX .stateGroup .composit{fill:white;border-bottom:1px;}#mermaid-svg-pchS6kx0kucR2lcX .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px;}#mermaid-svg-pchS6kx0kucR2lcX .state-note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-pchS6kx0kucR2lcX .state-note text{fill:black;stroke:none;font-size:10px;}#mermaid-svg-pchS6kx0kucR2lcX .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-pchS6kx0kucR2lcX .edgeLabel .label rect{fill:#ECECFF;opacity:0.5;}#mermaid-svg-pchS6kx0kucR2lcX .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-pchS6kx0kucR2lcX .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-pchS6kx0kucR2lcX .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-pchS6kx0kucR2lcX .edgeLabel .label text{fill:#333;}#mermaid-svg-pchS6kx0kucR2lcX .label div .edgeLabel{color:#333;}#mermaid-svg-pchS6kx0kucR2lcX .stateLabel text{fill:#131300;font-size:10px;font-weight:bold;}#mermaid-svg-pchS6kx0kucR2lcX .node circle.state-start{fill:#333333;stroke:#333333;}#mermaid-svg-pchS6kx0kucR2lcX .node .fork-join{fill:#333333;stroke:#333333;}#mermaid-svg-pchS6kx0kucR2lcX .node circle.state-end{fill:#9370DB;stroke:white;stroke-width:1.5;}#mermaid-svg-pchS6kx0kucR2lcX .end-state-inner{fill:white;stroke-width:1.5;}#mermaid-svg-pchS6kx0kucR2lcX .node rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-pchS6kx0kucR2lcX .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-pchS6kx0kucR2lcX #statediagram-barbEnd{fill:#333333;}#mermaid-svg-pchS6kx0kucR2lcX .statediagram-cluster rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-pchS6kx0kucR2lcX .cluster-label,#mermaid-svg-pchS6kx0kucR2lcX .nodeLabel{color:#131300;}#mermaid-svg-pchS6kx0kucR2lcX .statediagram-cluster rect.outer{rx:5px;ry:5px;}#mermaid-svg-pchS6kx0kucR2lcX .statediagram-state .divider{stroke:#9370DB;}#mermaid-svg-pchS6kx0kucR2lcX .statediagram-state .title-state{rx:5px;ry:5px;}#mermaid-svg-pchS6kx0kucR2lcX .statediagram-cluster.statediagram-cluster .inner{fill:white;}#mermaid-svg-pchS6kx0kucR2lcX .statediagram-cluster.statediagram-cluster-alt .inner{fill:#f0f0f0;}#mermaid-svg-pchS6kx0kucR2lcX .statediagram-cluster .inner{rx:0;ry:0;}#mermaid-svg-pchS6kx0kucR2lcX .statediagram-state rect.basic{rx:5px;ry:5px;}#mermaid-svg-pchS6kx0kucR2lcX .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#f0f0f0;}#mermaid-svg-pchS6kx0kucR2lcX .note-edge{stroke-dasharray:5;}#mermaid-svg-pchS6kx0kucR2lcX .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-pchS6kx0kucR2lcX .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-pchS6kx0kucR2lcX .statediagram-note text{fill:black;}#mermaid-svg-pchS6kx0kucR2lcX .statediagram-note .nodeLabel{color:black;}#mermaid-svg-pchS6kx0kucR2lcX .statediagram .edgeLabel{color:red;}#mermaid-svg-pchS6kx0kucR2lcX #dependencyStart,#mermaid-svg-pchS6kx0kucR2lcX #dependencyEnd{fill:#333333;stroke:#333333;stroke-width:1;}#mermaid-svg-pchS6kx0kucR2lcX .statediagramTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-pchS6kx0kucR2lcX :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} w1 / s1
500ms
edges>=2 且 RPM>=60 或 steps>=18
10s 超时
s0 / 故障关断
复位
s0 / 故障
s0 / 故障
IDLE
ALIGN
DRAG
RUN
FAIL
阶段 1:定位(Align)
- 读当前霍尔态,
motor_set_openloop_comm(1, hall)强制该步六步换相; - 施加固定占空比
FOC_BOOT_ALIGN_DUTY=1200(不用 FOC / 不用 Vd)约FOC_BOOT_ALIGN_MS=500ms。
阶段 2:拖转(Drag)
- 按霍尔正转序
1→3→2→6→4→5→1定时换相 (速率 4→22 Hz 斜坡)+ 占空比 1200→4000(每周期 +80); - 若下一霍尔态已到位,则 提前同步 强制换相步(转子跟上时不再盲拖);
- 切换条件见 1.2 (最短拖转 600 ms);超时 10 s → fail=3。
- Run 阶段才启用 FOC 双闭环(Clark/Park → PI → InvPark → |V|→duty)。
阶段 3:运行(Run)
foc_get_electrical_theta():Align/Drag 读foc_boot_get_theta(),Run/Idle 恢复霍尔 + TIM5 插值;foc_spd_poll:速度环 →g_fm_target_iq;dq_curr_poll:电流环 → Vd/Vq → InvPark →dq_apply_vmag_sixstep_output()(六步板);svpwm_calc()仅更新g_fm_svpwm_sector等监视量,不set三相独立 CCR 驱动桥臂。
3.3 关键代码
启动请求(bsp_foc_boot.c)
c
/**
* @brief FOC三段式启动:启动请求触发函数
* @note 当系统收到电机启动指令时调用,进入三段式启动流程第一阶段【转子定位Align】
* 适用:带霍尔传感器BLDC/内置霍尔PMSM,霍尔原始扇区作为初始位置依据
* 三段式启动流程:ALIGN定位 → DRAG开环拖转 → RUN霍尔闭环FOC
*/
void foc_boot_request(void)
{
// 1. 实时读取当前霍尔电平,获取霍尔原始扇区编号(0~5 六扇区)
hall = motor_hall_read_now();
// 2. 将开环换相步长初始化为当前霍尔扇区
// 开环拖转阶段会基于该变量持续递增实现强制换相
s_openloop_step = hall;
// 3. 根据当前霍尔扇区,计算该扇区中心点电角度
// 目的:定位阶段向中心点角度施加恒定电压,把转子牢牢吸附到该角度
s_theta = foc_boot_hall_center_theta(hall);
// 4. 启动状态机切换至【转子对齐阶段 ALIGN】(三段式第1阶段)
s_stage = FOC_BOOT_ALIGN;
// 5. 配置开环换相模式,参数1:使能开环;hall:初始霍尔扇区基准
motor_set_openloop_comm(1U, hall);
// 6. 施加对齐占空比,输出恒定矢量电压,执行转子定位
// 持续通电一段时间(例500ms),保证转子机械角度收敛到目标角度
foc_boot_apply_duty(FOC_BOOT_ALIGN_DUTY);
// ...后续省略:对齐阶段定时计时、超时后自动跳入开环拖转DRAG阶段
}
拖转阶段 θ 插值与切换(bsp_foc_boot.c)
c
/* 扇区中心 + 步内比例,供 foc_get_electrical_theta() 在 Drag 期间读取 */
s_theta = foc_boot_hall_center_theta(s_openloop_step);
s_theta += (sub_ratio - 0.5f) * (FOC_PI / 3.0f);
/* 切换:边沿≥2 且 RPM≥60,或强拖步数≥18 且边沿≥2 */
if (s_hall_edges >= FOC_BOOT_HANDOVER_EDGES && rpm >= FOC_BOOT_HANDOVER_RPM)
foc_boot_enter_run();
θ 来源切换(bsp_foc.c)
c
float foc_get_electrical_theta(void)
{
if (foc_boot_is_active()) /* Align / Drag */
return foc_boot_get_theta();
/* 霍尔 + TIM5 插值 ... */
}
开环强制换相(bsp_motor_tim.c)
c
if (s_openloop_comm)
bldcm_commutate(s_openloop_step);
else
bldcm_commutate(get_hall_state());
| 模块 | lesson11 | lesson17 | lesson18 | lesson19(本章) |
|---|---|---|---|---|
| 速度外环 | ✓ 直出 PWM | ✓→Iq | ✓ | ✓(Run 阶段) |
| 输出映射 | PWM 0~5500 | |V|→单占空比 | 七段 SVPWM | Run:|V|→单占空比(六步板) |
| SVPWM | --- | --- | ✓ | 仅 FreeMASTER 扇区监视 |
| 电角度 | 霍尔换相 | 霍尔中心 | 霍尔+插值 | 拖转开环 θ + 运行霍尔 |
| 启动 | 无 | 无 | 无 | 三段式状态机 |
四、调参与串口命令
4.1 串口(与 lesson18 相同,启动自动化)
| 命令 | 含义 |
|---|---|
t 500 |
目标 RPM 500 |
w 1 |
开速度环(自动三段式启动 → 闭环) |
s 1 |
仅电流环(同样走三段式启动) |
s 0 |
关断并重校电流零偏 |
pid |
打印状态(含 boot_stage) |
? |
帮助 |
推荐首次上电流程:
- 上电等待
cal=1(电流零偏完成) t 500→w 1- 观察串口:
FOC boot start→align done -> drag→FOC boot handover - FreeMASTER 看
g_fm_boot_stage从 1→2→3
4.2 FreeMASTER 建议通道
| 变量 | 含义 |
|---|---|
g_fm_boot_stage |
0 idle / 1 align / 2 drag / 3 run / 4 fail |
g_fm_boot_theta_deg |
开环/运行电角度 |
g_fm_boot_omega |
拖转等效电角速度(由换相频率换算) |
g_fm_boot_hall_edges |
拖转阶段霍尔边沿计数 |
g_fm_target_rpm / g_fm_actual_rpm |
速度环(Run 后) |
g_fm_iq_actual |
拖转/运行 Iq 反馈 |
4.3 现场整定参数(与 bsp_foc_boot.c 一致)
| 宏 | 默认值 | 说明 |
|---|---|---|
FOC_BOOT_ALIGN_MS |
500 | 定位时间 (ms) |
FOC_BOOT_ALIGN_DUTY |
1200 | 定位六步占空比(不是 Vd) |
FOC_BOOT_DRAG_DUTY_MAX |
4000 | 拖转占空比上限 |
FOC_BOOT_DRAG_DUTY_RAMP |
80 | 拖转每周期 duty 增量 |
FOC_BOOT_DRAG_RATE_INIT_HZ |
4 | 拖转换相初频 (Hz) |
FOC_BOOT_DRAG_RATE_MAX_HZ |
22 | 拖转换相最高频 |
FOC_BOOT_DRAG_MIN_MS |
600 | 允许切换前最短拖转时间 |
FOC_BOOT_HANDOVER_RPM |
60 | 切换最低 RPM |
FOC_BOOT_HANDOVER_EDGES |
2 | 切换最少霍尔边沿 |
FOC_BOOT_HANDOVER_STEPS |
18 | 强拖步数备选切换条件 |
FOC_BOOT_DRAG_TIMEOUT_MS |
10000 | 拖转超时 (ms) |
FOC_BOOT_RUN_STALL_GRACE_MS |
4000 | Run 交接后堵转保护放宽 |
README 旧稿中的
FOC_BOOT_ALIGN_VD_V/FOC_BOOT_DRAG_IQ_MA/FOC_BOOT_OMEGA_MAX在本工程中不存在(Align/Drag 不走 Vd/Iq FOC)。
常见问题:
- 拖转超时 fail=3 :提高
FOC_BOOT_DRAG_DUTY_MAX/ 加快FOC_BOOT_DRAG_RATE_*,或略降FOC_BOOT_HANDOVER_RPM - Run 后转速卡在 ~350、改 t 无效 :确认 Run 走的是
dq_apply_vmag_sixstep_output(|V|),不是三相 SVPWM 直驱;可t 700再w 1复测 - 仍低于 lesson11 :FOC 双环有电流限幅,属正常;可略增
FOC_SPD_IQ_FF_PER_RPM或速度环kp - 转一点就停 / 彻底不转 :先
s 0再t 600+w 1;查看是否 Stall;交接后 4 s 内堵转保护放宽 - 定位时反转 :检查霍尔线序与
MOTOR_FWD方向
五、实验现象
5.1 预期波形
- Align(~500 ms) :θ 恒定,六步 duty=1200,转子轻微锁定;
- Drag(0.5数 s) :θ 随换相步进爬升,
g_fm_boot_omega随换相频率上升,霍尔边沿计数递增; - Run :
g_fm_boot_stage=3,RPM 跟踪g_fm_target_rpm,Iq 由电流环调节。
5.2 VOFA+ 三通道
- 速度模式:
RPM_SP / RPM_ACT / Iq - 电流模式:
Iq_SP / Iq_ACT / PWM
六、本章小结
| 要点 | 内容 |
|---|---|
| 问题 | 零速时霍尔 θ 不更新,无法直接 FOC 闭环 |
| 方案 | Align 六步定位 → Drag 六步开环拖转 → Run 霍尔 FOC(|V|→duty) |
| 新增文件 | bsp_foc_boot.c/h |
| 改动点 | bsp_foc.c θ 切换、bsp_motor_tim.c 开环换相、bsp_pid_app.c 启停挂钩与 |V| 输出 |
| 下一章 | lesson20:迁到 接口 1 ,Align/Drag/Run 全程 真 SVPWM(不是无感观测器) |
至此,野火霍尔 BLDC 接口 2 上的 FOC 双闭环 + 可靠三段启动 链路已完整。。