2025-02-06 17:05:29 +01:00
|
|
|
% Matlab Init :noexport:ignore:
|
|
|
|
|
|
|
|
%% test_id31_5_experiments.m
|
|
|
|
|
2025-02-04 14:10:54 +01:00
|
|
|
%% Clear Workspace and Close figures
|
|
|
|
clear; close all; clc;
|
|
|
|
|
|
|
|
%% Intialize Laplace variable
|
|
|
|
s = zpk('s');
|
|
|
|
|
|
|
|
%% Path for functions, data and scripts
|
|
|
|
addpath('./mat/'); % Path for Data
|
|
|
|
addpath('./src/'); % Path for functions
|
|
|
|
addpath('./STEPS/'); % Path for STEPS
|
|
|
|
addpath('./subsystems/'); % Path for Subsystems Simulink files
|
|
|
|
|
|
|
|
%% Data directory
|
|
|
|
data_dir = './mat/';
|
|
|
|
|
|
|
|
%% Colors for the figures
|
|
|
|
colors = colororder;
|
|
|
|
|
|
|
|
%% Frequency Vector
|
|
|
|
freqs = logspace(log10(1), log10(2e3), 1000);
|
|
|
|
|
|
|
|
%% Sampling Time
|
|
|
|
Ts = 1e-4;
|
|
|
|
|
|
|
|
%% Specifications for Experiments
|
|
|
|
specs_dz_peak = 50; % [nm]
|
|
|
|
specs_dy_peak = 100; % [nm]
|
|
|
|
specs_ry_peak = 0.85; % [urad]
|
|
|
|
specs_dz_rms = 15; % [nm RMS]
|
|
|
|
specs_dy_rms = 30; % [nm RMS]
|
|
|
|
specs_ry_rms = 0.25; % [urad RMS]
|
|
|
|
|
|
|
|
% Slow Tomography scans
|
|
|
|
|
2025-02-06 17:05:29 +01:00
|
|
|
% First, tomography scans were performed with a rotational velocity of $6\,\text{deg/s}$ for all considered payload masses (shown in Figure ref:fig:test_id31_picture_masses).
|
2025-02-04 14:10:54 +01:00
|
|
|
% Each experimental sequence consisted of two complete spindle rotations: an initial open-loop rotation followed by a closed-loop rotation.
|
|
|
|
% The experimental results for the $26\,\text{kg}$ payload are presented in Figure ref:fig:test_id31_tomo_m2_1rpm_robust_hac_iff_fit.
|
|
|
|
|
2025-02-06 17:05:29 +01:00
|
|
|
% Due to the static deformation of the micro-station stages under payload loading, a significant eccentricity was observed between the point of interest and the spindle rotation axis.
|
|
|
|
% To establish a theoretical lower bound for open-loop errors, an ideal scenario was assumed, where the point of interest perfectly aligns with the spindle rotation axis.
|
2025-02-04 14:10:54 +01:00
|
|
|
% This idealized case was simulated by first calculating the eccentricity through circular fitting (represented by the dashed black circle in Figure ref:fig:test_id31_tomo_m2_1rpm_robust_hac_iff_fit), and then subtracting it from the measured data, as shown in Figure ref:fig:test_id31_tomo_m2_1rpm_robust_hac_iff_fit_removed.
|
|
|
|
% While this approach likely underestimates actual open-loop errors, as perfect alignment is practically unattainable, it enables a more balanced comparison with closed-loop performance.
|
|
|
|
|
|
|
|
|
|
|
|
%% Load Tomography scans with robust controller
|
|
|
|
data_tomo_m0_Wz6 = load("2023-08-11_11-37_tomography_1rpm_m0.mat");
|
|
|
|
data_tomo_m0_Wz6.time = Ts*[0:length(data_tomo_m0_Wz6.Rz)-1];
|
|
|
|
|
|
|
|
data_tomo_m1_Wz6 = load("2023-08-11_11-15_tomography_1rpm_m1.mat");
|
|
|
|
data_tomo_m1_Wz6.time = Ts*[0:length(data_tomo_m1_Wz6.Rz)-1];
|
|
|
|
|
|
|
|
data_tomo_m2_Wz6 = load("2023-08-11_10-59_tomography_1rpm_m2.mat");
|
|
|
|
data_tomo_m2_Wz6.time = Ts*[0:length(data_tomo_m2_Wz6.Rz)-1];
|
|
|
|
|
|
|
|
data_tomo_m3_Wz6 = load("2023-08-11_10-24_tomography_1rpm_m3.mat");
|
|
|
|
data_tomo_m3_Wz6.time = Ts*[0:length(data_tomo_m3_Wz6.Rz)-1];
|
|
|
|
|
|
|
|
%% Find best circle fit for all experiments
|
|
|
|
[~, i_m0] = find(data_tomo_m0_Wz6.hac_status == 1);
|
|
|
|
[x_m0, y_m0, R_m0] = circlefit(data_tomo_m0_Wz6.Dx_int(1:i_m0), data_tomo_m0_Wz6.Dy_int(1:i_m0));
|
|
|
|
fun = @(theta)rms((data_tomo_m0_Wz6.Dx_int(1:i_m0) - (x_m0 + R_m0*cos(data_tomo_m0_Wz6.Rz(1:i_m0)+theta(1)))).^2 + ...
|
|
|
|
(data_tomo_m0_Wz6.Dy_int(1:i_m0) - (y_m0 + R_m0*sin(data_tomo_m0_Wz6.Rz(1:i_m0)+theta(1)))).^2);
|
|
|
|
delta_theta_m0 = fminsearch(fun, 0);
|
|
|
|
|
|
|
|
[~, i_m1] = find(data_tomo_m1_Wz6.hac_status == 1);
|
|
|
|
[x_m1, y_m1, R_m1] = circlefit(data_tomo_m1_Wz6.Dx_int(1:i_m1), data_tomo_m1_Wz6.Dy_int(1:i_m1));
|
|
|
|
fun = @(theta)rms((data_tomo_m1_Wz6.Dx_int(1:i_m1) - (x_m1 + R_m1*cos(data_tomo_m1_Wz6.Rz(1:i_m1)+theta(1)))).^2 + ...
|
|
|
|
(data_tomo_m1_Wz6.Dy_int(1:i_m1) - (y_m1 + R_m1*sin(data_tomo_m1_Wz6.Rz(1:i_m1)+theta(1)))).^2);
|
|
|
|
delta_theta_m1 = fminsearch(fun, 0);
|
|
|
|
|
|
|
|
[~, i_m2] = find(data_tomo_m2_Wz6.hac_status == 1);
|
|
|
|
[x_m2, y_m2, R_m2] = circlefit(data_tomo_m2_Wz6.Dx_int(1:i_m2), data_tomo_m2_Wz6.Dy_int(1:i_m2));
|
|
|
|
fun = @(theta)rms((data_tomo_m2_Wz6.Dx_int(1:i_m2) - (x_m2 + R_m2*cos(data_tomo_m2_Wz6.Rz(1:i_m2)+theta(1)))).^2 + ...
|
|
|
|
(data_tomo_m2_Wz6.Dy_int(1:i_m2) - (y_m2 + R_m2*sin(data_tomo_m2_Wz6.Rz(1:i_m2)+theta(1)))).^2);
|
|
|
|
delta_theta_m2 = fminsearch(fun, 0);
|
|
|
|
|
|
|
|
[~, i_m3] = find(data_tomo_m3_Wz6.hac_status == 1);
|
|
|
|
[x_m3, y_m3, R_m3] = circlefit(data_tomo_m3_Wz6.Dx_int(1:i_m3), data_tomo_m3_Wz6.Dy_int(1:i_m3));
|
|
|
|
fun = @(theta)rms((data_tomo_m3_Wz6.Dx_int(1:i_m3) - (x_m3 + R_m3*cos(data_tomo_m3_Wz6.Rz(1:i_m3)+theta(1)))).^2 + ...
|
|
|
|
(data_tomo_m3_Wz6.Dy_int(1:i_m3) - (y_m3 + R_m3*sin(data_tomo_m3_Wz6.Rz(1:i_m3)+theta(1)))).^2);
|
|
|
|
delta_theta_m3 = fminsearch(fun, 0);
|
|
|
|
|
|
|
|
%% Tomography experiment at 1rpm with 26kg payload
|
|
|
|
figure;
|
|
|
|
hold on;
|
|
|
|
plot(1e6*data_tomo_m2_Wz6.Dx_int(1:10:i_m2), 1e6*data_tomo_m2_Wz6.Dy_int(1:10:i_m2), 'DisplayName', '$m = 26$ kg (OL)')
|
|
|
|
plot(1e6*data_tomo_m2_Wz6.Dx_int(i_m2:10:i_m2+1e4), 1e6*data_tomo_m2_Wz6.Dy_int(i_m2:10:i_m2+1e4), 'color', colors(3,:), 'HandleVisibility', 'off')
|
|
|
|
plot(1e6*data_tomo_m2_Wz6.Dx_int(i_m2+1e4:10:end), 1e6*data_tomo_m2_Wz6.Dy_int(i_m2+1e4:10:end), 'color', colors(2,:), 'DisplayName', '$m = 26$ kg (CL)')
|
|
|
|
theta = linspace(0, 2*pi, 500); % Angle to plot the circle [rad]
|
|
|
|
plot(1e6*(x_m2 + R_m2*cos(theta)), 1e6*(y_m2 + R_m2*sin(theta)), 'k--', 'DisplayName', 'Best Circular Fit')
|
|
|
|
hold off;
|
|
|
|
xlabel('$D_x$ [$\mu$m]'); ylabel('$D_y$ [$\mu$m]');
|
|
|
|
axis equal
|
|
|
|
xlim([-20, 100]); ylim([-20, 100]);
|
|
|
|
xticks([-20:20:100]);
|
|
|
|
yticks([-20:20:100]);
|
|
|
|
leg = legend('location', 'northeast', 'FontSize', 8, 'NumColumns', 1);
|
|
|
|
leg.ItemTokenSize(1) = 15;
|
|
|
|
|
|
|
|
%% Measured radial errors of the Spindle
|
|
|
|
figure;
|
|
|
|
hold on;
|
|
|
|
plot(1e6*(data_tomo_m2_Wz6.Dx_int(1:10:i_m2) - (x_m2 + R_m2*cos(data_tomo_m2_Wz6.Rz(1:10:i_m2)+delta_theta_m2))), ...
|
|
|
|
1e6*(data_tomo_m2_Wz6.Dy_int(1:10:i_m2) - (y_m2 + R_m2*sin(data_tomo_m2_Wz6.Rz(1:10:i_m2)+delta_theta_m2))), 'color', colors(1,:), 'DisplayName', '$m = 26$ kg (OL)')
|
|
|
|
plot(1e6*detrend(data_tomo_m2_Wz6.Dx_int(i_m2+1e4:10:end), 0), 1e6*detrend(data_tomo_m2_Wz6.Dy_int(i_m2+1e4:10:end), 0), 'color', colors(2,:), 'DisplayName', '$m = 26$ kg (CL)')
|
|
|
|
hold off;
|
|
|
|
xlabel('$D_x$ [$\mu$m]'); ylabel('$D_y$ [$\mu$m]');
|
|
|
|
axis equal
|
|
|
|
xlim([-0.6, 0.4]); ylim([-0.4, 0.6]);
|
|
|
|
xticks([-0.6:0.2:0.4]);
|
|
|
|
yticks([-0.4:0.2:0.6]);
|
|
|
|
leg = legend('location', 'northeast', 'FontSize', 8, 'NumColumns', 1);
|
|
|
|
leg.ItemTokenSize(1) = 15;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
% #+name: fig:test_id31_tomo_m2_1rpm_robust_hac_iff
|
2025-02-06 17:05:29 +01:00
|
|
|
% #+caption: Tomography experiment with a rotation velocity of $6\,\text{deg/s}$, and payload mass of 26kg. Errors in the $(x,y)$ plane are shown in (\subref{fig:test_id31_tomo_m2_1rpm_robust_hac_iff_fit}). The estimated eccentricity is represented by the black dashed circle. The errors with subtracted eccentricity are shown in (\subref{fig:test_id31_tomo_m2_1rpm_robust_hac_iff_fit_removed}).
|
2025-02-04 14:10:54 +01:00
|
|
|
% #+attr_latex: :options [htbp]
|
|
|
|
% #+begin_figure
|
|
|
|
% #+attr_latex: :caption \subcaption{\label{fig:test_id31_tomo_m2_1rpm_robust_hac_iff_fit}Errors in $(x,y)$ plane}
|
|
|
|
% #+attr_latex: :options {0.49\textwidth}
|
|
|
|
% #+begin_subfigure
|
|
|
|
% #+attr_latex: :scale 0.9
|
|
|
|
% [[file:figs/test_id31_tomo_m2_1rpm_robust_hac_iff_fit.png]]
|
|
|
|
% #+end_subfigure
|
|
|
|
% #+attr_latex: :caption \subcaption{\label{fig:test_id31_tomo_m2_1rpm_robust_hac_iff_fit_removed}Removed eccentricity}
|
|
|
|
% #+attr_latex: :options {0.49\textwidth}
|
|
|
|
% #+begin_subfigure
|
|
|
|
% #+attr_latex: :scale 0.9
|
|
|
|
% [[file:figs/test_id31_tomo_m2_1rpm_robust_hac_iff_fit_removed.png]]
|
|
|
|
% #+end_subfigure
|
|
|
|
% #+end_figure
|
|
|
|
|
2025-02-06 17:05:29 +01:00
|
|
|
% The residual motion (i.e. after compensating for eccentricity) in the $Y-Z$ is compared against the minimum beam size, as illustrated in Figure ref:fig:test_id31_tomo_Wz36_results.
|
|
|
|
% Results are indicating the NASS succeeds in keeping the sample's point of interest on the beam, except for the highest mass of $39\,\text{kg}$ for which the lateral motion is a bit too high.
|
|
|
|
% These experimental findings are consistent with the predictions from the tomography simulations presented in Section ref:ssec:test_id31_iff_hac_robustness.
|
2025-02-04 14:10:54 +01:00
|
|
|
|
|
|
|
|
|
|
|
%% Tomography experiment at 1rpm - Results in the YZ - All masses tested
|
|
|
|
figure;
|
|
|
|
tiledlayout(2, 2, 'TileSpacing', 'compact', 'Padding', 'None');
|
|
|
|
|
|
|
|
ax1 = nexttile;
|
|
|
|
hold on;
|
|
|
|
plot(1e9*detrend(data_tomo_m0_Wz6.Dy_int(1:10:i_m0) - y_m0 - R_m0*sin(data_tomo_m0_Wz6.Rz(1:10:i_m0)+delta_theta_m0), 0), 1e9*detrend(data_tomo_m0_Wz6.Dz_int(1:10:i_m0), 0), 'DisplayName', 'OL')
|
|
|
|
plot(1e9*detrend(data_tomo_m0_Wz6.Dy_int(i_m0+1e4:10:end), 0), 1e9*detrend(data_tomo_m0_Wz6.Dz_int(i_m0+1e4:10:end), 0), 'DisplayName', 'CL')
|
|
|
|
theta = linspace(0, 2*pi, 500); % Angle to plot the circle [rad]
|
|
|
|
plot(100*cos(theta), 50*sin(theta), 'k--', 'DisplayName', 'Beam')
|
|
|
|
text(-430, 90, '$m = 0$ kg', 'Horiz','left', 'Vert','top', 'FontWeight', 'bold')
|
|
|
|
hold off;
|
|
|
|
xlabel('$D_y$ [nm]'); ylabel('$D_z$ [nm]');
|
|
|
|
axis equal
|
|
|
|
xlim([-450, 450]); ylim([-100, 100]);
|
|
|
|
xticks([-400:100:400]); yticks([-50:50:50]);
|
|
|
|
leg = legend('location', 'southeast', 'FontSize', 8, 'NumColumns', 1);
|
|
|
|
leg.ItemTokenSize(1) = 15;
|
|
|
|
|
|
|
|
ax2 = nexttile;
|
|
|
|
hold on;
|
|
|
|
plot(1e9*detrend(data_tomo_m1_Wz6.Dy_int(1:10:i_m1) - y_m1 - R_m1*sin(data_tomo_m1_Wz6.Rz(1:10:i_m1)+delta_theta_m1), 0), 1e9*detrend(data_tomo_m1_Wz6.Dz_int(1:10:i_m1), 0), 'DisplayName', 'OL')
|
|
|
|
plot(1e9*detrend(data_tomo_m1_Wz6.Dy_int(i_m1+1e4:10:end), 0), 1e9*detrend(data_tomo_m1_Wz6.Dz_int(i_m1+1e4:10:end), 0), 'DisplayName', 'CL')
|
|
|
|
theta = linspace(0, 2*pi, 500); % Angle to plot the circle [rad]
|
|
|
|
plot(100*cos(theta), 50*sin(theta), 'k--', 'HandleVisibility', 'off')
|
|
|
|
text(-430, 90, '$m = 13$ kg', 'Horiz','left', 'Vert','top', 'FontWeight', 'bold')
|
|
|
|
hold off;
|
|
|
|
xlabel('$D_y$ [nm]'); ylabel('$D_z$ [nm]');
|
|
|
|
axis equal
|
|
|
|
xlim([-450, 450]); ylim([-100, 100]);
|
|
|
|
xticks([-400:100:400]); yticks([-50:50:50]);
|
|
|
|
leg = legend('location', 'southeast', 'FontSize', 8, 'NumColumns', 1);
|
|
|
|
leg.ItemTokenSize(1) = 15;
|
|
|
|
|
|
|
|
ax3 = nexttile;
|
|
|
|
hold on;
|
|
|
|
plot(1e9*detrend(data_tomo_m2_Wz6.Dy_int(1:10:i_m2) - y_m2 - R_m2*sin(data_tomo_m2_Wz6.Rz(1:10:i_m2)+delta_theta_m2), 0), 1e9*detrend(data_tomo_m2_Wz6.Dz_int(1:10:i_m2), 0), 'DisplayName', 'OL')
|
|
|
|
plot(1e9*detrend(data_tomo_m2_Wz6.Dy_int(i_m2+1e4:10:end), 0), 1e9*detrend(data_tomo_m2_Wz6.Dz_int(i_m2+1e4:10:end), 0), 'DisplayName', 'CL')
|
|
|
|
theta = linspace(0, 2*pi, 500); % Angle to plot the circle [rad]
|
|
|
|
plot(100*cos(theta), 50*sin(theta), 'k--', 'HandleVisibility', 'off')
|
|
|
|
text(-430, 90, '$m = 26$ kg', 'Horiz','left', 'Vert','top', 'FontWeight', 'bold')
|
|
|
|
hold off;
|
|
|
|
xlabel('$D_y$ [nm]'); ylabel('$D_z$ [nm]');
|
|
|
|
axis equal
|
|
|
|
xlim([-450, 450]); ylim([-100, 100]);
|
|
|
|
xticks([-400:100:400]); yticks([-50:50:50]);
|
|
|
|
leg = legend('location', 'southeast', 'FontSize', 8, 'NumColumns', 1);
|
|
|
|
leg.ItemTokenSize(1) = 15;
|
|
|
|
|
|
|
|
ax4 = nexttile;
|
|
|
|
hold on;
|
|
|
|
plot(1e9*detrend(data_tomo_m3_Wz6.Dy_int(1:10:i_m3) - y_m3 - R_m3*sin(data_tomo_m3_Wz6.Rz(1:10:i_m3)+delta_theta_m3), 0), 1e9*detrend(data_tomo_m3_Wz6.Dz_int(1:10:i_m3), 0), 'DisplayName', 'OL')
|
|
|
|
plot(1e9*detrend(data_tomo_m3_Wz6.Dy_int(i_m3+1e4:10:end), 0), 1e9*detrend(data_tomo_m3_Wz6.Dz_int(i_m3+1e4:10:end), 0), 'DisplayName', 'CL')
|
|
|
|
theta = linspace(0, 2*pi, 500); % Angle to plot the circle [rad]
|
|
|
|
plot(100*cos(theta), 50*sin(theta), 'k--', 'HandleVisibility', 'off')
|
|
|
|
text(-860, 180, '$m = 39$ kg', 'Horiz','left', 'Vert','top', 'FontWeight', 'bold')
|
|
|
|
hold off;
|
|
|
|
xlabel('$D_y$ [nm]'); ylabel('$D_z$ [nm]');
|
|
|
|
axis equal
|
|
|
|
xlim([-900, 900]); ylim([-200, 200]);
|
|
|
|
xticks([-800:200:800]); yticks([-100:100:100]);
|
|
|
|
leg = legend('location', 'southeast', 'FontSize', 8, 'NumColumns', 1);
|
|
|
|
leg.ItemTokenSize(1) = 15;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
% #+name: fig:test_id31_tomo_Wz36_results
|
|
|
|
% #+caption: Measured errors in the $Y-Z$ plane during tomography experiments at $6\,\text{deg/s}$ for all considered payloads. In the open-loop case, the effect of eccentricity is removed from the data.
|
|
|
|
% #+RESULTS:
|
|
|
|
% [[file:figs/test_id31_tomo_Wz36_results.png]]
|
|
|
|
|
|
|
|
|
|
|
|
%% Estimate RMS of the errors while in closed-loop and open-loop - Tomography at 6deg/s
|
|
|
|
% No mass
|
|
|
|
data_tomo_m0_Wz6.Dy_rms_cl = rms(detrend(data_tomo_m0_Wz6.Dy_int(i_m0+1e4:end), 0));
|
|
|
|
data_tomo_m0_Wz6.Dz_rms_cl = rms(detrend(data_tomo_m0_Wz6.Dz_int(i_m0+1e4:end), 0));
|
|
|
|
data_tomo_m0_Wz6.Ry_rms_cl = rms(detrend(data_tomo_m0_Wz6.Ry_int(i_m0+1e4:end), 0));
|
|
|
|
|
|
|
|
% Remove eccentricity for OL errors
|
|
|
|
data_tomo_m0_Wz6.Dy_rms_ol = rms(data_tomo_m0_Wz6.Dy_int(1:i_m0) - (y_m0 + R_m0*sin(data_tomo_m0_Wz6.Rz(1:i_m0)+delta_theta_m0)));
|
|
|
|
data_tomo_m0_Wz6.Dz_rms_ol = rms(detrend(data_tomo_m0_Wz6.Dz_int(1:i_m0), 0));
|
|
|
|
[x0, y0, R] = circlefit(data_tomo_m0_Wz6.Rx_int(1:i_m0), data_tomo_m0_Wz6.Ry_int(1:i_m0));
|
|
|
|
fun = @(theta)rms((data_tomo_m0_Wz6.Rx_int(1:i_m0) - (x0 + R*cos(data_tomo_m0_Wz6.Rz(1:i_m0)+theta(1)))).^2 + ...
|
|
|
|
(data_tomo_m0_Wz6.Ry_int(1:i_m0) - (y0 + R*sin(data_tomo_m0_Wz6.Rz(1:i_m0)+theta(1)))).^2);
|
|
|
|
delta_theta = fminsearch(fun, 0);
|
|
|
|
data_tomo_m0_Wz6.Ry_rms_ol = rms(data_tomo_m0_Wz6.Ry_int(1:i_m0) - (y0 + R*sin(data_tomo_m0_Wz6.Rz(1:i_m0)+delta_theta)));
|
|
|
|
|
|
|
|
% 1 "layer mass"
|
|
|
|
data_tomo_m1_Wz6.Dy_rms_cl = rms(detrend(data_tomo_m1_Wz6.Dy_int(i_m1+1e4:end), 0));
|
|
|
|
data_tomo_m1_Wz6.Dz_rms_cl = rms(detrend(data_tomo_m1_Wz6.Dz_int(i_m1+1e4:end), 0));
|
|
|
|
data_tomo_m1_Wz6.Ry_rms_cl = rms(detrend(data_tomo_m1_Wz6.Ry_int(i_m1+1e4:end), 0));
|
|
|
|
|
|
|
|
% Remove eccentricity for OL errors
|
|
|
|
data_tomo_m1_Wz6.Dy_rms_ol = rms(data_tomo_m1_Wz6.Dy_int(1:i_m1) - (y_m1 + R_m1*sin(data_tomo_m1_Wz6.Rz(1:i_m1)+delta_theta_m1)));
|
|
|
|
data_tomo_m1_Wz6.Dz_rms_ol = rms(detrend(data_tomo_m1_Wz6.Dz_int(1:i_m1), 0));
|
|
|
|
[x0, y0, R] = circlefit(data_tomo_m1_Wz6.Rx_int(1:i_m1), data_tomo_m1_Wz6.Ry_int(1:i_m1));
|
|
|
|
fun = @(theta)rms((data_tomo_m1_Wz6.Rx_int(1:i_m1) - (x0 + R*cos(data_tomo_m1_Wz6.Rz(1:i_m1)+theta(1)))).^2 + ...
|
|
|
|
(data_tomo_m1_Wz6.Ry_int(1:i_m1) - (y0 + R*sin(data_tomo_m1_Wz6.Rz(1:i_m1)+theta(1)))).^2);
|
|
|
|
delta_theta = fminsearch(fun, 0);
|
|
|
|
data_tomo_m1_Wz6.Ry_rms_ol = rms(data_tomo_m1_Wz6.Ry_int(1:i_m1) - (y0 + R*sin(data_tomo_m1_Wz6.Rz(1:i_m1)+delta_theta)));
|
|
|
|
|
|
|
|
% 2 "layer masses"
|
|
|
|
data_tomo_m2_Wz6.Dy_rms_cl = rms(detrend(data_tomo_m2_Wz6.Dy_int(i_m2+1e4:end), 0));
|
|
|
|
data_tomo_m2_Wz6.Dz_rms_cl = rms(detrend(data_tomo_m2_Wz6.Dz_int(i_m2+1e4:end), 0));
|
|
|
|
data_tomo_m2_Wz6.Ry_rms_cl = rms(detrend(data_tomo_m2_Wz6.Ry_int(i_m2+1e4:end), 0));
|
|
|
|
|
|
|
|
% Remove eccentricity for OL errors
|
|
|
|
data_tomo_m2_Wz6.Dy_rms_ol = rms(data_tomo_m2_Wz6.Dy_int(1:i_m2) - (y_m2 + R_m2*sin(data_tomo_m2_Wz6.Rz(1:i_m2)+delta_theta_m2)));
|
|
|
|
data_tomo_m2_Wz6.Dz_rms_ol = rms(detrend(data_tomo_m2_Wz6.Dz_int(1:i_m2), 0));
|
|
|
|
[x0, y0, R] = circlefit(data_tomo_m2_Wz6.Rx_int(1:i_m2), data_tomo_m2_Wz6.Ry_int(1:i_m2));
|
|
|
|
fun = @(theta)rms((data_tomo_m2_Wz6.Rx_int(1:i_m2) - (x0 + R*cos(data_tomo_m2_Wz6.Rz(1:i_m2)+theta(1)))).^2 + ...
|
|
|
|
(data_tomo_m2_Wz6.Ry_int(1:i_m2) - (y0 + R*sin(data_tomo_m2_Wz6.Rz(1:i_m2)+theta(1)))).^2);
|
|
|
|
delta_theta = fminsearch(fun, 0);
|
|
|
|
data_tomo_m2_Wz6.Ry_rms_ol = rms(data_tomo_m2_Wz6.Ry_int(1:i_m2) - (y0 + R*sin(data_tomo_m2_Wz6.Rz(1:i_m2)+delta_theta)));
|
|
|
|
|
|
|
|
% 3 "layer masses"
|
|
|
|
data_tomo_m3_Wz6.Dy_rms_cl = rms(detrend(data_tomo_m3_Wz6.Dy_int(i_m3+1e4:end), 0));
|
|
|
|
data_tomo_m3_Wz6.Dz_rms_cl = rms(detrend(data_tomo_m3_Wz6.Dz_int(i_m3+1e4:end), 0));
|
|
|
|
data_tomo_m3_Wz6.Ry_rms_cl = rms(detrend(data_tomo_m3_Wz6.Ry_int(i_m3+1e4:end), 0));
|
|
|
|
|
|
|
|
% Remove eccentricity for OL errors
|
|
|
|
data_tomo_m3_Wz6.Dy_rms_ol = rms(data_tomo_m3_Wz6.Dy_int(1:i_m3) - (y_m3 + R_m3*sin(data_tomo_m3_Wz6.Rz(1:i_m3)+delta_theta_m3)));
|
|
|
|
data_tomo_m3_Wz6.Dz_rms_ol = rms(detrend(data_tomo_m3_Wz6.Dz_int(1:i_m3), 0));
|
|
|
|
[x0, y0, R] = circlefit(data_tomo_m3_Wz6.Rx_int(1:i_m3), data_tomo_m3_Wz6.Ry_int(1:i_m3));
|
|
|
|
fun = @(theta)rms((data_tomo_m3_Wz6.Rx_int(1:i_m3) - (x0 + R*cos(data_tomo_m3_Wz6.Rz(1:i_m3)+theta(1)))).^2 + ...
|
|
|
|
(data_tomo_m3_Wz6.Ry_int(1:i_m3) - (y0 + R*sin(data_tomo_m3_Wz6.Rz(1:i_m3)+theta(1)))).^2);
|
|
|
|
delta_theta = fminsearch(fun, 0);
|
|
|
|
data_tomo_m3_Wz6.Ry_rms_ol = rms(data_tomo_m3_Wz6.Ry_int(1:i_m3) - (y0 + R*sin(data_tomo_m3_Wz6.Rz(1:i_m3)+delta_theta)));
|
|
|
|
|
|
|
|
% Fast Tomography scans
|
|
|
|
|
2025-02-06 17:05:29 +01:00
|
|
|
% A tomography experiment was then performed with the highest rotational velocity of the Spindle: $180\,\text{deg/s}$[fn:test_id31_7].
|
|
|
|
% The trajectory of the point of interest during the fast tomography scan is shown in Figure ref:fig:test_id31_tomo_m0_30rpm_robust_hac_iff_exp.
|
|
|
|
% Although the experimental results closely mirror the simulation results (Figure ref:fig:test_id31_tomo_m0_30rpm_robust_hac_iff_sim), the actual performance was slightly lower than predicted.
|
|
|
|
% Nevertheless, even with this robust (i.e. conservative) HAC implementation, the system performance was already close to the specified requirements.
|
2025-02-04 14:10:54 +01:00
|
|
|
|
|
|
|
|
|
|
|
%% Experimental Results for Tomography at 180deg/s, no payload
|
|
|
|
data_tomo_m0_Wz180 = load('2023-08-17_15-26_tomography_30rpm_m0_robust.mat');
|
|
|
|
|
|
|
|
[~, i_m0] = find(data_tomo_m0_Wz180.hac_status == 1);
|
|
|
|
[x_m0, y_m0, R_m0] = circlefit(data_tomo_m0_Wz180.Dx_int(1:i_m0), data_tomo_m0_Wz180.Dy_int(1:i_m0));
|
|
|
|
fun = @(theta)rms((data_tomo_m0_Wz180.Dx_int(1:i_m0) - (x_m0 + R_m0*cos(data_tomo_m0_Wz180.Rz(1:i_m0)+theta(1)))).^2 + ...
|
|
|
|
(data_tomo_m0_Wz180.Dy_int(1:i_m0) - (y_m0 + R_m0*sin(data_tomo_m0_Wz180.Rz(1:i_m0)+theta(1)))).^2);
|
|
|
|
delta_theta_m0 = fminsearch(fun, 0);
|
|
|
|
|
|
|
|
%% Tomography at 180deg/s - Errors in the X/Y plane
|
|
|
|
figure;
|
|
|
|
hold on;
|
|
|
|
plot(1e6*data_tomo_m0_Wz180.Dx_int(1:i_m0), 1e6*data_tomo_m0_Wz180.Dy_int(1:i_m0), 'DisplayName', 'OL')
|
|
|
|
plot(1e6*data_tomo_m0_Wz180.Dx_int(i_m0:i_m0+1e4), 1e6*data_tomo_m0_Wz180.Dy_int(i_m0:i_m0+1e4), 'color', colors(3,:), 'HandleVisibility', 'off')
|
|
|
|
plot(1e6*data_tomo_m0_Wz180.Dx_int(i_m0+1e4:end), 1e6*data_tomo_m0_Wz180.Dy_int(i_m0+1e4:end), 'color', colors(2,:), 'DisplayName', 'CL')
|
|
|
|
theta = linspace(0, 2*pi, 500); % Angle to plot the circle [rad]
|
|
|
|
plot(1e6*(x_m0 + R_m0*cos(theta)), 1e6*(y_m0 + R_m0*sin(theta)), 'k--', 'DisplayName', 'Circ. Fit')
|
|
|
|
hold off;
|
|
|
|
xlabel('$D_x$ [$\mu$m]'); ylabel('$D_y$ [$\mu$m]');
|
|
|
|
axis equal
|
|
|
|
xlim([-3, 3]); ylim([-3, 3]);
|
|
|
|
xticks([-3:1:3]);
|
|
|
|
yticks([-3:1:3]);
|
|
|
|
leg = legend('location', 'northeast', 'FontSize', 8, 'NumColumns', 1);
|
|
|
|
leg.ItemTokenSize(1) = 15;
|
|
|
|
|
|
|
|
%% Tomography at 180deg/s - Errors in the Y/Z plane
|
|
|
|
figure;
|
|
|
|
tiledlayout(2, 1, 'TileSpacing', 'compact', 'Padding', 'None');
|
|
|
|
|
|
|
|
ax1 = nexttile();
|
|
|
|
hold on;
|
|
|
|
plot(1e6*data_tomo_m0_Wz180.Dy_int(1:i_m0), 1e6*data_tomo_m0_Wz180.Dz_int(1:i_m0), 'DisplayName', 'OL')
|
|
|
|
plot(1e6*data_tomo_m0_Wz180.Dy_int(i_m0:i_m0+1e4), 1e6*data_tomo_m0_Wz180.Dz_int(i_m0:i_m0+1e4), 'color', colors(3,:), 'HandleVisibility', 'off')
|
|
|
|
plot(1e6*data_tomo_m0_Wz180.Dy_int(i_m0+1e4:end), 1e6*data_tomo_m0_Wz180.Dz_int(i_m0+1e4:end), 'color', colors(2,:), 'DisplayName', 'CL')
|
|
|
|
hold off;
|
|
|
|
xlabel('$D_y$ [$\mu$m]'); ylabel('$D_z$ [$\mu$m]');
|
|
|
|
axis equal
|
|
|
|
xlim([-3, 3]); ylim([-0.6, 0.6]);
|
|
|
|
xticks([-3:1:3]);
|
|
|
|
yticks([-3:0.3:3]);
|
|
|
|
leg = legend('location', 'northeast', 'FontSize', 8, 'NumColumns', 1);
|
|
|
|
leg.ItemTokenSize(1) = 15;
|
|
|
|
|
|
|
|
ax2 = nexttile();
|
|
|
|
hold on;
|
|
|
|
plot(1e9*data_tomo_m0_Wz180.Dy_int(i_m0+1e4:end), 1e9*data_tomo_m0_Wz180.Dz_int(i_m0+1e4:end), 'color', colors(2,:), 'DisplayName', 'CL')
|
|
|
|
theta = linspace(0, 2*pi, 500); % Angle to plot the circle [rad]
|
|
|
|
plot(100*cos(theta), 50*sin(theta), 'k--', 'DisplayName', 'Beam size')
|
|
|
|
hold off;
|
|
|
|
xlabel('$D_y$ [nm]'); ylabel('$D_z$ [nm]');
|
|
|
|
axis equal
|
|
|
|
xlim([-300, 300]); ylim([-100, 100]);
|
|
|
|
leg = legend('location', 'northeast', 'FontSize', 8, 'NumColumns', 1);
|
|
|
|
leg.ItemTokenSize(1) = 15;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
% #+name: fig:test_id31_tomo_m0_30rpm_robust_hac_iff_exp
|
2025-02-06 17:05:29 +01:00
|
|
|
% #+caption: Experimental results of tomography experiment at 180 deg/s without payload. The position error of the sample is shown in the XY (\subref{fig:test_id31_tomo_m0_30rpm_robust_hac_iff_exp_xy}) and YZ (\subref{fig:test_id31_tomo_m0_30rpm_robust_hac_iff_exp_yz}) planes.
|
2025-02-04 14:10:54 +01:00
|
|
|
% #+attr_latex: :options [htbp]
|
|
|
|
% #+begin_figure
|
|
|
|
% #+attr_latex: :caption \subcaption{\label{fig:test_id31_tomo_m0_30rpm_robust_hac_iff_exp_xy}XY plane}
|
|
|
|
% #+attr_latex: :options {0.49\textwidth}
|
|
|
|
% #+begin_subfigure
|
|
|
|
% #+attr_latex: :scale 0.9
|
|
|
|
% [[file:figs/test_id31_tomo_m0_30rpm_robust_hac_iff_exp_xy.png]]
|
|
|
|
% #+end_subfigure
|
|
|
|
% #+attr_latex: :caption \subcaption{\label{fig:test_id31_tomo_m0_30rpm_robust_hac_iff_exp_yz}YZ plane}
|
|
|
|
% #+attr_latex: :options {0.49\textwidth}
|
|
|
|
% #+begin_subfigure
|
|
|
|
% #+attr_latex: :scale 0.9
|
|
|
|
% [[file:figs/test_id31_tomo_m0_30rpm_robust_hac_iff_exp_yz.png]]
|
|
|
|
% #+end_subfigure
|
|
|
|
% #+end_figure
|
|
|
|
|
|
|
|
|
|
|
|
%% Estimate RMS of the errors while in closed-loop and open-loop - Tomography at 180deg/s
|
|
|
|
% No mass
|
|
|
|
data_tomo_m0_Wz180.Dy_rms_cl = rms(detrend(data_tomo_m0_Wz180.Dy_int(i_m0+1e4:end), 0));
|
|
|
|
data_tomo_m0_Wz180.Dz_rms_cl = rms(detrend(data_tomo_m0_Wz180.Dz_int(i_m0+1e4:end), 0));
|
|
|
|
data_tomo_m0_Wz180.Ry_rms_cl = rms(detrend(data_tomo_m0_Wz180.Ry_int(i_m0+1e4:end), 0));
|
|
|
|
|
|
|
|
% Remove eccentricity for OL errors
|
|
|
|
data_tomo_m0_Wz180.Dy_rms_ol = rms(data_tomo_m0_Wz180.Dy_int(1:i_m0) - (y_m0 + R_m0*sin(data_tomo_m0_Wz180.Rz(1:i_m0)+delta_theta_m0)));
|
|
|
|
data_tomo_m0_Wz180.Dz_rms_ol = rms(detrend(data_tomo_m0_Wz180.Dz_int(1:i_m0), 0));
|
|
|
|
[x0, y0, R] = circlefit(data_tomo_m0_Wz180.Rx_int(1:i_m0), data_tomo_m0_Wz180.Ry_int(1:i_m0));
|
|
|
|
fun = @(theta)rms((data_tomo_m0_Wz180.Rx_int(1:i_m0) - (x0 + R*cos(data_tomo_m0_Wz180.Rz(1:i_m0)+theta(1)))).^2 + ...
|
|
|
|
(data_tomo_m0_Wz180.Ry_int(1:i_m0) - (y0 + R*sin(data_tomo_m0_Wz180.Rz(1:i_m0)+theta(1)))).^2);
|
|
|
|
delta_theta = fminsearch(fun, 0);
|
|
|
|
data_tomo_m0_Wz180.Ry_rms_ol = rms(data_tomo_m0_Wz180.Ry_int(1:i_m0) - (y0 + R*sin(data_tomo_m0_Wz180.Rz(1:i_m0)+delta_theta)));
|
|
|
|
|
|
|
|
% Cumulative Amplitude Spectra
|
|
|
|
|
|
|
|
% A comparative analysis was conducted using three tomography scans at $180,\text{deg/s}$ to evaluate the effectiveness of the HAC-LAC strategy in reducing positioning errors.
|
|
|
|
% The scans were performed under three conditions: open-loop, with decentralized IFF control, and with the complete HAC-LAC strategy.
|
2025-02-06 17:05:29 +01:00
|
|
|
% For these specific measurements, an enhanced high authority controller was optimized for low payload masses to meet the performance requirements.
|
2025-02-04 14:10:54 +01:00
|
|
|
|
|
|
|
% Figure ref:fig:test_id31_hac_cas_cl presents the cumulative amplitude spectra of the position errors for all three cases.
|
|
|
|
% The results reveal two distinct control contributions: the decentralized IFF effectively attenuates vibrations near the nano-hexapod suspension modes (an achievement not possible with HAC alone), while the high authority controller suppresses low-frequency vibrations primarily arising from Spindle guiding errors.
|
|
|
|
% Notably, the spectral patterns in Figure ref:fig:test_id31_hac_cas_cl closely resemble the cumulative amplitude spectra computed in the project's early stages.
|
|
|
|
|
2025-02-06 17:05:29 +01:00
|
|
|
% This experiment also illustrates that when needed, performance can be enhanced by designing controllers for specific experimental conditions rather than relying solely on robust controllers that can accommodate all payload ranges.
|
2025-02-04 14:10:54 +01:00
|
|
|
|
|
|
|
|
|
|
|
%% Jacobian to compute the motion in the X-Y-Z-Rx-Ry directions
|
|
|
|
J_int_to_X = [ 0 0 -0.787401574803149 -0.212598425196851 0;
|
|
|
|
0.78740157480315 0.21259842519685 0 0 0;
|
|
|
|
0 0 0 0 -1;
|
|
|
|
-13.1233595800525 13.1233595800525 0 0 0;
|
|
|
|
0 0 -13.1233595800525 13.1233595800525 0];
|
|
|
|
|
|
|
|
%% Parameters for frequency analysis computation
|
|
|
|
Nfft = floor(20.0/Ts);
|
|
|
|
win = hanning(Nfft);
|
|
|
|
Noverlap = floor(Nfft/2);
|
|
|
|
|
|
|
|
%% Open-Loop measurement
|
|
|
|
data_ol_Wz180 = load('2023-08-11_16-51_m0_lac_off.mat'); % no rotation
|
|
|
|
|
|
|
|
a = J_int_to_X*[data_ol_Wz180.d1; data_ol_Wz180.d2; data_ol_Wz180.d3; data_ol_Wz180.d4; data_ol_Wz180.d5];
|
|
|
|
data_ol_Wz180.Dx_int = a(1,:);
|
|
|
|
data_ol_Wz180.Dy_int = a(2,:);
|
|
|
|
data_ol_Wz180.Dz_int = a(3,:);
|
|
|
|
data_ol_Wz180.Rx_int = a(4,:);
|
|
|
|
data_ol_Wz180.Ry_int = a(5,:);
|
|
|
|
|
|
|
|
[data_ol_Wz180.pxx_Dx, data_ol_Wz180.f] = pwelch(detrend(data_ol_Wz180.Dx_int, 0), win, Noverlap, Nfft, 1/Ts);
|
|
|
|
[data_ol_Wz180.pxx_Dy, ~ ] = pwelch(detrend(data_ol_Wz180.Dy_int, 0), win, Noverlap, Nfft, 1/Ts);
|
|
|
|
[data_ol_Wz180.pxx_Dz, ~ ] = pwelch(detrend(data_ol_Wz180.Dz_int, 0), win, Noverlap, Nfft, 1/Ts);
|
|
|
|
[data_ol_Wz180.pxx_Rx, ~ ] = pwelch(detrend(data_ol_Wz180.Rx_int, 0), win, Noverlap, Nfft, 1/Ts);
|
|
|
|
[data_ol_Wz180.pxx_Ry, ~ ] = pwelch(detrend(data_ol_Wz180.Ry_int, 0), win, Noverlap, Nfft, 1/Ts);
|
|
|
|
|
|
|
|
%% Effect of LAC - 180 deg/s
|
|
|
|
data_lac_Wz180 = load('2023-08-11_17-36_m0_lac_on_30rpm.mat');
|
|
|
|
|
|
|
|
a = J_int_to_X*[data_lac_Wz180.d1; data_lac_Wz180.d2; data_lac_Wz180.d3; data_lac_Wz180.d4; data_lac_Wz180.d5];
|
|
|
|
data_lac_Wz180.Dx_int = a(1,:);
|
|
|
|
data_lac_Wz180.Dy_int = a(2,:);
|
|
|
|
data_lac_Wz180.Dz_int = a(3,:);
|
|
|
|
data_lac_Wz180.Rx_int = a(4,:);
|
|
|
|
data_lac_Wz180.Ry_int = a(5,:);
|
|
|
|
|
|
|
|
[data_lac_Wz180.pxx_Dx, data_lac_Wz180.f] = pwelch(detrend(data_lac_Wz180.Dx_int, 0), win, Noverlap, Nfft, 1/Ts);
|
|
|
|
[data_lac_Wz180.pxx_Dy, ~ ] = pwelch(detrend(data_lac_Wz180.Dy_int, 0), win, Noverlap, Nfft, 1/Ts);
|
|
|
|
[data_lac_Wz180.pxx_Dz, ~ ] = pwelch(detrend(data_lac_Wz180.Dz_int, 0), win, Noverlap, Nfft, 1/Ts);
|
|
|
|
[data_lac_Wz180.pxx_Rx, ~ ] = pwelch(detrend(data_lac_Wz180.Rx_int, 0), win, Noverlap, Nfft, 1/Ts);
|
|
|
|
[data_lac_Wz180.pxx_Ry, ~ ] = pwelch(detrend(data_lac_Wz180.Ry_int, 0), win, Noverlap, Nfft, 1/Ts);
|
|
|
|
|
|
|
|
%% Effect of HAC - 180 deg/s
|
|
|
|
data_hac_Wz180 = load('2023-08-11_16-49_m0_hac_on.mat');
|
|
|
|
|
|
|
|
a = J_int_to_X*[data_hac_Wz180.d1; data_hac_Wz180.d2; data_hac_Wz180.d3; data_hac_Wz180.d4; data_hac_Wz180.d5];
|
|
|
|
data_hac_Wz180.Dx_int = a(1,:);
|
|
|
|
data_hac_Wz180.Dy_int = a(2,:);
|
|
|
|
data_hac_Wz180.Dz_int = a(3,:);
|
|
|
|
data_hac_Wz180.Rx_int = a(4,:);
|
|
|
|
data_hac_Wz180.Ry_int = a(5,:);
|
|
|
|
|
|
|
|
[data_hac_Wz180.pxx_Dx, data_hac_Wz180.f] = pwelch(detrend(data_hac_Wz180.Dx_int, 0), win, Noverlap, Nfft, 1/Ts);
|
|
|
|
[data_hac_Wz180.pxx_Dy, ~ ] = pwelch(detrend(data_hac_Wz180.Dy_int, 0), win, Noverlap, Nfft, 1/Ts);
|
|
|
|
[data_hac_Wz180.pxx_Dz, ~ ] = pwelch(detrend(data_hac_Wz180.Dz_int, 0), win, Noverlap, Nfft, 1/Ts);
|
|
|
|
[data_hac_Wz180.pxx_Rx, ~ ] = pwelch(detrend(data_hac_Wz180.Rx_int, 0), win, Noverlap, Nfft, 1/Ts);
|
|
|
|
[data_hac_Wz180.pxx_Ry, ~ ] = pwelch(detrend(data_hac_Wz180.Ry_int, 0), win, Noverlap, Nfft, 1/Ts);
|
|
|
|
|
|
|
|
% Compute closed-loop RMS errors
|
|
|
|
data_hac_Wz180.Dy_rms_cl = rms(detrend(data_hac_Wz180.Dy_int(1e4:end), 0));
|
|
|
|
data_hac_Wz180.Dz_rms_cl = rms(detrend(data_hac_Wz180.Dz_int(1e4:end), 0));
|
|
|
|
data_hac_Wz180.Ry_rms_cl = rms(detrend(data_hac_Wz180.Ry_int(1e4:end), 0));
|
|
|
|
|
|
|
|
%% Cumulative Amplitude Spectrum - Closed-Loop - Dy
|
|
|
|
figure;
|
|
|
|
tiledlayout(1, 1, 'TileSpacing', 'compact', 'Padding', 'None');
|
|
|
|
hold on;
|
|
|
|
plot(data_ol_Wz180.f, sqrt(flip(-cumtrapz(flip(data_ol_Wz180.f), flip(data_ol_Wz180.pxx_Dy)))), 'DisplayName', sprintf('OL $%.1f \\mu m$', 1e6*rms(detrend(data_ol_Wz180.Dy_int, 0))));
|
|
|
|
plot(data_lac_Wz180.f, sqrt(flip(-cumtrapz(flip(data_lac_Wz180.f), flip(data_lac_Wz180.pxx_Dy)))), 'DisplayName', sprintf('LAC $%.1f \\mu m$', 1e6*rms(detrend(data_lac_Wz180.Dy_int, 0))));
|
|
|
|
plot(data_hac_Wz180.f, sqrt(flip(-cumtrapz(flip(data_hac_Wz180.f), flip(data_hac_Wz180.pxx_Dy)))), 'DisplayName', sprintf('HAC $%.0f nm$', 1e9*rms(detrend(data_hac_Wz180.Dy_int, 0))));
|
|
|
|
plot([1e-2, 1e4], 1e-9*[specs_dy_rms, specs_dy_rms], 'k--', 'DisplayName', sprintf('Spec: $%.0f$nm', specs_dy_rms))
|
|
|
|
hold off;
|
|
|
|
set(gca, 'XScale', 'log'); set(gca, 'YScale', 'log');
|
|
|
|
xlabel('Frequency [Hz]'); ylabel('CAS [m]');
|
|
|
|
leg = legend('location', 'northeast', 'FontSize', 8, 'NumColumns', 1);
|
|
|
|
leg.ItemTokenSize(1) = 15;
|
|
|
|
xticks([1e0, 1e1, 1e2]); yticks([1e-9, 1e-8, 1e-7, 1e-6, 1e-5]);
|
|
|
|
xlim([0.1, 5e2]); ylim([1e-10, 2e-5]);
|
|
|
|
|
|
|
|
%% Cumulative Amplitude Spectrum - Closed-Loop - Dz
|
|
|
|
figure;
|
|
|
|
tiledlayout(1, 1, 'TileSpacing', 'compact', 'Padding', 'None');
|
|
|
|
hold on;
|
|
|
|
plot(data_ol_Wz180.f, sqrt(flip(-cumtrapz(flip(data_ol_Wz180.f), flip(data_ol_Wz180.pxx_Dz)))), 'DisplayName', sprintf('OL $%.0f nm$', 1e9*rms(detrend(data_ol_Wz180.Dz_int, 0))));
|
|
|
|
plot(data_lac_Wz180.f, sqrt(flip(-cumtrapz(flip(data_lac_Wz180.f), flip(data_lac_Wz180.pxx_Dz)))), 'DisplayName', sprintf('LAC $%.0f nm$', 1e9*rms(detrend(data_lac_Wz180.Dz_int, 0))));
|
|
|
|
plot(data_hac_Wz180.f, sqrt(flip(-cumtrapz(flip(data_hac_Wz180.f), flip(data_hac_Wz180.pxx_Dz)))), 'DisplayName', sprintf('HAC $%.0f nm$', 1e9*rms(detrend(data_hac_Wz180.Dz_int, 0))));
|
|
|
|
plot([1e-2, 1e4], 1e-9*[specs_dz_rms, specs_dz_rms], 'k--', 'DisplayName', sprintf('Spec: $%.0f$nm', specs_dz_rms))
|
|
|
|
hold off;
|
|
|
|
set(gca, 'XScale', 'log'); set(gca, 'YScale', 'log');
|
|
|
|
xlabel('Frequency [Hz]'); ylabel('CAS [m]');
|
|
|
|
leg = legend('location', 'northeast', 'FontSize', 8, 'NumColumns', 1);
|
|
|
|
leg.ItemTokenSize(1) = 15;
|
|
|
|
xticks([1e0, 1e1, 1e2]); yticks([1e-9, 1e-8, 1e-7, 1e-6, 1e-5]);
|
|
|
|
xlim([0.1, 5e2]); ylim([1e-10, 2e-5]);
|
|
|
|
|
|
|
|
%% Cumulative Amplitude Spectrum - Closed-Loop - Ry
|
|
|
|
figure;
|
|
|
|
tiledlayout(1, 1, 'TileSpacing', 'compact', 'Padding', 'None');
|
|
|
|
hold on;
|
|
|
|
plot(data_ol_Wz180.f, sqrt(flip(-cumtrapz(flip(data_ol_Wz180.f), flip(data_ol_Wz180.pxx_Ry)))), 'DisplayName', sprintf('OL $%.0f \\mu$rad', 1e6*rms(detrend(data_ol_Wz180.Ry_int, 0))));
|
|
|
|
plot(data_lac_Wz180.f, sqrt(flip(-cumtrapz(flip(data_lac_Wz180.f), flip(data_lac_Wz180.pxx_Ry)))), 'DisplayName', sprintf('LAC $%.0f \\mu$rad', 1e6*rms(detrend(data_lac_Wz180.Ry_int, 0))));
|
|
|
|
plot(data_hac_Wz180.f, sqrt(flip(-cumtrapz(flip(data_hac_Wz180.f), flip(data_hac_Wz180.pxx_Ry)))), 'DisplayName', sprintf('HAC $%.2f \\mu$rad', 1e6*rms(detrend(data_hac_Wz180.Ry_int, 0))));
|
|
|
|
plot([1e-2, 1e4], 1e-6*[specs_ry_rms, specs_ry_rms], 'k--', 'DisplayName', sprintf('Spec: $%.2f \\mu$rad', specs_ry_rms))
|
|
|
|
hold off;
|
|
|
|
set(gca, 'XScale', 'log'); set(gca, 'YScale', 'log');
|
|
|
|
xlabel('Frequency [Hz]'); ylabel('CAS [rad]');
|
|
|
|
leg = legend('location', 'southwest', 'FontSize', 8, 'NumColumns', 1);
|
|
|
|
leg.ItemTokenSize(1) = 15;
|
|
|
|
xticks([1e0, 1e1, 1e2]); yticks([1e-9, 1e-8, 1e-7, 1e-6, 1e-5]);
|
|
|
|
xlim([0.1, 5e2]); ylim([1e-10, 2e-5]);
|
|
|
|
|
|
|
|
% Reflectivity Scans
|
|
|
|
% <<ssec:test_id31_scans_reflectivity>>
|
|
|
|
|
|
|
|
% X-ray reflectivity measurements involve scanning thin structures, particularly solid/liquid interfaces, through the beam by varying the $R_y$ angle.
|
|
|
|
% In this experiment, a $R_y$ scan was executed at a rotational velocity of $100,\mu rad/s$, and the closed-loop positioning errors were monitored (Figure ref:fig:test_id31_reflectivity).
|
2025-02-06 17:05:29 +01:00
|
|
|
% The results confirmed that the NASS successfully maintained the point of interest within the specified beam parameters throughout the scanning process.
|
2025-02-04 14:10:54 +01:00
|
|
|
|
|
|
|
|
|
|
|
%% Load data for the reflectivity scan
|
|
|
|
data_ry = load("2023-08-18_15-24_first_reflectivity_m0.mat");
|
|
|
|
data_ry.time = Ts*[0:length(data_ry.Ry_int)-1];
|
|
|
|
|
|
|
|
% Compute closed-loop errors
|
|
|
|
data_ry.Dy_rms_cl = rms(detrend(data_ry.e_dy,0)); % [m RMS]
|
|
|
|
data_ry.Dz_rms_cl = rms(detrend(data_ry.e_dz,0)); % [m RMS]
|
|
|
|
data_ry.Ry_rms_cl = rms(detrend(data_ry.e_ry,0)); % [rad RMS]
|
|
|
|
|
|
|
|
%% Ry reflectivity scan - Lateral error
|
|
|
|
figure;
|
|
|
|
hold on;
|
|
|
|
plot(data_ry.time, 1e9*data_ry.e_dy, 'DisplayName', sprintf('$\\epsilon D_y = %.0f$ nm RMS', 1e9*rms(data_ry.e_dy)))
|
|
|
|
plot([0, 6.2], [specs_dy_peak, specs_dy_peak], '--', 'color', colors(1,:), 'HandleVisibility', 'off');
|
|
|
|
plot([0, 6.2], [-specs_dy_peak, -specs_dy_peak], '--', 'color', colors(1,:), 'HandleVisibility', 'off');
|
|
|
|
hold off;
|
|
|
|
xlabel('Time [s]');
|
|
|
|
ylabel('$D_y$ error [nm]')
|
|
|
|
% legend('location', 'southeast', 'FontSize', 8, 'NumColumns', 1);
|
|
|
|
xlim([0, 6.2]);
|
|
|
|
ylim([-150, 150]);
|
|
|
|
xticks([0:2:6]);
|
|
|
|
|
|
|
|
%% Ry reflectivity scan - Vertical error
|
|
|
|
figure;
|
|
|
|
hold on;
|
|
|
|
plot(data_ry.time, 1e9*data_ry.e_dz, 'DisplayName', sprintf('$\\epsilon D_z = %.0f$ nm RMS', 1e9*rms(data_ry.e_dz)))
|
|
|
|
plot([0, 6.2], [specs_dz_peak, specs_dz_peak], '--', 'color', colors(1,:), 'HandleVisibility', 'off');
|
|
|
|
plot([0, 6.2], [-specs_dz_peak, -specs_dz_peak], '--', 'color', colors(1,:), 'HandleVisibility', 'off');
|
|
|
|
hold off;
|
|
|
|
xlabel('Time [s]');
|
|
|
|
ylabel('$D_z$ error [nm]')
|
|
|
|
% legend('location', 'southeast', 'FontSize', 8, 'NumColumns', 1);
|
|
|
|
xlim([0, 6.2]);
|
|
|
|
ylim([-100, 100]);
|
|
|
|
xticks([0:2:6]);
|
|
|
|
|
|
|
|
%% Ry reflectivity scan - Setpoint and Error
|
|
|
|
figure;
|
|
|
|
yyaxis left
|
|
|
|
hold on;
|
|
|
|
plot(data_ry.time, 1e6*data_ry.e_ry, 'DisplayName', '$\epsilon_{R_y}$')
|
|
|
|
plot([0, 6.2], [specs_ry_peak, specs_ry_peak], '--', 'HandleVisibility', 'off');
|
|
|
|
plot([0, 6.2], [-specs_ry_peak, -specs_ry_peak], '--', 'HandleVisibility', 'off');
|
|
|
|
hold off;
|
|
|
|
ylim([-2, 2])
|
|
|
|
ylabel('$R_y$ error [$\mu$rad]')
|
|
|
|
yyaxis right
|
|
|
|
hold on;
|
|
|
|
plot(data_ry.time, 1e6*data_ry.Ry_int, 'DisplayName', '$R_y$')
|
|
|
|
plot(data_ry.time, 1e6*data_ry.m_hexa_ry, 'k--', 'DisplayName', 'Setpoint')
|
|
|
|
hold off;
|
|
|
|
xlabel('Time [s]');
|
|
|
|
ylabel('$R_y$ motion [$\mu$rad]')
|
|
|
|
leg = legend('location', 'southeast', 'FontSize', 8, 'NumColumns', 1);
|
|
|
|
leg.ItemTokenSize(1) = 15;
|
|
|
|
xlim([0, 6.2]);
|
|
|
|
ylim([-310, 310]);
|
|
|
|
xticks([0:2:6]);
|
|
|
|
|
|
|
|
% Step by Step $D_z$ motion
|
|
|
|
|
2025-02-06 17:05:29 +01:00
|
|
|
% The vertical step motion was performed exclusively with the nano-hexapod.
|
|
|
|
% Testing was conducted across step sizes ranging from $10\,nm$ to $1\,\mu m$.
|
|
|
|
% Results are presented in Figure ref:fig:test_id31_dz_mim_steps.
|
|
|
|
% The system successfully resolved 10nm steps (red curve in Figure ref:fig:test_id31_dz_mim_10nm_steps) if a 50ms integration time is considered for the detectors, which is compatible with many experimental requirements.
|
2025-02-04 14:10:54 +01:00
|
|
|
|
2025-02-06 17:05:29 +01:00
|
|
|
% In step-by-step scanning procedures, the settling time is a critical parameter as it significantly affects the total experiment duration.
|
|
|
|
% The system achieved a response time of approximately $70\,ms$ to reach the target position (within $\pm 20\,nm$), as demonstrated by the $1\,\mu m$ step response in Figure ref:fig:test_id31_dz_mim_1000nm_steps.
|
|
|
|
% The settling duration typically decreases for smaller step sizes.
|
2025-02-04 14:10:54 +01:00
|
|
|
|
|
|
|
|
|
|
|
%% Load Dz steps data
|
|
|
|
data_dz_steps_10nm = load("2023-08-18_14-57_dz_mim_10_nm.mat");
|
|
|
|
data_dz_steps_10nm.time = Ts*[0:length(data_dz_steps_10nm.Dz_int)-1];
|
|
|
|
|
|
|
|
data_dz_steps_100nm = load("2023-08-18_14-57_dz_mim_100_nm.mat");
|
|
|
|
data_dz_steps_100nm.time = Ts*[0:length(data_dz_steps_100nm.Dz_int)-1];
|
|
|
|
|
|
|
|
data_dz_steps_1000nm = load("2023-08-18_14-57_dz_mim_1000_nm.mat");
|
|
|
|
data_dz_steps_1000nm.time = Ts*[0:length(data_dz_steps_1000nm.Dz_int)-1];
|
|
|
|
|
|
|
|
%% Dz MIM test with 10nm steps
|
|
|
|
figure;
|
|
|
|
hold on;
|
|
|
|
plot(data_dz_steps_10nm.time, 1e9*(data_dz_steps_10nm.Dz_int - mean(data_dz_steps_10nm.Dz_int(1:1000))), 'DisplayName', '$D_z$')
|
|
|
|
plot(data_dz_steps_10nm.time, 1e9*lsim(1/(1 + s/2/pi/20), data_dz_steps_10nm.Dz_int - mean(data_dz_steps_10nm.Dz_int(1:1000)), data_dz_steps_10nm.time), 'DisplayName', '$D_z$ (LPF)')
|
|
|
|
plot(data_dz_steps_10nm.time, 1e9*(data_dz_steps_10nm.m_hexa_dz-data_dz_steps_10nm.m_hexa_dz(1)), 'k--', 'DisplayName', 'Setpoint')
|
|
|
|
hold off;
|
|
|
|
xlabel('Time [s]');
|
|
|
|
ylabel('$D_z$ Motion [nm]');
|
|
|
|
legend('location', 'northwest', 'FontSize', 8, 'NumColumns', 1);
|
|
|
|
xlim([0, 0.6]);
|
|
|
|
ylim([-10, 40]);
|
|
|
|
xticks([0:0.2:0.6]);
|
|
|
|
yticks([-10:10:50]);
|
|
|
|
|
|
|
|
%% Dz MIM test with 100nm steps
|
|
|
|
figure;
|
|
|
|
hold on;
|
|
|
|
plot(data_dz_steps_100nm.time, 1e9*(data_dz_steps_100nm.Dz_int - mean(data_dz_steps_100nm.Dz_int(1:1000))), 'DisplayName', '$D_z$')
|
|
|
|
plot(data_dz_steps_100nm.time, 1e9*(data_dz_steps_100nm.m_hexa_dz-data_dz_steps_100nm.m_hexa_dz(1)), 'k--', 'DisplayName', 'Setpoint')
|
|
|
|
hold off;
|
|
|
|
xlabel('Time [s]');
|
|
|
|
ylabel('$D_z$ Motion [nm]');
|
|
|
|
legend('location', 'northwest', 'FontSize', 8, 'NumColumns', 1);
|
|
|
|
xlim([0, 0.6]);
|
|
|
|
% ylim([-10, 40]);
|
|
|
|
xticks([0:0.2:0.6]);
|
|
|
|
yticks([-0:100:300]);
|
|
|
|
|
|
|
|
%% Dz step response - Stabilization time is around 70ms
|
|
|
|
figure;
|
|
|
|
[~, i] = find(data_dz_steps_1000nm.m_hexa_dz>data_dz_steps_1000nm.m_hexa_dz(1));
|
|
|
|
i0 = i(1);
|
|
|
|
|
|
|
|
figure;
|
|
|
|
hold on;
|
|
|
|
plot(1e3*(data_dz_steps_1000nm.time-data_dz_steps_1000nm.time(i0)), 1e6*(data_dz_steps_1000nm.Dz_int - mean(data_dz_steps_1000nm.Dz_int(1:1000))))
|
|
|
|
plot(1e3*[-1, 1], 1e-3*[1000-20, 1000-20], 'k--')
|
|
|
|
plot(1e3*[-1, 1], 1e-3*[1000+20, 1000+20], 'k--')
|
|
|
|
xline(0, 'k--', 'LineWidth', 1.5)
|
|
|
|
xline(70, 'k--', 'LineWidth', 1.5)
|
|
|
|
hold off;
|
|
|
|
xlabel('Time [ms]');
|
|
|
|
ylabel('$D_z$ Motion [$\mu$m]');
|
|
|
|
xlim([-10, 140]);
|
|
|
|
ylim([-0.1, 1.6])
|
|
|
|
xticks([0, 70])
|
|
|
|
yticks([0, 1])
|
|
|
|
|
|
|
|
% Continuous $D_z$ motion: Dirty Layer Scans
|
|
|
|
|
|
|
|
% For these and subsequent experiments, the NASS performs "ramp scans" (constant velocity scans).
|
|
|
|
% To eliminate tracking errors, the feedback controller incorporates two integrators, compensating for the plant's lack of integral action at low frequencies.
|
|
|
|
|
2025-02-06 17:05:29 +01:00
|
|
|
% Initial testing at $10,\mu m/s$ demonstrated positioning errors well within specifications (indicated by dashed lines in Figure ref:fig:test_id31_dz_scan_10ums).
|
2025-02-04 14:10:54 +01:00
|
|
|
|
|
|
|
|
|
|
|
%% Dirty layer scans - 10um/s
|
|
|
|
data_dz_10ums = load("2023-08-18_15-33_dirty_layer_m0_small.mat");
|
|
|
|
data_dz_10ums.time = Ts*[0:length(data_dz_10ums.Dz_int)-1];
|
|
|
|
|
|
|
|
%% Dirty layer scans - 100um/s
|
|
|
|
data_dz_100ums = load("2023-08-18_15-32_dirty_layer_m0.mat");
|
|
|
|
data_dz_100ums.time = Ts*[0:length(data_dz_100ums.Dz_int)-1];
|
|
|
|
|
|
|
|
%% Performances for Dz scans - 10 um/s
|
|
|
|
% Determine when the motion starts and stops
|
|
|
|
i_dz_10ums = abs(diff(data_dz_10ums.m_hexa_dz)/Ts-10e-6) < 10*eps;
|
|
|
|
|
|
|
|
% RMS error
|
|
|
|
data_dz_10ums.Dy_rms_cl = rms(detrend(data_dz_10ums.e_dy(i_dz_10ums), 0));
|
|
|
|
data_dz_10ums.Dz_rms_cl = rms(detrend(data_dz_10ums.e_dz(i_dz_10ums), 0));
|
|
|
|
data_dz_10ums.Ry_rms_cl = rms(detrend(data_dz_10ums.e_ry(i_dz_10ums), 0));
|
|
|
|
|
|
|
|
%% Performances for Dz scans - 100 um/s
|
|
|
|
i_dz_100ums = abs(diff(data_dz_100ums.m_hexa_dz)/Ts-100e-6) < 10*eps;
|
|
|
|
|
|
|
|
% RMS error
|
|
|
|
data_dz_100ums.Dy_rms_cl = rms(detrend(data_dz_100ums.e_dy(i_dz_100ums), 0));
|
|
|
|
data_dz_100ums.Dz_rms_cl = rms(detrend(data_dz_100ums.e_dz(i_dz_100ums), 0));
|
|
|
|
data_dz_100ums.Ry_rms_cl = rms(detrend(data_dz_100ums.e_ry(i_dz_100ums), 0));
|
|
|
|
|
|
|
|
%% Dz scan at 10um/s - Lateral error
|
|
|
|
figure;
|
|
|
|
hold on;
|
|
|
|
plot(data_dz_10ums.time, 1e9*data_dz_10ums.e_dy, 'DisplayName', sprintf('$\\epsilon D_y: %.0f$ nm RMS', 1e9*rms(data_dz_10ums.e_dy)))
|
|
|
|
plot([0, 2.2], [specs_dy_peak, specs_dy_peak], '--', 'color', colors(1,:), 'HandleVisibility', 'off');
|
|
|
|
plot([0, 2.2], [-specs_dy_peak, -specs_dy_peak], '--', 'color', colors(1,:), 'HandleVisibility', 'off');
|
|
|
|
hold off;
|
|
|
|
xlabel('Time [s]');
|
|
|
|
ylabel('$D_y$ error [nm]')
|
|
|
|
% leg = legend('location', 'northwest', 'FontSize', 8, 'NumColumns', 1);
|
|
|
|
% leg.ItemTokenSize(1) = 15;
|
|
|
|
xlim([0, 2.2]);
|
|
|
|
ylim([-150, 150])
|
|
|
|
|
|
|
|
%% Dz scan at 10um/s - Vertical error
|
|
|
|
figure;
|
|
|
|
yyaxis left
|
|
|
|
hold on;
|
|
|
|
plot(data_dz_10ums.time, 1e9*data_dz_10ums.e_dz, 'DisplayName', '$\epsilon_{D_z}$')
|
|
|
|
plot([0, 2.2], [specs_dz_peak, specs_dz_peak], '--', 'HandleVisibility', 'off');
|
|
|
|
plot([0, 2.2], [-specs_dz_peak, -specs_dz_peak], '--', 'HandleVisibility', 'off');
|
|
|
|
hold off;
|
|
|
|
ylabel('$D_z$ error [nm]');
|
|
|
|
ylim([-100, 100]);
|
|
|
|
yticks([-50:50:50]);
|
|
|
|
yyaxis right
|
|
|
|
hold on;
|
|
|
|
plot(data_dz_10ums.time, 1e6*(data_dz_10ums.Dz_int), 'DisplayName', '$D_z$')
|
|
|
|
plot(data_dz_10ums.time, 1e6*(data_dz_10ums.m_hexa_dz), 'k--', 'DisplayName', 'Setpoint')
|
|
|
|
hold off;
|
|
|
|
xlabel('Time [s]');
|
|
|
|
ylabel('$D_z$ Motion [$\mu$m]');
|
|
|
|
leg = legend('location', 'northwest', 'FontSize', 8, 'NumColumns', 1);
|
|
|
|
leg.ItemTokenSize(1) = 15;
|
|
|
|
xlim([0, 2.2]);
|
|
|
|
ylim([-10, 10]);
|
|
|
|
|
|
|
|
%% Dz scan at 10um/s - Ry error
|
|
|
|
figure;
|
|
|
|
hold on;
|
|
|
|
plot(data_dz_10ums.time, 1e6*data_dz_10ums.e_ry, 'DisplayName', sprintf('$\\epsilon R_y: %.2f \\mu$rad RMS', 1e6*rms(data_dz_10ums.e_ry)))
|
|
|
|
plot([0, 2.2], [specs_ry_peak, specs_ry_peak], '--', 'color', colors(1,:), 'HandleVisibility', 'off');
|
|
|
|
plot([0, 2.2], [-specs_ry_peak, -specs_ry_peak], '--', 'color', colors(1,:), 'HandleVisibility', 'off');
|
|
|
|
hold off;
|
|
|
|
xlabel('Time [s]');
|
|
|
|
ylabel('$R_y$ error [$\mu$rad]')
|
|
|
|
% leg = legend('location', 'north', 'FontSize', 8, 'NumColumns', 1);
|
|
|
|
% leg.ItemTokenSize(1) = 15;
|
|
|
|
xlim([0, 2.2]);
|
|
|
|
ylim([-2, 2]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
% #+name: fig:test_id31_dz_scan_10ums
|
2025-02-06 17:05:29 +01:00
|
|
|
% #+caption: $D_z$ scan at a velocity of $10\,\mu m/s$. $D_z$ setpoint, measured position and error are shown in (\subref{fig:test_id31_dz_scan_10ums_dz}). Errors in $D_y$ and $R_y$ are respectively shown in (\subref{fig:test_id31_dz_scan_10ums_dy}) and (\subref{fig:test_id31_dz_scan_10ums_ry})
|
2025-02-04 14:10:54 +01:00
|
|
|
% #+attr_latex: :options [htbp]
|
|
|
|
% #+begin_figure
|
|
|
|
% #+attr_latex: :caption \subcaption{\label{fig:test_id31_dz_scan_10ums_dy}$D_y$}
|
|
|
|
% #+attr_latex: :options {0.33\textwidth}
|
|
|
|
% #+begin_subfigure
|
|
|
|
% #+attr_latex: :scale 1
|
|
|
|
% [[file:figs/test_id31_dz_scan_10ums_dy.png]]
|
|
|
|
% #+end_subfigure
|
|
|
|
% #+attr_latex: :caption \subcaption{\label{fig:test_id31_dz_scan_10ums_dz}$D_z$}
|
|
|
|
% #+attr_latex: :options {0.33\textwidth}
|
|
|
|
% #+begin_subfigure
|
|
|
|
% #+attr_latex: :scale 1
|
|
|
|
% [[file:figs/test_id31_dz_scan_10ums_dz.png]]
|
|
|
|
% #+end_subfigure
|
|
|
|
% #+attr_latex: :caption \subcaption{\label{fig:test_id31_dz_scan_10ums_ry}$R_y$}
|
|
|
|
% #+attr_latex: :options {0.33\textwidth}
|
|
|
|
% #+begin_subfigure
|
|
|
|
% #+attr_latex: :scale 1
|
|
|
|
% [[file:figs/test_id31_dz_scan_10ums_ry.png]]
|
|
|
|
% #+end_subfigure
|
|
|
|
% #+end_figure
|
|
|
|
|
2025-02-06 17:05:29 +01:00
|
|
|
% A subsequent scan at $100,\mu m/s$ - the maximum velocity for high-precision $D_z$ scans[fn:test_id31_8] - maintains positioning errors within specifications during the constant velocity phase, with deviations occurring only during acceleration and deceleration phases (Figure ref:fig:test_id31_dz_scan_100ums).
|
|
|
|
% Since detectors typically operate only during the constant velocity phase, these transient deviations do not compromise the measurement quality.
|
|
|
|
% However, performance during acceleration phases could be enhanced through the implementation of feedforward control.
|
2025-02-04 14:10:54 +01:00
|
|
|
|
|
|
|
|
|
|
|
%% Dz scan at 100um/s - Lateral error
|
|
|
|
figure;
|
|
|
|
hold on;
|
|
|
|
plot(data_dz_100ums.time, 1e9*data_dz_100ums.e_dy, 'DisplayName', sprintf('$\\epsilon D_y = %.0f$ nm RMS', 1e9*rms(data_dz_100ums.e_dy)))
|
|
|
|
plot([0, 2.2], [specs_dy_peak, specs_dy_peak], '--', 'color', colors(1,:), 'HandleVisibility', 'off');
|
|
|
|
plot([0, 2.2], [-specs_dy_peak, -specs_dy_peak], '--', 'color', colors(1,:), 'HandleVisibility', 'off');
|
|
|
|
hold off;
|
|
|
|
xlabel('Time [s]');
|
|
|
|
ylabel('$D_y$ error [nm]')
|
|
|
|
% legend('location', 'southeast', 'FontSize', 8, 'NumColumns', 1);
|
|
|
|
xlim([0, 2.2]);
|
|
|
|
ylim([-150, 150])
|
|
|
|
|
|
|
|
%% Dz scan at 100um/s - Vertical error
|
|
|
|
figure;
|
|
|
|
yyaxis left
|
|
|
|
hold on;
|
|
|
|
plot(data_dz_100ums.time, 1e9*data_dz_100ums.e_dz, 'DisplayName', '$\epsilon_{d_z}$')
|
|
|
|
plot([0, 2.2], [specs_dz_peak, specs_dz_peak], '--', 'HandleVisibility', 'off');
|
|
|
|
plot([0, 2.2], [-specs_dz_peak, -specs_dz_peak], '--', 'HandleVisibility', 'off');
|
|
|
|
hold off;
|
|
|
|
ylabel('$D_z$ error [nm]');
|
|
|
|
ylim([-100, 100]);
|
|
|
|
yticks([-50:50:50]);
|
|
|
|
yyaxis right
|
|
|
|
hold on;
|
|
|
|
plot(data_dz_100ums.time, 1e6*(data_dz_100ums.Dz_int), 'DisplayName', '$D_z$')
|
|
|
|
plot(data_dz_100ums.time, 1e6*(data_dz_100ums.m_hexa_dz), 'k--', 'DisplayName', 'Setpoint')
|
|
|
|
hold off;
|
|
|
|
xlabel('Time [s]');
|
|
|
|
ylabel('$D_z$ Motion [$\mu$m]');
|
|
|
|
leg = legend('location', 'northwest', 'FontSize', 8, 'NumColumns', 1);
|
|
|
|
leg.ItemTokenSize(1) = 15;
|
|
|
|
xlim([0, 2.2]);
|
|
|
|
ylim([-100, 100]);
|
|
|
|
|
|
|
|
%% Dz scan at 100um/s - Tilt error
|
|
|
|
figure;
|
|
|
|
hold on;
|
|
|
|
plot(data_dz_100ums.time, 1e6*data_dz_100ums.e_ry, 'DisplayName', sprintf('$\\epsilon R_y = %.2f \\mu$rad RMS', 1e6*rms(data_dz_100ums.e_ry)))
|
|
|
|
plot([0, 2.2], [specs_ry_peak, specs_ry_peak], '--', 'color', colors(1,:), 'HandleVisibility', 'off');
|
|
|
|
plot([0, 2.2], [-specs_ry_peak, -specs_ry_peak], '--', 'color', colors(1,:), 'HandleVisibility', 'off');
|
|
|
|
hold off;
|
|
|
|
xlabel('Time [s]');
|
|
|
|
ylabel('$R_y$ error [$\mu$rad]')
|
|
|
|
% legend('location', 'southeast', 'FontSize', 8, 'NumColumns', 1);
|
|
|
|
xlim([0, 2.2]);
|
|
|
|
ylim([-2, 2])
|
|
|
|
|
|
|
|
% Slow scan
|
|
|
|
|
|
|
|
% Initial testing utilized a scanning velocity of $10,\mu m/s$, which is typical for these experiments.
|
|
|
|
% Figure ref:fig:test_id31_dy_10ums compares the positioning errors between open-loop (without NASS) and closed-loop operation.
|
|
|
|
% In the scanning direction, open-loop measurements reveal periodic errors (Figure ref:fig:test_id31_dy_10ums_dy) attributable to the $T_y$ stage's stepper motor.
|
2025-02-06 17:05:29 +01:00
|
|
|
% These micro-stepping errors, which are inherent to stepper motor operation, occur 200 times per motor rotation with approximately $1\,\text{mrad}$ angular error amplitude.
|
2025-02-04 14:10:54 +01:00
|
|
|
% Given the $T_y$ stage's lead screw pitch of $2\,mm$, these errors manifest as $10\,\mu m$ periodic oscillations with $\approx 300\,nm$ amplitude, which can indeed be seen in the open-loop measurements (Figure ref:fig:test_id31_dy_10ums_dy).
|
|
|
|
|
2025-02-06 17:05:29 +01:00
|
|
|
% In the vertical direction (Figure ref:fig:test_id31_dy_10ums_dz), open-loop errors likely stem from metrology measurement error because the top interferometer points at a spherical target surface (see Figure ref:fig:test_id31_xy_map_sphere).
|
|
|
|
% Under closed-loop control, positioning errors remain within specifications in all directions.
|
2025-02-04 14:10:54 +01:00
|
|
|
|
|
|
|
|
|
|
|
%% Slow Ty scan (10um/s) - OL
|
|
|
|
data_ty_ol_10ums = load("2023-08-21_20-05_ty_scan_m1_open_loop_slow.mat");
|
|
|
|
data_ty_ol_10ums.time = Ts*[0:length(data_ty_ol_10ums.Dy_int)-1];
|
|
|
|
|
|
|
|
%% Slow Ty scan (10um/s) - CL
|
|
|
|
data_ty_cl_10ums = load("2023-08-21_20-07_ty_scan_m1_cf_closed_loop_slow.mat");
|
|
|
|
data_ty_cl_10ums.time = Ts*[0:length(data_ty_cl_10ums.Dy_int)-1];
|
|
|
|
|
|
|
|
%% Ty scan (at 10um/s) - Dy errors
|
|
|
|
figure;
|
|
|
|
hold on;
|
|
|
|
plot(1e6*data_ty_ol_10ums.Ty, 1e6*detrend(data_ty_ol_10ums.e_dy, 0), ...
|
|
|
|
'DisplayName', 'Open-loop')
|
|
|
|
plot(1e6*data_ty_cl_10ums.Ty, 1e6*detrend(data_ty_cl_10ums.e_dy, 0), ...
|
|
|
|
'DisplayName', 'Closed-loop')
|
|
|
|
plot([-100, 100], 1e-3*[specs_dy_peak, specs_dy_peak], 'k--', 'DisplayName', 'Specifications');
|
|
|
|
plot([-100, 100], 1e-3*[-specs_dy_peak, -specs_dy_peak], 'k--', 'HandleVisibility', 'off');
|
|
|
|
hold off;
|
|
|
|
xlabel('Ty position [$\mu$m]');
|
|
|
|
ylabel('$D_y$ error [$\mu$m]');
|
|
|
|
xlim([-100, 100])
|
|
|
|
leg = legend('location', 'northeast', 'FontSize', 8, 'NumColumns', 1);
|
|
|
|
leg.ItemTokenSize(1) = 15;
|
|
|
|
|
|
|
|
%% Ty scan (at 10um/s) - Dz and Ry errors
|
|
|
|
figure;
|
|
|
|
hold on;
|
|
|
|
plot(1e6*data_ty_ol_10ums.Ty, 1e6*detrend(data_ty_ol_10ums.e_dz, 0), ...
|
|
|
|
'DisplayName', 'Open-loop')
|
|
|
|
plot(1e6*data_ty_cl_10ums.Ty, 1e6*detrend(data_ty_cl_10ums.e_dz, 0), ...
|
|
|
|
'DisplayName', 'Closed-loop')
|
|
|
|
plot([-100, 100], 1e-3*[specs_dz_peak, specs_dz_peak], 'k--', 'DisplayName', 'Specifications');
|
|
|
|
plot([-100, 100], 1e-3*[-specs_dz_peak, -specs_dz_peak], 'k--', 'HandleVisibility', 'off');
|
|
|
|
hold off;
|
|
|
|
xlabel('Ty position [$\mu$m]');
|
|
|
|
ylabel('$D_z$ error [$\mu$m]');
|
|
|
|
xlim([-100, 100])
|
|
|
|
ylim([-0.4, 0.4])
|
|
|
|
leg = legend('location', 'northeast', 'FontSize', 8, 'NumColumns', 1);
|
|
|
|
leg.ItemTokenSize(1) = 15;
|
|
|
|
|
|
|
|
figure;
|
|
|
|
hold on;
|
|
|
|
plot(1e6*data_ty_ol_10ums.Ty, 1e6*data_ty_ol_10ums.e_ry, ...
|
|
|
|
'DisplayName', 'Open-loop')
|
|
|
|
plot(1e6*data_ty_cl_10ums.Ty, 1e6*data_ty_cl_10ums.e_ry, ...
|
|
|
|
'DisplayName', 'Closed-loop')
|
|
|
|
plot([-100, 100], [specs_ry_peak, specs_ry_peak], 'k--', 'DisplayName', 'Specifications');
|
|
|
|
plot([-100, 100], [-specs_ry_peak, -specs_ry_peak], 'k--', 'HandleVisibility', 'off');
|
|
|
|
hold off;
|
|
|
|
xlabel('Ty position [$\mu$m]');
|
|
|
|
ylabel('$R_y$ error [$\mu$rad]');
|
|
|
|
xlim([-100, 100]);
|
|
|
|
ylim([-10, 10])
|
|
|
|
leg = legend('location', 'northwest', 'FontSize', 8, 'NumColumns', 1);
|
|
|
|
leg.ItemTokenSize(1) = 15;
|
|
|
|
|
|
|
|
% Fast Scan
|
|
|
|
|
2025-02-06 17:05:29 +01:00
|
|
|
% The system performance was evaluated at an increased scanning velocity of $100\,\mu m/s$, and the results are presented in Figure ref:fig:test_id31_dy_100ums.
|
2025-02-04 14:10:54 +01:00
|
|
|
% At this velocity, the micro-stepping errors generate $10\,\text{Hz}$ vibrations, which are further amplified by micro-station resonances.
|
2025-02-06 17:05:29 +01:00
|
|
|
% These vibrations exceeded the NASS feedback controller bandwidth, resulting in limited attenuation under closed-loop control.
|
2025-02-04 14:10:54 +01:00
|
|
|
% This limitation exemplifies why stepper motors are suboptimal for "long-stroke/short-stroke" systems requiring precise scanning performance [[cite:&dehaeze22_fastj_uhv]].
|
|
|
|
|
|
|
|
% Two potential solutions exist for improving high-velocity scanning performance.
|
2025-02-06 17:05:29 +01:00
|
|
|
% First, the $T_y$ stage's stepper motor could be replaced by a three-phase torque motor.
|
2025-02-04 14:10:54 +01:00
|
|
|
% Alternatively, since closed-loop errors in $D_z$ and $R_y$ directions remain within specifications (Figures ref:fig:test_id31_dy_100ums_dz and ref:fig:test_id31_dy_100ums_ry), detector triggering could be based on measured $D_y$ position rather than time or $T_y$ setpoint, reducing sensitivity to $D_y$ vibrations.
|
2025-02-06 17:05:29 +01:00
|
|
|
% For applications requiring small $D_y$ scans, the nano-hexapod can be used exclusively, although with limited stroke capability.
|
2025-02-04 14:10:54 +01:00
|
|
|
|
|
|
|
|
|
|
|
%% Fast Ty scan (100um/s) - OL
|
|
|
|
data_ty_ol_100ums = load("2023-08-21_20-05_ty_scan_m1_open_loop.mat");
|
|
|
|
data_ty_ol_100ums.time = Ts*[0:length(data_ty_ol_100ums.Dy_int)-1];
|
|
|
|
|
|
|
|
%% Fast Ty scan (100um/s) - CL
|
|
|
|
data_ty_cl_100ums = load("2023-08-21_20-07_ty_scan_m1_cf_closed_loop.mat");
|
|
|
|
data_ty_cl_100ums.time = Ts*[0:length(data_ty_cl_100ums.Dy_int)-1];
|
|
|
|
|
|
|
|
%% Ty scan (at 100um/s) - Dy errors
|
|
|
|
figure;
|
|
|
|
hold on;
|
|
|
|
plot(1e6*data_ty_ol_100ums.Ty, 1e6*detrend(data_ty_ol_100ums.e_dy, 0), ...
|
|
|
|
'DisplayName', 'Open-loop')
|
|
|
|
plot(1e6*data_ty_cl_100ums.Ty, 1e6*detrend(data_ty_cl_100ums.e_dy, 0), ...
|
|
|
|
'DisplayName', 'Closed-loop')
|
|
|
|
plot([-100, 100], 1e-3*[specs_dy_peak, specs_dy_peak], 'k--', 'DisplayName', 'Specifications');
|
|
|
|
plot([-100, 100], 1e-3*[-specs_dy_peak, -specs_dy_peak], 'k--', 'HandleVisibility', 'off');
|
|
|
|
hold off;
|
|
|
|
xlabel('Ty position [$\mu$m]');
|
|
|
|
ylabel('$D_y$ error [$\mu$m]');
|
|
|
|
xlim([-100, 100]);
|
|
|
|
ylim([-3, 3]);
|
|
|
|
leg = legend('location', 'southwest', 'FontSize', 8, 'NumColumns', 1);
|
|
|
|
leg.ItemTokenSize(1) = 15;
|
|
|
|
|
|
|
|
%% Ty scan (at 100um/s) - Dz and Ry errors
|
|
|
|
figure;
|
|
|
|
hold on;
|
|
|
|
plot(1e6*data_ty_ol_100ums.Ty, 1e6*detrend(data_ty_ol_100ums.e_dz, 0), ...
|
|
|
|
'DisplayName', 'Open-loop')
|
|
|
|
plot(1e6*data_ty_cl_100ums.Ty, 1e6*detrend(data_ty_cl_100ums.e_dz, 0), ...
|
|
|
|
'DisplayName', 'Closed-loop')
|
|
|
|
plot([-100, 100], 1e-3*[specs_dz_peak, specs_dz_peak], 'k--', 'DisplayName', 'Specifications');
|
|
|
|
plot([-100, 100], 1e-3*[-specs_dz_peak, -specs_dz_peak], 'k--', 'HandleVisibility', 'off');
|
|
|
|
hold off;
|
|
|
|
xlabel('Ty position [$\mu$m]');
|
|
|
|
ylabel('$D_z$ error [$\mu$m]');
|
|
|
|
xlim([-100, 100]);
|
|
|
|
ylim([-0.4, 0.4]);
|
|
|
|
leg = legend('location', 'southeast', 'FontSize', 8, 'NumColumns', 1);
|
|
|
|
leg.ItemTokenSize(1) = 15;
|
|
|
|
|
|
|
|
figure;
|
|
|
|
hold on;
|
|
|
|
plot(1e6*data_ty_ol_100ums.Ty, 1e6*data_ty_ol_100ums.e_ry, ...
|
|
|
|
'DisplayName', 'Open-loop')
|
|
|
|
plot(1e6*data_ty_cl_100ums.Ty, 1e6*data_ty_cl_100ums.e_ry, ...
|
|
|
|
'DisplayName', 'Closed-loop')
|
|
|
|
plot([-100, 100], [specs_ry_peak, specs_ry_peak], 'k--', 'DisplayName', 'Specifications');
|
|
|
|
plot([-100, 100], [-specs_ry_peak, -specs_ry_peak], 'k--', 'HandleVisibility', 'off');
|
|
|
|
hold off;
|
|
|
|
xlabel('Ty position [$\mu$m]');
|
|
|
|
ylabel('$R_y$ error [$\mu$rad]');
|
|
|
|
xlim([-100, 100]);
|
|
|
|
ylim([-10, 10])
|
|
|
|
leg = legend('location', 'northwest', 'FontSize', 8, 'NumColumns', 1);
|
|
|
|
leg.ItemTokenSize(1) = 15;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
% #+name: fig:test_id31_dy_100ums
|
|
|
|
% #+caption: Open-Loop (in blue) and Closed-loop (i.e. using the NASS, in red) during a $100\,\mu m/s$ scan with the $T_y$ stage. Errors in $D_y$ is shown in (\subref{fig:test_id31_dy_100ums_dy}).
|
|
|
|
% #+attr_latex: :options [htbp]
|
|
|
|
% #+begin_figure
|
|
|
|
% #+attr_latex: :caption \subcaption{\label{fig:test_id31_dy_100ums_dy} $D_y$}
|
|
|
|
% #+attr_latex: :options {0.33\textwidth}
|
|
|
|
% #+begin_subfigure
|
|
|
|
% #+attr_latex: :scale 1
|
|
|
|
% [[file:figs/test_id31_dy_100ums_dy.png]]
|
|
|
|
% #+end_subfigure
|
|
|
|
% #+attr_latex: :caption \subcaption{\label{fig:test_id31_dy_100ums_dz} $D_z$}
|
|
|
|
% #+attr_latex: :options {0.33\textwidth}
|
|
|
|
% #+begin_subfigure
|
|
|
|
% #+attr_latex: :scale 1
|
|
|
|
% [[file:figs/test_id31_dy_100ums_dz.png]]
|
|
|
|
% #+end_subfigure
|
|
|
|
% #+attr_latex: :caption \subcaption{\label{fig:test_id31_dy_100ums_ry} $R_y$}
|
|
|
|
% #+attr_latex: :options {0.33\textwidth}
|
|
|
|
% #+begin_subfigure
|
|
|
|
% #+attr_latex: :scale 1
|
|
|
|
% [[file:figs/test_id31_dy_100ums_ry.png]]
|
|
|
|
% #+end_subfigure
|
|
|
|
% #+end_figure
|
|
|
|
|
|
|
|
|
|
|
|
%% Compute errors for Dy scans
|
|
|
|
i_ty_ol_10ums = data_ty_ol_10ums.Ty > data_ty_ol_10ums.Ty(1) & data_ty_ol_10ums.Ty < data_ty_ol_10ums.Ty(end);
|
|
|
|
i_ty_cl_10ums = data_ty_cl_10ums.Ty > data_ty_cl_10ums.Ty(1) & data_ty_cl_10ums.Ty < data_ty_cl_10ums.Ty(end);
|
|
|
|
i_ty_ol_100ums = data_ty_ol_100ums.Ty > data_ty_ol_100ums.Ty(1) & data_ty_ol_100ums.Ty < data_ty_ol_100ums.Ty(end);
|
|
|
|
i_ty_cl_100ums = data_ty_cl_100ums.Ty > data_ty_cl_100ums.Ty(1) & data_ty_cl_100ums.Ty < data_ty_cl_100ums.Ty(end);
|
|
|
|
|
|
|
|
% RMS error
|
|
|
|
data_ty_ol_10ums.Dy_rms = rms(detrend(data_ty_ol_10ums.e_dy(i_ty_ol_10ums), 0));
|
|
|
|
data_ty_ol_10ums.Dz_rms = rms(detrend(data_ty_ol_10ums.e_dz(i_ty_ol_10ums), 0));
|
|
|
|
data_ty_ol_10ums.Ry_rms = rms(detrend(data_ty_ol_10ums.e_ry(i_ty_ol_10ums), 0));
|
|
|
|
|
|
|
|
data_ty_cl_10ums.Dy_rms = rms(detrend(data_ty_cl_10ums.e_dy(i_ty_cl_10ums), 0));
|
|
|
|
data_ty_cl_10ums.Dz_rms = rms(detrend(data_ty_cl_10ums.e_dz(i_ty_cl_10ums), 0));
|
|
|
|
data_ty_cl_10ums.Ry_rms = rms(detrend(data_ty_cl_10ums.e_ry(i_ty_cl_10ums), 0));
|
|
|
|
|
|
|
|
data_ty_ol_100ums.Dy_rms = rms(detrend(data_ty_ol_100ums.e_dy(i_ty_ol_100ums), 0));
|
|
|
|
data_ty_ol_100ums.Dz_rms = rms(detrend(data_ty_ol_100ums.e_dz(i_ty_ol_100ums), 0));
|
|
|
|
data_ty_ol_100ums.Ry_rms = rms(detrend(data_ty_ol_100ums.e_ry(i_ty_ol_100ums), 0));
|
|
|
|
|
|
|
|
data_ty_cl_100ums.Dy_rms = rms(detrend(data_ty_cl_100ums.e_dy(i_ty_cl_100ums), 0));
|
|
|
|
data_ty_cl_100ums.Dz_rms = rms(detrend(data_ty_cl_100ums.e_dz(i_ty_cl_100ums), 0));
|
|
|
|
data_ty_cl_100ums.Ry_rms = rms(detrend(data_ty_cl_100ums.e_ry(i_ty_cl_100ums), 0));
|
|
|
|
|
|
|
|
% Diffraction Tomography
|
|
|
|
% <<ssec:test_id31_scans_diffraction_tomo>>
|
2025-02-06 17:05:29 +01:00
|
|
|
|
2025-02-04 14:10:54 +01:00
|
|
|
% In diffraction tomography experiments, the micro-station executes combined motions: continuous rotation around the $R_z$ axis while performing lateral scans along $D_y$.
|
|
|
|
% For this validation, the spindle maintained a constant rotational velocity of $6\,\text{deg/s}$ while the nano-hexapod executed the lateral scanning motion.
|
|
|
|
% To avoid high-frequency vibrations typically induced by the stepper motor, the $T_y$ stage was not utilized, which constrained the scanning range to approximately $\pm 100\,\mu m/s$.
|
2025-02-06 17:05:29 +01:00
|
|
|
% The system performance was evaluated at three lateral scanning velocities: $0.1\,mm/s$, $0.5\,mm/s$, and $1\,mm/s$. Figure ref:fig:test_id31_diffraction_tomo_setpoint presents both the $D_y$ position setpoints and the corresponding measured $D_y$ positions for all tested velocities.
|
2025-02-04 14:10:54 +01:00
|
|
|
|
|
|
|
|
|
|
|
%% 100um/s - Robust controller
|
|
|
|
data_dt_100ums = load("2023-08-18_17-12_diffraction_tomo_m0.mat");
|
|
|
|
t = Ts*[0:length(data_dt_100ums.Dy_int)-1];
|
|
|
|
data_dt_100ums = structfun(@(field) field(t>1.0861),data_dt_100ums, 'UniformOutput', false);
|
|
|
|
data_dt_100ums.time = Ts*[0:length(data_dt_100ums.Dy_int)-1];
|
|
|
|
|
|
|
|
%% 500um/s - Complementary filters
|
|
|
|
data_dt_500ums = load("2023-08-21_15-15_diffraction_tomo_m0_fast_cf.mat");
|
|
|
|
t = Ts*[0:length(data_dt_500ums.Dy_int)-1];
|
|
|
|
data_dt_500ums = structfun(@(field) field(t>0.275),data_dt_500ums, 'UniformOutput', false);
|
|
|
|
data_dt_500ums.time = Ts*[0:length(data_dt_500ums.Dy_int)-1];
|
|
|
|
|
|
|
|
%% 1mm/s - Complementary filters
|
|
|
|
data_dt_1000ums = load("2023-08-21_15-16_diffraction_tomo_m0_fast_cf.mat");
|
|
|
|
t = Ts*[0:length(data_dt_1000ums.Dy_int)-1];
|
|
|
|
data_dt_1000ums = structfun(@(field) field(t>0.19),data_dt_1000ums, 'UniformOutput', false);
|
|
|
|
data_dt_1000ums.time = Ts*[0:length(data_dt_1000ums.Dy_int)-1];
|
|
|
|
|
|
|
|
%% Dy motion for several configured velocities
|
|
|
|
figure;
|
|
|
|
hold on;
|
|
|
|
plot(data_dt_1000ums.time, 1e6*data_dt_1000ums.Dy_int, 'color', colors(1,:), ...
|
|
|
|
'DisplayName', '$1 mm/s$')
|
|
|
|
plot(data_dt_1000ums.time, 1e6*data_dt_1000ums.m_hexa_dy, 'k--', ...
|
|
|
|
'HandleVisibility', 'off')
|
|
|
|
plot(data_dt_500ums.time, 1e6*data_dt_500ums.Dy_int, 'color', colors(2,:), ...
|
|
|
|
'DisplayName', '$0.5 mm/s$')
|
|
|
|
plot(data_dt_500ums.time, 1e6*data_dt_500ums.m_hexa_dy, 'k--', ...
|
|
|
|
'HandleVisibility', 'off')
|
|
|
|
plot(data_dt_100ums.time, 1e6*data_dt_100ums.Dy_int, 'color', colors(3,:), ...
|
|
|
|
'DisplayName', '$0.1 mm/s$')
|
|
|
|
plot(data_dt_100ums.time, 1e6*data_dt_100ums.m_hexa_dy, 'k--', ...
|
|
|
|
'DisplayName', 'Setpoint')
|
|
|
|
hold off;
|
|
|
|
xlim([0, 4]);
|
|
|
|
ylim([-110, 110]);
|
|
|
|
xlabel('Time [s]');
|
|
|
|
ylabel('$D_y$ position [$\mu$m]')
|
|
|
|
legend('location', 'southeast', 'FontSize', 8, 'NumColumns', 1);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
% #+name: fig:test_id31_diffraction_tomo_setpoint
|
|
|
|
% #+caption: Dy motion for several configured velocities
|
|
|
|
% #+RESULTS:
|
|
|
|
% [[file:figs/test_id31_diffraction_tomo_setpoint.png]]
|
|
|
|
|
|
|
|
% The positioning errors measured along $D_y$, $D_z$, and $R_y$ directions are displayed in Figure ref:fig:test_id31_diffraction_tomo.
|
|
|
|
% The system maintained positioning errors within specifications for both $D_z$ and $R_y$ (Figures ref:fig:test_id31_diffraction_tomo_dz and ref:fig:test_id31_diffraction_tomo_ry).
|
2025-02-06 17:05:29 +01:00
|
|
|
% However, the lateral positioning errors exceeded specifications during the acceleration and deceleration phases (Figure ref:fig:test_id31_diffraction_tomo_dy).
|
|
|
|
% These large errors occurred only during $\approx 20\,ms$ intervals; thus, the issue could be addressed by implementing a corresponding delay in detector integration.
|
|
|
|
% Alternatively, a feedforward controller could improve the lateral positioning accuracy during these transient phases.
|
2025-02-04 14:10:54 +01:00
|
|
|
|
|
|
|
|
|
|
|
%% Diffraction Tomography - Dy errors for several configured velocities
|
|
|
|
figure;
|
|
|
|
hold on;
|
|
|
|
plot(data_dt_1000ums.time, 1e9*(data_dt_1000ums.Dy_int - data_dt_1000ums.m_hexa_dy), ...
|
|
|
|
'DisplayName', '$1 mm/s$')
|
|
|
|
plot(data_dt_500ums.time, 1e9*(data_dt_500ums.Dy_int - data_dt_500ums.m_hexa_dy), ...
|
|
|
|
'DisplayName', '$0.5 mm/s$')
|
|
|
|
plot(data_dt_100ums.time, 1e9*(data_dt_100ums.Dy_int - data_dt_100ums.m_hexa_dy), ...
|
|
|
|
'DisplayName', '$0.1 mm/s$')
|
|
|
|
plot([0, 6.2], [specs_dy_peak, specs_dy_peak], 'k--', 'DisplayName', 'Specs');
|
|
|
|
plot([0, 6.2], [-specs_dy_peak, -specs_dy_peak], 'k--', 'HandleVisibility', 'off');
|
|
|
|
hold off;
|
|
|
|
xlim([0, 3]);
|
|
|
|
xlabel('Time [s]');
|
|
|
|
ylabel('$D_y$ error [nm]')
|
|
|
|
leg = legend('location', 'northeast', 'FontSize', 8, 'NumColumns', 1);
|
|
|
|
leg.ItemTokenSize(1) = 15;
|
|
|
|
|
|
|
|
%% Diffraction Tomography - Dz errors for several configured velocities
|
|
|
|
figure;
|
|
|
|
hold on;
|
|
|
|
plot(data_dt_1000ums.time, 1e9*data_dt_1000ums.Dz_int, ...
|
|
|
|
'DisplayName', '$1 mm/s$')
|
|
|
|
plot(data_dt_500ums.time, 1e9*data_dt_500ums.Dz_int, ...
|
|
|
|
'DisplayName', '$0.5 mm/s$')
|
|
|
|
plot(data_dt_100ums.time, 1e9*data_dt_100ums.Dz_int, ...
|
|
|
|
'DisplayName', '$0.1 mm/s$')
|
|
|
|
plot([0, 6.2], [specs_dz_peak, specs_dz_peak], 'k--', 'DisplayName', 'Specs');
|
|
|
|
plot([0, 6.2], [-specs_dz_peak, -specs_dz_peak], 'k--', 'HandleVisibility', 'off');
|
|
|
|
hold off;
|
|
|
|
xlim([0, 4]);
|
|
|
|
ylim([-100, 100])
|
|
|
|
xlabel('Time [s]');
|
|
|
|
ylabel('$D_z$ position [nm]')
|
|
|
|
leg = legend('location', 'southwest', 'FontSize', 8, 'NumColumns', 1);
|
|
|
|
leg.ItemTokenSize(1) = 15;
|
|
|
|
|
|
|
|
%% Diffraction Tomography - Ry errors for several configured velocities
|
|
|
|
figure;
|
|
|
|
hold on;
|
|
|
|
plot(data_dt_1000ums.time, 1e6*data_dt_1000ums.Ry_int, ...
|
|
|
|
'DisplayName', '$1 mm/s$')
|
|
|
|
plot(data_dt_500ums.time, 1e6*data_dt_500ums.Ry_int, ...
|
|
|
|
'DisplayName', '$0.5 mm/s$')
|
|
|
|
plot(data_dt_100ums.time, 1e6*data_dt_100ums.Ry_int, ...
|
|
|
|
'DisplayName', '$0.1 mm/s$')
|
|
|
|
plot([0, 6.2], [specs_ry_peak, specs_ry_peak], 'k--', 'DisplayName', 'Specs');
|
|
|
|
plot([0, 6.2], [-specs_ry_peak, -specs_ry_peak], 'k--', 'HandleVisibility', 'off');
|
|
|
|
hold off;
|
|
|
|
xlim([0, 4]);
|
|
|
|
ylim([-1.5, 1.5])
|
|
|
|
xlabel('Time [s]');
|
|
|
|
ylabel('$R_y$ position [$\mu$rad]')
|
|
|
|
leg = legend('location', 'southwest', 'FontSize', 8, 'NumColumns', 1);
|
|
|
|
leg.ItemTokenSize(1) = 15;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
% #+name: fig:test_id31_diffraction_tomo
|
|
|
|
% #+caption: Diffraction tomography scans (combined $R_z$ and $D_y$ motions) at several $D_y$ velocities ($R_z$ rotational velocity is $6\,\text{deg/s}$).
|
|
|
|
% #+attr_latex: :options [htbp]
|
|
|
|
% #+begin_figure
|
|
|
|
% #+attr_latex: :caption \subcaption{\label{fig:test_id31_diffraction_tomo_dy} $D_y$}
|
|
|
|
% #+attr_latex: :options {0.33\textwidth}
|
|
|
|
% #+begin_subfigure
|
|
|
|
% #+attr_latex: :scale 1
|
|
|
|
% [[file:figs/test_id31_diffraction_tomo_dy.png]]
|
|
|
|
% #+end_subfigure
|
|
|
|
% #+attr_latex: :caption \subcaption{\label{fig:test_id31_diffraction_tomo_dz} $D_z$}
|
|
|
|
% #+attr_latex: :options {0.33\textwidth}
|
|
|
|
% #+begin_subfigure
|
|
|
|
% #+attr_latex: :scale 1
|
|
|
|
% [[file:figs/test_id31_diffraction_tomo_dz.png]]
|
|
|
|
% #+end_subfigure
|
|
|
|
% #+attr_latex: :caption \subcaption{\label{fig:test_id31_diffraction_tomo_ry} $R_y$}
|
|
|
|
% #+attr_latex: :options {0.33\textwidth}
|
|
|
|
% #+begin_subfigure
|
|
|
|
% #+attr_latex: :scale 1
|
|
|
|
% [[file:figs/test_id31_diffraction_tomo_ry.png]]
|
|
|
|
% #+end_subfigure
|
|
|
|
% #+end_figure
|
|
|
|
|
|
|
|
|
|
|
|
%% Computation of errors during diffraction tomography experiments
|
|
|
|
% Ignore acceleration and deceleration phases
|
|
|
|
acc_dt = 20e-3; % Acceleration phase to remove [s]
|
|
|
|
acc_n = acc_dt/Ts; % Number of points to delete
|
|
|
|
|
|
|
|
% Determine when the motion starts and stops
|
|
|
|
i_dt_100ums = data_dt_100ums.m_hexa_dy>data_dt_100ums.m_hexa_dy(1) & data_dt_100ums.m_hexa_dy<data_dt_100ums.m_hexa_dy(end);
|
|
|
|
[~, i_acc] = find(diff(i_dt_100ums)>0); % Acceleration phases
|
|
|
|
for i = i_acc
|
|
|
|
i_dt_100ums(i:i+acc_n) = 0;
|
|
|
|
end
|
|
|
|
[~, i_dec] = find(diff(i_dt_100ums)<0); % Deceleration phases
|
|
|
|
for i = i_dec(2:2:end)
|
|
|
|
i_dt_100ums(i-acc_n:i) = 0;
|
|
|
|
end
|
|
|
|
|
|
|
|
i_dt_500ums = data_dt_500ums.m_hexa_dy>data_dt_500ums.m_hexa_dy(1) & data_dt_500ums.m_hexa_dy<data_dt_500ums.m_hexa_dy(end);
|
|
|
|
[~, i_acc] = find(diff(i_dt_500ums)>0); % Acceleration phases
|
|
|
|
for i = i_acc
|
|
|
|
i_dt_500ums(i:i+acc_n) = 0;
|
|
|
|
end
|
|
|
|
[~, i_dec] = find(diff(i_dt_500ums)<0); % Deceleration phases
|
|
|
|
for i = i_dec(2:2:end)
|
|
|
|
i_dt_500ums(i-acc_n:i) = 0;
|
|
|
|
end
|
|
|
|
|
|
|
|
i_dt_1000ums = data_dt_1000ums.m_hexa_dy>data_dt_1000ums.m_hexa_dy(1) & data_dt_1000ums.m_hexa_dy<data_dt_1000ums.m_hexa_dy(end);
|
|
|
|
[~, i_acc] = find(diff(i_dt_1000ums)>0); % Acceleration phases
|
|
|
|
for i = i_acc
|
|
|
|
i_dt_1000ums(i:i+acc_n) = 0;
|
|
|
|
end
|
|
|
|
[~, i_dec] = find(diff(i_dt_1000ums)<0); % Deceleration phases
|
|
|
|
for i = i_dec(2:2:end)
|
|
|
|
i_dt_1000ums(i-acc_n:i) = 0;
|
|
|
|
end
|
|
|
|
|
|
|
|
% RMS error
|
|
|
|
data_dt_100ums.Dy_rms_cl = rms(detrend(data_dt_100ums.Dy_int(i_dt_100ums)-data_dt_100ums.m_hexa_dy(i_dt_100ums), 0));
|
|
|
|
data_dt_100ums.Dz_rms_cl = rms(detrend(data_dt_100ums.Dz_int(i_dt_100ums), 0));
|
|
|
|
data_dt_100ums.Ry_rms_cl = rms(detrend(data_dt_100ums.Ry_int(i_dt_100ums), 0));
|
|
|
|
|
|
|
|
data_dt_500ums.Dy_rms_cl = rms(detrend(data_dt_500ums.Dy_int(i_dt_500ums)-data_dt_500ums.m_hexa_dy(i_dt_500ums), 0));
|
|
|
|
data_dt_500ums.Dz_rms_cl = rms(detrend(data_dt_500ums.Dz_int(i_dt_500ums), 0));
|
|
|
|
data_dt_500ums.Ry_rms_cl = rms(detrend(data_dt_500ums.Ry_int(i_dt_500ums), 0));
|
|
|
|
|
|
|
|
data_dt_1000ums.Dy_rms_cl = rms(detrend(data_dt_1000ums.Dy_int(i_dt_1000ums)-data_dt_1000ums.m_hexa_dy(i_dt_1000ums), 0));
|
|
|
|
data_dt_1000ums.Dz_rms_cl = rms(detrend(data_dt_1000ums.Dz_int(i_dt_1000ums), 0));
|
|
|
|
data_dt_1000ums.Ry_rms_cl = rms(detrend(data_dt_1000ums.Ry_int(i_dt_1000ums), 0));
|
|
|
|
|
|
|
|
% Conclusion
|
2025-02-06 17:05:29 +01:00
|
|
|
% :PROPERTIES:
|
|
|
|
% :UNNUMBERED: t
|
|
|
|
% :END:
|
2025-02-04 14:10:54 +01:00
|
|
|
% <<ssec:test_id31_scans_conclusion>>
|
|
|
|
|
2025-02-06 17:05:29 +01:00
|
|
|
% A comprehensive series of experimental validations was conducted to evaluate the NASS performance over a wide range of typical scientific experiments.
|
2025-02-04 14:10:54 +01:00
|
|
|
% The system demonstrated robust performance in most scenarios, with positioning errors generally remaining within specified tolerances (30 nm RMS in $D_y$, 15 nm RMS in $D_z$, and 250 nrad RMS in $R_y$).
|
|
|
|
|
2025-02-06 17:05:29 +01:00
|
|
|
% For tomography experiments, the NASS successfully maintained good positioning accuracy at rotational velocities up to $180\,\text{deg/s}$ with light payloads, though performance degraded somewhat with heavier masses.
|
|
|
|
% The HAC-LAC control architecture proved particularly effective, with the decentralized IFF providing damping of nano-hexapod suspension modes, while the high authority controller addressed low-frequency disturbances.
|
2025-02-04 14:10:54 +01:00
|
|
|
|
2025-02-06 17:05:29 +01:00
|
|
|
% The vertical scanning capabilities were validated in both step-by-step and continuous motion modes.
|
2025-02-04 14:10:54 +01:00
|
|
|
% The system successfully resolved 10 nm steps with 50 ms detector integration time, while maintaining positioning accuracy during continuous scans at speeds up to $100\,\mu m/s$.
|
|
|
|
|
|
|
|
% For lateral scanning, the system performed well at moderate speeds ($10\,\mu m/s$) but showed limitations at higher velocities ($100\,\mu m/s$) due to stepper motor-induced vibrations in the $T_y$ stage.
|
|
|
|
|
|
|
|
% The most challenging test case - diffraction tomography combining rotation and lateral scanning - demonstrated the system's ability to maintain vertical and angular stability while highlighting some limitations in lateral positioning during rapid accelerations.
|
2025-02-06 17:05:29 +01:00
|
|
|
% These limitations could be addressed through feedforward control or alternative detector triggering strategies.
|
2025-02-04 14:10:54 +01:00
|
|
|
|
|
|
|
% Overall, the experimental results validate the effectiveness of the developed control architecture and demonstrate that the NASS meets most design specifications across a wide range of operating conditions (summarized in Table ref:tab:test_id31_experiments_results_summary).
|
|
|
|
% The identified limitations, primarily related to high-speed lateral scanning and heavy payload handling, provide clear directions for future improvements.
|
|
|
|
|
|
|
|
|
|
|
|
%% Summary of results
|
|
|
|
% 1e9*data_tomo_m0_Wz6.Dy_rms_ol, 1e9*data_tomo_m0_Wz6.Dz_rms_ol, 1e6*data_tomo_m0_Wz6.Ry_rms_ol; % Tomo - OL - 6deg/s - 0kg
|
|
|
|
% 1e9*data_tomo_m0_Wz6.Dy_rms_cl, 1e9*data_tomo_m0_Wz6.Dz_rms_cl, 1e6*data_tomo_m0_Wz6.Ry_rms_cl; % Tomo - CL - 6deg/s - 0kg
|
|
|
|
% 1e9*data_tomo_m1_Wz6.Dy_rms_ol, 1e9*data_tomo_m1_Wz6.Dz_rms_ol, 1e6*data_tomo_m1_Wz6.Ry_rms_ol; % Tomo - OL - 6deg/s - 13kg
|
|
|
|
% 1e9*data_tomo_m1_Wz6.Dy_rms_cl, 1e9*data_tomo_m1_Wz6.Dz_rms_cl, 1e6*data_tomo_m1_Wz6.Ry_rms_cl; % Tomo - CL - 6deg/s - 13kg
|
|
|
|
% 1e9*data_tomo_m2_Wz6.Dy_rms_ol, 1e9*data_tomo_m2_Wz6.Dz_rms_ol, 1e6*data_tomo_m2_Wz6.Ry_rms_ol; % Tomo - OL - 6deg/s - 26kg
|
|
|
|
% 1e9*data_tomo_m2_Wz6.Dy_rms_cl, 1e9*data_tomo_m2_Wz6.Dz_rms_cl, 1e6*data_tomo_m2_Wz6.Ry_rms_cl; % Tomo - CL - 6deg/s - 26kg
|
|
|
|
% 1e9*data_tomo_m3_Wz6.Dy_rms_ol, 1e9*data_tomo_m3_Wz6.Dz_rms_ol, 1e6*data_tomo_m3_Wz6.Ry_rms_ol; % Tomo - OL - 6deg/s - 39kg
|
|
|
|
% 1e9*data_tomo_m3_Wz6.Dy_rms_cl, 1e9*data_tomo_m3_Wz6.Dz_rms_cl, 1e6*data_tomo_m3_Wz6.Ry_rms_cl; % Tomo - CL - 6deg/s - 39kg
|
|
|
|
% 1e9*data_tomo_m0_Wz180.Dy_rms_ol, 1e9*data_tomo_m0_Wz180.Dz_rms_ol, 1e6*data_tomo_m0_Wz180.Ry_rms_ol; % Tomo - OL - 180deg/s - 0kg
|
|
|
|
% 1e9*data_tomo_m0_Wz180.Dy_rms_cl, 1e9*data_tomo_m0_Wz180.Dz_rms_cl, 1e6*data_tomo_m0_Wz180.Ry_rms_cl; % Tomo - CL - 180deg/s - 0kg
|
|
|
|
% 1e9*data_hac_Wz180.Dy_rms_cl, 1e9*data_hac_Wz180.Dz_rms_cl, 1e6*data_hac_Wz180.Ry_rms_cl; % Tomo - CL (high performance HAC) - 180deg/s - 0kg
|
|
|
|
% 1e9*data_ry.Dy_rms_cl, 1e9*data_ry.Dz_rms_cl, 1e6*data_ry.Ry_rms_cl; % Ry 100urad/s
|
|
|
|
% 1e9*data_dz_10ums.Dy_rms_cl, 1e9*data_dz_10ums.Dz_rms_cl, 1e6*data_dz_10ums.Ry_rms_cl; % Dz 10um/s
|
|
|
|
% 1e9*data_dz_100ums.Dy_rms_cl, 1e9*data_dz_100ums.Dz_rms_cl, 1e6*data_dz_100ums.Ry_rms_cl; % Dz 100um/s
|
|
|
|
% 1e9*data_ty_ol_10ums.Dy_rms, 1e9*data_ty_ol_10ums.Dz_rms, 1e6*data_ty_ol_10ums.Ry_rms; % Ty - OL - 10um/s
|
|
|
|
% 1e9*data_ty_cl_10ums.Dy_rms, 1e9*data_ty_cl_10ums.Dz_rms, 1e6*data_ty_cl_10ums.Ry_rms; % Ty - CL - 10um/s
|
|
|
|
% 1e9*data_ty_ol_100ums.Dy_rms, 1e9*data_ty_ol_100ums.Dz_rms, 1e6*data_ty_ol_100ums.Ry_rms; % Ty - OL - 100um/s
|
|
|
|
% 1e9*data_ty_cl_100ums.Dy_rms, 1e9*data_ty_cl_100ums.Dz_rms, 1e6*data_ty_cl_100ums.Ry_rms; % Ty - CL - 100um/s
|
|
|
|
% 1e9*data_dt_100ums.Dy_rms_cl, 1e9*data_dt_100ums.Dz_rms_cl, 1e6*data_dt_100ums.Ry_rms_cl; % Diffraction Tomo - CL - 6deg/s, 100um/s
|
|
|
|
% 1e9*data_dt_500ums.Dy_rms_cl, 1e9*data_dt_500ums.Dz_rms_cl, 1e6*data_dt_500ums.Ry_rms_cl; % Diffraction Tomo - CL - 6deg/s, 500um/s
|
|
|
|
% 1e9*data_dt_1000ums.Dy_rms_cl, 1e9*data_dt_1000ums.Dz_rms_cl, 1e6*data_dt_1000ums.Ry_rms_cl; % Diffraction Tomo - CL - 6deg/s, 1000um/s
|