13 KiB
齿轮泵压力调节算法移植方案
一、概述
oldProject/InkJetPrinter.cpp 中的齿轮泵压力调节算法相比当前项目进行了重大重构,引入了软启动斜坡、闭环稳定性检测、硬保护、分区Kp、滑动平均滤波等机制,显著提升了压力调节的稳定性、响应速度和安全性。本文档详细对比两个版本,并给出移植方案。
二、算法对比分析
2.1 PID_realize2 函数对比
| 特性 | 当前项目(旧版) | oldProject(新版) |
|---|---|---|
| PID类型 | 位置式PID(输出绝对电压) | 增量式PID(输出Δu = Kp×(e[k]-e[k-1]) + Ki×e[k]) |
| Kp | 0.8 | 0.15 |
| Ki | 0.02 | 0.02 |
| Kd | 0.5 | 0.0(无微分项) |
| 积分限幅 | integral_max = 12000 | 无(增量式不需要积分限幅) |
| 自适应限幅 | 无 | 有:根据误差方向非对称限幅 |
| 输出含义 | 绝对电压值 | 增量值(叠加到u上) |
新版PID_realize2 自适应限幅逻辑:
err < -30(压力严重偏高): delta_u ∈ [-6, +1] → 允许快速减速,限制加速
err > 50(压力严重偏低): delta_u ∈ [-2, +3] → 允许正常加速,限制减速
-30 ≤ err ≤ 50(接近目标): delta_u ∈ [-3, +3] → 小步长平稳调节
评价: 新版PID_realize2从位置式改为增量式,避免了积分饱和问题;自适应非对称限幅让超压时能快速降速、欠压时平稳加速,大幅减少了过冲和振荡。
2.2 pressureAdj() 函数对比
| 特性 | 当前项目(旧版) | oldProject(新版) |
|---|---|---|
| 调用频率 | 每2次调用执行1次(50%降频) | 每次调用都执行 |
| 停止/告警处理 | 函数末尾清零 | 函数开头立即返回+状态全复位 |
| 硬保护 | 无 | 压力>目标+50时强制减速,优先级最高 |
| 软启动 | 无 | 自适应步长斜坡,800ms周期 |
| 闭环稳定性检测 | 无 | 等待压力稳定(连续15次变化<10)才调速 |
| 滑动平均滤波 | 无 | 10点滑动窗口均值 |
| 分区Kp | 无 | 三档Kp(0.05/0.04/0.03),误差越大Kp越大 |
| 转速上限保护 | 静态maxValue截断 | 动态检测:转速达上限时禁止继续加速 |
| 状态变量 | 无(仅static局部变量) | 6个成员变量(m_rampDone等) |
2.3 pressureAdj() 控制流程对比
当前项目(旧版)流程:
pressureAdj() 被调用
├─ printOut%2 != 0 → return(跳过一次)
├─ if gearPumpRunStopFlag == false
│ ├─ if gearPumpEnable && reclaimPumpEnable → PID_realize2(位置式PID)
│ └─ else if u>0 || 压力偏低 → PID_realize(简单PID)
├─ u > 0 → gearPumpValue = u,否则 = 0
├─ 限幅800
├─ reclaim模式下额外限幅(基于speedAlarm)
├─ 最后再检查 gearPumpRunStopFlag / flagOfSpeedAlarm → 清零
└─ WriteMoterPump()
问题:
- 停止/告警状态在函数末尾才处理,中间已经执行了PID计算
- 无条件直接输出PID结果,没有压力稳定等待
- 没有超压硬保护,压力飙升时响应慢
- 回收泵限幅逻辑复杂且硬编码
oldProject(新版)流程:
pressureAdj() 被调用
├─ [阶段1] 停止/告警?→ 清零状态 + 复位所有状态变量 + return
├─ [阶段2] gearPumpEnable==false → 简单PID + return
├─ [阶段3] 硬保护:actual > target+50 → 强制减速 + return
├─ [阶段4] 软启动(m_rampDone==false)
│ ├─ 每8次调用(≈800ms)执行一次
│ ├─ 自适应步长(err>200→5, >100→3, >50→2, else→1)
│ ├─ 超压→减1
│ ├─ 转速上限保护
│ └─ selfCheckDone && err在±50内 → m_rampDone=true
├─ [阶段5] 闭环稳态调节
│ ├─ 更新滑动平均缓冲
│ ├─ 稳定检测:连续15次压力变化<10 → 可调速
│ ├─ 5秒超时兜底
│ ├─ 分区Kp调速
│ └─ 转速上限保护
└─ WriteMoterPump()
优势:
- 停止/告警立即处理,状态完全复位
- 硬保护优先于一切,安全性最高
- 软启动避免开机时压力突变
- 闭环等待压力稳定后再调,避免管路抖动导致误调
- 滑动平均滤除传感器噪声
- 分区Kp实现大误差快调、小误差精调
- 转速上限保护防止电机超速
2.4 新增成员变量
oldProject 在 InkJetPrinter.h 中新增了以下成员变量:
bool m_rampDone = false; // 软启动完成标志
int m_adjSkip = 0; // 节拍计数(软启动/超时复用)
int m_lastPressure = 0; // 上次采样压力(稳定检测用)
int m_stableCount = 0; // 压力连续稳定计数
int m_pressureBuf[10] = {}; // 压力滑动平均缓冲(10点=1s)
int m_bufIdx = 0; // 滑动平均写入索引
此外还需要一个 m_lastByHand 变量用于检测手动→自动控制切换边沿,以及 m_lastReclaimMode 用于检测回收泵模式切换。
2.5 timerHandler 中的配套修改
oldProject 在 timerHandler() 中还有以下配套修改,确保状态机正确工作:
- 开机/重启时清零:
gearPumpValue=0, u=0,确保软启动从0开始爬坡 - 自检完成时重置软启动:
selfCheckDone变为1时重置m_rampDone=false及所有缓冲 - 手动→自动切换时重置:
m_lastByHand边沿检测,切换回自动时重置软启动 - 回收泵模式切换时重置:
m_lastReclaimMode边沿检测,模式切换时重置软启动和泄漏保护学习状态
三、移植方案
3.1 需要修改的文件
| 文件 | 修改内容 |
|---|---|
InkJetPrinter.h |
新增6个成员变量 + m_lastByHand + m_lastReclaimMode |
InkJetPrinter.cpp |
替换 PID_realize2 + 替换 pressureAdj + 修改 timerHandler 中的配套逻辑 |
3.2 详细修改步骤
步骤1:修改 InkJetPrinter.h — 新增成员变量
在 InkJetPrinter 类的 private 区域添加:
// 压力调节算法状态变量
bool m_rampDone = false; // pressureAdj 软启动完成标志
int m_adjSkip = 0; // pressureAdj 节拍计数(软启动/超时复用)
int m_lastPressure = 0; // pressureAdj 上次采样压力(稳定检测用)
int m_stableCount = 0; // pressureAdj 压力连续稳定计数
int m_pressureBuf[10] = {}; // pressureAdj 压力滑动平均缓冲(10点=1s)
int m_bufIdx = 0; // pressureAdj 滑动平均写入索引
bool m_lastByHand = false; // 上一帧手动控制标志(边沿检测用)
bool m_lastReclaimMode = false; // 上一帧回收泵模式(边沿检测用)
步骤2:替换 PID_realize2 函数
将当前项目的 PID_realize2 替换为 oldProject 的增量式版本。
替换前(当前项目):
float PID_realize2(float speed, float act){
pid.Kp = 0.8;
pid.Ki = 0.02;
pid.Kd = 0.5;
pid.integral_max = 12000;
// ... 位置式PID,返回绝对电压
}
替换后(oldProject):
float PID_realize2(float speed, float act){
// 增量式 PID:Δu = Kp*(e[k]-e[k-1]) + Ki*e[k]
pid.Kp = 0.15f;
pid.Ki = 0.02f;
pid.Kd = 0.0f;
pid.SetSpeed = speed;
pid.ActualSpeed = act;
pid.err = pid.SetSpeed - pid.ActualSpeed;
float delta_u = pid.Kp * (pid.err - pid.err_last)
+ pid.Ki * pid.err;
// 自适应非对称限幅
if(pid.err < -30.0f) {
if(delta_u > 1.0f) delta_u = 1.0f;
if(delta_u < -6.0f) delta_u = -6.0f;
} else if(pid.err > 50.0f) {
if(delta_u > 3.0f) delta_u = 3.0f;
if(delta_u < -2.0f) delta_u = -2.0f;
} else {
if(delta_u > 3.0f) delta_u = 3.0f;
if(delta_u < -3.0f) delta_u = -3.0f;
}
pid.err_last = pid.err;
return delta_u;
}
注意: 新版 PID_realize2 返回的是增量 delta_u,而不是绝对电压。调用方需要改为 u += PID_realize2(...) 而不是 u = PID_realize2(...)。
步骤3:替换 pressureAdj() 函数
将整个 pressureAdj() 函数替换为 oldProject 版本。由于新旧版本差异较大,建议直接整体替换,不保留旧代码。
关键修改点:
- 旧版
pressureAdj()中调用PID_realize2时用的是u = PID_realize2(...)(赋值),新版需要改为u += PID_realize2(...)(累加),因为新版返回的是增量。 - 注意当前项目中
sysPara->devOp->WriteMoterPump()的调用方式一致,无需修改。 - 当前项目中引用
m_MainWidget而不是ui->tabMainWindow,在新版 pressureAdj 中不涉及 UI 控件引用,所以不影响。
实际上 oldProject 的新版 pressureAdj() 已经不再调用 PID_realize2 了,而是采用分阶段手动调速逻辑(软启动 + 分区Kp闭环),所以 PID_realize2 的替换主要是为了完整性,实际在 gearPumpEnable 模式下不会用到。
步骤4:修改 timerHandler() 中的配套逻辑
需要在当前项目的 timerHandler() 中做以下修改:
4.1 开机/重启时清零控制字(在 gearPumpStartFlag 相关逻辑中):
当前项目(约第581-590行):
if(sysPara->para.gearPumpEnable) {
sysPara->para.gearPumpValue = 150;
if(tempFlag){
sysPara->devOp->WriteMoterPump();
tempFlag=0;
}
gearPumpStartFlag = true;
}
改为:
if(sysPara->para.gearPumpEnable) {
// 确保开机时控制字从 0 开始,让 pressureAdj 软启动从 0 爬坡
if(sysPara->para.gearPumpValue != 0) {
sysPara->para.gearPumpValue = 0;
u = 0.0f;
sysPara->devOp->WriteMoterPump();
}
gearPumpStartFlag = true;
}
4.2 自检完成时重置软启动状态:
在 selfCheckDone = 1 的位置(约第883行),在设置完成后添加:
sysPara->status.selfCheckDone = 1;
// 自检完成后重置软启动,从当前控制字开始,避免突变
u = (float)sysPara->para.gearPumpValue;
m_rampDone = false;
m_adjSkip = 0;
m_lastPressure = 0;
m_stableCount = 0;
for(int i = 0; i < 10; i++) m_pressureBuf[i] = 0;
m_bufIdx = 0;
4.3 手动→自动控制切换时重置软启动:
在 isPumpValveControledByHand 相关逻辑中(约第655行附近),添加边沿检测:
// 在 timerHandler 中增加 static 变量追踪上一帧状态
static bool lastByHand = false;
// 检测手动→自动边沿
if(lastByHand && !sysPara->status.isPumpValveControledByHand)
{
ControledSwitchFlag = true;
u = (float)sysPara->para.gearPumpValue;
m_rampDone = false;
m_adjSkip = 0;
m_lastPressure = 0;
m_stableCount = 0;
for(int i = 0; i < 10; i++) m_pressureBuf[i] = 0;
m_bufIdx = 0;
}
lastByHand = sysPara->status.isPumpValveControledByHand;
4.4 回收泵模式切换时重置软启动:
在 timerHandler 中增加:
static bool lastReclaimMode = false;
if(lastReclaimMode != sysPara->para.flagOfReclaimPumEnable)
{
lastReclaimMode = sysPara->para.flagOfReclaimPumEnable;
gearPumpRunNormalFlag = false;
gearPumpRunNormalValue = 0.0f;
m_rampDone = false;
m_adjSkip = 0;
m_lastPressure = 0;
m_stableCount = 0;
for(int i = 0; i < 10; i++) m_pressureBuf[i] = 0;
m_bufIdx = 0;
}
3.3 注意事项
- PID_realize 函数保留:
PID_realize和PID_init在非齿轮泵模式下仍被使用,保留不变。 - static 变量 u:
static float u在旧版和新版中都被使用,保持文件作用域定义。 - WriteMoterPump() 调用:确认当前项目中
sysPara->devOp->WriteMoterPump()接口一致。 - 测试验证:移植后需要在以下场景重点测试:
- 冷启动(从0开始软启动)
- 热重启(运行中重启)
- 目标压力突变
- 回收泵模式切换
- 手动/自动控制切换
- 超压保护触发
- 长时间运行稳定性
四、总结
| 维度 | 当前项目 | oldProject | 提升 |
|---|---|---|---|
| 超调量 | 较大(无软启动) | 小(软启动+自适应限幅) | ★★★★★ |
| 稳态精度 | 一般(直接输出) | 高(稳定检测+分区Kp) | ★★★★ |
| 响应速度 | 快但不稳 | 自适应(大误差快调,小误差精调) | ★★★★ |
| 超压保护 | 无 | 有硬保护+转速上限保护 | ★★★★★ |
| 抗噪声能力 | 弱(直接采样) | 强(10点滑动平均) | ★★★★ |
| 代码可维护性 | 一般 | 好(分阶段清晰) | ★★★ |
移植风险:低。oldProject 的算法已在生产环境验证,且与当前项目共享相同的 SysPara 数据结构。修改集中在 InkJetPrinter.cpp 和 InkJetPrinter.h 两个文件,不涉及跨模块接口变更。