Files
markusro 3096e83a43
Build Debian Packages / build (bookworm, debian12) (push) Failing after 7m59s
Build Debian Packages / build (bullseye, debian11) (push) Failing after 7m29s
Build Debian Packages / build (trixie, debian13) (push) Failing after 8m14s
major documentation rework and packaging
2026-03-25 14:36:24 +01:00

187 lines
11 KiB
ReStructuredText

.. _advanced:
######################
DAMARIS for Developers
######################
This chapter is dealing with the internals of DAMARIS. After an introduction of the back end, it is in more detail described how the Tecmag DAC20 driver is operating and how it has been implemented into DAMARIS. The complete source code of DAMARIS is available in a subversion source repository for convenient access at http://www.fkp.physik.tu-darmstadt.de/damaris.
************
The Back End
************
The front end is responsible for writing experiment description files (job files) in XML format.
.. figure:: pics/damaris_interaction.svg
:alt: Traversing machine
:width: 100%
Starting the experiment will execute the back end which will start polling the given spool directory for new XML job files. These numbered files are containing a sequence composed of states, which themselves contain instructions for the experiment, or sub-sequences (which can contain further states or sub-sequences and can be looped).
A state defines the output (24 bits) of the pulse programmer and can be either empty or contain sub-elements (state atoms), which for example could be an instruction to set a channel on the pulse programmer or set the DAC for a PFG current source.
The back end examines this XML job files and traverses the state sequence. Encountering a sub-sequence, this is traversed to its end, where the back end jumps out of the sub-sequence and continues where it entered the sub-sequence. This traversing is called *depth-first*, and causes this nested *state tree* to be processed sequentially in order.
As an example the pulse sequence π-wait-pfg-wait-π-(wait-π-wait-π)n will be explained in depth. Once this pulse sequence is written in the DAMARIS front end, it results in a XML file.
.. code-block:: xml
:caption: XML job containing a loop
<?xml version="1.0"?>
<experiment>
<state time="1e-6">
<ttl id="0" value="1"/>
</state>
<state time="2e-6">
<ttl id="0" value="0"/>
</state>
<sequence iterations="10">
<state time="1e-6">
<ttl id="0" value="1"/>
</state>
<state time="2e-6">
<ttl id="0" value="0"/>
</state>
</sequence>
</experiment>
.. figure:: pics/traversing_example2.png
:alt: Traversing example
:width: 100%
The XML file represented as a state tree. Elements are numbered in the order of the traversing. The loop is only traversed once, despite that the elements inside the loop will be executed repeatedly in the experiment.
Each driver in the back end (PTS synthesizer, PFG, etc.) is traversing this tree, searching for elements it is responsible for. The driver then translates these elements in pure states for the pulse programmer, i.e. TTL signals. Then, the next driver is traversing the tree, translating the elements it is responsible for. This is going on until all elements from the tree are translated into states, which are then written in machine code to the pulse programmer and executed.
.. figure:: pics/traversing_machine.svg
:alt: Traversing machine
:width: 100%
Part of the state tree where the element of the state dealing with PFG was translated
Any result obtained by the back end is written to the corresponding XML result file (job filename + .result).
The DAC driver
==============
If in a state an *analogout* element with suitable ID is encountered, the DAC driver extracts the integer value for the gradient strength. The integer value is translated to binary representation, and the single bits are transferred to the DAC. Then, the time of the state is shortened by the time needed to transfer the complete data to the DAC. Every pulse necessary for transferring the data is created by the pulse programmer.
Transferring the data to the DAC is achieved by three lines, notably LE (latch enable), CLK (clock signal) and DATA (data line). Reading in a DAC word goes as follows: LE stays high, a data bit is read in with the falling edge of the clock signal, i.e. CLK high to CLK low. After the last (20th) bit is recorded, LE is going low thus signaling the DAC that the word is complete.
.. code-block:: c++
:caption: Main logic of the DAC driver
:linenos:
// Main logic of the DAC driver
void DAC20::send_word(int value) {
// Set LE high
set_pin(LE, HIGH);
// Transfer 20 bits
for (int i = 0; i < 20; i++) {
// Set data bit
set_pin(DATA, (value >> i) & 1);
// Clock falling edge
set_pin(CLK, HIGH);
delay_ns(180);
set_pin(CLK, LOW);
}
// Set LE low to complete
set_pin(LE, LOW);
}
This procedure was first tested and further refined in Python. Embedding the driver properly in the DAMARIS back end made it necessary to translate the test program to C++ code. The following files of the back end needed to be modified or created:
- drivers/pfggen.h
- drivers/Tecmag-DAC20/DAC20.cpp
- drivers/Tecmag-DAC20/DAC20.h
These three files are the main part of the driver. They contain the main logic for the DAC serial line.
- machines/hardware.cpp
- machines/hardware.h
- machines/PFGcore.cpp
Finally, these last files contain the spectrometer setup information, i.e. which frequency synthesizer, ADC card, pulse programmer or temperature controller is used. The resulting PFGcore.exe is the back end which is executed by the front end and has to be specified in the configuration tab.
.. code-block:: c++
:caption: PFGcore.cpp which defines the spectrometer hardware
:linenos:
// PFGcore.cpp
#include "hardware.h"
int main() {
// Initialize hardware
Hardware hw;
hw.setFrequencySynthesizer(new PTS());
hw.setADCCard(new ADC200());
hw.setPulseProgrammer(new PulseBlaster());
hw.setPFG(new DAC20());
// Start the backend
hw.run();
return 0;
}
In the front end the following file needed to be modified to be able to use the PFG conveniently:
- Experiment.py
This file contains the functions to create a pulse sequence. The command **set_pfg**, which adds the proper XML element to the state tree, has been added.
.. code-block:: python
:linenos:
def set_pfg(self, I_out=None, dac_value=None, length=None, is_seq=0):
"""This sets the value for the PFG, it also sets it back automatically.
If you don't wish to do so (i.e. line shapes) set is_seq=1"""
if I_out != None:
print "I_out is deprecated"
if I_out == None and dac_value == None:
dac_value = 0
if I_out != None and dac_value == None:
dac_value = dac.conv(I_out)
if I_out == None and dac_value != None:
dac_value = dac_value
if I_out != None and dac_value != None:
dac_value = 0
print "WARNING: You can't set both, I_out and dac_value! dac_value set to 0"
if length == None:
# minimum length
length = 42 * 9e-8
self.state_list.append('<state time="%s">
<analogout id="1" dac_value="%i"/>
</state>\n' % (repr(length), dac_value))
if is_seq == 0:
# Set the DAC back to zero if this is not part of a sequence
self.state_list.append('<state time="%s">
<analogout id="1" dac_value="0"/>
</state>\n' % (repr(42 * 9e-8)))
The conversion factor is 50 A/V. The DAC receives the data from the pulse card after the signals are brought into rectangular shape by the cable driver. The bipolar signal is digitally encoded in 2s-complement, i.e. one bit represents the sign of the integer, thus the range of integer values starts from -pow(2, 19) to pow(2, 19)-1.
The DAC outputs a voltage according to the DAC word until the DAC receives another value. This behavior is important to the experimenter because if one wants to set a value only for a certain time it is necessary to set the value back to zero. In the DAMARIS front end this is done automatically with **set_pfg**\ (dac_value, length) whereas in the back end it is done at the beginning of each experiment to protect the hardware and to set the DAC into a defined state.
Arbitrary shaping of gradient pulses is also possible by using the command **set_pfg**\ (*dac_value*\ , *length*\ , *isseq*\ =1) which prevents setting the DAC to zero after the given length. Through concatenating subsequently **set_pfg**\ (dac_value, length, isseq=1) commands one can create almost any possible shape and/or background gradients.
Shaping the gradients was not the focus of this work but the general function was tested using an oscilloscope at the current monitor of the PFG current source with the DAC creating a sine wave using several resolutions.
.. figure:: pics/shapegrad1.png
:alt: Shaped PFG pulse
:width: 500px
A sin² PFG pulse with 1 ms length. The upper one with 100 μs, the lower one with 3.78 μs resolution
Theoretical resolution is given by approximately 280A/2**19 = 0.000534 A. Transferring in a 20bit word needs 21 cycles where each clock cycle is 180 ns long. This leads to a time resolution of 3.78 μs. Note that each DAC setting needs 42 instructions which can lead to problems regarding the memory of the PulseBlaster in the case of gradient pulse shaping. In figure above, the lower gradient, the maximum time resolution of 3.78 μs was used, there are 264 steps, each of them with 42 states leading to more than 11,000 instructions for this pulsed field gradient alone.
An improvement would be the use of loops for repeating patterns in the DAC word. The easiest approach would be counting consecutively ones and zeros and write a loop if a one or a zero is repeated, i.e. so called *run-length compression*. Even better would be searching for general patterns and use the best (in respect to memory, the smallest) instruction set. Another way to save instructions is to save the shape as a subprogram on the pulse programmers memory and recall it when it is needed. This possibility is not yet implemented in the pulse programmer driver.
Stability of the DAC has been tested by measuring the shifts of resonance frequency in a Field Cycling spectrometer where the DAC controlled the magnetic field. The DAC was very stable and the standard deviation of the frequency shift was about 3 kHz and no drifts were visible except for one outlier which is good compared to the usual 6 kHz with the standard field cycling DAC. Further stability measurements using an Agilent 3440 A high resolution multimeter have been also performed. These measurements lead to the conclusion that the DAC is sufficiently stable and temperature drifts do not occur.
The offset of the PFG amplifier was adjusted via the offset potentiometer on the DAC board. There is also an offset adjustment possible at the PFG amplifier directly. For adjustment, the signal of water in a sample tube with a 0.5 mm teflon stripe perpendicular to the magnetic field B0 was recorded. First, the PFG power supply has been switched off and the sample was shimmed with the room temperature shims until the longest decay time T*2 of the FID has been achieved. After this, the PFG was switched on and the offset has been adjusted to cause least distortion in the spectrum.