6月29日崩溃前缓存
This commit is contained in:
+240
-78
@@ -387,102 +387,228 @@ float PID_realize(float speed, float act){
|
|||||||
}
|
}
|
||||||
|
|
||||||
float PID_realize2(float speed, float act){
|
float PID_realize2(float speed, float act){
|
||||||
pid.Kp = 0.8;
|
// 增量式 PID:Δu = Kp*(e[k]-e[k-1]) + Ki*e[k]
|
||||||
pid.Ki = 0.02;
|
pid.Kp = 0.15f;
|
||||||
pid.Kd = 0.5;
|
pid.Ki = 0.02f;
|
||||||
pid.integral_max = 12000;
|
pid.Kd = 0.0f;
|
||||||
pid.SetSpeed=speed;
|
pid.SetSpeed = speed;
|
||||||
pid.ActualSpeed = act;
|
pid.ActualSpeed = act;
|
||||||
pid.err=pid.SetSpeed-pid.ActualSpeed;
|
pid.err = pid.SetSpeed - pid.ActualSpeed;
|
||||||
|
|
||||||
float Pout = pid.Kp * pid.err;
|
float delta_u = pid.Kp * (pid.err - pid.err_last)
|
||||||
pid.integral += pid.err;
|
+ pid.Ki * pid.err;
|
||||||
if (pid.integral > pid.integral_max) {
|
|
||||||
pid.integral = pid.integral_max;
|
|
||||||
} else if (pid.integral < -pid.integral_max) {
|
|
||||||
pid.integral = -pid.integral_max;
|
|
||||||
}
|
|
||||||
float Iout = pid.Ki * pid.integral;
|
|
||||||
float Dout = pid.Kd * ((pid.err + pid.err_last)/2 - pid.err_last);
|
|
||||||
|
|
||||||
pid.voltage = Pout + Iout + Dout;
|
// 自适应非对称限幅:
|
||||||
pid.err_last=pid.err;
|
// 压力严重偏高(err < -30):允许快速减速,限制慢速加速
|
||||||
return pid.voltage;
|
// 压力严重偏低(err > 50):允许正常加速,限制减速(避免过度降速)
|
||||||
|
// 接近目标:小步长,平稳调节
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkJetPrinter::pressureAdj()
|
void InkJetPrinter::pressureAdj()
|
||||||
{
|
{
|
||||||
//float u, actual;
|
if(sysPara->status.gearPumpRunStopFlag || sysPara->status.flagOfSpeedAlarm)
|
||||||
signed short delta_u;
|
{
|
||||||
signed short old = (signed short)sysPara->para.gearPumpValue;
|
sysPara->para.gearPumpValue = 0;
|
||||||
static unsigned int oldPressure = 0;
|
u = 0.0f;
|
||||||
static qint32 printOut = 0;
|
m_rampDone = false;
|
||||||
if(printOut++%2 !=0) {
|
m_adjSkip = 0;
|
||||||
|
m_lastPressure = 0;
|
||||||
|
m_stableCount = 0;
|
||||||
|
for(int i = 0; i < 10; i++) m_pressureBuf[i] = 0;
|
||||||
|
m_bufIdx = 0;
|
||||||
|
sysPara->devOp->WriteMoterPump();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
static qint32 dbgCnt = 0;
|
|
||||||
if(++dbgCnt % 50 == 0){
|
if(!sysPara->para.gearPumpEnable)
|
||||||
// qDebug()<<"[pressureAdj] pressure="<<sysPara->para.pressure
|
{
|
||||||
// <<" PRESSURE="<<sysPara->status.PRESSURE
|
if(u > 0 || ((sysPara->para.pressure - sysPara->status.PRESSURE) > 0))
|
||||||
// <<" gearPumpValue="<<sysPara->para.gearPumpValue
|
u = PID_realize((float)sysPara->para.pressure, (float)sysPara->status.PRESSURE);
|
||||||
// <<" u="<<u
|
sysPara->para.gearPumpValue = (u > 0) ? (unsigned short)u : 0;
|
||||||
// <<" flagOfReclaimPumEnable="<<sysPara->para.flagOfReclaimPumEnable
|
if(sysPara->para.gearPumpValue > 800)
|
||||||
// <<" gearPumpSpeedAlarm="<<sysPara->para.gearPumpSpeedAlarm
|
sysPara->para.gearPumpValue = 800;
|
||||||
// <<" gearPumpRunStopFlag="<<sysPara->status.gearPumpRunStopFlag
|
sysPara->devOp->WriteMoterPump();
|
||||||
// <<" flagOfSpeedAlarm="<<sysPara->status.flagOfSpeedAlarm;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!sysPara->status.gearPumpRunStopFlag)
|
int maxValue = sysPara->para.flagOfReclaimPumEnable ? 420 : 800;
|
||||||
|
if(maxValue < 60) maxValue = 60;
|
||||||
|
|
||||||
|
int maxSpeed = (int)sysPara->para.gearPumpSpeedAlarm;
|
||||||
|
|
||||||
|
auto clampU = [&]() {
|
||||||
|
if(u < 60.0f) u = 60.0f;
|
||||||
|
if(u > (float)maxValue) u = (float)maxValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
int actual = sysPara->status.PRESSURE;
|
||||||
|
int target = (int)sysPara->para.pressure;
|
||||||
|
int cur;
|
||||||
|
|
||||||
|
// 每次进入都更新滑动平均缓冲(软启动用瞬时值,闭环用平均值)
|
||||||
|
m_pressureBuf[m_bufIdx] = actual;
|
||||||
|
m_bufIdx = (m_bufIdx + 1) % 10;
|
||||||
|
int avgPressure = 0;
|
||||||
|
for(int i = 0; i < 10; i++) avgPressure += m_pressureBuf[i];
|
||||||
|
avgPressure /= 10;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
// 硬保护:压力超过目标+50,立即强制减速,优先级最高
|
||||||
|
// 覆盖自检、软启动、闭环所有阶段
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
if(actual > target + 50)
|
||||||
{
|
{
|
||||||
if(sysPara->para.gearPumpEnable && sysPara->para.flagOfReclaimPumEnable)
|
float overErr = (float)(actual - (target + 50));
|
||||||
|
float delta = -0.1f * overErr;
|
||||||
|
if(delta < -15.0f) delta = -15.0f;
|
||||||
|
u += delta;
|
||||||
|
clampU();
|
||||||
|
m_lastPressure = actual;
|
||||||
|
sysPara->para.gearPumpValue = (unsigned short)(int)u;
|
||||||
|
sysPara->devOp->WriteMoterPump();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------
|
||||||
u=PID_realize2((float)sysPara->para.pressure,(float)sysPara->status.PRESSURE);
|
// 软启动:自适应步长,每 800ms 一次
|
||||||
|
// 自检期间也走此逻辑(硬保护兜底),自检完成后重置重新收敛
|
||||||
else if(u > 0 || ((sysPara->para.pressure - sysPara->status.PRESSURE) > 0) )
|
// 软启动阶段不做压力稳定检测,优先快速收敛
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
if(!m_rampDone)
|
||||||
|
{
|
||||||
|
if(++m_adjSkip < 8)
|
||||||
{
|
{
|
||||||
//
|
sysPara->para.gearPumpValue = (unsigned short)(int)u;
|
||||||
u=PID_realize((float)sysPara->para.pressure,(float)sysPara->status.PRESSURE);
|
sysPara->devOp->WriteMoterPump();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
m_adjSkip = 0;
|
||||||
|
|
||||||
if(u > 0)
|
int err = target - actual;
|
||||||
{
|
|
||||||
sysPara->para.gearPumpValue = (unsigned short)u;
|
if(err < 0)
|
||||||
}
|
{
|
||||||
else
|
// 超压:小步减速
|
||||||
{
|
u -= 1.0f;
|
||||||
sysPara->para.gearPumpValue = 0;
|
}
|
||||||
}
|
else
|
||||||
if(sysPara->para.gearPumpValue>800) {
|
{
|
||||||
sysPara->para.gearPumpValue = 800;
|
// 自适应步长:差得远步子大,差得近步子小
|
||||||
}
|
float step;
|
||||||
/*********************改回收泵后压力调整*********************************/
|
if (err > 200) step = 5.0f;
|
||||||
if(sysPara->para.gearPumpValue && sysPara->para.flagOfReclaimPumEnable){
|
else if(err > 100) step = 3.0f;
|
||||||
int maxValue;
|
else if(err > 50) step = 2.0f;
|
||||||
if(sysPara->para.gearPumpSpeedAlarm <= 1200){
|
else step = 1.0f;
|
||||||
maxValue = 200 + (sysPara->para.pressure - 500)*0.1;
|
u += step;
|
||||||
if(maxValue > 260) maxValue = 260;
|
|
||||||
}else{
|
|
||||||
maxValue = 350 + (sysPara->para.pressure - 500)*0.1;
|
|
||||||
if(maxValue > 420) maxValue = 420;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(sysPara->para.gearPumpValue < 60)
|
clampU();
|
||||||
sysPara->para.gearPumpValue = 75;
|
|
||||||
else if(sysPara->para.gearPumpValue > maxValue)
|
|
||||||
sysPara->para.gearPumpValue = maxValue;
|
|
||||||
}
|
|
||||||
/************************************************************/
|
|
||||||
|
|
||||||
if(sysPara->status.gearPumpRunStopFlag)
|
// 转速上限保护
|
||||||
|
if(sysPara->para.flagOfReclaimPumEnable &&
|
||||||
|
sysPara->status.gearPumpRunSpeed >= maxSpeed)
|
||||||
|
{
|
||||||
|
u -= 3.0f;
|
||||||
|
clampU();
|
||||||
|
}
|
||||||
|
|
||||||
|
cur = (int)u;
|
||||||
|
|
||||||
|
// 进入闭环条件:压力已稳定在目标±50以内
|
||||||
|
if(sysPara->status.selfCheckDone && err >= -50 && err <= 50)
|
||||||
|
{
|
||||||
|
m_rampDone = true;
|
||||||
|
m_adjSkip = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
sysPara->para.gearPumpValue = (unsigned short)cur;
|
||||||
|
sysPara->devOp->WriteMoterPump();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
// 闭环:等压力稳定后再调速,自适应管路响应时间
|
||||||
|
//
|
||||||
|
// 每100ms采样一次,连续3次压力变化<15认为稳定,才执行调速
|
||||||
|
// 最长等待5s(50次)兜底,防止管路持续抖动导致永远不调
|
||||||
|
//
|
||||||
|
// 调速后立即重置稳定计数,重新等待下一次稳定
|
||||||
|
//
|
||||||
|
// 分区Kp:误差大步子大,误差小步子小
|
||||||
|
// |err| > 100 → Kp=0.05,限幅±5
|
||||||
|
// |err| 50~100→ Kp=0.04,限幅±4
|
||||||
|
// |err| < 50 → Kp=0.03,限幅±3
|
||||||
|
// ---------------------------------------------------------------
|
||||||
{
|
{
|
||||||
sysPara->para.gearPumpValue = 0;
|
// 稳定检测用平均值,滤掉传感器噪声
|
||||||
|
int pressureChange = avgPressure - m_lastPressure;
|
||||||
|
if(pressureChange < 0) pressureChange = -pressureChange;
|
||||||
|
m_lastPressure = avgPressure;
|
||||||
|
m_adjSkip++; // 复用为超时计数
|
||||||
|
|
||||||
|
bool stable = (pressureChange < 10);
|
||||||
|
bool timeout = (m_adjSkip >= 50); // 5s兜底
|
||||||
|
|
||||||
|
if(stable)
|
||||||
|
m_stableCount++;
|
||||||
|
else
|
||||||
|
m_stableCount = 0;
|
||||||
|
|
||||||
|
bool readyToAdj = (m_stableCount >= 15) || timeout;
|
||||||
|
|
||||||
|
if(!readyToAdj)
|
||||||
|
{
|
||||||
|
sysPara->para.gearPumpValue = (unsigned short)(int)u;
|
||||||
|
sysPara->devOp->WriteMoterPump();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 平均压力已稳定(或超时兜底),执行调速,重置计数
|
||||||
|
m_adjSkip = 0;
|
||||||
|
m_stableCount = 0;
|
||||||
|
|
||||||
|
// 误差用平均值计算,避免瞬时噪声影响调速幅度
|
||||||
|
float absErr = (float)(target - avgPressure);
|
||||||
|
if(absErr < 0.0f) absErr = -absErr;
|
||||||
|
|
||||||
|
float kp, capDelta;
|
||||||
|
if(absErr > 100.0f) { kp = 0.05f; capDelta = 5.0f; }
|
||||||
|
else if(absErr > 50.0f) { kp = 0.04f; capDelta = 4.0f; }
|
||||||
|
else { kp = 0.03f; capDelta = 3.0f; }
|
||||||
|
|
||||||
|
float err = (float)(target - avgPressure);
|
||||||
|
float delta_u = kp * err;
|
||||||
|
|
||||||
|
if(delta_u > capDelta) delta_u = capDelta;
|
||||||
|
if(delta_u < -capDelta) delta_u = -capDelta;
|
||||||
|
|
||||||
|
// 转速上限软限制:转速已达上限时禁止继续加速
|
||||||
|
if(sysPara->para.flagOfReclaimPumEnable &&
|
||||||
|
sysPara->status.gearPumpRunSpeed >= maxSpeed)
|
||||||
|
{
|
||||||
|
if(delta_u > 0.0f) delta_u = 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
u += delta_u;
|
||||||
|
clampU();
|
||||||
|
cur = (int)u;
|
||||||
|
|
||||||
|
sysPara->para.gearPumpValue = (unsigned short)cur;
|
||||||
|
sysPara->devOp->WriteMoterPump();
|
||||||
}
|
}
|
||||||
if(sysPara->status.flagOfSpeedAlarm)
|
|
||||||
sysPara->para.gearPumpValue = 0;
|
|
||||||
sysPara->devOp->WriteMoterPump();
|
|
||||||
oldPressure = sysPara->status.PRESSURE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -580,10 +706,11 @@ void InkJetPrinter::timerHandler(void){
|
|||||||
if(!(sysPara->status.statWarn & WARN_STATUS_INK_WARN) && !gearPumpRunFlag && !sysPara->status.isPumpValveControledByHand)//刚启动前3秒
|
if(!(sysPara->status.statWarn & WARN_STATUS_INK_WARN) && !gearPumpRunFlag && !sysPara->status.isPumpValveControledByHand)//刚启动前3秒
|
||||||
{
|
{
|
||||||
if(sysPara->para.gearPumpEnable) {
|
if(sysPara->para.gearPumpEnable) {
|
||||||
sysPara->para.gearPumpValue = 150;
|
// 确保开机时控制字从 0 开始,让 pressureAdj 软启动从 0 爬坡,避免突变到高转速
|
||||||
if(tempFlag){
|
if(sysPara->para.gearPumpValue != 0) {
|
||||||
|
sysPara->para.gearPumpValue = 0;
|
||||||
|
u = 0.0f;
|
||||||
sysPara->devOp->WriteMoterPump();
|
sysPara->devOp->WriteMoterPump();
|
||||||
tempFlag=0;
|
|
||||||
}
|
}
|
||||||
gearPumpStartFlag = true;
|
gearPumpStartFlag = true;
|
||||||
}
|
}
|
||||||
@@ -613,6 +740,19 @@ void InkJetPrinter::timerHandler(void){
|
|||||||
ControledSwitchFlag = false;
|
ControledSwitchFlag = false;
|
||||||
pulse_3s = 0;
|
pulse_3s = 0;
|
||||||
}
|
}
|
||||||
|
// 检测手动→自动边沿,直接恢复压力控制,不走 gearPumpStartFlag 的 3s 等待
|
||||||
|
if(m_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;
|
||||||
|
}
|
||||||
|
m_lastByHand = sysPara->status.isPumpValveControledByHand;
|
||||||
if(!(sysPara->status.statWarn & WARN_STATUS_INK_SWINT)){//墨线开关,压力抖动
|
if(!(sysPara->status.statWarn & WARN_STATUS_INK_SWINT)){//墨线开关,压力抖动
|
||||||
InkSwintFlag = true;
|
InkSwintFlag = true;
|
||||||
}
|
}
|
||||||
@@ -654,6 +794,19 @@ void InkJetPrinter::timerHandler(void){
|
|||||||
test_flag = 0;
|
test_flag = 0;
|
||||||
}
|
}
|
||||||
if(pulse_1s) {
|
if(pulse_1s) {
|
||||||
|
// 回收泵模式切换时重置软启动和泄漏保护学习状态
|
||||||
|
if(m_lastReclaimMode != sysPara->para.flagOfReclaimPumEnable)
|
||||||
|
{
|
||||||
|
m_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;
|
||||||
|
}
|
||||||
if(sysPara->status.selfCheckDone && sysPara->para.gearPumpEnable && sysPara->para.flagOfReclaimPumEnable)
|
if(sysPara->status.selfCheckDone && sysPara->para.gearPumpEnable && sysPara->para.flagOfReclaimPumEnable)
|
||||||
checkPressureControl();
|
checkPressureControl();
|
||||||
if((QTime::currentTime()>=QTime(00,00,00))&&(QTime::currentTime()<=QTime(00,00,02))&&sysPara->para.Reciclar){
|
if((QTime::currentTime()>=QTime(00,00,00))&&(QTime::currentTime()<=QTime(00,00,02))&&sysPara->para.Reciclar){
|
||||||
@@ -881,6 +1034,15 @@ void InkJetPrinter::timerHandler(void){
|
|||||||
if(sysPara->status.selfCheckCnt >= MAX_SELF_CHECK_CNT)
|
if(sysPara->status.selfCheckCnt >= MAX_SELF_CHECK_CNT)
|
||||||
{
|
{
|
||||||
sysPara->status.selfCheckDone = 1;
|
sysPara->status.selfCheckDone = 1;
|
||||||
|
// 自检完成后管道恢复正常阻力,重置软启动让压力重新平稳收敛
|
||||||
|
// 不清零 u,从当前控制字开始,避免突变
|
||||||
|
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;
|
||||||
LogInfo::setRunStatusText(tr("自检完成!!!"));
|
LogInfo::setRunStatusText(tr("自检完成!!!"));
|
||||||
m_MainWidget->setRunStatus(tr("自检完成!!!"));
|
m_MainWidget->setRunStatus(tr("自检完成!!!"));
|
||||||
qDebug()<<"自检完成!!!";
|
qDebug()<<"自检完成!!!";
|
||||||
|
|||||||
@@ -204,4 +204,15 @@ public slots:
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
bool eventFilter(QObject *watched, QEvent *event);
|
bool eventFilter(QObject *watched, QEvent *event);
|
||||||
|
|
||||||
|
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; // 上一帧回收泵模式(边沿检测用)
|
||||||
};
|
};
|
||||||
@@ -0,0 +1,240 @@
|
|||||||
|
# PrintSetting 崩溃风险分析报告
|
||||||
|
|
||||||
|
## 涉及文件
|
||||||
|
|
||||||
|
| 文件 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| [Printsetting.h](../Printsetting.h) | `PrintSettingModel` 结构体 + `PrintSetting` 类声明 |
|
||||||
|
| [Printsetting.cpp](../Printsetting.cpp) | `PrintSetting` 类实现 + `PrintSettingModel` 静态方法实现 |
|
||||||
|
| [testprintwidget.cpp](../testprintwidget.cpp) | `TestPrintWidget` 中调用 `m_printSetting` 的代码 |
|
||||||
|
| [InkJetPrinter.cpp](../InkJetPrinter.cpp) | `InkJetPrinter` 中使用 `m_PrintSetting` 的代码 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔴 高风险(必定崩溃)
|
||||||
|
|
||||||
|
### 1. `applyPrintSettingsToSysPara()` — `sysPara->devOp` 在 if 保护块外被访问
|
||||||
|
**文件**: [Printsetting.cpp:600-609](../Printsetting.cpp#L600-L609)
|
||||||
|
```cpp
|
||||||
|
void PrintSetting::applyPrintSettingsToSysPara(bool showTip)
|
||||||
|
{
|
||||||
|
updateModelFromUI();
|
||||||
|
PrintSettingModel::applyToSysPara(m_model, sysPara); // L600: 无 null 检查
|
||||||
|
|
||||||
|
if (sysPara) { // L602: 有检查
|
||||||
|
sysPara->devOp->setHighVoltageAmplitudetoSTM32();
|
||||||
|
sysPara->devOp->writeFpgaCalandar(0);
|
||||||
|
sysPara->devOp->writeFlightCompCtrl(...);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(sysPara->devOp->writeFpgaPrintPara(true) != -1 && showTip) { // L609: ❌ 在 if(sysPara) 之外!
|
||||||
|
QMessageBox box(...);
|
||||||
|
box.exec();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(sysPara)
|
||||||
|
sysPara->saveParaToFile();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
**问题**:
|
||||||
|
- L600 调用 `PrintSettingModel::applyToSysPara(m_model, sysPara)` 时未检查 `sysPara` 是否为 null。
|
||||||
|
- L609 的 `sysPara->devOp->writeFpgaPrintPara(true)` 在 `if(sysPara)` 保护块**之外**,如果 `sysPara` 为 null,直接崩溃。
|
||||||
|
- L602-L606 虽在 `if(sysPara)` 内,但 `sysPara->devOp` 本身也可能为 null。
|
||||||
|
|
||||||
|
**触发场景**:`bindSysPara()` 未被调用或 `sysPara` 被意外置空时,用户点击"应用"按钮。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. `PrintSettingModel::applyToSysPara()` — `sysPara` 参数无 null 检查
|
||||||
|
**文件**: [Printsetting.cpp:743-799](../Printsetting.cpp#L743-L799)
|
||||||
|
```cpp
|
||||||
|
void PrintSettingModel::applyToSysPara(const PrintSettingModel &model, SysPara *sysPara)
|
||||||
|
{
|
||||||
|
sysPara->para.leftRightInverse = model.leftRightInverse; // ❌ 无 null 检查
|
||||||
|
sysPara->para.upDownInverse = model.upDownInverse;
|
||||||
|
// ... 后续约 30 行全部直接访问 sysPara->para.xxx
|
||||||
|
}
|
||||||
|
```
|
||||||
|
**问题**:整个函数体没有对 `sysPara` 做 null 检查,直接解引用。如果传入 null,立即崩溃。
|
||||||
|
|
||||||
|
**调用链**:
|
||||||
|
- `PrintSetting::applyPrintSettingsToSysPara()` → `PrintSettingModel::applyToSysPara(m_model, sysPara)`
|
||||||
|
- `PrintSetting::bindSysPara()` → `PrintSettingModel::fromSysPara(sysPara)` (同样无 null 检查,见下文)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. `PrintSettingModel::fromSysPara()` — `sysPara` 参数无 null 检查
|
||||||
|
**文件**: [Printsetting.cpp:802-845](../Printsetting.cpp#L802-L845)
|
||||||
|
```cpp
|
||||||
|
PrintSettingModel PrintSettingModel::fromSysPara(SysPara *sysPara)
|
||||||
|
{
|
||||||
|
PrintSettingModel model;
|
||||||
|
model.leftRightInverse = sysPara->para.leftRightInverse; // ❌ 无 null 检查
|
||||||
|
model.upDownInverse = sysPara->para.upDownInverse;
|
||||||
|
// ... 后续约 30 行全部直接访问 sysPara->para.xxx
|
||||||
|
}
|
||||||
|
```
|
||||||
|
**问题**:与 `applyToSysPara()` 相同,整个函数体没有对 `sysPara` 做 null 检查。
|
||||||
|
|
||||||
|
**调用链**:
|
||||||
|
- `PrintSetting::bindSysPara()` → `PrintSettingModel::fromSysPara(sysPara)`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. `on_pushButtonApplyPulseFix_clicked()` — `sysPara` 可能为 null
|
||||||
|
**文件**: [Printsetting.cpp:287-290](../Printsetting.cpp#L287-L290)
|
||||||
|
```cpp
|
||||||
|
void PrintSetting::on_pushButtonApplyPulseFix_clicked()
|
||||||
|
{
|
||||||
|
pulseNumFix->bindSysPara(sysPara); // ❌ sysPara 可能为 null
|
||||||
|
pulseNumFix->hide();
|
||||||
|
pulseNumFix->show();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
**问题**:未检查 `sysPara` 是否为 null,直接传给 `pulseNumFix->bindSysPara()`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5. TestPrintWidget 中 `m_printSetting` 空指针解引用(3处)
|
||||||
|
|
||||||
|
已在 [TestPrintWidget 崩溃风险分析报告](./TestPrintWidget_Crash_Risk_Analysis.md) 中详述,此处汇总:
|
||||||
|
|
||||||
|
| 位置 | 代码 | 问题 |
|
||||||
|
|------|------|------|
|
||||||
|
| [testprintwidget.cpp:435](../testprintwidget.cpp#L435) | `PrintSettingModel::saveToXml(m_printSetting->model(), writer)` | `m_printSetting` 可能为 null |
|
||||||
|
| [testprintwidget.cpp:621-622](../testprintwidget.cpp#L621-L622) | `m_printSetting->setModel(model)` / `m_printSetting->applyPrintSettingsToSysPara(false)` | `m_printSetting` 可能为 null |
|
||||||
|
| [testprintwidget.cpp:1122](../testprintwidget.cpp#L1122) | `m_printSetting->setModel(defModel)` | `m_printSetting` 可能为 null |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🟡 中风险(特定条件下崩溃)
|
||||||
|
|
||||||
|
### 6. `applyPrintSettingsToSysPara()` — `sysPara->devOp` 可能为 null
|
||||||
|
**文件**: [Printsetting.cpp:603-606](../Printsetting.cpp#L603-L606)
|
||||||
|
```cpp
|
||||||
|
if (sysPara) {
|
||||||
|
sysPara->devOp->setHighVoltageAmplitudetoSTM32(); // devOp 可能为 null
|
||||||
|
sysPara->devOp->writeFpgaCalandar(0); // devOp 可能为 null
|
||||||
|
sysPara->devOp->writeFlightCompCtrl(...); // devOp 可能为 null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
**问题**:虽然检查了 `sysPara`,但 `sysPara->devOp` 指针本身可能为 null(取决于 `SysPara` 的初始化时机)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 7. `on_checkEyeTriggerSynPrintEn_clicked()` — `sysPara` 有检查但 `comboBoxSyncActType` 可能越界
|
||||||
|
**文件**: [Printsetting.cpp:302-340](../Printsetting.cpp#L302-L340)
|
||||||
|
```cpp
|
||||||
|
void PrintSetting::on_checkEyeTriggerSynPrintEn_clicked(bool checked)
|
||||||
|
{
|
||||||
|
if(sysPara) // ✅ 有 null 检查
|
||||||
|
{
|
||||||
|
// ...
|
||||||
|
if(ui->comboBoxSyncActType->currentIndex() >= ui->comboBoxSyncActType->count()
|
||||||
|
|| ui->comboBoxSyncActType->currentIndex() == -1)
|
||||||
|
{
|
||||||
|
ui->comboBoxSyncActType->setCurrentIndex(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
**问题**:`comboBoxSyncActType` 的 `currentIndex()` 值为 -1 时的处理是正确的,但如果 comboBox 未正确初始化(count() 为 0),`setCurrentIndex(0)` 可能无效,但不会崩溃。此处理论上安全,标注为中风险仅供参考。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🟢 低风险(逻辑瑕疵,不太可能崩溃)
|
||||||
|
|
||||||
|
### 8. `PrintSettingModel::toString()` — 格式说明符类型不匹配
|
||||||
|
**文件**: [Printsetting.cpp:849-856](../Printsetting.cpp#L849-L856)
|
||||||
|
```cpp
|
||||||
|
QString PrintSettingModel::toString(const PrintSettingModel &model)
|
||||||
|
{
|
||||||
|
QString str = QString::asprintf("LRInverse: %d, ... triggerInterval: %d, ... eleEyeRepeatCnt: %d, ...",
|
||||||
|
model.triggerInterval, ... model.eleEyeRepeatCnt, ...);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
**问题**:`model.triggerInterval` 和 `model.eleEyeRepeatCnt` 是 `unsigned` 类型,但格式说明符用了 `%d`(有符号)。不会崩溃,但可能输出异常值(如大数字显示为负数)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 9. `PrintSettingModel::defPrintSettingModel()` — 文件不存在时静默返回默认值
|
||||||
|
**文件**: [Printsetting.cpp:860-873](../Printsetting.cpp#L860-L873)
|
||||||
|
```cpp
|
||||||
|
PrintSettingModel PrintSettingModel::defPrintSettingModel()
|
||||||
|
{
|
||||||
|
QFile file(DEF_PRINT_SETTINGS_PATH);
|
||||||
|
if (!file.open(QIODevice::ReadOnly)) {
|
||||||
|
return PrintSettingModel(); // 静默返回默认构造
|
||||||
|
}
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
**问题**:文件打开失败时没有日志输出,不利于调试。不会崩溃。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 10. `updateModelFromUI()` — 无返回值校验
|
||||||
|
**文件**: [Printsetting.cpp:114-170](../Printsetting.cpp#L114-L170)
|
||||||
|
```cpp
|
||||||
|
m_model.encoderPulsePerMm = ui->lineEditTriggerPulsePerMm->text().toFloat();
|
||||||
|
```
|
||||||
|
**问题**:`toFloat()` 在输入为空时返回 0.0,不会崩溃。但如果用户输入了非法字符(虽然有 `QDoubleValidator`),结果可能不符合预期。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 11. `on_radioButtonEleEyeLevelTrigger_pressed()` 等函数中 `setEnableSynTriggerSetting` 的判断逻辑
|
||||||
|
**文件**: [Printsetting.cpp:310-325](../Printsetting.cpp#L310-L325)
|
||||||
|
```cpp
|
||||||
|
void PrintSetting::on_radioButtonEleEyeEdgetTrigger_pressed()
|
||||||
|
{
|
||||||
|
setTriggerUI(PRINT_TRIGGER_ELEC_EYE_EDGE);
|
||||||
|
if(ui->radioButtonSynTriggerExternal->isChecked())
|
||||||
|
setEnableSynTriggerSetting(true);
|
||||||
|
else
|
||||||
|
setEnableSynTriggerSetting(false);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
**问题**:这些函数未检查 `sysPara` 是否为 null 就调用 `setTriggerUI`,但 `setTriggerUI` 只操作 `m_model` 和 UI 控件,不依赖 `sysPara`,所以安全。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 修复建议汇总
|
||||||
|
|
||||||
|
| 优先级 | 位置 | 问题 | 修复方案 |
|
||||||
|
|--------|------|------|----------|
|
||||||
|
| **P0** | `applyPrintSettingsToSysPara()` L600 | `sysPara` 未检查就传给 `applyToSysPara` | 在 L600 前加 `if(!sysPara) return;` |
|
||||||
|
| **P0** | `applyPrintSettingsToSysPara()` L609 | `sysPara->devOp` 在 if 块外访问 | 将 L609-L614 移入 `if(sysPara)` 块内 |
|
||||||
|
| **P0** | `PrintSettingModel::applyToSysPara()` L743 | `sysPara` 无 null 检查 | 函数开头加 `if(!sysPara) return;` |
|
||||||
|
| **P0** | `PrintSettingModel::fromSysPara()` L802 | `sysPara` 无 null 检查 | 函数开头加 `if(!sysPara) return PrintSettingModel();` |
|
||||||
|
| **P0** | `on_pushButtonApplyPulseFix_clicked()` L287 | `sysPara` 未检查 | 加 `if(!sysPara) return;` |
|
||||||
|
| **P1** | `applyPrintSettingsToSysPara()` L603 | `sysPara->devOp` 可能 null | 加 `if(sysPara->devOp)` 判断 |
|
||||||
|
| **P1** | `testprintwidget.cpp` 多处 | `m_printSetting` 可能 null | 加 `if(m_printSetting)` 判断(共3处) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 崩溃调用链总结
|
||||||
|
|
||||||
|
```
|
||||||
|
用户点击"应用"按钮
|
||||||
|
→ PrintSetting::on_pushButtonApply_pressed()
|
||||||
|
→ PrintSetting::applyPrintSettingsToSysPara(true)
|
||||||
|
→ PrintSettingModel::applyToSysPara(m_model, sysPara) ← ❌ sysPara 可能 null
|
||||||
|
→ sysPara->devOp->writeFpgaPrintPara(true) ← ❌ 在 if(sysPara) 外
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
用户点击"脉冲修正"按钮
|
||||||
|
→ PrintSetting::on_pushButtonApplyPulseFix_clicked()
|
||||||
|
→ pulseNumFix->bindSysPara(sysPara) ← ❌ sysPara 可能 null
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
加载/新建文件
|
||||||
|
→ TestPrintWidget::saveFile() / parseVbfile() / on_newfileButton_pressed()
|
||||||
|
→ m_printSetting->model() / setModel() / applyPrintSettingsToSysPara() ← ❌ m_printSetting 可能 null
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 生成时间
|
||||||
|
2026-06-29
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
# TestPrintWidget 崩溃风险分析报告
|
||||||
|
|
||||||
|
## 🔴 高风险(必定崩溃)
|
||||||
|
|
||||||
|
### 1. `saveFile()` — `m_printSetting` 空指针解引用
|
||||||
|
**文件**: [testprintwidget.cpp:435](../testprintwidget.cpp#L435)
|
||||||
|
```cpp
|
||||||
|
PrintSettingModel::saveToXml(m_printSetting->model(), writer);
|
||||||
|
```
|
||||||
|
`m_printSetting` 在 `setPrintSettingWidget()` 调用前为 `nullptr`。如果用户在 `setPrintSettingWidget()` 被调用之前就触发了保存,程序会崩溃。虽然 `InkJetPrinter.cpp:104` 在构造时调用了它,但如果构造顺序出现问题或 `m_PrintSetting` 尚未初始化,就会崩溃。
|
||||||
|
|
||||||
|
### 2. `saveFile()` — `qgraphicsitem_cast` 返回 null 后解引用
|
||||||
|
**文件**: [testprintwidget.cpp:406](../testprintwidget.cpp#L406)
|
||||||
|
```cpp
|
||||||
|
PicTextItem *picText = qgraphicsitem_cast<PicTextItem*>(item);
|
||||||
|
writer.writeTextElement(..., QString::fromLocal8Bit("%1").arg(picText->pos().x()));
|
||||||
|
```
|
||||||
|
`qgraphicsitem_cast` 可能返回 null(当 item 不是 PicTextItem 类型时),紧接着调用 `picText->pos().x()` 就会崩溃。
|
||||||
|
|
||||||
|
### 3. `onFileOpen()` — `sysPara->devOp` 空指针解引用
|
||||||
|
**文件**: [testprintwidget.cpp:477](../testprintwidget.cpp#L477)
|
||||||
|
```cpp
|
||||||
|
sysPara->devOp->fpgaWr(0xa4b0, (unsigned char *)&SpecialVarEnable, 1);
|
||||||
|
```
|
||||||
|
- `sysPara` 可能为 null(`bindSysPara()` 未被调用时)
|
||||||
|
- `sysPara->devOp` 也可能为 null
|
||||||
|
|
||||||
|
### 4. `on_newfileButton_pressed()` — `m_printSetting` 空指针解引用
|
||||||
|
**文件**: [testprintwidget.cpp:1122](../testprintwidget.cpp#L1122)
|
||||||
|
```cpp
|
||||||
|
m_printSetting->setModel(defModel);
|
||||||
|
```
|
||||||
|
同样的问题,`m_printSetting` 可能为 null。
|
||||||
|
|
||||||
|
### 5. `getShowSelectItemText()` — `dynamic_cast` 后无空检查
|
||||||
|
**文件**: [testprintwidget.cpp:312-317](../testprintwidget.cpp#L312-L317)
|
||||||
|
```cpp
|
||||||
|
PicTextItem* txtItem = dynamic_cast<PicTextItem*>(item);
|
||||||
|
if(txtItem->sourceType() != SourceType::IMAGE && txtItem->sourceType() != SourceType::CODE ...) {
|
||||||
|
lastSelectedTxtItem = txtItem;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
如果场景中包含非 `PicTextItem` 派生类的 `QGraphicsItem`,`dynamic_cast` 返回 null,直接解引用 `txtItem->sourceType()` 崩溃。
|
||||||
|
|
||||||
|
### 6. `onBtnItemConfigClicked()` — `dynamic_cast` 后无空检查
|
||||||
|
**文件**: [testprintwidget.cpp:1042-1043](../testprintwidget.cpp#L1042-L1043)
|
||||||
|
```cpp
|
||||||
|
auto item = dynamic_cast<PicTextItem*>(selectedItems[0]);
|
||||||
|
if(item->sourceType() == SourceType::COUNTER) {
|
||||||
|
```
|
||||||
|
如果选中的 item 不是 `PicTextItem` 类型,`dynamic_cast` 返回 null,直接崩溃。
|
||||||
|
|
||||||
|
### 7. `addDateTimeItem()` / `addScheduleShiftsItem()` — `dynamic_cast` 后无空检查
|
||||||
|
**文件**: [testprintwidget.cpp:1310-1312](../testprintwidget.cpp#L1310-L1312)
|
||||||
|
```cpp
|
||||||
|
auto item = dynamic_cast<PicTextItem*>(selectItems[0]);
|
||||||
|
if(item->sourceType() == SourceType::DATETIME) {
|
||||||
|
```
|
||||||
|
同样的问题,`testprintwidget.cpp:1338` 也存在。
|
||||||
|
|
||||||
|
### 8. `onEditItemDone()` — C风格强转,无空检查
|
||||||
|
**文件**: [testprintwidget.cpp:287](../testprintwidget.cpp#L287)
|
||||||
|
```cpp
|
||||||
|
PicTextItem *pTextItem = (PicTextItem *)item;
|
||||||
|
if(pTextItem->sourceType() == SourceType::TEXT)
|
||||||
|
pTextItem->setSource(content);
|
||||||
|
```
|
||||||
|
C风格强转不做任何类型检查,如果 item 不是 PicTextItem,访问 `sourceType()` 是未定义行为。
|
||||||
|
|
||||||
|
### 9. `addCounterItem()` — `dynamic_cast` 后无空检查
|
||||||
|
**文件**: [testprintwidget.cpp:1380](../testprintwidget.cpp#L1380)
|
||||||
|
```cpp
|
||||||
|
auto item = dynamic_cast<PicTextItem*>(items[index]);
|
||||||
|
font = f ? *f : item->font();
|
||||||
|
```
|
||||||
|
如果选中的 item 不是 PicTextItem,`item->font()` 崩溃。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🟡 中风险(特定条件下崩溃)
|
||||||
|
|
||||||
|
### 10. `onItemEditorAccepted()` — `currentItem()` 可能返回 null
|
||||||
|
**文件**: [testprintwidget.cpp:1095](../testprintwidget.cpp#L1095)
|
||||||
|
```cpp
|
||||||
|
if(itemEditor->currentItem()->source() == SourceType::UNKNOWN) {
|
||||||
|
```
|
||||||
|
`currentItem()` 可能返回 null 导致崩溃。
|
||||||
|
|
||||||
|
### 11. `parseVbfile()` 加载 XML 时 `m_printSetting` 空指针
|
||||||
|
**文件**: [testprintwidget.cpp:621-622](../testprintwidget.cpp#L621-L622)
|
||||||
|
```cpp
|
||||||
|
m_printSetting->setModel(model);
|
||||||
|
m_printSetting->applyPrintSettingsToSysPara(false);
|
||||||
|
```
|
||||||
|
当加载带有 PrintSettings 的 XML 文件时,`loadPrintSetting` 为 true 才会执行,但如果 `m_printSetting` 为 null,会崩溃。
|
||||||
|
|
||||||
|
### 12. `on_cbResolution_currentIndexChanged()` — 数组越界后的修复逻辑不完整
|
||||||
|
**文件**: [testprintwidget.cpp:1274-1280](../testprintwidget.cpp#L1274-L1280)
|
||||||
|
```cpp
|
||||||
|
if(index < 0 || int(ARRAY_SIZE(IdxToFontFamily)) <= index) {
|
||||||
|
index = std::max(std::min(int(ARRAY_SIZE(IdxToFontFamily) - 1), index), 0);
|
||||||
|
...
|
||||||
|
}
|
||||||
|
auto selectedItems = pPicScene->selectedItems();
|
||||||
|
...
|
||||||
|
qInfo() << "Selected font family: " << IdxToFontFamily[index];
|
||||||
|
```
|
||||||
|
虽然有边界修正,但 `IdxToFontFamily` 是一个局部 `constexpr` 数组,如果 `ARRAY_SIZE` 宏未正确定义,可能导致越界访问。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🟢 低风险(逻辑问题,不太可能崩溃)
|
||||||
|
|
||||||
|
### 13. `pasteCopiedLayer()` — 临时对象传入 nullptr
|
||||||
|
```cpp
|
||||||
|
PicTextItem tempSrc(nullptr, nullptr, m_copiedSourceType);
|
||||||
|
```
|
||||||
|
用 `nullptr` 作为 parent 和 scene 创建临时对象,仅用于类型判断。如果 PicTextItem 构造函数中对 nullptr 做了安全处理,则没问题。
|
||||||
|
|
||||||
|
### 14. `onSceneFocusedItemPosChanged()` — `QString::asprintf` 格式字符串不安全
|
||||||
|
**文件**: [testprintwidget.cpp:1112-1113](../testprintwidget.cpp#L1112-L1113)
|
||||||
|
```cpp
|
||||||
|
ui.m_lblX->setText(QString::asprintf("X:%d", p.x()));
|
||||||
|
ui.m_lblY->setText(QString::asprintf("Y:%d", p.y()));
|
||||||
|
```
|
||||||
|
使用 `asprintf` 而非 `QString::number()`,虽然不会崩溃,但不是最佳实践。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 修复建议汇总
|
||||||
|
|
||||||
|
| 优先级 | 位置 | 问题 | 修复方案 |
|
||||||
|
|--------|------|------|----------|
|
||||||
|
| **P0** | `saveFile()` L435 | `m_printSetting` 空指针 | 加 `if(m_printSetting)` 判断 |
|
||||||
|
| **P0** | `saveFile()` L406 | `qgraphicsitem_cast` 返回 null | 加 null 检查,跳过非 PicTextItem |
|
||||||
|
| **P0** | `onFileOpen()` L477 | `sysPara->devOp` 空指针 | 加 `if(sysPara && sysPara->devOp)` |
|
||||||
|
| **P0** | `getShowSelectItemText()` L312 | `dynamic_cast` 无空检查 | 加 `if(txtItem)` |
|
||||||
|
| **P0** | `onBtnItemConfigClicked()` L1042 | `dynamic_cast` 无空检查 | 加 `if(item)` |
|
||||||
|
| **P0** | `addDateTimeItem()` L1310 | `dynamic_cast` 无空检查 | 加 `if(item)` |
|
||||||
|
| **P0** | `addScheduleShiftsItem()` L1338 | `dynamic_cast` 无空检查 | 加 `if(item)` |
|
||||||
|
| **P0** | `onEditItemDone()` L287 | C风格强转无检查 | 改用 `dynamic_cast` + null 检查 |
|
||||||
|
| **P0** | `addCounterItem()` L1380 | `dynamic_cast` 无空检查 | 加 `if(item)` |
|
||||||
|
| **P0** | `on_newfileButton_pressed()` L1122 | `m_printSetting` 空指针 | 加 `if(m_printSetting)` |
|
||||||
|
| **P1** | `parseVbfile()` L621 | `m_printSetting` 空指针 | 加 `if(m_printSetting)` |
|
||||||
|
| **P1** | `onItemEditorAccepted()` L1095 | `currentItem()` 可能 null | 加 null 检查 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 生成时间
|
||||||
|
2026-06-29
|
||||||
@@ -0,0 +1,333 @@
|
|||||||
|
# 齿轮泵压力调节算法移植方案
|
||||||
|
|
||||||
|
## 一、概述
|
||||||
|
|
||||||
|
`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` 两个文件,不涉及跨模块接口变更。
|
||||||
Reference in New Issue
Block a user