333 lines
13 KiB
Markdown
333 lines
13 KiB
Markdown
# 齿轮泵压力调节算法移植方案
|
||
|
||
## 一、概述
|
||
|
||
`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` 两个文件,不涉及跨模块接口变更。 |