Update figures and tangle matlab code

This commit is contained in:
2020-11-24 18:28:16 +01:00
parent 0b0c5d14a9
commit 805af508fd
33 changed files with 5460 additions and 356 deletions

View File

@@ -0,0 +1,95 @@
%% Clear Workspace and Close figures
clear; close all; clc;
%% Intialize Laplace variable
s = zpk('s');
addpath('./mat/');
% Load Data :ignore:
% The data are loaded:
load('apa95ml_5kg_2a_1s.mat', 't', 'u', 'v');
% Any offset is removed.
u = detrend(u, 0); % Speedgoat DAC output Voltage [V]
v = detrend(v, 0); % Speedgoat ADC input Voltage (sensor stack) [V]
% Here, the amplifier gain is 20.
u = 20*u; % Actuator Stack Voltage [V]
% Compute TF estimate and Coherence :ignore:
% The transfer function from the actuator voltage $V_a$ to the force sensor stack voltage $V_s$ is computed.
Ts = t(end)/(length(t)-1);
Fs = 1/Ts;
win = hann(ceil(5/Ts));
[tf_est, f] = tfestimate(u, v, win, [], [], 1/Ts);
[coh, ~] = mscohere( u, v, win, [], [], 1/Ts);
% The coherence is shown in Figure [[fig:apa95ml_5kg_cedrat_coh]].
figure;
hold on;
plot(f, coh, 'k-')
set(gca, 'Xscale', 'log'); set(gca, 'Yscale', 'lin');
ylabel('Coherence'); xlabel('Frequency [Hz]');
hold off;
xlim([10, 5e3]);
% #+name: fig:apa95ml_5kg_cedrat_coh
% #+caption: Coherence
% #+RESULTS:
% [[file:figs/apa95ml_5kg_cedrat_coh.png]]
% The Simscape model is loaded and compared with the identified dynamics in Figure [[fig:bode_plot_force_sensor_voltage_comp_fem]].
% The non-minimum phase zero is just a side effect of the not so great identification.
% Taking longer measurements would results in a minimum phase zero.
load('mat/fem_simscape_models.mat', 'Gfm');
freqs = logspace(1, 4, 1000);
figure;
tiledlayout(3, 1, 'TileSpacing', 'None', 'Padding', 'None');
ax1 = nexttile([2,1]);
hold on;
plot(f, abs(tf_est))
plot(freqs, abs(squeeze(freqresp(Gfm, freqs, 'Hz'))))
set(gca, 'Xscale', 'log'); set(gca, 'Yscale', 'log');
ylabel('Amplitude [V/V]'); set(gca, 'XTickLabel',[]);
hold off;
ylim([1e-3, 1e1]);
ax2 = nexttile;
hold on;
plot(f, 180/pi*angle(tf_est), ...
'DisplayName', 'Identification')
plot(freqs, 180/pi*angle(squeeze(freqresp(Gfm, freqs, 'Hz'))), ...
'DisplayName', 'FEM')
set(gca, 'Xscale', 'log'); set(gca, 'Yscale', 'lin');
ylabel('Phase'); xlabel('Frequency [Hz]');
hold off;
ylim([-180, 180]);
yticks(-180:90:180);
legend('location', 'northeast')
linkaxes([ax1,ax2], 'x');
xlim([10, 5e3]);

34
matlab/huddle_test.m Normal file
View File

@@ -0,0 +1,34 @@
%% Clear Workspace and Close figures
clear; close all; clc;
%% Intialize Laplace variable
s = zpk('s');
addpath('./mat/');
% Load Data :noexport:
load('huddle_test.mat', 't', 'y');
y = y - mean(y(1000:end));
% Time Domain Data
figure;
plot(t(1000:end), y(1000:end))
ylabel('Output Displacement [m]'); xlabel('Time [s]');
% PSD of Measurement Noise
Ts = t(end)/(length(t)-1);
Fs = 1/Ts;
win = hanning(ceil(1*Fs));
[pxx, f] = pwelch(y(1000:end), win, [], [], Fs);
figure;
plot(f, sqrt(pxx));
set(gca, 'XScale', 'log'); set(gca, 'YScale', 'log');
xlabel('Frequency [Hz]'); ylabel('ASD [$m/\sqrt{Hz}$]');
xlim([1, Fs/2]);

View File

