Added multi string handling and balancing

This commit is contained in:
Matthias Heisig
2025-02-13 21:26:50 +01:00
parent f9df0a5180
commit 2ded890dd4
14 changed files with 554 additions and 302 deletions

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<TcPlcObject Version="1.1.0.1" ProductVersion="3.1.4024.12">
<TcPlcObject Version="1.1.0.1" ProductVersion="3.1.4026.8">
<DUT Name="E_BMS_CONTROL_MODE" Id="{ab000a04-c252-420d-ac1e-2bf611fa911a}">
<Declaration><![CDATA[{attribute 'qualified_only'}
{attribute 'strict'}
@@ -10,7 +10,9 @@ TYPE E_BMS_CONTROL_MODE :
SAFETY_CHECK := 3,
CAPACITY_TEST := 4,
MANUAL := 5,
BALANCING := 6
BALANCING := 6,
CYCLING := 7,
PRECHARGE := 8
);
END_TYPE
]]></Declaration>

View File

@@ -2,6 +2,12 @@
<TcPlcObject Version="1.1.0.1" ProductVersion="3.1.4026.8">
<GVL Name="GVL_CONFIG" Id="{0773bf51-0237-454d-a970-cfd896054edb}">
<Declaration><![CDATA[{attribute 'qualified_only'}
VAR_GLOBAL CONSTANT
// ===========================
// Number of active strings
// ===========================
uiNumberOfStrings : UINT := 2;
END_VAR
VAR_GLOBAL PERSISTENT
// ===========================
// Unit hardware config
@@ -242,8 +248,8 @@ VAR_GLOBAL PERSISTENT
rPumpNegolytOnPower : REAL := 65.0;
// Pump discharge segment without inverter power (%)
rPumpPosolytDisChrgPower : REAL := 35.0;
rPumpNegolytDisChrgPower : REAL := 35.0;
rPumpPosolytDisChrgPower : REAL := 45.0;
rPumpNegolytDisChrgPower : REAL := 45.0;
// Unit voltage pumps shutoff threshold (Volt)
rPumpshutoffThreshold : REAL := 15.0;
@@ -252,7 +258,7 @@ VAR_GLOBAL PERSISTENT
rMinimumUnitVoltage : REAL := 55.0;
// Maximum unit voltage for fully charged (Volt)
rMaximumUnitVoltage : REAL := 79.0;
rMaximumUnitVoltage : REAL := 79.5;
// Delta value to minimum unit voltage for shutdown discharge (Volt)
rDeltaUnitVoltageShutdownDischarge : REAL := 5.0;
@@ -270,11 +276,11 @@ VAR_GLOBAL PERSISTENT
// Maximum allowed charging power (Watt) per String
// 24.000 W -> 2.000 W per Unit
diMaxStringChargingPower : DINT := -24_000;
diMaxStringChargingPower : DINT := -50_000;
// Maximum allowed discharging power (Watt) per String
// 24.000 W -> 2.000 W per Unit
diMaxStringDischargePower : DINT := 24_000;
diMaxStringDischargePower : DINT := 50_000;
// Inverter ip address for string 1
sInverterIpString1 : STRING := '192.168.42.10';

View File

@@ -111,6 +111,9 @@
<Compile Include="POUs\FB_Module.TcPOU">
<SubType>Code</SubType>
</Compile>
<Compile Include="POUs\FB_PowerMeter.TcPOU">
<SubType>Code</SubType>
</Compile>
<Compile Include="POUs\FB_Safety.TcPOU">
<SubType>Code</SubType>
</Compile>
@@ -212,8 +215,8 @@
<ProjectExtensions>
<PlcProjectOptions>
<XmlArchive>
<Data>
<o xml:space="preserve" t="OptionKey">
<Data>
<o xml:space="preserve" t="OptionKey">
<v n="Name">"&lt;ProjectRoot&gt;"</v>
<d n="SubKeys" t="Hashtable" ckt="String" cvt="OptionKey">
<v>{192FAD59-8248-4824-A8DE-9177C94C195A}</v>
@@ -2595,16 +2598,16 @@
</d>
<d n="Values" t="Hashtable" />
</o>
</Data>
<TypeList>
<Type n="Boolean">System.Boolean</Type>
<Type n="Hashtable">System.Collections.Hashtable</Type>
<Type n="Int32">System.Int32</Type>
<Type n="OptionKey">{54dd0eac-a6d8-46f2-8c27-2f43c7e49861}</Type>
<Type n="String">System.String</Type>
<Type n="UInt32">System.UInt32</Type>
</TypeList>
</XmlArchive>
</Data>
<TypeList>
<Type n="Boolean">System.Boolean</Type>
<Type n="Hashtable">System.Collections.Hashtable</Type>
<Type n="Int32">System.Int32</Type>
<Type n="OptionKey">{54dd0eac-a6d8-46f2-8c27-2f43c7e49861}</Type>
<Type n="String">System.String</Type>
<Type n="UInt32">System.UInt32</Type>
</TypeList>
</XmlArchive>
</PlcProjectOptions>
</ProjectExtensions>
</Project>

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<TcPlcObject Version="1.1.0.1" ProductVersion="3.1.4026.8">
<POU Name="FB_PowerMeter" Id="{9d4159f4-2d3f-4522-a770-c038a26d9d77}" SpecialFunc="None">
<Declaration><![CDATA[FUNCTION_BLOCK FB_PowerMeter
VAR_INPUT
xResetEnergyCounter : BOOL;
END_VAR
VAR_OUTPUT
END_VAR
VAR
_fbReadRegs : FB_MBReadInputRegs;
_fbWriteRegs : FB_MBWriteRegs;
_fbREResetEnergyCounter : R_TRIG;
_xResetEnergyCounter : BOOL;
_iState : INT := 0;
END_VAR
]]></Declaration>
<Implementation>
<ST><![CDATA[_fbREResetEnergyCounter(CLK := xResetEnergyCounter);
IF _fbREResetEnergyCounter.Q THEN
_xResetEnergyCounter := TRUE;
END_IF
CASE _iState OF
0: // Idle
IF _xResetEnergyCounter THEN
_xResetEnergyCounter := FALSE;
_iState := 10;
END_IF
10: // Reset energy counters
_fbWriteRegs(
sIPAddr:= '192.168.42.80',
nTCPPort:= 502,
nUnitID:= 16#FF ,
nQuantity:= ,
nMBAddr:= 16#0600,
cbLength:= ,
pSrcAddr:= ,
bExecute:= ,
tTimeout:= ,
bBusy=> ,
bError=> ,
nErrId=> );
END_CASE]]></ST>
</Implementation>
</POU>
</TcPlcObject>

View File

@@ -140,6 +140,9 @@ VAR
// Isolatio alarm
_fbIsolationAlarm : FB_TcAlarm;
// Safety interlock reset timeout
_fbSafetyIntlkTimeoutAlarm : FB_TcAlarm;
// Shutdown discharge stopped messages
_fbSDDCLevel : FB_TcMessage;
_fbSDUnitThreshold : FB_TcMessage;
@@ -598,6 +601,9 @@ CASE _iState OF
xCloseDCCB := TRUE;
xError := TRUE;
xReady := FALSE;
IF (NOT _fbSafetyIntlkTimeoutAlarm.bRaised) THEN
_fbSafetyIntlkTimeoutAlarm.Raise(0);
END_IF
_iState := 1000;
END_IF
@@ -688,6 +694,14 @@ CASE _iState OF
_rPowerInverterInternal := rPowerInverter;
END_IF
IF _rPowerInverterInternal > 0.0 THEN
eStatus := E_COMPONENT_STATUS.DISCHARGING;
ELSIF _rPowerInverterInternal < 0.0 THEN
eStatus := E_COMPONENT_STATUS.CHARGING;
ELSE
eStatus := E_COMPONENT_STATUS.ON;
END_IF
// Shutdown
IF (NOT xEnable) THEN
_xEnable := FALSE;
@@ -815,7 +829,7 @@ END_CASE
// Copy inverter data to SCADA interface
stHMIInterface.stInverterData := stInverterData;
IF _xAllModulesReady AND _xBalanceOk AND (_iState = 30) THEN
IF _xAllModulesReady AND _xBalanceOk AND ((_iState = 30) OR (_iState = 29)) THEN
xReady := TRUE;
ELSE
xReady := FALSE;
@@ -824,7 +838,15 @@ END_IF
// Reset inverter startup timeout alarm
IF _fbInverterStartupTimeoutAlarm.bRaised AND xConfirmAlarms THEN
_fbInverterStartupTimeoutAlarm.Clear(0, TRUE);
END_IF]]></ST>
END_IF
// Reset safetyinterlock timeout alarm
IF _fbSafetyIntlkTimeoutAlarm.bRaised AND xConfirmAlarms THEN
_fbInverterStartupTimeoutAlarm.Clear(0, TRUE);
END_IF
// Copy status to hmi interface
stHMIInterface.eStatus := eStatus;]]></ST>
</Implementation>
<Method Name="FB_init" Id="{9e8494eb-1b40-4be9-91c8-810ecbdf7f0c}">
<Declaration><![CDATA[METHOD FB_init : BOOL
@@ -867,6 +889,10 @@ _fbSCSConnLost.CreateEx(stEventEntry := TC_EVENTS.General.CommError, bWithConfir
_sTemp := CONCAT(_sName, ' SCS');
_fbSCSConnLost.ipArguments.Clear().AddString(_sTemp);
// Safety interlock reset timeout alarm
_fbSafetyIntlkTimeoutAlarm.CreateEx(stEventEntry := TC_EVENTS.BMSEvents.SafetyIntlkTimeout, bWithConfirmation := TRUE, 0);
_fbSafetyIntlkTimeoutAlarm.ipArguments.Clear().AddString(_sName);
// Shutdown discharge messages
_fbSDDCLevel.CreateEx(TC_EVENTS.BMSEvents.SDDCVoltage, 0);
_fbSDDCLevel.ipArguments.Clear().AddString(_sName);
@@ -906,7 +932,8 @@ _fbInverter.Name := _sName;
// Create alarm messages
_fbModulesOutOfBalanceAlarm.ipArguments.Clear().AddString(_sName);
_fbSafetyInterlocksNotOkAlarm.ipArguments.Clear().AddString(_sName);]]></ST>
_fbSafetyInterlocksNotOkAlarm.ipArguments.Clear().AddString(_sName);
_fbSafetyIntlkTimeoutAlarm.ipArguments.Clear().AddString(_sName);]]></ST>
</Implementation>
</Set>
</Property>

View File

@@ -237,11 +237,14 @@ END_VAR
_fbNegolytPumpInlet.Name := CONCAT(_sName, ' - Negolyt segment inlet');
stHMIInterface.stNS21.sName := 'Negolyt segment inlet';
_fbPressurePosolytSegmentInlet.Name := CONCAT(_sName, ' - Posolyt Segment Inlet');
_fbPressurePosolytTankInlet.Name := CONCAT(_sName, ' - Posolyt Tank inlet');
_fbPressurePosolytSegmentInlet.Name := CONCAT(_sName, ' - Pressure Posolyt Segment Inlet');
_fbPressurePosolytTankInlet.Name := CONCAT(_sName, ' - Pressure Posolyt Tank inlet');
_fbPressureNegolytSegmentInlet.Name := CONCAT(_sName, ' - Negolyt Segment Inlet');
_fbPressureNegolytTankInlet.Name := CONCAT(_sName, ' - Negolyt Tank Inlet');
_fbPressureNegolytSegmentInlet.Name := CONCAT(_sName, ' - Pressure Negolyt Segment Inlet');
_fbPressureNegolytTankInlet.Name := CONCAT(_sName, ' - Pressure Negolyt Tank Inlet');
_fbTempSensorPosolyt.Name := CONCAT(_sName, ' - Temperature Posolyt Tank Inlet');
_fbTempSensorNegolyt.Name := CONCAT(_sName, ' - Temperature Negolyt Tank Inlet');
_fbVoltageSegment.Name := CONCAT(_sName, ' - Voltage');

