Files

1364 lines
74 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
+++
title = "Closed-Loop Control - Hands-On"
author = ["Dehaeze Thomas"]
draft = false
venue = "MOCRAF"
year = 2026
pubtype = "lecture"
pdf = "https://gitlab.esrf.fr/dehaeze/xy-piezo-stage-hands-on/-/raw/main/closed-loop-hands-on.pdf"
code = "https://gitlab.esrf.fr/dehaeze/xy-piezo-stage-hands-on"
+++
## Introduction {#introduction}
For many Synchrotron experiments, the sample has to be positioned with nanometer accuracy over strokes of several hundred micrometers, and to be either scanned continuously or using step-be-step motion.
To address this need, a 3 axis piezoelectric stage is being developed at the ESRF.
The system used during this hands-on ([Figure 1](#figure--fig:picture-xy-stage)) is a two axis prototype, offering \\(\approx 300\\,\mu m\\) of stroke with a target accuracy of \\(10\\,nm\\).
<a id="figure--fig:picture-xy-stage"></a>
{{< figure src="figs/picture_xy_stage.jpg" caption="<span class='figure-number'>Figure 1: </span>Picture of XY piezo stage including the two amplified piezoelectric actuators" >}}
This stage is an excellent system to learn feedback and feedforward control.
A full cycle "measure, design, test" can be performed in a couple of minutes.
The tools used here (system identification, loop shaping, ..) are quite general and therefore can be reused for the control of other systems such as air bearing spindles, stepper motor stages or active vibration isolation platforms.
Tuning a controller only makes sense against a specification.
Two typical use cases are considered throughout this document:
- **Step motion**: perform a \\(1\\,\mu m\\) step and settle within \\(\pm 10\\,nm\\) as fast as possible
- **Constant velocity scan**: scan at \\(100\\,\mu m/s\\) with a tracking error below \\(10\\,nm\\)
These two cases do not call for the same controller, and this is the interesting part: the first one is a matter of speed, the second one a matter of steady state error.
Several control strategies will be applied and compared against these two numbers, and the reason why each one succeeds or fails will be analyzed.
It is suggested to fill [Table 1](#table--tab:results-summary) as the hands-on progresses, in order to compare the different controllers at the end.
<a id="table--tab:results-summary"></a>
<div class="table-caption">
<span class="table-number"><a href="#table--tab:results-summary">Table 1</a>:</span>
Results sheet to be filled during the hands-on
</div>
| Controller | \\(f\_b\\) [Hz] | \\(\Delta M\\) | Step perf [ms] | Scan perf [nm] | Comments |
|--------------|-----------------|----------------|----------------|----------------|----------|
| FF | | | | | |
| cst gain | - | - | | | |
| | | | | | |
| FF | | | | | |
| notch | - | - | | | |
| | | | | | |
| FB | | | | | |
| Int | | 0.5 | | | |
| | | | | | |
| FB | | | | | |
| Int | | 0.1 | | | |
| | | | | | |
| FB | | | | | |
| Int | | | | | |
| Notch | | | | | |
| FB | | | | | |
| 2 Int | | | | | |
| Lead + Notch | | | | | |
| | | | | | |
| FB + FF | | | | | |
| | | | | | |
The document follows the order in which a system is actually commissioned:
- Section [Hardware and Real-Time Software](#sec:hardware) describes the mechanics, the control electronics and the real-time software used to control the stage.
- Section [System Identification](#sec:system_identification) shows how the dynamics of the stage is **measured**, and not modeled.
This measured frequency response function is the only ingredient needed for the rest of the document.
- Section [Feedforward Control](#sec:feedforward_control) uses this measurement to build a feedforward controller, which improves the tracking without any risk of instability.
- Section [Feedback Control](#sec:feedback_control) closes the loop, introduces the few theoretical tools required to tune a feedback controller (loop gain, sensitivity functions, stability margins), and applies them step by step.
- Section [Conclusion](#sec:conclusion) summarizes the method and discusses its limits.
## Hardware and Real-Time Software {#sec:hardware}
Before diving into the control of this piezoelectric stage, the hardware and the real-time control software are briefly described.
The complete chain, from the control signal computed by the real-time controller to the measured position, is shown in [Figure 2](#figure--fig:hardware-signal-chain).
For the rest of this document, what has to be retained from each element is its **gain** (it will be part of the plant to be controlled) and its **limitation** (resolution, noise, range, delay).
<a id="figure--fig:hardware-signal-chain"></a>
{{< figure src="figs/hardware_signal_chain.png" caption="<span class='figure-number'>Figure 2: </span>Signal chain of one axis, from the control signal \\(u\\) to the measured position \\(y\_m\\)" >}}
### Mechanical Design {#mechanical-design}
As was shown in [Figure 1](#figure--fig:picture-xy-stage), the flexure stage is designed in such a way that the X and Y directions may be controlled independently.
Each actuator pushes along its own axis, and the flexible guiding is compliant in that direction while being stiff in the others.
Consequently, moving the X actuator produces almost no motion on the Y encoder: this will be verified experimentally in Section [System Identification](#sec:system_identification), and is what allows two independent SISO controllers to be used instead of a single MIMO one.
Each axis is composed of an amplified piezoelectric actuator (the APA300ML from Cedrat Technologies[^fn:1]) and an optical encoder (Renishaw Vionic), both shown in [Figure 3](#figure--fig:apa300ml):
- The APA300ML has an axial stiffness of \\(1.8\\,N/\mu m\\) and a stroke of \\(\approx 300\\,\mu m\\) for a voltage between \\(-20\\) and \\(150\\,V\\).
- The Vionic encoder has a resolution of \\(2.5\\,nm\\), and outputs the motion with a quadrature signal at maximum \\(10\\,MHz\\).
Note that the actuator does not push against free space: it is loaded by the stiffness of the flexible guiding (\\(0.6\\,N/\mu m\\)) and by the mass to be moved (\\(\approx 0.7\\,kg\\)).
This is what determines both the stroke actually available and the resonance frequency of the stage, as will be estimated in Section [System Identification](#sec:system_identification).
<div class="subfigures">
<a id="figure--fig:apa300ml"></a>
{{< figure src="figs/APA300ML.jpg" caption="<span class='figure-number'>Figure 3: </span>Amplified Piezoelectric Actuator - APA300ML" >}}
<a id="figure--fig:renishaw-vionic"></a>
{{< figure src="figs/renishaw_vionic.jpg" caption="<span class='figure-number'>Figure 4: </span>Renishaw Vionic optical encoder" >}}
</div>
<div class="subfigures_caption">
The actuator and the sensor used in the XY piezoelectric stage
</div>
### Control Electronics {#control-electronics}
The control electronics is composed of two parts:
- The "Control Box" (see [Figure 5](#figure--fig:speedgoat-unit)) with:
- A "Unit Real-Time Target Machine"[^fn:2], which is programmed from Simulink
- EtherCAT modules (EL2124 and EL1124) for digital inputs/output.
These are used for triggering purposes and interfacing with detectors and other electronics
- The "Driver box" (see [Figure 7](#figure--fig:pdm200)) with:
- 3x DAC, +/-10V, 18 bits: IOLITE-1XAO from Dewesoft
- 3x voltage amplifiers: PDm200[^fn:3]: gain: 20V/V, max output: -30 to 200V
- 3x EtherCAT modules for quadrature inputs[^fn:4]
Everything (DAC, encoder counters, digital I/O) is connected to the real-time machine through EtherCAT, which guarantees a deterministic exchange of all the signals once per cycle.
With the amplifier gain of \\(20\\,V/V\\), the \\(\pm 10\\,V\\) of the DAC more than cover the \\(-20\\) to \\(150\\,V\\) range of the APA300ML, the amplifier itself being limited to \\(-30\\) to \\(200\\,V\\).
This is why, when the stage is turned on, a DAC offset of \\(3\\,V\\) is applied: the actuator is then biased at \\(60\\,V\\), roughly in the middle of its range, so that it can move in both directions.
<div class="note">
Two numbers are worth keeping in mind for the \\(\pm 10\\,nm\\) specification:
- the encoder resolution is \\(2.5\\,nm\\);
- the DAC resolution is \\(20\\,V/2^{18} \approx 76\\,\mu V\\), that is \\(1.5\\,mV\\) after the amplifier, which represents less than \\(3\\,nm\\) of stroke.
The \\(18\\) bits of the DAC are therefore not a luxury: a \\(16\\) bit DAC would give steps of \\(\approx 10\\,nm\\), i.e. the specification itself.
The hardware sets a floor to what any controller can achieve, and this floor should always be estimated **before** designing the control.
</div>
<div class="subfigures">
<a id="figure--fig:speedgoat-unit"></a>
{{< figure src="figs/speedgoat_unit.jpg" caption="<span class='figure-number'>Figure 5: </span>Speedgoat Unit" >}}
<a id="figure--fig:beckhoff-ethercat"></a>
{{< figure src="figs/beckhoff_ethercat.png" caption="<span class='figure-number'>Figure 6: </span>EtherCAT modules from Beckhoff" >}}
</div>
<div class="subfigures_caption">
The components inside the "Control box"
</div>
<div class="subfigures">
<a id="figure--fig:pdm200"></a>
{{< figure src="figs/pdm200.jpg" caption="<span class='figure-number'>Figure 7: </span>PDm200 voltage amplifier" >}}
<a id="figure--fig:iolite1xao"></a>
{{< figure src="figs/iolite1xao.png" caption="<span class='figure-number'>Figure 8: </span>IOLite 1xAO DAC" >}}
</div>
<div class="subfigures_caption">
The components inside the "Driver box"
</div>
### Real-Time Software {#real-time-software}
All the control is implemented in the Speedgoat real-time "target", which is programmed from Simulink and runs at a fixed rate of \\(5\\,kHz\\) (\\(T\_s = 200\\,\mu s\\), which is also the EtherCAT cycle time).
The Simulink model[^fn:5] is shown in [Figure 9](#figure--fig:xy-piezo-stage).
The different blocs are:
- Red block: hardware interfaces.
The two inputs are the voltages to be generated by the DAC, in \\(V\\), and the two outputs are the encoder positions, in \\(\mu m\\).
- Yellow blocks: generation of setpoints, 2nd order profiles (constant acceleration, constant velocity, deceleration)
- Blue blocks: feedback and feedforward controllers, made of simple biquad and/or PID filters
- Green blocks: "safety" blocks to make sure signals are within defined boundaries.
They are also used to activate and deactivate the feedback and feedforward controllers
- Purple blocks: signal generators used for system identification
<div class="note">
Everything is **discrete**: the controllers designed in this document are defined as continuous filters, and are discretized by the software before being written to the target.
Sampling is not free: the zero-order hold, the computation time and the EtherCAT transfers add up to a delay of one to two sampling periods.
This delay is a pure phase lag, growing linearly with frequency, and it is one of the reasons why the achievable bandwidth stays far below \\(F\_s/2\\).
It will be directly visible on the measured FRF in Section [System Identification](#sec:system_identification).
</div>
A specific Simulink library[^fn:6] has been developed for the control of instruments at the ESRF.
It implements the fundamental functions needed by most instruments: filters, trajectory generation, safety blocks, signal generators for system identification, lookup tables, ...
<a id="figure--fig:xy-piezo-stage"></a>
{{< figure src="figs/xy_piezo_stage.png" caption="<span class='figure-number'>Figure 9: </span>Overview of the Simulink model" >}}
The feedback controller is composed by a series of standard "biquad" filters and a standard PID controller (see [Figure 10](#figure--fig:xy-piezo-stage-Kfb-x)).
The feedforward controller only contains one "biquad" filter.
<a id="figure--fig:xy-piezo-stage-Kfb-x"></a>
{{< figure src="figs/xy_piezo_stage_Kfb_x.png" caption="<span class='figure-number'>Figure 10: </span>Simulink implementation of the Feedback controller" >}}
"Regulation" blocks are also added in the model to deal with the "safety".
These blocks continuously check that certain signals stay within defined boundaries, and deactivate the feedback or feedforward controller when they do not.
This way, the risk of damaging the system by generating too large control signals is mitigated.
This is also the mechanism used throughout this hands-on to switch the controllers on and off: enabling the `feedback` or `feedforward` regulator closes the corresponding path in the model.
### Graphical User Interface {#graphical-user-interface}
A graphical user interface[^fn:7] has been developed to ease the use of the Speedgoat machine, and it is the only tool that will be used during this hands-on.
It connects to the real-time target and gives access to its counters, generators, filters, motors and parameters, without having to open Simulink.
The pages used in this document are summarized in [Table 2](#table--tab:gui-pages).
<a id="table--tab:gui-pages"></a>
<div class="table-caption">
<span class="table-number"><a href="#table--tab:gui-pages">Table 2</a>:</span>
The pages of the graphical user interface used during this hands-on
</div>
| Page | Use |
|------------------------------|----------------------------------------------------------------------------------|
| `Custom` | Dashboard: turn the stage on, reset the encoders, enable the regulators |
| `Motors` | Configure and perform motions (velocity, acceleration time, setpoint), save them |
| `Generators` | Configure and visualize the excitation signals |
| `Signal Analysis` | Acquire time traces and compute their spectral content (PSD/ASD) |
| `Dynamical Identification` | Excite the plant and compute the FRF, the coherence and the RGA-number |
| `Transfer Function Analysis` | Overlay measured FRFs with the response of the filters running on the target |
| `Feedback Control Design` | Loop shaping: \\(L\\), Nyquist plot, \\(S\\) and \\(T\\), stability margins |
All the data saved from these pages (time traces, spectra, FRFs) are written as `.mat` files in the program data directory, which is where the figures of this document are generated from.
## System Identification {#sec:system_identification}
Before controlling the stage, we need to know how it behaves: how much does it move per volt applied?
At which frequencies does it resonate?
Instead of deriving a physical model (mass-spring-damper model for instance), we **measure** the dynamics directly.
This is done by "exciting" the stage with a known signal \\(u(t)\\) and recording its motion \\(y(t)\\).
The ratio between the two in the frequency domain gives the **frequency response function** (FRF) \\(G(j\omega)\\).
<div class="note">
An FRF is a complex function of frequency.
At each frequency \\(\omega\\), the magnitude \\(|G(j\omega)|\\) tells the amplification (here in \\(\mu m/V\\)), and the phase \\(\angle G(j\omega)\\) the delay/lag between voltage and motion.
</div>
All the controllers of this hands-on (feedforward and feedback) will be designed from this measured FRF only — no physical model is required.
<div class="note">
**Turning on the Piezo stage**:
1. Go the `Custom` page, and click on `Turn On` button.
This has the effect of outputting a DAC offset of 3V
2. Click on the `Fire` button to reset the encoder position to zero volt
3. Turn On the `safety` regulator, which allows some voltage variation on the DAC
</div>
### Step 1 - What do we expect? {#step-1-what-do-we-expect}
<div class="exercice">
Before measuring anything, sketch by hand (in [Figure 11](#figure--fig:empty-bode-plot-expected-plant)) the Bode plot you expect for the transfer function from the DAC voltage to the encoder position:
- Low frequency: what is the static gain?
- High frequency: how should the magnitude roll off?
- Where do you expect a resonance?
- What about the phase at low and high frequency?
<summary>Hint</summary>
The stroke of the APA300ML is \\(\approx 300\\,\mu m\\) for a \\(-20\text{--}150\\,V\\) range, and the PDm200 amplifier has a gain of \\(20\\,V/V\\).
The stiffness of the APA is \\(1.8\\,N/\mu m\\), and the stiffness of the flexible guiding is \\(0.6\\,N/\mu m\\).
The mobile mass is \\(\approx 0.7\\,\text{kg}\\).
</div>
<a id="figure--fig:empty-bode-plot-expected-plant"></a>
{{< figure src="figs/empty_bode_plot_expected_plant.png" caption="<span class='figure-number'>Figure 11: </span>Expected bode plot of the transfer function from DAC voltage to Encoder displacement" >}}
### The FRF estimation in a nutshell {#the-frf-estimation-in-a-nutshell}
Consider a system with dynamics represented by a transfer function \\(G\\) with inputs \\(u\\) and output \\(y\_m\\) which is affected by noise and disturbances, represented by \\(d\\) (see [Figure 12](#figure--fig:siso-identification-schematic-simplier)).
The goal is to estimate the frequency response function (FRF) of \\(G\\) by exciting the system by a wisely chosen \\(u\\) and by measuring \\(y\_m\\) at the same time.
<a id="figure--fig:siso-identification-schematic-simplier"></a>
{{< figure src="figs/siso_identification_schematic_simplier.png" caption="<span class='figure-number'>Figure 12: </span>Block diagram of the system identification scheme" >}}
The FRF of \\(G\\) can be estimated from the recorded signals using \eqref{eq:frf\_est} where \\(S\_{yu}\\) is the cross-spectrum between output and input, and \\(S\_{uu}\\) the auto-spectrum of the input.
\begin{equation}\label{eq:frf\_est}
\hat{G}(f) = \frac{S\_{yu}(f)}{S\_{uu}(f)}
\end{equation}
The quality of the estimate is quantified by the **coherence** \eqref{eq:coherence} :
- \\(\gamma^2 \approx 1\\): the output is fully explained by the excitation: good data.
- \\(\gamma^2 \ll 1\\): noise, nonlinearities or unmeasured disturbances dominate at that frequency: the FRF should not be trusted at that frequency
\begin{equation}\label{eq:coherence}
\gamma^2(f) = \frac{|S\_{yu}(f)|^2}{S\_{uu}(f)\\, S\_{yy}(f)}, \quad 0 \le \gamma^2 \le 1
\end{equation}
Since the stage has two axes excited one at a time, a \\(2\times2\\) FRF matrix is obtained.
Concretely, the spectra of \eqref{eq:frf\_est} are estimated by splitting the recording into \\(N\\) segments of duration \\(T\_\text{win}\\), and by averaging the cross- and auto-spectra computed on each segment \eqref{eq:frf\_spectra} where \\(U\_k(f)\\) and \\(Y\_k(f)\\) are the DFTs of the \\(k^\text{th}\\) segment of \\(u(t)\\) and \\(y\_m(t)\\).
\begin{equation}\label{eq:frf\_spectra}
\hat{S}\_{yu}(f) = \frac{1}{N T\_\text{win}} \sum\_{k=1}^{N} Y\_k(f)\\, U\_k^\*(f), \qquad
\hat{S}\_{uu}(f) = \frac{1}{N T\_\text{win}} \sum\_{k=1}^{N} U\_k(f)\\, U\_k^\*(f)
\end{equation}
Averaging over segments reduces the random error of the estimate (by a factor \\(\sqrt{N}\\)), while the segment length \\(T\_\text{win}\\) sets the frequency resolution.
The coupling between the two axes is quantified using the **Relative Gain Array** \eqref{eq:rga} where \\(\circ\\) denotes the element-wise product.
\begin{equation}\label{eq:rga}
\Lambda(j\omega) = G(j\omega) \circ \left( G(j\omega)^{-1} \right)^T
\end{equation}
The RGA is summarized into a single positive number as a function of frequency, the **RGA-number** \eqref{eq:rga\_number}.
In the frequency band of interest (say up to the wanted closed-loop bandwidth):
- RGA-number \\(\ll 0\\): the axes are decoupled, and two independent SISO loops can be implemented
- RGA-number \\(\gg 1\\): strong coupling; should decouple the system or implement MIMO control
\begin{equation}\label{eq:rga\_number}
\text{RGA-number}(\omega) = \left\\| \Lambda(j\omega) - I \right\\|\_{\text{sum}}
\end{equation}
### Step 2 - Choosing the excitation signal {#sec:excitation_signal}
The art of system identification is to choose the correct excitation signal.
The effect of the excitation on the plant output must be well above the measured displacement in the absence of excitation, for all the frequency band of interest.
We want the measured output to be dominated by our excitation, not by disturbances or encoder noise.
But we don't want to excitation signal to be too high to have some non-linear behavior or to damage the system.
There are many options for the choice of the excitation signal, such as an impulse, a step, a sweep sine...
Here two options are proposed:
- A white noise signal, which is very simple to generate and to tune (only one parameter)
- A multi-sine, which gives more control on its frequency content.
It is also periodic, which will allow window-free spectral estimation later.
<div class="exercice">
**Goal:** Estimate an adequate excitation signal for the XY piezoelectric stage.
First, we want to estimate the required frequency content of the excitation signal to have a good identification:
1. In the `Signal Analysis` page, record the `enc_x` and `enc_y` counters for 10 seconds, and display their ASD.
2. Based on the recorded ASD, estimate the required excitation voltage ASD so that the generated motion is 100 times higher than the previously measured sensor output at "rest".
<summary>Hint</summary>
The ASD of the measured encoder at "rest" should be around \\(10^{-4}\\,\mu m/\sqrt{Hz}\\).
The plant DC gain being estimated to be \\(\approx 25\\,\mu m/V\\), the excitation voltage spectrum must therefore be well above \\(\frac{10^{-4}}{25} \approx 4\\,\mu V/\sqrt{Hz}\\).
Therefore, we can aim at a excitation voltage ASD of \\(\approx 400\\, \mu V/\sqrt{Hz}\\).
<summary>Choice of the excitation signal</summary>
Here, we choose to use a normal white noise for the excitation signal as it has a flat frequency spectrum and it is easy to tune:
1. Go to the `Generators` page
2. Select the `id_plant` generator
3. Set the generator's type to `Normal White Noise`
4. Choose a `pattern duration` of \\(1\\,s\\), and adjust the amplitude to have the wanted voltage ASD
<summary>Hint</summary>
A normal white noise with RMS value \\(A\\) has its ASD flat from \\(0\\) to \\(F\_s/2\\) with \\(\text{ASD} = \sqrt{\frac{2}{F\_s}} \cdot \text{RMS}\\).
Therefore, the RMS value of the normal white noise should be \\(\approx 0.02\\,V\\).
</div>
### Step 3 - Choosing the frequency analysis parameters {#step-3-choosing-the-frequency-analysis-parameters}
The FRF is estimated using \eqref{eq:frf\_est}, with the spectra of \eqref{eq:frf\_spectra} computed on each segment.
Two phenomena have to be dealt with by a proper choice of the analysis parameters:
**Leakage.**
The DFT implicitly assumes each segment repeats periodically.
A segment whose beginning and end do not match creates a discontinuity that spreads energy over all frequencies ("leakage"), which biases the estimated FRF.
To mitigate this, a "taper" (`Hanning` window for instance) is usually applied on each segment before the estimator is computed, which forces the segment to start and end at zero.
If the excitation is periodic and whole periods are analyzed, the signals match naturally and no window is needed (that is the case with the multi-sine).
**Noise.**
FRF estimate are usually impact by noise present in the system.
To reduce this random error, the recorded signals are split into many segments whose estimates are averaged (\eqref{eq:frf\_spectra}); with \\(N\\) segments, the random error decreases as \\(1/\sqrt{N}\\).
For a given recording duration, more segments means shorter ones, i.e. a coarser frequency resolution: this is a trade-off between averaging and frequency resolution.
The main knobs and their effects are summarized in [Table 3](#table--tab:freq-analysis-parameters).
Typical values used in this hands-on are:
- Sampling frequency: \\(5\\,kHz\\), meaning a maximum measured frequency of \\(2.5\\,kHz\\) (above the \\(\sim 1\\,kHz\\) band of interest)
- Window size: \\(1\\,s\\) meaning \\(1\\,\text{Hz}\\) of resolution frequency
- for the White noise excitation signal: Hanning window, detrend `mean`, overlap \\(0.5\\), total duration \\(10\\,s\\) for some averaging
- Multi-sine: window size should match one period, no window, no overlap, skip the first period (transient), total duration \\(10\\,s\\)
<a id="table--tab:freq-analysis-parameters"></a>
<div class="table-caption">
<span class="table-number"><a href="#table--tab:freq-analysis-parameters">Table 3</a>:</span>
Main parameters for a frequency analysis
</div>
| Parameter | Role | Comment |
|----------------|----------------------------------------------------|-----------------------------------------------------------|
| Window size | Frequency resolution \\(\approx 1/T\_\text{win}\\) | Small: poor low-frequency resolution / Large: noisier FRF |
| Window (taper) | Reduces leakage for **non-periodic** signals | Can affect the estimated FRF |
| Sampling rate | Sets the maximum frequency (\\(F\_s/2\\), Nyquist) | Must be at least twice the highest frequency of interest |
### Step 4 - Identification with white noise {#step-4-identification-with-white-noise}
**Goal:** obtain the FRF of the plant from voltage to encoder position, for both axes.
<div class="exercice">
To identify the plant dynamics:
1. Open the `Dynamical Identification` page of the GUI.
2. Set **Identification Type:** `Classical identification`.
3. Select **Generator:** `id_plant` and set **Input counter:** `id_plant`.
4. Select the **Output index**: `MIMO` (both piezo will be excited, one at a time)
5. Configure the generator waveform:
- **Type:** `Normal white noise`
- **Amplitude:** `0.02` V
- **Pattern duration:** `1` s, **Repeat:** yes
- **Duration:** `10` s
6. Click `Load`.
7. In **Signals**, tick the output counters `enc_x` and `enc_y`.
8. Set the **Frequency Analysis Parameters**:
- **Window size:** `1` s, **Window:** `hanning`, **Detrend:** `mean`, **Overlap:** `0.5`
9. Click `Acquire`.
The Bode plot, coherence and RGA-number are displayed automatically.
<summary>Checkpoints</summary>
- Is the measured FRF close to your sketch from Step 1?
- Read the low-frequency (DC) gain: \\(\mu\text{m/V}\\)
- Read the first resonance frequency: Hz
- Is the coherence \\(> 0.9\\) up to 1 kHz?
- What about the phase ? Any time-delay ?
- Look at the off-diagonal FRF terms: how much coupling between \\(x\\) and \\(y\\)?
Is it confirmed by the RGA-number?
</div>
The Bode plot should look like the one in [Figure 13](#figure--fig:id-white-noise).
Save the result with `Save TF...` as `G_id_plant_white_noise.mat` for later use.
<a id="figure--fig:id-white-noise"></a>
{{< figure src="figs/id_white_noise.png" caption="<span class='figure-number'>Figure 13: </span>Plant FRF identified with white noise excitation" >}}
### Step 5 - Identification with a multi-sine (bonus) {#step-5-identification-with-a-multi-sine--bonus}
**Goal:** improve the FRF quality using a multi-sine excitation.
<div class="exercice">
**Procedure**:
1. Similar to what was done in Section [Step 2 - Choosing the excitation signal](#sec:excitation_signal), generate a multi-sine signal with the wanted ASD amplitude.
Limit the excited frequency from 1Hz to 1kHz.
2. In the `Dynamical Identification` page, set the **Frequency Analysis Parameters**:
- **Window size:** `1` s (one period of the excitation)
- **Window:** `none` (periodic signal means no leakage)
- **Overlap:** `0`
- **Skip transient:** `1` s (discard the first period: the plant has not reached its periodic steady state yet)
3. Click `Acquire`.
It the result seems good, save the result with `Save TF...` as `G_id_plant_multisine.mat` for later use.
<summary>Hint</summary>
For the multi-sine excitation, you should get something like:
- **Type:** `multisine`
- **Amplitude:** `0.02` V
- **Pattern duration:** `1` s
- **Lower frequency:** `1` Hz, **Upper frequency:** `1000` Hz
- **Repeat:** yes, **Duration:** `10` s
- **Phase type:** `schroeder`
</div>
**Expected result:** compared to the white noise identification, the FRF should be smoother in the 1-1000 Hz band, and the coherence close to 1 over the whole multi-sine band.
### Summary {#summary}
<div class="sum">
The main difficulty of system identification is the choice of the **excitation signal**: it must dominate noise and disturbances over the whole frequency band of interest (coherence \\(\gamma^2 \approx 1\\)), while staying small enough to avoid non-linearities and to respect the hardware limits.
- A **white noise** signal is flat in frequency and easy to tune (one parameter): usually a good first choice.
- A **multi-sine** gives full control of the excited band, is periodic (no leakage, no window), and typically yields a smoother FRF for the same excitation amplitude.
The quality of the identification is judged from the **coherence** (is the output explained by the excitation?).
The measured FRF is all we need: the feedforward (Section [Feedforward Control](#sec:feedforward_control)) and feedback (Section [Feedback Control](#sec:feedback_control)) controllers will be designed directly from it.
</div>
## Feedforward Control {#sec:feedforward_control}
The goal of the feedforward controller (schematically shown in [Figure 14](#figure--fig:feedforward-schematic-simplier)) is to generate a control signal \\(u\_{ff}\\) from the reference \\(r\\) such that the plant output \\(y\\) follows \\(r\\) as precisely as possible.
Basically, we want the transfer function from \\(r\\) to \\(y\\) to be close to unity, or more realistically to a low pass filter with a high enough cut-off frequency:
\begin{equation}\label{eq:feedforward\_tf}
G(j\omega) \cdot K\_{ff}(j\omega) \approx 1
\end{equation}
<a id="figure--fig:feedforward-schematic-simplier"></a>
{{< figure src="figs/feedforward_schematic_simplier.png" caption="<span class='figure-number'>Figure 14: </span>Schematic of a basic feedforward controller" >}}
<div class="exercice">
Based on the goal expressd by \eqref{eq:feedforward\_tf}, what should be the shape of the ideal feedforward controller \\(K\_{ff}\\)?
<details><summary>Answer</summary>
Ideally, the feedforward controller is the inverse of the plant: \\(K\_{ff} = 1/G\\) such that \eqref{eq:feedforward\_tf} is verified.
Such an inverse is however not realizable in practice as its gain would grow indefinitely at high frequency.
So we will approximate it with simple filters: first a constant gain, then a notch filter.
</div>
The feedforward controller should typically be tuned as **before** implementing the feedback controller (unless the system is open-loop unstable).
A well-tuned feedforward means the feedback loop only has to deal with residual errors: disturbances, drift and model uncertainty.
### Step 1 - Static Feedforward {#step-1-static-feedforward}
We start with the simplest feedforward controller: a constant gain \\(K\_{ff}(s) = K\_\text{ff}\\).
As the setpoint is in \\(\mu m\\) and the DAC input in \\(V\\), the feedforward gain \\(K\_{ff}\\) is expressed in \\(V/\mu m\\).
<div class="exercice">
Based on the system identification done in Section [System Identification](#sec:system_identification), what should be the \\(K\_{ff}\\) value?
<summary>Hint</summary>
It should be the inverse of the plant DC gain identified.
<summary>Visual Tuning of the Feedforward Controller</summary>
To visualize the FRF of \\(G(j\omega) \cdot K\_{ff}(j\omega)\\) and tune the feedforward controller:
1. Go to the `Transfer Function Analysis` page and load the previously identified plant (e.g. `G_id_plant_white_noise.mat`)
2. Start with the transfer function from `u_x` to `enc_x`
3. Activate (tick) the `Kff_x` feedforward controller (a Biquad filter) in the list of filters.
4. Set the `Kff_x` filter to be a `pass-through` filter (i.e. a constant gain).
5. In the `Display` section, tick `Loaded FRF x Individual Filters`.
6. Click on `Visual Tuning` for the `Kff_x` filter and adjust the filter's gain to have the DC gain of the feedforward path equal to one.
Alternatively, directly set the numerical value of the gain that you computed previously.
<summary>Checkpoints</summary>
- What is the shape of the FRF of \\(G \cdot K\_{ff}\\) (the product of the plant and the feedforward controller)?
- Is the low frequency gain now close to 1?
- What kind of time response do we expect from such a 2nd order resonant low pass filter with a DC gain of 1?
</div>
Once the feedforward controller is tuned, let us test its performance with an open-loop motion.
<div class="exercice">
To perform some motion and test the performance of the feedforward controller:
1. In the `Custom` page:
- Make sure the `pz_x` motor's position is `0`
- Activate the `feedforward` regulation
- Reset the encoder using the `Fire` button
2. Go to the `Motors` page:
- Configure the motor's parameters:
- `Velocity`: \\(100\\,\mu m/s\\)
- `Acceleration time`: \\(0.001\\,s\\)
- Make a \\(5\\,\mu m\\) motion by configuring the `Setpoint` and clicking on `Start`
<summary>Checkpoints</summary>
The typical result is shown in [Figure 15](#figure--fig:constant-gain-feedforward-motion).
Which of the following imperfections do you observe?
- Drifts due to piezoelectric creep
- Static error due to hysteresis (i.e. non-linearity of the piezoelectric stack)
- Vibrations due to the resonance of the system: does the frequency match the identified resonance?
- Time delay: does it match the delay observed in the FRF?
</div>
<a id="figure--fig:constant-gain-feedforward-motion"></a>
{{< figure src="figs/constant_gain_feedforward_motion.png" caption="<span class='figure-number'>Figure 15: </span>Obtained motion result with the constant gain feedforward" >}}
### Step 2 - Dynamic Feedforward {#step-2-dynamic-feedforward}
The constant gain feedforward makes the low frequency gain of \\(G \cdot K\_{ff}\\) equal to one, but the resonance of the plant is still present.
During fast motions, the setpoint excites the resonance which results in oscillations at the stage resonance frequency.
To tackle this issue, we can add a **notch filter** in the feedforward path to "virtually damp" the system resonance.
A notch filter suppresses a narrow frequency band around \\(\omega\_n\\):
\begin{equation}
N(s) = \frac{s^2 + 2\zeta\_1\omega\_n s + \omega\_n^2}{s^2 + 2\zeta\_2\omega\_n s + \omega\_n^2}
\end{equation}
<div class="note">
Here \\(\omega\_n\\) is tuned to the plant resonance frequency, \\(\zeta\_1\\) sets the notch depth, and \\(\zeta\_2\\) the notch width.
After tuning, the product \\(G \cdot K\_{ff}\\) should look like a well damped low pass filter with a DC gain of 1.
</div>
<div class="exercice">
Let's first tune the feedforward filter:
1. Move the `pz_x` motor's to `0`
2. Deactivate the `feedforward` regulation in the `Custom` page
3. Go to the `Transfer Function Analysis` page
4. In the `Display` section, tick `Loaded FRF x Individual Filters`
5. Set the `Kff_x` filter to be a `notch` filter, and click on `Apply`
6. Click on `Visual Tuning` for the `Kff_x` filter, and adjust the filter's parameters (\\(\omega\_n\\), notch depth and width) so that the overall "feedforward FRF" looks like a well damped low pass filter with a DC gain of 1
<summary>Performance estimation</summary>
1. Activate the `feedforward` regulation in the `Custom` page
2. Reset the encoder using the `Fire` button
3. Go to the `Motors` page
4. Configure the motor's parameters:
- `Velocity`: \\(100\\,\mu m/s\\)
- `Acceleration time`: \\(0.001\\,s\\)
5. Make a \\(5\\,\mu m\\) motion by configuring the `Setpoint` and clicking on `Start`
<summary>Checkpoints</summary>
The typical result is shown in [Figure 16](#figure--fig:notch-feedforward-motion).
- Is the resonance issue solved?
- What about the robustness to a change of payload?
</div>
<a id="figure--fig:notch-feedforward-motion"></a>
{{< figure src="figs/notch_feedforward_motion.png" caption="<span class='figure-number'>Figure 16: </span>Obtained motion result with the notch feedforward" >}}
### Summary {#summary}
<div class="note">
Feedforward control is very effective to follow trajectories, but it is **open-loop**: it cannot handle model uncertainty, external disturbances, creep and hysteresis.
This is why feedback control is still required, and is the subject of Section [Feedback Control](#sec:feedback_control).
Closely related to feedforward is the shaping of the trajectory itself.
Here the setpoint is generated as a 2nd order profile (constant acceleration, constant velocity, deceleration), and its acceleration time directly sets the frequency content that excites the plant.
**Input shaping** techniques go one step further and adapts the trajectory so that it contains little energy at the plant resonance .
Fast motion with low vibration is thus a joint design of the **trajectory profile** and of the **feedforward controller**.
</div>
## Feedback Control {#sec:feedback_control}
Feedforward control can only be as good as our knowledge of the plant: creep, hysteresis, external disturbances and payload changes are invisible to it.
Feedback uses the **measured** position to correct whatever the feedforward did not anticipate.
The price to pay is a risk of instability, the injection of sensor noise into the system, and a tuning effort that requires to understand few points that will be described in this section.
A schematic of a classical feedback/feedforward controller is shown in [Figure 17](#figure--fig:feedback-schematic-simplier), with the following signals:
- \\(r\\) is the trajectory to be followed
- \\(\epsilon\\) the tracking error
- \\(u\\) the controller output
- \\(d\\) some disturbances affecting the plant output
- \\(y\\) the displacement to be controlled
- \\(n\\) the measurement noise
- \\(y\_m\\) the measured displacement
<a id="figure--fig:feedback-schematic-simplier"></a>
{{< figure src="figs/feedback_schematic_simplier.png" caption="<span class='figure-number'>Figure 17: </span>Block diagram of a classical feedback controller" >}}
For this system, the feedback controller \\(K\_{fb}\\) is a combination of second order filters ("biquads") followed by a PID filter ([Figure 10](#figure--fig:xy-piezo-stage-Kfb-x)).
As for the feedforward controller, it will be tuned directly from the FRF measured in Section [System Identification](#sec:system_identification): no model of the stage is required.
<div class="note">
Many other control strategies exist: state space control, model predictive control, non-linear control...
For nano-positioning systems, which are usually quite linear and well described by their measured FRF, a series of second order LTI filters tuned by loop shaping gives excellent results for a fraction of the effort of the other more advanced methods.
</div>
### Building blocks of the controller {#building-blocks-of-the-controller}
Any LTI transfer function can be factored into a series of first and second order terms.
This is what makes the **biquad** structure universal: a transfer function with numerator/denominator polynomials in \\(z^{-1}\\) can be decomposed into second order sections, each implemented numerically with only 5 coefficients \eqref{eq:biquad\_formula}.
Cascading such sections is both numerically robust (poorly conditioned high-order polynomials are avoided) and flexible (a controller is simply a list of biquads in series).
\begin{equation}\label{eq:biquad\_formula}
H(z) = \frac{b\_0 + b\_1 z^{-1} + b\_2 z^{-2}}{1 + a\_1 z^{-1} + a\_2 z^{-2}}
\end{equation}
These coefficients are never entered by hand but automatically computed for a number of "standard" filters, such as:
- **Pass-through** (`gain`): \\(H(s) = g\\).
A pure gain, used to scale the loop.
- **Integrator** (`gain`): \\(H(s) = g/s\\).
Infinite gain at DC, hence no steady state error, at the cost of a constant \\(-90^\circ\\) phase lag.
- **Lead** (`wn`, `a`): \\(H(s) = \sqrt{a}\\,\frac{s + \omega\_n/\sqrt{a}}{s + \omega\_n\sqrt{a}}\\).
Adds phase around \\(\omega\_n\\), up to \\(\arcsin\frac{a-1}{a+1}\\) exactly at \\(\omega\_n\\) where its gain is unity.
This is what makes a loop stable again when integrators have eaten all the available phase.
- **Lag** (`wn`, `a`): \\(H(s) = \frac{s + \sqrt{a}\\,\omega\_n}{s + \omega\_n/\sqrt{a}}\\).
Multiplies the low frequency gain by \\(a\\) while leaving the gain unchanged above \\(\omega\_n\\): a "soft" integrator that costs less phase.
- **Notch** (`wn`, `xi`, `gc`): \\(H(s) = \frac{s^2 + 2 \xi g\_c \omega\_n s + \omega\_n^2}{s^2 + 2 \xi \omega\_n s + \omega\_n^2}\\).
\\(g\_c\\) is the gain at \\(\omega\_n\\) (the depth of the notch) and \\(\xi\\) sets its width.
Used to take a resonance out of the loop gain.
- **Low pass** (`wn`, `xi`): \\(H(s) = \frac{\omega\_n^2}{s^2 + 2\xi\omega\_n s + \omega\_n^2}\\).
Rolls off the loop gain at high frequency: less measurement noise is injected, and the loop is made more robust to poorly known high frequency modes.
The Bode plots of these filters are compared in [Figure 18](#figure--fig:basic-filters-bode).
<a id="figure--fig:basic-filters-bode"></a>
{{< figure src="figs/basic_filters_bode.png" caption="<span class='figure-number'>Figure 18: </span>Bode plots of the standard second order filters, all centered on \\(100\\,Hz\\)" >}}
Even though these Biquad filters are sufficient to represent any linear controller, a specific controller usually has its separate implementation: the PID filter.
The PID filter can be written in different ways, here we choose the formula of equation \eqref{eq:pid\_formula}, with the following parameters:
- \\(k\\): overall gain, which mostly sets the cross-over frequency
- \\(f\_i\\): integral corner frequency, below which the gain rises with a \\(-20\\,dB/dec\\) slope
- \\(f\_d\\): derivative corner frequency, above which phase lead is generated
- \\(f\_t\\): roll-off of the derivative action, mandatory to keep the high frequency gain bounded
\begin{equation}\label{eq:pid\_formula}
K\_{pid}(s) = k \underbrace{\left(1 + \frac{2\pi f\_i}{s}\right)}\_{\text{lag}} \underbrace{\left(1 + \frac{s}{2\pi f\_d}\right) \frac{1}{1 + \frac{s}{2\pi f\_t}}}\_{\text{lead}}
\end{equation}
Its Bode plot is shown in [Figure 19](#figure--fig:pid-bode) where we can see that a PID controller is nothing more than a **lag** filter (integral action) in series with a **lead** filter (derivative action and its roll-off).
This is why it is also called a lead-lag controller, and why the biquad filters alone would be enough to build the same controller.
PID controllers remain popular for historical reasons: meaningful parameters, many tuning recipes (Ziegler-Nichols, ...), and an explicit integrator block that makes anti-windup easy to implement.
<a id="figure--fig:pid-bode"></a>
{{< figure src="figs/pid_bode.png" caption="<span class='figure-number'>Figure 19: </span>Bode plot of the PID controller (\\(k = 1\\), \\(f\_i = 10\\,Hz\\), \\(f\_d = 100\\,Hz\\), \\(f\_t = 500\\,Hz\\))" >}}
### Loop shaping in a nutshell {#loop-shaping-in-a-nutshell}
The **loop gain** is the product of every transfer function a signal encounters when travelling once around the loop:
\begin{equation}
L(s) = G(s) K\_{fb}(s)
\end{equation}
This single quantity is at the heart of feedback design because four properties make it a nice quantity to work with:
- it is tightly linked to the closed-loop transfer functions, which describe the actual behavior of the system
- it depends **linearly** on the controller, so it is easy to shape \\(L\\) by tuning the controller \\(K\_{fb}\\)
- performance and robustness can both be estimated at a glance from the shape of \\(|L(j\omega)|\\)
- the loop gain \\(|L(j\omega)|\\) can be computed from the **measured** plant FRF, so no model of the plant is required
##### Loop gain and closed-loop transfer functions {#loop-gain-and-closed-loop-transfer-functions}
What we ultimately care about is the closed-loop behavior of the system.
From the block diagram of [Figure 17](#figure--fig:feedback-schematic-simplier) (feedforward off), the output and the tracking error are:
\begin{equation}
y = T\\, r - T\\, n + S\\, d, \quad \epsilon = S\\, (r - d - n)
\end{equation}
with the **sensitivity** \\(S\\) and **complementary sensitivity** \\(T\\) functions:
\begin{equation}
S = \frac{1}{1 + L}, \quad T = \frac{L}{1 + L}, \quad S + T = 1
\end{equation}
Almost everything is contained in these two functions:
- \\(S\\) governs the tracking error and the rejection of disturbances
- \\(T\\) governs the transmission of the reference and of the sensor noise
We would like to shape \\(S\\) and \\(T\\) directly, but they depend non-linearly on \\(K\_{fb}\\), which makes the problem difficult.
Fortunately, where the loop gain is large or small, they simplify and depend **linearly** on \\(L\\):
\begin{equation}
|L| \gg 1: \quad S \approx \frac{1}{L}, \\; T \approx 1 \qquad\qquad |L| \ll 1: \quad S \approx 1, \\; T \approx L
\end{equation}
Shaping \\(|L|\\) is therefore enough: a high loop gain gives a small \\(|S|\\) (good tracking and disturbance rejection), and a low loop gain gives a small \\(|T|\\) (good measurement-noise attenuation).
##### Stability and module margin {#stability-and-module-margin}
For a SISO loop, instability occurs when \\(L\\) reaches a phase of \\(-180^\circ\\) with a magnitude still above one: the correction then comes back in phase with the error and the system diverges.
How far the loop is from this condition is classically measured by the **gain margin** and **phase margin**, both read directly on the Bode plot of \\(L\\) ([Figure 20](#figure--fig:loop-gain-phase-gain-margins)).
Keeping some margin ensures that the loop remains stable even if the plant response changes slightly.
<a id="figure--fig:loop-gain-phase-gain-margins"></a>
{{< figure src="figs/loop_gain_phase_gain_margins.png" caption="<span class='figure-number'>Figure 20: </span>Typical Loop Gain with indicated phase and gain margin" >}}
These two margins are convenient but only defined for SISO systems.
A more general view plots the response \\(L(j\omega)\\) in the complex plane, giving the **Nyquist plot** ([Figure 21](#figure--fig:nyquist-module-margin)).
For a stable plant, the closed-loop is stable if this curve does not encircle the critical point \\(-1\\).
The shortest distance to that point is called the **module margin**:
\begin{equation}\label{eq:module\_margin}
\Delta M = \min\_\omega |1 + L(j\omega)| = \frac{1}{\max\_\omega |S(j\omega)|}
\end{equation}
It combines gain and phase margin into a single number: \\(\Delta M = 0.5\\) guarantees a gain margin \\(\ge 6\\,dB\\) and a phase margin \\(\ge 29^\circ\\), and corresponds to \\(\max|S| = 2\\).
Therefore, from \eqref{eq:module\_margin} we can see that a large peak of \\(|S|\\) is a direct symptom of poor stability margins.
Most importantly, the module margin generalizes naturally to MIMO systems through the characteristic loci, which is why it is the criterion reported in the GUI.
<a id="figure--fig:nyquist-module-margin"></a>
{{< figure src="figs/nyquist_module_margin.png" caption="<span class='figure-number'>Figure 21: </span>Nyquist plot of the loop gain, and module margin defined as the distance to the \\(-1\\) point" >}}
##### From the frequency domain to the time domain {#from-the-frequency-domain-to-the-time-domain}
The shapes designed in the frequency domain have direct time-domain counterparts, and being able to read one from the other is what makes loop shaping intuitive.
Consider first a loop gain containing a single integrator (Step 1 in the next section).
The closed-loop transfer functions, the step response and the "ramp" response (i.e. constant velocity scans) are shown in [Figure 22](#figure--fig:S-T-one-int).
Thanks to the integrator, there is no steady state error on the step response ([Figure 23](#figure--fig:step-resp-one-int)).
The time response is set by the closed-loop bandwidth \\(f\_b\\) seen on \\(|S|\\) ([Figure 22](#figure--fig:S-T-one-int)): the smaller \\(f\_b\\), the slower the error decays, with a time constant of \\(1/(2\pi f\_b)\\).
Two consequences for the specifications:
- **Step motion**: The time it takes for the output to reach a step amplitude \\(A\\) within \\(\pm \delta\\) can be estimated using \eqref{eq:one\_int\_step\_time}.
\begin{equation}\label{eq:one\_int\_step\_time}
t\_s \approx \frac{1}{2\pi f\_b} \ln\left(\frac{A}{\delta}\right)
\end{equation}
- **Constant velocity scan**: tracking a ramp leaves a constant error \\(v/(2\pi f\_b)\\) ([Figure 24](#figure--fig:ramp-resp-one-int)).
One integrator is therefore not sufficient to meet the \\(10\\,nm\\) scan specification unless the bandwidth is unreasonably high.
<div class="exercice">
Estimate the required closed-loop bandwidth \\(f\_b\\) to that the time it takes to reach \\(\pm 10\\,nm\\) of error for a \\(1\\,\mu m\\) step is less than \\(20\\,\text{ms}\\).
<details><summary>Answer</summary>
Using \eqref{eq:one\_int\_step\_time}, with \\(A = 1\\,\mu m\\) and \\(\delta = 10\\,nm\\), we get \\(t\_s \approx 0.73/f\_c\\): a \\(20\\,ms\\) settling time requires \\(f\_c \approx 37\\,Hz\\).
</div>
<div class="subfigures">
<a id="figure--fig:S-T-one-int"></a>
{{< figure src="figs/S_T_one_int.png" caption="<span class='figure-number'>Figure 22: </span>Filter's response" >}}
<a id="figure--fig:step-resp-one-int"></a>
{{< figure src="figs/step_resp_one_int.png" caption="<span class='figure-number'>Figure 23: </span>Step Response" >}}
<a id="figure--fig:ramp-resp-one-int"></a>
{{< figure src="figs/ramp_resp_one_int.png" caption="<span class='figure-number'>Figure 24: </span>Ramp Response" >}}
</div>
<div class="subfigures_caption">
Relationship between frequency domain and time domain response for a control system with one integrator
</div>
Then, consider a closed-loop system with two integrators in the loop.
The closed-loop transfer functions, the step response and the ramp response are shown in [Figure 25](#figure--fig:S-T-two-int).
The peak magnitude of \\(|T|\\) ([Figure 25](#figure--fig:S-T-two-int)) is translated in some overshoot in the step response ([Figure 26](#figure--fig:step-resp-two-int)).
Having a second integrator allows to have no steady state error for a constant velocity scan ([Figure 27](#figure--fig:ramp-resp-two-int)).
<div class="subfigures">
<a id="figure--fig:S-T-two-int"></a>
{{< figure src="figs/S_T_two_int.png" caption="<span class='figure-number'>Figure 25: </span>Filter's response" >}}
<a id="figure--fig:step-resp-two-int"></a>
{{< figure src="figs/step_resp_two_int.png" caption="<span class='figure-number'>Figure 26: </span>Step Response" >}}
<a id="figure--fig:ramp-resp-two-int"></a>
{{< figure src="figs/ramp_resp_two_int.png" caption="<span class='figure-number'>Figure 27: </span>Ramp Response" >}}
</div>
<div class="subfigures_caption">
Relationship between frequency domain and time domain response for a control system with two integrators
</div>
The steady state errors for different conditions are summarized in [Table 4](#table--tab:steady-state-errors).
This table drives the rest of this section: one integrator is enough for step motions (Step 1 and Step 2), but a constant velocity scan then leaves a steady state error proportional to the velocity, which is why a second integrator is added in Step 3.
<a id="table--tab:steady-state-errors"></a>
<div class="table-caption">
<span class="table-number"><a href="#table--tab:steady-state-errors">Table 4</a>:</span>
Steady state tracking error as a function of the number of integrators in the loop gain
</div>
| Integrators in \\(L\\) | Step | Constant velocity \\(v\\) | Constant acceleration \\(a\\) |
|------------------------|---------|---------------------------|-------------------------------|
| 1 | \\(0\\) | \\(v/(2\pi f\_c)\\) | \\(\infty\\) |
| 2 | \\(0\\) | \\(0\\) | \\(a/(2\pi f\_c)^2\\) |
##### The wanted shape of the loop gain {#the-wanted-shape-of-the-loop-gain}
All of this can be summarized by one picture ([Figure 28](#figure--fig:open-loop-shaping-shape)): the shape we are trying to give to \\(|L(j\omega)|\\):
- **At low frequency**, the gain must be as high as possible to have good rejection of disturbances
- **Around the cross-over frequency**, the slope must be close to \\(-1\\) (\\(-20\\,dB/dec\\)) to have some stability margins.
- **At high frequency**, the gain must roll off quickly to attenuate the measurement noise and to stay insensitive to the badly known high frequency dynamics.
Loop shaping is nothing more than picking the filters listed above so that the measured plant FRF is bent into this shape.
This is what is done step by step in the rest of this section.
<a id="figure--fig:open-loop-shaping-shape"></a>
{{< figure src="figs/open_loop_shaping_shape.png" caption="<span class='figure-number'>Figure 28: </span>Typical Wanted Shape for the Loop Gain \\(L(s)\\)" >}}
When using loop shaping to control a plant, several thing are worth keeping in mind during the process.
The sensitivity and complementary sensitivity transfer functions are linked with \\(S + T = 1\\).
This means that \\(S\\) and \\(T\\) cannot both be made small at the same frequency.
Below \\(f\_c\\) the loop follows the reference but also injects the sensor noise (\\(|T| \approx 1\\)), and above \\(f\_c\\) the loop does essentially nothing (\\(|S| \approx 1\\)).
Then, Bode's sensitivity integral (the "waterbed" theorem) states that reducing \\(|S|\\) over one frequency band unavoidably increases it somewhere else ([Figure 29](#figure--fig:bode-integral), from (<a href="#citeproc_bib_item_2">Stein 2003</a>)).
There is always a trade-off between low frequency performance and magnification of errors at another frequency.
<a id="figure--fig:bode-integral"></a>
{{< figure src="figs/bode_integral.png" caption="<span class='figure-number'>Figure 29: </span>Sensitivity reduction at low frequency unavoidably leads to sensitivity increase at higher frequencies." >}}
Finally, we should keep in mind that **gain and phase are not independent**.
For a minimum phase system, a slope of \\(-20\\,dB/dec\\) on the magnitude comes with \\(-90^\circ\\) of phase (Bode's gain-phase relationship).
This is the whole difficulty of loop shaping: added gain brought at low frequency is paid by phase lag near the cross-over frequency, that is by stability margins.
### Step 1 - Integral controller {#step-1-integral-controller}
**Goal:** obtain the fastest possible loop with a single integrator, while keeping a module margin of \\(0.5\\).
The simplest useful controller is a pure integrator that gives a very large gain at low frequency, hence no steady state error on a step.
Only one parameter has to be tuned: the gain, which directly sets the cross-over frequency.
<div class="exercice">
**Procedure**:
1. In the `Feedback Control Design` page, click on `Load plant...` and load the identified plant (e.g. `G_id_plant_white_noise.mat`)
2. In `Filters in the loop`, tick `pid_x` for `u_1` and `pid_y` for `u_2`
3. Make sure that all the biquad filters in the loop are configured as `pass-through`
4. Set the PID type to `I` (we only want an integrator for now)
5. Set `P (RHP poles)` to `0`: the plant is stable, as verified during the identification
6. Increase the gain of the integrator until the module margin reaches \\(0.5\\)
7. Look at how \\(|S|\\) and \\(|T|\\) change as a function of the gain
<summary>Checkpoints</summary>
- What is the achieved closed-loop bandwidth \\(f\_b\\)? Hz
- What is limiting it?
Look at the Nyquist plot: which part of the curve comes close to the \\(-1\\) point?
- What is the shape of \\(T\\) (transfer function from \\(r\\) to \\(y\\))?
Using \\(t\_s \approx 0.73/f\_c\\), what settling time within \\(\pm 10\\,nm\\) do you predict for a \\(1\\,\mu m\\) step?
</div>
Let's now check these predictions on the real system.
<div class="exercice">
**Procedure**:
1. In the `Custom` page, move the motors to zero with the `Move to Zero` button, then enable the `feedback` regulator
2. Go to the `Motors` page
3. Configure the motor's parameters:
- `Velocity`: \\(100\\,\mu m/s\\)
- `Acceleration time`: \\(0.001\\,s\\)
4. Perform a \\(1\\,\mu m\\) motion
<summary>Checkpoints</summary>
The typical result is shown in [Figure 30](#figure--fig:step-response-I-controller).
- Verify that there is no steady state error, contrary to the feedforward-only case
- Measure the settling time within \\(\pm 10\\,nm\\): ms
- How could the settling time be reduced?
What cross-over frequency would be required for a \\(20\\,ms\\) settling time?
</div>
<a id="figure--fig:step-response-I-controller"></a>
{{< figure src="figs/step_response_I_controller.png" caption="<span class='figure-number'>Figure 30: </span>Step response with the integral controller (Module margin of 0.5)" >}}
##### A more aggressive controller {#a-more-aggressive-controller}
Let's try to increase the gain to have faster response time, and see what it actually costs.
<div class="exercice">
**Procedure**:
1. In the `Custom` page, disable the `feedback` regulator
2. Go back to the `Feedback Control Design` page
3. Increase the gain of the integral controller until the module margin drops to \\(0.1\\)
4. In the `Custom` page, move the motors to zero and enable the `feedback` regulator again
5. In the `Motors` page, perform the same \\(1\\,\mu m\\) motion
<summary>Checkpoints</summary>
- By how much has the closed-loop bandwidth been increased?
- How has the time response changed?
Is the settling time within \\(\pm 10\\,nm\\) really shorter?
- What are the new peak values of \\(|S|\\) and \\(|T|\\), and at which frequency do they occur?
- What is the frequency of the oscillation, and could it have been predicted from \\(T\\)?
</div>
The typical result is shown in [Figure 31](#figure--fig:step-response-I-controller-bad-margin).
<a id="figure--fig:step-response-I-controller-bad-margin"></a>
{{< figure src="figs/step_response_I_controller_bad_margin.png" caption="<span class='figure-number'>Figure 31: </span>Step response with the integral controller (Module margin of 0.1)" >}}
<div class="important">
A poor module margin means a large peak of \\(|S|\\) and \\(|T|\\), which in the time domain translates into overshoot, ringing at the peak frequency and, in the end, a longer settling time.
Pushing the gain is therefore not a way to go faster: beyond a certain point, performance and robustness degrade together.
When tuning a feedback controller, always look at the module margin and at the shape of the closed-loop transfer functions, not only at the cross-over frequency.
</div>
### Step 2 - Adding a notch filter {#step-2-adding-a-notch-filter}
**Goal:** remove the limitation caused by the resonance, and reach a \\(20\\,ms\\) settling time.
The gain could not be increased further because of the lightly damped resonance of the stage, which brings the Nyquist plot close to the \\(-1\\) point.
One way to overcome this limitation is to add a notch filter that cancels the plant resonance.
<div class="exercice">
**Procedure**:
1. Make sure that the `feedback` regulation is `off`
2. Tune the notch:
- Go to the `Transfer Function Analysis` or the `Feedback Control Design` page
- Make sure the plant is loaded
- Configure the `biquad_1_x` and `biquad_1_y` filters as a `notch`, and click on `Visual Tuning`
- Tune the notch frequency, depth and width until the resonance of the loop gain is nicely canceled
3. Back on the loop gain, increase the gain `k` of the integral controller until the module margin is \\(0.5\\) again
<summary>Checkpoints</summary>
- What is the new closed-loop bandwidth \\(f\_b\\)?
- What is the estimated settling time?
- Perform the \\(1\\,\mu m\\) step again ([Figure 32](#figure--fig:step-response-I-controller-notch)) and verify this prediction
- What limits the bandwidth now?
</div>
<a id="figure--fig:step-response-I-controller-notch"></a>
{{< figure src="figs/step_response_I_controller_notch.png" caption="<span class='figure-number'>Figure 32: </span>Step response with the integral controller and the added notch filter (Module margin of 0.5)" >}}
##### Performances during a constant velocity scan {#performances-during-a-constant-velocity-scan}
Step motions are only half of the story: most experiments are performed as "fly scans", at constant velocity.
Let's see how this controller behaves in that case.
<div class="exercice">
**Procedure**:
1. Configure the motor's parameters in the `Motors` page:
- `Velocity`: \\(100\\,\mu m/s\\)
- `Acceleration time`: \\(0.1\\,s\\)
- `Setpoint`: \\(100\\,\mu m\\)
2. Perform the motion by clicking on `Start`
3. Save the motion data
<summary>Checkpoints</summary>
The typical result is shown in [Figure 33](#figure--fig:cst-velocity-int-notch).
- Zoom on the error: what do you observe during the constant velocity phase?
- Measure this steady state error: nm.
Compare it with \\(v/(2\pi f\_c)\\) predicted in [Table 4](#table--tab:steady-state-errors).
- Would increasing the gain be enough to make this error below \\(\pm 10\\,nm\\)?
</div>
<a id="figure--fig:cst-velocity-int-notch"></a>
{{< figure src="figs/cst_velocity_int_notch.png" caption="<span class='figure-number'>Figure 33: </span>Constant velocity scan with the Integral + Notch controller" >}}
### Step 3 - Optimizing the controller for constant velocity scans {#step-3-optimizing-the-controller-for-constant-velocity-scans}
**Goal:** Have no steady state error during the constant velocity phase of a scan.
[Table 4](#table--tab:steady-state-errors) tells us what is needed: a **second** integrator in the loop gain.
As the plant has none (it is a static gain at low frequency), both have to be put in the controller.
The difficulty is that each integrator adds \\(-90^\circ\\) of phase: with two of them the phase drop reaches \\(180^\circ\\) and the feedback system becomes unstable, so phase must be given back around the cross-over frequency with a **lead filter**.
<div class="exercice">
**Procedure**:
1. Turn off the `feedback` regulation
2. In the `Feedback Control Design` page, configure `biquad_2_x` and `biquad_2_y` as an `integrator`
3. Observe that the loop is now announced as unstable, and look at the phase of \\(L\\) to understand why
4. Configure `biquad_3_x` and `biquad_3_y` as a `lead`, and click on `Visual Tuning`
5. Tune the lead and the gain to obtain a cross-over frequency around \\(40\\,Hz\\) with a module margin of \\(0.5\\)
<summary>Hint</summary>
Put the central frequency `wn` of the lead at the wanted cross-over frequency (\\(40\\,Hz\\)), where it provides its maximum phase and a unity gain.
Then adjust the overall gain so that the cross-over frequency is indeed at \\(40\\,Hz\\), and finally increase the amount of phase lead (`a`) until the module margin reaches \\(0.5\\).
</div>
Let's now check the performances of this controller with two integrators.
<div class="exercice">
**Procedure**:
1. Configure the motor's parameters in the `Motors` page:
- `Velocity`: \\(100\\,\mu m/s\\)
- `Acceleration time`: \\(0.1\\,s\\)
- `Setpoint`: \\(100\\,\mu m\\)
2. Perform the motion by clicking on `Start`
3. Save the motion data
<summary>Checkpoints</summary>
The typical result is shown in [Figure 34](#figure--fig:cst-velocity-double-int-notch).
- Is there still a steady state error during the constant velocity phase?
- What happens during the acceleration and deceleration phases?
With \\(a = 1000\\,\mu m/s^2\\) and \\(f\_c = 40\\,Hz\\), does the measured error match \\(a/(2\pi f\_c)^2\\)?
- How could the performances be improved during the acceleration phases?
</div>
<a id="figure--fig:cst-velocity-double-int-notch"></a>
{{< figure src="figs/cst_velocity_double_int_notch.png" caption="<span class='figure-number'>Figure 34: </span>Constant velocity scan with a Double Integral + Notch controller" >}}
### Step 4 - Combined Feedback/Feedforward controller {#step-4-combined-feedback-feedforward-controller}
**Goal:** use the feedforward controller to take care of the known part of the trajectory, and the feedback controller for everything else.
<div class="exercice">
**Procedure**:
1. In the `Custom` page, click on `Move to Zero`
2. Activate the `feedforward` regulation, keeping the feedback one enabled
3. Go to the `Motors` page and click on `Overlay` to keep the last scan data (performed without feedforward)
4. Perform the same \\(100\\,\mu m\\) constant velocity scan
<summary>Checkpoints</summary>
The typical result is shown in [Figure 35](#figure--fig:cst-velocity-double-int-notch-feedforward).
- Is the error during the acceleration phases reduced?
- Conclude on the benefits of feedforward control, even when a feedback controller is implemented
</div>
<div class="note">
Adding a feedforward path changes neither \\(L\\), nor \\(S\\), nor the stability margins: it only modifies the transfer function from \\(r\\) to \\(y\\).
It is therefore "free" in terms of robustness, which is why feedback and feedforward are almost always used together.
</div>
<a id="figure--fig:cst-velocity-double-int-notch-feedforward"></a>
{{< figure src="figs/cst_velocity_double_int_notch_feedforward.png" caption="<span class='figure-number'>Figure 35: </span>Constant velocity scan with the combined feedback/feedforward controller" >}}
### Step 5 - Robust Control (bonus) {#step-5-robust-control--bonus}
A very important problematic in the field of control is the **robustness** to the change of plant dynamics.
In our case, the change of the plant dynamics is most of the time due to a change of payload.
<div class="exercice">
- How do you expect the plant dynamics to change when the payload mass is increased?
- What about the static gain?
- What about the resonance?
- What is the implications on the feedforward and the feedback controllers?
Should they be re-tuned?
</div>
<div class="important">
**Performance is usually traded against robustness.**
Every time some knowledge of the plant is injected into the controller (a notch tuned on the resonance, an inverse model in the feedforward path, ...), the loop becomes sensitive to the accuracy of that knowledge.
The notch of Step 2 bought us bandwidth, but it only works as long as the resonance stays where it was measured.
Simple controllers are usually the robust ones: would the integral controller of Step 1 survive a change of payload mass?
</div>
Let's put a payload with mass \\(\approx 1\\,\text{kg}\\) on the stage ([Figure 36](#figure--fig:picture-xy-stage-mass)) and identify the plant dynamics again.
<a id="figure--fig:picture-xy-stage-mass"></a>
{{< figure src="figs/picture_xy_stage_mass.jpg" caption="<span class='figure-number'>Figure 36: </span>XY stage with an added payload of \\(\approx 1\\,kg\\)" >}}
<div class="exercice">
**Procedure**:
- Turn off the feedback and feedforward controllers in the `Custom` page
- Go to the `Dynamical Identification` page, configure the `id_plant` generator as before, and click on `Acquire`
- Click on `Load TF...` to load the previously computed transfer function (without any payload)
<summary>Checkpoints</summary>
- How has the transfer function changed?
- Keeping the controller tuned without payload, what happened to the module margin and to the notch?
</div>
The last exercise is to design controllers that work for both configurations.
<a id="figure--fig:id-comp-low-high-mass"></a>
{{< figure src="figs/id_comp_low_high_mass.png" caption="<span class='figure-number'>Figure 37: </span>Measured FRF of the system with 1kg payload. Comparison with the case without any payload." >}}
<div class="exercice">
**Procedure**:
1. Go to the `Feedback Control Design` page, and load the two FRF (with and without payload)
2. Switch between the two plants: stability, margins and closed-loop transfer functions are computed for each of them
3. Tune the feedback controller to obtain good stability margins for **both** plants, with a bandwidth as high as possible
4. For the feedforward controller, use a constant gain or a low pass filter rather than a notch
<summary>Checkpoints</summary>
- How much bandwidth did you have to give up compared to the controller tuned for a single payload?
- Which "knowledge" of the plant is now used in the feedforward and feedback controllers?
Is this "knowledge" a function of the payload mass?
</div>
<div class="note">
Several strategies exist to deal with a change of dynamics:
- **Robust control** (\\(\mathcal{H}\_\infty\\), \\(\mu\text{-synthesis}\\)): the uncertainty is modeled explicitly and a controller is synthesized to guarantee stability and performance for the whole uncertainty set.
Parametric uncertainty (a resonance that moves for instance) is difficult to model this way, the procedure is heavy, and the result is often conservative.
Interestingly, the resulting "robust" controllers are again combinations of second order filters, which could have been tuned by hand.
Have a look at this (<a href="#citeproc_bib_item_1">Skogestad and Postlethwaite 2007</a>) reference book.
- **Adaptive control**: the idea is to re-tune the notch and the feedforward automatically, for instance by performing a new system identification each time the payload has changed.
- **Specific architectures**: for instance, using collocated actuator/sensor pairs can guarantee the stability of the feedback system.
But these techniques usually requires additional sensors.
</div>
### Summary {#summary}
<div class="sum">
- The feedback controller is tuned by shaping the loop gain \\(L = G K\_{fb}\\) measured on the real system: high gain at low frequency for performance, a \\(-20\\,dB/dec\\) slope at the cross-over, a fast roll-off above.
- The module margin (\\(\ge 0.5\\)) and the shape of \\(S\\) and \\(T\\) are the tuning criteria.
- Integrators remove the steady state errors (one for steps, two for constant velocity scans), notch filters remove resonances, and lead filters pay back the phase that integrators and notches consumed.
- Feedback handles what is unknown (disturbances, creep, hysteresis, drift), feedforward handles what is known (the trajectory, the plant dynamics): the two are usually combined.
- Every bit of plant knowledge injected into the controller is bought with robustness, and has to be re-checked when the payload changes.
</div>
## Conclusion {#sec:conclusion}
The hands-on followed the order in which a positioning system is typically commissioned:
- **System identification** (Section [System Identification](#sec:system_identification)): excite the plant with an adequate signal and **measure** its FRF \\(\hat{G}(j\omega)\\) — the coherence tells if the measurement can be trusted, the RGA-number if the axes are decoupled.
- **Feedforward control** (Section [Feedforward Control](#sec:feedforward_control)): approximate the inverse of the plant with simple filters (a gain, then a notch) so that \\(G K\_{ff} \approx 1\\).
Being open-loop, it carries no risk of instability, but it can only correct what is known in advance: the trajectory and the nominal plant.
- **Feedback control** (Section [Feedback Control](#sec:feedback_control)): shape the loop gain \\(L = G K\_{fb}\\) with a handful of standard filters — integrators to remove steady state errors, a notch to remove the resonance, a lead to recover the phase, a low pass for noise and robustness — while keeping the module margin large enough.
None of this required a physical model: one measured FRF was enough to design and validate all the controllers.
The tools presented in this document are generic — identification, loop shaping, sensitivity functions — and the same workflow can be reused for other positioning systems such as air bearing spindles, stepper motor stages or active vibration isolation platforms.
## Bibliography {#bibliography}
<style>.csl-entry{text-indent: -1.5em; margin-left: 1.5em;}</style><div class="csl-bib-body">
<div class="csl-entry"><a id="citeproc_bib_item_1"></a>Skogestad, S., and I. Postlethwaite. 2007. <i>Multivariable Feedback Control: Analysis and Design - Second Edition</i>. John Wiley.</div>
<div class="csl-entry"><a id="citeproc_bib_item_2"></a>Stein, Gunter. 2003. “Respect the Unstable.” <i>IEEE Control Systems Magazine</i> 23 (4). IEEE: 12–25.</div>
</div>
[^fn:1]: <https://cedrat-technologies.com/fr/produit/apa300ml/>
[^fn:2]: Make by Speedgoat: <https://www.speedgoat.com/>
[^fn:3]: Made by Piezodrive: <https://www.piezodrive.com/modules/pdm200/>
[^fn:4]: EL5112 from Beckhoff
[^fn:5]: Accessible here: <https://gitlab.esrf.fr/dehaeze/xy-piezo-stage>
[^fn:6]: Accessible here: <https://gitlab.esrf.fr/dehaeze/bliss-speedgoat-library>
[^fn:7]: Accessible here: <https://gitlab.esrf.fr/dehaeze/real-time-interface-gui>