The issue is when the setpoint stabilize and I need change to another value the output control valve/ MV drop to 0% for a while … how to stabilize or do PID ulti function block have feature anti wind up ?
The issue is when the setpoint stabilize and I need change to another value the output control valve/ MV drop to 0% for a while … how to stabilize or do PID ulti function block have feature anti wind up ?
Since this is a pure Codesys question, have you tried searching in other places for suggestions?
Hopefully someone here can help, but just letting you know, Codesys is not exclusive to EPIC or RIO, so there might be solutions in other places on the web.
Is the PV tag that is plotted the same PV tag that is showing the value? I don’t see the PV going to 31.55 in the chart.
Edit: Nevermind - I see you are looking at a historical range.
Velocity algorithm will solve this. It is pretty much all I use now. Not sure what is available in Codesys though.
I read the documentation that opto22 has on the velocity form of the PID loop and wrote my own function block looking at that documentation. The velocity form has the advantage of not bumping the output when the SP changes or the tuning values change.
I am somewhat hesitant to share my code but here it is:
There are some of my own custom DUTs in the code to handle modes and if the function block is control by the user directly or by another function block. Note that my P values are opposite to what OPTO22 normally is for normal vs reverse acting. It is that way so that I can reuse some of my old PID and not have a chance of a loop I forgot about going nuclear.
{attribute 'symbol' := 'none'}
FUNCTION_BLOCK PID_VEL
VAR_INPUT
{attribute 'symbol' := 'readwrite'}
OUT_MAN: REAL;
{attribute 'symbol' := 'read'}
PV: REAL;
{attribute 'symbol' := 'readwrite'}
SP: REAL;
{attribute 'symbol' := 'readwrite'}
MODE: PID_MODES;
{attribute 'symbol' := 'read'}
CASMode: CAS_MODES;
SP_CAS: REAL;
MODE_CAS: PID_MODES;
OUT_CAS: REAL;
FEED_FWD:REAL;
END_VAR
VAR_INPUT CONSTANT
{attribute 'symbol' := 'readwrite'}
HI_LIMIT: REAL :=100;
{attribute 'symbol' := 'readwrite'}
LO_LIMIT: REAL :=0;
{attribute 'symbol' := 'readwrite'}
P: REAL :=0.8;
{attribute 'symbol' := 'readwrite'}
I: REAL := 45;
{attribute 'symbol' := 'readwrite'}
D: REAL := 0;
RAW_IN_HI : REAL := 100;
RAW_IN_LO : REAL := 0;
ENG_IN_LO : REAL := 4;
ENG_IN_HI : REAL:= 20;
END_VAR
VAR_OUTPUT
{attribute 'symbol' := 'none'}
OUT:REAL;
{attribute 'symbol' := 'none'}
Alog_OUT: REAL;
END_VAR
VAR
P_TERM:REAL;
I_TERM:REAL;
D_TERM:REAL;
SCALE:Scale_ALOG;
ERROR:REAL;
ERROR_1:REAL;
PV_1:REAL;
PV_SLOPE:REAL;
PV_SLOPE_1:REAL;
PV_FILT:REAL;
PV_FILT_1:REAL;
TX:TIME;
T_CURRENT:DWORD;
T_LAST:DWORD;
T_DELTA:REAL;
OUT_DELTA:REAL;
D_TERM_FILT:IIR_FILTER;
INIT:BOOL:=TRUE;
END_VAR
//Calculates using the PID velocity form C. This form does not have windup issues and does not kick due to SP changes
// http://documents.opto22.com/1641_OptoTutorial_SNAP_PAC_PID.pdf
//Handle the user input or from remote CAS sources
IF CASMode=CAS_MODES.CASCADE THEN
SP:=SP_CAS;
MODE:=MODE_CAS;
OUT_MAN:=OUT_CAS;
END_IF
//How Much time has passed between scans
TX := TIME();
//Time in microseconds
T_CURRENT:=TIME_TO_DWORD(TX)*1000;
IF INIT THEN
T_LAST:=T_CURRENT;
PV_1:=PV;
INIT:=FALSE;
END_IF
T_DELTA:=DWORD_TO_REAL(T_CURRENT-T_LAST)/1000000;
/// PID Calculations ///
//Note that a positive gain is reverse acting
//The following calc if PID in velocity C form
// IF PV is not NAN then do the calcs
IF PV = PV THEN
ERROR:=PV-SP;
//Calculate the output change contrib for each P I D term
P_TERM:=PV-PV_1;
// If I is 0 or below...Disable the I term
IF I > 0 THEN
I_TERM:=T_DELTA*ERROR/I;
ELSE
I_TERM:=0;
END_IF
//Apply Dirivitive filtering using an IIR filter, It is common to use 1/10 the the I time as the filter
D_TERM_FILT(UNFILTERED_RAW:=PV,FilterTime:=I/10,FILTERED=>PV_FILT);
// Calculate the diriv contrib only if time has passed. (Mostly a problem on first scan)
IF T_DELTA>0 THEN
PV_SLOPE:=(PV_FILT-PV_FILT_1)/T_DELTA;
D_TERM:=D*(PV_SLOPE-PV_SLOPE_1);
END_IF
PV_SLOPE_1:=PV_SLOPE;
PV_FILT_1:=PV_FILT;
OUT_DELTA:=-P*(P_TERM+I_TERM+D_TERM);
//PV_1 is the last PV and PV_2 is from the scan before that
PV_1:=PV;
END_IF
T_LAST:=T_CURRENT;
IF MODE=PID_MODES.AUTO THEN
OUT:=LIMIT(LO_LIMIT,(OUT+OUT_DELTA+FEED_FWD),HI_LIMIT);
OUT_MAN:=OUT;
ELSE
OUT:=LIMIT(LO_LIMIT,OUT_MAN,HI_LIMIT);
END_IF
SCALE(
RAW_IN_HI:= RAW_IN_HI,
RAW_IN_LO:= RAW_IN_LO,
ENG_IN_LO:= ENG_IN_LO,
ENG_IN_HI:= ENG_IN_HI,
RAW_IN:=OUT );
Alog_OUT:=Scale.ScaledVal;
Totally get your ‘code sharing’ feelings so wanted to say thank-you for stepping up.
Its exactly these sorts of posts that make this community so awesome.
P.S It reminds me of about 25+ years ago, long before SNAP, I coded the Opto PID loop math in flow chart (it was before the days of OptoControl) on a controller for a Mistic I/O unit that did not have any PID support (at the time).