@@ -0,0 +1,307 @@
%% Clear Workspace and Close figures
clear; close all; clc;
%% Intialize Laplace variable
s = zpk('s');
addpath('./mat/');
% IFF Plant
% From the identified plant, a model of the transfer function from the actuator stack voltage to the force sensor generated voltage is developed.
w_z = 2*pi*111; % Zeros frequency [rad/s]
w_p = 2*pi*255; % Pole frequency [rad/s]
xi_z = 0.05;
xi_p = 0.015;
G_inf = 0.1;
Gi = G_inf*(s^2 + 2*xi_z*w_z*s + w_z^2)/(s^2 + 2*xi_p*w_p*s + w_p^2);
% Its bode plot is shown in Figure [[fig:iff_plant_identification_apa95ml]].
freqs = logspace(1, 4, 1000);
figure;
tiledlayout(3, 1, 'TileSpacing', 'None', 'Padding', 'None');
ax1 = nexttile([2,1]);
hold on;
plot(freqs, abs(squeeze(freqresp(Gi, freqs, 'Hz'))), 'k-')
set(gca, 'Xscale', 'log'); set(gca, 'Yscale', 'log');
ylabel('Amplitude'); set(gca, 'XTickLabel', []);
hold off;
ylim([1e-3, 1e1]);
ax2 = nexttile;
hold on;
plot(freqs, 180/pi*angle(squeeze(freqresp(Gi, freqs, 'Hz'))), 'k-')
set(gca, 'Xscale', 'log'); set(gca, 'Yscale', 'lin');
ylabel('Phase'); xlabel('Frequency [Hz]');
hold off;
ylim([-180, 180]);
yticks(-180:90:180);
linkaxes([ax1,ax2], 'x');
xlim([10, 5e3]);
% #+name: fig:iff_plant_identification_apa95ml
% #+caption: Bode plot of the IFF plant
% #+RESULTS:
% [[file:figs/iff_plant_identification_apa95ml.png]]
% The controller used in the Integral Force Feedback Architecture is:
% \begin{equation}
% K_{\text{IFF}}(s) = \frac{g}{s + 2\cdot 2\pi} \cdot \frac{s}{s + 0.5 \cdot 2\pi}
% \end{equation}
% where $g$ is a gain that can be tuned.
% Above 2 Hz the controller is basically an integrator, whereas an high pass filter is added at 0.5Hz to further reduce the low frequency gain.
% In the frequency band of interest, this controller should mostly act as a pure integrator.
% The Root Locus corresponding to this controller is shown in Figure [[fig:root_locus_iff_apa95ml_identification]].
gains = logspace(0, 5, 1000);
figure;
hold on;
plot(real(pole(Gi)), imag(pole(Gi)), 'kx');
plot(real(tzero(Gi)), imag(tzero(Gi)), 'ko');
for i = 1:length(gains)
cl_poles = pole(feedback(Gi, (gains(i)/(s + 2*2*pi)*s/(s + 0.5*2*pi))));
plot(real(cl_poles), imag(cl_poles), 'k.');
end
ylim([0, 1800]);
xlim([-1600,200]);
xlabel('Real Part')
ylabel('Imaginary Part')
axis square
% First tests with few gains
% The controller is now implemented in practice, and few controller gains are tested: $g = 0$, $g = 10$ and $g = 100$.
% For each controller gain, the identification shown in Figure [[fig:test_bench_apa_schematic_iff]] is performed.
iff_g10 = load('apa95ml_iff_g10_res.mat', 'u', 't', 'y', 'v');
iff_g100 = load('apa95ml_iff_g100_res.mat', 'u', 't', 'y', 'v');
iff_of = load('apa95ml_iff_off_res.mat', 'u', 't', 'y', 'v');
Ts = 1e-4;
win = hann(ceil(10/Ts));
[tf_iff_g10, f] = tfestimate(iff_g10.u, iff_g10.y, win, [], [], 1/Ts);
[co_iff_g10, ~] = mscohere( iff_g10.u, iff_g10.y, win, [], [], 1/Ts);
[tf_iff_g100, ~] = tfestimate(iff_g100.u, iff_g100.y, win, [], [], 1/Ts);
[co_iff_g100, ~] = mscohere( iff_g100.u, iff_g100.y, win, [], [], 1/Ts);
[tf_iff_of, ~] = tfestimate(iff_of.u, iff_of.y, win, [], [], 1/Ts);
[co_iff_of, ~] = mscohere( iff_of.u, iff_of.y, win, [], [], 1/Ts);
% The coherence between the excitation signal and the mass displacement as measured by the interferometer is shown in Figure [[fig:iff_first_test_coherence]].
figure;
hold on;
plot(f, co_iff_of, '-', 'DisplayName', 'g=0')
plot(f, co_iff_g10, '-', 'DisplayName', 'g=10')
plot(f, co_iff_g100, '-', 'DisplayName', 'g=100')
set(gca, 'Xscale', 'log'); set(gca, 'Yscale', 'lin');
ylabel('Coherence'); xlabel('Frequency [Hz]');
hold off;
legend();
xlim([60, 600])
% #+name: fig:iff_first_test_coherence
% #+caption: Coherence
% #+RESULTS:
% [[file:figs/iff_first_test_coherence.png]]
% The obtained transfer functions are shown in Figure [[fig:iff_first_test_bode_plot]].
% It is clear that the IFF architecture can actively damp the main resonance of the system.
figure;
tiledlayout(3, 1, 'TileSpacing', 'None', 'Padding', 'None');
ax1 = nexttile([2, 1]);
hold on;
plot(f, abs(tf_iff_of), '-', 'DisplayName', 'g=0')
plot(f, abs(tf_iff_g10), '-', 'DisplayName', 'g=10')
plot(f, abs(tf_iff_g100), '-', 'DisplayName', 'g=100')
set(gca, 'Xscale', 'log'); set(gca, 'Yscale', 'log');
ylabel('Amplitude'); set(gca, 'XTickLabel',[]);
hold off;
legend();
ax2 = nexttile;
hold on;
plot(f, 180/pi*angle(-tf_iff_of), '-')
plot(f, 180/pi*angle(-tf_iff_g10), '-')
plot(f, 180/pi*angle(-tf_iff_g100), '-')
set(gca, 'Xscale', 'log'); set(gca, 'Yscale', 'lin');
ylabel('Phase'); xlabel('Frequency [Hz]');
hold off;
yticks(-360:90:360);
linkaxes([ax1,ax2], 'x');
xlim([60, 600]);
% Second test with many Gains
% Then, the same identification test is performed for many more gains.
load('apa95ml_iff_test.mat', 'results');
Ts = 1e-4;
win = hann(ceil(10/Ts));
tf_iff = {zeros(1, length(results))};
co_iff = {zeros(1, length(results))};
g_iff = [0, 1, 5, 10, 50, 100];
for i=1:length(results)
[tf_est, f] = tfestimate(results{i}.u, results{i}.y, win, [], [], 1/Ts);
[co_est, ~] = mscohere(results{i}.u, results{i}.y, win, [], [], 1/Ts);
tf_iff(i) = {tf_est};
co_iff(i) = {co_est};
end
% The obtained dynamics are shown in Figure [[fig:iff_results_bode_plots]].
figure;
tiledlayout(3, 1, 'TileSpacing', 'None', 'Padding', 'None');
ax1 = nexttile([2, 1]);
hold on;
for i = 1:length(results)
plot(f, abs(tf_iff{i}), '-', 'DisplayName', sprintf('g = %0.f', g_iff(i)))
end
set(gca, 'Xscale', 'log'); set(gca, 'Yscale', 'log');
ylabel('Amplitude'); set(gca, 'XTickLabel',[]);
hold off;
legend();
ax2 = nexttile;
hold on;
for i = 1:length(results)
plot(f, 180/pi*angle(-tf_iff{i}), '-')
end
set(gca, 'Xscale', 'log'); set(gca, 'Yscale', 'lin');
ylabel('Phase'); xlabel('Frequency [Hz]');
yticks(-360:90:360);
axis padded 'auto x'
linkaxes([ax1,ax2], 'x');
xlim([60, 600]);
% #+name: fig:iff_results_bode_plots
% #+caption: Identified dynamics from excitation voltage to the mass displacement
% #+RESULTS:
% [[file:figs/iff_results_bode_plots.png]]
% For each gain, the parameters of a second order resonant system that best fits the data are estimated and are compared with the data in Figure [[fig:iff_results_bode_plots_identification]].
G_id = {zeros(1,length(results))};
f_start = 70; % [Hz]
f_end = 500; % [Hz]
for i = 1:length(results)
tf_id = tf_iff{i}(sum(f<f_start):length(f)-sum(f>f_end));
f_id = f(sum(f<f_start):length(f)-sum(f>f_end));
gfr = idfrd(tf_id, 2*pi*f_id, Ts);
G_id(i) = {procest(gfr,'P2UDZ')};
end
figure;
tiledlayout(3, 1, 'TileSpacing', 'None', 'Padding', 'None');
ax1 = nexttile([2, 1]);
hold on;
for i = 1:length(results)
set(gca,'ColorOrderIndex',i)
plot(f, abs(tf_iff{i}), '-', 'DisplayName', sprintf('g = %0.f', g_iff(i)))
set(gca,'ColorOrderIndex',i)
plot(f, abs(squeeze(freqresp(G_id{i}, f, 'Hz'))), '--', 'HandleVisibility', 'off')
end
set(gca, 'Xscale', 'log'); set(gca, 'Yscale', 'log');
ylabel('Amplitude'); set(gca, 'XTickLabel',[]);
hold off;
legend();
ax2 = nexttile;
hold on;
for i = 1:length(results)
set(gca,'ColorOrderIndex',i)
plot(f, 180/pi*angle(tf_iff{i}), '-')
set(gca,'ColorOrderIndex',i)
plot(f, 180/pi*angle(squeeze(freqresp(G_id{i}, f, 'Hz'))), '--')
end
set(gca, 'Xscale', 'log'); set(gca, 'Yscale', 'lin');
ylabel('Phase'); xlabel('Frequency [Hz]');
hold off;
yticks(-360:90:360);
axis padded 'auto x'
linkaxes([ax1,ax2], 'x');
xlim([60, 600]);
% #+name: fig:iff_results_bode_plots_identification
% #+caption: Comparison of the measured dynamic and the identified 2nd order resonant systems that best fits the data
% #+RESULTS:
% [[file:figs/iff_results_bode_plots_identification.png]]
% Finally, we can represent the position of the poles of the 2nd order systems on the Root Locus plot (Figure [[fig:iff_results_root_locus]]).
w_z = 2*pi*111; % Zeros frequency [rad/s]
w_p = 2*pi*255; % Pole frequency [rad/s]
xi_z = 0.05;
xi_p = 0.015;
G_inf = 2;
Gi = G_inf*(s^2 - 2*xi_z*w_z*s + w_z^2)/(s^2 + 2*xi_p*w_p*s + w_p^2);
gains = logspace(0, 5, 1000);
figure;
hold on;
plot(real(pole(Gi)), imag(pole(Gi)), 'kx', 'HandleVisibility', 'off');
plot(real(tzero(Gi)), imag(tzero(Gi)), 'ko', 'HandleVisibility', 'off');
for i = 1:length(results)
set(gca,'ColorOrderIndex',i)
plot(real(pole(G_id{i})), imag(pole(G_id{i})), 'o', 'DisplayName', sprintf('g = %0.f', g_iff(i)));
end
for i = 1:length(gains)
cl_poles = pole(feedback(Gi, (gains(i)/(s + 2*pi*2))));
plot(real(cl_poles), imag(cl_poles), 'k.', 'HandleVisibility', 'off');
end
ylim([0, 1800]);
xlim([-1600,200]);
xlabel('Real Part')
ylabel('Imaginary Part')
axis square
legend('location', 'northwest');

