# 齿轮泵压力调节算法移植方案 ## 一、概述 `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() ``` **问题:** 1. 停止/告警状态在函数末尾才处理,中间已经执行了PID计算 2. 无条件直接输出PID结果,没有压力稳定等待 3. 没有超压硬保护,压力飙升时响应慢 4. 回收泵限幅逻辑复杂且硬编码 #### 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() ``` **优势:** 1. 停止/告警立即处理,状态完全复位 2. 硬保护优先于一切,安全性最高 3. 软启动避免开机时压力突变 4. 闭环等待压力稳定后再调,避免管路抖动导致误调 5. 滑动平均滤除传感器噪声 6. 分区Kp实现大误差快调、小误差精调 7. 转速上限保护防止电机超速 --- ### 2.4 新增成员变量 oldProject 在 `InkJetPrinter.h` 中新增了以下成员变量: ```cpp 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()` 中还有以下配套修改,确保状态机正确工作: 1. **开机/重启时清零**:`gearPumpValue=0, u=0`,确保软启动从0开始爬坡 2. **自检完成时重置软启动**:`selfCheckDone` 变为1时重置 `m_rampDone=false` 及所有缓冲 3. **手动→自动切换时重置**:`m_lastByHand` 边沿检测,切换回自动时重置软启动 4. **回收泵模式切换时重置**:`m_lastReclaimMode` 边沿检测,模式切换时重置软启动和泄漏保护学习状态 --- ## 三、移植方案 ### 3.1 需要修改的文件 | 文件 | 修改内容 | |------|---------| | `InkJetPrinter.h` | 新增6个成员变量 + `m_lastByHand` + `m_lastReclaimMode` | | `InkJetPrinter.cpp` | 替换 `PID_realize2` + 替换 `pressureAdj` + 修改 `timerHandler` 中的配套逻辑 | ### 3.2 详细修改步骤 #### 步骤1:修改 InkJetPrinter.h — 新增成员变量 在 `InkJetPrinter` 类的 private 区域添加: ```cpp // 压力调节算法状态变量 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 的增量式版本。 **替换前(当前项目):** ```cpp 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):** ```cpp 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行): ```cpp if(sysPara->para.gearPumpEnable) { sysPara->para.gearPumpValue = 150; if(tempFlag){ sysPara->devOp->WriteMoterPump(); tempFlag=0; } gearPumpStartFlag = true; } ``` 改为: ```cpp 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行),在设置完成后添加: ```cpp 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行附近),添加边沿检测: ```cpp // 在 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 中增加: ```cpp 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 注意事项 1. **PID_realize 函数保留**:`PID_realize` 和 `PID_init` 在非齿轮泵模式下仍被使用,保留不变。 2. **static 变量 u**:`static float u` 在旧版和新版中都被使用,保持文件作用域定义。 3. **WriteMoterPump() 调用**:确认当前项目中 `sysPara->devOp->WriteMoterPump()` 接口一致。 4. **测试验证**:移植后需要在以下场景重点测试: - 冷启动(从0开始软启动) - 热重启(运行中重启) - 目标压力突变 - 回收泵模式切换 - 手动/自动控制切换 - 超压保护触发 - 长时间运行稳定性 --- ## 四、总结 | 维度 | 当前项目 | oldProject | 提升 | |------|---------|-----------|------| | 超调量 | 较大(无软启动) | 小(软启动+自适应限幅) | ★★★★★ | | 稳态精度 | 一般(直接输出) | 高(稳定检测+分区Kp) | ★★★★ | | 响应速度 | 快但不稳 | 自适应(大误差快调,小误差精调) | ★★★★ | | 超压保护 | 无 | 有硬保护+转速上限保护 | ★★★★★ | | 抗噪声能力 | 弱(直接采样) | 强(10点滑动平均) | ★★★★ | | 代码可维护性 | 一般 | 好(分阶段清晰) | ★★★ | 移植风险:**低**。oldProject 的算法已在生产环境验证,且与当前项目共享相同的 `SysPara` 数据结构。修改集中在 `InkJetPrinter.cpp` 和 `InkJetPrinter.h` 两个文件,不涉及跨模块接口变更。