How to do anti wind up on my codesys programm by using PID ulti function block

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;
3 Likes

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).