Binary file not shown.

View File

@@ -0,0 +1,100 @@
%% Clear Workspace and Close figures
clear; close all; clc;
%% Intialize Laplace variable
s = zpk('s');
addpath('./mat/');
% Load Data
% The data from the "noise test" and the identification test are loaded.
ht = load('huddle_test.mat', 't', 'y');
load('apa95ml_5kg_Amp_E505.mat', 't', 'um', 'y');
% Any offset value is removed:
um = detrend(um, 0); % Generated DAC Voltage [V]
y = detrend(y , 0); % Mass displacement [m]
ht.y = detrend(ht.y, 0);
% Now we add a factor 10 to take into account the gain of the voltage amplifier.
Va = 10*um;
% Comparison of the PSD with Huddle Test
Ts = t(end)/(length(t)-1);
Fs = 1/Ts;
win = hanning(ceil(1*Fs));
[pxx, f] = pwelch(y, win, [], [], Fs);
[pht, ~] = pwelch(ht.y, win, [], [], Fs);
figure;
hold on;
plot(f, sqrt(pxx), 'DisplayName', '5kg');
plot(f, sqrt(pht), 'DisplayName', 'Huddle Test');
hold off;
set(gca, 'XScale', 'log'); set(gca, 'YScale', 'log');
xlabel('Frequency [Hz]'); ylabel('ASD [$m/\sqrt{Hz}$]');
legend('location', 'northeast');
xlim([1, Fs/2]);
% Compute TF estimate and Coherence
[tf_est, f] = tfestimate(Va, -y, win, [], [], 1/Ts);
[co_est, ~] = mscohere( Va, -y, win, [], [], 1/Ts);
figure;
hold on;
plot(f, co_est, 'k-')
set(gca, 'Xscale', 'log'); set(gca, 'Yscale', 'lin');
ylabel('Coherence'); xlabel('Frequency [Hz]');
hold off;
xlim([10, 5e3]);
% #+name: fig:apa95ml_5kg_PI_coh
% #+caption: Coherence
% #+RESULTS:
% [[file:figs/apa95ml_5kg_PI_coh.png]]
% Comparison with the FEM model
load('mat/fem_simscape_models.mat', 'Ghm');
freqs = logspace(0, 4, 1000);
figure;
tiledlayout(3, 1, 'TileSpacing', 'None', 'Padding', 'None');
ax1 = nexttile([2,1]);
hold on;
plot(f, abs(tf_est), 'DisplayName', 'Identification')
plot(freqs, abs(squeeze(freqresp(Ghm, freqs, 'Hz'))), 'DisplayName', 'FEM')
set(gca, 'Xscale', 'log'); set(gca, 'Yscale', 'log');
ylabel('Amplitude [m/V]'); set(gca, 'XTickLabel', []);
legend('location', 'northeast')
hold off;
ax2 = nexttile;
hold on;
plot(f, 180/pi*angle(tf_est))
plot(freqs, 180/pi*angle(squeeze(freqresp(Ghm, freqs, 'Hz'))))
set(gca, 'Xscale', 'log'); set(gca, 'Yscale', 'lin');
ylabel('Phase'); xlabel('Frequency [Hz]');
hold off;
ylim([-180, 180]);
yticks(-180:90:180);
linkaxes([ax1,ax2], 'x');
xlim([10, 5e3]);

