PLC Energy Management: Power Monitoring, Load Shedding & Demand Control in Structured Text

Electricity is your factory's second-largest cost. Build PLC-based energy management with power monitoring, intelligent load shedding, demand prediction, and ISO 50001 KPI tracking in Structured Text.

⚡ Why PLC-Based Energy Management?

Energy typically represents 15–30% of manufacturing costs. Most plants pay not just for energy consumed (kWh) but also for peak demand (kW) — the highest 15-minute average power draw in the billing period. A single demand spike can increase your electricity bill by thousands of dollars per month for an entire year.

PLCs are perfectly positioned for energy management because they already control the equipment that consumes power. By adding power monitoring and intelligent load coordination, you can cut energy costs 10–25% without reducing production.

📊 Power Monitoring Data Structures

Modern power meters (Schneider PM5xxx, Siemens PAC, ABB M4M) communicate via Modbus TCP or PROFINET. Structure the data cleanly:

TYPE PowerMeterData :
STRUCT
    // Instantaneous values
    Voltage_L1N     : REAL;     // Volts
    Voltage_L2N     : REAL;
    Voltage_L3N     : REAL;
    Voltage_L1L2    : REAL;
    Current_L1      : REAL;     // Amps
    Current_L2      : REAL;
    Current_L3      : REAL;
    ActivePower     : REAL;     // kW (total 3-phase)
    ReactivePower   : REAL;     // kVAR
    ApparentPower   : REAL;     // kVA
    PowerFactor     : REAL;     // 0.0 to 1.0
    Frequency       : REAL;     // Hz
    
    // Accumulated values
    ActiveEnergy    : REAL;     // kWh (total since reset)
    ReactiveEnergy  : REAL;     // kVARh
    
    // Demand values
    DemandPower     : REAL;     // kW (current demand interval average)
    PeakDemand      : REAL;     // kW (highest this billing period)
    
    // Quality
    CommOK          : BOOL;
    DataValid       : BOOL;
END_STRUCT;
END_TYPE

Scaling from Modbus Registers

FUNCTION_BLOCK FB_PowerMeterReader
VAR_INPUT
    RawRegisters    : ARRAY[0..39] OF INT;   // Modbus holding registers
    CommStatus      : BOOL;
END_VAR
VAR_OUTPUT
    Data            : PowerMeterData;
END_VAR

Data.CommOK := CommStatus;

IF NOT CommStatus THEN Data.DataValid := FALSE; RETURN; END_IF;

// Scale registers to engineering units (meter-specific scaling) // Example: PM5xxx uses register pairs for REAL values Data.Voltage_L1N := FC_RegsToReal( HighWord := INT_TO_WORD(RawRegisters[0]), LowWord := INT_TO_WORD(RawRegisters[1]), BigEndian := TRUE );

Data.Current_L1 := FC_RegsToReal( HighWord := INT_TO_WORD(RawRegisters[6]), LowWord := INT_TO_WORD(RawRegisters[7]), BigEndian := TRUE );

Data.ActivePower := FC_RegsToReal( HighWord := INT_TO_WORD(RawRegisters[12]), LowWord := INT_TO_WORD(RawRegisters[13]), BigEndian := TRUE );

Data.PowerFactor := FC_RegsToReal( HighWord := INT_TO_WORD(RawRegisters[18]), LowWord := INT_TO_WORD(RawRegisters[19]), BigEndian := TRUE );

Data.DataValid := TRUE;

📈 Demand Monitoring and Prediction

The utility measures demand in 15-minute intervals. If you can predict you're about to exceed your target, you can shed loads before the interval closes:

FUNCTION_BLOCK FB_DemandPredictor
VAR_INPUT
    InstantPower    : REAL;          // Current kW
    DemandTarget    : REAL;          // kW target (contract limit)
    IntervalLength  : TIME := T#15M; // Utility demand interval
END_VAR
VAR_OUTPUT
    CurrentDemand   : REAL;          // Rolling average this interval
    PredictedDemand : REAL;          // Where we'll end up
    ExceedingTarget : BOOL;
    ShedRequired    : REAL;          // kW to shed to stay under target
    IntervalProgress: REAL;          // 0–100%
END_VAR
VAR
    EnergyAccum     : REAL := 0.0;   // kWh accumulated this interval
    SampleCount     : DINT := 0;
    IntervalSecs    : REAL;
    ElapsedSecs     : REAL;
    tmrInterval     : TON;
    scanTimeSec     : REAL := 0.01;
