9.3 cw-EPR monitor script
Before doing a real experiment one often needs to optimize the
experimental parameters like phase, amplification etc. In these cases
it would be rather inconvenient to have start an experiment in order
to check e.g the signal intensity, adjust the parameters and start
another test experiment. One rather would have a program where it's
possible to adjust the parameters while scanning the field region
where the signal is to be expected. This can be done easily with a
another EDL
script. Here we also will make use of the built-in
methods to create additional graphical elements like buttons, input
fields etc.
Before starting to write such a script let's collect the requirements:
- It should be possible to sweep up and down as well as to stop the sweep. Whenever the sweep direction is changed or the sweep is stopped a marker should be drawn.
- We need two input fields to be able to set a new magnetic field value and sweep step size.
- The current field should be displayed in an output field.
- It should be possible to stop and restart acquiring new data from the lock-in amplifier.
- We would like to be able to clear the display when the old data aren't needed anymore.
- While running the test experiment the lock-in amplifiers keyboard should still be usable to allow adjustments of phase, sensitivity, time constant etc.
Now here is the complete EDL
script:
1 DEVICES: 2 3 er032m; // Bruker field controller 4 sr530; // SR 530 lock-in amplifier 5 6 7 VARIABLES: 8 9 field; 10 field_step = 0.5 G 11 data[ 2 ]; 12 13 last_field; 14 last_field_step = field_step; 15 new_field, new_field_step; 16 17 max_field = -50.0 G 18 min_field = 23 kG; 19 min_field_step = 1 mG; 20 max_field_step = 100 G; 21 22 I; 23 24 Sweep_State = 0; // 0: stopped, 1: up, -1: down 25 Pause_State = 1; // 0: running, 1: paused 26 27 Sweep_Up, Sweep_Down, Sweep_Stop, 28 Field_In, Field_Step_In, Field_Out, 29 Pause, Clear; 30 31 32 PREPARATIONS: 33 34 init_1d( 2, "Points", "Signal [uV]" ); 35 36 37 EXPERIMENT: 38 39 lockin_lock_keyboard( "OFF" ); 40 field = magnet_field( field ); 41 last_field = field; 42 43 hide_toolbox( "ON" ); 44 Field_Out = output_create( "FLOAT_OUTPUT", field, 45 "Current field (in G)" ); 46 Sweep_Up = button_create( "RADIO_BUTTON", "Sweep up" ); 47 Sweep_Stop = button_create( "RADIO_BUTTON", Sweep_Up, "Stop sweep" ); 48 Sweep_Down = button_create( "RADIO_BUTTON", Sweep_Up, "Sweep Down" ); 49 button_state( Sweep_Stop, "ON" ); 50 51 Field_In = input_create( "FLOAT_INPUT", field, 52 "Set a new field (in G)" ); 53 Field_Step_In = input_create( "FLOAT_INPUT", field_step, 54 "Set a new field step (in G)" ); 55 Pause = button_create( "PUSH_BUTTON", "Pause display" ); 56 button_state( Pause, Pause_State ); 57 Clear = button_create( "NORMAL_BUTTON", "Clear screen" ); 58 hide_toolbox( "OFF" ); 59 60 I = 1; 61 62 FOREVER { 63 64 wait( lockin_time_constant( ); 65 66 IF Pause_State == 0 67 { 68 data = lockin_get_data( 1, 2 ); 69 display_1d( I, data[ 1 ] / 1 uV, 1, 70 I, data[ 2 ] / 1 uV, 2 ); 71 I += 1; 72 } 73 74 IF button_state( Sweep_Up ) { 75 IF Sweep_State != 1 { 76 Sweep_State = 1; 77 draw_marker( I, "RED" ); 78 } 79 } ELSE IF button_state( Sweep_Down ) { 80 IF Sweep_State != -1 { 81 Sweep_State = -1; 82 draw_marker( I, "GREEN" ); 83 } 84 } ELSE { 85 IF Sweep_State != 0 { 86 Sweep_State = 0; 87 draw_marker( I, "YELLOW" ); 88 } 89 } 90 91 IF Sweep_State == 1 { 92 IF field + field_step <= max_field { 93 field = magnet_field( field + field_step ); 94 output_value( Field_Out, field ); 95 } ELSE { 96 Sweep_State = 0; 97 button_state( Sweep_Stop, 1 ); 98 draw_marker( I, "YELLOW" ); 99 } 100 } ELSE IF Sweep_State == -1 { 101 IF field - field_step >= min_field { 102 field = magnet_field( field - field_step ); 103 output_value( Field_Out, field ); 104 } ELSE { 105 Sweep_State = 0; 106 button_state( Sweep_Stop, 1 ); 107 draw_marker( I, "YELLOW" ); 108 } 109 } 110 111 new_field = input_value( Field_In ); 112 IF new_field != last_field { 113 IF ( new_field >= min_field AND new_field <= max_field { 114 field = magnet_field( new_field ); 115 output_field( Field_Out, field ); 116 last_field = new_field; 117 } ELSE { 118 input_field( Field_In, last_field ); 119 } 120 } 121 122 new_field_step = input_value( Field_Step_In ); 123 IF new_field_step != last_field_step { 124 IF new_field_step >= min_field_step AND 125 new_field_step <= max_field_step { 126 field_step = new_field_step; 127 last_field_step = new_field_step; 128 } ELSE { 129 input_field( Field_Step_In, last_field_step ); 130 } 131 } 132 133 Pause_State = button_state( Pause ); 134 135 IF button_state( Clear ) { 136 clear_curve( ); 137 clear_marker( ); 138 rescale( 64 ); 139 I = 1; 140 } 141 } |
I hope that this script doesn't look too daunting, but most of its length is due to checking for and reacting to user input, the really interesting content being rather small as you will see.
The DEVICES
section again is very simple, we're just loading
the modules for the Bruker
field controller and the
SR530
lock-in amplifier. Since this lock-in has two channels we
will be able to display both the x- and the y-phase signal
which might help to set the correct phase for the real experiment.
The VARIABLES
section has gotten rather long. But the most
important variables are just the first three for the current field,
the current field step size and an array with two elements for storing
the x- and y-phase signal.
The next four variables at line 13 to 15 are needed in the evaluation of the input fields for setting a new field and field step size. We will discuss their meaning later.
The variables defined on line 17 to 20 for the minimum and maximum field and field step size are not strictly necessary. We also could have hard-coded the values into the script (but at the cost of readability). It's also going to be easier to adapt the script for using a different field controller with a different set of limits when such values can be found in just one place instead of having them scattered all over the script.
The variable I
is just a counter variable which will tell us at
which coordinate to draw the next measured data points.
Sweep_State
and Pause_State
(line 24 and 25) are for
remembering the current state the program is in. If Sweep_State
is set to 1
the program is currently sweeping the field to
higher values, if it's set to 0
the field sweep is stopped, and
if it is -1
we're sweeping down. If Pause_State
is set
to 0
we're supposed to measure and display new data, if it's
1
data acquistion is disabled.
The final variables declared in the lines 27 to 29 are integer variables for storing handles for the graphical elements like buttons and in- and output fields. To e.g. determine if a button has been pressed we will need its handle.
The PREPARATIONS
section is very short, all which is done here
is the initialization of the display: we need two curves for the
x- and y-phase signal and we set the labels for the x- and
y-axis. We can't know in advance how many points we're going to
have to display, so the corresponding parameter in the function call
has been left out. Since it is rather likely that while running the
script the field will not be swept in just one direction (and in
fixed-sized steps) but will be swept up and down (or even stay at the
same value for longer times) in unpredictable ways it's not possible
to draw a reasonable x-axis scale, so also the parameters for
defining the scale are omitted, resulting in just point numbers
getting displayed at the x-axis.
Now we're getting to the most interesting part, the EXPERIMENT
section. The first thing we do (line 39) is to re-enable the lock-in
amplifiers keyboard. Normally, all keys of devices get disabled when
an experiment starts, so we have to re-enable them if we want to
manually change the settings.
The second thing to be done is figuring out at which field the magnet
currently is and store it in both the variables field
and
last_field
. Because we didn't call the function
magnet_setup()
in the PREPARATIONS
section as in
the previous example script the magnet will simply start of at the
field value it has been set to manually.
All the lines 43 to 58 are for setting up an additional window with
graphical elements, i.e. buttons and in- and output fields. The
first element is an output field, i.e. a box to just display values.
It is created by calling the function output_create()
with three arguments. The first is the type of the field where
"FLOAT_OUTPUT" stands for a field for displaying floating point
values. The second is the value to display in the field and the third
is a label to appear below the field. Obviously, this first field is
for displaying the current magnetic field. The integer value returned
by the function is later going to used when we have to refer to the
field for updating the displayed value when the magnet gets swept.
The following 4 lines (46-49) are for creating a set of radio buttons.
Radio buttons always appear in groups and the important property is
that only one of them can be switched on at a time. That's exactly
what we need to control if the magnet is to be swept up or down or
stopped - it can only be in one of these states. To create a button
we need to call the function button_create()
For the
first of group of radio buttons it takes just two arguments, the first
one indicating the type of the button, i.e. "RADIO_BUTTON"
(there are also other types of buttons as we will see soon) and a
label to be drawn to the right of the button.
For the other two radio buttons belonging to the group we need an additional argument to state to which group they belong to. This extra argument is simply the number of the first button of the group. It has to be given between the buttons type and the label string.
After creating all three buttons we now select one of them as
activated with a call to button_state()
. When we want to
set the state of a button we have to pass the function two arguments,
the number associated with the button and the state, either
"ON"
or "OFF"
(but 1
and 0
will also do).
If we don't set one of the buttons of a group of radio buttons the
first of the group will be selected per default. But since we better
start off with the sweep being stopped we here have to manually set
the corresponding button.
Next we add two input fields where the user can enter a new field as
well as a new field step size. This is down by a call of the function
input_create()
. Since both values are floating point
numbers we use "FLOAT_INPUT"
as the first argument. As already
in the case of the function for creating an output field, the second
argument is the value to be shown at first in the input field and the
third argument is a label string.
Finally two further buttons are created. The first one is for stopping
and restarting the acquisition of data. This button must be either
active or inactive, i.e. it's either pushed in or not. The natural
choice in this case is to use a button of type "PUSH_BUTTON"
which has exactly these properties. As we already have seen when
creating radio buttons, the function to create a button is
button_create()
and the first argument passed to it must
be the type of the button. The second argument is just the label
string to be shown on its right side. Since push buttons start of in
the inactive state per default but it is probably better not to start
the experiment already acquiring data (the user probably first will
have to set a reasonable field value) we have to activate button
(indicating that the acquisition is stopped) by calling
button_state()
with the buttons handle and the new state
to be set.
The last button for clearing the display is just a normal button,
i.e. a button that just can be clicked on. This is indicated by the
button type "NORMAL_BUTTON"
as the first argument to
button_create()
.
The creation of all the buttons and in- and output fields is enclosed
by two calls of the function hide_toolbox()
(line 43 and
58). Before we start creating the buttons etc. this disables the
immediate display of the newly created objects in a window labled
"Toolbox", redrawing the window for each object. When we're done with
adding new objects we "un-hide" the window and it willl now be visible
with all the new buttons etc. at once. Actually, this isn't
necessary, it's just cosmetics to make the creation of the objects
look nicer. The script will also work when you leave out both the
lines.
Just before entering the main loop for the experiment we set the
counter variable I
, which will be used as the x-coordinate
when drawing new data points, to 1
, i.e. to represent the
leftmost point in the display area.
After all these preparations the real fun starts. Since we can't know
in advance how many points the user is going to measure be use a
FOREVER
loop, i.e. a loop that will run until the user hits
the Stop
button. All of the test experiment will happen within
this loop.
The first action within the loop is to wait for the lock-in amplifiers time constant. In contrast to the previous script we don't determine the lock-ins time constant once at the start of the experiment but ask the lock-in each time we have to wait. This is necessary because the keyboard of the lock-in is unlocked, so the user can change the time constant whenever he likes, so we have to make sure that we always get the correct time constant.
As you will notice, we also wait even when the acquisition is stopped. This is reasonable because otherwise loop would be repeated extremely fast in paused mode, uselessly eating up computer time.
Unless the acquistion hasn't been stopped (i.e. as long as
Pause_State
is 0
) we now ask the lock-in amplifier for
new data. To be able to display both the x- and y-phase signal
we tell it to return data for both channel 1
and 2
. Of
course, it must return 2 values, which it stores in an array with two
elements, data
. Please note that not all lock-in amplifiers
have two channels, for these we would have to change the script a bit
to fetch and display only one data point.
After having gotten both the new measured data points we draw them in
the display at x-coordinate I
and afterwards we increment
I
to be prepared for the next set of data points.
Before now also stepping the field up or down we first have to check
the state of the sweep buttons - the user may have pushed one of
them. This is what happens in the lines 74 to 89. Checking the state
of a button is done using the same function that already was used to
set the state of a button, button_state()
, but without a
second argument, just with the button handle as the only argument. If
a radio or push button is active it returns a non-zero number,
otherwise 0
.
The first of a series of tests is for the sweep up button. If it is
active but we're not sweeping up (i.e. when Sweep_State
isn't
1
) we'll have to change the Sweep_State
variable to in
accordance with the users request. To also give a visual indication
that the sweep directon changed we then draw a marker at the current
coordinate. This is one via the draw_marker()
function.
It takes two arguments, the x-coordinate and a string for the
color of the marker, and will draw a vertical dashed line in the
selected color.
Of course, if the sweep-up button isn't active we also have to check the sweep-down and the stop-sweep buttons, in exactly the same way as we did for the sweep-up button. When we're done we know into which direction we have to sweep the field (or if we shouldn't do a field step at all).
This brings us to the lines 91 to 109. Here the field is swept (unless the stop-sweep button is active). But we have to be a bit careful: before stepping up or down the field we have to make sure that the new field value is still within the allowed limits for the magnet. (If we don't take care here the script might be stopped by the driver for the magnet - but most drivers will handle the situation more gracefully by simply refusing to sweep the field out of the allowed limits, but you shouldn't count on this.)
If the new field value is still within the allowed range we tell the
magnet to set the new field by calling the function
magnet_field()
with the field to be set as the argument.
It returns the value of the new field, which we store in the
field
variable. When done with this we still have to update the
output field for displaying the current field by calling the
output_value()
function. It takes two arguments, the
handle of the output field we got when it was created and the new
value to be displayed.
But if the new field is not within the magnets limits we have to
automatically stop the sweep and give the user a visual feedback. This
is done by setting the Sweep_State
variable to 0
,
indicating a stopped sweep. By setting the variable we avoid that the
next time we run through the loop the program again will try to change
the field. And to notify the user about this we also activate the
button the user would push for stopping a sweep and draw a marker in
the same color that would be used in this case.
But we're not finished yet. In the mean time the user might have
entered a new field or field step value in one of the input fields. To
be able to figure this out the last known value in these entry fields
had been stored in the variables last_field
and
last_field_step
. To check if something has changed we need to
figure out the current values in the input fields and compare them to
our stored version. If something changed we need to take appropriate
action. This is what happens during lines 111 to 130.
For both the field and the field step size we fetch the current value
by calling input_value()
with just one argument, the
entry fields handle, and store the returned value in a variable,
new_field
and new_field_step
, respectively. Now we can
compare the last recoded value to the current value of the input
field. If they are identical nothing neeeds to be done.
But if they should differ we shouldn't blindly set a new field or
field step size but instead first check if the user supplied value is
reasonable. So we have to compare it to the minimum and maximum field
or field step size. If e.g. the new field value is within the
allowed range we can tell the magnet to move to the requested field
and store it in both the variables field
and last_field
.
Of course, we also have the output field showing the current field. On
the other hand, if the value isn't acceptable we must notify the user
which we do by resetting the input field to its previous value, using
again the input_value()
function.
Now we're nearly done, there only remain two more buttons to be taken
care of. Handling the button for stopping the acquisition is simple -
we just have to get its state and set the Pause_State
variable
accordingly, which is done in line 133.
Also dealing with the button for clearing the display isn't
complicated. First we must determine if it had been clicked on in the
mean time. Again, we can use the button_state()
function.
It will return a non-zero value if the button has been clicked on
since the last invocation of the function for this button, otherwise
0
. (Actually, the value returned by the function is the number
of times the button was clicked on since the last call.)
If the user clicked onto the button we have to clear both the curves
and also remove the markers that might have been drawn, using the
functions clear_curve()
and clear_marker()
(if invoked without an argument the functions remove all curves and
markers, otherwise you would have to supply a list of curve numbers
and marker handles). But we also call the function
rescale()
to reduce the x-scaling to some reasonable
value (here 64
) - if we wouldn't do so and the number of
points displayed before removing the curves was very large, e.g.
10000, the x-scaling would remain set for displaying 10000 points,
which would probably be rather not what the user expects.
Finally, we also have to set the counter variable I
to 1
in order to have the next measured value drawn at the leftmost position
of the display (instead to the right of the deleted curve).
This document was generated by Jens Thoms Toerring on September 6, 2017 using texi2html 1.82.