电机驱动开发学习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(定时器中断))
- [1. 输出限幅(`out_min` / `out_max`)](#1. 输出限幅(
- 二、实验简介
-
- [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)
- [目标值改为70 `t 70`](#目标值改为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) 限幅 |
常对 Δu 或 u 限幅 |
| 手动/异常干预 | 改执行器后宜 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·dt,D = Kd·(e(k)-e(k-1))/dt。dt 应固定,且由定时器中断周期性调用 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 结构体设计
- 参数:
Kp、Ki、Kd - 限幅:
out_min、out_max、integral_max - 状态:
integral、prev_error - 可选调试量:
p_term、i_term、d_term、output - 接口:
pid_init()、pid_reset()、pid_update(setpoint, measurement, dt)
3.3 位置式 PID 核心流程
- 计算误差
e = setpoint - measurement - 求 P / I / D 三项
- 合成输出并限幅
- 抗饱和:输出顶满且误差同向时撤销本次积分
- 保存
prev_error供下次 D 项使用
3.4 被控对象:一阶惯性(软件仿真)
T · dy/dt + y = K · u
- 离散化(欧拉法):
y += (K*u - y) / T * dt - 建议初值:
K=1.0,T=2.0(与 lesson8 仿真一致) - PID 输出
u作为对象输入,对象输出y作为反馈
3.5 采样周期 dt
- 使用定时器周期中断(如 10ms →
dt=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 60 与 T 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 0→kd 0→kp 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+ 上位机设置
- 安装并打开 VOFA+
- 串口:115200 (与
DEBUG_USART_BAUDRATE一致) - 协议:FireWater
- 通道数:7 ,按上表命名
SP / ACT / ERR / OUT / P / I / D - 打开串口后,下位机运行,应看到多条曲线
- 在「命令/调试」窗口发送
t 60改目标,观察 ACT 阶跃响应(与 lesson8 阶跃实验对照)
建议观察的曲线
| 曲线 | 作用 |
|---|---|
| SP + ACT | 是否跟踪目标、有无超调/稳态误差(对应 lesson8 响应曲线) |
| ERR | 误差是否收敛到 0 |
| OUT | 是否长时间顶在限幅 |
| P / I / D | 调 Kp/Ki/Kd 时分项变化 |
五、主程序流程
5.1 初始化
- 时钟、LED、串口
pid_init()设置 Kp/Ki/Kd 与限幅- 启动定时器中断(PID 周期)
5.2 定时器中断(PID 任务)
setpoint ← 全局目标(串口命令已更新)
measurement ← plant_get_output()
output ← pid_update(setpoint, measurement, dt)
plant_update(output, dt)
5.3 主循环
- 解析串口命令,更新
setpoint或Kp/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.5,Ki 从 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 曲线 |
源码地址: