Files
Uniper_PLC/PLC/POUs/Sunspec/FB_PowerSupplySunspec.TcPOU
2025-04-28 17:14:43 +02:00

1330 lines
37 KiB
XML

<?xml version="1.0" encoding="utf-8"?>
<TcPlcObject Version="1.1.0.1" ProductVersion="3.1.4026.12">
<POU Name="FB_PowerSupplySunspec" Id="{a826dd09-442c-45c5-8ae3-9b71f293003c}" SpecialFunc="None">
<Declaration><![CDATA[FUNCTION_BLOCK FB_PowerSupplySunspec
VAR_INPUT
sInverterIPAddr : STRING;
xEnable : BOOL;
rPower : REAL;
//rReactivePower : REAL := 0.0;
xReset : BOOL;
rMaxBattPower : REAL := 40_000; // 24kW
END_VAR
VAR_OUTPUT
// Inverter active
xActive : BOOL;
// FB error
xError : BOOL;
// Heartbeat ok signal
xHeartbeatOk : BOOL := TRUE;
// Current inverter values
stCurrentValues : ST_SUNSPEC_CURRENT_VALUES;
END_VAR
VAR
// Current state
_iState : INT := 0;
// Internal power command
_rPowerInternal : REAL;
// FB for reading Modbus holding registers
_fbReadRegister : FB_MBReadRegs;
// FB for writing Modbus holding registers
_fbWriteRegister : FB_MBWriteRegs;
// Timer for checking if the inverter started in a reasonable amount of time
//_tonInverterStartup : TON;
// converter max power scaling factor
_iWMaxSF : INT;
// Unscaled converter max power
_uiWMax : UINT;
// Scaled converter max power
_rWMax : REAL;
// Unscaled limit for converter power
_iWMaxLimPct : INT;
// Scaling factor for reactive power percent value
_iVarPctSF : INT;
// Reread set power limit
//_iWMaxLimPctRead : INT;
//_rWMaxLimPctReadScaled : REAL;
// Scaling factor for power limiting
_iWMaxLimPctSF : INT;
// Unscaled maximum power from type label
//_iWRTGSF : INT;
// Scaling for maximum power from type label
//_rWRTGScaling : REAL;
// Current state of the inverters internal statemachine
_uiInverterState : UINT;
// Value to enable or dissable the Power limiting feature
_uiMaxLimEn : UINT;
// Value for commanding the target state of the inverter
_uiPCSSetOperation : UINT;
// Maximum reactive power
_iMaxPowerVar : INT := 0;
// Enable max reactive power percent controller
_iMaxVarPct : INt := 1;
// Holds the state number in which an error occured
_iErrorInState : INT;
// Time for polling for current dc values and check for inverter error
_timPollingDelay : TIME := T#500MS;
// Time for setting the current power
_timSetPowerDelay : TIME := T#250MS;
// Timer for polling of current values
_tonPollingTimer : TON;
// Timer for setting the inverter power
_tonSetPowerTimer : TON;
// Timer for incrementing heartbeat signal
_tonHearbeatIncTimer : TON := (PT := T#500ms);
// Current DC values (DCA, DCA_SF, DCV, DCV_SF, DCW, DCW_SF) in word array for efficient modbus reading
_awCurrentDCValues : ARRAY[0..5] OF WORD;
// Current AC values (W, W_SF, Hz, Hz_SF, VA, VA_SF, VAr, VAr_SF, PF, PF_SF) in word array for efficient modbus reading
_awCurrentACValues : ARRAY[0..21] OF WORD;
// Inverter error bits
_dwErrorBits : DWORD;
// Inverter reset errors command
_uiResetInverter : UINT := 1;
// PLC -> Inverter heartbeat
_uiPLCToInverterCounter : UINT;
// Inverter -> PLC heartbeat
_uiInverterToPLCCounter : UINT;
_uiInverterToPLCCounterOld : UINT;
// Flag to check if inverter has incremented the heartbeat counter
_xInverterHBCounterIncremented : BOOL := TRUE;
// Inverter alarm
_fbErrorInverterAlarm : FB_TcAlarm;
// Heartbeat timeout
_fbHeartbeatTimeout : TON;
_sName : STRING;
_uiSetPowerLimitErrorCounter : UINT;
_uiLastSetPowerLimitErrorCounter : UINT;
END_VAR
VAR CONSTANT
// Inverter statemachine status register
// Size 1, enum16 (Range = 0 .. 65534, Not implemented = 0xFFFF)
STATUS_REGISTER : WORD := 40108;
// Throttled power register
// Size 1, int16 (Range = -32767 .. 32767, Not implemented 0x8000)
W_MAX_LIM_PCT_REGISTER : WORD := 40187;
// Throttled power register scaling factor
// Size 1, sunssf (int16) (Range = -10 .. 10, Not implemented 0x8000)
W_MAX_LIM_PCT_SF_REGISTER : WORD := 40205;
// Control register to enable and dissable if the power throttleing should be active
// Size1, enum16 (Range = 0 .. 65534, Not implemented = 0xFFFF)
W_MAX_LIM_EN_REGISTER : WORD := 40191;
// Register to reset latched alarms in the inverter
// Size 1, uint16 (Range = 0 .. 65534, Not implemented = 0xFFFF)
PCS_ALARM_RESET_REGISTER : WORD := 40230;
// Control register to set the target state of the inverters state machine
// Size 1, enum16 (Range = 0 .. 65534, Not implemented = 0xFFFF)
PCS_SET_OPERATION_REGISTER : WORD := 40231;
// Maximum inverter output power
// Size 1, uint16 (Range = 0 .. 65534, Not implemented = 0xFFFF)
W_MAX_REGISTER : WORD := 40152;
// Maximum inverter output power scaling factor
// Size 1, sunssf (int16) (Range = -10 .. 10, Not implemented 0x8000)
W_MAX_SF_REGISTER : WORD := 40172;
// Maximum inverter output power from type label
// Size 1, uint16 (Range = 0 .. 65534, Not implemented = 0xFFFF)
//W_RTG_REGISTER : WORD := 40125;
// Maximum inverter output power from type label scaling factor
// Size 1, sunssf (int16) (Range = -10 .. 10, Not implemented 0x8000)
//W_RTG_SF_REGISTER : WORD := 40126;
// Start of register with the current dc values
// Size 4
DC_VALUES_START_REGISTER : WORD := 40097;
// Start of register with the current ac values
// SIZE 10
AC_VALUES_START_REGISTER : WORD := 40072;
// Power factor register in cosine of angle
// Size 1, int16 (Range = -32767 .. 32767, Not implemented 0x8000)
//OUT_PF_SET : WORD := 40192;
// Enable power factor controller
// Size 1, enum16 (Range = 0 .. 65534, Not implemented = 0xFFFF)
//OUT_PF_SET_ENA : WORD := 40196;
// Reactive power in percent of W_Max
// Size 1, int16 (Range = -32767 .. 32767, Not implemented 0x8000)
VAR_W_MAX_PCT : WORD := 40197;
// Enable percent limited var controller
// Size 1, enum16 (Range = 0 .. 65534, Not implemented = 0xFFFF)
VAR_PCT_ENA : WORD := 40204;
// Register for reactive power percent scaling factor
// Size 1, sunssf (int16) (Range = -10 .. 10, Not implemented 0x8000)
VAR_PCT_SF : WORD := 40207;
// Error bits register
// Size 2
EVT_1_REGISTER : WORD := 40110;
// PLC -> Inverter Heartbeat register
CONTROLLER_HB : WORD := 40229;
// Inverter -> PLC heartbeat register
PCS_HB : WORD := 40228;
END_VAR
]]></Declaration>
<Implementation>
<ST><![CDATA[// Clamp rPower to maximum allowed power
IF (rPower > rMaxBattPower) THEN
rPower := rMaxBattPower;
END_IF
IF (rPower < -rMaxBattPower) THEN
rPower := -rMaxBattPower;
END_IF
// Increment heartbeat counter
_tonHearbeatIncTimer(IN := TRUE);
IF _tonHearbeatIncTimer.Q THEN
_tonHearbeatIncTimer(IN := FALSE);
_uiPLCToInverterCounter := _uiPLCToInverterCounter + 1;
END_IF
// State machine
CASE _iState OF
0: // Off
IF _tonPollingTimer.Q THEN
_tonPollingTimer(IN := FALSE, PT := _timPollingDelay);
_iState := 1;
END_IF
// If enable and INTLK Ok
IF xEnable THEN
_iState := 10;
_rPowerInternal := 0.0;
_tonPollingTimer(IN := FALSE, PT := _timPollingDelay);
ELSE
_tonPollingTimer(IN := TRUE, PT := _timPollingDelay);
END_IF
1: // Read inverter status
_fbReadRegister(
sIPAddr:= sInverterIPAddr,
nTCPPort:= 502,
nUnitID:= 16#FF, // 16#FF for Modbus TCP
nQuantity:= 1,
nMBAddr:= STATUS_REGISTER,
cbLength:= SIZEOF(_uiInverterState),
pDestAddr:= ADR(_uiInverterState),
bExecute:= TRUE,
tTimeout:= T#5S,
bBusy=> ,
bError=> ,
nErrId=> ,
cbRead=> );
// Check if reading mudbus register is done
IF NOT _fbReadRegister.bBusy THEN
IF NOT _fbReadRegister.bError THEN
_iState := 2;
ELSE
_iErrorInState := _iState;
_iState := 1000;
END_IF
_fbReadRegister(bExecute := FALSE);
END_IF
2: // IF inverter is not in STANDYB(8) STATE, send command to shutdown inverter
IF (_uiInverterState = 8) OR (_uiInverterState = 1) OR (_uiInverterState = 7) THEN
_iState := 3;
ELSE
_uiPCSSetOperation := 3;
_iState := 200;
END_IF
3: // Read current DC values
_fbReadRegister(
sIPAddr:= sInverterIPAddr,
nTCPPort:= 502,
nUnitID:= 16#FF, // 16#FF for Modbus TCP
nQuantity:= 6,
nMBAddr:= DC_VALUES_START_REGISTER,
cbLength:= SIZEOF(_awCurrentDCValues),
pDestAddr:= ADR(_awCurrentDCValues),
bExecute:= TRUE,
tTimeout:= T#5S,
bBusy=> ,
bError=> ,
nErrId=> ,
cbRead=> );
// Check if reading mudbus register is done
IF NOT _fbReadRegister.bBusy THEN
// If there was no error and the converter has no error continue
IF NOT _fbReadRegister.bError THEN
_iState := 4;
stCurrentValues.rActDCCurrent := LREAL_TO_REAL(WORD_TO_INT(_awCurrentDCValues[0]) * EXPT(10,WORD_TO_INT(_awCurrentDCValues[1])));
stCurrentValues.rActDCVoltage := LREAL_TO_REAL(WORD_TO_UINT(_awCurrentDCValues[2]) * EXPT(10,WORD_TO_INT(_awCurrentDCValues[3])));
stCurrentValues.rActDCPower := LREAL_TO_REAL(WORD_TO_INT(_awCurrentDCValues[4]) * EXPT(10,WORD_TO_INT(_awCurrentDCValues[5])));
ELSE
_iErrorInState := _iState;
// Read error register
_iState := 1000;
END_IF
_fbReadRegister(bExecute := FALSE);
END_IF
4: // Read current ac values
_fbReadRegister(
sIPAddr:= sInverterIPAddr,
nTCPPort:= 502,
nUnitID:= 16#FF, // 16#FF for Modbus TCP
nQuantity:= 22,
nMBAddr:= AC_VALUES_START_REGISTER,
cbLength:= SIZEOF(_awCurrentACValues),
pDestAddr:= ADR(_awCurrentACValues),
bExecute:= TRUE,
tTimeout:= T#5S,
bBusy=> ,
bError=> ,
nErrId=> ,
cbRead=> );
// Check if reading mudbus register is done
IF NOT _fbReadRegister.bBusy THEN
// If there was no error and the converter has no error continue
IF NOT _fbReadRegister.bError THEN
// Go back to polling state
_iState := 5;
stCurrentValues.rActACCurrent := LREAL_TO_REAL(WORD_TO_INT(_awCurrentACValues[0]) * EXPT(10,WORD_TO_INT(_awCurrentACValues[4])));
stCurrentValues.rActtACPhaseACurrent := LREAL_TO_REAL(WORD_TO_INT(_awCurrentACValues[1]) * EXPT(10,WORD_TO_INT(_awCurrentACValues[4])));
stCurrentValues.rActtACPhaseBCurrent := LREAL_TO_REAL(WORD_TO_INT(_awCurrentACValues[2]) * EXPT(10,WORD_TO_INT(_awCurrentACValues[4])));
stCurrentValues.rActtACPhaseCCurrent := LREAL_TO_REAL(WORD_TO_INT(_awCurrentACValues[3]) * EXPT(10,WORD_TO_INT(_awCurrentACValues[4])));
stCurrentValues.rActACPower := LREAL_TO_REAL(WORD_TO_INT(_awCurrentACValues[12]) * EXPT(10,WORD_TO_INT(_awCurrentACValues[13])));
stCurrentValues.rActACFreq := LREAL_TO_REAL(WORD_TO_UINT(_awCurrentACValues[14]) * EXPT(10,WORD_TO_INT(_awCurrentACValues[15])));
stCurrentValues.rActApparentPower := LREAL_TO_REAL(WORD_TO_INT(_awCurrentACValues[16]) * EXPT(10,WORD_TO_INT(_awCurrentACValues[17])));
stCurrentValues.rActReactivePower := LREAL_TO_REAL(WORD_TO_INT(_awCurrentACValues[18]) * EXPT(10,WORD_TO_INT(_awCurrentACValues[19])));
stCurrentValues.rActPowerFactor := LREAL_TO_REAL(WORD_TO_INT(_awCurrentACValues[20]) * EXPT(10,WORD_TO_INT(_awCurrentACValues[21])));
ELSE
_iErrorInState := _iState;
// Read error register
_iState := 1000;
END_IF
_fbReadRegister(bExecute := FALSE);
END_IF
5: // Send heartbeat signal
_fbWriteRegister(
sIPAddr:= sInverterIPAddr,
nTCPPort:= 502,
nUnitID:= 16#FF, // 16#FF for Modbus TCP
nQuantity:= 1,
nMBAddr:= CONTROLLER_HB,
cbLength:= SIZEOF(_uiPLCToInverterCounter),
pSrcAddr:= ADR(_uiPLCToInverterCounter),
bExecute:= TRUE,
tTimeout:= T#5S,
bBusy=> ,
bError=> ,
nErrId=> );
// If writing modbus register is done
IF NOT _fbWriteRegister.bBusy THEN
// And there is no error, then continue
IF NOT _fbWriteRegister.bError THEN
_iState := 6;
ELSE
xError := TRUE;
_iErrorInState := _iState;
// Goto error state
_iState := 1000;
END_IF
_fbWriteRegister(bExecute := FALSE);
END_IF
6: // Check heartbeat signal
_fbReadRegister(
sIPAddr:= sInverterIPAddr,
nTCPPort:= 502,
nUnitID:= 16#FF, // 16#FF for Modbus TCP
nQuantity:= 1,
nMBAddr:= PCS_HB,
cbLength:= SIZEOF(_uiInverterToPLCCounter),
pDestAddr:= ADR(_uiInverterToPLCCounter),
bExecute:= TRUE,
tTimeout:= T#5S,
bBusy=> ,
bError=> ,
nErrId=> ,
cbRead=> );
IF NOT _fbReadRegister.bBusy THEN
IF (NOT _fbReadRegister.bError) THEN
// Check if counter has been incremented by the inverter
IF (_uiInverterToPLCCounter - _uiInverterToPLCCounterOld) > 0 THEN
_xInverterHBCounterIncremented := TRUE;
// Safe old value
_uiInverterToPLCCounterOld := _uiInverterToPLCCounter;
ELSE
_xInverterHBCounterIncremented := FALSE;
END_IF
_iState := 0;
ELSE
xError := TRUE;
_iErrorInState := _iState;
// Goto error state
_iState := 1000;
END_IF
_fbReadRegister(bExecute := FALSE);
END_IF
10: // Wait for inverter to be online and in state STANDBY(8)
_fbReadRegister(
sIPAddr:= sInverterIPAddr,
nTCPPort:= 502,
nUnitID:= 16#FF, // 16#FF for Modbus TCP
nQuantity:= 1,
nMBAddr:= STATUS_REGISTER,
cbLength:= SIZEOF(_uiInverterState),
pDestAddr:= ADR(_uiInverterState),
bExecute:= TRUE,
tTimeout:= T#5S,
bBusy=> ,
bError=> ,
nErrId=> ,
cbRead=> );
// Check if reading mudbus register is done
IF NOT _fbReadRegister.bBusy THEN
// If there was no error and the state is STANDBY(8) then continue
IF NOT _fbReadRegister.bError AND _uiInverterState = 8 THEN
_iState := 20;
END_IF
// If the inverter is not ready wait some time before polling again
IF NOT _fbReadRegister.bError AND _uiInverterState <> 8 THEN
_iState := 15;
END_IF
_fbReadRegister(bExecute := FALSE);
END_IF
// If not enable, go back to idle
IF NOT xEnable THEN
_fbReadRegister(bExecute := FALSE);
_iState := 0;
END_IF
15: // Delay polling inverter ready
_tonPollingTimer(IN := TRUE, PT := _timPollingDelay);
IF _tonPollingTimer.Q THEN
_tonPollingTimer(IN := FALSE);
_iState := 10;
END_IF
// If not enable, go back to idle
IF NOT xEnable THEN
_tonPollingTimer(IN := FALSE);
_iState := 0;
END_IF
20: // Read inverter max power scaling
_fbReadRegister(
sIPAddr:= sInverterIPAddr,
nTCPPort:= 502,
nUnitID:= 16#FF, // 16#FF for Modbus TCP
nQuantity:= 1,
nMBAddr:= W_MAX_SF_REGISTER,
cbLength:= SIZEOF(_iWMaxSF),
pDestAddr:= ADR(_iWMaxSF),
bExecute:= TRUE,
tTimeout:= T#5S,
bBusy=> ,
bError=> ,
nErrId=> ,
cbRead=> );
// Check if reading mudbus register is done
IF NOT _fbReadRegister.bBusy THEN
// If there was no error then continue
IF NOT _fbReadRegister.bError THEN
_iState := 25;
// Check for valid value
IF (_iWMaxSF < -10) OR (_iWMaxSF > 10) OR (_iWMaxSF = 16#8000) THEN
ADSLOGSTR(msgCtrlMask := ADSLOG_MSGTYPE_HINT, msgFmtStr := 'FBInverter into error state from: %s', strArg := TO_STRING(_iState));
// Goto error state
_iState := 1000;
END_IF
ELSE
xError := TRUE;
_iErrorInState := _iState;
// Goto error state
_iState := 1000;
END_IF
_fbReadRegister(bExecute := FALSE);
END_IF
25: // Read inverter Max power limit scaling
_fbReadRegister(
sIPAddr:= sInverterIPAddr,
nTCPPort:= 502,
nUnitID:= 16#FF, // 16#FF for Modbus TCP
nQuantity:= 1,
nMBAddr:= W_MAX_LIM_PCT_SF_REGISTER,
cbLength:= SIZEOF(_iWMaxLimPctSF),
pDestAddr:= ADR(_iWMaxLimPctSF),
bExecute:= TRUE,
tTimeout:= T#5S,
bBusy=> ,
bError=> ,
nErrId=> ,
cbRead=> );
// Check if reading mudbus register is done
IF NOT _fbReadRegister.bBusy THEN
// If there was no error then continue
IF NOT _fbReadRegister.bError THEN
_iState := 30;
// Check for valid value
IF (_iWMaxLimPctSF < -10) OR (_iWMaxLimPctSF > 10) OR (_iWMaxLimPctSF = 16#8000) THEN
// Goto error state
_iState := 1000;
END_IF
ELSE
xError := TRUE;
_iErrorInState := _iState;
// Goto error state
_iState := 1000;
END_IF
_fbReadRegister(bExecute := FALSE);
END_IF
26: // Read inverter scaling factor for reactive power
_fbReadRegister(
sIPAddr:= sInverterIPAddr,
nTCPPort:= 502,
nUnitID:= 16#FF, // 16#FF for Modbus TCP
nQuantity:= 1,
nMBAddr:= VAR_PCT_SF,
cbLength:= SIZEOF(_iVarPctSF),
pDestAddr:= ADR(_iVarPctSF),
bExecute:= TRUE,
tTimeout:= T#5S,
bBusy=> ,
bError=> ,
nErrId=> ,
cbRead=> );
// Check if reading mudbus register is done
IF NOT _fbReadRegister.bBusy THEN
// If there was no error then continue
IF NOT _fbReadRegister.bError THEN
_iState := 30;
// Check for valid value
IF (_iVarPctSF < -10) OR (_iVarPctSF > 10) OR (_iVarPctSF = 16#8000) THEN
// Goto error state
_iState := 1000;
END_IF
ELSE
xError := TRUE;
_iErrorInState := _iState;
// Goto error state
_iState := 1000;
END_IF
_fbReadRegister(bExecute := FALSE);
END_IF
30: // Read inverter max power
_fbReadRegister(
sIPAddr:= sInverterIPAddr,
nTCPPort:= 502,
nUnitID:= 16#FF, // 16#FF for Modbus TCP
nQuantity:= 1,
nMBAddr:= W_MAX_REGISTER,
cbLength:= SIZEOF(_uiWMax),
pDestAddr:= ADR(_uiWMax),
bExecute:= TRUE,
tTimeout:= T#5S,
bBusy=> ,
bError=> ,
nErrId=> ,
cbRead=> );
// Check if reading mudbus register is done
IF NOT _fbReadRegister.bBusy THEN
// If there was no error then continue
IF NOT _fbReadRegister.bError THEN
_iState := 40;
// Reading a register with scaling factor = value * 10^SF
_rWMax := LREAL_TO_REAL(_uiWMax * EXPT(10,_iWMaxSF));
// Calculate power to write to register
_iWMaxLimPct := LREAL_TO_INT((_rPowerInternal*100)/(_rWMax * EXPT(10,_iWMaxLimPctSF)));
ELSE
xError := TRUE;
_iErrorInState := _iState;
// Goto error state
_iState := 1000;
END_IF
_fbReadRegister(bExecute := FALSE);
END_IF
40: // Set power limit
_fbWriteRegister(
sIPAddr:= sInverterIPAddr,
nTCPPort:= 502,
nUnitID:= 16#FF, // 16#FF for Modbus TCP
nQuantity:= 1,
nMBAddr:= W_MAX_LIM_PCT_REGISTER,
cbLength:= SIZEOF(_iWMaxLimPct),
pSrcAddr:= ADR(_iWMaxLimPct),
bExecute:= TRUE,
tTimeout:= T#5S,
bBusy=> ,
bError=> ,
nErrId=> );
// If writing modbus register is done
IF NOT _fbWriteRegister.bBusy THEN
// And there is no error, then continue
IF NOT _fbWriteRegister.bError THEN
_iState := 50;
_uiMaxLimEn := 1;
_uiLastSetPowerLimitErrorCounter := _uiSetPowerLimitErrorCounter;
_uiSetPowerLimitErrorCounter := 0;
// Calculate reactive power setting
//_iMaxPowerVar := LREAL_TO_INT((rReactivePower*100)/(_iMaxPowerVar * EXPT(10,_iVarPctSF)));
ELSE
_uiSetPowerLimitErrorCounter := _uiSetPowerLimitErrorCounter + 1;
IF _uiSetPowerLimitErrorCounter > 5 THEN
_iErrorInState := _iState;
// Goto error state
_iState := 1000;
ELSE
_iState := 41;
END_IF
END_IF
_fbWriteRegister(bExecute := FALSE);
END_IF
41: // Retry send power limit delay
_tonPollingTimer(IN := TRUE, PT := _timPollingDelay);
IF _tonPollingTimer.Q THEN
_tonPollingTimer(IN := FALSE);
_iState := 40;
END_IF
42: // Set max reactive power in percent
_fbWriteRegister(
sIPAddr:= sInverterIPAddr,
nTCPPort:= 502,
nUnitID:= 16#FF, // 16#FF for Modbus TCP
nQuantity:= 1,
nMBAddr:= VAR_W_MAX_PCT,
cbLength:= SIZEOF(_iMaxPowerVar),
pSrcAddr:= ADR(_iMaxPowerVar),
bExecute:= TRUE,
tTimeout:= T#5S,
bBusy=> ,
bError=> ,
nErrId=> );
// If writing modbus register is done
IF NOT _fbWriteRegister.bBusy THEN
// And there is no error, then continue
IF NOT _fbWriteRegister.bError THEN
_iState := 42;
ELSE
xError := TRUE;
_iErrorInState := _iState;
// Goto error state
_iState := 1000;
END_IF
_fbWriteRegister(bExecute := FALSE);
END_IF
43: // Enable reactive power percent limiting
_fbWriteRegister(
sIPAddr:= sInverterIPAddr,
nTCPPort:= 502,
nUnitID:= 16#FF, // 16#FF for Modbus TCP
nQuantity:= 1,
nMBAddr:= VAR_PCT_ENA,
cbLength:= SIZEOF(_iMaxVarPct),
pSrcAddr:= ADR(_iMaxVarPct),
bExecute:= TRUE,
tTimeout:= T#5S,
bBusy=> ,
bError=> ,
nErrId=> );
// If writing modbus register is done
IF NOT _fbWriteRegister.bBusy THEN
// And there is no error, then continue
IF NOT _fbWriteRegister.bError THEN
_iState := 50;
ELSE
xError := TRUE;
_iErrorInState := _iState;
// Goto error state
_iState := 1000;
END_IF
_fbWriteRegister(bExecute := FALSE);
END_IF
50: // Enable Power limiting (THROTTLED)
_fbWriteRegister(
sIPAddr:= sInverterIPAddr,
nTCPPort:= 502,
nUnitID:= 16#FF, // 16#FF for Modbus TCP
nQuantity:= 1,
nMBAddr:= W_MAX_LIM_EN_REGISTER,
cbLength:= SIZEOF(_uiMaxLimEn),
pSrcAddr:= ADR(_uiMaxLimEn),
bExecute:= TRUE,
tTimeout:= T#5S,
bBusy=> ,
bError=> ,
nErrId=> );
// If writing modbus register is done
IF NOT _fbWriteRegister.bBusy THEN
// And there is no error, then continue
IF NOT _fbWriteRegister.bError THEN
_iState := 60; // 51
_uiPCSSetOperation := 1; // 4
xActive := TRUE;
ELSE
xError := TRUE;
_iErrorInState := _iState;
// Goto error state
_iState := 1000;
END_IF
_fbWriteRegister(bExecute := FALSE);
END_IF
51: // Go to started
_fbWriteRegister(
sIPAddr:= sInverterIPAddr,
nTCPPort:= 502,
nUnitID:= 16#FF, // 16#FF for Modbus TCP
nQuantity:= 1,
nMBAddr:= PCS_SET_OPERATION_REGISTER,
cbLength:= SIZEOF(_uiPCSSetOperation),
pSrcAddr:= ADR(_uiPCSSetOperation),
bExecute:= TRUE,
tTimeout:= T#5S,
bBusy=> ,
bError=> ,
nErrId=> );
// If writing modbus register is done
IF NOT _fbWriteRegister.bBusy THEN
// And there is no error, then continue
IF (NOT _fbWriteRegister.bError) THEN
//_uiPCSSetOperation := 1;
_iState := 60;
ELSE
_uiPCSSetOperation := 1;
_iErrorInState := _iState;
// Goto error state
_iState := 1000;
END_IF
_fbWriteRegister(bExecute := FALSE);
END_IF
60: // Switch to THROTTLED mode
_fbWriteRegister(
sIPAddr:= sInverterIPAddr,
nTCPPort:= 502,
nUnitID:= 16#FF, // 16#FF for Modbus TCP
nQuantity:= 1,
nMBAddr:= PCS_SET_OPERATION_REGISTER,
cbLength:= SIZEOF(_uiPCSSetOperation),
pSrcAddr:= ADR(_uiPCSSetOperation),
bExecute:= TRUE,
tTimeout:= T#5S,
bBusy=> ,
bError=> ,
nErrId=> );
// If writing modbus register is done
IF NOT _fbWriteRegister.bBusy THEN
// And there is no error, then continue
IF NOT _fbWriteRegister.bError THEN
_iState := 65;
ELSE
_iErrorInState := _iState;
// Goto error state
_iState := 1000;
END_IF
_fbWriteRegister(bExecute := FALSE);
END_IF
65: // Wait for polling timer
_tonPollingTimer(IN := TRUE, PT := _timPollingDelay);
_tonSetPowerTimer(IN := TRUE, PT := _timSetPowerDelay);
IF _tonPollingTimer.Q THEN
_tonPollingTimer(IN := FALSE);
_iState := 70;
ELSIF (ABS(rPower - _rPowerInternal) > 0.1) AND _tonSetPowerTimer.Q AND xEnable THEN
_tonSetPowerTimer(IN := FALSE);
_rPowerInternal := rPower;
// Calculate power to write to register
_iWMaxLimPct := LREAL_TO_INT((_rPowerInternal*100)/(_rWMax * EXPT(10,_iWMaxLimPctSF)));
// If power has been changed, goto set power limit mode
_iState := 40;
END_IF
// check if inverter should shut down
IF (NOT xEnable) THEN
_uiPCSSetOperation := 3;
_rPowerInternal := 0.0;
_iWMaxLimPct := 0;
// Goto shutdown sequence
_iState := 200;
END_IF
70: // Enabled, check for error
_fbReadRegister(
sIPAddr:= sInverterIPAddr,
nTCPPort:= 502,
nUnitID:= 16#FF, // 16#FF for Modbus TCP
nQuantity:= 1,
nMBAddr:= STATUS_REGISTER,
cbLength:= SIZEOF(_uiInverterState),
pDestAddr:= ADR(_uiInverterState),
bExecute:= TRUE,
tTimeout:= T#5S,
bBusy=> ,
bError=> ,
nErrId=> ,
cbRead=> );
// Check if reading mudbus register is done
IF NOT _fbReadRegister.bBusy THEN
// If there was no error and the converter has no error continue
IF (NOT _fbReadRegister.bError) AND (_uiInverterState <> 7) THEN
_iState := 80;
IF (_uiInverterState = 4) OR (_uiInverterState = 5) THEN
xActive := TRUE;
ELSE
xActive := FALSE;
END_IF
ELSE
xError := TRUE;
xActive := FALSE;
_uiPCSSetOperation := 3;
_iErrorInState := _iState;
ADSLOGDINT(msgCtrlMask:= ADSLOG_MSGTYPE_ERROR, msgFmtStr:= 'Fehler im state: %s', dintArg:= INT_TO_DINT(_iErrorInState));
// Read error register
_iState := 200;
END_IF
_fbReadRegister(bExecute := FALSE);
END_IF
80: // Read current DC values
_fbReadRegister(
sIPAddr:= sInverterIPAddr,
nTCPPort:= 502,
nUnitID:= 16#FF, // 16#FF for Modbus TCP
nQuantity:= 6,
nMBAddr:= DC_VALUES_START_REGISTER,
cbLength:= SIZEOF(_awCurrentDCValues),
pDestAddr:= ADR(_awCurrentDCValues),
bExecute:= TRUE,
tTimeout:= T#5S,
bBusy=> ,
bError=> ,
nErrId=> ,
cbRead=> );
// Check if reading mudbus register is done
IF NOT _fbReadRegister.bBusy THEN
// If there was no error and the converter has no error continue
IF NOT _fbReadRegister.bError THEN
_iState := 85;
stCurrentValues.rActDCCurrent := LREAL_TO_REAL(WORD_TO_INT(_awCurrentDCValues[0]) * EXPT(10,WORD_TO_INT(_awCurrentDCValues[1])));
stCurrentValues.rActDCVoltage := LREAL_TO_REAL(WORD_TO_INT(_awCurrentDCValues[2]) * EXPT(10,WORD_TO_INT(_awCurrentDCValues[3])));
stCurrentValues.rActDCPower := LREAL_TO_REAL(WORD_TO_INT(_awCurrentDCValues[4]) * EXPT(10,WORD_TO_INT(_awCurrentDCValues[5])));
ELSE
_iErrorInState := _iState;
// Read error register
_iState := 1000;
END_IF
_fbReadRegister(bExecute := FALSE);
END_IF
85: // Read current ac values
_fbReadRegister(
sIPAddr:= sInverterIPAddr,
nTCPPort:= 502,
nUnitID:= 16#FF, // 16#FF for Modbus TCP
nQuantity:= 22,
nMBAddr:= AC_VALUES_START_REGISTER,
cbLength:= SIZEOF(_awCurrentACValues),
pDestAddr:= ADR(_awCurrentACValues),
bExecute:= TRUE,
tTimeout:= T#5S,
bBusy=> ,
bError=> ,
nErrId=> ,
cbRead=> );
// Check if reading mudbus register is done
IF NOT _fbReadRegister.bBusy THEN
// If there was no error and the converter has no error continue
IF NOT _fbReadRegister.bError THEN
// Go back to polling state
_iState := 90;
stCurrentValues.rActACCurrent := LREAL_TO_REAL(WORD_TO_INT(_awCurrentACValues[0]) * EXPT(10,WORD_TO_INT(_awCurrentACValues[4])));
stCurrentValues.rActtACPhaseACurrent := LREAL_TO_REAL(WORD_TO_INT(_awCurrentACValues[1]) * EXPT(10,WORD_TO_INT(_awCurrentACValues[4])));
stCurrentValues.rActtACPhaseBCurrent := LREAL_TO_REAL(WORD_TO_INT(_awCurrentACValues[2]) * EXPT(10,WORD_TO_INT(_awCurrentACValues[4])));
stCurrentValues.rActtACPhaseCCurrent := LREAL_TO_REAL(WORD_TO_INT(_awCurrentACValues[3]) * EXPT(10,WORD_TO_INT(_awCurrentACValues[4])));
stCurrentValues.rActACPower := LREAL_TO_REAL(WORD_TO_INT(_awCurrentACValues[12]) * EXPT(10,WORD_TO_INT(_awCurrentACValues[13])));
stCurrentValues.rActACFreq := LREAL_TO_REAL(WORD_TO_UINT(_awCurrentACValues[14]) * EXPT(10,WORD_TO_INT(_awCurrentACValues[15])));
stCurrentValues.rActApparentPower := LREAL_TO_REAL(WORD_TO_INT(_awCurrentACValues[16]) * EXPT(10,WORD_TO_INT(_awCurrentACValues[17])));
stCurrentValues.rActReactivePower := LREAL_TO_REAL(WORD_TO_INT(_awCurrentACValues[18]) * EXPT(10,WORD_TO_INT(_awCurrentACValues[19])));
stCurrentValues.rActPowerFactor := LREAL_TO_REAL(WORD_TO_INT(_awCurrentACValues[20]) * EXPT(10,WORD_TO_INT(_awCurrentACValues[21])));
ELSE
_iErrorInState := _iState;
// Read error register
_iState := 1000;
END_IF
_fbReadRegister(bExecute := FALSE);
END_IF
90: // Read current inverter status
_fbReadRegister(
sIPAddr:= sInverterIPAddr,
nTCPPort:= 502,
nUnitID:= 16#FF, // 16#FF for Modbus TCP
nQuantity:= 1,
nMBAddr:= STATUS_REGISTER,
cbLength:= SIZEOF(_uiInverterState),
pDestAddr:= ADR(_uiInverterState),
bExecute:= TRUE,
tTimeout:= T#5S,
bBusy=> ,
bError=> ,
nErrId=> ,
cbRead=> );
// Check if reading mudbus register is done
IF NOT _fbReadRegister.bBusy THEN
IF NOT _fbReadRegister.bError THEN
_iState := 91;
stCurrentValues.uiStatus := _uiInverterState;
ELSE
_iErrorInState := _iState;
// Read error register
_iState := 1000;
END_IF
_fbReadRegister(bExecute := FALSE);
END_IF
91: // Send heartbeat signal
_uiPLCToInverterCounter := _uiPLCToInverterCounter + 1;
_fbWriteRegister(
sIPAddr:= sInverterIPAddr,
nTCPPort:= 502,
nUnitID:= 16#FF, // 16#FF for Modbus TCP
nQuantity:= 1,
nMBAddr:= CONTROLLER_HB,
cbLength:= SIZEOF(_uiPLCToInverterCounter),
pSrcAddr:= ADR(_uiPLCToInverterCounter),
bExecute:= TRUE,
tTimeout:= T#5S,
bBusy=> ,
bError=> ,
nErrId=> );
// If writing modbus register is done
IF NOT _fbWriteRegister.bBusy THEN
// And there is no error, then continue
IF NOT _fbWriteRegister.bError THEN
_iState := 92;
ELSE
_iErrorInState := _iState;
xError := TRUE;
// Goto error state
_iState := 1000;
END_IF
_fbWriteRegister(bExecute := FALSE);
END_IF
92: // Check heartbeat signal
_fbReadRegister(
sIPAddr:= sInverterIPAddr,
nTCPPort:= 502,
nUnitID:= 16#FF, // 16#FF for Modbus TCP
nQuantity:= 1,
nMBAddr:= PCS_HB,
cbLength:= SIZEOF(_uiInverterToPLCCounter),
pDestAddr:= ADR(_uiInverterToPLCCounter),
bExecute:= TRUE,
tTimeout:= T#5S,
bBusy=> ,
bError=> ,
nErrId=> ,
cbRead=> );
IF NOT _fbReadRegister.bBusy THEN
IF (NOT _fbReadRegister.bError) THEN
// Check if counter has been incremented by the inverter
IF (_uiInverterToPLCCounter - _uiInverterToPLCCounterOld) > 0 THEN
_xInverterHBCounterIncremented := TRUE;
// Safe old value
_uiInverterToPLCCounterOld := _uiInverterToPLCCounter;
ELSE
_xInverterHBCounterIncremented := FALSE;
END_IF
// _iState := 65;
_iState := 93;
ELSE
xError := TRUE;
_iErrorInState := _iState;
// Goto error state
_iState := 1000;
END_IF
_fbReadRegister(bExecute := FALSE);
END_IF
93: // Send current power demand
_fbWriteRegister(
sIPAddr:= sInverterIPAddr,
nTCPPort:= 502,
nUnitID:= 16#FF, // 16#FF for Modbus TCP
nQuantity:= 1,
nMBAddr:= W_MAX_LIM_PCT_REGISTER,
cbLength:= SIZEOF(_iWMaxLimPct),
pSrcAddr:= ADR(_iWMaxLimPct),
bExecute:= TRUE,
tTimeout:= T#5S,
bBusy=> ,
bError=> ,
nErrId=> );
// If writing modbus register is done
IF NOT _fbWriteRegister.bBusy THEN
// And there is no error, then continue
IF NOT _fbWriteRegister.bError THEN
_iState := 65;
_uiMaxLimEn := 1;
_uiLastSetPowerLimitErrorCounter := _uiSetPowerLimitErrorCounter;
_uiSetPowerLimitErrorCounter := 0;
// Calculate reactive power setting
//_iMaxPowerVar := LREAL_TO_INT((rReactivePower*100)/(_iMaxPowerVar * EXPT(10,_iVarPctSF)));
ELSE
_uiSetPowerLimitErrorCounter := _uiSetPowerLimitErrorCounter + 1;
IF _uiSetPowerLimitErrorCounter > 5 THEN
_iErrorInState := _iState;
// Goto error state
_iState := 1000;
ELSE
_iState := 41;
END_IF
END_IF
_fbWriteRegister(bExecute := FALSE);
END_IF
200: // Shutdown send zero power command
_fbWriteRegister(
sIPAddr:= sInverterIPAddr,
nTCPPort:= 502,
nUnitID:= 16#FF, // 16#FF for Modbus TCP
nQuantity:= 1,
nMBAddr:= W_MAX_LIM_PCT_REGISTER,
cbLength:= SIZEOF(_iWMaxLimPct),
pSrcAddr:= ADR(_iWMaxLimPct),
bExecute:= TRUE,
tTimeout:= T#5S,
bBusy=> ,
bError=> ,
nErrId=> );
// If writing modbus register is done
IF NOT _fbWriteRegister.bBusy THEN
// And there is no error, then continue
IF NOT _fbWriteRegister.bError THEN
_iState := 201;
_uiPCSSetOperation := 3;
ELSE
_iErrorInState := _iState;
// Goto error state
_iState := 1000;
END_IF
_fbWriteRegister(bExecute := FALSE);
END_IF
201: // Shutdown sequence
_fbWriteRegister(
sIPAddr:= sInverterIPAddr,
nTCPPort:= 502,
nUnitID:= 16#FF, // 16#FF for Modbus TCP
nQuantity:= 1,
nMBAddr:= PCS_SET_OPERATION_REGISTER,
cbLength:= SIZEOF(_uiPCSSetOperation),
pSrcAddr:= ADR(_uiPCSSetOperation),
bExecute:= TRUE,
tTimeout:= T#5S,
bBusy=> ,
bError=> ,
nErrId=> );
// If writing modbus register is done
IF NOT _fbWriteRegister.bBusy THEN
// And there is no error, then continue
IF NOT _fbWriteRegister.bError THEN
_iState := 210;
ELSE
_iErrorInState := _iState;
// Goto error state
_iState := 1000;
END_IF
_fbWriteRegister(bExecute := FALSE);
END_IF
210: // Wait for poll timer to
_tonPollingTimer(IN := TRUE, PT := _timPollingDelay);
IF _tonPollingTimer.Q THEN
_tonPollingTimer(IN := FALSE);
_iState := 220;
END_IF
220: // Poll and wait for standby state
_iErrorInState := _iState;
_fbReadRegister(
sIPAddr:= sInverterIPAddr,
nTCPPort:= 502,
nUnitID:= 16#FF, // 16#FF for Modbus TCP
nQuantity:= 1,
nMBAddr:= STATUS_REGISTER,
cbLength:= SIZEOF(_uiInverterState),
pDestAddr:= ADR(_uiInverterState),
bExecute:= TRUE,
tTimeout:= T#5S,
bBusy=> ,
bError=> ,
nErrId=> ,
cbRead=> );
// Check if reading mudbus register is done
IF NOT _fbReadRegister.bBusy THEN
// If there was no error and the converter has no error continue
IF NOT _fbReadRegister.bError THEN
_iState := 0;
xActive := FALSE;
//xCloseDCRelais := FALSE;
END_IF
_fbReadRegister(bExecute := FALSE);
END_IF
990: // Read error register
_fbReadRegister(
sIPAddr:= sInverterIPAddr,
nTCPPort:= 502,
nUnitID:= 16#FF, // 16#FF for Modbus TCP
nQuantity:= 2,
nMBAddr:= EVT_1_REGISTER,
cbLength:= SIZEOF(_dwErrorBits),
pDestAddr:= ADR(_dwErrorBits),
bExecute:= TRUE,
tTimeout:= T#5S,
bBusy=> ,
bError=> ,
nErrId=> ,
cbRead=> );
// Check if reading mudbus register is done
IF NOT _fbReadRegister.bBusy THEN
// If there was no error and the converter has no error continue
IF NOT _fbReadRegister.bError THEN
_iErrorInState := _iState;
_iState := 1000;
END_IF
_fbReadRegister(bExecute := FALSE);
END_IF
1000: // Write error state to log
ADSLOGDINT(msgCtrlMask:= ADSLOG_MSGTYPE_ERROR, msgFmtStr:= 'Fehler im state: %s', dintArg:= INT_TO_DINT(_iErrorInState));
_iState := 1001;
1001: // Error state, wait for reset
IF xReset AND (NOT xEnable) THEN
_iState := 1010;
END_IF
1010: // Try to clear all latched events
_fbWriteRegister(
sIPAddr:= sInverterIPAddr,
nTCPPort:= 502,
nUnitID:= 16#FF, // 16#FF for Modbus TCP
nQuantity:= 1,
nMBAddr:= PCS_ALARM_RESET_REGISTER,
cbLength:= SIZEOF(_uiResetInverter),
pSrcAddr:= ADR(_uiResetInverter),
bExecute:= TRUE,
tTimeout:= T#5S,
bBusy=> ,
bError=> ,
nErrId=> );
IF NOT _fbWriteRegister.bBusy THEN
_iState := 0;
xError := FALSE;
_fbWriteRegister(bExecute := FALSE);
END_IF
END_CASE
// ===============================
// Heartbeat check
// ===============================
_fbHeartbeatTimeout(IN := (NOT _xInverterHBCounterIncremented), PT := T#5S);
// Reset heartbeat ok signal
IF xReset AND (NOT _fbHeartbeatTimeout.Q) THEN
xHeartbeatOk := TRUE;
END_IF
// Check for heartbeat
IF _fbHeartbeatTimeout.Q THEN
xHeartbeatOk := FALSE;
END_IF
// ===============================
// Inverter alarm handling
// ===============================
IF xError AND (NOT _fbErrorInverterAlarm.bRaised) THEN
_fbErrorInverterAlarm.Raise(0);
END_IF
IF (NOT xError) AND _fbErrorInverterAlarm.bRaised THEN
_fbErrorInverterAlarm.Clear(0, FALSE);
END_IF
IF (_fbErrorInverterAlarm.eConfirmationState = TcEventConfirmationState.WaitForConfirmation) AND xReset THEN
_fbErrorInverterAlarm.Confirm(0);
END_IF]]></ST>
</Implementation>
<Method Name="FB_init" Id="{a80728a8-68c7-4f6a-87fc-246cb88104d4}">
<Declaration><![CDATA[METHOD FB_init : BOOL
VAR_INPUT
bInitRetains : BOOL; // if TRUE, the retain variables are initialized (warm start / cold start)
bInCopyCode : BOOL; // if TRUE, the instance afterwards gets moved into the copy code (online change)
sName : STRING;
END_VAR
]]></Declaration>
<Implementation>
<ST><![CDATA[_sName := sName;
// Create inverter main alarm
_fbErrorInverterAlarm.CreateEx(stEventEntry := TC_EVENTS.Inverter.InverterError, bWithConfirmation := TRUE, 0);
_fbErrorInverterAlarm.ipArguments.Clear().AddString(_sName);]]></ST>
</Implementation>
</Method>
<Property Name="Name" Id="{ef8c6e8f-7c1b-4781-b201-87f759acb289}">
<Declaration><![CDATA[PROPERTY Name : string]]></Declaration>
<Get Name="Get" Id="{bc17161e-727d-4abd-a845-a7eacc08f995}">
<Declaration><![CDATA[VAR
END_VAR
]]></Declaration>
<Implementation>
<ST><![CDATA[Name := _sName;]]></ST>
</Implementation>
</Get>
<Set Name="Set" Id="{7fdecf53-efe5-43de-bc46-5f24fb6a7ffb}">
<Declaration><![CDATA[VAR
END_VAR
]]></Declaration>
<Implementation>
<ST><![CDATA[_sName := Name;
_fbErrorInverterAlarm.ipArguments.Clear().AddString(_sName);]]></ST>
</Implementation>
</Set>
</Property>
</POU>
</TcPlcObject>