Source code for soft4pes.control.modulation.carrier_pwm
"""
Asynchronous carrier-based pulse width modulation (CB-PWM) for two and three-level converters.
"""
import numpy as np
from soft4pes.control.common.controller import Controller
from types import SimpleNamespace
[docs]class CarrierPWM(Controller):
def __init__(self):
super().__init__()
self.carrier_rising = False
[docs] def execute(self, sys, kTs):
"""
Generate switching time instants and switch positions using asynchronous carrier-based pulse
width modulation (CB-PWM). The modulating signal is sampled at the peaks of the carrier,
resulting in the device switching frequency of 1/(2Ts) for two-level converters. For three-
level converters, the device switching frequency is roughly half the apparent switching, ie.
the carrier frequency, when phase-disposition PWM is used.
The produced output is presented below. Note that the switching times are in ascending
order.
[docs]
| Switch positions |
Switching time instants | Phase A | Phase B | Phase C |
--------------- |---------|---------|---------|
[docs] t_switch[1] | state | state | state
[docs] t_switch[2] | state | state | state
t_switch[3] | state | state | state
Parameters
----------
sys : system object
The system model.
kTs : float
Current discrete time instant [s].
Returns
-------
t_switch : ndarray
Switching time instants.
switch_array : ndarray
Switch positions.
"""
u_ref_abc = self.input.u_abc
# Calculate the duty ratio, bound to [0 1]. Note that the full duty cycle corresponds to the
# controller sampling interval.
# For two-level converter, the duty ratio of a phase represents the fraction of the sampling
# interval that the upper switch in the corresponding phase is on. When the upper switch is
# on, the converter phase is in switch position 1, and when the upper switch is off, the
# switch position of the phase is -1.
# In the case of a three-level converter, the duty ratio represents the fraction of the
# sampling interval that
# - Switch S1 is on and S3 off, when the modulating signal is >= 0. Switch S2 is always on
# and S4 off in this case. The phase is then in switch position 1 when S1 is on and and 0
# while S1 is off.
# - Switch S4 is on and S2 off, when the modulating signal is < 0. Switch S1 is always off
# and S3 on. The phase is then in switch position -1 when S4 is on and 0 when S4 is off
if sys.conv.nl == 2:
d_abc = 0.5 * (u_ref_abc + 1)
elif sys.conv.nl == 3:
d_abc = np.where(u_ref_abc < 0, 1 + u_ref_abc, u_ref_abc)
else:
raise ValueError(
'Only two- and three-level converters are supported.')
# Compute switching time instants and corresponding switch positions. Add a zero at the
# beginning of the array to include the switch position when the cycle starts
if self.carrier_rising:
# If the carrier is rising, the duty ratio corresponds to the percentage the switch is
# on from the start of the cycle.
t_switch = np.concatenate(([0], d_abc))
switch_pos = (d_abc > t_switch[:, np.newaxis]).astype(int)
else:
# If the carrier is falling, calculate the time the switch is off from the start of the
# cycle (d-1).
t_switch = np.concatenate(([0], 1 - d_abc))
switch_pos = (~(1 - d_abc > t_switch[:, np.newaxis])).astype(int)
# Switch positions are 1 and 0, corresponding the state of the individual switches (S1 for
# two-level converter, S1/S4 for three-level converter depending on the sign of the
# modulating signal). Deduce the phase switch positions.
if sys.conv.nl == 2:
# Map 0 → -1 and 1 → 1
switch_pos_phase = 2 * switch_pos - 1
elif sys.conv.nl == 3:
# Map 0 → 0 and 1 → 1. The negative phase switch position -1 is selected when the
# modulating signal is negative. Then, map 1 -> 0 and 0 -> -1.
switch_pos_phase = np.where(u_ref_abc < 0, switch_pos - 1,
switch_pos)
else:
raise ValueError(
'Only two- and three-level converters are supported.')
# Sort switching time instants and switch positions to ensure that the switching time
# instants are in ascending order. This is important in case two switching instants appear
# at (almost) the same time. By sorting, we can pick only the latter switch position, as it
# also includes the former switch position
sorted_t_indices = np.argsort(t_switch)
t_switch = t_switch[sorted_t_indices]
switch_pos_phase = switch_pos_phase[sorted_t_indices]
# Toggle the carrier direction
self.carrier_rising = not self.carrier_rising
self.output = SimpleNamespace(
t_switch=t_switch,
switch_pos=switch_pos_phase,
)
return self.output