Skip to content

Commit aa90a9c

Browse files
committed
autotune: show stability margins on bode plot
1 parent c974a0a commit aa90a9c

1 file changed

Lines changed: 84 additions & 17 deletions

File tree

autotune/autotune.py

Lines changed: 84 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ def __init__(self, parent=None):
9393
self.input_ref = None
9494
self.closed_loop_ref = None
9595
self.closed_loop_ax = None
96-
self.bode_plot_ref = None
96+
self.bode_plot_ref = []
9797
self.pz_plot_refs = []
9898
self.file_name = None
9999
self.is_system_identified = False
@@ -213,7 +213,7 @@ def reset(self):
213213
self.model_ref = None
214214
self.input_ref = None
215215
self.closed_loop_ref = None
216-
self.bode_plot_ref = None
216+
self.bode_plot_ref = []
217217
self.pz_plot_refs = []
218218
self.is_system_identified = False
219219

@@ -538,7 +538,7 @@ def plotPolesZeros(self):
538538
poles = self.Gz.poles()
539539
zeros = self.Gz.zeros()
540540
if not self.pz_plot_refs:
541-
ax = self.figure.add_subplot(3, 3, 6)
541+
ax = self.figure.add_subplot(3, 3, 4)
542542
plot_ref = ax.plot(poles.real, poles.imag, "rx", markersize=10)
543543
self.pz_plot_refs.append(plot_ref[0])
544544
plot_ref = ax.plot(zeros.real, zeros.imag, "ro", markersize=10)
@@ -737,11 +737,27 @@ def updateClosedLoop(self):
737737
y_out += y_d
738738

739739
self.plotClosedLoop(t_out, y_out)
740-
w = np.logspace(-1, 3, 40).tolist()
741-
(mag_cl, phase_cl, omega_cl) = ctrl.frequency_response(
742-
closed_loop, omega=np.asarray(w)
740+
741+
sum_feedback = ctrl.summing_junction(inputs=["rd"], output="e")
742+
open_loop = ctrl.interconnect(
743+
[
744+
delays,
745+
sampler,
746+
sum_feedback,
747+
feedforward,
748+
sum_control,
749+
p_control,
750+
i_control,
751+
d_control,
752+
id_control,
753+
out_sign,
754+
plant,
755+
],
756+
inputs="r",
757+
outputs="y",
743758
)
744-
self.plotBode(omega_cl, mag_cl)
759+
760+
self.plotBode(open_loop, closed_loop)
745761

746762
def plotClosedLoop(self, t, y):
747763
if self.closed_loop_ref is None:
@@ -760,21 +776,72 @@ def plotClosedLoop(self, t, y):
760776

761777
self.canvas.draw()
762778

763-
def plotBode(self, w_cl, mag_cl):
764-
if self.bode_plot_ref is None:
765-
ax = self.figure.add_subplot(3, 3, (8, 9))
766-
f = w_cl / (2 * np.pi)
767-
plot_ref = ax.semilogx(f, 20 * np.log10(mag_cl))
779+
def plotBode(self, open_loop, closed_loop):
780+
781+
(
782+
gain_margin,
783+
phase_margin,
784+
stab_margin,
785+
phase_crossover,
786+
gain_crossover,
787+
stab_margin_w,
788+
) = ctrl.stability_margins(open_loop)
789+
stability_margins_text = f"Gain margin: {20 * np.log10(gain_margin):.2f}dB (@{phase_crossover / (2 * np.pi):.1f}Hz)\nPhase margin: {phase_margin:.1f}deg (@{gain_crossover / (2 * np.pi):.1f}Hz)"
790+
791+
w = np.logspace(-1, 3, 40).tolist()
792+
(mag_ol, phase_ol, omega_ol) = ctrl.frequency_response(
793+
open_loop, omega=np.asarray(w)
794+
)
795+
796+
(mag_cl, phase_cl, omega_cl) = ctrl.frequency_response(
797+
closed_loop, omega=np.asarray(w)
798+
)
799+
f = omega_cl / (2 * np.pi)
800+
801+
if not self.bode_plot_ref:
802+
ax = self.figure.add_subplot(3, 3, (5, 6))
803+
plot_ref = ax.semilogx(f, 20 * np.log10(mag_ol), label="Open-loop")
804+
self.bode_plot_ref.append(plot_ref[0])
805+
plot_ref = ax.semilogx(f, 20 * np.log10(mag_cl), label="Closed-loop")
806+
self.bode_plot_ref.append(plot_ref[0])
807+
ax.set_ylim(-20, 20)
768808
ax.plot([f[0], f[-1]], [0, 0], "k--")
769809
ax.plot([f[0], f[-1]], [-3, -3], "g--")
770-
self.bode_plot_ref = plot_ref[0]
810+
771811
ax.set_title("Bode")
772-
ax.set_xlabel("Frequency (Hz)")
773812
ax.set_ylabel("Magnitude (dB)")
813+
ax.legend()
814+
815+
ax = self.figure.add_subplot(3, 3, (8, 9))
816+
plot_ref = ax.semilogx(f, phase_ol * 180 / np.pi, label="Open-loop")
817+
self.bode_plot_ref.append(plot_ref[0])
818+
plot_ref = ax.semilogx(f, phase_cl * 180 / np.pi, label="Closed-loop")
819+
self.bode_plot_ref.append(plot_ref[0])
820+
ax.set_ylim(-180, 180)
821+
822+
ax.set_xlabel("Frequency (Hz)")
823+
ax.set_ylabel("Phase (deg)")
824+
self.gain_margin_text_ref = ax.text(
825+
0.01,
826+
0.9,
827+
stability_margins_text,
828+
verticalalignment="top",
829+
transform=ax.transAxes,
830+
)
831+
774832
else:
775-
f = w_cl / (2 * np.pi)
776-
self.bode_plot_ref.set_xdata(f)
777-
self.bode_plot_ref.set_ydata(20 * np.log10(mag_cl))
833+
self.bode_plot_ref[0].set_xdata(f)
834+
mag_ol_db = 20 * np.log10(mag_ol)
835+
self.bode_plot_ref[0].set_ydata(mag_ol_db)
836+
self.bode_plot_ref[1].set_xdata(f)
837+
mag_cl_db = 20 * np.log10(mag_cl)
838+
self.bode_plot_ref[1].set_ydata(mag_cl_db)
839+
self.gain_margin_text_ref.set_text(stability_margins_text)
840+
841+
self.bode_plot_ref[2].set_xdata(f)
842+
self.bode_plot_ref[2].set_ydata(phase_ol * 180 / np.pi)
843+
self.bode_plot_ref[3].set_xdata(f)
844+
self.bode_plot_ref[3].set_ydata(phase_cl * 180 / np.pi)
778845

779846
self.canvas.draw()
780847

0 commit comments

Comments
 (0)