Skip to content
Snippets Groups Projects
Commit 1fca34e0 authored by Sebastian Steinlechner's avatar Sebastian Steinlechner
Browse files

some section missing in notebook

parent 40955c76
No related branches found
No related tags found
No related merge requests found
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
# Development of ADwin-Based Control System # Development of ADwin-Based Control System
This is a growing script to document the performance and development of the ADwin data acquisition and control system. This is a growing script to document the performance and development of the ADwin data acquisition and control system.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
%pylab inline %pylab inline
``` ```
%% Output %% Output
Populating the interactive namespace from numpy and matplotlib Populating the interactive namespace from numpy and matplotlib
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
from nqlab import * from nqlab import *
import pandas as pd import pandas as pd
import scipy.signal as sig import scipy.signal as sig
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Read/Write Transfer Functions ## Read/Write Transfer Functions
First of all, we want to know the transfer functions of a very simple "Read 8 channels", "Send 8 channels" program running at different sampling frequencies (corresponding to a ProcessDelay of F_CPU/F_SAMPLE). First of all, we want to know the transfer functions of a very simple "Read 8 channels", "Send 8 channels" program running at different sampling frequencies (corresponding to a ProcessDelay of F_CPU/F_SAMPLE).
P2_ADCF_MODE(2,1) P2_ADCF_MODE(2,1)
... ...
P2_Read_ADCF8(module, buffer, 1) P2_Read_ADCF8(module, buffer, 1)
P2_DAC8(module, buffer, 1) P2_DAC8(module, buffer, 1)
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
fsamples = { fsamples = {
'20k': ['09', '10'], '20k': ['09', '10'],
'50k': ['11', '12'], '50k': ['11', '12'],
'100k': ['13', '15'], '100k': ['13', '15'],
'200k': ['05', '06'], '200k': ['05', '06'],
'500k': ['07', '08'] '500k': ['07', '08']
} }
amplitudes = pd.DataFrame() amplitudes = pd.DataFrame()
phases = pd.DataFrame() phases = pd.DataFrame()
for rate, files in fsamples.items(): for rate, files in fsamples.items():
df = io.import_data('SCRN00'+files[0]+'.TXT', 'ascii_pandas') df = io.import_data('SCRN00'+files[0]+'.TXT', 'ascii_pandas')
amplitudes[rate] = df['SCRN00'+files[0]] amplitudes[rate] = df['SCRN00'+files[0]]
df = io.import_data('SCRN00'+files[1]+'.TXT', 'ascii_pandas') df = io.import_data('SCRN00'+files[1]+'.TXT', 'ascii_pandas')
phases[rate] = df['SCRN00'+files[1]] phases[rate] = df['SCRN00'+files[1]]
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
fig = plots.frequency_domain.amplitude_phase(amplitudes, phases, title='Response for various sample rates') fig = plots.frequency_domain.amplitude_phase(amplitudes, phases, title='Response for various sample rates')
fig.axes[0].legend(); fig.axes[0].legend();
``` ```
%% Output %% Output
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Read/Write with Fixed Delay ## Read/Write with Fixed Delay
The above transfer functions were made when the DAC outputs are always written immediately after being read. However, we want to do calculations in the mean time. Thus, the time delay between read and write would change depending on the amount of calculations. It might therefore be beneficial to always write the data at the beginning of the Event, i.e. with a constant time delay: The above transfer functions were made when the DAC outputs are always written immediately after being read. However, we want to do calculations in the mean time. Thus, the time delay between read and write would change depending on the amount of calculations. It might therefore be beneficial to always write the data at the beginning of the Event, i.e. with a constant time delay:
P2_ADCF_MODE(2,1) P2_ADCF_MODE(2,1)
... ...
P2_DAC8(module, buffer, 1) P2_DAC8(module, buffer, 1)
P2_Read_ADCF8(module, buffer, 1) P2_Read_ADCF8(module, buffer, 1)
This results in an additional delay: This results in an additional delay:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
amplitude = io.import_data('SCRN0013.TXT', 'ascii_pandas') amplitude = io.import_data('SCRN0013.TXT', 'ascii_pandas')
amplitude.columns = ['100kHz'] amplitude.columns = ['100kHz']
phases = io.import_data('SCRN0014.TXT', 'ascii_pandas') phases = io.import_data('SCRN0014.TXT', 'ascii_pandas')
phases = phases.join(io.import_data('SCRN0015.TXT', 'ascii_pandas')) phases = phases.join(io.import_data('SCRN0015.TXT', 'ascii_pandas'))
phases.columns = ['DAC:ADC', 'ADC:DAC'] phases.columns = ['DAC:ADC', 'ADC:DAC']
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
fig = plots.frequency_domain.amplitude_phase(amplitude, phases, title='Response at 100kHz') fig = plots.frequency_domain.amplitude_phase(amplitude, phases, title='Response at 100kHz')
fig.axes[1].legend(); fig.axes[1].legend();
``` ```
%% Output %% Output
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Let us calculate that delay: Let us calculate that delay:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
fsample = 1e5 fsample = 1e5
delta_t = phases['DAC:ADC']/360/phases.index * 1e6 delta_t = phases['DAC:ADC']/360/phases.index * 1e6
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
fig = delta_t.plot(logx=True, title='Time Delay in $\mu s$') fig = delta_t.plot(logx=True, title='Time Delay in $\mu s$')
fig.grid() fig.grid()
fig.set_ylabel('$\Delta t$'); fig.set_ylabel('$\Delta t$');
``` ```
%% Output %% Output
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
So, roughly 17us, whereas we would expect something on the order of 1/100kHz, i.e. 10us: So, roughly 17us, whereas we would expect something on the order of 1/100kHz, i.e. 10us:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
1/fsample * 1e6 1/fsample * 1e6
``` ```
%% Output %% Output
10.0 10.0
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
At first I `P2_DAC8` to write the values, however this first needs to send the values to the DAC module and then starts the conversion, also it cannot synchronously start conversion on both output modules. Therefore I switched to `P2_Write_DAC8` at the end of the Event cycle, which just populates the DAC modules with new values but doesn't convert yet, and then at the start of the cycle I trigger the conversion using `P2_Sync_All`. However this did not lead to a measurable improvement in the time delay. At first I `P2_DAC8` to write the values, however this first needs to send the values to the DAC module and then starts the conversion, also it cannot synchronously start conversion on both output modules. Therefore I switched to `P2_Write_DAC8` at the end of the Event cycle, which just populates the DAC modules with new values but doesn't convert yet, and then at the start of the cycle I trigger the conversion using `P2_Sync_All`. However this did not lead to a measurable improvement in the time delay.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Second Order Sections ## Second Order Sections
The most straight forward approach for digital filters is the so-called second-order section (SOS). It has a transfer function that looks like this: The most straight forward approach for digital filters is the so-called second-order section (SOS). It has a transfer function that looks like this:
$$H(z) = \frac{b_0 + b_1 z^{-1} + b_2 z^{-2}}{a_0 + a_1 z^{-1} + a_2 z^{-2}}\,.$$ $$H(z) = \frac{b_0 + b_1 z^{-1} + b_2 z^{-2}}{a_0 + a_1 z^{-1} + a_2 z^{-2}}\,.$$
Usually, it is normalised such that $a_0 = 1$. Any second-order filter (e.g. pole-zero, 2nd order lowpass, notch) can be written in this form, and e.g. `scipy.signal` has functions to calculate the necessary coefficients: Usually, it is normalised such that $a_0 = 1$. Any second-order filter (e.g. pole-zero, 2nd order lowpass, notch) can be written in this form, and e.g. `scipy.signal` has functions to calculate the necessary coefficients:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
sig.bessel(2, 0.5/fsample) sig.bessel(2, 0.5/fsample)
``` ```
%% Output %% Output
(array([ 6.16841884e-11, 1.23368377e-10, 6.16841884e-11]), (array([ 6.16841884e-11, 1.23368377e-10, 6.16841884e-11]),
array([ 1. , -1.99997279, 0.99997279])) array([ 1. , -1.99997279, 0.99997279]))
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
A useful form to digitally calculate such filters is the so-called Direct Form II. It's equation looks like this: A useful form to digitally calculate such filters is the so-called Direct Form II. It's equation looks like this:
$$
\begin{align} \begin{align}
y[n] &= b_0 w[n] + b_1 w[n-1] + b_2 w[n-2] \\ y[n] &= b_0 w[n] + b_1 w[n-1] + b_2 w[n-2] \\
w[n] &= x[n] - a_1 w[n-1] - a_2 w[n-2] w[n] &= x[n] - a_1 w[n-1] - a_2 w[n-2]
\end{align} \end{align}
$$
In the LIGO filters, things are slightly rearranged: In the LIGO filters, things are slightly rearranged:
$$y[n] = b_0\bigl(x[n]-(a_1 + b_1/b_0)w[n-1] - (a_2 + b_2/b_0)w[n-2]\bigr)$$ $$y[n] = b_0\bigl(x[n]-(a_1 + b_1/b_0)w[n-1] - (a_2 + b_2/b_0)w[n-2]\bigr)$$
Thus, we need the 5 coefficients Thus, we need the 5 coefficients
$$
\begin{align} \begin{align}
c_0 &= b_0,\\ c_0 &= b_0,\\
c_1 &= a_1,\\ c_1 &= a_1,\\
c_2 &= a_2,\\ c_2 &= a_2,\\
c_3 &= b_1/b_0,\\ c_3 &= b_1/b_0,\\
c_4 &= b_2/b_0, c_4 &= b_2/b_0,
\end{align} \end{align}
$$
and also storage space for the two _history_ items $w[n-1]$ and $w[n-2]$. and also storage space for the two _history_ items $w[n-1]$ and $w[n-2]$.
The actual code then looks like this: The actual code then looks like this:
' overall gain ' overall gain
out = sos_input * filter_coeffs[1] out = sos_input * filter_coeffs[1]
' poles ' poles
out = out - filter_history[1] * filter_coeffs[2] out = out - filter_history[1] * filter_coeffs[2]
new_history = out - filter_history[2] * filter_coeffs[3] new_history = out - filter_history[2] * filter_coeffs[3]
' zeros ' zeros
out = new_history + filter_history[1] * filter_coeffs[4] out = new_history + filter_history[1] * filter_coeffs[4]
out = out + filter_history[2] * filter_coeffs[5] out = out + filter_history[2] * filter_coeffs[5]
filter_history[2] = filter_history[1] filter_history[2] = filter_history[1]
filter_history[1] = new_history filter_history[1] = new_history
... where indexing in ADwin-Basic starts with 1. And, it works! Here's an example with 4 notch filters and a low-pass filter: ... where indexing in ADwin-Basic starts with 1. And, it works! Here's an example with 4 notch filters and a low-pass filter:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
amplitude = io.import_data('SCRN0016.TXT', 'ascii_pandas') amplitude = io.import_data('SCRN0016.TXT', 'ascii_pandas')
fig = amplitude.plot(logx=True,legend=False) fig = amplitude.plot(logx=True,legend=False)
fig.grid() fig.grid()
fig.set_ylabel('Magnitude'); fig.set_ylabel('Magnitude');
``` ```
%% Output %% Output
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
At 100kHz sample rate and with 8 filter modules ($8*5 = 40$ SOSs, 200 coefficients), this runs at about 46% CPU load (T12 processor module). At 100kHz sample rate and with 8 filter modules, $8*5 = 40$ SOSs, 200 coefficients, this runs at about 46% CPU load (T12 processor module).
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
``` ```
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment