, eState=> ); IF _xFirstCycle THEN _xFirstCycle := FALSE; _afbStrings[0].Name := 'String 1'; _afbStrings[1].Name := 'String 2'; END_IF // Dely release of errors during PLC startup phase _tonStartupDelay(IN := TRUE); // Call string 1 _afbStrings[0]( stStringModuleVoltageConfig := GVL_CONFIG.stString1VoltageConfig, xEnable := _xEnableString, xStartBalancing := _xStartBalancing, sInverterIP := GVL_CONFIG.sInverterIpString1, rPowerInverter := _arPowerString[0], xInSafetyCheckMode := _xInSafetyCheckMode, stHMIInterface:= GVL_SCADA.stHMIInterface[0], xEmergencyStopOk:= _xEmergencyStopOk, xReleaseErrors:= _xReleaseErrors AND _tonStartupDelay.Q AND _xEtherCatString1Ok, xReleaseLimitErrors:= _xReleaseLimitsErrors AND _tonStartupDelay.Q AND _xEtherCatString1Ok, xReleaseManualMode := _xReleaseManualMode, xConfirmAlarms:= _xConfirmAlarms, xAllToManualMode := _xAllComponentsToManualMode, xResetSafety := xSafetyResterTaster); IF _afbStrings[0].xError THEN _xErrorActive := TRUE; END_IF // Call string 2 _afbStrings[1]( stStringModuleVoltageConfig := GVL_CONFIG.stString2VoltageConfig, xEnable := _xEnableString, xStartBalancing := _xStartBalancing, sInverterIP := GVL_CONFIG.sInverterIpString2, rPowerInverter := _arPowerString[1], xInSafetyCheckMode := _xInSafetyCheckMode, stHMIInterface:= GVL_SCADA.stHMIInterface[1], xEmergencyStopOk:= _xEmergencyStopOk, xReleaseErrors:= _xReleaseErrors AND _tonStartupDelay.Q AND _xEtherCatString2Ok, xReleaseLimitErrors:= _xReleaseLimitsErrors AND _tonStartupDelay.Q AND _xEtherCatString2Ok, xReleaseManualMode := _xReleaseManualMode, xConfirmAlarms:= _xConfirmAlarms, xAllToManualMode := _xAllComponentsToManualMode, xResetSafety := xSafetyResterTaster); 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 GVL_SCADA.stHMIInterface[0].eStatus := E_COMPONENT_STATUS.DISCHARGING; ELSIF _iState = 30 AND _rPowerInverter < 0 THEN GVL_SCADA.stHMIInterface[0].eStatus := E_COMPONENT_STATUS.CHARGING; ELSE GVL_SCADA.stHMIInterface[0].eStatus :=_afbStrings[0].eStatus; END_IF ELSE GVL_SCADA.stHMIInterface[0].eStatus :=_afbStrings[0].eStatus; END_IF *) // =============================== // Read modbus request count // =============================== _timADSReadTimer(IN := NOT _fbADSReader.BUSY, PT := T#200MS); _fbADSReader( NETID:= '', PORT:= 10500, IDXGRP:= 16#2000, IDXOFFS:= 1, LEN:= 4, DESTADDR:= ADR(GVL_MODBUS.stModbusEMSComm.stModbusReg11.udiLifeMessage), READ:= _timADSReadTimer.Q, TMOUT:= T#1S, BUSY=> , ERR=> , ERRID=> ); _fbModbusRead( sIPAddr:= '127.0.0.1', nTCPPort:= 502, nUnitID:= 16#FF, nQuantity:= 48, nMBAddr:= 11000, cbLength:= SIZEOF(_ModbusDebugTest), pDestAddr:= ADR(_ModbusDebugTest), bExecute:= xDebugTest, tTimeout:= T#5S, bBusy=> , bError=> , nErrId=> , cbRead=> ); // =============================== // Copy data to modbus registers // =============================== // Modbus current inverter values 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; // =============================== // State machine // =============================== CASE _eBMSControlMode OF E_BMS_CONTROL_MODE.AUTO_REMOTE: _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(); E_BMS_CONTROL_MODE.AUTO_LOCAL: _xAllComponentsToManualMode := FALSE; _xInSafetyCheckMode := FALSE; _xReleaseManualMode := FALSE; GVL_MODBUS.stModbusEMSComm.stModbusReg11.eBatteryStatus := E_BATTERY_STATUS.TESTING; _rAutoPowerRequest := DINT_TO_REAL(GVL_SCADA.stAutomaticModeHMI.diSetpointAutomatic); IF (GVL_SCADA.eRequestedControlMode <> _eBMSControlMode) AND (GVL_SCADA.xCanChangeControlMode) THEN _eBMSControlMode := GVL_SCADA.eRequestedControlMode; END_IF SM_AUTO(); E_BMS_CONTROL_MODE.MANUAL: _xAllComponentsToManualMode := TRUE; _xInSafetyCheckMode := FALSE; _xReleaseManualMode := TRUE; GVL_MODBUS.stModbusEMSComm.stModbusReg11.eBatteryStatus := E_BATTERY_STATUS.MAINTENANCE; IF (GVL_SCADA.eRequestedControlMode <> _eBMSControlMode) AND (GVL_SCADA.xCanChangeControlMode) THEN _eBMSControlMode := GVL_SCADA.eRequestedControlMode; END_IF SM_MANUAL(); E_BMS_CONTROL_MODE.SAFETY_CHECK: _xAllComponentsToManualMode := FALSE; _xInSafetyCheckMode := TRUE; _xReleaseManualMode := FALSE; GVL_MODBUS.stModbusEMSComm.stModbusReg11.eBatteryStatus := E_BATTERY_STATUS.MAINTENANCE; IF (GVL_SCADA.eRequestedControlMode <> _eBMSControlMode) AND (GVL_SCADA.xCanChangeControlMode) THEN _eBMSControlMode := GVL_SCADA.eRequestedControlMode; END_IF SM_SAFETY_CHECK(); E_BMS_CONTROL_MODE.CAPACITY_TEST: _xAllComponentsToManualMode := FALSE; _xInSafetyCheckMode := FALSE; _xReleaseManualMode := FALSE; GVL_MODBUS.stModbusEMSComm.stModbusReg11.eBatteryStatus := E_BATTERY_STATUS.TESTING; IF (GVL_SCADA.eRequestedControlMode <> _eBMSControlMode) AND (GVL_SCADA.xCanChangeControlMode) THEN _eBMSControlMode := GVL_SCADA.eRequestedControlMode; END_IF SM_CAPACITY_TEST(); E_BMS_CONTROL_MODE.BALANCING: _xAllComponentsToManualMode := FALSE; _xInSafetyCheckMode := FALSE; _xReleaseManualMode := FALSE; IF (GVL_SCADA.eRequestedControlMode <> _eBMSControlMode) AND (GVL_SCADA.xCanChangeControlMode) THEN _xStartBalancing := FALSE; _eBMSControlMode := GVL_SCADA.eRequestedControlMode; END_IF SM_BALANCING(); E_BMS_CONTROL_MODE.CYCLING: _xAllComponentsToManualMode := FALSE; _xInSafetyCheckMode := FALSE; _xReleaseManualMode := FALSE; IF (_iState <> 30) OR _rAutoPowerRequest = 0.0 THEN _rAutoPowerRequest := DINT_TO_REAL(GVL_SCADA.stAutomaticModeHMI.diSetpointAutomatic); END_IF IF (GVL_SCADA.eRequestedControlMode <> _eBMSControlMode) AND (GVL_SCADA.xCanChangeControlMode) THEN _eBMSControlMode := GVL_SCADA.eRequestedControlMode; END_IF SM_AUTO(); END_CASE GVL_SCADA.xCanChangeControlMode := _xCanChangeMode; GVL_SCADA.eCurrentControlMode := _eBMSControlMode; // Calculate current battery dc power GVL_SCADA.diCurrentBatteryPower := REAL_TO_DINT(_afbStrings[0].stInverterData.rActDCPower + _afbStrings[1].stInverterData.rActDCPower); _fbSafety(); // Reset automatic buttons IF GVL_SCADA.stAutomaticModeHMI.stStartAutoButton.xRequest THEN GVL_SCADA.stAutomaticModeHMI.stStartAutoButton.xRequest := FALSE; END_IF IF GVL_SCADA.stAutomaticModeHMI.stStopAutoButton.xRequest THEN GVL_SCADA.stAutomaticModeHMI.stStopAutoButton.xRequest := FALSE; END_IF // Reset alarm confirmation IF _xConfirmAlarms OR _rtHardwareResetButton.Q THEN _xConfirmAlarms := FALSE; END_IF]]> DINT_TO_REAL(GVL_CONFIG.diMinimumAbsPowerForEnable)) AND (NOT _afbStrings[_uiDebugCurrentString].xError) AND _afbStrings[_uiDebugCurrentString].xAllModulesInAutoMode THEN _iState := 5; _xCanChangeMode := FALSE; END_IF 5: // Check if power command is within limits IF _rAutoPowerRequest < DINT_TO_REAL(GVL_CONFIG.diMaxStringDischargePower) AND _rAutoPowerRequest > DINT_TO_REAL(GVL_CONFIG.diMaxStringChargingPower) THEN _xEnableString := TRUE; _xStartBalancing := FALSE; _iState := 10; ELSE // Set error bitmap flag GVL_MODBUS.stModbusEMSComm.stModbusReg11.lwErrorBitmap.0 := 1; // Goto error state _iState := 1000; END_IF 10: // Wait for string to be ready _fbStringReadyTimeout(IN := TRUE, PT := GVL_CONFIG.timStringReadyTimeout); IF _xStringsReady AND (NOT _xStringsErrorActive) THEN _fbStringReadyTimeout(IN := FALSE); _rPowerInverter := 0.0; _iState := 30; END_IF IF _xStringsErrorActive OR _fbStringReadyTimeout.Q THEN _fbStringReadyTimeout(IN := FALSE); _xEnableString := FALSE; GVL_SCADA.stAutomaticModeHMI.diSetpointAutomatic := 0; _xCanChangeMode := TRUE; _iState := 45; END_IF IF (ABS(_rAutoPowerRequest) < DINT_TO_REAL(GVL_CONFIG.diMinimumAbsPowerForEnable)) THEN _fbStringReadyTimeout(IN := FALSE); _xEnableString := FALSE; _xCanChangeMode := TRUE; _iState := 45; END_IF 30: // String and inverter enabled // Set inverter power to modbus requested power _rPowerInverter := _rAutoPowerRequest;//DINT_TO_REAL(GVL_MODBUS.stModbusEMSComm.stModbusReg12.diSetpointActivePower); // Check if the battery should still be active IF (_rAutoPowerRequest = 0.0) THEN _xNoPowerRequested := TRUE; ELSE _xNoPowerRequested := FALSE; END_IF // Set battery status IF _rAutoPowerRequest > 0 THEN GVL_MODBUS.stModbusEMSComm.stModbusReg11.eChargeStatus := E_CHARGE_STATUS.DISCHARGING; ELSIF _rAutoPowerRequest < 0 THEN GVL_MODBUS.stModbusEMSComm.stModbusReg11.eChargeStatus := E_CHARGE_STATUS.CHARGING; ELSE GVL_MODBUS.stModbusEMSComm.stModbusReg11.eChargeStatus := E_CHARGE_STATUS.UNDEFINED; END_IF // Add small delay before shutdown by EMS is detected _tonBeginShutdown(IN := _xNoPowerRequested); // shutdown triggered from EMS IF _tonBeginShutdown.Q THEN _tonBeginShutdown(In := FALSE); // Set inverter to zero power _rPowerInverter := 0.0; // Start string shutdown _xEnableString := FALSE; GVL_MODBUS.stModbusEMSComm.stModbusReg11.eChargeStatus := E_CHARGE_STATUS.DISCHARGING; GVL_MODBUS.stModbusEMSComm.stModbusReg10.uiActiveParallelMembers := 0; _iState := 35; END_IF // Shutdown triggered by battery fully charged IF GVL_MODBUS.stModbusEMSComm.stModbusReg11.eChargeStatus = E_CHARGE_STATUS.CHARGING AND ((_rMaxCurrentInverterDCVoltage >= GVL_CONFIG.rStringFullyChargedVoltage) OR _rHighestSegmentVoltage >= GVL_CONFIG.rMaximumUnitVoltage) THEN IF (_eBMSControlMode = E_BMS_CONTROL_MODE.CYCLING) THEN _rAutoPowerRequest := _rAutoPowerRequest * -1; // Change of charge discharge should be handled in next cycle by same 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 END_IF // Shutdown triggered by battery empty 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 _rAutoPowerRequest := _rAutoPowerRequest * -1; // Change of charge discharge should be handled in next cycle by same 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; END_IF END_IF // Check for errors IF _xStringsErrorActive THEN _xEnableString := FALSE; _tonBeginShutdown(In := FALSE); GVL_SCADA.stAutomaticModeHMI.diSetpointAutomatic := 0; _xCanChangeMode := TRUE; _iState := 45; END_IF 35: // Wait for string to be in shutdown discharge mode IF _xStringsInSchutdownDischargeMode THEN // Check if we are allowed to discharge during shutdown with inverter IF GVL_CONFIG.xShutdownDischargeWithInverter THEN _iState := 40; ELSE _rPowerInverter := 0.0; _xEnableString := FALSE; GVL_SCADA.stAutomaticModeHMI.diSetpointAutomatic := 0; _xCanChangeMode := TRUE; _iState := 45; END_IF END_IF // Check for errors IF _xStringsErrorActive THEN _iState := 1000; END_IF 40: // Wait for inverter discharge done IF _xStringsShutdownDischargeAllowed THEN _rPowerInverter := GVL_CONFIG.rAbsShutdownDischargePower; ELSE _rPowerInverter := 0.0; GVL_SCADA.stAutomaticModeHMI.diSetpointAutomatic := 0; _xEnableString := FALSE; _xCanChangeMode := TRUE; _iState := 45; END_IF // Check for errors IF _xStringsErrorActive THEN _iState := 1000; END_IF // Restart if possible 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 _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 _xStringsErrorActive) AND _xStringsAllInAutomaticMode THEN _xCanChangeMode := FALSE; _iState := 5; END_IF // Check for errors IF _xStringsErrorActive THEN _iState := 1000; END_IF 1000: // Error state _xEnableString := FALSE; _rPowerInverter := 0.0; GVL_MODBUS.stModbusEMSComm.stModbusReg11.eBatteryStatus := E_BATTERY_STATUS.ERROR; _iState := 1010; 1010: // Wait for reset from error state IF (_rAutoPowerRequest = 0.0) AND (NOT _xStringsErrorActive) AND _xConfirmAlarms THEN // Reset modbus error register GVL_MODBUS.stModbusEMSComm.stModbusReg11.lwErrorBitmap := 0; // Reset modbus error flag GVL_MODBUS.stModbusEMSComm.stModbusReg11.eBatteryStatus := E_BATTERY_STATUS.OFF; // Goto init state _iState := 0; _xCanChangeMode := TRUE; END_IF 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 ]]>