电机驱动开发学习9. PID位置式算法实现与串口修改目标值
电机驱动开发学习9. PID位置式算法实现与串口修改目标值
- 一、位置式与增量式 PID介绍
- 1.1 位置式 PID
- 1.2 增量式 PID
- 1.3 两种形式对比
- 1.4 位置式离散公式(本章实现)
- 1.5 为何本章先学位置式
- 1.6 工程上必须处理的点
- 1. 输出限幅(`out_min` / `out_max`)
- 2. 积分限幅
- 3. 积分抗饱和(Anti-windup)
- 4. 固定采样周期 `dt`(定时器中断)
- 二、实验简介
- 2.1 本章目标
- 2.2 与前后章节的关系
- 2.3 硬件与工程说明
- 三、程序设计
- 3.1 目录与模块划分
- 3.2 PID 结构体设计
- 3.3 位置式 PID 核心流程
- 3.4 被控对象:一阶惯性(软件仿真)
- 3.5 采样周期 dt
- 四、串口命令与 FireWater 输出
- 4.1 串口命令
- 上电默认值(仅首次初始化,之后可被串口命令覆盖)
- 指令一览
- 常用示例
- 4.2 FireWater 波形输出(本章核心)
- 修改目标时的注意点
- 与前后实验的关系
- 协议选择
- 通道定义(FireWater,逗号分隔,行尾 `\n`)
- 下位机发送示例
- VOFA+ 上位机设置
- 建议观察的曲线
- 五、主程序流程
- 5.1 初始化
- 5.2 定时器中断(PID 任务)
- 5.3 主循环
- 六、实验步骤
- 6.1 上电过程
- 6.2 仅 P 控制
- 目标值改为70 `t 70`
- 6.2 加入 I
- 6.3 加入 D
- 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 + D1.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 发 FireWater3.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并再次打印帮助。
常用示例
? # 查看帮助与当前参数 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等。
下位机帮助信息与上表一致:
Serial: t [0-100] kp [val] ki [val] kd [val] pid r ? Example: t 60 | kp 1.5 ki 0.35 kd 0.254.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 波形刷新节奏一致):
voidvofa_send_firewater(constpid_handle_t*pid,floatsp,floatact){floaterr=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 节) - 每 50ms 调用
vofa_send_firewater()发送 FireWater 帧
六、实验步骤
实验前请确认:VOFA+ 已按 4.2 节 配置(115200、FireWater、7 通道);串口指令见 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
目标值改为70t 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 曲线 |
源码地址:
https://gitee.com/xundh/learn-motor-stm32