Binary file not shown.

View File

@@ -38,7 +38,7 @@ ka = 235e6; % [N/m]
Lmax = 20e-6; % [m]
Vmax = 170; % [V]
ka*Lmax/Vmax % [N/V]
ga = ka*Lmax/Vmax; % [N/V]
% Estimation from Piezoelectric parameters
% <<sec:estimation_piezo_params>>

120
matlab/simscape_model.m Normal file
View File

@@ -0,0 +1,120 @@
%% Clear Workspace and Close figures
clear; close all; clc;
%% Intialize Laplace variable
s = zpk('s');
addpath('./mat/');
% Import Mass Matrix, Stiffness Matrix, and Interface Nodes Coordinates
% We first extract the stiffness and mass matrices.
K = readmatrix('APA95ML_K.CSV');
M = readmatrix('APA95ML_M.CSV');
% #+caption: First 10x10 elements of the Mass matrix
% #+RESULTS:
% | 0.03 | 7e-08 | 2e-06 | -3e-09 | -0.0002 | -6e-08 | -0.001 | 8e-07 | 6e-07 | -8e-09 |
% | 7e-08 | 0.02 | -1e-06 | 9e-05 | -3e-09 | -4e-09 | -1e-06 | -0.0006 | -4e-08 | 5e-06 |
% | 2e-06 | -1e-06 | 0.02 | -3e-08 | -4e-08 | 1e-08 | 1e-07 | -2e-07 | 0.0003 | 1e-09 |
% | -3e-09 | 9e-05 | -3e-08 | 1e-06 | -3e-11 | -3e-13 | -7e-09 | -5e-06 | -3e-10 | 3e-08 |
% | -0.0002 | -3e-09 | -4e-08 | -3e-11 | 2e-06 | 6e-10 | 2e-06 | -7e-09 | -2e-09 | 7e-11 |
% | -6e-08 | -4e-09 | 1e-08 | -3e-13 | 6e-10 | 1e-06 | 1e-08 | 3e-09 | -2e-09 | 2e-13 |
% | -0.001 | -1e-06 | 1e-07 | -7e-09 | 2e-06 | 1e-08 | 0.03 | 4e-08 | -2e-06 | 8e-09 |
% | 8e-07 | -0.0006 | -2e-07 | -5e-06 | -7e-09 | 3e-09 | 4e-08 | 0.02 | -9e-07 | -9e-05 |
% | 6e-07 | -4e-08 | 0.0003 | -3e-10 | -2e-09 | -2e-09 | -2e-06 | -9e-07 | 0.02 | 2e-08 |
% | -8e-09 | 5e-06 | 1e-09 | 3e-08 | 7e-11 | 2e-13 | 8e-09 | -9e-05 | 2e-08 | 1e-06 |
% Then, we extract the coordinates of the interface nodes.
[int_xyz, int_i, n_xyz, n_i, nodes] = extractNodes('APA95ML_out_nodes_3D.txt');
% Simscape Model
% The flexible element is imported using the =Reduced Order Flexible Solid= Simscape block.
% To model the actuator, an =Internal Force= block is added between the nodes 3 and 12.
% A =Relative Motion Sensor= block is added between the nodes 1 and 2 to measure the displacement and the amplified piezo.
% One mass is fixed at one end of the piezo-electric stack actuator, the other end is fixed to the world frame.
m = 5.5;
load('apa95ml_params.mat', 'ga', 'gs');
% Dynamics from Actuator Voltage to Vertical Mass Displacement
% The identified dynamics is shown in Figure [[fig:dynamics_act_disp_comp_mass]].
%% Name of the Simulink File
mdl = 'piezo_amplified_3d';
%% Input/Output definition
clear io; io_i = 1;
io(io_i) = linio([mdl, '/Va'], 1, 'openinput'); io_i = io_i + 1; % Actuator Voltage [V]
io(io_i) = linio([mdl, '/y'], 1, 'openoutput'); io_i = io_i + 1; % Vertical Displacement [m]
Ghm = linearize(mdl, io);
freqs = logspace(0, 4, 5000);
figure;
tiledlayout(3, 1, 'TileSpacing', 'None', 'Padding', 'None');
ax1 = nexttile([2,1]);
hold on;
plot(freqs, abs(squeeze(freqresp(Ghm, freqs, 'Hz'))));
hold off;
set(gca, 'XScale', 'log'); set(gca, 'YScale', 'log');
ylabel('Amplitude $d/V_a$ [m/V]'); set(gca, 'XTickLabel',[]);
hold off;
ax2 = nexttile;
hold on;
plot(freqs, 180/pi*unwrap(angle(squeeze(freqresp(Ghm, freqs, 'Hz')))));
set(gca, 'XScale', 'log'); set(gca, 'YScale', 'lin');
yticks(-360:90:360);
xlabel('Frequency [Hz]'); ylabel('Phase [deg]');
hold off;
linkaxes([ax1,ax2],'x');
xlim([freqs(1), freqs(end)]);
% Dynamics from Actuator Voltage to Force Sensor Voltage
% The obtained dynamics is shown in Figure [[fig:dynamics_force_force_sensor_comp_mass]].
%% Name of the Simulink File
mdl = 'piezo_amplified_3d';
%% Input/Output definition
clear io; io_i = 1;
io(io_i) = linio([mdl, '/Va'], 1, 'openinput'); io_i = io_i + 1; % Voltage Actuator [V]
io(io_i) = linio([mdl, '/Vs'], 1, 'openoutput'); io_i = io_i + 1; % Sensor Voltage [V]
Gfm = linearize(mdl, io);
freqs = logspace(1, 5, 1000);
figure;
tiledlayout(3, 1, 'TileSpacing', 'None', 'Padding', 'None');
ax1 = nexttile([2,1]);
hold on;
plot(freqs, abs(squeeze(freqresp(Gfm, freqs, 'Hz'))));
hold off;
set(gca, 'XScale', 'log'); set(gca, 'YScale', 'log');
ylabel('Amplitude $V_s/V_a$ [V/V]'); set(gca, 'XTickLabel',[]);
hold off;
ax2 = nexttile;
hold on;
plot(freqs, 180/pi*unwrap(angle(squeeze(freqresp(Gfm, freqs, 'Hz')))));
set(gca, 'XScale', 'log'); set(gca, 'YScale', 'lin');
yticks(-360:90:360); ylim([-360, 180]);
xlabel('Frequency [Hz]'); ylabel('Phase [deg]');
hold off;
linkaxes([ax1,ax2],'x');
xlim([freqs(1), freqs(end)]);
save('mat/fem_simscape_models.mat', 'Ghm', 'Gfm')