电机驱动开发学习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)限幅常对Δ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 与前后章节的关系

章节内容
lesson8PID 原理、离散公式、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、无前缀,二者不要混淆。

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

项目默认值串口能否修改
目标 SP50能(t
Kp1.5能(kp
Ki0.35能(ki
Kd0.25能(kd
对象输出 ACT0不能(随 PID 运算变化)
采样周期 dt0.01 s(10 ms)不能
输出限幅0~100不能

指令一览

命令作用取值范围执行后副作用
t [值]修改目标值SP0~100自动pid_reset(),触发阶跃响应
kp [值]修改比例系数Kp0~10自动pid_reset()
ki [值]修改积分系数Ki0~5自动pid_reset()
kd [值]修改微分系数Kd0~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 0kd 0kp 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.25

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

修改目标时的注意点

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

与前后实验的关系

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

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

协议选择

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

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

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

序号通道名含义
ch0SP目标值 setpoint
ch1ACT被控对象输出(反馈)
ch2ERR误差 SP − ACT
ch3OUTPID 输出
ch4P比例项
ch5I积分项
ch6D微分项

下位机发送示例

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+ 上位机设置

  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 节)
  • 每 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.5Ki从 0.1 调到 0.5

误差缩至0.

6.3 加入 D

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

6.4 与 lesson8 对照

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

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