Приветствую вас, дорогие коллеги!
Столкнулся с проблемой, погружение в которую вызывает у меня ассоциации с Алисой из "Страны Чудес" -
становится всё страньше и страньше.
Итак, имеем несколько воздушных турбо-компрессоров одинаковой модели фирмы, название которой начинается на КОП, а заканчивается на КО.
Когда-то давно эти компрессоры были заказаны с управлением на базе CompactLogix (почему так - отдельная и длинная история).
Технологи жалуются на нестабильную работу ведущего компрессора при работе компрессоров в паре. Приводы входного направляющего аппарата и разгрузочного клапана начинают рыскать почти по всему диапазону, компрессор добром не качает...
Поиски вывели на странный функциональный блок ПИД-регулятора с функцией мёртвой зоны.
Блок этот, судя по всему, не из стандартной библиотеки, а порождение сумрачного итальяно-германского гения.
- [+] ST-код этого ПИДа
Код: Выделить всё
// PID REGULATION
// Update Control Value Internal
// Necessary when enabling-disabling PID (and changing CV)
// is faster then the PID execution interval
IF Invert_ControlValue THEN
ControlValue_Internal := ControlValue_Max - (ControlValue - ControlValue_Min);
ELSE
ControlValue_Internal := ControlValue;
END_IF;
//Check if ProcesValue_Gain is OK to avoid dividing by zero and not accept negative values
//ProcesValue_Gain is inroduced to have that are similar as the ones used in Elektonikon :
//- Elektonikon uses cbar & PLC uses mbar as Engineering units
//- Kp, Ki, Kd values independent of control value range (BOV : 0->100%; rpm : e.g. 1000-2500)
IF ProcesValue_Gain <= 0 THEN
ProcesValue_Gain := 1;
END_IF;
//Calculation error
Error := (ProcesSetPoint - ProcesValue)/ProcesValue_Gain;
//-------------------------------------------Deadband---------------------------------------------------------------
//Check if entered Factor_Gain_Deadband is OK and not accept negative Factor_Gain_Deadband values
IF Factor_Gain_Deadband < 0 THEN
Factor_Gain_Deadband := 0;
END_IF;
//Check if within deadband :
// Use opposite value for deadband max, because : deadband max is positive offset from setpoint & a negative error corresponds with an actual value above setpoint
// Use opposite value for deadband min, because : deadband min is negative offset from setpoint & a positive error corresponds with an actual value below setpoint
IF Error < -Deadband_Delta_Min/ProcesValue_Gain AND Error > -Deadband_Delta_Max/ProcesValue_Gain THEN
Deadband_Active := 1;
ELSE
Deadband_Active := 0;
END_IF;
//When deadband is enabled & active : apply deadband gain to kp factor
IF Deadband_Enabled AND Deadband_Active THEN
Factor_Kp_Gain := Factor_Kp * Factor_Gain_Deadband;
ELSE
Factor_Kp_Gain := Factor_Kp;
END_IF;
//-------------------------------------------Classic Formula P-------------------------------------------------------
//Classic Formula P
IF Factor_Kp_Gain <= 0 THEN
P_actionClassic := 0; //Kp = 0 = exclude proportional influence
//Check if entered Kp is OK to avoid dividing by zero or accept negative Kp values
ELSE
P_actionClassic := Error / Factor_Kp_Gain;
END_IF;
//----------------------------------------------------PID------------------------------------------------------------
//Formula's P,I,D
IF Factor_Ki <= 0 THEN //Ki = 0 = exclude integral influence
//Check if entered Ki is OK to avoid dividing by zero or accept negative Ki values
Factor_Ki := 0;
I_action := 0;
ELSE
I_action := Error* ScanTime / (Factor_Kp_Gain * Factor_Ki * 1000000); //Deviding by 1000000 because ScanTime is µs
END_IF;
IF Factor_Kd <= 0 THEN //Kd = 0 = exclude differntial influence
//Check if entered Kd is OK to avoid dividing by zero or accept negative Kd values
//Added minus sign in D-action because opposite reaction,
//bigger should increase D-action and make PID action more stable
Factor_Kd := 0;
D_action := 0;
ELSE
D_action := -(Error - 2 * Error_1previous + Error_2previous) * 1000000 / (Factor_Kp_Gain * Factor_Kd * ScanTime); //Deviding by 1000000 because ScanTime is µs
END_IF;
IF Factor_Kp_Gain <= 0 THEN //Kp = 0 = exclude proportional influence
//Check if entered Kp is OK to avoid dividing by zero or accept negative Kp values
Factor_Kp := 0;
P_action := 0;
ELSE
P_action := (Error - Error_1previous ) / Factor_Kp_Gain;
END_IF;
//------------------------------------PID control---------------------------------------------------------
// Setting ControlValue Internal, not limited by controlvalue max and min
IF P_actionClassic + ControlValue_Min > ControlValue_Max AND P_ClassicActive THEN
// When reaching max control value with only P-action : I and D action are eliminated
ControlValue_Internal := ControlValue_Max;
ELSE
ControlValue_Internal := ControlValueInternal_previous + (P_action + I_action + D_action);
END_IF;
//To stay within the control band (avoid Windup)
IF ControlValue_Internal > ControlValue_Max THEN
ControlValue_Internal := ControlValue_Max;
ELSIF ControlValue_Internal < ControlValue_Min THEN
ControlValue_Internal := ControlValue_Min;
END_IF;
//PID regulation type : positive error resulting in increasing or decreasing output
IF Invert_ControlValue THEN
ControlValue := ControlValue_Max - (ControlValue_Internal - ControlValue_Min); //decreasing output
ELSE
ControlValue := ControlValue_Internal; //increasing output
END_IF;
//Save values for next PID cycle
ControlValueInternal_previous := ControlValue_Internal;
Error_2previous := Error_1previous;
Error_1previous := Error;
Сначала мы заметили, что один из экземпляров этого регулятора в своих входных параметрах имеет
Factor_Gain_Deadband = 0, и решили, что как только
Error оказывается в "мёртвой зоне", деление на ноль (реально - на очень малую величину
Factor_Kp_Gain := Factor_Kp * Factor_Gain_Deadband ) через интегральное
I_action := Error* ScanTime / (Factor_Kp_Gain * Factor_Ki * 1000000) и/или дифференциальное
D_action := -(Error - 2 * Error_1previous + Error_2previous) * 1000000 / (Factor_Kp_Gain * Factor_Kd * ScanTime) воздействие швыряет регулятор прочь из зоны со всей своей пролетарской ненавистью.
Но, продолжая размышлять над происходящим, я задался вопросом :
"- А с какого такого перепугу
Factor_Kp_Gain входит в формулы вычисления воздействий в качестве делителя, а не множителя?"
Код: Выделить всё
P_action := (Error - Error_1previous ) / Factor_Kp_Gain;
I_action := Error* ScanTime / (Factor_Kp_Gain * Factor_Ki * 1000000);
D_action := -(Error - 2 * Error_1previous + Error_2previous) * 1000000 / (Factor_Kp_Gain * Factor_Kd * ScanTime);
ControlValue_Internal := ControlValueInternal_previous + (P_action + I_action + D_action);
//конкретное значение ScanTime = 200000 микросекунд или 200 милисекунд//
Если у нас формула с зависимыми коэффициентами, а судя по всему это так, то
Factor_Kp_Gain := Factor_Kp * Factor_Gain_Deadband
должен быть множителем.
Или я не прав и чего-то не учёл?
Как вы думаете?
Дополнительные материалы выложил тут
https://disk.yandex.ru/d/EvwbZ0gqtGDZag