电机驱动开发学习9. PID位置式算法实现与串口修改目标值

电机驱动开发学习9. PID位置式算法实现与串口修改目标值

  • [一、位置式与增量式 PID介绍](#一、位置式与增量式 PID介绍)
    • [1.1 位置式 PID](#1.1 位置式 PID)
    • [1.2 增量式 PID](#1.2 增量式 PID)
    • [1.3 两种形式对比](#1.3 两种形式对比)
    • [1.4 位置式离散公式(本章实现)](#1.4 位置式离散公式(本章实现))
    • [1.5 为何本章先学位置式](#1.5 为何本章先学位置式)
    • [1.6 工程上必须处理的点](#1.6 工程上必须处理的点)
      • [1. 输出限幅(`out_min` / `out_max`)](#1. 输出限幅(out_min / out_max))
      • [2. 积分限幅](#2. 积分限幅)
      • [3. 积分抗饱和(Anti-windup)](#3. 积分抗饱和(Anti-windup))
      • [4. 固定采样周期 `dt`(定时器中断)](#4. 固定采样周期 dt(定时器中断))
  • 二、实验简介
    • [2.1 本章目标](#2.1 本章目标)
    • [2.2 与前后章节的关系](#2.2 与前后章节的关系)
    • [2.3 硬件与工程说明](#2.3 硬件与工程说明)
  • 三、程序设计
    • [3.1 目录与模块划分](#3.1 目录与模块划分)
    • [3.2 PID 结构体设计](#3.2 PID 结构体设计)
    • [3.3 位置式 PID 核心流程](#3.3 位置式 PID 核心流程)
    • [3.4 被控对象:一阶惯性(软件仿真)](#3.4 被控对象:一阶惯性(软件仿真))
    • [3.5 采样周期 dt](#3.5 采样周期 dt)
  • [四、串口命令与 FireWater 输出](#四、串口命令与 FireWater 输出)
  • 五、主程序流程
    • [5.1 初始化](#5.1 初始化)
    • [5.2 定时器中断(PID 任务)](#5.2 定时器中断(PID 任务))
    • [5.3 主循环](#5.3 主循环)
  • 六、实验步骤
    • [6.1 上电过程](#6.1 上电过程)
    • [6.2 仅 P 控制](#6.2 仅 P 控制)
      • [目标值改为70 `t 70`](#目标值改为70 t 70)
    • [6.2 加入 I](#6.2 加入 I)
    • [6.3 加入 D](#6.3 加入 D)
    • [6.4 与 lesson8 对照](#6.4 与 lesson8 对照)

一、位置式与增量式 PID介绍

PID 在嵌入式里常见两种离散写法:位置式增量式 。二者用的是同一套 P/I/D 思想,差别在于控制器输出表示什么

1.1 位置式 PID

位置式 每次直接算出控制量的绝对值

复制代码
u(k) = Kp·e(k) + Ki·Σe(i)·dt + Kd·[e(k)-e(k-1)]/dt
项目 说明
输出含义 第 k 次采样时,执行机构应处于的完整控制量(如 PWM=800、DAC=3.3V)
使用方式 pwm = pid_update(目标, 反馈),把返回值直接赋给执行器
直观性 输出就是「当前该输出多少」,与 lesson8 仿真一致
典型场景 速度环给定 PWM、位置环、温度/液位等

例子 :目标转速 500 RPM,当前 400 RPM,位置式 PID 算出 u=650 → 直接 set_pwm(650)

1.2 增量式 PID

增量式 不算绝对输出,只算相对上一次的调整量 Δu

复制代码
Δu(k) = Kp·[e(k)-e(k-1)] + Ki·e(k)·dt + Kd·[e(k)-2e(k-1)+e(k-2)]/dt

u(k) = u(k-1) + Δu(k)
项目 说明
输出含义 现有控制量上再加(或减)多少
使用方式 u += pid_update(...),需保存上次输出 u(k-1)
直观性 输出是「这一步微调多少」
典型场景 步进电机脉冲频率微调、阀门开度微调、部分执行器只能增量调节

例子 :当前 PWM 已是 600,增量式算出 Δu=+50 → 新 PWM = 650。

1.3 两种形式对比

对比项 位置式 增量式
输出 控制量绝对值 u(k) 控制量增量 Δu(k)
积分项 显式累加 Σe·dt 含在 Ki·e·dt
微分项 e(k)-e(k-1) e(k)-2e(k-1)+e(k-2)
需保存的状态 积分、上次误差 上次/上上次误差、上次输出
输出限幅 u(k) 限幅 常对 Δuu 限幅
手动/异常干预 改执行器后宜 pid_reset() 清积分 改执行器后 u(k-1) 易与真实不同步
切换手动/自动 需重新对齐输出 有时切换更平滑(从当前 u 继续累加)

如何选择:

  • 无刷 PWM 调速、位置环 :本系列统一采用位置式(lesson9 起)
  • 增量式常见于步进加减速等场景,本 BLDC 主线不单独成章

二者调好的 Kp/Ki/Kd 数值通常不能直接互换,因为公式形式不同,需分别整定。

1.4 位置式离散公式(本章实现)

复制代码
e(k) = setpoint - measurement

P项 = Kp · e(k)
I项 += Ki · e(k) · dt
D项 = Kd · [e(k) - e(k-1)] / dt

output = P + I + D

1.5 为何本章先学位置式

  • 输出为控制量绝对值,直观,便于限幅
  • 与 lesson8 仿真、set_bldcm_speed() 等接口一致
  • 速度环 / 位置环常用位置式或在其基础上封装

1.6 工程上必须处理的点


1. 输出限幅(out_min / out_max

PID 算出的 output = P + I + D 会被限制在 [out_min, out_max] 内。

作用:对应实际执行器的物理范围(例如 PWM 0~100%、电机最大允许占空比)。超出范围的控制量既无效,还可能损坏硬件。


2. 积分限幅

积分累加值 integral 被限制在 [-integral_max, integral_max] 内(见代码第 53--54 行)。

作用:防止 I 项因长期误差过大而无限增长。即使还没触发输出饱和,也能限制积分"蓄力"的上限,减轻超调和恢复慢的问题。


3. 积分抗饱和(Anti-windup)

输出已经顶到 out_max / out_min,但误差仍会让积分继续往"更饱和"的方向累加时,会把刚才加进去的那一步积分撤销回去(代码第 62--71 行)。

作用:解决"积分饱和(windup)"------输出已满却还在积分,导致误差反向时 I 项很大、响应迟钝、超调明显。Anti-windup 让饱和时积分不再无效累积。


4. 固定采样周期 dt(定时器中断)

离散 PID 里 I、D 都依赖时间:I += Ki·e·dtD = Kd·(e(k)-e(k-1))/dtdt 应固定,且由定时器中断周期性调用 pid_update()

作用 :保证公式与整定参数一致。若 dt 忽大忽小,同一组 Kp/Ki/Kd 表现会变,D 项也会因除法放大噪声,控制不稳定。


二、实验简介

2.1 本章目标

  • 在 STM32 上实现可复用的位置式 PID 模块bsp_pid.c/h
  • 用软件一阶惯性被控对象验证算法(与 lesson8 Python 仿真同一模型)
  • VOFA+ FireWater 协议输出 PID 波形(7 通道,50ms 一帧),对照 lesson8 曲线调参
  • 串口命令在线修改目标值Kp/Ki/Kd,无需重新编译
  • 为后续 lesson10 无刷速度环 打好 PID 代码基础

2.2 与前后章节的关系

章节 内容
lesson8 PID 原理、离散公式、Python 交互仿真
本章 位置式 PID 上板 + FireWater 波形输出 + 串口改目标
lesson10 速度环:霍尔反馈 + 位置式 PID 控 PWM

2.3 硬件与工程说明

  • 实验平台:野火骄阳 F407 + 无刷驱动板
  • 本章暂不驱动电机闭环,先在 MCU 内用仿真对象验证 PID
  • 保留 lesson7 的监测代码(电压/电流/霍尔等)便于工程统一,PID 实验不依赖电机转动

三、程序设计

3.1 目录与模块划分

复制代码
User/
├── pid/
│   ├── bsp_pid.h      # 结构体、接口声明
│   └── bsp_pid.c      # 位置式 PID 实现
├── plant/
│   ├── bsp_plant.h    # 一阶惯性被控对象(可选独立模块)
│   └── bsp_plant.c
├── usart/             # 串口命令 + FireWater 波形发送
├── vofa/
│   ├── bsp_vofa.h     # FireWater 帧发送
│   └── bsp_vofa.c
└── main.c             # 初始化、定时 PID、50ms 发 FireWater

3.2 PID 结构体设计

  • 参数:KpKiKd
  • 限幅:out_minout_maxintegral_max
  • 状态:integralprev_error
  • 可选调试量:p_termi_termd_termoutput
  • 接口:pid_init()pid_reset()pid_update(setpoint, measurement, dt)

3.3 位置式 PID 核心流程

  1. 计算误差 e = setpoint - measurement
  2. 求 P / I / D 三项
  3. 合成输出并限幅
  4. 抗饱和:输出顶满且误差同向时撤销本次积分
  5. 保存 prev_error 供下次 D 项使用

3.4 被控对象:一阶惯性(软件仿真)

复制代码
T · dy/dt + y = K · u
  • 离散化(欧拉法):y += (K*u - y) / T * dt
  • 建议初值:K=1.0T=2.0(与 lesson8 仿真一致)
  • PID 输出 u 作为对象输入,对象输出 y 作为反馈

3.5 采样周期 dt

  • 使用定时器周期中断(如 10msdt=0.01f
  • dt 必须与中断周期一致,不可在 main 循环里"随缘"调用
  • 对比 lesson8:dt=0.05s 为仿真步长;上板常用 5~20ms

四、串口命令与 FireWater 输出

本章不再使用 lesson6/7 的 0xAA 0x55 二进制帧,改为 VOFA+ FireWater 输出 PID 曲线;目标值仍通过串口文本命令修改。

VOFA+方便看曲线,不是必须使用。后续章节会改用FreeMASTER。

4.1 串口命令

本章所有实验命令均通过 USART1、115200 发送。可在 VOFA+「命令/调试」窗口或任意串口助手输入,以回车结束 ;字母大小写均可(如 t 60T 60 等效)。

与 FireWater 波形共用同一串口 :命令回显带 [MOT] 前缀(如 Target: 60),波形数据为纯 CSV、无前缀,二者不要混淆。

上电默认值(仅首次初始化,之后可被串口命令覆盖)

项目 默认值 串口能否修改
目标 SP 50 能(t
Kp 1.5 能(kp
Ki 0.35 能(ki
Kd 0.25 能(kd
对象输出 ACT 0 不能(随 PID 运算变化)
采样周期 dt 0.01 s(10 ms) 不能
输出限幅 0~100 不能

指令一览

命令 作用 取值范围 执行后副作用
t [值] 修改目标值 SP 0~100 自动 pid_reset(),触发阶跃响应
kp [值] 修改比例系数 Kp 0~10 自动 pid_reset()
ki [值] 修改积分系数 Ki 0~5 自动 pid_reset()
kd [值] 修改微分系数 Kd 0~5 自动 pid_reset()
pid 打印当前 SP、Kp、Ki、Kd ---
r 清 PID 内部状态(积分、上次误差等) --- pid_reset()不改 SP 与 Kp/Ki/Kd
? 打印帮助与当前参数 ---

非法命令会提示 Invalid command 并再次打印帮助。

常用示例

text 复制代码
?              # 查看帮助与当前参数
pid            # 仅查看 SP / Kp / Ki / Kd
t 70           # 目标改为 70(阶跃,非改 PID)
kp 1.5         # 仅 P 实验时可先设 Kp
ki 0           # 关闭积分 → 纯 P 控制
kd 0           # 关闭微分
t 60           # 改目标观察阶跃响应
r              # 波形异常时可手动清积分,一般不必单独发

说明:

  • t 70 改的是目标值 SP,不是 Kp/Ki/Kd。
  • t / kp / ki / kd 时程序会自动清积分,避免旧积分拖累新参数或新目标。
  • 6.2 仅 P 控制 时,典型顺序:ki 0kd 0kp 1.5(或其它 Kp)→ t 70
  • 6.3 加 I 时:在 P 基础上 ki 0.1 逐步加大;做 6.4 加 D 时:再 kd 0.1 等。

下位机帮助信息与上表一致:

text 复制代码
Serial: t [0-100]  kp [val]  ki [val]  kd [val]  pid  r  ?
Example: t 60  |  kp 1.5  ki 0.35  kd 0.25

4.2 FireWater 波形输出(本章核心)

修改目标时的注意点

  • 目标突变等价于阶跃响应,便于在 VOFA+ 上观察 SP/ACT 曲线
  • 大幅改目标时可调用 pid_reset() 清积分(按需)
  • 串口收命令与 FireWater 发波形共用 USART1,命令在 main 解析,波形按固定周期发送

与前后实验的关系

章节 串口输出方式 用途
lesson6 / 7 自定义二进制帧(帧头 0xAA 0x55 电压、电流、霍尔 RPM 等,需配套解析
lesson8 Python matplotlib 仿真曲线,无上位机
本章 VOFA+ FireWater PID 多通道曲线,对应 lesson8 的「看波形调参」
lesson10(预告) 可改 JustFloat 速度环通道增多、PID 周期更快时再换

本章是用 VOFA+ ,软件对象 + 7 路 float、50ms 一帧,FireWater 最合适

协议选择

VOFA+ 内置三种协议(详见 vofa.plus):

协议 格式 优点 缺点 本章
FireWater CSV 浮点 + \n printf 即可,调试直观 带宽比二进制大 推荐
JustFloat 小端 float 数组 + 帧尾 省带宽,适合多通道高速 需组包,不如字符串直观 lesson10 可选
RawData 原始字节 当普通串口助手 不能自动画曲线 不用

通道定义(FireWater,逗号分隔,行尾 \n

序号 通道名 含义
ch0 SP 目标值 setpoint
ch1 ACT 被控对象输出(反馈)
ch2 ERR 误差 SP − ACT
ch3 OUT PID 输出
ch4 P 比例项
ch5 I 积分项
ch6 D 微分项

下位机发送示例

50ms 发送一行(与 lesson7 波形刷新节奏一致):

c 复制代码
void vofa_send_firewater(const pid_handle_t *pid, float sp, float act)
{
    float err = sp - act;

    motor_log("%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f\n",
              sp, act, err,
              pid->output, pid->p_term, pid->i_term, pid->d_term);
}

注意:

  • 必须是 FireWater :纯浮点 CSV,\n 结尾
  • 不要加 SP= 等前缀(否则 VOFA+ 无法按列解析)
  • 发送频率 20Hz 左右 即可,不必与 PID 中断同频(10ms PID、50ms 发波形)

VOFA+ 上位机设置

  1. 安装并打开 VOFA+
  2. 串口:115200 (与 DEBUG_USART_BAUDRATE 一致)
  3. 协议:FireWater
  4. 通道数:7 ,按上表命名 SP / ACT / ERR / OUT / P / I / D
  5. 打开串口后,下位机运行,应看到多条曲线
  6. 在「命令/调试」窗口发送 t 60 改目标,观察 ACT 阶跃响应(与 lesson8 阶跃实验对照)

建议观察的曲线

曲线 作用
SP + ACT 是否跟踪目标、有无超调/稳态误差(对应 lesson8 响应曲线)
ERR 误差是否收敛到 0
OUT 是否长时间顶在限幅
P / I / D 调 Kp/Ki/Kd 时分项变化

五、主程序流程

5.1 初始化

  1. 时钟、LED、串口
  2. pid_init() 设置 Kp/Ki/Kd 与限幅
  3. 启动定时器中断(PID 周期)

5.2 定时器中断(PID 任务)

复制代码
setpoint  ← 全局目标(串口命令已更新)
measurement ← plant_get_output()
output    ← pid_update(setpoint, measurement, dt)
plant_update(output, dt)

5.3 主循环

  • 解析串口命令,更新 setpointKp/Ki/Kd(见 [4.1 节](#4.1 节))
  • 每 50ms 调用 vofa_send_firewater() 发送 FireWater 帧

六、实验步骤

实验前请确认 :VOFA+ 已按 [4.2 节](#4.2 节) 配置(115200、FireWater、7 通道);串口指令见 [4.1 节](#4.1 节)------改目标用 t,改 PID 用 kp/ki/kd,全程无需重新编译烧录

6.1 上电过程

上电时参数:

  • SP: 50
  • ACT: 0
  • ERR: 50
  • PID: 0
    从图可以看到ACT在0升到近目标50,ERR值近1.47。在这个过程中,P的值一直下降,而积分值一路累加,到后面积分值成为OUT主力;而D的值接近0,轻微阻尼;

6.2 仅 P 控制

kp=1.5,ki=0,kd=0

目标值改为70 t 70

最后有稳态误差42。

6.2 加入 I

固定 Kp=1.5Ki 从 0.1 调到 0.5

误差缩至0.

6.3 加入 D

本章被控对象是 一阶惯性,响应 平滑、基本无超调,而D 的主要作用是抑制超调、加快边沿、阻尼振荡。由于对象本身就不振荡,Kd 能发挥的空间很小。本节不作过多D项调整测试。

6.4 与 lesson8 对照

项目 lesson8 Python 本章 STM32
对象 一阶 + 滞后 一阶(可先不加滞后)
dt 0.05s 0.01s(示例)
改目标 滑块 / 阶跃 串口 t [值]
观察 matplotlib 曲线 VOFA+ FireWater 曲线

源码地址:

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