电机驱动开发学习12. 位置环控制

电机驱动开发学习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 FreeMASTERVOFA+ 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_posg_fm_actual_posg_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 1g_fm_pos_loop_en=1.0;只改 g_fm_target_pos 不会自动启电机(与 lesson11 一致)。


五、主程序流程

5.1 初始化

  1. 时钟、LED、串口、ADC
  2. bldcm_init():TIM8 PWM、TIM5 霍尔
  3. pos_pid_init():默认 SP=24、Kp=184 Ki=0 Kd=170、闭环关
  4. 默认 不使能电机 ,等待 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 12p 24 小目标先试 1~2 圈
5 s 1 启位置闭环
6 观察 ACT 曲线 应单调逼近 SP 后稳定
7 kp/kd 见下文

6.2 阶跃响应现象

理想波形:

  1. 远离目标:|ERR| 大 → PWM 迅速升高,ACT 斜率大;
  2. 接近目标:|ERR| 减小 → PWM 下降(D 项起「刹车」作用);
  3. 进入死区: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: rki 0kd 0kp 100p 12s 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.0s 1;建议加 到位死区PWM 斜坡
  • 改大目标前 r 清积分未停稳勿再启,否则霍尔计数与 PID 状态会乱。

参考:野火《电机应用开发实战指南》「直流无刷电机-位置环控制-位置式PID」;

源码: https://gitee.com/xundh/learn-motor-stm32