"and also storage space for the two _history_ items $w[n-1]$ and $w[n-2]$.\n",
"and also storage space for the two _history_ items $w[n-1]$ and $w[n-2]$.\n",
"\n",
"\n",
"The actual code then looks like this:\n",
"The actual code then looks like this:\n",
...
@@ -352,7 +358,7 @@
...
@@ -352,7 +358,7 @@
"collapsed": true
"collapsed": true
},
},
"source": [
"source": [
"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: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
%pylabinline
%pylabinline
```
```
%% 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
fromnqlabimport*
fromnqlabimport*
importpandasaspd
importpandasaspd
importscipy.signalassig
importscipy.signalassig
```
```
%% 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).
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:
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:
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: