Ejemplo n.º 1
0
class Nifpga(Base, LaserControlInterface, FPGAInterface):
    """ National Instruments FPGA that controls the lasers via an OTF.

    Example config for copy-paste:
        nifpga:
            module.Class: 'fpga.ni_fpga.Nifpga'
            resource: 'RIO0'
            default_bitfile: 'C:\\Users\\sCMOS-1\\qudi-cbs\\hardware\\fpga\\FPGA\\FPGA Bitfiles\\FPGAv0_FPGATarget_FPGAlasercontrol_mLrb7Qjptmw.lvbitx'
            wavelengths:
                - '405 nm'
                - '488 nm'
                - '561 nm'
                - '640 nm'
            registers:
                - '405'
                - '488'
                - '561'
                - '640'
            registers_qpd:
                - 'x'
                - 'y'
                - 'i'

            # registers represent something like the channels.
            # The link between registers and the physical channel is made in the labview file from which the bitfile is generated.
    """
    # config
    resource = ConfigOption('resource', None, missing='error')
    default_bitfile = ConfigOption('default_bitfile', None, missing='error')
    _wavelengths = ConfigOption('wavelengths', None, missing='warn')
    _registers_laser = ConfigOption('registers_laser', None, missing='warn')
    _registers_qpd = ConfigOption('registers_qpd', None, missing='warn')
    _registers_autofocus = ConfigOption('registers_autofocus',
                                        None,
                                        missing='warn')
    _registers_general = ConfigOption('registers_general',
                                      None,
                                      missing='warn')

    def __init__(self, config, **kwargs):
        super().__init__(config=config, **kwargs)

    def on_activate(self):
        """ Required initialization steps when module is called."""
        self.session = Session(bitfile=self.default_bitfile,
                               resource=self.resource)
        # self.laser1_control = self.session.registers[self._registers[0]]
        # self.laser2_control = self.session.registers[self._registers[1]]
        # self.laser3_control = self.session.registers[self._registers[2]]
        # self.laser4_control = self.session.registers[self._registers[3]]
        # # maybe think of replacing the hardcoded version of assigning the registers to an identifier by something more dynamic
        # self.session.reset()
        # for i in range(len(self._registers)):
        #     self.apply_voltage(0, self._registers[i])  # set initial value to each channel
        #
        # self.QPD_X_read = self.session.registers[self._registers_qpd[0]]
        # self.QPD_Y_read = self.session.registers[self._registers_qpd[1]]
        # self.QPD_I_read = self.session.registers[self._registers_qpd[2]]
        # self.Integration_time_us = self.session.registers[self._registers_qpd[3]]
        # self.Duration_ms = self.session.registers[self._registers_qpd[4]]
        # self.Task = self.session.registers[self._registers_qpd[5]]
        #
        # self.Task.write(False)
        # self.session.run()

        # Initialize registers dictionnary according to the type of experiment selected

        if self._wavelengths is not None:

            self.laser1_control = self.session.registers[
                self._registers_laser[0]]
            self.laser2_control = self.session.registers[
                self._registers_laser[1]]
            self.laser3_control = self.session.registers[
                self._registers_laser[2]]
            self.laser4_control = self.session.registers[
                self._registers_laser[3]]
            self.update = self.session.registers[self._registers_laser[4]]
            self.session.reset()
            for i in range(len(self._registers_laser) - 1):
                self.apply_voltage(0, self._registers_laser[i]
                                   )  # set initial value to each channel

        if self._registers_qpd is not None:

            self.QPD_X_read = self.session.registers[self._registers_qpd[0]]
            self.QPD_Y_read = self.session.registers[self._registers_qpd[1]]
            self.QPD_I_read = self.session.registers[self._registers_qpd[2]]
            self.Counter = self.session.registers[self._registers_qpd[3]]
            self.Duration_ms = self.session.registers[self._registers_qpd[4]]

            self.Stop = self.session.registers[self._registers_general[0]]
            self.Integration_time_us = self.session.registers[
                self._registers_general[1]]
            self.Reset_counter = self.session.registers[
                self._registers_general[2]]

            self.setpoint = self.session.registers[
                self._registers_autofocus[0]]
            self.P = self.session.registers[self._registers_autofocus[1]]
            self.I = self.session.registers[self._registers_autofocus[2]]
            self.reset = self.session.registers[self._registers_autofocus[3]]
            self.autofocus = self.session.registers[
                self._registers_autofocus[4]]
            self.ref_axis = self.session.registers[
                self._registers_autofocus[5]]
            self.output = self.session.registers[self._registers_autofocus[6]]

            self.Stop.write(False)
            self.Integration_time_us.write(10)
        self.session.run()

    def on_deactivate(self):
        """ Required deactivation steps. """
        for i in range(len(self._registers_laser) - 1):
            self.apply_voltage(
                0, self._registers_laser[i]
            )  # make sure to switch the lasers off before closing the session

        self.Stop.write(True)
        self.session.close()

    def read_qpd(self):
        """ read QPD signal and return a list containing the X,Y position of the spot, the SUM signal,
        the number of counts (iterations) since the session was launched and the duration of each iteration
        """
        X = self.QPD_X_read.read()
        Y = self.QPD_Y_read.read()
        I = self.QPD_I_read.read()
        count = self.Counter.read()
        d = self.Duration_ms.read()

        return [X, Y, I, count, d]

    def reset_qpd_counter(self):
        self.Reset_counter.write(True)

    def update_pid_gains(self, p_gain, i_gain):
        self.P.write(p_gain)
        self.I.write(i_gain)

    def init_pid(self, p_gain, i_gain, setpoint, ref_axis):
        self.reset_qpd_counter()
        self.setpoint.write(setpoint)
        self.P.write(p_gain)
        self.I.write(i_gain)
        if ref_axis == 'X':
            self.ref_axis.write(True)
        elif ref_axis == 'Y':
            self.ref_axis.write(False)
        self.reset.write(True)
        self.autofocus.write(True)
        sleep(0.1)
        self.reset.write(False)

    def read_pid(self):
        pid_output = self.output.read()
        return pid_output

    def stop_pid(self):
        self.autofocus.write(False)

    def apply_voltage(self, voltage, channel):
        """ Writes a voltage to the specified channel.

        @param: any numeric type, (recommended int) voltage: percent of maximal volts to be applied

        if value < 0 or value > 100, value will be rescaled to be in the allowed range

        @param: str channel: register name corresponding to the physical channel (link made in labview bitfile), example '405'

        @returns: None
        """
        # maybe think of replacing the hardcoded version of comparing channels with registers by something more dynamic
        value = max(0, voltage)
        conv_value = self.convert_value(value)
        if channel == self._registers_laser[0]:  # '405'
            self.laser1_control.write(conv_value)
        elif channel == self._registers_laser[1]:  # '488'
            self.laser2_control.write(conv_value)
        elif channel == self._registers_laser[2]:  # '561'
            self.laser3_control.write(conv_value)
        elif channel == self._registers_laser[3]:  # '640'
            self.laser4_control.write(conv_value)
        else:
            pass
        self.update.write(True)

    def convert_value(self, value):
        """ helper function: fpga needs int16 (-32768 to + 32767) data format: do rescaling of value to apply in percent of max value

        apply min function to limit the allowed range """
        return min(int(value / 100 * (2**15 - 1)),
                   32767)  # set to maximum in case value > 100

    def read_values(self):
        """ for tests - returns the (converted) values applied to the registers """
        return self.laser1_control.read(), self.laser2_control.read(
        ), self.laser3_control.read(), self.laser4_control.read()

    def get_dict(self):
        """ Retrieves the register name (and the corresponding voltage range???) for each analog output from the
        configuration file and associates it to the laser wavelength which is controlled by this channel.

        @returns: laser_dict
        """
        laser_dict = {}

        for i, item in enumerate(
                self._wavelengths
        ):  # use any of the lists retrieved as config option, just to have an index variable
            label = 'laser{}'.format(
                i + 1
            )  # create a label for the i's element in the list starting from 'laser1'

            dic_entry = {
                'label': label,
                'wavelength': self._wavelengths[i],
                'channel': self._registers_laser[i]
            }
            # 'ao_voltage_range': self._ao_voltage_ranges[i]

            laser_dict[dic_entry['label']] = dic_entry

        return laser_dict

    ### new 3 march 2021 test with tasks
    ## these methods must be callable from the lasercontrol logic
    def close_default_session(self):
        """ This method is called before another bitfile than the default one shall be loaded

        (in this version it actually does the same as on_deactivate (we could also just call this method ..  but this might evolve)
        """
        for i in range(len(self._registers_laser)):
            self.apply_voltage(
                0, self._registers_laser[i]
            )  # make sure to switch the lasers off before closing the session
        self.session.close()

    def restart_default_session(self):
        """ This method allows to restart the default session"""
        self.on_activate()
        #or is it sufficient to just call         self.session = Session(bitfile=self.default_bitfile, resource=self.resource) and session run ?

    def start_task_session(self, bitfile):
        """ loads a bitfile used for a specific task """
        self.session = Session(bitfile=bitfile, resource=self.resource)

    def end_task_session(self):
        self.session.close()

    # methods associated to a specific bitfile
    def run_test_task_session(self, data):
        """
        associated bitfile 'C:\\Users\\sCMOS-1\\qudi-cbs\\hardware\\fpga\\FPGA\\FPGA Bitfiles\\FPGAv0_FPGATarget_FPGAlasercontrol_pdDEc3yii+w.lvbitx'
        @param: list data: values to be applied to the output (in % of max intensity) """
        #using for a simple test the FPGA_laser_control_Qudi bitfile (control only for the 561 nm laser)
        n_lines = self.session.registers['N']
        laser_control = self.session.registers['561 Laser Power']
        self.session.reset()

        print(n_lines.read())
        n_lines.write(5)
        print(n_lines.read())

        conv_values = [self.convert_value(item) for item in data]
        print(conv_values)
        laser_control.write(conv_values)
        self.session.run()

    def run_multicolor_imaging_task_session(self, z_planes, wavelength, values,
                                            num_laserlines, exposure_time_ms):
        """
        associated bitfile 'C:\\Users\\sCMOS-1\\qudi-cbs\\hardware\\fpga\\FPGA\\FPGA Bitfiles\\FPGAv0_FPGATarget_FPGAtriggercamer_u12WjFsC0U8.lvbitx'
        @param: int z_planes: number of z planes
        @param: int list wavelength: list containing the number of the laser line to be addressed: 0: BF, 1: 405, 2: 488, 3: 561, 4: 640
        @param: int list values: intensity in per cent to be applied to the line given at the same index in the wavelength list
        """
        num_lines = self.session.registers[
            'N laser lines']  # number of laser lines
        num_z_pos = self.session.registers[
            'N Z positions']  # number of z positions
        num_images_acquired = self.session.registers[
            'Images acquired']  # indicator register how many images have been acquired
        laser_lines = self.session.registers[
            'Laser lines']  # list containing numbers of the laser lines which should be addressed
        laser_power = self.session.registers[
            'Laser power']  # list containing the intensity in % to apply (to the element at the same index in laser_lines list
        stop = self.session.registers['stop']
        exposure = self.session.registers[
            'exposure_time_ms']  # integer indicating the exposure time of the camera in ms

        self.QPD_X_read = self.session.registers[self._registers_qpd[0]]
        self.QPD_Y_read = self.session.registers[self._registers_qpd[1]]
        self.QPD_I_read = self.session.registers[self._registers_qpd[2]]
        self.Counter = self.session.registers[self._registers_qpd[3]]
        self.Duration_ms = self.session.registers[self._registers_qpd[4]]

        self.Stop = self.session.registers[self._registers_general[0]]
        self.Integration_time_us = self.session.registers[
            self._registers_general[1]]
        self.Reset_counter = self.session.registers[self._registers_general[2]]

        self.setpoint = self.session.registers[self._registers_autofocus[0]]
        self.P = self.session.registers[self._registers_autofocus[1]]
        self.I = self.session.registers[self._registers_autofocus[2]]
        self.reset = self.session.registers[self._registers_autofocus[3]]
        self.autofocus = self.session.registers[self._registers_autofocus[4]]
        self.ref_axis = self.session.registers[self._registers_autofocus[5]]
        self.output = self.session.registers[self._registers_autofocus[6]]

        self.Stop.write(False)
        self.Integration_time_us.write(10)

        self.session.reset()

        conv_values = [self.convert_value(item) for item in values]
        num_lines.write(num_laserlines)
        print(num_laserlines)
        num_z_pos.write(z_planes)
        print(z_planes)
        laser_lines.write(wavelength)
        print(wavelength)
        laser_power.write(conv_values)
        print(conv_values)
        stop.write(False)
        print("exposure time = " + str(exposure_time_ms))
        exposure_time_ms = int(exposure_time_ms * 1000 * 2)
        exposure.write(exposure_time_ms)

        self.session.run()  #  wait_until_done=True
        num_imgs = num_images_acquired.read()
        print(f'number images acquired: {num_imgs}')
