电机驱动开发学习12. 位置环控制
- 一、位置环是什么
-
- [1.1 速度环与位置环的区别](#1.1 速度环与位置环的区别)
- [1.2 位置反馈:霍尔计数与「一圈多少格」](#1.2 位置反馈:霍尔计数与「一圈多少格」)
- [1.3 位置式 PID 与「位置环」易混概念](#1.3 位置式 PID 与「位置环」易混概念)
- [1.4 控制周期](#1.4 控制周期)
- [1.5 位置环特有问题](#1.5 位置环特有问题)
- 二、实验简介
-
- [2.1 本章目标](#2.1 本章目标)
- [2.2 硬件说明](#2.2 硬件说明)
- 三、程序设计
-
- [3.1 目录与模块划分](#3.1 目录与模块划分)
- [3.2 位置环数据流](#3.2 位置环数据流)
- [3.3 核心代码示例](#3.3 核心代码示例)
-
- [3.3.1 头文件常量建议](#3.3.1 头文件常量建议)
- [3.3.2 单次位置环计算](#3.3.2 单次位置环计算)
- [3.3.3 输出执行(与速度环共用逻辑)](#3.3.3 输出执行(与速度环共用逻辑))
- [3.3.4 主循环轮询](#3.3.4 主循环轮询)
- [3.4 位置误差 wrap(扩展)](#3.4 位置误差 wrap(扩展))
- [3.5 PID 参数与限幅建议](#3.5 PID 参数与限幅建议)
- [3.6 与 lesson11 的接口对照](#3.6 与 lesson11 的接口对照)
- [四、FreeMASTER 与串口调参](#四、FreeMASTER 与串口调参)
-
- [4.1 FreeMASTER 监视变量](#4.1 FreeMASTER 监视变量)
- [4.2 VOFA+ FireWater](#4.2 VOFA+ FireWater)
- [4.3 串口命令(规划)](#4.3 串口命令(规划))
- 五、主程序流程
-
- [5.1 初始化](#5.1 初始化)
- [5.2 主循环](#5.2 主循环)
- [六、建议操作与 PID 整定](#六、建议操作与 PID 整定)
-
- [6.1 推荐操作流程](#6.1 推荐操作流程)
- [6.2 阶跃响应现象](#6.2 阶跃响应现象)
- [6.3 推荐起步参数(参考野火官方)](#6.3 推荐起步参数(参考野火官方))
- [6.4 分步调参](#6.4 分步调参)
- [6.5 常见现象](#6.5 常见现象)
- [6.6 圈数与计数换算](#6.6 圈数与计数换算)
- 七、本章小结

一、位置环是什么
lesson11 我们已经能用 速度环 PID 让电机稳定在目标 RPM 上:给定转速,PID 自动调节 PWM。本章把控制目标从「转多快」换成 「转到哪里」 ------给定 目标位置(霍尔计数),PID 自动决定正转还是反转、输出多大 PWM,直到实际位置与目标一致。
这就是 单环位置闭环 :被控量是 累计位置 ,执行量仍是 PWM 占空比 ,反馈来自 lesson7 已在 TIM5 霍尔中断里维护的 motor_position。
1.1 速度环与位置环的区别
| 环 | 被控量 SP/ACT | 典型现象 | 适用场景 |
|---|---|---|---|
| 速度环(lesson11) | RPM | 稳定在某一转速,负载变化时自动加力 | 风扇、恒速输送 |
| 位置环(本章) | 霍尔计数(可换算圈数) | 转到指定位置后停下,阶跃目标时先加速再制动 | 定点转动、角度定位 |
两者 PID 公式相同 (位置式,lesson8/9),差别在于 反馈量纲 与 整定思路:
- 速度环:误差单位是 RPM,输出直接对应「需要多大力矩」;
- 位置环:误差单位是 计数,远距离误差大 → PID 输出大 PWM 快速接近;接近目标后误差变小 → PWM 减小直至停转。
1.2 位置反馈:霍尔计数与「一圈多少格」
lesson7 已在霍尔状态 有效跳变 时对 motor_position 累加/累减(本仓库 bsp_motor_tim.c):
c
int32_t motor_hall_get_position(void)
{
return motor_position; /* 有符号累计计数,正转增、反转减 */
}
本电机 2 对极 ,六步换相下一圈机械角对应 12 次 霍尔跳变(6 步 × 2)。因此:
| 计数变化 | 含义 |
|---|---|
| ±1 | 转过 1 个霍尔步距(约 30° 电角 / 15° 机械角量级) |
| ±12 | 正/反转 1 圈(机械 360°) |
| 24 | 2 圈(野火官方例程默认目标) |
注意 :hall_enable() 时本仓库会将 motor_position 清零 并同步当前霍尔状态。位置环的「零点」以 最近一次启霍尔/使能电机 为参考;若需绝对零点,须机械对齐后再使能,或在上位机记录偏移。
1.3 位置式 PID 与「位置环」易混概念
- 位置式 PID (lesson8):指 PID 算法形式 ------输出是控制量的 绝对值 (不是增量叠加)。lesson11 速度环、本章位置环 都用位置式 PID。
- 位置环 :指 外环被控量是位置。不要与「位置式 PID」混为一谈。
1.4 控制周期
与 lesson11 一致,推荐 50 ms 固定周期调用位置环(野火官方用 TIM6 中断;本仓库沿用 HAL_GetTick() 节拍 ,与 lesson11 相同)。改周期后 Kp/Ki/Kd 须重调 ,且 dt 必须与周期严格一致。
1.5 位置环特有问题
| 问题 | 说明 | 本章处理思路 |
|---|---|---|
| 到位振荡 | 接近目标时 PWM 仍大,冲过目标再拉回 | 加 到位死区 ;官方 Ki=0、Kd 较大 |
| 积分饱和 | 大误差时 I 项过大导致超调 | 到位清积分;可选 Ki=0(与官方一致) |
| 未停稳再启 | 惯性转动时霍尔仍跳变,位置/ PID 状态错乱 | 改目标前先 s 0 停稳 或 r 清积分 |
| 速度过快 | 大误差时 PID 直接给满 PWM,冲击大 | 输出限幅 + PWM 斜坡(复用 lesson11) |
| 误差 wrap | 若做「转最短路径到某角度」需对圆周误差取模 | 本章单环 线性误差 即可;圆周 wrap 见扩展说明 |
二、实验简介
2.1 本章目标
在 lesson11 工程基础上,将速度环改为 位置环 PID:
- 反馈:
motor_hall_get_position()→ 霍尔累计计数 - 给定:
g_fm_target_pos(目标计数,可换算圈数 = pos/12) - 输出:位置式 PID → PWM + 方向 (与 lesson11
speed_pid_apply_output相同套路) - 复用 lesson9
bsp_pid.c/h、lesson10 FreeMASTER 、VOFA+ 7 通道波形 - 保留 lesson6/7 过流、堵转保护
- 串口命令:
p [pos]设目标、s 1/0启停闭环、kp/ki/kd调参
2.2 硬件说明
与 lesson4 / lesson7 / lesson11 完全相同:野火骄阳 F407 + 无刷驱动板 + 带霍尔 BLDC,推荐 「无刷电机驱动接口 2」 牛角排线。
三、程序设计
3.1 目录与模块划分
在 lesson11 基础上,速度环应用层 改为位置环 (或新增 pos_pid_app,与速度环二选一编译):
User/
├── pid/
│ ├── bsp_pid.c/h # 复用 lesson9,不改公式
│ └── bsp_pid_app.c/h # 位置环:读 position、调 PWM、串口/FreeMASTER
├── freemaster/
│ └── bsp_fm_vars.h # g_fm_target_pos、g_fm_pos_loop_en 等
├── tim/
│ └── bsp_motor_tim.c/h # motor_hall_get_position() 已有
└── main.c
3.2 位置环数据流
每 50 ms(pos_pid_poll):
1. actual_pos = motor_hall_get_position()
2. error = target_pos - actual_pos
3. 若 |error| <= POS_PID_ARRIVE_CNT:停 PWM、清积分,return
4. out = pid_update(&g_fm_pid, target_pos, actual_pos)
5. 符号 → 方向;限幅 + 斜坡 → set_bldcm_speed(duty)
#mermaid-svg-Hd1CaZ3EtAFuLKnc{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-Hd1CaZ3EtAFuLKnc .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Hd1CaZ3EtAFuLKnc .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Hd1CaZ3EtAFuLKnc .error-icon{fill:#552222;}#mermaid-svg-Hd1CaZ3EtAFuLKnc .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Hd1CaZ3EtAFuLKnc .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Hd1CaZ3EtAFuLKnc .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Hd1CaZ3EtAFuLKnc .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Hd1CaZ3EtAFuLKnc .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Hd1CaZ3EtAFuLKnc .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Hd1CaZ3EtAFuLKnc .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Hd1CaZ3EtAFuLKnc .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Hd1CaZ3EtAFuLKnc .marker.cross{stroke:#333333;}#mermaid-svg-Hd1CaZ3EtAFuLKnc svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Hd1CaZ3EtAFuLKnc p{margin:0;}#mermaid-svg-Hd1CaZ3EtAFuLKnc .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Hd1CaZ3EtAFuLKnc .cluster-label text{fill:#333;}#mermaid-svg-Hd1CaZ3EtAFuLKnc .cluster-label span{color:#333;}#mermaid-svg-Hd1CaZ3EtAFuLKnc .cluster-label span p{background-color:transparent;}#mermaid-svg-Hd1CaZ3EtAFuLKnc .label text,#mermaid-svg-Hd1CaZ3EtAFuLKnc span{fill:#333;color:#333;}#mermaid-svg-Hd1CaZ3EtAFuLKnc .node rect,#mermaid-svg-Hd1CaZ3EtAFuLKnc .node circle,#mermaid-svg-Hd1CaZ3EtAFuLKnc .node ellipse,#mermaid-svg-Hd1CaZ3EtAFuLKnc .node polygon,#mermaid-svg-Hd1CaZ3EtAFuLKnc .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Hd1CaZ3EtAFuLKnc .rough-node .label text,#mermaid-svg-Hd1CaZ3EtAFuLKnc .node .label text,#mermaid-svg-Hd1CaZ3EtAFuLKnc .image-shape .label,#mermaid-svg-Hd1CaZ3EtAFuLKnc .icon-shape .label{text-anchor:middle;}#mermaid-svg-Hd1CaZ3EtAFuLKnc .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Hd1CaZ3EtAFuLKnc .rough-node .label,#mermaid-svg-Hd1CaZ3EtAFuLKnc .node .label,#mermaid-svg-Hd1CaZ3EtAFuLKnc .image-shape .label,#mermaid-svg-Hd1CaZ3EtAFuLKnc .icon-shape .label{text-align:center;}#mermaid-svg-Hd1CaZ3EtAFuLKnc .node.clickable{cursor:pointer;}#mermaid-svg-Hd1CaZ3EtAFuLKnc .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Hd1CaZ3EtAFuLKnc .arrowheadPath{fill:#333333;}#mermaid-svg-Hd1CaZ3EtAFuLKnc .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Hd1CaZ3EtAFuLKnc .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Hd1CaZ3EtAFuLKnc .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Hd1CaZ3EtAFuLKnc .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Hd1CaZ3EtAFuLKnc .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Hd1CaZ3EtAFuLKnc .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Hd1CaZ3EtAFuLKnc .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Hd1CaZ3EtAFuLKnc .cluster text{fill:#333;}#mermaid-svg-Hd1CaZ3EtAFuLKnc .cluster span{color:#333;}#mermaid-svg-Hd1CaZ3EtAFuLKnc div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Hd1CaZ3EtAFuLKnc .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Hd1CaZ3EtAFuLKnc rect.text{fill:none;stroke-width:0;}#mermaid-svg-Hd1CaZ3EtAFuLKnc .icon-shape,#mermaid-svg-Hd1CaZ3EtAFuLKnc .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Hd1CaZ3EtAFuLKnc .icon-shape p,#mermaid-svg-Hd1CaZ3EtAFuLKnc .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Hd1CaZ3EtAFuLKnc .icon-shape .label rect,#mermaid-svg-Hd1CaZ3EtAFuLKnc .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Hd1CaZ3EtAFuLKnc .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Hd1CaZ3EtAFuLKnc .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Hd1CaZ3EtAFuLKnc :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} g_fm_target_pos
位置式 PID
motor_hall_get_position
带符号控制量
方向 + 限幅 + 斜坡
六步换相 + TIM8 PWM
TIM5 霍尔
与野火官方 bldcm_pid_control() 一致:PID 输出带符号,负值反转并取绝对值作为占空比。
3.3 核心代码示例
以下为本章目标实现(User/pid/bsp_pid_app.c 设计稿,命名可与工程对齐后微调)。
3.3.1 头文件常量建议
c
/* bsp_pid_app.h --- 位置环 */
#define POS_PID_TARGET_INIT 24.0f /* 默认目标:2 圈(12×2) */
#define POS_PID_LOOP_MS 50U
#define POS_PID_DT 0.05f
#define POS_PID_ARRIVE_CNT 1.0f /* |SP-ACT|<=1 视为到位,停转 */
#define POS_PID_PWM_MAX_STEP 250U /* 每周期 PWM 最大变化,复用 lesson11 */
#define POS_PID_KP_INIT 184.0f /* 参考野火官方初值,须实机微调 */
#define POS_PID_KI_INIT 0.0f /* 官方位置环 Ki=0,靠 P+D 到位 */
#define POS_PID_KD_INIT 170.0f
3.3.2 单次位置环计算
c
static void pos_pid_control(void)
{
float cont_val;
int32_t actual_pos;
float err_abs;
actual_pos = motor_hall_get_position();
g_fm_actual_pos = (float)actual_pos;
g_fm_error = g_fm_target_pos - g_fm_actual_pos;
err_abs = g_fm_error;
if (err_abs < 0.0f)
err_abs = -err_abs;
/* ① 到位:停 PWM,清积分,避免目标附近抖动 */
if (err_abs <= POS_PID_ARRIVE_CNT)
{
set_bldcm_speed(0U);
bldcm_apply_state();
g_fm_pwm_out = 0.0f;
pid_reset(&g_fm_pid);
return;
}
/* ② 位置式 PID:out 量纲 ≈ PWM 计数 */
cont_val = pid_update(&g_fm_pid, g_fm_target_pos, (float)actual_pos);
/* ③ 符号→方向、限幅、斜坡、写硬件(与 lesson11 相同) */
pos_pid_apply_output(cont_val);
}
3.3.3 输出执行(与速度环共用逻辑)
c
static void pos_pid_apply_output(float cont_val)
{
uint16_t duty;
motor_dir_t dir;
if (cont_val < 0.0f)
{
cont_val = -cont_val;
dir = MOTOR_REV;
}
else
{
dir = MOTOR_FWD;
}
duty = (uint16_t)cont_val;
if (duty > MOTOR_PWM_MAX_PERIOD_COUNT)
duty = MOTOR_PWM_MAX_PERIOD_COUNT;
/* 斜坡限幅,抑制到位前 OUT 尖峰 */
if (duty > s_pwm_slew_duty + POS_PID_PWM_MAX_STEP)
duty = (uint16_t)(s_pwm_slew_duty + POS_PID_PWM_MAX_STEP);
else if (duty + POS_PID_PWM_MAX_STEP < s_pwm_slew_duty)
duty = (uint16_t)(s_pwm_slew_duty - POS_PID_PWM_MAX_STEP);
s_pwm_slew_duty = duty;
set_bldcm_direction(dir);
set_bldcm_speed(duty);
bldcm_apply_state();
g_fm_pwm_out = (float)duty;
}
3.3.4 主循环轮询
c
void pos_pid_poll(void)
{
uint32_t now = HAL_GetTick();
static float s_prev_loop_en = 0.0f;
int loop_on = (g_fm_pos_loop_en >= 0.5f) ? 1 : 0;
int prev_on = (s_prev_loop_en >= 0.5f) ? 1 : 0;
if (loop_on && !prev_on)
{
pid_reset(&g_fm_pid);
set_bldcm_direction(MOTOR_FWD);
motor_stall_reset_timer();
s_pwm_slew_duty = get_bldcm_speed();
pos_pid_control(); /* 启环立刻算一次 */
}
else if (!loop_on && prev_on)
{
pos_pid_stop_motor_only();
}
s_prev_loop_en = g_fm_pos_loop_en;
g_fm_actual_pos = (float)motor_hall_get_position();
g_fm_error = g_fm_target_pos - g_fm_actual_pos;
g_fm_pwm_out = (float)get_bldcm_speed();
g_fm_vbus_v = get_vbus_val();
if (!loop_on)
return;
if ((now - s_last_pid_ms) < POS_PID_LOOP_MS)
return;
s_last_pid_ms = now;
fm_reset_on_param_change(); /* FreeMASTER 改 SP/Kp/Ki/Kd 时清积分 */
pos_pid_control();
}
3.4 位置误差 wrap(扩展)
若目标位置在 圆周坐标 上(例如只允许在 0~11 内转最短路径),可将误差改为:
c
static float pos_error_wrap(float target, float actual, float period)
{
float err = target - actual;
float half = period * 0.5f;
while (err > half) err -= period;
while (err < -half) err += period;
return err;
}
野火官方 单环位置例程不做 wrap ,直接用 target - actual;本章默认与官方一致。只有做「任意角度最短路径」时才需要 wrap。
3.5 PID 参数与限幅建议
| 项目 | 建议初值 | 说明 |
|---|---|---|
| 目标 SP | 24 (2 圈)或 ±12 阶跃 | 野火 KEY3/KEY4 每次 ±12 |
out_min / out_max |
0 / ~5500 | 与 lesson4 一致 |
integral_max |
5000~15000 | Ki>0 时防饱和 |
dt |
0.05f | 与 50 ms 周期一致 |
| Kp / Ki / Kd | 184 / 0 / 170(官方) | 不能照搬 lesson11 的 RPM 环参数 |
| 到位死区 | 1 计数 | 可调;过大则精度差,过小则抖动 |
位置环与速度环调参差异:
- 官方 Ki=0 :位置环常靠 P + D 到位,I 易在目标附近积分导致超调;
- Kd 往往较大(官方 170):用误差变化率「刹车」,减轻冲过目标;
- Kp 较大(官方 184):远距离时快速拉高 PWM。
3.6 与 lesson11 的接口对照
| lesson11 速度环 | lesson12 位置环 |
|---|---|
g_fm_target_rpm |
g_fm_target_pos |
motor_hall_get_rpm() |
motor_hall_get_position() |
speed_pid_control() |
pos_pid_control() |
g_fm_speed_loop_en |
g_fm_pos_loop_en |
串口 t [rpm] |
串口 p [pos] |
| SP≈0 停转 | ` |
四、FreeMASTER 与串口调参
4.1 FreeMASTER 监视变量
| 符号 | 类型 | 含义 |
|---|---|---|
g_fm_target_pos |
float | 目标位置 SP(霍尔计数) |
g_fm_actual_pos |
float | 实际位置 ACT |
g_fm_error |
float | SP − ACT |
g_fm_pid.output |
float | PID 总输出 |
g_fm_pid.p_term / i_term / d_term |
float | P / I / D 分项 |
g_fm_pwm_out |
float | 实际 PWM 占空比 |
g_fm_vbus_v |
float | 母线电压 (V) |
g_fm_pos_loop_en |
float | 1.0=闭环开,0.0=关 |
g_fm_protect_en |
float | 过流+堵转保护开关 |
g_fm_fault_code |
float | 0/1/2 故障码 |
类型注意 :g_fm_pos_loop_en 必须为 float 4 字节(与 lesson10/11 相同,勿用 uint8)。
Scope 建议:g_fm_target_pos、g_fm_actual_pos、g_fm_pwm_out 三曲线同屏,阶跃目标时最直观。
4.2 VOFA+ FireWater
| 通道 | 含义 |
|---|---|
| ch0 SP | g_fm_target_pos |
| ch1 ACT | g_fm_actual_pos |
| ch2 ERR | SP − ACT |
| ch3 OUT | PID 总输出 |
| ch4/5/6 | P / I / D |
115200、协议 FireWater、7 通道;与 lesson11 相同。
4.3 串口命令(规划)
| 命令 | 作用 | 示例 |
|---|---|---|
p [pos] |
设置目标位置(计数) | p 24(转 2 圈) |
p +12 / p -12 |
相对当前目标 ±1 圈(可选) | p +12 |
kp / ki / kd |
修改 PID | kp 200 |
s 1 / s 0 |
启 / 停位置闭环 | s 1 |
pid |
打印 SP/ACT/PWM | pid |
r |
清积分 | r |
z |
将当前位置设为零点(可选) | z |
? |
帮助 | ? |
启闭环 :须 s 1 或 g_fm_pos_loop_en=1.0;只改 g_fm_target_pos 不会自动启电机(与 lesson11 一致)。
五、主程序流程
5.1 初始化
- 时钟、LED、串口、ADC
bldcm_init():TIM8 PWM、TIM5 霍尔pos_pid_init():默认 SP=24、Kp=184 Ki=0 Kd=170、闭环关- 默认 不使能电机 ,等待
s 1
5.2 主循环
text
while (1)
{
deal_pos_pid_serial_data();
deal_key_input();
pos_pid_poll(); /* 50 ms 位置环 */
motor_stall_poll();
motor_hall_speed_poll();
motor_overcurrent_poll();
pos_pid_vofa_poll();
}
六、建议操作与 PID 整定
安全提示 :首次闭环请 空载、小阶跃(如 ±12,即 1 圈) ,手勿靠近转子;振动大或过流时立即
s 0。
6.1 推荐操作流程
text
① 烧录 → ② 确认 ACT 随手动轻转变化 → ③ r 清积分 → ④ p 24 设目标 → ⑤ s 1 启闭环 → ⑥ 看 ACT 是否逼近 SP → ⑦ 调 Kp/Kd
| 步骤 | 操作 | 目的 |
|---|---|---|
| 1 | 烧录 lesson12,FreeMASTER 加载 .axf |
确认变量符号 |
| 2 | s 0,轻拨转子或开环慢转,看 g_fm_actual_pos 增减 |
确认霍尔计数方向 |
| 3 | r |
清积分 |
| 4 | p 12 或 p 24 |
小目标先试 1~2 圈 |
| 5 | s 1 |
启位置闭环 |
| 6 | 观察 ACT 曲线 | 应单调逼近 SP 后稳定 |
| 7 | 调 kp/kd |
见下文 |
6.2 阶跃响应现象
理想波形:
- 远离目标:|ERR| 大 → PWM 迅速升高,ACT 斜率大;
- 接近目标:|ERR| 减小 → PWM 下降(D 项起「刹车」作用);
- 进入死区:ACT 与 SP 差 ≤1 → PWM=0,电机停。
若 冲过目标再回来 :Kd 偏小或 Kp 过大 → 略减 Kp 或加 Kd。
若 永远不到位 :Kp 过小 → 加大 Kp。
若 目标附近来回抖 :死区太小或 Kp 过大 → 加大 POS_PID_ARRIVE_CNT 或减 Kp。
6.3 推荐起步参数(参考野火官方)
| 参数 | 起步值 | 说明 |
|---|---|---|
| Kp | 150~200 | 官方 184 |
| Ki | 0 | 先不加 I;确有静差再试 0.1~1 |
| Kd | 100~200 | 官方 170,抑制超调 |
| 目标 SP | 12 或 24 | 1~2 圈 |
| 到位死区 | 1 | 约 1 个霍尔步 |
6.4 分步调参
只加 P: r → ki 0 → kd 0 → kp 100 → p 12 → s 1,逐步加大 Kp 直到响应够快但不严重超调。
加 D: kd 50 起,逐步加大,观察超调是否减小。
可选 I: 仅当 长期差 1~2 个计数不到位 时,小 Ki(如 0.5)试补静差;I 过大易振荡。
6.5 常见现象
| 现象 | 可能原因 | 处理 |
|---|---|---|
| 启环后方向反 | 霍尔接线/计数方向与 PID 符号约定相反 | 交换两相或软件取反 error |
| 未停稳再启 SP 乱跳 | 惯性导致霍尔仍计数 | 先 s 0 等 ACT 稳定再 s 1 |
| ACT 始终 0 | 未使能霍尔 / 电机未转 | 检查 s 1、SD、换相 |
| 到位后微抖 | 死区太小 | 增大 POS_PID_ARRIVE_CNT |
| 远距离冲击大 | Kp 过大且无斜坡 | 减小 Kp 或保留 PWM 斜坡限幅 |
6.6 圈数与计数换算
text
圈数 = g_fm_actual_pos / 12.0
目标圈数 N → p 命令写入 (int)(N * 12)
例:想 正转 3 圈 → p 36;反转 1 圈 (从 0 起)→ p -12。
运行效果:


七、本章小结
- 位置环 = 以 霍尔累计计数 为反馈、PWM 为输出的单环 PID;SP/ACT 单位是 计数,不是 RPM。
- 1 圈 = 12 计数(本电机 2 对极);默认目标 24 = 2 圈,与野火官方一致。
- 位置环宜 Ki=0、Kd 较大 起步;与 lesson11 RPM 环参数完全不能互换。
- 启闭环 须
g_fm_pos_loop_en=1.0或s 1;建议加 到位死区 与 PWM 斜坡。 - 改大目标前
r清积分 ;未停稳勿再启,否则霍尔计数与 PID 状态会乱。
参考:野火《电机应用开发实战指南》「直流无刷电机-位置环控制-位置式PID」;