END_VAR

IntervalSecs := TIME_TO_REAL(IntervalLength) / 1000.0;

// Accumulate energy EnergyAccum := EnergyAccum + (InstantPower * scanTimeSec / 3600.0); SampleCount := SampleCount + 1; ElapsedSecs := DINT_TO_REAL(SampleCount) * scanTimeSec;

// Current demand = energy / elapsed hours * 1 hour IF ElapsedSecs > 0.0 THEN CurrentDemand := EnergyAccum / (ElapsedSecs / 3600.0); END_IF;

// Predict end-of-interval demand IntervalProgress := (ElapsedSecs / IntervalSecs) * 100.0;

IF IntervalProgress > 5.0 THEN // Linear projection PredictedDemand := CurrentDemand; // Weight recent power more heavily in early interval IF IntervalProgress < 50.0 THEN PredictedDemand := (CurrentDemand 0.4) + (InstantPower 0.6); END_IF; END_IF;

ExceedingTarget := PredictedDemand > (DemandTarget * 0.95); ShedRequired := PredictedDemand - DemandTarget; IF ShedRequired < 0.0 THEN ShedRequired := 0.0; END_IF;

// Reset at interval boundary tmrInterval(IN := TRUE, PT := IntervalLength); IF tmrInterval.Q THEN tmrInterval(IN := FALSE); EnergyAccum := 0.0; SampleCount := 0; END_IF;

🔌 Intelligent Load Shedding

When demand approaches the target, shed loads in priority order — least critical first:

TYPE SheddableLoad :
STRUCT
    LoadName     : STRING[20];
    RatedPower   : REAL;          // kW when running
    Priority     : INT;           // 1=shed first, 5=shed last
    MinOffTime   : TIME;          // Don't cycle too fast
    CanShed      : BOOL;          // Currently available to shed
    IsShed       : BOOL;          // Currently shed
    RunCmd       : BOOL;          // Normal run command
END_STRUCT;
END_TYPE

FUNCTION_BLOCK FB_LoadShedder VAR_INPUT Enable : BOOL; ShedRequired : REAL; // kW to shed Loads : ARRAY[1..12] OF SheddableLoad; NumLoads : INT; END_VAR VAR_OUTPUT TotalShed : REAL; // kW currently shed LoadsShed : INT; // Count of shed loads ShedCommands : ARRAY[1..12] OF BOOL; // TRUE = shed this load END_VAR VAR i : INT; accumulated : REAL; tmrMinOff : ARRAY[1..12] OF TON; END_VAR

IF NOT Enable OR ShedRequired <= 0.0 THEN // No shedding needed — release all loads FOR i := 1 TO NumLoads DO ShedCommands[i] := FALSE; END_FOR; TotalShed := 0.0; LoadsShed := 0; RETURN; END_IF;

// Shed loads in priority order (1=first to shed) accumulated := 0.0; LoadsShed := 0;

FOR i := 1 TO NumLoads DO // Simple priority scan — shed lowest priority first IF Loads[i].CanShed AND accumulated < ShedRequired THEN ShedCommands[i] := TRUE; accumulated := accumulated + Loads[i].RatedPower; LoadsShed := LoadsShed + 1; ELSE ShedCommands[i] := FALSE; END_IF; END_FOR;

TotalShed := accumulated;

Load Shedding Priority Example

| Priority | Load | Rated kW | Notes | | 1 (first) | HVAC compressor 2 | 45 | Redundant — HVAC 1 maintains temp | | 2 | Lighting zone B | 12 | Non-production area | | 3 | Air compressor 3 | 30 | Receiver tank provides buffer | | 4 | Cooling tower fan 2 | 22 | Partial cooling acceptable short-term | | 5 (last) | Packaging line 2 | 35 | Reduces throughput — shed only if critical |

🔋 Energy KPI Calculations

Track these metrics to identify savings opportunities:

TYPE EnergyKPIs :
STRUCT
    kWh_Total         : REAL;     // Total energy consumed
    kWh_PerUnit       : REAL;     // Energy per product unit (SEC)
    PeakDemand_kW     : REAL;     // Peak demand this period
    LoadFactor        : REAL;     // Average/Peak (0–1, higher=better)
    PowerFactor_Avg   : REAL;     // Average PF (target >0.95)
    CostPerHour       : REAL;     // Estimated $/hour
    CO2_Tonnes        : REAL;     // Carbon footprint