View File

@@ -134,6 +134,21 @@ VAR
_fbStringReadyTimeout : TON;
// Sum of voltage of all active strings
_rStringsSumVoltage : REAL;
_arPowerString : ARRAY[0..(GVL_CONFIG.uiNumberOfStrings-1)] OF REAL;
_ui : UINT := 0;
_xStringsReady : BOOL;
_xStringsErrorActive : BOOL;
_xStringsInSchutdownDischargeMode : BOOL;
_xStringsShutdownDischargeAllowed : BOOL;
_xStringsAllInAutomaticMode : BOOL;
_xStringsOff : BOOL;
_rMaxCurrentInverterDCVoltage : REAL;
_rMinCurrentInverterDCVoltage : REAL;
_fbModbusRead : FB_MBReadRegs;
_wLength : WORD := 49;
xDebugTest : BOOL;
@@ -286,15 +301,15 @@ _tonStartupDelay(IN := TRUE);
// Call string 1
_afbStrings[0](
stStringModuleVoltageConfig := GVL_CONFIG.stString1VoltageConfig,
xEnable := _xEnableString, //AND GVL_CONFIG.xDummy,
xStartBalancing := _xStartBalancing, // AND GVL_CONFIG.xDummy,
xEnable := _xEnableString,
xStartBalancing := _xStartBalancing,
sInverterIP := GVL_CONFIG.sInverterIpString1,
rPowerInverter := _rPowerInverter,
rPowerInverter := _arPowerString[0],
xInSafetyCheckMode := _xInSafetyCheckMode,
stHMIInterface:= GVL_SCADA.stHMIInterface[0],
xEmergencyStopOk:= _xEmergencyStopOk,
xReleaseErrors:= _xReleaseErrors AND _tonStartupDelay.Q AND _xEtherCatString1Ok,
xReleaseLimitErrors:= _xReleaseLimitsErrors AND _tonStartupDelay.Q,
xReleaseLimitErrors:= _xReleaseLimitsErrors AND _tonStartupDelay.Q AND _xEtherCatString1Ok,
xReleaseManualMode := _xReleaseManualMode,
xConfirmAlarms:= _xConfirmAlarms,
xAllToManualMode := _xAllComponentsToManualMode,
@@ -310,11 +325,11 @@ _afbStrings[1](
xEnable := _xEnableString,
xStartBalancing := _xStartBalancing,
sInverterIP := GVL_CONFIG.sInverterIpString2,
rPowerInverter := _rPowerInverter,
rPowerInverter := _arPowerString[1],
xInSafetyCheckMode := _xInSafetyCheckMode,
stHMIInterface:= GVL_SCADA.stHMIInterface[1],
xEmergencyStopOk:= _xEmergencyStopOk,
xReleaseErrors:= _xReleaseErrors AND _tonStartupDelay.Q AND _xEtherCatString2Ok AND FALSE,
xReleaseErrors:= _xReleaseErrors AND _tonStartupDelay.Q AND _xEtherCatString2Ok,
xReleaseLimitErrors:= _xReleaseLimitsErrors AND _tonStartupDelay.Q AND _xEtherCatString2Ok,
xReleaseManualMode := _xReleaseManualMode,
xConfirmAlarms:= _xConfirmAlarms,
@@ -325,12 +340,85 @@ IF _afbStrings[1].xError THEN
_xErrorActive := TRUE;
END_IF
// ===============================
// Get global string status information
// ===============================
_xStringsReady := TRUE;
_xStringsErrorActive := FALSE;
_xStringsInSchutdownDischargeMode := FALSE;
_xStringsShutdownDischargeAllowed := TRUE;
_xStringsAllInAutomaticMode := TRUE;
_xStringsOff := TRUE;
_rMaxCurrentInverterDCVoltage := 0.0;
_rMinCurrentInverterDCVoltage := 10_000;
_rHighestSegmentVoltage := 0.0;
_rSmallestSegmentVoltage := 1_000.0;
FOR _ui := 0 TO (GVL_CONFIG.uiNumberOfStrings-1) DO
// Check ready state
IF (NOT _afbStrings[_ui].xReady) THEN
_xStringsReady := FALSE;
END_IF
// Check error state
IF _afbStrings[_ui].xError THEN
_xStringsErrorActive := TRUE;
END_IF
// Check for shutdown discharge mode
IF _afbStrings[_ui].xInShutdownDischargeMode THEN
_xStringsInSchutdownDischargeMode := TRUE;
END_IF
// Check for shutdown discharge allowed
IF (NOT _afbStrings[_ui].xShutdownDischargeAllowed) THEN
_xStringsShutdownDischargeAllowed := FALSE;
END_IF
// Check for all in automatic mode
IF (NOT _afbStrings[_ui].xAllModulesInAutoMode) THEN
_xStringsAllInAutomaticMode := FALSE;
END_IF
// Check for all strings off
IF (NOT _afbStrings[_ui].xOff) THEN
_xStringsOff := FALSE;
END_IF
// Check for max dc voltage
IF _rMaxCurrentInverterDCVoltage < _afbStrings[_ui].stInverterData.rActDCVoltage THEN
_rMaxCurrentInverterDCVoltage := _afbStrings[_ui].stInverterData.rActDCVoltage;
END_IF
// Check for min DC voltage
IF _rMinCurrentInverterDCVoltage > _afbStrings[_ui].stInverterData.rActDCVoltage THEN
_rMinCurrentInverterDCVoltage := _afbStrings[_ui].stInverterData.rActDCVoltage;
END_IF
// Calculate highest segment voltage
IF _rSmallestSegmentVoltage > _afbStrings[_ui].rSmallestSegmentVoltage THEN
_rSmallestSegmentVoltage := _afbStrings[_ui].rSmallestSegmentVoltage;
END_IF
// Calculate lowest segment voltage
IF _rHighestSegmentVoltage < _afbStrings[_ui].rHighestSegmentVoltage THEN
_rHighestSegmentVoltage := _afbStrings[_ui].rHighestSegmentVoltage;
END_IF
END_FOR
// ===============================
// Calculate sum power for string balancing
// ===============================
_rStringsSumVoltage := _afbStrings[0].rCurrentVoltage + _afbStrings[1].rCurrentVoltage;
// ===============================
// Hardware reset button part 2
// ===============================
_xShowErrorOnButton := _xErrorActive;
// HMI Feedback
(*
GVL_SCADA.stHMIInterface[0].rVoltage := _afbStrings[0].rCurrentVoltage;
IF _afbStrings[0].eStatus = E_COMPONENT_STATUS.ON THEN
IF _iState = 30 AND _rPowerInverter > 0 THEN
@@ -343,6 +431,7 @@ IF _afbStrings[0].eStatus = E_COMPONENT_STATUS.ON THEN
ELSE
GVL_SCADA.stHMIInterface[0].eStatus :=_afbStrings[0].eStatus;
END_IF
*)
// ===============================
// Read modbus request count
@@ -381,25 +470,25 @@ _fbModbusRead(
// Copy data to modbus registers
// ===============================
// Modbus current inverter values
GVL_MODBUS.stModbusEMSComm.stModbusReg11.diCurrentActivePower := REAL_TO_DINT(_afbStrings[_uiDebugCurrentString].stInverterData.rActACPower);
GVL_MODBUS.stModbusEMSComm.stModbusReg11.diCurrentReactivePower := REAL_TO_DINT(_afbStrings[_uiDebugCurrentString].stInverterData.rActReactivePower);
GVL_MODBUS.stModbusEMSComm.stModbusReg11.diTotalACCurrentPhase1 := REAL_TO_DINT(_afbStrings[_uiDebugCurrentString].stInverterData.rActtACPhaseACurrent);
GVL_MODBUS.stModbusEMSComm.stModbusReg11.diTotalACCurrentPhase2 := REAL_TO_DINT(_afbStrings[_uiDebugCurrentString].stInverterData.rActtACPhaseBCurrent);
GVL_MODBUS.stModbusEMSComm.stModbusReg11.diTotalACCurrentPhase3 := REAL_TO_DINT(_afbStrings[_uiDebugCurrentString].stInverterData.rActtACPhaseCCurrent);
GVL_MODBUS.stModbusEMSComm.stModbusReg11.diCurrentActivePower := 0;
GVL_MODBUS.stModbusEMSComm.stModbusReg11.diCurrentReactivePower := 0;
GVL_MODBUS.stModbusEMSComm.stModbusReg11.diTotalACCurrentPhase1 := 0;
GVL_MODBUS.stModbusEMSComm.stModbusReg11.diTotalACCurrentPhase2 := 0;
GVL_MODBUS.stModbusEMSComm.stModbusReg11.diTotalACCurrentPhase3 := 0;
FOR _ui := 0 TO (GVL_CONFIG.uiNumberOfStrings-1) DO
GVL_MODBUS.stModbusEMSComm.stModbusReg11.diCurrentActivePower := GVL_MODBUS.stModbusEMSComm.stModbusReg11.diCurrentActivePower + REAL_TO_DINT(_afbStrings[_ui].stInverterData.rActACPower);
GVL_MODBUS.stModbusEMSComm.stModbusReg11.diCurrentReactivePower := GVL_MODBUS.stModbusEMSComm.stModbusReg11.diCurrentReactivePower + REAL_TO_DINT(_afbStrings[_ui].stInverterData.rActReactivePower);
GVL_MODBUS.stModbusEMSComm.stModbusReg11.diTotalACCurrentPhase1 := GVL_MODBUS.stModbusEMSComm.stModbusReg11.diTotalACCurrentPhase1 + REAL_TO_DINT(_afbStrings[_ui].stInverterData.rActtACPhaseACurrent);
GVL_MODBUS.stModbusEMSComm.stModbusReg11.diTotalACCurrentPhase2 := GVL_MODBUS.stModbusEMSComm.stModbusReg11.diTotalACCurrentPhase2 + REAL_TO_DINT(_afbStrings[_ui].stInverterData.rActtACPhaseBCurrent);
GVL_MODBUS.stModbusEMSComm.stModbusReg11.diTotalACCurrentPhase3 := GVL_MODBUS.stModbusEMSComm.stModbusReg11.diTotalACCurrentPhase3 + REAL_TO_DINT(_afbStrings[_ui].stInverterData.rActtACPhaseCCurrent);
END_FOR
// Set Modbus mirror values
GVL_MODBUS.stModbusEMSComm.stModbusReg11.diSetpointActivePowerMirror := GVL_MODBUS.stModbusEMSComm.stModbusReg12.diSetpointActivePower;
GVL_MODBUS.stModbusEMSComm.stModbusReg11.rSetpointCosPhiMirror := GVL_MODBUS.stModbusEMSComm.stModbusReg12.rSetpointCosPhi;
// ===============================
// Calculate highest and lowest
// segment voltage
// ===============================
_rSmallestSegmentVoltage := _afbStrings[_uiDebugCurrentString].rSmallestSegmentVoltage;
_rHighestSegmentVoltage := _afbStrings[_uiDebugCurrentString].rHighestSegmentVoltage;
// ===============================
// State machine
// ===============================
@@ -464,6 +553,16 @@ CASE _eBMSControlMode OF
_eBMSControlMode := GVL_SCADA.eRequestedControlMode;
END_IF
SM_BALANCING();
E_BMS_CONTROL_MODE.CYCLING:
_xAllComponentsToManualMode := FALSE;
_xInSafetyCheckMode := FALSE;
_xReleaseManualMode := FALSE;
_rAutoPowerRequest := DINT_TO_REAL(GVL_MODBUS.stModbusEMSComm.stModbusReg12.diSetpointActivePower);
IF (GVL_SCADA.eRequestedControlMode <> _eBMSControlMode) AND (GVL_SCADA.xCanChangeControlMode) THEN
_eBMSControlMode := GVL_SCADA.eRequestedControlMode;
END_IF
SM_AUTO();
END_CASE
GVL_SCADA.xCanChangeControlMode := _xCanChangeMode;
@@ -514,13 +613,13 @@ END_IF]]></ST>
10: // Wait for string to be ready
_fbStringReadyTimeout(IN := TRUE, PT := GVL_CONFIG.timStringReadyTimeout);
IF _afbStrings[_uiDebugCurrentString].xReady AND (NOT _afbStrings[_uiDebugCurrentString].xError) THEN
IF _xStringsReady AND (NOT _xStringsErrorActive) THEN
_fbStringReadyTimeout(IN := FALSE);
_rPowerInverter := 0.0;
_iState := 30;
END_IF
IF _afbStrings[_uiDebugCurrentString].xError OR _fbStringReadyTimeout.Q THEN
IF _xStringsErrorActive OR _fbStringReadyTimeout.Q THEN
_fbStringReadyTimeout(IN := FALSE);
_xEnableString := FALSE;
GVL_SCADA.stAutomaticModeHMI.diSetpointAutomatic := 0;
@@ -528,6 +627,7 @@ END_IF]]></ST>
_iState := 45;
END_IF
IF (ABS(_rAutoPowerRequest) < DINT_TO_REAL(GVL_CONFIG.diMinimumAbsPowerForEnable)) THEN
_fbStringReadyTimeout(IN := FALSE);
_xEnableString := FALSE;
@@ -574,51 +674,65 @@ END_IF]]></ST>
END_IF
// Shutdown triggered by battery fully charged
IF GVL_MODBUS.stModbusEMSComm.stModbusReg11.eChargeStatus = E_CHARGE_STATUS.CHARGING AND ((_afbStrings[_uiDebugCurrentString].stInverterData.rActDCVoltage >= GVL_CONFIG.rStringFullyChargedVoltage) OR _rHighestSegmentVoltage >= GVL_CONFIG.rMaximumUnitVoltage) THEN
_tonBeginShutdown(In := FALSE);
IF GVL_MODBUS.stModbusEMSComm.stModbusReg11.eChargeStatus = E_CHARGE_STATUS.CHARGING AND ((_rMaxCurrentInverterDCVoltage >= GVL_CONFIG.rStringFullyChargedVoltage) OR _rHighestSegmentVoltage >= GVL_CONFIG.rMaximumUnitVoltage) THEN
// Send message
_fbBatteryFullMessage.Send(0);
IF (_eBMSControlMode = E_BMS_CONTROL_MODE.CYCLING) THEN
GVL_MODBUS.stModbusEMSComm.stModbusReg12.diSetpointActivePower := GVL_MODBUS.stModbusEMSComm.stModbusReg12.diSetpointActivePower * -1;
// Change of charge discharge should be handled in next cycle by sasme state
// GVL_MODBUS.stModbusEMSComm.stModbusReg11.eChargeStatus := E_CHARGE_STATUS.DISCHARGING;
ELSE
_tonBeginShutdown(In := FALSE);
// Send message
_fbBatteryFullMessage.Send(0);
// Set inverter to zero power
_rPowerInverter := 0.0;
// Set local remote to zero power
GVL_SCADA.stAutomaticModeHMI.diSetpointAutomatic := 0;
// Start string shutdown
_xEnableString := FALSE;
// Change battery status
GVL_MODBUS.stModbusEMSComm.stModbusReg11.eChargeStatus := E_CHARGE_STATUS.FULL;
GVL_MODBUS.stModbusEMSComm.stModbusReg10.uiActiveParallelMembers := 0;
_iState := 35;
END_IF
// Set inverter to zero power
_rPowerInverter := 0.0;
// Set local remote to zero power
GVL_SCADA.stAutomaticModeHMI.diSetpointAutomatic := 0;
// Start string shutdown
_xEnableString := FALSE;
// Change battery status
GVL_MODBUS.stModbusEMSComm.stModbusReg11.eChargeStatus := E_CHARGE_STATUS.FULL;
GVL_MODBUS.stModbusEMSComm.stModbusReg10.uiActiveParallelMembers := 0;
_iState := 35;
END_IF
// Shutdown triggered by battery empty
IF GVL_MODBUS.stModbusEMSComm.stModbusReg11.eChargeStatus = E_CHARGE_STATUS.DISCHARGING AND ((_afbStrings[_uiDebugCurrentString].stInverterData.rActDCVoltage <= GVL_CONFIG.rStringEmptyVoltage) OR _rSmallestSegmentVoltage <= GVL_CONFIG.rMinimumUnitVoltage) THEN
_tonBeginShutdown(In := FALSE);
IF GVL_MODBUS.stModbusEMSComm.stModbusReg11.eChargeStatus = E_CHARGE_STATUS.DISCHARGING AND ((_rMinCurrentInverterDCVoltage <= GVL_CONFIG.rStringEmptyVoltage) OR _rSmallestSegmentVoltage <= GVL_CONFIG.rMinimumUnitVoltage) THEN
IF (_eBMSControlMode = E_BMS_CONTROL_MODE.CYCLING) THEN
GVL_MODBUS.stModbusEMSComm.stModbusReg12.diSetpointActivePower := GVL_MODBUS.stModbusEMSComm.stModbusReg12.diSetpointActivePower * -1;
// Change of charge discharge should be handled in next cycle by sasme state
// GVL_MODBUS.stModbusEMSComm.stModbusReg11.eChargeStatus := E_CHARGE_STATUS.CHARGING;
ELSE
_tonBeginShutdown(In := FALSE);
// Send Message
_fbBatteryEmptyMessage.Send(0);
// Set local remote to zero power
GVL_SCADA.stAutomaticModeHMI.diSetpointAutomatic := 0;
// Set inverter to zero power
_rPowerInverter := 0.0;
// Start string shutdown
_xEnableString := FALSE;
// Change battery status
GVL_MODBUS.stModbusEMSComm.stModbusReg11.eChargeStatus := E_CHARGE_STATUS.EMPTY;
GVL_MODBUS.stModbusEMSComm.stModbusReg10.uiActiveParallelMembers := 0;
_iState := 35;
// Send Message
_fbBatteryEmptyMessage.Send(0);
// Set local remote to zero power
GVL_SCADA.stAutomaticModeHMI.diSetpointAutomatic := 0;
// Set inverter to zero power
_rPowerInverter := 0.0;
// Start string shutdown
_xEnableString := FALSE;
// Change battery status
GVL_MODBUS.stModbusEMSComm.stModbusReg11.eChargeStatus := E_CHARGE_STATUS.EMPTY;
GVL_MODBUS.stModbusEMSComm.stModbusReg10.uiActiveParallelMembers := 0;
_iState := 35;
END_IF
END_IF
// Check for errors
IF _afbStrings[_uiDebugCurrentString].xError THEN
IF _xStringsErrorActive THEN
_xEnableString := FALSE;
_tonBeginShutdown(In := FALSE);
GVL_SCADA.stAutomaticModeHMI.diSetpointAutomatic := 0;
@@ -627,7 +741,7 @@ END_IF]]></ST>
END_IF
35: // Wait for string to be in shutdown discharge mode
IF _afbStrings[_uiDebugCurrentString].xInShutdownDischargeMode THEN
IF _xStringsInSchutdownDischargeMode THEN
// Check if we are allowed to discharge during shutdown with inverter
IF GVL_CONFIG.xShutdownDischargeWithInverter THEN
_iState := 40;
@@ -641,12 +755,12 @@ END_IF]]></ST>
END_IF
// Check for errors
IF _afbStrings[_uiDebugCurrentString].xError THEN
IF _xStringsErrorActive THEN
_iState := 1000;
END_IF
40: // Wait for inverter discharge done
IF _afbStrings[_uiDebugCurrentString].xShutdownDischargeAllowed THEN
IF _xStringsShutdownDischargeAllowed THEN
_rPowerInverter := GVL_CONFIG.rAbsShutdownDischargePower;
ELSE
_rPowerInverter := 0.0;
@@ -657,29 +771,30 @@ END_IF]]></ST>
END_IF
// Check for errors
IF _afbStrings[_uiDebugCurrentString].xError THEN
IF _xStringsErrorActive THEN
_iState := 1000;
END_IF
// Restart if possible
IF (ABS(_rAutoPowerRequest) > DINT_TO_REAL(GVL_CONFIG.diMinimumAbsPowerForEnable)) AND (NOT _afbStrings[_uiDebugCurrentString].xError) AND _afbStrings[_uiDebugCurrentString].xAllModulesInAutoMode THEN
IF (ABS(_rAutoPowerRequest) > DINT_TO_REAL(GVL_CONFIG.diMinimumAbsPowerForEnable)) AND (NOT _xStringsErrorActive) AND _xStringsAllInAutomaticMode THEN
_iState := 5;
END_IF
45: // Wait for shutdown of string to be done
IF (NOT _afbStrings[_uiDebugCurrentString].xInShutdownDischargeMode) AND _afbStrings[_uiDebugCurrentString].xOff THEN
IF (NOT _xStringsInSchutdownDischargeMode) AND _xStringsOff THEN
GVL_MODBUS.stModbusEMSComm.stModbusReg11.eBatteryStatus := E_BATTERY_STATUS.OFF;
GVL_MODBUS.stModbusEMSComm.stModbusReg11.eChargeStatus := E_CHARGE_STATUS.UNDEFINED;
_iState := 0;
END_IF
// Restart if possible
IF (ABS(_rAutoPowerRequest) > DINT_TO_REAL(GVL_CONFIG.diMinimumAbsPowerForEnable)) AND (NOT _afbStrings[_uiDebugCurrentString].xError) AND _afbStrings[_uiDebugCurrentString].xAllModulesInAutoMode THEN
IF (ABS(_rAutoPowerRequest) > DINT_TO_REAL(GVL_CONFIG.diMinimumAbsPowerForEnable)) AND (NOT _xStringsErrorActive) AND _xStringsAllInAutomaticMode THEN
_xCanChangeMode := FALSE;
_iState := 5;
END_IF
// Check for errors
IF _afbStrings[_uiDebugCurrentString].xError THEN
IF _xStringsErrorActive THEN
_iState := 1000;
END_IF
@@ -691,7 +806,7 @@ END_IF]]></ST>
_iState := 1010;
1010: // Wait for reset from error state
IF (_rAutoPowerRequest = 0.0) AND (NOT _afbStrings[_uiDebugCurrentString].xError) AND _xConfirmAlarms THEN
IF (_rAutoPowerRequest = 0.0) AND (NOT _xStringsErrorActive) AND _xConfirmAlarms THEN
// Reset modbus error register
GVL_MODBUS.stModbusEMSComm.stModbusReg11.lwErrorBitmap := 0;
@@ -703,7 +818,28 @@ END_IF]]></ST>
_xCanChangeMode := TRUE;
END_IF
END_CASE]]></ST>
END_CASE
// Calculate string power balancing
IF _rStringsSumVoltage <> 0 THEN
FOR _ui := 0 TO (GVL_CONFIG.uiNumberOfStrings-1) DO
// Discharging
IF _rPowerInverter > 0 THEN
_arPowerString[_ui] := _rPowerInverter * (_afbStrings[_ui].rCurrentVoltage / _rStringsSumVoltage);
// Charging
ELSIF _rPowerInverter < 0 THEN
_arPowerString[_ui] := _rPowerInverter * (1.0 - (_afbStrings[_ui].rCurrentVoltage / _rStringsSumVoltage));
// Nothing
ELSE
_arPowerString[_ui] := 0.0;
END_IF
END_FOR
ELSE
FOR _ui := 0 TO (GVL_CONFIG.uiNumberOfStrings-1) DO
_arPowerString[_ui] := 0.0;
END_FOR
END_IF
]]></ST>
</Implementation>
</Action>
<Action Name="SM_BALANCING" Id="{f1f90032-de29-468d-899c-50bfb54e48e0}">
@@ -760,6 +896,11 @@ END_CASE]]></ST>
<ST><![CDATA[_xCanChangeMode := TRUE;]]></ST>
</Implementation>
</Action>
<Action Name="SM_PRECHARGE" Id="{b84aedc8-0039-40a2-8abe-a166eca7bebc}">
<Implementation>
<ST><![CDATA[]]></ST>
</Implementation>
</Action>
<Action Name="SM_SAFETY_CHECK" Id="{6d8e5993-cf32-4980-9ea3-c1fbfa4b8601}">
<Implementation>
<ST><![CDATA[// Start on start button pressed
@@ -796,6 +937,7 @@ CASE _iStateSafetyCheck OF
IF NOT _xStartSafetyCheck THEN
_xEnableString := FALSE;
_iStateSafetyCheck := 0;
_xCanChangeMode := TRUE;
END_IF
// Check for errors

View File

@@ -797,8 +797,8 @@ CASE _iState OF
IF _tonPollingTimer.Q THEN
_tonPollingTimer(IN := FALSE);
_iState := 70;
ELSIF ABS(rPower - _rOldPower) > 0.1 THEN
_tonPollingTimer(IN := FALSE);
ELSIF (ABS(rPower - _rOldPower) > 0.1) THEN
//_tonPollingTimer(IN := FALSE);
// If power has ben changed, goto set power limit mode
_iState := 40;
// Calculate power to write to register

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<safetyApplicationLanguageDiagram Crc="518324452" dslVersion="1.4.0.0" Id="b1b6b4f5-2a37-4725-94da-4b4e0499132e" absoluteBounds="0, 0, 28.875, 23.125" name="TwinSAFE">
<safetyApplicationLanguageDiagram Crc="71004029" dslVersion="1.5.0.0" Id="b1b6b4f5-2a37-4725-94da-4b4e0499132e" absoluteBounds="0, 0, 28.875, 23.125" name="TwinSAFE">
<safetyApplicationMoniker name="/" />
<nestedChildShapes>
<networkSwimLane Id="cdd3abf9-920f-4dee-bac6-a51b58482f55" absoluteBounds="0, 0, 28.875, 4.25">

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<MultiSetting Crc="1554816737" Version="1.2">
<MultiSetting Crc="227871081" Version="1.2">
<ProjectData>
<Id>1</Id>
<TargetSystem>
@@ -214,6 +214,9 @@
<SubType>EL2912</SubType>
<ObjectId>50462856</ObjectId>
<SafeAddress>28</SafeAddress>
<Customizing>
<Group Id="87605930-f4c2-4c12-816d-f0103cb2103d" Value="3" />
</Customizing>
</TargetSystem>
<SafetyAliasDevice>
<SdsId>36</SdsId>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<TargetSystemConfig Crc="960810402" Version="1.5">
<TargetSystemConfig Crc="2950660737" Version="1.5">
<TargetSystemType>HSafetyPLC</TargetSystemType>
<TargetSystemSubType>EL2912</TargetSystemSubType>
<IsExternalDevice>false</IsExternalDevice>
@@ -19,4 +19,7 @@
<TakeOverSafetyAliasDeviceNamesInProcessImage>true</TakeOverSafetyAliasDeviceNamesInProcessImage>
<TakeOverStandardAliasDeviceNamesInProcessImage>true</TakeOverStandardAliasDeviceNamesInProcessImage>
<BackupRestore Needed="0" Activated="false" RestoreUserAdministration="false" />
<Customizing>
<Group Id="87605930-f4c2-4c12-816d-f0103cb2103d" Value="3" />
</Customizing>
</TargetSystemConfig>

View File

@@ -269,7 +269,7 @@
</System>
<Plc>
<Project GUID="{9AE64910-5EB2-4866-93FD-EFE059C38C36}" Name="PLC" PrjFilePath="PLC\PLC.plcproj" TmcFilePath="PLC\PLC.tmc" ReloadTmc="true" AmsPort="851" FileArchiveSettings="#x000e" CopyTmcToTarget="true" CopyTpyToTarget="false" SymbolicMapping="true">
<Instance Id="#x08502000" TcSmClass="TComPlcObjDef" KeepUnrestoredLinks="2" TmcHash="{64C8C7AD-2D5C-17E5-766C-EEDB24034E45}" TmcPath="PLC\PLC.tmc">
<Instance Id="#x08502000" TcSmClass="TComPlcObjDef" KeepUnrestoredLinks="2" TmcHash="{8EE1E4A8-795F-0390-3BF7-7B289B0C6EAD}" TmcPath="PLC\PLC.tmc">
<Name>PLC Instance</Name>
<CLSID ClassFactory="TcPlc30">{08500001-0000-0000-F000-000000000064}</CLSID>
<Vars VarGrpType="2" AreaNo="1">
@@ -18018,7 +18018,7 @@ Bit1: Value smaller than Limit2]]></Comment>
<Box Id="78" BoxType="9099" BoxFlags="#x00000020">
<Name>=STRNG01++BATMOD02-38K3 (EL5122)</Name>
<ImageId>1009</ImageId>
<EtherCAT SlaveType="2" PdiType="#x0405" MboxDataLinkLayer="true" StateMBoxPolling="true" CycleMBoxPollingTime="0" CoeType="39" FoeType="1" VendorId="#x00000002" ProductCode="#x14023052" RevisionNo="#x00110000" InfoDataAddr="true" TimeoutMailbox2="2000" CheckRevisionNoType="3" PortPhys="51" SdoUploadWithMaxLength="true" MaxSlotCount="256" MaxSlotGroupCount="1" SlotPdoIncrement="1" SlotIndexIncrement="16" Type="EL5122 2K. Inc. Encoder 5V (2xAB TTL)" Desc="EL5122" PortABoxInfo="#x0100004d">
<EtherCAT SlaveType="2" PdiType="#x0405" MboxDataLinkLayer="true" StateMBoxPolling="true" CycleMBoxPollingTime="0" CoeType="39" FoeType="1" VendorId="#x00000002" ProductCode="#x14023052" RevisionNo="#x00100000" InfoDataAddr="true" TimeoutMailbox2="2000" CheckRevisionNoType="3" PortPhys="51" SdoUploadWithMaxLength="true" MaxSlotCount="256" MaxSlotGroupCount="1" SlotPdoIncrement="1" SlotIndexIncrement="16" Type="EL5122 2K. Inc. Encoder 5V (2xAB TTL)" Desc="EL5122" PortABoxInfo="#x0100004d">
<SyncMan>001080002600010001000000800000018000001026010000</SyncMan>
<SyncMan>001180002200010002000000800000018000001122010000</SyncMan>
<SyncMan>00120c002400010003000000000000000c00001224010000</SyncMan>
@@ -27369,6 +27369,8 @@ Bit1: Value bigger/equal Limit2]]></Comment>
<DcMode>53796e6368726f6e00000000000000004672656552756e2f534d2d53796e6368726f6e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000</DcMode>
<DcMode>4443000000000000000000000000000044432d53796e6368726f6e000000000000000000000000000000000000000000000000000000000050c30000000000070100000000000000000000000000000000000000000000000000000000000000</DcMode>
<DcMode>4443494e00000000000000000000000044432d53796e6368726f6e2028696e707574206261736500000000000000000000000000c0f2fcff204e0000000000070100000000000000000001000000000000000000000000000000000000000000</DcMode>
<MBoxUserCmdData>020003000a00000007000000000000002300090300000000000000000000000020408001020000001d004164647265737300</MBoxUserCmdData>
<MBoxUserCmdData>020003000c0000000f000000000000002300090300000000000000000000000020408002040000000f7b0100436f6e6e656374696f6e204d6f646500</MBoxUserCmdData>
<Pdo Name="AI1" Index="#x1a00" Flags="#x0010" SyncMan="3">
<ExcludePdo>#x1a01</ExcludePdo>
<Entry Name="Status__Underrange" Index="#x6000" Sub="#x01" Flags="#x00008020">