import os
import sys
import numpy as np
from nifpga import Session

from Calibration.Encoder import *
from ProofSignals.Signals import *

with Session("Mybitfile.lvbitx", "rio://172.22.11.2/RIO0") as Session:
    Session.reset()
    Session.run()

    qd = Session.registers['qd']
    qr = Session.registers['qr']
    ResetPID = Session.registers['Reset PID']
    ResetEncoder = Session.registers['Reset Enc']
    PythonMode = Session.registers['Python Mode']

    # PID Gains for Python Mode
    P = Session.registers['P']
    I = Session.registers['I']
    D = Session.registers['D']

    # Gains
    PythonMode.write(True)
    P.write(np.uint16(15))
    I.write(np.uint16(0))
    D.write(np.uint16(8))

    print("Simple Pendulum by means an FPGA")
class VeriStandFPGA(object):
    """
    DMA FIFO info pulled from an .fpgaconfig file
    config_file is the file name of the XML that defines this FIFO
    Direction is a string stating read or write. 

    """
    def __init__(self, filepath):
        """
        Create a fpga_config object. The filepath is the path to the bitfile you wish to interact with.
        This creates the fpga configuration with a number of read and write packets.
        :param filepath:
        """
        self.filepath = filepath
        self.tree = ET.parse(self.filepath)
        self.root = self.tree.getroot()
        self.read_packets = None
        self.write_packets = None
        self.session = None
        self.write_fifo_object = None
        self.read_fifo_object = None
        for child in self.root:
            if child.tag == 'DMA_Read':
                self.read_packets = int(child[0].text)
                self.read_fifo_tag = child
            elif child.tag == 'DMA_Write':
                self.write_packets = int(child[0].text)
                self.write_fifo_tag = child
            elif child.tag == 'Bitfile':
                self.bitfile = child.text
        self.folder = ntpath.split(self.filepath)
        self.full_bitpath = self.folder[0] + '\\{}'.format(self.bitfile)
        if self.read_packets is None:
            raise ConfigError(message='No DMA_Read tag present')
        elif self.write_packets is None:
            raise ConfigError(message='No DMA_Write tag present')
        self.read_packet_list = []
        self.write_packet_list = []
        self.channel_value_table = {}
        for pack_index in range(self.read_packets):
            self.read_packet_list.append(
                self._create_packet('read', pack_index + 1))
            for chan_index in range(self.read_packet_list[pack_index].
                                    definition['channel_count']):
                self.channel_value_table[self.read_packet_list[
                    pack_index].definition['name{}'.format(chan_index)]] = 0
        for pack_index in range(self.write_packets):
            self.write_packet_list.append(
                self._create_packet('write', pack_index + 1))
            for chan_index in range(self.write_packet_list[pack_index].
                                    definition['channel_count']):
                self.channel_value_table[self.write_packet_list[
                    pack_index].definition['name{}'.format(chan_index)]] = 0

    def init_fpga(self, device, loop_rate):
        self.session = Session(self.full_bitpath,
                               device,
                               reset_if_last_session_on_exit=True)
        self.session.download()
        self.session.run()
        print(self.session.fpga_vi_state)
        self.write_fifo_object = self.session.fifos['DMA_WRITE']
        self.read_fifo_object = self.session.fifos['DMA_READ']
        self.loop_timer = self.session.registers['Loop Rate (usec)']
        self.fpga_start_control = self.session.registers['Start']
        self.fpga_rtsi = self.session.registers['Write to  RTSI']
        self.fpga_ex_timing = self.session.registers['Use External Timing']
        self.fpga_irq = self.session.registers['Generate IRQ']

        self.loop_timer.write(loop_rate)
        self.fpga_rtsi.write(False)
        self.fpga_ex_timing.write(False)
        self.fpga_irq.write(False)

    def start_fpga_main_loop(self):
        self.fpga_start_control.write(True)

    def stop_fpga(self):
        self.session.reset()
        self.session.close()

    def set_channel(self, channel_name, value):
        self.channel_value_table[channel_name] = value

    def get_channel(self, channel_name):
        return self.channel_value_table[channel_name]

    def vs_read_fifo(self, timeout):
        if self.read_fifo_object is None:
            raise ConfigError('Session not initialized. Please first call the'
                              ' VeriStandFPGA.init fpga method before reading')
        else:
            read_tup = self.read_fifo_object.read(
                number_of_elements=self.read_packets, timeout_ms=timeout)
            data = read_tup.data
            for i, u64 in enumerate(data):
                this_packet = self.read_packet_list[i]
                read_vals = this_packet._unpack(u64)
                for key in read_vals:
                    self.channel_value_table[key] = read_vals[key]

    def vs_write_fifo(self, timeout):
        if self.write_fifo_object is None:
            raise ConfigError(
                'Session not initialized. '
                'Please first call the VeriStandFPGA.init_fpga method before writing'
            )
        else:
            write_list = []
            for current_packet in self.write_packet_list:
                packet_vals = []
                for channel in current_packet:
                    packet_vals.append(
                        self.channel_value_table[channel['name']])
                write_list.append(current_packet._pack(packet_vals))
            self.write_fifo_object.write(data=write_list, timeout_ms=timeout)

    def _create_packet(self, direction, index):
        """

        :param direction:
        :param index:
        :return:
        """
        if index == 1 and direction.lower() == 'read':
            this_packet = FirstReadPacket()
        else:
            this_packet = Packet(self, direction, index)
        return this_packet

    def __del__(self):
        try:
            self.stop_fpga()
        except:
            print('Configuration closed')
        print('FPGA hardware session closed.')
        print('{} configuration closed'.format(self.bitfile))