END_STRUCT;
END_TYPE

FUNCTION_BLOCK FB_EnergyKPICalculator VAR_INPUT ActivePower : REAL; // Current kW PowerFactor : REAL; ProductCount : DINT; // Units produced this period CostPerKWh : REAL; // $/kWh CO2PerKWh : REAL; // kg CO2/kWh (grid emission factor) PeriodHours : REAL; END_VAR VAR_OUTPUT KPIs : EnergyKPIs; END_VAR VAR EnergyAccum : REAL := 0.0; PFAccum : REAL := 0.0; SampleCount : DINT := 0; scanTimeSec : REAL := 0.01; END_VAR

// Accumulate energy EnergyAccum := EnergyAccum + (ActivePower * scanTimeSec / 3600.0); KPIs.kWh_Total := EnergyAccum;

// Track peak demand IF ActivePower > KPIs.PeakDemand_kW THEN KPIs.PeakDemand_kW := ActivePower; END_IF;

// Average power factor SampleCount := SampleCount + 1; PFAccum := PFAccum + PowerFactor; KPIs.PowerFactor_Avg := PFAccum / DINT_TO_REAL(SampleCount);

// Specific Energy Consumption (SEC) — kWh per unit produced IF ProductCount > 0 THEN KPIs.kWh_PerUnit := EnergyAccum / DINT_TO_REAL(ProductCount); END_IF;

// Load factor IF KPIs.PeakDemand_kW > 0.0 AND PeriodHours > 0.0 THEN KPIs.LoadFactor := (EnergyAccum / PeriodHours) / KPIs.PeakDemand_kW; END_IF;

// Cost and carbon KPIs.CostPerHour := ActivePower * CostPerKWh; KPIs.CO2_Tonnes := EnergyAccum * CO2PerKWh / 1000.0;

⚖️ Power Factor Correction

Poor power factor (< 0.9) means you're drawing more current than necessary, causing higher losses and utility penalties:

FUNCTION_BLOCK FB_PFCController
VAR_INPUT
    PowerFactor     : REAL;       // Measured PF
    TargetPF        : REAL := 0.95;
    ReactivePower   : REAL;       // kVAR measured
    CapBankSteps    : INT := 6;   // Number of capacitor steps
    StepSize        : REAL := 25.0; // kVAR per step
END_VAR
VAR_OUTPUT
    StepsEngaged    : INT;
    CapacitorKVAR   : REAL;
    PFCorrected     : REAL;
END_VAR
VAR
    RequiredKVAR    : REAL;
    tmrStepDelay    : TON;
    pendingStep     : INT;
END_VAR

// Calculate required compensation IF PowerFactor < TargetPF AND PowerFactor > 0.0 THEN // kVAR needed = kW × (tan(arccos(current PF)) - tan(arccos(target PF))) // Simplified: use reactive power directly RequiredKVAR := ReactivePower * (1.0 - (TargetPF / PowerFactor)); pendingStep := REAL_TO_INT(RequiredKVAR / StepSize); IF pendingStep < 0 THEN pendingStep := 0; END_IF; IF pendingStep > CapBankSteps THEN pendingStep := CapBankSteps; END_IF; ELSE pendingStep := 0; END_IF;

// Step change delay — prevent capacitor hunting tmrStepDelay(IN := (pendingStep <> StepsEngaged), PT := T#30S); IF tmrStepDelay.Q THEN StepsEngaged := pendingStep; tmrStepDelay(IN := FALSE); END_IF;

CapacitorKVAR := INT_TO_REAL(StepsEngaged) * StepSize;

Summary

| Feature | Implementation | | Power monitoring | Structured data from Modbus meters with scaling | | Demand prediction | Linear projection with weighted recent-power bias | | Load shedding | Priority-based automatic shedding with min-off timers | | Energy KPIs | SEC, load factor, power factor, cost, CO2 tracking | | Power factor correction | Automatic capacitor bank stepping with anti-hunting delay | | ISO 50001 data | Structured logging for energy management system compliance |

Energy management isn't a separate system — it's a layer on top of your existing PLC control. Every motor start, compressor cycle, and heater activation is an opportunity to optimize.