Developer Manual

This document deals with the internals of DAMARIS and is meant to be an intruduction for developers who want to extend DAMARIS. After an introduction of the back end, a description how the Tecmag DAC20 driver is operating and how it has been implemented into DAMARIS will be given.

The Backend

Starting the back end is causing it to search in the given spool directory for XML job files. These numbered XML files contain 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 (so called state atoms), which for example could be an instruction to set a channel on the pulse programmer or set a 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 equentially in order (numbering in above figure). As an example the pulse sequence π-wait-pfg-wait-π-(wait-π-wait-π)n will be explained in detail. Once this pulse sequence is written in the DAMARIS front end, it results in the following XML file:


<?xml version=""1.0"" encoding=""ISO-8859-1""?>
<experiment no=""0"">
<state time=""2e-6"">
  <analogout id=""0"" f=""300.01e6"" phase=""0""/>
</state>
<state time=""5e-6""><ttlout value=""0x1""/></state>
<state time=""4e-6""><ttlout value=""0x3""/></state>
<state time=""5e-3""/>
<state time=""1e-3"">
  <analogout id=""1"" dac_value="" 15040""/>
</state>
<state time=""3.8e-6"">
  <analogout id=""1"" dac_value=""0""/>
</state>
<state time=""5e-3""/>
<state time=""5e-6""><ttlout value=""0x1""/></state>
<state time=""4e-6""><ttlout value=""0x3""/></state>
<sequent repeat=""10"">
  <state time=""5e-3""/>
  <state time=""5e-6""><ttlout value=""0x1""/></state>
  <state time=""4e-6""><ttlout value=""0x3""/></state>
  <state time=""5e-3""/>
  <state time=""5e-6""><ttlout value=""0x1""/></state>
  <state time=""4e-6""><ttlout value=""0x3""/></state>
</sequent>
<state time=""0.0206848"">
  <analogin s=""1024"" f=""50000"" sensitivity=""5.0""/>
</state>
</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. Any result obtained by the back end is written to the corresponding XML result file (job filename + .result).

Example Driver: DAC Driver

If the parser is encountered an analogout element with suitable ID in a state, the DAC driver will extract the integer value for the gradient strength. The integer value is translated to binary representation (Listing 2.2, lines 133-141), and the single bits are transferred to the DAC (Listing 2.2, lines 160-190). 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.


if (PFG_aout != NULL) { // Begin state modifications
 // Check the length of the state
 if (this_state->length < TIMING*41.0)
  throw pfg_exception""time is too short to save DAC information"");
 else {
  // Copy of original state
  stateregister_state = new state(*this_state);
  ttloutregister_ttls = new ttlout();
  register_ttls->id = 0;
  register_state->length = TIMING;
  register_state->push_back(register_ttls);
  // Return error if dac_value is out of bounds
  if (PFG_aout->dac_value > (pow(2.0int(DAC_BIT_DEPTH-1))-1) )
   throw pfg_exception(""dac_value too high"");
  if ( abs(PFG_aout->dac_value) > pow(2.0int(DAC_BIT_DEPTH-1)) )
   throw pfg_exception(""dac_value too low"");
  // Now, create the bit pattern
  vector<intdac_word;
  for (int j = 0j < DAC_BIT_DEPTH ; j++) {
   int bit = PFG_aout->dac_value & 1;
 dac_word.push_back(bit);
 PFG_aout->dac_value >>= 1;
 }
// Reverse the bit pattern
reverse(dac_word.begin(), dac_word.end());
/*
      Datasheet AD1862:
          - Bit is read with rising edge of the CLK
          - Word is read with falling edge of LE
      The opto-coupler in the DAC20 are inverting the signal!
      CLK is here inverted, the rest not. This does not affect functionality
      because the inverse of a 2s complement is then just negative - 1:
      Example:
          5 bits:
           15 --> 01111 -->inverting--> 10000 = -16
            1 --> 00001 -->inverting--> 11110 = -2
            0 --> 00000 -->inverting--> 11111 = -1
           -1 --> 11111 -->inverting--> 00000 = 0
           -2 --> 11110 -->inverting--> 00001 = 1
          -16 --> 10000 -->inverting--> 01111 = 15
      Latch enable is going high after 41st bit.
*/
// Transfer the bit pattern
for (int i = 0i < DAC_BIT_DEPTHi++) {
 // Two states = one clock cycle to read in bit
  // State 1
  register_ttls->ttls=(1<<DATA_BIT)*dac_word[i]+(1<<CLK_BIT)+(1<<LE_BIT);
  the_sequence.insert(the_state,register_state->copy_new());
  // State 2
  register_ttls->ttls = (1<<DATA_BIT)*dac_word[i]+(1<<LE_BIT);
  the_sequence.insert(the_state,register_state->copy_new());
  if (i == (DAC_BIT_DEPTH-1)) {
    // Last bit => LE -> 0, prepare DAC to read the word in
    register_ttls->ttls = 0;
    the_sequence.insert(the_state,register_state->copy_new());
  }
}
/*
      Shorten the remaining state
      and add LE high to this state.
      The word is read in.
*/
ttloutttls=new ttlout();
// 42nd pulse
this_state->length -= TIMING*41;
// Here is the word read in
ttls->ttls = 1 << LE_BIT;
this_state->push_front(ttls);
delete register_state;
delete PFG_aout;
  }
}

This procedure was first tested and further refined in Python. Embeding the driver properly in the DAMARIS back end made it necessary to translate the test program to C++ code. Generelly 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

The former 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 front end configuration tab.

 

In the front end the following file needed to be modified to be able to use the PFG driver conveniently in the experiment script:

  • 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. As an example the function could look like this:


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 whish 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:
        # mimimum 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 −219 to 219 − 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 an 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 different numbers of supporting points.

 

Trouble shooting

In order to find problems in the backend the author recommends the following guidelines:

  • Check the XML job file for the correct statements, if necessary simplify the job file as much as possible. Let the front end keep the job/result files.
  • Compile the PulseProgrammer and ADC card driver with debug symbols enabled (gcc -DSP_DEBUG -DSPC_DEBUG) and check for proper pulse program or any warning messages of the drivers.
  • start the backend in the terminal: backend --spool  spool_dir