Details about the implementation of the MultipleScannerControl move
Similarly to the case of the PLcounter, we need to use several channels together to make the scanner work, a clock and an analog output. In principle, we can use the same trick as for the PLcounter and define the controller as a dict containing 2 DAQmx
objects. This is what is done in the ScannerControl plugin, but its means that you need one clock channel for each analog output channel, so if you have many scanners, it will very quickly become an issue. In addition, with a scanner, you most probably want to use the Scan extension. For each movement, the extension sends the command to move to every actuator roughly simultaneously: if your scanners share the same clock, you will get into trouble.
Introduction of a new object to use as controller
The solution proposed with the MultipleScannerControl plugin is to add an additional object to handle the synchronisation of the different scanners. This object is the AO_with_clock_DAQmx
which is defined in the file daqmx_objects.py
, which is used as controller instead of the DAQmx
.
The AO_with_clock_DAQmx
has 2 attributes, clock
and analog
which are DAQmx
objects, each of them containing, as usual, a single task.
The clock will thus be shared by all the scanners connected to the AO_with_clock_DAQmx
controller. The parameters of the clock can thus only be modified in the settings of the Master scanner.
The analog DAQmx object has an AnalogOutput task with several output channels, one for each scanner. The list of these channels is stored in the attribute AOchannels
As a result, the list of positions sent by each scanner plugins to describe the movement have to be reformatted, as values have to be sent to all the channels. For exemple, to move the X scanner from 1 to 5 µm in steps of 1 µm, the plugins sends the list [1000, 2000, 3000, 4000, 5000]
, but if a Y scanner is also connected to the AO_with_clock_DAQmx
, it needs to be reformatted to [[1000, 2000, 3000, 4000, 5000], [current_Y_position, current_Y_position, current_Y_position, current_Y_position, current_Y_position]]
. This reformatting is done by the AO_with_clock_DAQmx
controller.
Handling of the timing
Having a single controller object allows performing the movements of each scanners one after the other, without conflicts in the use of the clock counters by the different scanners. This is achieved by locking the AO_with_clock_DAQmx
controller during the movement of any scanner. When a move command is sent, the locked
attribute of the controller is set to True
.
As long as this variable is set to True
, no other movement is started. If another scanner gets a command for moving, then it is set to a waiting state (the attribute waiting_to_move
from the scanner plugin is set to True
) until the signal ni_card_ready_for_moving
is received from the controller indicating that the movement can resume.
When the movement is over, the scanner plugin received a move_done_signal
from PymoDAQ, which is emitted when the current position is close enough to the current position. This signal is connected to the AO_with_clock_DAQmx
controller, which switches to the unlocked state (locked
attribute set back to False
) and emits the signal ni_card_ready_for_moving
.
Reading the scanner position
Another issue with using a NI card to pilot a piezo scanner is that one cannot read the current voltage applied at an analog output. In order to know the current position of the scanner, the solution used here is to get the current index of the clock from the NI card, and then display the corresponding position in the list that was sent to the device.
As a consequence, the position is set to 1 nm when restarting the plugin, as the current position cannot be read from the hardware. This might cause quick movements, so be careful to go back to zero before closing the plugin.