BLISS: Spec Tutorial CH4 Pseudo Motors and Counters
Pseudo Motors and Counters
This chapter is devoted to one special subject. It will describe how we implement a technique we called pseudo motors (counters) in SPEC. It does require that the reader is familiar with SPEC and its basic commands.
Why use pseudo motors and counters
In the following I will give some examples where you could use pseudo devices to your advantage:
- Standard devices are controlled from the internal "C" code of spec. The config editor in SPEC is used to select a motor controller from a list of these built-in devices. If you would like to add an unknown motor controller to this list, you will have to modify the source code of SPEC. This makes adding a new motor controller inaccessible for the average user of SPEC. There are very good reasons to put these SPEC motor controller drivers into the internal code, but in simple cases (i.e. no interrupts) the pseudo motors allow you to control a motor by writing only macros. You will be able to use this pseudo motor in the same way as a standard motor. You will be able to move it, scan it, read it, and so on with the same standard SPEC macros.
- A pseudo device does not actually have to control a real device. A pseudo counter could be defined for example to hold the detector to monitor ratio. This counter would be displayed in the standard ct command and also included in the scan file.
- A combined movement of many motors could be the reason for a pseudo motor, too. A pseudo motor "energy" could move many motors on a monochromator to align it for a certain energy. The standard SPEC macros could be use to scan this motor, actually move many motors and make an energy scan in this way. Please note that SPEC already includes macros for monochromators in simple cases.
What is the principle mechanism behind a pseudo device
Let's look into the standard count macro "ct" to see the mechanism how to read from a pseudo counter.
def ct '{
waitmove
count_em $*
rdef cleanup \'
undef cleanup
onp; show_cnts; offp
user_handlecounts
\'
waitcount
undef cleanup
onp; show_cnts; offp
user_handlecounts
}'
This macro does mainly the following:
count_em $* # Start all the counters and the timer
waitcount # Wait until the timer is finished
show_cnts # Get the counts from the counters and display them.
The show_cnts macro which is defined as
def show_cnts '{
local i
get_counts
printf("\n%s\n\n", date())
for (i=0;i<COUNTERS;i++)
if (cnt_name(i) != "unused")
printf("%12s = %g%s\n", cnt_name(i), S[i], \
i != sec && S[sec]? sprintf(" (%g/s)", S[i] /
S[sec]):"")
}'
This macro will first read the contents of the scalers / counters and put them in a built-in array S[]. This reading is done in the macro "get_counts". The rest of the macro is used to print the contents of this S[] array on the screen. Let us look at the definition of this get_counts macro and we have reached the point where I would like to start my explanation.
def get_counts '
getcounts
user_getcounts
'
"getcounts" is not a macro but a built-in function which will call the internal "C" code in SPEC to read out the scalers and put there contents to the S[] array. The macro user_getcounts has been added to SPEC for our purpose of pseudo counters. It's definition is initially empty and will therefore do nothing. But we can define it, to load the S[] array with any value we want. We can get this value by reading some hardware at that point or just by doing some calculation.
We could define the macro user_getcounts in the following way:
def user_getcounts 'S[detmon] = S[det] / S[mon]'
and define a new counter with the mnemonic "detmon" with the config editor.
Scaler (Counter) Configuration
Number Name Mnemonic <>Device Unit Chan <>Use As Scale Factor
0 Seconds sec VCT6 0 1 timebase 1e+06
1 Monitor mon VCT6 0 2 monitor 1
2 Detector det VCT6 0 3 counter 1
3 DetMon detmon NONE 0 4 counter 1
We entered "NONE" as a device type. Therefore the internal C code of SPEC will not put a value in the array element S[detmon]. On the other hand our definition of user_getcounts will do that. It will divide the count value of the detector by the count value of the monitor and put the result in S[detmon]. Therefore we arrive at the following result:
251.SPEC> ct 1
Wed Oct 1 11:10:09 1997
Seconds = 1
Monitor = 1000 (1000/s)
Detector = 23456 (23456/s)
DetMon = 23.456 (23.456/s)
Cdef - or more secrets
In the above examples we defined a special macro (user_getcounts) for our purpose of pseudo counters / motors. There is a problem related to this definition. We only have one special macro user_getcounts and if more one person defined this macro for his purpose, he would delete the previous definition from somebody else. Therefore a new command called cdef for "chained definition" had been created to allow the definition of really general macro sets. The cdef command can be used in other circumstances but it is specially useful to define pseudo devices.
cdef(macro, string, key, flag)
The argument macro is the name of the macro. The argument string contains a piece to add to the macro.With the optional key argument, the pieces can be selectively replaced or deleted. The optional argument flag adds some additional functionality which will be explained later.
Let's have a look into an easy example:
cdef("testmacro", "print \"hallo\" ; ", "mykey")
This macro will add to the macro testmacro the string <print "hello";>. As the macro was empty before, it will now contain:
def testmacro 'print "hallo" ; '
This part of the macro can be changed or deleted now. As there might be many different parts of a macro we need a way to identify the part we would like to work with. This identifier is the string "mykey". If we would like to change the part we could write simply:
cdef("testmacro", "print \"hallo for the second time\" ; ", "mykey")
and the macro testmacro contains now:
def testmacro 'print "hallo for the second time"; '
In this case there is no evident difference between the cdef and the def command. To add another part to the macro we will have to use a new key.
cdef("testmacro", "print \"hallo for another time\" ; ", "mynewkey")
The macro testmacro contains now both parts - the one defined with the key "mykey" and the other defined with the key "mynewkey". You have access to both parts by using the right key - the right handle.
def testmacro 'print "hallo for the second time" ; print "hallo for another time" ; '
I would like to point you to two extra things in these examples. First, we had to escape the double quotes in "print \"hallo for the second time\" ". The \" will put a literal " in the string. Second, it is very important to realize the significance of the semi-colon after the print statement. Both of our strings are just concationated together to form the macro definition. The cdef function does not understand the meaning of the macro, it will just add both strings. But as soon as you try to execute the macro "testmacro", you will create a syntax error. This is simply because the syntax of the command
262.SPEC> print "hallo for the second time" print "hallo for another time"
print "hallo for the second time" print "hallo for another time"
^
Syntax error on "print"
is wrong. Therefore, do not forget to end your definition string in a semi-colon ";" or a newline "\n".
You can list all the chained definitions with the command:
cdef("?")
In our example this will result in:
263.SPEC> cdef("?")
testmacro:
0x000 mykey 'print "hallo for the second time" ; '
0x000 mynewkey 'print "hallo for another time" ; '
and some more output depending on the other chained definitions in your SPEC session.
Cdef with flags - or even more secrets
In the last paragraph you saw how you could add actions to a macro without deleting the old contents. In this section we will talk about another handy feature of the cdef command, which will allow us to write "macro code" for a pseudo counter which will be automatically disabled as soon as this pseudo counter is deleted from the SPEC config file and enabled again if it is put back. This is the explanation from the SPEC manual (see help funcs on-line):
The flags argument controls whether the pieces are added to the front or to the back of the macro or whether the pieces should be selectively included in the definition based on whether key is a currently configured motor or counter mnemonic. The bit meanings for flags are as follows:
0x01 - only include if key is a motor mnemonic and the motor is not disabled.
0x02 - only include if key is a counter mnemonic and the counteris not disabled.
0x10 - place in the front part of the macro.
0x20 - place in the back part of the macro.
If flag is the string "delete", the piece associated with key is deleted from the named macro, or if the name is the null string, from all the chained macros. If key is the null string, the flags have no effect.
Let's see this working in our example:
286.SPEC> cdef("user_getcounts", "S[mycnt] = 45; ", "mycnt", 0x2)
287.SPEC> prdef user_getcounts
def user_getcounts ''
The meaning of the cdef command is the following. Add to the macro user_getcounts the code "S[mycnt]=45;". This part of the code would create a run time error if the counter mycnt is not defined (or even worse, it might just use the counter 0 - normally the timer). Therefore we instructed the cdef function to not put this part into the macro if the counter with mnemonic mycnt does not exist. We did that by giving the counter mnemonic as a key and told the function that the key is a counter mnemonic with the flag 0x02. The definition of user_getcounts is empty as mycnt is currently not a counter mnemonic. If we define a counter with the mnemonic "mycnt" with the SPEC config editor:
Scaler (Counter) Configuration
Number Name Mnemonic <>Device Unit Chan <>Use As Scale Factor
0 Seconds sec SFTWARE 0 1 timebase 1e+06
1 Monitor mon NONE 0 2 monitor 1
2 Detector det NONE 0 3 counter 1
3 DetMon mycnt NONE 0 4 counter 1
our definition will appear in the macro user_getcounts.
def user_getcounts 'S[mycnt] = 45; '
You saw that this functionality had nothing to do with the macro user_getcounts nor the device type "NONE" in the config editor. It is a simple feature of the cdef command.
Example Pseudo Devices
Pseudo Counter
The simplest pseudo device is a pseudo counter as it is only necessary to modify one macro. You saw in the previous chapters examples for pseudo counters. The following example is a short but working macro set to define an incremental encoder controlled by a CC133 card and the cc133 device server as a pseudo counter. This can be used to scan a motor and watch the corresponding encoder to check if the response is linear. The example has been extracted from the macro set cc133.mac in the general distribution. To see other examples you could look into k2001.mac, adc.mac, multim.mac, maxeenc.mac.
#%UU% <pseudo_counter> <device_name> [<scale_factor>]
#%MDESC%
# Associates the encoder <device_name> to <pseudo_counter>.
# If scale factor is not explicitly specified, unity is assumed.
def cc133setup '{
cdef("user_getcounts","cc133_getcounts $*\n","$1",0x02)
'
#%IU% <pseudo_counter> <device_name> [<scale_factor>]
#%MDESC%
# Reads the current encoder position and stores it in the corresponding
# global variable.
#
def cc133_getcounts '
S[$1] = esrf_io("$2","DevReadCount") * (($# == 3)?$3:1)
'
The macro cc133_getcounts has to be called with 2 or three parameters. The first parameter is the device name of the cc133 device. The command esrf_io("$2","DevReadCount") will read the current encoder value from the device server. The macro gets the counter mnemonic as the first parameter. As already stated on previous pages, the counter mnemonic is a built-in variable for SPEC which contains the sequential number of the counter in the config file. For example if you have a counter with mnemonic "det" defined as third entry in the config file you will get:
SPEC> p det
2
You see that the macro cc133_getcounts sets the element S[2] of the built-in array S[] to the value read from the device server.
The macro cc133setup called with 2 or 3 parameters will add the macro cc133_getcounts to the macro user_getcounts. It will also add the parameters given in the command line by the user.
Pseudo motors
SPEC's internal macros communicate with the built-in driver code with the built-in array A[]. The A[] array is the counterpart of the S[] array which we encountered in the pseudo counter section. In order to read positions one calls the macro get_angles. This will fill the A[] array with the current motor positions. This is a simplified version of the wa macro:
def wa '
waitmove; get_angles
printf("\nCurrent Positions (user, dial)\n")
{
local i
for (i = 0; i < MOTORS; i ++) {
print motor_name(i), A[i]
}
}
'
To move motors to a certain position you have to fill the A[] array with these positions and call the macro move_em. This is for example a simplified version of the standard "mv" command:
def mv '
if ($# != 2) {
print "Usage: mv motor position"
exit
}
waitmove; get_angles; A[$1]=$2
move_em
'
The macro get_angles calls the user macro user_getpangles and the macro move_em calls the macros user_checkall before and user_moveall after starting the internal motors of SPEC.
A very simple example of a pseudo motor is our piezo pseudo motor. It is just controlled from a digital to analog converter (dac). You move the piezo by setting a value in the device server for the dac. For most dacs you can read this value back. This pseudo motor is so simple because you do not have to wait for it to move. (applying a voltage is done in almost no time). Therefore you do not have write macros to wait for the motor nor any special macros to abort the move when the user types ^c. The following code is extracted of the macro set piezo.mac:
#%UU% [No of piezos] [piezo1 motor name] [piezo1 device name] ...
#%MDESC%
# ... [piezo n motor name] [piezo n device name]
# Sets up the piezo motor. You have to do that before
# you can use the piezo.
def piezosetup '{
global PIEZO_NO PIEZO_DEV PIEZO_MNE
local _pmne _pdev
_pdev[0]="$3" ; _pdev[1]="$5" ; _pdev[2]="$7" ; _pdev[3]="$9" ; _pdev[4]="$11"
_pmne[0]="$2" ; _pmne[1]="$4" ; _pmne[2]="$6" ; _pmne[3]="$8" ; _pmne[4]="$10"
if ($#) {
PIEZO_NO = $1
for (i=0; i< PIEZO_NO; i++) {
PIEZO_MNE[i]=_pmne[i]
PIEZO_DEV[i]=_pdev[i]
}
} else {
PIEZO_NO = getval("How many piezos do you want to use",PIEZO_NO)
for (i=0; i< PIEZO_NO; i++) {
PIEZO_MNE[i] = getval(sprintf("Mne for piezo %d",i+1),PIEZO_MNE[i])
PIEZO_DEV[i] = getval(sprintf("DAC device for piezo %d ",i+1), PIEZO_DEV[i])
}
}
piezo_def
}'
#%IU%
#%MDESC% Defines pseudo motors from global PIEZO variables
def piezo_def '{
for (i=0;i<PIEZO_NO; i++) {
cdef ("user_getpangles, sprintf("_piezogetangles %s ;", PIEZO_DEV[i]),PIEZO_MNE[i],0x02)
cdef ("user_moveall, sprintf("_piezomove %s ;", PIEZO_DEV[i]), PIEZO_MNE[i],0x02)
}
} '
def _piezomove '
esrf_io("$2","DevSetValue",A[$1])
'
def _piezogetangles '
A[$1]=esrf_io("$2","DevReadValue")
'
The actual commands for the pseudo motors are sent from the two macros _piezomove and _piezogetangles. The macro piezo_def is called to add them to the user macros user_getpangles and user_moveall.
Where to put my macro code
If you want to fully understand which macro to cdef for which functionality of a pseudo counter, you will have to look carefully through the definition of every SPEC macro. To make this task easier I will provide a list of macros, where you should put your code.
Motors:
|
TABLE 1. |
|
|
Hook macro |
Meaning |
|
user_getpangles |
You should create a macro which reads the position of your motor. The result has to be put in the A[] array for your motor. The command line to put a value from a device in the pseudo motor with mne "dac" could read: A[dac] = gpib_get(23) |
|
user_checkall |
Called before the actual move is started. You can implement some special limit conditions in this macro for all the motors. Something like: Do not move motor a if motor b is below 7. This macro can also be used to make SPEC's built in motors move by writing values to the A[] array. Be careful about soft limits. These are checked in the built in function move_all which is called later. |
|
user_moveall |
This is the point where you can check if you have to move your pseudo motor (you can do that by comparing the value in the A[] array with an old saved value or by using the macro "pseudouseactive" which after issued once will set the global array PSEUDO_ACTIVE[] to one for all the motors which have to be moved.) At this time you should move the position of the actual motor to the position indicated in the A[] array. |
|
user_motorsrun |
You have to define this macro to return 1 if your pseudo motor is still moving. The part of the macro which does that could be. if (my_motor_still_moves()) return 1 |
|
user_m_cleanup |
If a user hits ^c while the motors are moving, this macro is called. The standard behaviour is to immediatly stop the motor. Test this part of your macro with great care. Built in "C" code is much more reliable. One problem is that SPEC deletes this built in macros if there is a problem during the execution of the macro (Syntax error, another ^c). |
|
user_set |
Called inside the set macro. The motor in the variable "motor2set" should be set to the user position from the variable "position2set". |
|
user_finished1 |
This macro is called when all the motors stopped moving. This macro is called from most of the standard macro. The user might have written macros which dont call this macro. Be careful. |
Counters:
|
TABLE 2. |
|
|
Hook macro |
Meaning |
|
user_c_cleanup |
If a user hits ^c while the counters are counting, this macro is called. |
|
user_getcounts |
You have to fill the build-in S[] array with the values for your pseudo counter. |
|
user_prepcount |
Actions to take before all the SPEC internal counters are started. If you would like to arm a counter for example. |
|
user_postcount |
Actions to take after the internal counters are started. |
|
user_countersrun |
If your counter is still counting and you would like the SPEC macros to wait for it you will have to write some code which returns 1. This code fragment could look like: if (check_my_counter()) return 1 |
|
user_handlecounts |
This macro is called after ct. You can use it to save information after a count for example. |
Scanning:
|
TABLE 3. |
|
|
Hook macro |
Meaning |
|
measure0 |
This macro is called before the count is done in a normal scan. |
|
measure1 |
This macro is called after the count is done. It is tradidionally defined as measuretemp. Therefore you better use measure2 for the exact same purpose. |
|
measure2 |
This macro is called after the count is done in a normal step scan. |
File handling:
|
TABLE 4. |
|
|
Hook macro |
Meaning |
|
Fheader |
Everything printed in this macro is put on top of every scan in the standard scan file. |
|
Flabel |
Flabel is used to add a new label to the #L line (which holds the labels for the columns in a scan) Flabel is used in the following line: printf("#L %s%sEpoch",FPRNT,Flabel) Therefore it is not simply possible to use this macro independently. The idea was to define Flabel as something like: def Flabel '"DegC " |
|
Fout |
This macro is called for every scan point. It can be used to add an additional column to the scan file. In Flabel one adds the label for that column and in Fout one has to print the data for every scan point in the data file. An example could be: def Fout 'sprintf("%g ",DEGC)' |
|
Ftail |
Everything printed from this macro is appended to each scan. One example of usage is to put the plot results into the scan file with the definition: def Ftail ' printf("#R %d %g %g %g %g %g %g\n", \ SCAN_N, pl_xMAX, pl_MAX, pl_FWHM, pl_CWHM, pl_COM, pl_SUM);' |
These macros have their counterpart for the printer in the macros Pheader, Plabel, Pout and Ptail. They were used to add another column to the data file without adding a pseudo counter in the config editor. They will be changed in the near future.
def Fheader '_cols++;printf("#X %gKohm (%gC)\n", TEMP_SP,DEGC_SP)'
def Flabel '"DegC "'
def Fout 'sprintf("%g ",DEGC)'
General:
|
TABLE 5. |
|
|
Hook macro |
Meaning |
|
user_cleanup2 |
Every time a user hits ^c this macro is called. |
|
prompt_mac |
Called at every SPEC prompt. |
|
begin_mac |
Called when SPEC starts up. |
|
config_mac |
Called after every config, reconfig and program start. |
|
end_mac |
Called when you quit SPEC |
Be careful as these built-in macros are deleted under some circumstances. If for example the prompt_mac contains a syntax error it will be deleted to allow SPEC to continue its operation. Another common problem is that the standard SPEC cleanup macros (cleanup and cleanup1) are deleted if the user hits ^c twice.
All the macros mentioned in this paragraph are either standard SPEC macros or defined in the special ESRF file pseudo.mac. Look into that macro file for more information.
Some style questions and warnings
Be careful with pseudo devices:
- ATTENTION with cdef. cdef resembles a lot the old programming technique of self modifying code. It can make your macros completely unreadable. Use the cdef command only in the cases discussed above.
- ATTENTION with pseudo devices controlling real hardware. There are good reasons to put device drivers in c-code and not in macros. The c-code will be much more robust. Specially to stop the motion on a ^c does not work 100% reliable with macros. Also pseudo motors are more difficult to se then normal built-in motors.
- ATTENTION: Your macro code for pseudo motors is called for every move. It is your responsibility not to move the pseudo motor if the user did not want to move it and it is also your responsibility not to move another motor. Users tend to think that it is impossible to move motor Y if they type a move command for motor X. An error in your macro can make this happen.
Some recommendations:
- When you define your macros to read your pseudo device, remember that is called on every read. SPEC internally only reads the position of active motors (an active motor is one which is still moving). You might want to mimic this behaviour in your macros and read the position only once after the move has stopped. SPEC can be forced to read all the position with the sync command. A future plan is to implement a user_sync macro, which is called when SPEC's sync function is called. This could be used for that purpose.
- Keep it simple. Pseudo device code is inserted in a very low level of the SPEC standard macros. A macro might contain many scan commands, which contain many calls to your pseudo device macros. In this way your code could be expanded by a factor as large as 1000. Use therefore macro functions wherever possible - but as always "Keep it simple".
A macro set for pseudo devices should contain:
xxxsetup: A macro which accepts either input from the command line or asks the user (The user might want to enter information like the device name, the mnemonic of the pseudo device, the channel,.)
xxxunsetup: This macro should undo all the effects of the setup macro. To automatically call this unsetup macro when the user deletes (or comments) the xxxsetup line in the setup file you have to call
setup_tail(prefix,parameters)
The prefix will be "xxx" to lead to xxxunsetup and the parameters are any parameters you would like to pass to the macro xxxunsetup. The setup functionality will be explained in another document.You can see the documentation for the macro file stlocal.mac for some hints.
xxxon / xxxoff: The pseudo device slows down very often the execution of the standard SPEC commands. To read the undulator gap might take some seconds at every point in the scan. The user must have therefore have a possibility to switch the pseudo motor for the undulator gap on and off. The macros to implement this are normally called xxxon and xxxoff. It should be also possible to switch them on and off with the standard beam line menu. For a description of the standard beam line menu see the documentation for the macro file blmenu.mac)
Debugging
To debug pseudo device code might be tidies.
- You can have a look into the definition of the corresponding user hook macro (for example user_getcounts if the problem is that a certain detector is not read)
- If the pseudo macro definition is not in the user hook macro, you can use cdef("?") to see if it has been defined with cdef already.
- If the macro is in the user hook macro you can use debug 192 to see the hardware accesses.
- You can have a look into the corresponding xxxsetup routine if all the parameters are entered correctly.
- If everything works if you call the user hook macro directly on the command line, you might verify that the user hook macro is really called from the macro the user typed. Forgetting to load the macro set pseudo.mac might result in these funny errors.
Some advanced issues
Enabling or disabling motors or counters
"cdef" definition can be temporarily disabled with the command
cdef ("","",key","disable")
To enable them again you can use
cdef ("","",key","disable")
This is useful for cdef-definitions which have been defined without the motor or counter flag. cdef-definitions with the counter flag set (0x02) will get automatically disabled/enabled, when the corresponding counter gets disabled with:
counter_par(mne, "disable",1)
or enabled with:
counter_par (mne, "disable", 0)
The corresponding commands for motors are:
motor_par(mne, "disable", 0 / 1)
Getting parameters from the config file
You can get some parameters from the config file and use them in you macros. The spec functions to call will be motor_par(), counter_par(), mca_par(), or image_par() depending on the device. There are many parameters for these functions, some of which are listed in the following tables:
|
TABLE 6. xxx_par function |
|
|
|
|
|
motor_par(mne,"device_id"), counter_par(mne,"device_id"), mca_par(mne,"device_id"), image_par(mne,"device_id") |
Reads the device identity of a device. This can be a serial device name, a gpib device name or a device name in the ESRF device server concept. |
|
motor_par(mne,"controller"), counter_par(mne,"controller"), mca_par(mne,"controller"), image_par(mne,"controller") |
Tells you the device type of a device.. An example is "MAXE". |
|
_par(mne,"channel") |
Tells you the channel of a device (starts with 0). This should represent the actual hardware channel on a certain controller card, but with the device abstraction of the device servers it does not have to be. |
|
_par(mne,"unit") |
If you have multiple units of the same controller defined (for example 3 MAXE controllers), this function will tell you to which unit this device belongs. The unit numbers start a 0. |
|
motor_par - parameters: "home_slew_rate", "home_base_rate", "home_acceleration", "dc_dead_band", "dc_settle_time", "dc_gain", "dc_dynamic_gain", "dc_damping_constant", "dc_integration_constant", "dc_integration_limit", "dc_following_error", "dc_sampling_interval", "encoder_step_size", "step_mode", "slop", "deceleration", "torque", "misc_par_1", "misc_par_2", "misc_par_3", "misc_par_4", "misc_par_5", "misc_par_6", |
See the corresponding parameters in the config editor for motors. |
|
counter_par functions: "scale", "monitor", "timer" |
You can get information about the current monitor and timer channel. You can also use the scale parameter from the config editor. |