View File

@@ -66,7 +66,7 @@
</Hides>
</DataType>
<DataType>
<Name GUID="{99721C04-AF32-4BF0-BB6B-A59D0F9957F2}">BMSEvents</Name>
<Name GUID="{4B5D56D2-4431-41C5-8F0A-06E1FC56151A}">BMSEvents</Name>
<DisplayName TxtId=""><![CDATA[String event class]]></DisplayName>
<EventId>
<Name Id="1">NotAllCompInAuto</Name>
@@ -123,6 +123,11 @@
<DisplayName TxtId=""><![CDATA[String {0}: EtherCAT communication error]]></DisplayName>
<Severity>Error</Severity>
</EventId>
<EventId>
<Name Id="12">SafetyIntlkTimeout</Name>
<DisplayName TxtId=""><![CDATA[{0}: Safetyinterlock reset timeout]]></DisplayName>
<Severity>Error</Severity>
</EventId>
<Hides>
<Hide GUID="{1D326C00-DF37-4B94-8E0D-C22524EB2E89}"/>
<Hide GUID="{E7132508-795D-4A6C-AFB1-FED6C1DE44FD}"/>
@@ -142,6 +147,7 @@
<Hide GUID="{BFBE5ACE-2C85-42F3-81C7-7085445C6CF6}"/>
<Hide GUID="{E2F93DD0-9542-4F83-B1C1-7401F6E7E423}"/>
<Hide GUID="{F05A8F7C-1061-4AE4-AAAC-173C036FF4FC}"/>
<Hide GUID="{99721C04-AF32-4BF0-BB6B-A59D0F9957F2}"/>
</Hides>
</DataType>
<DataType>