#+TITLE: Nano Active Stabilization System - Instrumentation :DRAWER: #+LANGUAGE: en #+EMAIL: dehaeze.thomas@gmail.com #+AUTHOR: Dehaeze Thomas #+HTML_LINK_HOME: ../index.html #+HTML_LINK_UP: ../index.html #+HTML_HEAD: #+HTML_HEAD: #+BIND: org-latex-image-default-option "scale=1" #+BIND: org-latex-image-default-width "" #+LaTeX_CLASS: scrreprt #+LaTeX_CLASS_OPTIONS: [a4paper, 10pt, DIV=12, parskip=full, bibliography=totoc] #+LATEX_HEADER: \input{preamble.tex} #+LATEX_HEADER_EXTRA: \input{preamble_extra.tex} #+LATEX_HEADER_EXTRA: \bibliography{nass-instrumentation.bib} #+BIND: org-latex-bib-compiler "biber" #+PROPERTY: header-args:matlab :session *MATLAB* #+PROPERTY: header-args:matlab+ :comments org #+PROPERTY: header-args:matlab+ :exports none #+PROPERTY: header-args:matlab+ :results none #+PROPERTY: header-args:matlab+ :eval no-export #+PROPERTY: header-args:matlab+ :noweb yes #+PROPERTY: header-args:matlab+ :mkdirp yes #+PROPERTY: header-args:matlab+ :output-dir figs #+PROPERTY: header-args:matlab+ :tangle no #+PROPERTY: header-args:latex :headers '("\\usepackage{tikz}" "\\usepackage{import}" "\\import{$HOME/Cloud/tikz/org/}{config.tex}") #+PROPERTY: header-args:latex+ :imagemagick t :fit yes #+PROPERTY: header-args:latex+ :iminoptions -scale 100% -density 150 #+PROPERTY: header-args:latex+ :imoutoptions -quality 100 #+PROPERTY: header-args:latex+ :results file raw replace #+PROPERTY: header-args:latex+ :buffer no #+PROPERTY: header-args:latex+ :tangle no #+PROPERTY: header-args:latex+ :eval no-export #+PROPERTY: header-args:latex+ :exports results #+PROPERTY: header-args:latex+ :mkdirp yes #+PROPERTY: header-args:latex+ :output-dir figs #+PROPERTY: header-args:latex+ :post pdf2svg(file=*this*, ext="png") :END: #+latex: \clearpage * Build :noexport: #+NAME: startblock #+BEGIN_SRC emacs-lisp :results none :tangle no (add-to-list 'org-latex-classes '("scrreprt" "\\documentclass{scrreprt}" ("\\chapter{%s}" . "\\chapter*{%s}") ("\\section{%s}" . "\\section*{%s}") ("\\subsection{%s}" . "\\subsection*{%s}") ("\\paragraph{%s}" . "\\paragraph*{%s}") )) ;; Remove automatic org heading labels (defun my-latex-filter-removeOrgAutoLabels (text backend info) "Org-mode automatically generates labels for headings despite explicit use of `#+LABEL`. This filter forcibly removes all automatically generated org-labels in headings." (when (org-export-derived-backend-p backend 'latex) (replace-regexp-in-string "\\\\label{sec:org[a-f0-9]+}\n" "" text))) (add-to-list 'org-export-filter-headline-functions 'my-latex-filter-removeOrgAutoLabels) ;; Remove all org comments in the output LaTeX file (defun delete-org-comments (backend) (loop for comment in (reverse (org-element-map (org-element-parse-buffer) 'comment 'identity)) do (setf (buffer-substring (org-element-property :begin comment) (org-element-property :end comment)) ""))) (add-hook 'org-export-before-processing-hook 'delete-org-comments) ;; Use no package by default (setq org-latex-packages-alist nil) (setq org-latex-default-packages-alist nil) ;; Do not include the subtitle inside the title (setq org-latex-subtitle-separate t) (setq org-latex-subtitle-format "\\subtitle{%s}") (setq org-export-before-parsing-hook '(org-ref-glossary-before-parsing org-ref-acronyms-before-parsing)) #+END_SRC * Notes :noexport: ** Notes Prefix is =detail_instrumentation= Compilation of the following reports: - [ ] [[file:~/Cloud/work-projects/ID31-NASS/matlab/test-bench-PD200/test-bench-pd200.org][test-bench-PD200]] and [[file:~/Cloud/work-projects/ID31-NASS/matlab/test-bench-piezo-amplifiers/index.org][test-bench-piezo-amplifiers]] (but less useful) - [ ] [[file:~/Cloud/work-projects/ID31-NASS/matlab/test-bench-vionic/test-bench-vionic.org][test-bench-vionic]] - [ ] [[file:~/Cloud/work-projects/ID31-NASS/matlab/test-bench-attocube/test-bench-attocube.org][test-bench-attocube]] - [ ] [[file:~/Cloud/work-projects/ID31-NASS/matlab/nass-simscape/org/noise_budgeting.org][file:~/Cloud/work-projects/ID31-NASS/matlab/nass-simscape/org/noise_budgeting.org]] for the maximum allowed noise of the relative motion sensors - [ ] DAC, ADC, Control system - [ ] Reading of the force sensor: ADC + [[file:~/Cloud/work-projects/ID31-NASS/matlab/test-bench-force-sensor/test-bench-force-sensor.org][test-bench-force-sensor]] Electrical characteristics of the APA force sensor ** DONE [#A] Noise budgeting to have specifications CLOSED: [2025-03-17 Mon 14:51] Simplified model (uniaxial?) From maximum induced vibration, and estimated bandwidth (S and T), estimate the maximum: - Actuator noise (i.e DAC + voltage amplifier) - force sensor noise (i.e. ADC) + encoder noise? - external metrology noise? (not very relevant, maybe this should be assumed, similar to Attocube noise [[file:~/Cloud/work-projects/ID31-NASS/matlab/test-bench-attocube/test-bench-attocube.org][test-bench-attocube]]) => *Specifications* And specifications in terms of bandwidth, voltage ranges, etc... - Most stringent requirement: vertical vibrations below 15nm RMS - Because of system symmetry, only one strut is considered It is suppose that the vibrations induced by each strut is uncorrelated combination of 6 actuators with uncorrelated noise => x2.5 - Therefore, each actuator should induce less than 15/2.5=6nm RMS of vibration in the vertical direction 3 sources of noise: - ADC - DAC - voltage amplifier Suppose uncorrelated noise: each should be sqrt(3) lower => 3.5 nm RMS max for each source From noise ASD of elements => easy to compute the effect in nm RMS on the output (in closed-loop). Ideally, we would like to have the specifications in terms of ASD for each element. We can suppose the noise ASD to be flat with frequency and find the maximum value to reach x nm RMS => this is our specifications. *We need inputs and outputs to be in volts for the NASS plant !* - use 2DoF model of APA and 3 and 4 DoF model of the flexible joints ** DONE [#B] Verify that the specs are correct CLOSED: [2025-03-17 Mon 14:51] [[*Output Voltage Noise][Output Voltage Noise]] Are we considering output voltage noise for the specs? ** DONE [#A] Make schematic with the plant and all the instrumentation CLOSED: [2025-02-27 Thu 14:25] #+begin_src latex :file detail_instrumentation_plant.pdf \begin{tikzpicture} % Blocs \node[block={2.0cm}{2.0cm}, align=center] (plant) {NASS}; \coordinate[] (inputVa) at ($(plant.south west)!0.5!(plant.north west)$); \coordinate[] (outputVs) at ($(plant.south east)!0.7!(plant.north east)$); \coordinate[] (outputde) at ($(plant.south east)!0.3!(plant.north east)$); \node[block={1.0cm}{1.0cm}, left=0.8 of inputVa] (ampl_tf) {$G_{\text{ampl}}$}; \node[addb={+}{}{}{}{}, left=0.4 of ampl_tf] (ampl_noise) {}; \node[addb={+}{}{}{}{}, left=0.8 of ampl_noise] (dac_noise) {}; \node[DAC, left=0.4 of dac_noise] (dac_tf) {}; \node[addb={+}{}{}{}{}, left=1.0 of dac_tf] (iff_sum) {}; \node[block={1.0cm}{1.0cm}, above=0.4 of iff_sum] (Kiff) {$\bm{K}_{\text{IFF}}$}; \node[block={1.0cm}{1.0cm}, left=0.4 of iff_sum] (Khac) {$\bm{K}_{\text{HAC}}$}; % \node[block={1.0cm}{1.0cm}, above=0.4 of plant] (Kiff) {$\bm{K}_{\text{IFF}}$}; % \node[block={1.0cm}{1.0cm}, below=0.4 of plant] (Khac) {$\bm{K}_{\text{HAC}}$}; \node[addb={+}{}{}{}{}, right=0.8 of outputVs] (adc_noise) {}; \node[ADC, right=0.4 of adc_noise] (adc_tf) {}; \draw[->] (iff_sum.east) -- node[sloped]{$/$} (dac_tf.west); \draw[->] (dac_tf.east) -- (dac_noise.west); \draw[->] (dac_noise.east) -- (ampl_noise.west); \draw[->] (ampl_noise.east) -- (ampl_tf.west); \draw[->] (ampl_tf.east) -- (inputVa)node[above left]{$\bm{V}_a$}; \draw[->] (outputVs)node[above right]{$\bm{V}_s$} -- (adc_noise.west); \draw[->] (adc_noise.east) -- (adc_tf.west); \draw[->] (adc_tf.east) -| ++(0.4, 1.8) -| node[near start, sloped]{$/$} (Kiff.north); \draw[->] (Kiff.south) -- node[sloped]{$/$} (iff_sum.north); \draw[->] (outputde)node[above right]{$\bm{\epsilon}_{\mathcal{L}}$} -| ++(0.6, -1.0) -| node[near start, sloped]{$/$} ($(Khac.west)+(-0.6, 0)$) -- (Khac.west); \draw[->] (Khac.east) -- node[sloped]{$/$} (iff_sum.west); \draw[<-] (dac_noise.north) -- ++(0, 0.8)coordinate(dac_noise_input) node[below left]{$n_{\text{ad}}$}; \draw[<-] (ampl_noise.north) -- ++(0, 0.8)coordinate(ampl_noise_input) node[below right]{$n_{\text{ampl}}$}; \draw[<-] (adc_noise.north) -- ++(0, 0.8)coordinate(adc_noise_input) node[below right]{$n_{\text{da}}$}; \begin{scope}[on background layer] \node[fit={(dac_tf.south west) (dac_noise.east|-dac_noise_input)}, fill=colorblue!20!white, draw, dashed, inner sep=4pt] (dac_system) {}; \node[anchor={north}] at (dac_system.south){$\text{DAC}$}; \end{scope} \begin{scope}[on background layer] \node[fit={(ampl_noise.west|-ampl_tf.south) (ampl_tf.east|-ampl_noise_input)}, fill=colorred!20!white, draw, dashed, inner sep=4pt] (ampl_system) {}; \node[anchor={north}] at (ampl_system.south){$\text{Amplifier}$}; \end{scope} \begin{scope}[on background layer] \node[fit={(adc_noise.south -| adc_noise.west) (adc_tf.east|-adc_noise_input)}, fill=coloryellow!20!white, draw, dashed, inner sep=4pt] (adc_system) {}; \node[anchor={north}] at (adc_system.south){$\text{ADC}$}; \end{scope} \begin{scope}[on background layer] \node[fit={(Khac.south west) (Kiff.north east)}, fill=black!20!white, draw, dashed, inner sep=4pt] (control_system) {}; \node[anchor={north}] at (control_system.south){$\text{RT Controller}$}; \end{scope} \end{tikzpicture} #+end_src #+RESULTS: [[file:figs/detail_instrumentation_plant.png]] ** DONE [#C] Find instrumentation that meet such specification CLOSED: [2025-03-17 Mon 14:51] How to read force sensor, etc... ** DONE [#C] Characterization of received instruments and compare with specifications CLOSED: [2025-03-17 Mon 14:51] Test benches to characterize instruments - [[file:~/Cloud/work-projects/ID31-NASS/matlab/test-bench-PD200/test-bench-pd200.org][test-bench-PD200]] - [[file:~/Cloud/work-projects/ID31-NASS/matlab/test-bench-force-sensor/test-bench-force-sensor.org][test-bench-force-sensor]] ** DONE [#C] Perform rigorous noise budgeting CLOSED: [2025-03-17 Mon 14:51] Sources: - ADC: measured - PD200: measured - Encoder: measured - ADC (force sensors): measured - Ground motion: estimated By taking into account all those sources, we should be able to do a noise budgeting and compare with the obtain measurements. We need the following transfer functions: - from ground motion (x,y,z) to strut motion (1 to 6) => Simscape - from DAC voltage to strut motion => identified FRF - from PD200 voltage to strut motion => identified FRF - from ADC noise to strut motion => identified FRF + Controller (closed-loop) ** DONE [#B] Design reasonable IFF and HAC controllers CLOSED: [2025-02-27 Thu 16:34] #+begin_src matlab %% Identify open-loop plant for IFF initializeController('type', 'open-loop'); % Input/Output definition clear io; io_i = 1; io(io_i) = linio([mdl, '/Controller'], 1, 'openinput'); io_i = io_i + 1; % Control output [V] io(io_i) = linio([mdl, '/NASS'], 3, 'openoutput', [], 'Vs'); io_i = io_i + 1; % Force Sensors Voltages [V] G_iff = linearize(mdl, io); G_iff.InputName = {'u1', 'u2', 'u3', 'u4', 'u5', 'u6'}; G_iff.OutputName = {'Vs1', 'Vs2', 'Vs3', 'Vs4', 'Vs5', 'Vs6'}; %% IFF Controller Design % Second order high pass filter wz = 2*pi*2; xiz = 0.7; Ghpf = (s^2/wz^2)/(s^2/wz^2 + 2*xiz*s/wz + 1); Kiff = -200 * ... % Gain 1/(0.01*2*pi + s) * ... % LPF: provides integral action Ghpf * ... % 2nd order HPF (limit low frequency gain) eye(6); % Diagonal 6x6 controller (i.e. decentralized) Kiff.InputName = {'fm1', 'fm2', 'fm3', 'fm4', 'fm5', 'fm6'}; Kiff.OutputName = {'f1', 'f2', 'f3', 'f4', 'f5', 'f6'}; %% Root Locus for the Decentralized IFF controller - 1kg Payload gains = logspace(-1, 1, 200); figure; tiledlayout(1, 1, 'TileSpacing', 'compact', 'Padding', 'None'); nexttile(); hold on; plot(real(pole(G_iff)), imag(pole(G_iff)), 'x', 'color', colors(1,:), ... 'DisplayName', '$g = 0$'); plot(real(tzero(G_iff)), imag(tzero(G_iff)), 'o', 'color', colors(1,:), ... 'HandleVisibility', 'off'); for g = gains clpoles = pole(feedback(G_iff, g*Kiff, +1)); plot(real(clpoles), imag(clpoles), '.', 'color', colors(1,:), ... 'HandleVisibility', 'off'); end % Optimal gain clpoles = pole(feedback(G_iff, Kiff, +1)); plot(real(clpoles), imag(clpoles), 'kx', ... 'DisplayName', '$g_{opt}$'); xline(0); yline(0); hold off; axis equal; xlim([-900, 100]); ylim([-100, 900]); xticks([-900:100:0]); yticks([0:100:900]); set(gca, 'XTickLabel',[]); set(gca, 'YTickLabel',[]); xlabel('Real part'); ylabel('Imaginary part'); %% Identify Damped plant initializeController('type', 'iff'); % Input/Output definition clear io; io_i = 1; io(io_i) = linio([mdl, '/Controller'], 1, 'openinput'); io_i = io_i + 1; % Control output [V] io(io_i) = linio([mdl, '/Tracking Error'], 1, 'openoutput', [], 'EdL'); io_i = io_i + 1; % Position Errors [m] G_hac = linearize(mdl, io); G_hac.InputName = {'u1', 'u2', 'u3', 'u4', 'u5', 'u6'}; G_hac.OutputName = {'eL1', 'eL2', 'eL3', 'eL4', 'eL5', 'eL6'}; %% HAC Design % Wanted crossover wc = 2*pi*10; % [rad/s] % Integrator H_int = wc/s; % Lead to increase phase margin a = 2; % Amount of phase lead / width of the phase lead / high frequency gain H_lead = 1/sqrt(a)*(1 + s/(wc/sqrt(a)))/(1 + s/(wc*sqrt(a))); % Low Pass filter to increase robustness H_lpf = 1/(1 + s/2/pi/80); % Gain to have unitary crossover at wc H_gain = 1./abs(evalfr(G_hac(1,1), 1j*wc)); % Decentralized HAC Khac = -5e4 * ... % Gain H_int * ... % Integrator H_lead * ... % Low Pass filter H_lpf * ... % Low Pass filter eye(6); % 6x6 Diagonal #+end_src * Introduction :ignore: The goal is to show that each element in the system has been properly chosen based on certain requirements. In order to determine the maximum noise of each instrumentation, a dynamic error budgeting is performed in Section ref:sec:instrumentation_dynamic_error_budgeting. The required instrumentation are then selected based on obtained noise specifications and other requirements summarized in Section ref:sec:detail_instrumentation_choice. The received instrumentation are characterized in Section ref:sec:detail_instrumentation_characterization. - Say the the real time controller is a Speedgoat machine, as it is the standard real time controller used at the ESRF #+begin_src latex :file detail_instrumentation_plant.pdf \begin{tikzpicture} % Blocs \node[block={2.0cm}{2.0cm}, align=center] (plant) {NASS}; \coordinate[] (inputVa) at ($(plant.south west)!0.5!(plant.north west)$); \coordinate[] (outputVs) at ($(plant.south east)!0.7!(plant.north east)$); \coordinate[] (outputde) at ($(plant.south east)!0.3!(plant.north east)$); \node[addb={+}{}{}{}{}, left=0.8 of inputVa] (ampl_noise) {}; \node[block={1.0cm}{1.0cm}, left=0.4 of ampl_noise] (ampl_tf) {$G_{\text{ampl}}$}; \node[addb={+}{}{}{}{}, left=0.8 of ampl_tf] (dac_noise) {}; \node[DAC, left=0.4 of dac_noise] (dac_tf) {}; \node[addb={+}{}{}{}{}, left=1.0 of dac_tf] (iff_sum) {}; \node[block={1.0cm}{1.0cm}, above=0.4 of iff_sum] (Kiff) {$\bm{K}_{\text{IFF}}$}; \node[block={1.0cm}{1.0cm}, left=0.4 of iff_sum] (Khac) {$\bm{K}_{\text{HAC}}$}; \node[addb={+}{}{}{}{}, right=0.8 of outputVs] (adc_noise) {}; \node[ADC, right=0.4 of adc_noise] (adc_tf) {}; \draw[->] (iff_sum.east) --node[midway, above]{$\bm{u}$} node[near start, sloped]{$/$} (dac_tf.west); \draw[->] (dac_tf.east) -- (dac_noise.west); \draw[->] (dac_noise.east) -- (ampl_tf.west); \draw[->] (ampl_tf.east) -- (ampl_noise.west); \draw[->] (ampl_noise.east) -- (inputVa)node[above left]{$\bm{V}_a$}; \draw[->] (outputVs)node[above right]{$\bm{V}_s$} -- (adc_noise.west); \draw[->] (adc_noise.east) -- (adc_tf.west); \draw[->] (adc_tf.east) -| ++(0.4, 1.8) -| node[near start, sloped]{$/$} (Kiff.north); \draw[->] (Kiff.south) -- node[sloped]{$/$} (iff_sum.north); \draw[->] (outputde)node[above right]{$\bm{\epsilon}_{\mathcal{L}}$} -| ++(0.6, -1.0) -| node[near start, sloped]{$/$} ($(Khac.west)+(-0.6, 0)$) -- (Khac.west); \draw[->] (Khac.east) -- node[sloped]{$/$} (iff_sum.west); \draw[<-] (dac_noise.north) -- ++(0, 0.8)coordinate(dac_noise_input) node[below left]{$n_{\text{da}}$}; \draw[<-] (ampl_noise.north) -- ++(0, 0.8)coordinate(ampl_noise_input) node[below left]{$n_{\text{amp}}$}; \draw[<-] (adc_noise.north) -- ++(0, 0.8)coordinate(adc_noise_input) node[below right]{$n_{\text{ad}}$}; \begin{scope}[on background layer] \node[fit={(dac_tf.south west) (dac_noise.east|-dac_noise_input)}, fill=colorblue!20!white, draw, dashed, inner sep=4pt] (dac_system) {}; \node[anchor={north}] at (dac_system.south){$\text{DAC}$}; \end{scope} \begin{scope}[on background layer] \node[fit={(ampl_tf.south west) (ampl_noise.east|-ampl_noise_input)}, fill=colorred!20!white, draw, dashed, inner sep=4pt] (ampl_system) {}; \node[anchor={north}] at (ampl_system.south){$\text{Amplifier}$}; \end{scope} \begin{scope}[on background layer] \node[fit={(adc_noise.south -| adc_noise.west) (adc_tf.east|-adc_noise_input)}, fill=coloryellow!20!white, draw, dashed, inner sep=4pt] (adc_system) {}; \node[anchor={north}] at (adc_system.south){$\text{ADC}$}; \end{scope} \begin{scope}[on background layer] \node[fit={(Khac.south west) (Kiff.north east)}, fill=black!20!white, draw, dashed, inner sep=4pt] (control_system) {}; \node[anchor={north}] at (control_system.south){$\text{RT Controller}$}; \end{scope} \end{tikzpicture} #+end_src #+name: fig:detail_instrumentation_plant #+caption: Block diagram of the NASS with considered instrumentation #+RESULTS: [[file:figs/detail_instrumentation_plant.png]] * Dynamic Error Budgeting :PROPERTIES: :HEADER-ARGS:matlab+: :tangle matlab/detail_instrumentation_1_dynamic_error_budgeting.m :END: <> ** Introduction :ignore: *Goal*: - Obtain specifications regarding the maximum noise of instrumentation (ADC, DAC and voltage amplifier) such that it induces acceptable vibrations levels *Procedure*: - Get closed-loop transfer functions from noise sources (noise of ADC, DAC and amplifier noise) to positioning error. This is done using the multi-body model, with 2DoF APA model (having voltage input and outputs). - Focus is made on the vertical direction, as it is the direction with the most stringent requirements. If horizontal directions are considered, requirements are just less stringent than for the vertical direction. - Deduce the maximum acceptable ASD of the noise sources. As the voltage amplifier gain will impact how the DAC noise is amplified, some assumption are made: - we want to apply -20 to 150V to the stacks - Typical ADC are +/-10V - Assumption of voltage amplifier with gain 20 ** Matlab Init :noexport:ignore: #+begin_src matlab :tangle no :exports none :results silent :noweb yes :var current_dir=(file-name-directory buffer-file-name) <> #+end_src #+begin_src matlab :exports none :results silent :noweb yes <> #+end_src #+begin_src matlab :tangle no :noweb yes <> #+end_src #+begin_src matlab :eval no :noweb yes <> #+end_src #+BEGIN_SRC matlab %% Linearization options opts = linearizeOptions; opts.SampleTime = 0; %% Open Simscape Model mdl = 'detail_instrumentation_nass'; % Name of the Simulink File open(mdl); % Open Simscape Model #+END_SRC #+begin_src matlab :noweb yes <> #+end_src ** Closed-Loop Sensitivity to Instrumentation Disturbances The following noise sources are considered (Figure ref:fig:detail_instrumentation_plant): - $n_{da}$: output voltage noise of the DAC - $n_{amp}$: output voltage noise of the voltage amplifier - $n_{ad}$: voltage noise of the ADC measuring the force sensor stacks Encoder noise, only used to estimate $R_z$ is found to have little impact on the vertical sample error and is therefore ommited from this analysis for clarity. The transfer function from these three noise sources (for one strut) to the vertical error of the sample are estimated from the multi-body model, including the APA300ML and the designed flexible joints (Figure ref:fig:detail_instrumentation_noise_sensitivities). The lateral error was also considered, but the specifications are less stringent than vertical error and the sensitivity to disturbances is smaller. #+begin_src matlab %% Identify the transfer functions from disturbance sources to vertical position error % Let's initialize all the stages with default parameters. initializeGround(); initializeGranite(); initializeTy(); initializeRy(); initializeRz(); initializeMicroHexapod(); initializeSample('m', 1); initializeSimplifiedNanoHexapod(); initializeSimscapeConfiguration(); initializeDisturbances('enable', false); initializeLoggingConfiguration('log', 'none'); initializeController('type', 'hac-iff'); initializeReferences(); % Decentralized IFF controller wz = 2*pi*2; xiz = 0.7; Ghpf = (s^2/wz^2)/(s^2/wz^2 + 2*xiz*s/wz + 1); Kiff = -200 * ... % Gain 1/(0.01*2*pi + s) * ... % LPF: provides integral action Ghpf * ... % 2nd order HPF (limit low frequency gain) eye(6); % Diagonal 6x6 controller (i.e. decentralized) % Centralized HAC wc = 2*pi*10; % Wanted crossover [rad/s] H_int = wc/s; % Integrator a = 2; % Amount of phase lead / width of the phase lead / high frequency gain H_lead = 1/sqrt(a)*(1 + s/(wc/sqrt(a)))/(1 + s/(wc*sqrt(a))); % Lead to increase phase margin H_lpf = 1/(1 + s/2/pi/80); % Low Pass filter to increase robustness Khac = -5e4 * ... % Gain H_int * ... % Integrator H_lead * ... % Low Pass filter H_lpf * ... % Low Pass filter eye(6); % 6x6 Diagonal % Input/Output definition clear io; io_i = 1; io(io_i) = linio([mdl, '/dac_noise'], 1, 'input'); io_i = io_i + 1; % DAC noise [V] io(io_i) = linio([mdl, '/amp_noise'], 1, 'input'); io_i = io_i + 1; % Voltage Amplifier noise [V] io(io_i) = linio([mdl, '/NASS/adc_noise'], 1, 'input'); io_i = io_i + 1; % ADC noise [V] io(io_i) = linio([mdl, '/NASS/enc_noise'], 1, 'input'); io_i = io_i + 1; % Encoder noise [m] io(io_i) = linio([mdl, '/NASS'], 2, 'output', [], 'z'); io_i = io_i + 1; % Vertical error [m] io(io_i) = linio([mdl, '/NASS'], 2, 'output', [], 'y'); io_i = io_i + 1; % Lateral error [m] Gd = linearize(mdl, io); Gd.InputName = {... 'nda1', 'nda2', 'nda3', 'nda4', 'nda5', 'nda6', ... % DAC and Voltage amplifier noise 'namp1', 'namp2', 'namp3', 'namp4', 'namp5', 'namp6', ... % DAC and Voltage amplifier noise 'nad1', 'nad2', 'nad3', 'nad4', 'nad5', 'nad6', ... % ADC noise 'ddL1', 'ddL2', 'ddL3', 'ddL4', 'ddL5', 'ddL6' ... % Encoder noise }; Gd.OutputName = {'y', 'z'}; % Vertical error of the sample #+end_src #+begin_src matlab :exports none :tangle no %% Save Requirements save('./matlab/mat/instrumentation_sensitivity.mat', 'Gd'); #+end_src #+begin_src matlab :exports none :eval no %% Save Requirements save('./mat/instrumentation_sensitivity.mat', 'Gd'); #+end_src #+begin_src matlab :exports none :results none %% Transfer function from noise sources to vertical motion errors freqs = logspace(0, 3, 1000); figure; tiledlayout(1, 1, 'TileSpacing', 'compact', 'Padding', 'None'); nexttile(); hold on; plot(freqs, abs(squeeze(freqresp(Gd('z', 'nda1' ), freqs, 'Hz'))), 'DisplayName', '$\epsilon_z/n_{da}$'); plot(freqs, abs(squeeze(freqresp(Gd('z', 'namp1'), freqs, 'Hz'))), 'DisplayName', '$\epsilon_z/n_{amp}$'); plot(freqs, abs(squeeze(freqresp(Gd('z', 'nad1' ), freqs, 'Hz'))), 'DisplayName', '$\epsilon_z/n_{ad}$'); hold off; set(gca, 'XScale', 'log'); set(gca, 'YScale', 'log'); xlabel('Frequency [Hz]'); ylabel('Sensitivity [m/V]'); ylim([1e-9, 1e-4]); leg = legend('location', 'southwest', 'FontSize', 8, 'NumColumns', 1); leg.ItemTokenSize(1) = 15; xlim([1, 1e3]); #+end_src #+begin_src matlab :tangle no :exports results :results file replace exportFig('figs/detail_instrumentation_noise_sensitivities.pdf', 'width', 'wide', 'height', 'normal'); #+end_src #+name: fig:detail_instrumentation_noise_sensitivities #+caption: Transfer function from noise sources to vertical motion errors #+RESULTS: [[file:figs/detail_instrumentation_noise_sensitivities.png]] ** Estimation of maximum instrumentation noise From previous analysis, we know how the noise of the instrumentation will affect the vertical error of the sample as a function of frequency. Now, we want to determine specifications for each instrumentation such that the effect on the vertical error of the sample is within specifications. Most stringent requirement: - Vertical vibrations less than the smallest expected beam size of 100nm - This corresponds to a maximum allowed vibration of 15nm RMS Assumption on the noise: - DAC, DAC and amplifier noise are uncorrelated, which is reasonable. Noise corresponding each strut are each uncorrelated. This means that the PSD of the different noise sources adds up. Use of system symmetry to simplify the analysis: - the effect of all the struts on the vertical errors are identical (verify from the extracted sensitivity curves). Therefore only one strut can be considered for this analysis, and the total effect of the six struts is just six times the effect of one strut (in terms of power, but in terms of RMS value it's only sqrt(6)=2.5) In order to have specifications in terms of noise spectral density of each instrumentation, a white noise is assumed, which is quite typical. The noise specification is computed such that if all the instrumentation have this maximum noise, the specification in terms of vertical error is still respected. This is a pessimistic choice, but it gives a rough idea of the specifications. #+begin_src matlab % Maximum wanted effect of each noise source on the vertical error % Specifications: 15nm RMS % divide by sqrt(6) because 6 struts % divide by sqrt(3) because 3 considered noise sources max_asd_z = 15e-9 / sqrt(6) / sqrt(3); % [m/sqrt(Hz)] % Suppose unitary flat noise ASD => compute the effect on vertical noise unit_asd = ones(1, length(freqs)); rms_unit_asd_dac = sqrt(sum((unit_asd.*abs(squeeze(freqresp(Gd('z', 'nda1' ), freqs, 'Hz'))).').^2)); rms_unit_asd_amp = sqrt(sum((unit_asd.*abs(squeeze(freqresp(Gd('z', 'namp1'), freqs, 'Hz'))).').^2)); rms_unit_asd_adc = sqrt(sum((unit_asd.*abs(squeeze(freqresp(Gd('z', 'nad1' ), freqs, 'Hz'))).').^2)); % Obtained maximum ASD for different instruments max_dac_asd = max_asd_z./rms_unit_asd_dac; % [V/sqrt(Hz)] max_amp_asd = max_asd_z./rms_unit_asd_amp; % [V/sqrt(Hz)] max_adc_asd = max_asd_z./rms_unit_asd_adc; % [V/sqrt(Hz)] % Estimation of the equivalent RMS noise max_dac_rms = 1e3*max_dac_asd*sqrt(5e3) % [mV RMS] max_amp_rms = 1e3*max_amp_asd*sqrt(5e3) % [mV RMS] max_adc_rms = 1e3*max_adc_asd*sqrt(5e3) % [mV RMS] #+end_src Obtained maximum noise are: - DAC maximum output noise ASD $14\,\mu V/\sqrt{Hz}$. - Voltage amplifier maximum output voltage noise ASD $280\,\mu V/\sqrt{Hz}$ - ADC maximum measurement noise ASD $11\,\mu V/\sqrt{Hz}$. In terms of RMS noise, - DAC: <1 mV RMS - Voltage amplifier: < 20 mV RMS - ADC: < 0.8 mV RMS #+name: tab:detail_instrumentation_specification_noise #+caption: Obtained specification in terms of noise #+attr_latex: :environment tabularx :width 0.7\linewidth :align Xccc #+attr_latex: :center t :booktabs t | | ADC | DAC | Amplifier | |-------------+-----------------------+-----------------------+------------------------| | Maximum ASD | $11\,\mu V/\sqrt{Hz}$ | $14\,\mu V/\sqrt{Hz}$ | $280\,\mu V/\sqrt{Hz}$ | | RMS Noise | $0.8\,mV\,\text{RMS}$ | $1\,mV\,\text{RMS}$ | $20\,mV\,\text{RMS}$ | If the Amplitude Spectral Density of the noise of the ADC, DAC and voltage amplifiers are all below the specified maximum noises, then the induced vertical error will be below 15nmRMS. Such specification will guide the choice of instrumentation in Section ref:sec:detail_instrumentation_choice. #+begin_src matlab :exports none :tangle no %% Save Requirements save('./matlab/mat/instrumentation_requirements.mat', ... 'max_dac_asd', 'max_amp_asd', 'max_adc_asd', ... 'max_dac_rms', 'max_amp_rms', 'max_adc_rms'); #+end_src #+begin_src matlab :exports none :eval no %% Save Requirements save('./mat/instrumentation_requirements.mat', ... 'max_dac_asd', 'max_amp_asd', 'max_adc_asd', ... 'max_dac_rms', 'max_amp_rms', 'max_adc_rms'); #+end_src * Choice of Instrumentation :PROPERTIES: :HEADER-ARGS:matlab+: :tangle matlab/detail_instrumentation_2_choice.m :END: <> ** Introduction :ignore: Based on: - noise specifications extracted from Section ref:sec:detail_instrumentation_dynamic_error_budgeting - other specifications (input/output range, bandwidth, etc...) The most adequate ADC, DAC, Voltage amplifier, and relative positioning sensor are found. Different options that were considered are presented, and the choice of instrumentation is explained. ** Matlab Init :noexport:ignore: #+begin_src matlab :tangle no :exports none :results silent :noweb yes :var current_dir=(file-name-directory buffer-file-name) <> #+end_src #+begin_src matlab :exports none :results silent :noweb yes <> #+end_src #+begin_src matlab :tangle no :noweb yes <> #+end_src #+begin_src matlab :eval no :noweb yes <> #+end_src #+begin_src matlab :noweb yes <> #+end_src #+begin_src matlab %% Load computed requirements load('instrumentation_requirements.mat') #+end_src ** Piezoelectric Voltage Amplifier **** Introduction :ignore: There are several characteristics of the piezoelectric voltage amplifiers that should be considered. To be able to use the full stroke of the piezoelectric actuator, the voltage output should be between -20 and 150V. It should accept an analog input voltage, preferably between -10 and 10V, as it is quite typical for DAC. **** Small signal Bandwidth and Output Impedance There are two bandwidth that should be considered for a piezoelectric voltage amplifier: large signal bandwidth and small signal bandwidth. Large signal bandwidth are linked to the output current capacities of the amplifier and will be discussed next. Small signal bandwidth of the voltage amplifier is very important for feedback applications as it can limit the bandwidth of the complete feedback system. A simplified electrical model of a voltage amplifier connected to a piezoelectric stack is shown in Figure ref:fig:detail_instrumentation_amp_output_impedance. This model is only valid for small signals, but it is useful to understand the small signal bandwidth limitation [[cite:&fleming14_desig_model_contr_nanop_system, chap. 14]]. $R_o$ corresponds to the output impedance of the amplifier. With the piezoelectric load that corresponds to a capacitance $C_p$, it forms a first order low pass filter eqref:eq:detail_instrumentation_amp_output_impedance. \begin{equation}\label{eq:detail_instrumentation_amp_output_impedance} \frac{V_a}{V_i}(s) = \frac{1}{1 + \frac{s}{\omega_0}}, \quad \omega_0 = \frac{1}{R_o C_p} \end{equation} #+name: fig:detail_instrumentation_amp_output_impedance #+caption: Electrical model of a voltage amplifier with output impedance $R_0$ connected to a piezoelectric stack with capacitance $C_p$ [[file:figs/detail_instrumentation_amp_output_impedance.png]] Therefore, the small signal bandwidth is load dependent (it decreases as the load capacitance increases). The capacitance load of the two piezoelectric stacks in the APA300ML correspond to a capacitance $C_p = 8.8\,\mu F$. If a small signal bandwidth of $f_0 = \frac{\omega_0}{2\pi} = 5\,\text{kHz}$ is wanted, the voltage amplifier output impedance should be smaller than $R_0 = 3.6\,\Omega$. #+begin_src matlab Cp = 8.8e-6; % Capacitive load of the two piezoelectric actuators f0 = 5e3; % Wanted low signal bandwidth [Hz] Ro_max = 1/(2*pi*f0 * Cp); % Maximum wanted output impedance [Ohm] #+end_src **** Large signal Bandwidth Large signal bandwidth are linked to the maximum output capabilities of the amplifiers in terms of amplitude as a function of frequency [[cite:&spengen16_high_voltag_amplif]]. As the primary objective of the NASS is to stabilize the position and not to perform scans, this specification is not as important as the small signal bandwidth. However, let's take into account scanning capabilities, and consider the worst case scenario of a constant velocity scan (i.e. triangular reference signal) with a repetition rate of $f_r = 100\,\text{Hz}$ and using full voltage capabilities of the piezoelectric actuator $V_{pp} = 170\,V$. There are two things to consider: - Slew rate that should be above $2 \cdot V_{pp} \cdot f_r = 34\,V/ms$ This specification is easily achieved by commercial voltage amplifiers. - Current output capabilities: as the capacitance impedance decreases the the inverse of the frequency, it can reach very low values at high frequency In order to reach high voltage at high frequency, the required current that the voltage amplifier needs to provide may reach very large values. $I_{\text{max}} = 2 \cdot V_{pp} \cdot f \cdot C_p = 0.3\,A$ So ideally, a voltage amplifier capable of providing $0.3\,A$ of current is wanted. #+begin_src matlab %% Slew-rate specifications - Triangular scan Vpp = 170; % Full voltage scan [V] f0 = 100; % Repetition rate of the triangular scan [Hz] slew_rate = 1e-3*2*Vpp*f0 % Required slew rate [V/ms] %% Maximum Output Current - Triangular scan max_current = 2*Vpp*f0*Cp % [A] #+end_src **** Output voltage noise As discussed in Section ref:sec:detail_instrumentation_dynamic_error_budgeting, the output noise of the voltage amplifier should be smaller than $20\,mV\,\text{RMS}$. As explained in [[cite:&spengen20_high_voltag_amplif]], the load capacitance of the piezoelectric stack filters the output noise of the amplifier (low pass filter of Figure ref:fig:detail_instrumentation_amp_output_impedance). Therefore, when comparing noise indicated in the datasheet of different voltage amplifiers, it is important to check what is the considered capacitance of the load (i.e. the low signal bandwidth considered). Here, the output noise should be smaller than 20mVRMS for a load of 8.8uF and a bandwidth larger than 5kHz. **** Choice of voltage amplifier The specifications are summarized in Table ref:tab:detail_instrumentation_amp_choice. The most important characteristics being the (small signal) bandwidth > 5 [kHz] and the output voltage noise (< 20 [mV RMS]). Several voltage amplifiers were considered, with datasheet information summarized in Table ref:tab:detail_instrumentation_amp_choice. - Issue for the selection: manufacturers are not specifying the output noise as a function of frequency (i.e. the ASD of the noise), but only the RMS value (i.e. the integrated value over all frequency). It does not take into account the frequency dependency of the noise, that is very important to perform error budgets Also, the load used to estimate the bandwidth and noise is often not mentioned. Most of the time, the bandwidth is indicated with very little load and the noise with high load. It renders the comparison between different models more complex. - The chosen model is the PD200 from PiezoDrive. - It fulfill the specification - It has clear documentation, especially about noise and bandwidth #+name: tab:detail_instrumentation_amp_choice #+caption: Characteristics of the PD200 compared with the specifications #+attr_latex: :environment tabularx :width \linewidth :align Xcccc #+attr_latex: :center t :booktabs t :float t | *Specification* | *PD200* | WMA-200 | LA75B | E-505 | | | PiezoDrive | Falco | Cedrat | PI | |--------------------------------------+-----------------------+-------------------------------------------+--------------+-----------| | Input Voltage Range: $\pm 10\,V$ | $\pm 10\,V$ | $\pm8.75\,V$ | $-1/7.5\,V$ | | | Output Voltage Range: $-20/150\,V$ | $-50/150\,V$ | $\pm 175\,V$ | $-20/150\,V$ | -30/130 | | Gain $>15$ | 20 | 20 | 20 | 10 | | Output Current $> 300\,mA$ | $900\,mA$ | $150\,mA$ | $360\,mA$ | $215\,mA$ | | Slew Rate $> 34\,V/ms$ | $150\,V/\mu s$ | $80\,V/\mu s$ | n/a | n/a | | Output noise $< 20\,mV\ \text{RMS}$ | $0.7\,mV\,\text{RMS}$ | $0.05\,mV$ | $3.4\,mV$ | $0.6\,mV$ | | (10uF load) | ($10\,\mu F$ load) | ($10\,\mu F$ load) | | | | Small Signal Bandwidth $> 5\,kHz$ | $6.4\,kHz$ | $300\,Hz$ | $30\,kHz$ | n/a | | ($10\,\mu F$ load) | ($10\,\mu F$ load) | [fn:detail_instrumentation_1] | (unloaded) | | | Output Impedance: $< 3.6\,\Omega$ | n/a | $50\,\Omega$[fn:detail_instrumentation_1] | n/a | n/a | # #+name: fig:detail_instrumentation_pd200_specs # #+caption: Caption with reference to sub figure (\subref{fig:detail_instrumentation_pd200_specs_bandwidth}) # #+attr_latex: :options [htbp] # #+begin_figure # #+attr_latex: :caption \subcaption{\label{fig:detail_instrumentation_pd200_specs_bandwidth}sub caption a} # #+attr_latex: :options {0.48\textwidth} # #+begin_subfigure # #+attr_latex: :width 0.95\linewidth # [[file:figs/detail_instrumentation_pd200_specs_bandwidth.png]] # #+end_subfigure # #+attr_latex: :caption \subcaption{\label{fig:detail_instrumentation_pd200_specs_noise}sub caption b} # #+attr_latex: :options {0.48\textwidth} # #+begin_subfigure # #+attr_latex: :width 0.95\linewidth # [[file:figs/detail_instrumentation_pd200_specs_noise.png]] # #+end_subfigure # #+end_figure ** ADC and DAC **** Introduction :ignore: Analog to digital converters and digital to analog converters are very important to convert signals from the RT controller that only uses digital numbers to the physical plant, which is of course affected by analog signals. **** Synchronicity and Jitter For control systems, it is very important that the inputs and outputs are sampled synchronously with the controller and with low jitter [[cite:&abramovitch22_pract_method_real_world_contr_system;&abramovitch23_tutor_real_time_comput_issues_contr_system]]. Therefore, the ADC and DAC needs to be well interfaced with the Speedgoat, and triggered synchronously with the computation of the control signals. For that reason, it was decided to first look for ADC and DAC sold by Speedgoat (the RT controller used). **** Sampling Frequency, Bandwidth and delays Several requirements may appear the same but are very different in nature: - Sampling frequency: defines the interval between two sampled points, also determines the Nyquist frequency - Bandwidth: defines the maximum frequency of a measured signal (typically specified as the -3dB point), usually limited by implemented anti-aliasing filters - Delay/latency: delay between the analog signal at the input of the ADC to the digital information transferred to the control system Sigma-Delta ADC can have extremely good noise characteristics, high bandwidth and sampling frequency but very poor latency. Typically, the latency can reach 20 times the sampling period [[cite:&schmidt20_desig_high_perfor_mechat_third_revis_edition, chapt. 8.4]]. Therefore, Sigma-Delta ADC are very much used for signal acquisition applications, but has limited use for real-time control where latency is critical. Therefore, for real time control applications, SAR-ADC (Successive approximation ADCs) is still the mostly applied type because of its single sample latency. **** ADC Noise From the dynamical error budget in Section ref:sec:detail_instrumentation_dynamic_error_budgeting Measurement noise ASD should be bellow 11uV/sqrt(Hz), 0.8mV RMS ADC are affected by various noise sources One of them is quantization noise, and is linked to the fact that input/output values can only take a finite number of values. Let's first find the number of bits such that the quantization noise is fulfilling the requirements. Let's first suppose that the ADC is ideal and the only noise comes from the quantization error. Let's note $q = \frac{\Delta V}{2^n}$ the quantization in [V], which is the corresponding value in [V] of the least significant bit. $\Delta V$ is the full range of the ADC in [V], $n$ is the number of ADC's bits and $F_s$ is the sample frequency in [Hz]. The quantization noise can take a value between $\pm q/2$, and the probability density function is constant in this range (i.e., it’s a uniform distribution). Since the integral of the probability density function $p(e)$ is equal to one, its value is $1/q$ for $-q/2 < e < q/2$ as illustrated in Figure ref:fig:detail_instrumentation_adc_quantization. #+begin_src latex :file detail_instrumentation_adc_quantization.pdf \begin{tikzpicture} \path[fill=black!20!white] (-1, 0) |- (1, 1) |- (-1, 0); \draw[->] (-2, 0) -- (2, 0) node[above left]{$e$}; \draw[->] (0, -0.5) -- (0, 2) node[below right]{$p(e)$}; \draw[dashed] (-2, 0) -- (-1, 0) |- (1, 1) |- (2, 0); \node[below] at (1, 0){$\frac{q}{2}$}; \node[below] at (-1, 0){$-\frac{q}{2}$}; \node[right] at (1, 1){$\frac{1}{q}$}; \end{tikzpicture} #+end_src #+name: fig:detail_instrumentation_adc_quantization #+caption: Probability density function $p(e)$ of the ADC error $e$ #+RESULTS: [[file:figs/detail_instrumentation_adc_quantization.png]] The variance (or time average power) of the quantization noise is eqref:eq:detail_instrumentation_quant_power. \begin{equation}\label{eq:detail_instrumentation_quant_power} P_q = \int_{-q/2}^{q/2} e^2 p(e) de = \frac{q^2}{12} \end{equation} Now, the goal is to compute the power spectral density of the quantization noise, which is by definition the Fourier transform of the autocorrelation function of the quantization noise. Assuming that the noise samples are not correlated with one another, the autocorrelation function can be approximated with a delta function in the time domain. Since the Fourier transform of a delta function is equal to one, the power spectral density will be frequency independent (i.e. white noise). Therefore, thanks the the Perseval's theorem, the quantization noise is a white noise with total power equal to $P_q = \frac{q^2}{12}$. Thus, the two-sided PSD (from $\frac{-F_s}{2}$ to $\frac{F_s}{2}$), we should divide the noise power $P_q$ by $F_s$: \begin{equation} P_q = \int_{-F_s/2}^{F_s/2} \Gamma(f) d f = F_s \Gamma = \frac{q^2}{12} \end{equation} Finally, the Power Spectral Density of the quantization noise of an ADC is equal to eqref:eq:detail_instrumentation_psd_quant_noise. \begin{equation}\label{eq:detail_instrumentation_psd_quant_noise} \Gamma_q = \frac{q^2}{12 F_s} = \frac{\left(\frac{\Delta V}{2^n}\right)^2}{12 F_s} \quad \text{in } \left[ \frac{V^2}{\text{Hz}} \right] \end{equation} From a defined noise amplitude spectral density $\Phi_{\text{max}}$, the minimum number of bits so that the quantization noise is below $\Phi_{\text{max}}$ can be computed from eqref:eq:detail_instrumentation_min_n. \begin{equation}\label{eq:detail_instrumentation_min_n} n = \text{log}_2 \left( \frac{\Delta V}{\sqrt{12 Fs} \Phi_{\text{max}}} \right) \end{equation} With a sampling frequency $F_s = 10\,kHz$, a full range of $\Delta V = 20\,V$ and a maximum allowed ASD $\Phi_{\text{max}} = 11\,\mu V/\sqrt{Hz}$, the minimum number of bits is $n_{\text{min}} = 12.4$, which is easily satisfied by commercial ADCs. #+begin_src matlab delta_V = 20; % +/-10 V Fs = 10e3; % Sampling Frequency [Hz] max_adc_asd = 11e-6; % V/sqrt(Hz) min_n = log2(delta_V/(sqrt(12*Fs)*max_adc_asd)) #+end_src #+begin_src matlab %% Estimate quantization noise of the ADC delta_V = 20; % +/-10 V n = 16; % number of bits Fs = 10e3; % [Hz] q = delta_V/2^n; % Quantization in [V] q_psd = q^2/12/Fs; % Quantization noise Power Spectral Density [V^2/Hz] q_asd = sqrt(q_psd) % Quantization noise Amplitude Spectral Density [V/sqrt(Hz)] #+end_src **** DAC Output voltage noise Similarly to ADC, the DAC output voltage noise ASD should be below $14\,\mu V/\sqrt{Hz}$, 1mV RMS. This corresponds to a 13bits +/-10V DAC, which is easily satisfied. **** Choice of the ADC and DAC Board Based on the above analysis, the choice of ADC and DAC is quite straightforward. Integrated in Speedgoat for best synchronicity. Chosen model: IO131: - 16 analog inputs, based on the AD7609 - 16 bits, +/- 10V - Maximum sampling rate of 200kSPS - Simultaneous sampling - Differential inputs: can use shielded twisted pairs for high noise immunity - 8 analog outputs, based on the AD5754R - 16 bits, +/- 10V - Conversion time 10us - Simultaneous update Noise is not specified, but as it has 16 bits resolution, it should be well below the requirements. It will be experimentally measured in Section ref:sec:detail_instrumentation_characterization. ** Relative Displacement Sensors Specifications: - used for relative positioning - Small enough to be integrated in each strut - vertical errors of 15nmRMS => 6nmRMS for each strut => maximum 6nmRMS sensor noise - Stroke > 100um There are many different sensors that can fulfil the requirements [[cite:&fleming13_review_nanom_resol_posit_sensor]]: - Encoders - Capacitive Sensors - Eddy current sensors #+name: fig:detail_instrumentation_sensor_examples #+caption: Measurement of strut flexible modes #+attr_latex: :options [htbp] #+begin_figure #+attr_latex: :caption \subcaption{\label{fig:detail_instrumentation_sensor_encoder}Optical Linear Encoder} #+attr_latex: :options {0.33\textwidth} #+begin_subfigure #+attr_latex: :width 0.9\linewidth [[file:figs/detail_instrumentation_sensor_encoder.jpg]] #+end_subfigure #+attr_latex: :caption \subcaption{\label{fig:detail_instrumentation_sensor_eddy_current}Eddy Current Sensor} #+attr_latex: :options {0.33\textwidth} #+begin_subfigure #+attr_latex: :width 0.9\linewidth [[file:figs/detail_instrumentation_sensor_eddy_current.png]] #+end_subfigure #+attr_latex: :caption \subcaption{\label{fig:detail_instrumentation_sensor_capacitive}Capacitive Sensor} #+attr_latex: :options {0.33\textwidth} #+begin_subfigure #+attr_latex: :width 0.9\linewidth [[file:figs/detail_instrumentation_sensor_capacitive.jpg]] #+end_subfigure #+end_figure The implementation: - slight advantage to capacitive or eddy current sensors as they can measure in line with the APA (Figure ref:fig:detail_instrumentation_capacitive_implementation) - for the encoder, the measurement has to be "offset" from the strut "action line", and therefore relative rotations between the two ends of the APA induces measurement errors (Figure ref:fig:detail_instrumentation_encoder_implementation). #+name: fig:detail_instrumentation_sensor_implementation #+caption: Caption with reference to sub figure #+attr_latex: :options [htbp] #+begin_figure #+attr_latex: :caption \subcaption{\label{fig:detail_instrumentation_encoder_implementation}Optical Encoder} #+attr_latex: :options {0.48\textwidth} #+begin_subfigure #+attr_latex: :scale 1 [[file:figs/detail_instrumentation_encoder_implementation.png]] #+end_subfigure #+attr_latex: :caption \subcaption{\label{fig:detail_instrumentation_capacitive_implementation}Capacitive Sensor} #+attr_latex: :options {0.48\textwidth} #+begin_subfigure #+attr_latex: :scale 1 [[file:figs/detail_instrumentation_capacitive_implementation.png]] #+end_subfigure #+end_figure One major issue is the fact that the sensor signals have to pass through an electrical slip-ring (because of the continuous spindle rotation). Some measurements were performed on the slip-ring integrated in the micro-station, and the cross-talk between different slip-ring channels were found to be quite high. It was preferred to use a sensor that transmit the measured displacement digitally, such that it is much less sensitive to noise and cross-talk. For that reason, an optical encoder with digital output was preferred (i.e. the interpolation is performed directly in the head). The specifications are summarized in Table ref:tab:detail_instrumentation_sensor_specs. #+name: tab:detail_instrumentation_sensor_specs #+caption: Characteristics of the Vionic compared with the specifications #+attr_latex: :environment tabularx :width 0.9\linewidth :align Xccc #+attr_latex: :center t :booktabs t :float t | *Specification* | *Renishaw Vionic* | LION CPL190 | Cedrat ECP500 | |-----------------------------+---------------------+-------------+---------------| | Technology | Digital Encoder | Capacitive | Eddy Current | | Bandwidth $> 5\,\text{kHz}$ | $> 500\,\text{kHz}$ | 10kHz | 20kHz | | Noise $< 6\,nm\,\text{RMS}$ | 1.6 nm rms | 4 nm rms | 15 nm rms | | Range $> 100\,\mu m$ | Ruler length | 250 um | 500um | | In line measurement | | $\times$ | $\times$ | | Digital Output | $\times$ | | | * Characterization of Instrumentation :PROPERTIES: :HEADER-ARGS:matlab+: :tangle matlab/detail_instrumentation_3_characterization.m :END: <> ** Introduction :ignore: All the instrumentation was then procured and tested individually to verify whether it fulfils the specifications or not. ** Matlab Init :noexport:ignore: #+begin_src matlab :tangle no :exports none :results silent :noweb yes :var current_dir=(file-name-directory buffer-file-name) <> #+end_src #+begin_src matlab :exports none :results silent :noweb yes <> #+end_src #+begin_src matlab :tangle no :noweb yes <> #+end_src #+begin_src matlab :eval no :noweb yes <> #+end_src #+begin_src matlab :noweb yes <> #+end_src #+begin_src matlab %% Load computed requirements load('instrumentation_requirements.mat') #+end_src #+begin_src matlab %% Sensitivity to disturbances load('instrumentation_sensitivity.mat', 'Gd'); #+end_src ** Analog to Digital Converters **** Introduction :ignore: The ADC of the IO318 cards: - have differential inputs - internally uses the AD7609 ADC from Analog Devices. - capable of 200kSPS, 16 bits, +/-10V **** Measured Noise The measurement of the ADC noise was done by short-circuiting its input with a 50 Ohm resistor and recording the digital value at 10kHz. The amplitude spectral density of the recorded values are computed and shown in Figure ref:fig:detail_instrumentation_adc_noise_measured. The ADC noise is a white noise with an amplitude spectral density of $5.6\,\mu V/\sqrt{Hz}$ (RMS value of 0.4mV), which fulfills the specifications. All ADC channels are measuring the same, so the noise of only one channel is here shown. If required, it is possible to apply some oversampling to lower the obtained noise as explained in cite:lab13_improv_adc. To have additional $w$ bits of resolution, the oversampling frequency $f_{os}$ should be $f_{os} = 4^w \cdot f_s$. As the ADC can work at 200kSPS, and the real time controller only runs at 10kSPS, an oversampling factor of 16 can be used to have approximately two more bits of resolution (i.e. reducing the noise by a factor 4). This works because the noise can be approximated by a white noise and the amplitude is larger than 1 LSB (0.3 mV) [[cite:hauser91_princ_overs_conver]]. #+begin_src matlab %% ADC noise adc = load("2023-08-23_15-42_io131_adc_noise.mat"); % Spectral Analysis parameters Ts = 1e-4; Nfft = floor(1/Ts); win = hanning(Nfft); Noverlap = floor(Nfft/2); % Identification of the transfer function from Va to di [pxx, f] = pwelch(detrend(adc.adc_1, 0), win, Noverlap, Nfft, 1/Ts); adc.pxx = pxx; adc.f = f; % estimated mean ASD sprintf('Mean ASD of the ADC: %.1f uV/sqrt(Hz)', 1e6*sqrt(mean(adc.pxx))) sprintf('Specifications: %.1f uV/sqrt(Hz)', 1e6*max_adc_asd) % estimated RMS sprintf('RMS of the ADC: %.2f mV RMS', 1e3*rms(detrend(adc.adc_1,0))) sprintf('RMS specifications: %.2f mV RMS', max_adc_rms) % Estimate quantization noise of the IO318 ADC delta_V = 20; % +/-10 V n = 16; % number of bits Fs = 10e3; % [Hz] adc.q = delta_V/2^n; % Quantization in [V] adc.q_psd = adc.q^2/12/Fs; % Quantization noise Power Spectral Density [V^2/Hz] adc.q_asd = sqrt(adc.q_psd); % Quantization noise Amplitude Spectral Density [V/sqrt(Hz)] #+end_src #+begin_src matlab :exports none :results none %% Measured ADC noise (IO318) figure; hold on; plot(adc.f, sqrt(adc.pxx), 'color', colors(3,:), 'DisplayName', '$\Gamma_{q_{ad}}$') plot([adc.f(2), adc.f(end)], [max_adc_asd, max_adc_asd], '--', 'color', colors(3,:), 'DisplayName', 'Specs') plot([adc.f(2), adc.f(end)], [adc.q_asd, adc.q_asd], 'k--', 'DisplayName', 'Quantization noise (16 bits, $\pm 10\,V$)') hold off; set(gca, 'XScale', 'log'); set(gca, 'YScale', 'log'); xlabel('Frequency [Hz]'); ylabel('ASD [V/$\sqrt{Hz}$]'); legend('location', 'southwest', 'FontSize', 8, 'NumColumns', 1); ylim([1e-10, 4e-4]); xlim([1, 5e3]); xticks([1e0, 1e1, 1e2, 1e3]) #+end_src #+begin_src matlab :tangle no :exports results :results file replace exportFig('figs/detail_instrumentation_adc_noise_measured.pdf', 'width', 'normal', 'height', 'normal'); #+end_src #+name: fig:detail_instrumentation_adc_noise_measured #+caption: Measured ADC noise (IO318) #+RESULTS: [[file:figs/detail_instrumentation_adc_noise_measured.png]] **** Reading of piezoelectric force sensor To further verify that the ADC can effectively measure the voltage generated by a piezoelectric stack without issue, a test with the APA95ML was performed. The setup is shown in Figure ref:fig:detail_instrumentation_force_sensor_adc_setup where two stacks are used as actuator (in parallel) and one stack is used as a sensor. The voltage amplifier used has a gain of 20. #+name: fig:detail_instrumentation_force_sensor_adc_setup #+caption: Schematic of the setup to validate the use of the ADC for reading the force sensor volage [[file:figs/detail_instrumentation_force_sensor_adc_setup.png]] #+begin_src matlab %% Read force sensor voltage with the ADC load('force_sensor_steps.mat', 't', 'encoder', 'u', 'v'); % Exponential fit to compute the time constant % Fit function f_exp = @(b,x) b(1).*exp(-b(2).*x) + b(3); % Three steps are performed at the following time intervals: t_s = [ 2.5, 23; 23.8, 35; 35.8, 50]; tau = zeros(size(t_s, 1),1); % Time constant [s] V0 = zeros(size(t_s, 1),1); % Offset voltage [V] a = zeros(size(t_s, 1),1); % for t_i = 1:size(t_s, 1) t_cur = t(t_s(t_i, 1) < t & t < t_s(t_i, 2)); t_cur = t_cur - t_cur(1); y_cur = v(t_s(t_i, 1) < t & t < t_s(t_i, 2)); nrmrsd = @(b) norm(y_cur - f_exp(b,t_cur)); % Residual Norm Cost Function B0 = [0.5, 0.15, 2.2]; % Choose Appropriate Initial Estimates [B,rnrm] = fminsearch(nrmrsd, B0); % Estimate Parameters ‘B’ a(t_i) = B(1); tau(t_i) = 1/B(2); V0(t_i) = B(3); end % Data to show the exponential fit t_fit_1 = linspace(t_s(1,1), t_s(1,2), 100); y_fit_1 = f_exp([a(1),1/tau(1),V0(1)], t_fit_1-t_s(1,1)); t_fit_2 = linspace(t_s(2,1), t_s(2,2), 100); y_fit_2 = f_exp([a(2),1/tau(2),V0(2)], t_fit_2-t_s(2,1)); t_fit_3 = linspace(t_s(3,1), t_s(3,2), 100); y_fit_3 = f_exp([a(3),1/tau(3),V0(3)], t_fit_3-t_s(3,1)); % Speedgoat ADC input impedance Cp = 4.4e-6; % [F] Rin = abs(mean(tau))/Cp; % [Ohm] % Estimated input bias current in = mean(V0)/Rin; % [A] % Resistor added in parallel to the force sensor fc = 0.5; % Wanted corner frequency [Hz] Ra = Rin/(2*pi*fc*Cp*Rin - 1); % [Ohm] % New ADC offset voltage V_offset = Ra*Rin/(Ra + Rin) * in; % [V] #+end_src Steps signals was generated using the DAC with an amplitude of 1V, and the ADC signal was recorded. The excitation signal (steps) and measured voltage across the sensor stack are shown in Figure ref:fig:detail_instrumentation_step_response_force_sensor. Two things can be observed: - an offset voltage of $2.26\,V$ - the measured voltage shows an exponential decay response to the step input This can be understood by the electrical schematic shown in Figure ref:fig:detail_instrumentation_force_sensor_adc were the ADC has some input impedance $R_i$ and input bias current $i_n$. The input impedance $R_i$ of the ADC with the capacitance $C_p$ of the piezoelectric stack sensor forms an RC circuit with a time constant $\tau = R_i C_p$. The charge generated by the piezoelectric effects across its capacitance is discharging into the input resistor of the ADC. Therefore, the transfer function from the generated voltage $V_p$ to the measured voltage $V_{\text{ADC}}$ is a first order high filter, with time constant $\tau$. The an exponential curve was fitted to the experimental data and a time constant $\tau = 6.5\,s$ was obtained. With the capacitance of the piezoelectric sensor stack being $C_p = 4.4 \mu F$, the internal impedance of the Speedgoat ADC can be computed as follows $R_i = \frac{\tau}{C_p} = 1.5\,M\Omega$. It is close to the specified value of $1\,M\Omega$ found in the datasheet #+begin_src matlab :exports none :results none %% Measured voltage accross the sensor stacks - Voltage steps are applied to the actuators figure; tiledlayout(1, 1, 'TileSpacing', 'compact', 'Padding', 'None'); nexttile(); hold on; plot(t, u, 'DisplayName', '$u$'); plot(t, v, 'DisplayName', '$V_s$'); plot(t_fit_1, y_fit_1, 'k--', 'DisplayName', 'fit'); plot(t_fit_2, y_fit_2, 'k--', 'HandleVisibility', 'off'); plot(t_fit_3, y_fit_3, 'k--', 'HandleVisibility', 'off'); hold off; xlabel('Time [s]'); ylabel('Voltage [V]'); leg = legend('location', 'southeast', 'FontSize', 8, 'NumColumns', 1); leg.ItemTokenSize(1) = 15; xlim([0, 20]); #+end_src #+begin_src matlab :tangle no :exports results :results file none exportFig('figs/detail_instrumentation_step_response_force_sensor.pdf', 'width', 'third', 'height', 300); #+end_src #+name: fig:detail_instrumentation_force_sensor #+caption: Electrical schematic of the ADC measuring the piezoelectric force sensor (\subref{fig:detail_instrumentation_force_sensor_adc}), adapted from cite:reza06_piezoel_trans_vibrat_contr_dampin. Measured voltage $V_s$ while step voltages are generated for the actuator stacks (\subref{fig:detail_instrumentation_step_response_force_sensor}). #+attr_latex: :options [htbp] #+begin_figure #+attr_latex: :caption \subcaption{\label{fig:detail_instrumentation_force_sensor_adc}Electrical Schematic} #+attr_latex: :options {0.61\textwidth} #+begin_subfigure #+attr_latex: :scale 1 [[file:figs/detail_instrumentation_force_sensor_adc.png]] #+end_subfigure #+attr_latex: :caption \subcaption{\label{fig:detail_instrumentation_step_response_force_sensor}Measured Signals} #+attr_latex: :options {0.35\textwidth} #+begin_subfigure #+attr_latex: :width 0.95\linewidth [[file:figs/detail_instrumentation_step_response_force_sensor.png]] #+end_subfigure #+end_figure The constant voltage offset can be explained by the input bias current $i_n$ of the ADC, represented in Figure ref:fig:detail_instrumentation_force_sensor_adc At DC, the impedance of the piezoelectric stack is much larger that the input impedance of the ADC, and therefore the input bias current $i_n$ passing through the internal resistance $R_i$ produces a constant voltage offset $V_{\text{off}} = R_i i_n$. The input bias current $i_n$ is estimated from $i_n = V_{\text{off}}/R_i = 1.5\,\mu A$. In order to reduce the input voltage offset and to increase the corner frequency of the high pass filter, a resistor $R_p$ is added in parallel to the force sensor, as illustrated in Figure ref:fig:detail_instrumentation_force_sensor_adc_R It has two effects: - Reduction of input voltage offset: \[ V_{\text{off}} = \frac{R_p R_{in}}{R_p + R_{in}} i_n \] - Increase the high pass corner frequency $f_c$ \[ \frac{R_i R_p}{R_i + R_p} C_p = \tau_c = \frac{1}{2 \pi f_c} \] \[ R_p = \frac{R_i}{2 \pi f_c C_p R_i - 1} \] The resistor is chosen such that the high pass corner frequency is equal to $0.5\,\text{Hz}$. This corresponds to a resistor of $R_p = 76\,k\Omega$. With this parallel resistance value, the voltage offset would be $V_{\text{off}} = 0.11\,V$, which is much more acceptable. To validate this, a resistor $R_p \approx 82\,k\Omega$ is then added in parallel with the force sensor as shown in Figure ref:fig:detail_instrumentation_force_sensor_adc_R. After the resistor is added, the same steps response were performed (Figure ref:fig:detail_instrumentation_step_response_force_sensor_R). And indeed, we obtain a much smaller offset voltage ($V_{\text{off}} = 0.15\,V$) and a much faster time constant ($\tau = 0.45\,s$). This validates the model of the ADC and the effectiveness of the added resistor. #+begin_src matlab %% Read force sensor voltage with the ADC with added 82.7kOhm resistor load('force_sensor_steps_R_82k7.mat', 't', 'encoder', 'u', 'v'); % Step times t_s = [1.9, 6; 8.5, 13; 15.5, 21; 22.6, 26; 30.0, 36; 37.5, 41; 46.2, 49.5]; % [s] tau = zeros(size(t_s, 1),1); % Time constant [s] V0 = zeros(size(t_s, 1),1); % Offset voltage [V] a = zeros(size(t_s, 1),1); % for t_i = 1:size(t_s, 1) t_cur = t(t_s(t_i, 1) < t & t < t_s(t_i, 2)); t_cur = t_cur - t_cur(1); y_cur = v(t_s(t_i, 1) < t & t < t_s(t_i, 2)); nrmrsd = @(b) norm(y_cur - f_exp(b,t_cur)); % Residual Norm Cost Function B0 = [0.5, 0.1, 2.2]; % Choose Appropriate Initial Estimates [B,rnrm] = fminsearch(nrmrsd, B0); % Estimate Parameters ‘B’ a(t_i) = B(1); tau(t_i) = 1/B(2); V0(t_i) = B(3); end % Data to show the exponential fit t_fit_1 = linspace(t_s(1,1), t_s(1,2), 100); y_fit_1 = f_exp([a(1),1/tau(1),V0(1)], t_fit_1-t_s(1,1)); t_fit_2 = linspace(t_s(2,1), t_s(2,2), 100); y_fit_2 = f_exp([a(2),1/tau(2),V0(2)], t_fit_2-t_s(2,1)); t_fit_3 = linspace(t_s(3,1), t_s(3,2), 100); y_fit_3 = f_exp([a(3),1/tau(3),V0(3)], t_fit_3-t_s(3,1)); #+end_src #+begin_src matlab :exports none :results none %% Measured voltage accross the sensor stacks - Voltage steps are applied to the actuators figure; tiledlayout(1, 1, 'TileSpacing', 'compact', 'Padding', 'None'); nexttile(); hold on; plot(t, u, 'DisplayName', '$u$'); plot(t, v, 'DisplayName', '$V_s$'); plot(t_fit_1, y_fit_1, 'k--', 'DisplayName', 'fit'); plot(t_fit_2, y_fit_2, 'k--', 'HandleVisibility', 'off'); plot(t_fit_3, y_fit_3, 'k--', 'HandleVisibility', 'off'); hold off; xlabel('Time [s]'); ylabel('Voltage [V]'); leg = legend('location', 'northeast', 'FontSize', 8, 'NumColumns', 1); leg.ItemTokenSize(1) = 15; xlim([0, 20]); #+end_src #+begin_src matlab :tangle no :exports results :results file none exportFig('figs/detail_instrumentation_step_response_force_sensor_R.pdf', 'width', 'third', 'height', 300); #+end_src #+name: fig:detail_instrumentation_force_sensor_R #+caption: Effect of an added resistor $R_p$ in parallel to the force sensor. The electrical schematic is shown in (\subref{fig:detail_instrumentation_force_sensor_adc_R}) and the measured signals in (\subref{fig:detail_instrumentation_step_response_force_sensor_R}). #+attr_latex: :options [htbp] #+begin_figure #+attr_latex: :caption \subcaption{\label{fig:detail_instrumentation_force_sensor_adc_R}Electrical Schematic} #+attr_latex: :options {0.61\textwidth} #+begin_subfigure #+attr_latex: :scale 1 [[file:figs/detail_instrumentation_force_sensor_adc_R.png]] #+end_subfigure #+attr_latex: :caption \subcaption{\label{fig:detail_instrumentation_step_response_force_sensor_R}Measured Signals} #+attr_latex: :options {0.35\textwidth} #+begin_subfigure #+attr_latex: :width 0.95\linewidth [[file:figs/detail_instrumentation_step_response_force_sensor_R.png]] #+end_subfigure #+end_figure ** Instrumentation Amplifier Because the ADC noise may be too large to measure noise of other instruments (anything below $5.6\,\mu V/\sqrt{Hz}$ cannot be distinguish from the noise of the ADC itself), a low noise instrumentation amplifier can be used. Here, a Femto DLPVA-101-B-S amplifier, with gains from 20dB up to 80dB, was used. But first, the input[fn:detail_instrumentation_2] noise of the amplifier was characterized. To do so, its input was short circuited with a $50\,\Omega$ resistor, and the output voltage was measured by the ADC (Figure ref:fig:detail_instrumentation_femto_meas_setup). The maximum amplifier gain of 80dB (i.e. 10000) was used. The measured voltage $n$ was then divided by 10000 to obtain the equivalent noise at the input of the voltage amplifier $n_a$. In that case, the noise of the ADC $q_{ad}$ is negligible, thanks to the high gain used. The obtained amplifier noise ASD $\Gamma_{n_a}$ and the (negligible) contribution of the ADC noise are shown in Figure ref:fig:detail_instrumentation_femto_input_noise. It was also verified that the bandwidth of the instrumentation amplifier is much larger than 5kHz such that not phase drop are added by the use of the amplifier in the frequency band of interest. #+begin_src latex :file detail_instrumentation_femto_meas_setup.pdf \begin{tikzpicture} \node[block={0.6cm}{0.6cm}] (const) {$0$}; % Pre Amp \node[addb, right=0.4 of const] (addna) {}; \node[block, right=0.3 of addna] (Ga) {$G_a(s)$}; % ADC \node[addb, right=0.8 of Ga] (addqad){}; \node[ADC, right=0.3 of addqad] (ADC) {ADC}; \draw[->] (const.east) -- (addna.west); \draw[->] (addna.east) -- (Ga.west); \draw[->] (Ga.east) -- (addqad.west); \draw[->] (addqad.east) -- (ADC.west); \draw[->] (ADC.east) -- node[sloped]{$/$} ++(0.8, 0) node[above left]{$n$}; \draw[<-] (addna.north) -- ++(0, 0.6) node[below right](na){$n_{a}$}; \draw[<-] (addqad.north) -- ++(0, 0.6) node[below right](qad){$q_{ad}$}; \coordinate[] (top) at (na.north); \coordinate[] (bot) at (Ga.south); % 5113 \begin{scope}[on background layer] \node[fit={(addna.west|-bot) (Ga.east|-top)}, inner sep=4pt, draw, dashed, fill=colorgreen!20!white] (P) {}; \node[above] at (P.north) {Pre Amp}; \end{scope} % ADC \begin{scope}[on background layer] \node[fit={(addqad.west|-bot) (ADC.east|-top)}, inner sep=4pt, draw, dashed, fill=coloryellow!20!white] (P) {}; \node[above] at (P.north) {ADC}; \end{scope} \end{tikzpicture} #+end_src #+begin_src matlab :exports none %% Femto Input Voltage Noise femto = load('noise_femto.mat', 't', 'Vout', 'notes'); % Load Data % Compute the equivalent voltage at the input of the amplifier femto.Vout = femto.Vout/femto.notes.pre_amp.gain; femto.Vout = femto.Vout - mean(femto.Vout); Ts = (femto.t(end) - femto.t(1))/(length(femto.t) - 1); Nfft = floor(1/Ts); win = hanning(Nfft); Noverlap = floor(Nfft/2); % Power Spectral Density [pxx, f] = pwelch(detrend(femto.Vout, 0), win, Noverlap, Nfft, 1/Ts); % Save the results inside the struct femto.pxx = pxx(f<=5e3); femto.f = f(f<=5e3); #+end_src #+begin_src matlab :exports none :results none %% Measured input voltage noise of the Femto voltage pre-amplifier figure; hold on; plot(femto.f, sqrt(femto.pxx), 'color', colors(5,:), 'DisplayName', '$\Gamma_{n_a}$'); plot(adc.f, sqrt(adc.pxx)./femto.notes.pre_amp.gain, 'color', colors(3,:), 'DisplayName', '$\Gamma_{q_{ad}}/|G_a|$') hold off; set(gca, 'XScale', 'log'); set(gca, 'YScale', 'log'); xlabel('Frequency [Hz]'); ylabel('ASD [$V/\sqrt{Hz}$]'); legend('location', 'northeast'); xlim([1, 5e3]); ylim([2e-10, 1e-7]); xticks([1e0, 1e1, 1e2, 1e3]); yticks([1e-9, 1e-8]); #+end_src #+begin_src matlab :tangle no :exports results :results file none exportFig('figs/detail_instrumentation_femto_input_noise.pdf', 'width', 'half', 'height', 350); #+end_src #+attr_latex: :options [b]{0.48\linewidth} #+begin_minipage #+name: fig:detail_instrumentation_femto_meas_setup #+caption: Measurement of the instrumentation amplifier input voltage noise #+attr_latex: :scale 1 :float nil [[file:figs/detail_instrumentation_femto_meas_setup.png]] #+end_minipage \hfill #+attr_latex: :options [b]{0.48\linewidth} #+begin_minipage #+name: fig:detail_instrumentation_femto_input_noise #+caption: Obtained ASD of the instrumentation amplifier input voltage noise #+attr_latex: :scale 1 :float nil [[file:figs/detail_instrumentation_femto_input_noise.png]] #+end_minipage ** Digital to Analog Converters **** Output Voltage Noise In order to measure the output noise of the DAC, the measurement setup schematically represented in Figure ref:fig:detail_instrumentation_dac_setup was used. The DAC was instructed to output constant voltage (here zero), and the gain of the pre-amplifier is adjusted such that the measured amplified noise is much larger than the quantization noise of the ADC. The Amplitude Spectral Density $\Gamma_{n_{da}}(\omega)$ of the measured signal was computed and it was verified the contribution of the ADC noise and amplifier noise are negligible. #+begin_src latex :file detail_instrumentation_dac_setup.pdf \begin{tikzpicture} \node[block={0.6cm}{0.6cm}] (const) {$0$}; % DAC \node[DAC, right=0.4 of const] (DAC) {DAC}; \node[addb, right=0.3 of DAC] (addnda){}; % Pre Amp \node[addb, right=0.8 of addnda] (addna) {}; \node[block, right=0.3 of addna] (Ga) {$G_a(s)$}; % ADC \node[addb, right=0.8 of Ga] (addqad){}; \node[ADC, right=0.3 of addqad] (ADC) {ADC}; \draw[->] (const.east) -- node[sloped]{$/$} (DAC.west); \draw[->] (DAC.east) -- (addnda.west); \draw[->] (addnda.east) -- (addna.west); \draw[->] (addna.east) -- (Ga.west); \draw[->] (Ga.east) -- (addqad.west); \draw[->] (addqad.east) -- (ADC.west); \draw[->] (ADC.east) -- node[sloped]{$/$} ++(0.8, 0); \draw[<-] (addnda.north) -- ++(0, 0.6) node[below left](nda){$n_{da}$}; \draw[<-] (addna.north) -- ++(0, 0.6) node[below right](na){$n_{a}$}; \draw[<-] (addqad.north) -- ++(0, 0.6) node[below right](qad){$q_{ad}$}; \coordinate[] (top) at (na.north); \coordinate[] (bot) at (Ga.south); % DAC \begin{scope}[on background layer] \node[fit={(DAC.west|-bot) (addnda.east|-top)}, inner sep=4pt, draw, dashed, fill=colorblue!20!white] (P) {}; \node[above] at (P.north) {DAC}; \end{scope} % 5113 \begin{scope}[on background layer] \node[fit={(addna.west|-bot) (Ga.east|-top)}, inner sep=4pt, draw, dashed, fill=colorgreen!20!white] (P) {}; \node[above] at (P.north) {Pre Amp}; \end{scope} % ADC \begin{scope}[on background layer] \node[fit={(addqad.west|-bot) (ADC.east|-top)}, inner sep=4pt, draw, dashed, fill=coloryellow!20!white] (P) {}; \node[above] at (P.north) {ADC}; \end{scope} \end{tikzpicture} #+end_src #+name: fig:detail_instrumentation_dac_setup #+caption: Measurement of the DAC output voltage noise. A pre-amplifier with a gain of 1000 is used before measuring the signal with the ADC. #+RESULTS: [[file:figs/detail_instrumentation_dac_setup.png]] #+begin_src matlab :exports none %% DAC Output Voltage Noise dac = load('mat/noise_dac.mat', 't', 'Vn', 'notes'); % Take input acount the gain of the pre-amplifier dac.Vn = dac.Vn/dac.notes.pre_amp.gain; dac.Vn = dac.Vn - mean(dac.Vn); Ts = (dac.t(end) - dac.t(1))/(length(dac.t) - 1); Nfft = floor(1/Ts); win = hanning(Nfft); Noverlap = floor(Nfft/2); % Identification of the transfer function from Va to di [pxx, f] = pwelch(dac.Vn, win, Noverlap, Nfft, 1/Ts); dac.pxx = pxx(f<=5e3); dac.f = f(f<=5e3); % Estimated mean ASD sprintf('Mean ASD of the DAC: %.1f uV/sqrt(Hz)', 1e6*sqrt(mean(dac.pxx))) sprintf('Specifications: %.1f uV/sqrt(Hz)', 1e6*max_dac_asd) % Estimated RMS sprintf('RMS of the DAC: %.2f mV RMS', 1e3*rms(dac.Vn)) sprintf('RMS specifications: %.2f mV RMS', max_dac_rms) #+end_src The obtained Amplitude Spectral Density of the DAC's output voltage is shown in Figure ref:fig:detail_instrumentation_dac_output_noise. It is almost white noise with an ASD of 0.6uV/sqrt(Hz). There is a little bit of 50Hz, and some low frequency noise (thermal noise?) which are not foreseen to be an issue as it will be inside the bandwidth. Note that all channels are measuring the same, so only one channel is shown here. #+begin_src matlab :exports none figure; tiledlayout(1, 1, 'TileSpacing', 'compact', 'Padding', 'None'); ax1 = nexttile(); hold on; plot(femto.f, sqrt(femto.pxx), 'color', colors(5,:), 'DisplayName', '$\Gamma_{n_a}$'); plot(dac.f, sqrt(dac.pxx), 'color', colors(1,:), 'DisplayName', '$\Gamma_{n_{da}}$'); plot([dac.f(2), dac.f(end)], [max_dac_asd, max_dac_asd], '--', 'color', colors(1,:), 'DisplayName', 'DAC specs') plot(adc.f, sqrt(adc.pxx)./dac.notes.pre_amp.gain, 'color', colors(3,:), 'DisplayName', '$\Gamma_{q_{ad}}/|G_a|$') hold off; set(gca, 'XScale', 'log'); set(gca, 'YScale', 'log'); xlabel('Frequency [Hz]'); ylabel('ASD [$V/\sqrt{Hz}$]'); leg = legend('location', 'northeast', 'FontSize', 8, 'NumColumns', 1); leg.ItemTokenSize(1) = 15; xlim([1, 5e3]); ylim([2e-10, 4e-4]); xticks([1e0, 1e1, 1e2, 1e3]); #+end_src #+begin_src matlab :tangle no :exports results :results file none exportFig('figs/detail_instrumentation_dac_output_noise.pdf', 'width', 'half', 'height', 'normal'); #+end_src **** Delay from ADC to DAC In order to measure the transfer function from DAC to ADC and verify that the bandwidth of both instrument is high enough, the DAC output was directly wired to the ADC input. A white noise signal was generated by the DAC, and the ADC signal was recorded. The obtained frequency response function from the digital DAC signal to the digital ADC signal is shown in (Figure ref:fig:detail_instrumentation_dac_adc_tf). It corresponds to 1 sample delay, which is corresponding to the specifications. #+begin_src matlab %% Measure transfer function from DAC to ADC data_dac_adc = load("2023-08-22_15-52_io131_dac_to_adc.mat"); % Frequency analysis parameters Ts = 1e-4; % Sampling Time [s] Nfft = floor(1.0/Ts); win = hanning(Nfft); Noverlap = floor(Nfft/2); [G_dac_adc, f] = tfestimate(data_dac_adc.dac_1, data_dac_adc.adc_1, win, Noverlap, Nfft, 1/Ts); % G_delay = exp(-Ts*s); #+end_src #+begin_src matlab :exports none :results none %% Measure transfer function from DAC to ADC - It fits a pure "1-sample" delay figure; tiledlayout(3, 1, 'TileSpacing', 'compact', 'Padding', 'None'); ax1 = nexttile([2,1]); hold on; plot(f, abs(G_dac_adc), 'color', colors(2,:), 'DisplayName', 'Measurement'); plot(f, abs(squeeze(freqresp(G_delay, f, 'Hz'))), 'k--', 'DisplayName', 'Pure Delay'); hold off; set(gca, 'XScale', 'log'); set(gca, 'YScale', 'log'); ylabel('Amplitude [V/V]'); set(gca, 'XTickLabel',[]); ylim([1e-1, 1e1]); leg = legend('location', 'southeast', 'FontSize', 8, 'NumColumns', 1); leg.ItemTokenSize(1) = 15; ax2 = nexttile(); hold on; plot(f, 180/pi*unwrap(angle(G_dac_adc)), 'color', colors(2,:)); plot(f, 180/pi*unwrap(angle(squeeze(freqresp(G_delay, f, 'Hz')))), 'k--', 'DisplayName', 'Pure Delay'); hold off; set(gca, 'XScale', 'log'); set(gca, 'YScale', 'lin'); xlabel('Frequency [Hz]'); ylabel('Phase [deg]'); hold off; yticks(-360:90:360); ylim([-200, 20]) linkaxes([ax1,ax2],'x'); xlim([1, 5e3]); xticks([1e0, 1e1, 1e2, 1e3]); #+end_src #+begin_src matlab :tangle no :exports results :results file replace exportFig('figs/detail_instrumentation_dac_adc_tf.pdf', 'width', 'half', 'height', 'normal'); #+end_src #+name: fig:detail_instrumentation_dac #+caption: Measurement of the output voltage noise of the ADC (\subref{fig:detail_instrumentation_dac_output_noise}) and measured transfer function from DAC to ADC (\subref{fig:detail_instrumentation_dac_adc_tf}) which corresponds to a "1-sample" delay. #+attr_latex: :options [htbp] #+begin_figure #+attr_latex: :caption \subcaption{\label{fig:detail_instrumentation_dac_output_noise}Output noise of the DAC} #+attr_latex: :options {0.48\textwidth} #+begin_subfigure #+attr_latex: :width 0.95\linewidth [[file:figs/detail_instrumentation_dac_output_noise.png]] #+end_subfigure #+attr_latex: :caption \subcaption{\label{fig:detail_instrumentation_dac_adc_tf}Transfer function from DAC to ADC} #+attr_latex: :options {0.48\textwidth} #+begin_subfigure #+attr_latex: :width 0.95\linewidth [[file:figs/detail_instrumentation_dac_adc_tf.png]] #+end_subfigure #+end_figure ** Piezoelectric Voltage Amplifier **** Output Voltage Noise The measurement setup is shown in Figure ref:fig:detail_instrumentation_pd200_setup. The input of the PD200 amplifier is shunted with a $50\,\Omega$ resistor such that only the noise of the amplifier itself is measured. The gain of the pre-amplifier was increased in order to measure a signal much larger than the noise of the ADC. Two piezoelectric stacks of the APA95ML were connected to the PD200 output to provide an appropriate load. #+begin_src latex :file detail_instrumentation_pd200_setup.pdf \begin{tikzpicture} \node[block={0.6cm}{0.6cm}] (const) {$0$}; % PD200 \node[block, right=0.4 of const] (Gp){$G_p(s)$}; \node[addb, right=0.3 of Gp] (addnp){}; % Pre Amp \node[addb, right=0.8 of addnp] (addna) {}; \node[block, right=0.3 of addna] (Ga) {$G_a(s)$}; % ADC \node[addb, right=0.8 of Ga] (addqad){}; \node[ADC, right=0.3 of addqad] (ADC) {ADC}; \draw[->] (const.east) -- (Gp.west); \draw[->] (Gp.east) -- (addnp.west); \draw[->] (addnp.east) -- (addna.west); \draw[->] (addna.east) -- (Ga.west); \draw[->] (Ga.east) -- (addqad.west); \draw[->] (addqad.east) -- (ADC.west); \draw[->] (ADC.east) -- node[sloped]{$/$} ++(0.8, 0) node[above left]{$n$}; \draw[<-] (addnp.north) -- ++(0, 0.6) node[below left](np){$n_{p}$}; \draw[<-] (addna.north) -- ++(0, 0.6) node[below right](na){$n_{a}$}; \draw[<-] (addqad.north) -- ++(0, 0.6) node[below right](qad){$q_{ad}$}; \coordinate[] (top) at (na.north); \coordinate[] (bot) at (Ga.south); % PD200 \begin{scope}[on background layer] \node[fit={(addnp.east|-bot) (Gp.west|-top)}, inner sep=4pt, draw, dashed, fill=colorred!20!white] (P) {}; \node[above] at (P.north) {PD200}; \end{scope} % 5113 \begin{scope}[on background layer] \node[fit={(addna.west|-bot) (Ga.east|-top)}, inner sep=4pt, draw, dashed, fill=colorgreen!20!white] (P) {}; \node[above] at (P.north) {Pre Amp}; \end{scope} % ADC \begin{scope}[on background layer] \node[fit={(addqad.west|-bot) (ADC.east|-top)}, inner sep=4pt, draw, dashed, fill=coloryellow!20!white] (P) {}; \node[above] at (P.north) {ADC}; \end{scope} \end{tikzpicture} #+end_src #+name: fig:detail_instrumentation_pd200_setup #+caption: Setup used to measured the output voltage noise of the PD200 voltage amplifier. A gain $G_a = 1000$ was used for the instrumentation amplifier. #+RESULTS: [[file:figs/detail_instrumentation_pd200_setup.png]] The Amplitude Spectral Density $\Gamma_n(\omega)$ of the measured signal by the ADC is computed. The Amplitude Spectral Density of the output voltage noise of the PD200 amplifier $n_p$ is then computed taking into account the gain of the pre-amplifier: \begin{equation} \Gamma_{n_p}(\omega) = \frac{\Gamma_n(\omega)}{|G_p(j\omega) G_a(j\omega)|} \end{equation} The Amplitude Spectral Density of the measured output noise of the PD200 is computed and shown in Figure ref:fig:detail_instrumentation_pd200_noise. It is verified that the contribution of the PD200 noise is much larger than the contribution of the pre-amplifier noise of the quantization noise (i.e. what is measured is indeed the PD200 noise). Here, the measured noise of the six received amplifiers are all shown. The Amplitude Spectral Density of the output voltage noise of the PD200 amplifiers present sharp peaks. The reason for all these peaks is not clear, but as their amplitude are bellow the specifications, it should not pose any issue. #+begin_src matlab :exports none %% PD200 Output Voltage Noise % Load all the measurements pd200 = {}; for i = 1:6 pd200(i) = {load(['mat/noise_PD200_' num2str(i) '_10uF.mat'], 't', 'Vout', 'notes')}; end % Take into account the pre-amplifier gain for i = 1:6 pd200{i}.Vout = pd200{i}.Vout/pd200{i}.notes.pre_amp.gain; end % Sampling time / frequency Ts = (pd200{1}.t(end) - pd200{1}.t(1))/(length(pd200{1}.t) - 1); % Compute the PSD of the measured noise Nfft = floor(1/Ts); win = hanning(Nfft); Noverlap = floor(Nfft/2); for i = 1:6 % Identification of the transfer function from Va to di [pxx, f] = pwelch(pd200{i}.Vout, win, Noverlap, Nfft, 1/Ts); pd200{i}.pxx = pxx(f<=5e3); pd200{i}.f = f(f<=5e3); end % Estimated RMS sprintf('RMS of the PD200: %.2f mV RMS', 1e3*rms(detrend(pd200{1}.Vout,0))) sprintf('RMS specifications: %.2f mV RMS', max_amp_rms) #+end_src #+begin_src matlab :exports none :results none %% Measured output voltage noise of the PD200 amplifiers figure; hold on; plot([1 Fs/2], [max_amp_asd, max_amp_asd], '--', 'color', colors(2,:), 'DisplayName', 'Specs') plot(pd200{1}.f, sqrt(pd200{1}.pxx), 'color', [colors(2, :), 0.5], 'DisplayName', '$\Gamma_{n_p}$'); for i = 2:6 plot(pd200{i}.f, sqrt(pd200{i}.pxx), 'color', [colors(2, :), 0.5], 'HandleVisibility', 'off'); end plot(femto.f, sqrt(femto.pxx), 'color', [colors(5, :)], 'DisplayName', '$\Gamma_{n_a}$'); plot(adc.f, sqrt(adc.pxx)./pd200{1}.notes.pre_amp.gain, 'color', colors(3,:), 'DisplayName', '$\Gamma_{q_{ad}}/|G_a|$') hold off; set(gca, 'XScale', 'log'); set(gca, 'YScale', 'log'); xlabel('Frequency [Hz]'); ylabel('ASD [$V/\sqrt{Hz}$]'); leg = legend('location', 'southwest', 'FontSize', 8, 'NumColumns', 1); leg.ItemTokenSize(1) = 15; ylim([1e-10, 4e-4]); xlim([1, 5e3]); xticks([1e0, 1e1, 1e2, 1e3]) #+end_src #+begin_src matlab :tangle no :exports results :results file replace exportFig('figs/detail_instrumentation_pd200_noise.pdf', 'width', 'wide', 'height', 'normal'); #+end_src #+name: fig:detail_instrumentation_pd200_noise #+caption: Measured output voltage noise of the PD200 amplifiers #+RESULTS: [[file:figs/detail_instrumentation_pd200_noise.png]] **** Small Signal Bandwidth Here the small signal dynamics of all the PD200 amplifiers are identified. A (logarithmic) sweep sine excitation voltage is generated by the Speedgoat DAC with an amplitude of 0.1V and a frequency going from 1Hz up to 5kHz. The output voltage of the PD200 amplifier is measured thanks to the monitor voltage of the PD200 amplifier. The input voltage of the PD200 amplifier (the generated voltage by the DAC) is measured with another ADC of the Speedgoat. This way, the time delay related to the ADC will not be apparent in the results. All six received amplifiers are measuring the same regarding their transfer functions. The amplitude is constant over a wide frequency band and the phase drop is limited to less than 1 degree up to 500Hz, which is well within the specifications. The identified dynamics in Figure ref:fig:detail_instrumentation_pd200_tf can very well be modeled with a first order low pass filter or even a simple constant. #+begin_src matlab :exports none %% Load all the measurements pd200 = {}; for i = 1:6 pd200(i) = {load(['tf_pd200_' num2str(i) '_10uF_small_signal.mat'], 't', 'Vin', 'Vout', 'notes')}; end % Compute sampling Frequency Ts = (pd200{1}.t(end) - pd200{1}.t(1))/(length(pd200{1}.t)-1); % Compute all the transfer functions Nfft = floor(1.0/Ts); win = hanning(Nfft); Noverlap = floor(Nfft/2); for i = 1:length(pd200) [tf_est, f] = tfestimate(pd200{i}.Vin, 20*pd200{i}.Vout, win, Noverlap, Nfft, 1/Ts); pd200{i}.tf = tf_est(f<=5e3); pd200{i}.f = f(f<=5e3); end % Amplified model Gp = 20/(1 + s/2/pi/25e3); #+end_src #+begin_src matlab :exports none figure; tiledlayout(3, 1, 'TileSpacing', 'compact', 'Padding', 'None'); ax1 = nexttile([2,1]); hold on; plot(pd200{1}.f, abs(pd200{1}.tf), '-', 'color', [colors(2,:), 0.5], 'linewidth', 2.5, 'DisplayName', 'Measurement') plot(pd200{1}.f, abs(squeeze(freqresp(Gp, pd200{1}.f, 'Hz'))), '--', 'color', colors(2,:), 'DisplayName', 'Model') hold off; set(gca, 'XScale', 'log'); set(gca, 'YScale', 'log'); ylabel('Magnitude [V/V]'); set(gca, 'XTickLabel',[]); hold off; ylim([1, 1e2]); leg = legend('location', 'southeast', 'FontSize', 8, 'NumColumns', 1); leg.ItemTokenSize(1) = 15; ax2 = nexttile; hold on; plot(pd200{1}.f, 180/pi*unwrap(angle(pd200{1}.tf)), '-', 'color', [colors(2,:), 0.5], 'linewidth', 2.5) plot(pd200{1}.f, 180/pi*unwrap(angle(squeeze(freqresp(Gp, pd200{1}.f, 'Hz')))), '--', 'color', colors(2,:)) set(gca, 'XScale', 'log'); set(gca, 'YScale', 'lin'); xlabel('Frequency [Hz]'); ylabel('Phase [deg]'); hold off; yticks(-360:2:360); ylim([-13, 1]); linkaxes([ax1,ax2],'x'); xlim([1, 5e3]); #+end_src #+begin_src matlab :tangle no :exports results :results file replace exportFig('figs/detail_instrumentation_pd200_tf.pdf', 'width', 'wide', 'height', 600); #+end_src #+name: fig:detail_instrumentation_pd200_tf #+caption: Identified dynamics from input voltage to output voltage #+RESULTS: [[file:figs/detail_instrumentation_pd200_tf.png]] **** Output Impedance :noexport: To confirms the model of the voltage amplifiers shown in Figure ref:fig:detail_instrumentation_amp_output_impedance (page pageref:fig:detail_instrumentation_amp_output_impedance), the output impedance $R_o$ of the PD200 was measured. A DAC with a constant output voltage (here 0.1V) is connected to the input of the PD200 amplifier. Then, the output voltage of the PD200 amplifier is measured in two conditions: - $V$ when the output is not connected to any load - $V_p$ when a load $R_{\text{load}} = 10\,\Omega$ is connected at the output of the amplifier The load and the output impedance form a voltage divider, and thus: \[ V_p = \frac{R_{\text{load}}}{R_{\text{load}} + R_o} V \] From the two values of voltage, the output impedance of the amplifier can be estimated from eqref:eq:detail_instrumentation_output_impedance_amp. \begin{equation}\label{eq:detail_instrumentation_output_impedance_amp} R_{o} = R_{\text{load}} \frac{V - V_p}{V} \end{equation} From this measurement, all the PD200 amplifiers are found to have an output impedance $R_{\text{out}} \approx 1\,\Omega$. With the capacitive load $C_p = 8.8\,\mu F$, the output resistor of the amplifier forms a low pass filter with a corner frequency equal to $f_0 = \frac{1}{R_{\text{out}} C_p}$. We get a corner frequency around $17\,\text{kHz}$ which is not far from the specified $7.4\,\text{kHz}$. #+begin_src matlab % Estimate output impedance of PD200 amplifiers R_load = 10; % Load [Ohm] % V Vp meas = [1.988, 1.794; % PD200 - 1 1.990, 1.789; % PD200 - 2 1.982, 1.795; % PD200 - 3 1.984, 1.789; % PD200 - 4 1.998, 1.810; % PD200 - 5 1.984, 1.799];% PD200 - 6 R_out = R_load * (meas(:,1) - meas(:,2))./meas(:,2); % Output impedance [Ohm] % Estimate corner frequency of the high pass filter Cp = 8.8e-6; % Capacitive load of the two piezoelectric actuators f0 = 1./(2*pi*R_out*Cp) #+end_src ** Noise of the full setup with 16bits DAC :noexport: - [ ] Maybe not so useful Let's now measure the noise of the full setup in Figure ref:fig:noise_meas_procedure_bis and analyze the results. #+name: fig:noise_meas_procedure_bis #+caption: Sources of noise in the experimental setup #+RESULTS: [[file:figs/noise_meas_procedure.png]] #+begin_src matlab :exports none %% Full measurement chain from DAC to ADC pd200dac = {}; for i = 1:7 pd200dac(i) = {load(['mat/noise_PD200_' num2str(i) '_10uF_DAC.mat'], 't', 'Vout', 'notes')}; end #+end_src #+begin_src matlab :exports none % Take into account the pre-amplifier gain for i = 1:7 pd200dac{i}.Vout = pd200dac{i}.Vout/pd200dac{i}.notes.pre_amp.gain/20; pd200dac{i}.Vout = pd200dac{i}.Vout - mean(pd200dac{i}.Vout); end #+end_src #+begin_src matlab :exports none % Sampling time / frequency Ts = (pd200dac{1}.t(end) - pd200dac{1}.t(1))/(length(pd200dac{1}.t) - 1); Fs = 1/Ts; #+end_src The Amplitude Spectral Density of the measured noise is computed and the shown in Figure ref:fig:asd_noise_tot. We can very well see that to total measured noise is the sum of the DAC noise and the PD200 noise. #+begin_src matlab :exports none % Compute the PSD of the measured noise win = hanning(ceil(2/Ts)); for i = 1:7 [pxx, f] = pwelch(pd200dac{i}.Vout, win, [], [], Fs); pd200dac{i}.f = f; pd200dac{i}.pxx = pxx; end #+end_src #+begin_src matlab :exports none colors = get(gca,'colororder'); figure; hold on; plot(egg.f, sqrt(egg.pxx)/20, 'DisplayName', '$\Gamma_{n_a}$'); plot(pd200{1}.f, sqrt(pd200{1}.pxx), 'color', [colors(2, :), 0.5], 'DisplayName', '$\Gamma_{n_p}/|G_p|$'); for i = 2:7 plot(pd200{i}.f, sqrt(pd200{i}.pxx), 'color', [colors(2, :), 0.5], 'HandleVisibility', 'off'); end set(gca,'ColorOrderIndex',3) plot(dac.f, sqrt(dac.pxx), 'DisplayName', '$|G_p| \cdot \Gamma_{n_{da}}$'); plot([1 Fs/2], [adc.Gamma_q, adc.Gamma_q]./dac.notes.pre_amp.gain/20, 'k--', 'DisplayName', '$\Gamma_{q_{ad}}/|G_p G_a|$'); plot(pd200dac{1}.f, sqrt(pd200dac{1}.pxx), 'color', [colors(4, :), 0.5], 'DisplayName', '$\Gamma_{tot}$'); for i = 2:7 plot(pd200dac{i}.f, sqrt(pd200dac{i}.pxx), 'color', [colors(4, :), 0.5], 'HandleVisibility', 'off'); end hold off; set(gca, 'XScale', 'log'); set(gca, 'YScale', 'log'); xlabel('Frequency [Hz]'); ylabel('ASD [$V/\sqrt{Hz}$]'); legend('location', 'southeast'); xlim([1, Fs/2]); ylim([1e-11, 1e-4]); #+end_src #+begin_src matlab :tangle no :exports results :results file replace exportFig('figs/asd_noise_tot.pdf', 'width', 'wide', 'height', 'tall'); #+end_src #+name: fig:asd_noise_tot #+caption: Amplitude Spectral Density of the measured noise and of the individual sources of noise #+RESULTS: [[file:figs/asd_noise_tot.png]] #+begin_important The input noise of the PD200 amplifier is limited by the output voltage noise of the DAC. Having a DAC with lower output voltage noise could lower the overall noise of the setup. SSI2V 20bits DACs are used in the next section to verify that. #+end_important ** Linear Encoders To measure the noise $n$ of the encoder, one can rigidly fix the head and the ruler together such that no motion should be measured. Then, the measured signal $y_m$ corresponds to the noise $n$. The measurement bench is shown in Figure ref:fig:detail_instrumentation_vionic_bench. Note that the bench is then covered with a "plastic bubble sheet" in order to keep disturbances as small as possible. Then, and for all the six encoders, the measured motion during 100s with a sampling frequency of 20kHz. The obtained amplitude spectral density of the measured displacement (i.e. measurement noise) is shown in Figure ref:fig:detail_instrumentation_vionic_asd. It corresponds to a white noise, with an amplitude $\approx 1\,nm\,\text{RMS}$. #+begin_src matlab :exports none %% Load all the measurements enc = {}; for i = 1:6 enc(i) = {load(['mat/noise_meas_100s_20kHz_' num2str(i) '.mat'], 't', 'x')}; end % Compute sampling Frequency Ts = (enc{1}.t(end) - enc{1}.t(1))/(length(enc{1}.t)-1); Nfft = floor(1.0/Ts); win = hanning(Nfft); Noverlap = floor(Nfft/2); for i = 1:length(enc) [pxx, f] = pwelch(detrend(enc{i}.x, 0), win, Noverlap, Nfft, 1/Ts); enc{i}.pxx = pxx(f<=5e3); enc{i}.pxx(2) = enc{i}.pxx(3); % Remove first point which corresponds to drifts enc{i}.f = f(f<=5e3); end #+end_src #+begin_src matlab :exports none :results none %% Measured Amplitude Spectral Density of the encoder position noise figure; hold on; plot(enc{1}.f, sqrt(enc{1}.pxx), 'color', colors(4,:)); hold off; set(gca, 'XScale', 'log'); set(gca, 'YScale', 'log'); xlabel('Frequency [Hz]'); ylabel('ASD [$m/\sqrt{Hz}$]'); xlim([1, 5e3]); ylim([1e-12, 1e-8]); #+end_src #+begin_src matlab :tangle no :exports results :results file replace exportFig('figs/detail_instrumentation_vionic_asd.pdf', 'width', 'half', 'height', 'normal'); #+end_src #+attr_latex: :options [b]{0.48\linewidth} #+begin_minipage #+name: fig:detail_instrumentation_vionic_bench #+caption: Test bench used to measured the encoder noise #+attr_latex: :width 0.95\linewidth :float nil [[file:figs/detail_instrumentation_vionic_bench.jpg]] #+end_minipage \hfill #+attr_latex: :options [b]{0.48\linewidth} #+begin_minipage #+name: fig:detail_instrumentation_vionic_asd #+caption: Measured Amplitude Spectral Density of the encoder position noise #+attr_latex: :width 0.95\linewidth :float nil [[file:figs/detail_instrumentation_vionic_asd.png]] #+end_minipage ** Noise budgeting from measured instrumentation noise Once all the instrumentation noise were characterized, the effect of the instrumentation noise on the sample's vibration can be assessed using the multi-body model. The obtained vertical motion induced by the ADC noise, DAC noise and voltage amplifier noise is displayed in Figure ref:fig:detail_instrumentation_cl_noise_budget (the effect of encoder noise is negligible). The total motion induced by all the noise sources is around $1.5\,nm$ which is well within the specifications. #+begin_src matlab %% Estimate the resulting errors induced by noise of instruments f = dac.f; % Vertical direction psd_z_dac = 6*(abs(squeeze(freqresp(Gd('z', 'nda1' ), f, 'Hz'))).^2).*dac.pxx; psd_z_adc = 6*(abs(squeeze(freqresp(Gd('z', 'nad1' ), f, 'Hz'))).^2).*adc.pxx; psd_z_amp = 6*(abs(squeeze(freqresp(Gd('z', 'namp1'), f, 'Hz'))).^2).*pd200{1}.pxx; psd_z_enc = 6*(abs(squeeze(freqresp(Gd('z', 'ddL1' ), f, 'Hz'))).^2).*enc{1}.pxx; psd_z_tot = psd_z_dac + psd_z_adc + psd_z_amp + psd_z_enc; rms_z_dac = sqrt(trapz(f, psd_z_dac)); rms_z_adc = sqrt(trapz(f, psd_z_adc)); rms_z_amp = sqrt(trapz(f, psd_z_amp)); rms_z_enc = sqrt(trapz(f, psd_z_enc)); rms_z_tot = sqrt(trapz(f, psd_z_tot)); % Lateral direction psd_y_dac = 6*(abs(squeeze(freqresp(Gd('y', 'nda1' ), f, 'Hz'))).^2).*dac.pxx; psd_y_adc = 6*(abs(squeeze(freqresp(Gd('y', 'nad1' ), f, 'Hz'))).^2).*adc.pxx; psd_y_amp = 6*(abs(squeeze(freqresp(Gd('y', 'namp1'), f, 'Hz'))).^2).*pd200{1}.pxx; psd_y_enc = 6*(abs(squeeze(freqresp(Gd('y', 'ddL1' ), f, 'Hz'))).^2).*enc{1}.pxx; psd_y_tot = psd_y_dac + psd_y_adc + psd_y_amp + psd_y_enc; rms_y_tot = sqrt(trapz(f, psd_y_tot)); #+end_src #+begin_src matlab :exports none :results none %% Closed-loop noise budgeting using measured noise of instrumentation figure; hold on; plot(f, sqrt(psd_z_amp), 'color', [colors(2,:)], 'linewidth', 2.5, 'DisplayName', 'PD200'); plot(f, sqrt(psd_z_dac), 'color', [colors(1,:)], 'linewidth', 2.5, 'DisplayName', 'DAC') plot(f, sqrt(psd_z_adc), 'color', [colors(3,:)], 'linewidth', 2.5, 'DisplayName', 'ADC') plot(f, sqrt(psd_z_tot), 'k-', 'DisplayName', sprintf('Total: %.1f nm RMS', 1e9*rms_z_tot)); hold off; set(gca, 'XScale', 'log'); set(gca, 'YScale', 'log'); xlabel('Frequency [Hz]'); ylabel('ASD [$m/\sqrt{Hz}$]'); leg = legend('location', 'southwest', 'FontSize', 8, 'NumColumns', 1); leg.ItemTokenSize(1) = 15; xlim([1, 5e3]); ylim([1e-14, 1e-9]); xticks([1e0, 1e1, 1e2, 1e3]); #+end_src #+begin_src matlab :tangle no :exports results :results file replace exportFig('figs/detail_instrumentation_cl_noise_budget.pdf', 'width', 'wide', 'height', 'normal'); #+end_src #+name: fig:detail_instrumentation_cl_noise_budget #+caption: Closed-loop noise budgeting using measured noise of instrumentation #+RESULTS: [[file:figs/detail_instrumentation_cl_noise_budget.png]] * Conclusion :PROPERTIES: :UNNUMBERED: t :END: <> - thanks to multi-body model in which it is easy to include instrumentation and noise sources From specification on the sample's vertical motion (most stringent requirement), specification for each noise source was extracted. - based on those specifications, adequate instrumentation were chosen. for some instrumentation, it was difficult to choose only based on data-sheets are manufacturers often don't share relevant information for noise budgets, such as amplitude spectral densities - then, the instrumentation was procured and tested individually. All were found to comply with the requirements. Finally, based on the measured noise of all instrumentation, the expected sample's vibration induced by all the noise sources was estimated and found to comply with the requirements. * Bibliography :ignore: #+latex: \printbibliography[heading=bibintoc,title={Bibliography}] * Helping Functions :noexport: ** Initialize Path #+NAME: m-init-path #+BEGIN_SRC matlab %% Path for functions, data and scripts addpath('./matlab/'); addpath('./matlab/src/'); % Path for scripts addpath('./matlab/mat/'); % Path for data addpath('./matlab/STEPS/'); % Path for Simscape Model addpath('./matlab/subsystems/'); % Path for Subsystems Simulink files #+END_SRC #+NAME: m-init-path-tangle #+BEGIN_SRC matlab %% Path for functions, data and scripts addpath('./src/'); % Path for scripts addpath('./mat/'); % Path for data addpath('./STEPS/'); % Path for Simscape Model addpath('./subsystems/'); % Path for Subsystems Simulink files #+END_SRC ** Initialize other elements #+NAME: m-init-other #+BEGIN_SRC matlab %% Colors for the figures colors = colororder; freqs = logspace(1,4,1000); % Frequency vector [Hz] #+END_SRC * Matlab Functions :noexport: *** =initializeSimplifiedNanoHexapod=: Nano Hexapod #+begin_src matlab :tangle matlab/src/initializeSimplifiedNanoHexapod.m :comments none :mkdirp yes :eval no function [nano_hexapod] = initializeSimplifiedNanoHexapod(args) arguments args.type char {mustBeMember(args.type,{'none', 'stewart'})} = 'stewart' end stewart = initializeStewartPlatform(); switch args.type case 'none' stewart.type = 0; case 'stewart' stewart.type = 1; end stewart = initializeFramesPositions(stewart, ... 'H', 95e-3, ... 'MO_B', 150e-3); stewart = generateGeneralConfiguration(stewart, ... 'FH', 15e-3, ... 'FR', 120e-3, ... 'FTh', [220, 320, 340, 80, 100, 200]*(pi/180), ... 'MH', 15e-3, ... 'MR', 110e-3, ... 'MTh', [255, 285, 15, 45, 135, 165]*(pi/180)); stewart = computeJointsPose(stewart); stewart = initializeStrutDynamics(stewart, ... 'type', 'apa300ml', ... 'k', 0.3e6, ... 'ke', 4.3e6, ... 'ka', 2.15e6, ... 'c', 18, ... 'ce', 0.7, ... 'ca', 0.35, ... 'ga', -2.7, ... 'gs', 0.53e6); stewart = initializeJointDynamics(stewart, ... 'type_M', '4dof', ... 'type_F', '2dof_axial', ... 'Kf_M', 4.83, ... 'Cf_M', 1e-2, ... 'Kt_M', 260, ... 'Ct_M', 1e-2, ... 'Ka_M', 94e6, ... 'Ca_M', 1e-2, ... 'Kf_F', 4.83, ... 'Cf_F', 1e-2, ... 'Ka_F', 94e6, ... 'Ca_F', 1e-2); stewart = initializeCylindricalPlatforms(stewart, ... 'Fpm', 5, ... 'Fph', 10e-3, ... 'Fpr', 150e-3, ... 'Mpm', 5, ... 'Mph', 10e-3, ... 'Mpr', 150e-3); stewart = initializeCylindricalStruts(stewart, ... 'Fsm', 1e-3, ... 'Fsh', 60e-3, ... 'Fsr', 5e-3, ... 'Msm', 1e-3, ... 'Msh', 60e-3, ... 'Msr', 5e-3); stewart = computeJacobian(stewart); stewart = initializeStewartPose(stewart, ... 'AP', zeros(3,1), ... 'ARB', eye(3)); nano_hexapod = stewart; if exist('./mat', 'dir') if exist('./mat/nass_model_stages.mat', 'file') save('mat/nass_model_stages.mat', 'nano_hexapod', '-append'); else save('mat/nass_model_stages.mat', 'nano_hexapod'); end elseif exist('./matlab', 'dir') if exist('./matlab/mat/nass_model_stages.mat', 'file') save('matlab/mat/nass_model_stages.mat', 'nano_hexapod', '-append'); else save('matlab/mat/nass_model_stages.mat', 'nano_hexapod'); end end end #+end_src * Footnotes [fn:detail_instrumentation_2] For variable gain amplifiers, it is usual to refer to the input noise rather than the output noise, as the input referred noise is almost independent on the chosen gain. [fn:detail_instrumentation_1] The manufacturer proposed to remove the $50\,\Omega$ output resistor to improve to small signal bandwidth above $10\,kHz$