suservo: swap transfer function parametrization

The integrator is now parametrized through its gain and not the PI
corner frequency. The integrator gain limit is given in absolute gain
units and not relative to the proportional gain.

close #1033
This commit is contained in:
Robert Jördens 2018-06-01 09:38:18 +00:00
parent e408241233
commit 87d3ac9d25
2 changed files with 33 additions and 48 deletions

View File

@ -348,7 +348,7 @@ class Channel:
self.servo.write(base + 7, b0) self.servo.write(base + 7, b0)
@kernel @kernel
def set_iir(self, profile, adc, gain, corner=0., limit=0., delay=0.): def set_iir(self, profile, adc, kp, ki=0., g=0., delay=0.):
"""Set profile IIR coefficients. """Set profile IIR coefficients.
This method advances the timeline by four servo memory accesses. This method advances the timeline by four servo memory accesses.
@ -361,25 +361,23 @@ class Channel:
coefficient quantization errors): coefficient quantization errors):
.. math:: .. math::
H(s) = K \\frac{1 + \\frac{s}{\\omega_0}} H(s) = k_p + \\frac{k_i}{s + \\frac{k_i}{g}}
{\\frac{1}{g} + \\frac{s}{\\omega_0}}
Where: Where:
* :math:`s = \\sigma + i\\omega` is the complex frequency * :math:`s = \\sigma + i\\omega` is the complex frequency
* :math:`K` is the proportional gain * :math:`k_p` is the proportional gain
* :math:`\\omega_0 = 2\\pi f_0` is the integrator corner frequency * :math:`k_i` is the integrator gain
* :math:`g` is the integrator gain limit * :math:`g` is the integrator gain limit
:param profile: Profile number (0-31) :param profile: Profile number (0-31)
:param adc: ADC channel to take IIR input from (0-7) :param adc: ADC channel to take IIR input from (0-7)
:param gain: Proportional gain (1). This is usually negative (closed :param kp: Proportional gain (1). This is usually negative (closed
loop, positive ADC voltage, positive setpoint). When 0, this loop, positive ADC voltage, positive setpoint). When 0, this
implements a pure I controller with unit gain frequency at implements a pure I controller.
`corner` (use the sign of `corner` for overall gain sign). :param ki: Integrator gain (rad/s). When 0 (the default)
:param corner: Integrator corner frequency (Hz). When 0 (the default) this implements a pure P controller. Same sign as ``kp``.
this implements a pure P controller. :param g: Integrator gain limit (1). When 0 (the default) the
:param limit: Integrator gain limit (1). When 0 (the default) the integrator gain limit is infinite. Same sign as ``ki``.
integrator gain limit is infinite. Positive.
:param delay: Delay (in seconds, 0-300 µs) before allowing IIR updates :param delay: Delay (in seconds, 0-300 µs) before allowing IIR updates
after invoking :meth:`set`. This is rounded to the nearest number after invoking :meth:`set`. This is rounded to the nearest number
of servo cycles (~1.2 µs). Since the RF switch (:meth:`set`) can be of servo cycles (~1.2 µs). Since the RF switch (:meth:`set`) can be
@ -390,47 +388,34 @@ class Channel:
""" """
B_NORM = 1 << COEFF_SHIFT + 1 B_NORM = 1 << COEFF_SHIFT + 1
A_NORM = 1 << COEFF_SHIFT A_NORM = 1 << COEFF_SHIFT
PI_TS = 3.1415927*T_CYCLE
COEFF_MAX = 1 << COEFF_WIDTH - 1 COEFF_MAX = 1 << COEFF_WIDTH - 1
gain *= B_NORM kp *= B_NORM
corner *= PI_TS if ki == 0.:
if corner == 0.:
# pure P # pure P
a1_ = 0 a1 = 0
b1_ = 0 b1 = 0
b0_ = int(round(gain)) b0 = int(round(kp))
else: else:
a1_ = A_NORM # I or PI
if gain == 0.: ki *= B_NORM*T_CYCLE/2.
# pure I if g == 0.:
b0 = (2*B_NORM)*corner c = 1.
b1_ = 0 a1 = A_NORM
else: else:
# PI c = 1./(1. + ki/(g*B_NORM))
k = gain*corner a1 = int(round((2.*c - 1.)*A_NORM))
b1 = k - gain b0 = int(round(kp + ki*c))
b0 = k + gain b1 = int(round(kp + (ki - 2.*kp)*c))
if limit != 0.: if b1 == -b0:
# PI with limit raise ValueError("low integrator gain and/or gain limit")
q = corner/limit
qr = 1./(1. + q)
a1_ = int(round(a1_*(1. - q)*qr))
b0 *= qr
b1 *= qr
b1_ = int(round(b1))
b0_ = int(round(b0))
if b1_ == -b0_: if (b0 >= COEFF_MAX or b0 < -COEFF_MAX or
raise ValueError("low corner, gain, limit") b1 >= COEFF_MAX or b1 < -COEFF_MAX):
raise ValueError("high gains")
if (b0_ >= COEFF_MAX or b0_ < -COEFF_MAX or
b1_ >= COEFF_MAX or b1_ < -COEFF_MAX):
raise ValueError("high corner, gain, limit")
dly = int(round(delay/T_CYCLE)) dly = int(round(delay/T_CYCLE))
self.set_iir_mu(profile, adc, a1_, b0_, b1_, dly) self.set_iir_mu(profile, adc, a1, b0, b1, dly)
@kernel @kernel
def get_profile_mu(self, profile, data): def get_profile_mu(self, profile, data):

View File

@ -50,9 +50,9 @@ class SUServo(EnvExperiment):
self.suservo0_ch0.set_iir( self.suservo0_ch0.set_iir(
profile=0, profile=0,
adc=7, # take data from Sampler channel 7 adc=7, # take data from Sampler channel 7
gain=-.1, # -0.1 P gain kp=-.1, # -0.1 P gain
corner=70*Hz, # very low corner frequency ki=-300./s, # low integrator gain
limit=0., # unlimited integrator gain g=0., # no integrator gain limit
delay=0. # no IIR update delay after enabling delay=0. # no IIR update delay after enabling
) )
# setpoint 0.5 (5 V with above PGIA gain setting) # setpoint 0.5 (5 V with above PGIA gain setting)