def burn(firmware_file): global f f = Ftdi() try: f.open_mpsse(0x0403, 0x6010, description="HiSPARC III Master", interface=1, initial=1, direction=0b11) except (FtdiError, usb.USBError): print "RESET" usb.util.dispose_resources(f.usb_dev) f.open_mpsse(0x0403, 0x6010, description="HiSPARC III Master", interface=1, initial=1, direction=0b11) f.write_data([Ftdi.TCK_DIVISOR, 0, 0]) f.write_data([Ftdi.DISABLE_CLK_DIV5]) print_low_bits(f) print_high_bits(f) f.write_data([Ftdi.SET_BITS_HIGH, 0, 1]) print_high_bits(f) f.write_data([Ftdi.SET_BITS_HIGH, 1, 1]) print_high_bits(f) BUFSIZE = 64 * 1024 with open(os.path.expanduser(firmware_file), 'rb') as file: while True: xbuf = file.read(BUFSIZE) if not xbuf: break LENGTH = len(xbuf) - 1 LENGTH_L = LENGTH & 0xff LENGTH_H = LENGTH >> 8 & 0xff send_buf = [Ftdi.WRITE_BYTES_PVE_LSB ] + [LENGTH_L, LENGTH_H] + [ord(u) for u in xbuf] f.write_data(send_buf) #for i in range(10): # print_high_bits(f) # f.write_data([0x8e, 0]) print_high_bits(f) print_low_bits(f)
def burn(firmware_file): global f f = Ftdi() try: f.open_mpsse(0x0403, 0x6010, description="HiSPARC III Master", interface=1, initial=1, direction=0b11) except (FtdiError, usb.USBError): raise print >> sys.stderr, "RESET" usb.util.dispose_resources(f.usb_dev) f.open_mpsse(0x0403, 0x6010, description="HiSPARC III Master", interface=1, initial=1, direction=0b11) f.write_data([Ftdi.TCK_DIVISOR, 0, 0]) f.write_data([Ftdi.DISABLE_CLK_DIV5]) print_low_bits(f) print_high_bits(f) f.write_data([Ftdi.SET_BITS_HIGH, 0, 1]) print_high_bits(f) f.write_data([Ftdi.SET_BITS_HIGH, 1, 1]) print_high_bits(f) BUFSIZE = 64 * 1024 with open(os.path.expanduser(firmware_file), 'rb') as file: while True: xbuf = file.read(BUFSIZE) if not xbuf: break LENGTH = len(xbuf) - 1 LENGTH_L = LENGTH & 0xff LENGTH_H = LENGTH >> 8 & 0xff send_buf = [Ftdi.WRITE_BYTES_PVE_LSB] + [LENGTH_L, LENGTH_H] + [ord(u) for u in xbuf] f.write_data(send_buf) #for i in range(10): # print_high_bits(f) # f.write_data([0x8e, 0]) print_high_bits(f) print_low_bits(f)
class Tunnel_DAC(Instrument): def __init__(self, name, serial=None, channel='A0', numdacs=3, delay=1e-3): ''' discover and initialize Tunnel_DAC hardware Input: serial - serial number of the FTDI converter channel - 2 character channel id the DAC is connected to; the first byte identifies the channel (A..D for current devices) the second byte identifies the bit within that channel (0..7) numdacs - number of DACs daisy-chained on that line delay - communications delay assumed between PC and the USB converter ''' logging.info(__name__ + ': Initializing instrument Tunnel_DAC') Instrument.__init__(self, name, tags=['physical']) self._conn = Ftdi() # VIDs and PIDs of converters used vps = [ (0x0403, 0x6011), # FTDI UM4232H 4ch (0x0403, 0x6014) # FTDI UM232H 1ch ] # explicitly clear device cache of UsbTools #UsbTools.USBDEVICES = [] # find all devices and obtain serial numbers devs = self._conn.find_all(vps) # filter list by serial number if provided if (serial != None): devs = [dev for dev in devs if dev[2] == serial] if (len(devs) == 0): logging.error(__name__ + ': failed to find matching FTDI devices.') elif (len(devs) > 1): logging.error( __name__ + ': more than one converter found and no serial number given.') logging.info(__name__ + ': available devices are: %s.' % str([dev[2] for dev in devs])) vid, pid, self._serial, channels, description = devs[0] # parse channel string if (len(channel) != 2): logging.error( __name__ + ': channel identifier must be a string of length 2. ex. A0, D5.' ) self._channel = 1 + ord(channel[0]) - ord('A') self._bit = ord(channel[1]) - ord('0') if ((self._channel < 1) or (self._channel > channels)): logging.error(__name__ + ': channel %c is not supported by this device.' % (chr(ord('A') + self._channel - 1))) if ((self._bit < 0) or (self._bit > 7)): logging.error(__name__ + ': subchannel must be between 0 and 7, not %d.' % self._bit) # open device self._conn.open(vid, pid, interface=self._channel, serial=self._serial) logging.info(__name__ + ': using converter with serial #%s' % self._serial) self._conn.set_bitmode(0xFF, Ftdi.BITMODE_BITBANG) # 80k generates bit durations of 12.5us, 80 is magic :( # magic?: 4 from incorrect BITBANG handling of pyftdi, 2.5 from 120MHz instead of 48MHz clock of H devices # original matlab code uses 19kS/s self._conn.set_baudrate(19000 / 80) # house keeping self._numdacs = numdacs self._sleeptime = ( 10. + 16. * self._numdacs ) * 12.5e-6 + delay # 1st term from hardware parameters, 2nd term from USB self._minval = -5000. self._maxval = 5000. self._resolution = 16 # DAC resolution in bits self._voltages = [0.] * numdacs self.add_parameter('voltage', type=types.FloatType, flags=Instrument.FLAG_SET, channels=(1, self._numdacs), minval=self._minval, maxval=self._maxval, units='mV', format='%.02f') # tags=['sweep'] self.add_function('set_voltages') self.add_function('commit') def _encode(self, data, channel=0, bits_per_item=16, big_endian=True): ''' convert binary data into line symbols the tunnel electronic DAC logic box triggers on rising signal edges and samples data after 18us. we use a line code with three bits of duration 12us, where a logical 1 is encoded as B"110" and a logical 0 is encoded as B"100". the line data is returned as a byte string with three bytes/symbol. Input: data - a vector of data entities (usually a string or list of integers) channel - a number in [0, 7] specifying the output bit on the USB-to-UART chip bits_per_item - number of bits to extract from each element of the data vector ''' # build line code for the requested channel line_1 = chr(1 << channel) line_0 = chr(0) line_code = [ ''.join([line_0, line_1, line_1]), ''.join([line_0, line_1, line_0]) ] # do actual encoding result = [] result.append(10 * line_0) for item in data: for bit in (range(bits_per_item - 1, -1, -1) if big_endian else range(0, bits_per_item)): result.append(line_code[1 if (item & (1 << bit)) else 0]) result.append(10 * line_0) return ''.join(result) def commit(self): ''' send updated parameter values to the physical DACs via USB ''' # normalize, scale, clip voltages voltages = [ -1 + 2 * (x - self._minval) / (self._maxval - self._minval) for x in self._voltages ] voltages = [ max( -2**(self._resolution - 1), min(2**(self._resolution - 1) - 1, int(2**(self._resolution - 1) * x))) for x in voltages ] # encode and send data = self._encode(reversed(voltages), self._bit, self._resolution) self._conn.write_data(data) # wait for the FTDI fifo to clock the data to the DACs sleep(self._sleeptime) def do_set_voltage(self, value, channel): ''' immediately update voltage on channel ch parameter checking is done by qtlab ''' self._voltages[channel - 1] = value self.commit() def set_voltages(self, valuedict): ''' update voltages on several channels simultaneously todo: update instrument panel display ''' for channel, value in valuedict.iteritems(): # bounds checking & clipping if ((channel < 1) or (channel > self._numdacs)): logging.error(__name__ + ': channel %d out of range.' % channel) continue value = float(value) if ((value < self._minval) or (value >= self._maxval)): logging.error(__name__ + ': value %f out of range. clipping.' % value) value = max(self._minval, min(self._maxval, value)) # does not handle maxval correctly self._voltages[channel - 1] = value self.commit()
class JtagController(object): """JTAG master of an FTDI device""" TCK_BIT = 0x01 # FTDI output TDI_BIT = 0x02 # FTDI output TDO_BIT = 0x04 # FTDI input TMS_BIT = 0x08 # FTDI output TRST_BIT = 0x10 # FTDI output, not available on 2232 JTAG debugger JTAG_MASK = 0x1f FTDI_PIPE_LEN = 512 # Private API def __init__(self, trst=False, frequency=3.0E6): """ trst uses the nTRST optional JTAG line to hard-reset the TAP controller """ self._ftdi = Ftdi() self._trst = trst self._frequency = frequency self.direction = JtagController.TCK_BIT | \ JtagController.TDI_BIT | \ JtagController.TMS_BIT | \ (self._trst and JtagController.TRST_BIT or 0) self._last = None # Last deferred TDO bit self._write_buff = Array('B') def __del__(self): self.close() # Public API def configure(self, vendor, product, interface): """Configure the FTDI interface as a JTAG controller""" curfreq = self._ftdi.open_mpsse(vendor, product, interface, direction=self.direction, #initial=0x0, frequency=self._frequency) # FTDI requires to initialize all GPIOs before MPSSE kicks in cmd = Array('B', [Ftdi.SET_BITS_LOW, 0x0, self.direction]) self._ftdi.write_data(cmd) def close(self): if self._ftdi: self._ftdi.close() self._ftdi = None def purge(self): self._ftdi.purge_buffers() def reset(self, sync=False): """Reset the attached TAP controller. sync sends the command immediately (no caching) """ # we can either send a TRST HW signal or perform 5 cycles with TMS=1 # to move the remote TAP controller back to 'test_logic_reset' state # do both for now if not self._ftdi: raise JtagError("FTDI controller terminated") if self._trst: # nTRST value = 0 cmd = Array('B', [Ftdi.SET_BITS_LOW, value, self.direction]) self._ftdi.write_data(cmd) time.sleep(0.1) # nTRST should be left to the high state value = JtagController.TRST_BIT cmd = Array('B', [Ftdi.SET_BITS_LOW, value, self.direction]) self._ftdi.write_data(cmd) time.sleep(0.1) # TAP reset (even with HW reset, could be removed though) self.write_tms(BitSequence('11111')) if sync: self.sync() def sync(self): if not self._ftdi: raise JtagError("FTDI controller terminated") if self._write_buff: self._ftdi.write_data(self._write_buff) self._write_buff = Array('B') def write_tms(self, tms): """Change the TAP controller state""" if not isinstance(tms, BitSequence): raise JtagError('Expect a BitSequence') length = len(tms) if not (0 < length < 8): raise JtagError('Invalid TMS length') out = BitSequence(tms, length=8) # apply the last TDO bit if self._last is not None: out[7] = self._last # print "TMS", tms, (self._last is not None) and 'w/ Last' or '' # reset last bit self._last = None cmd = Array('B', [Ftdi.WRITE_BITS_TMS_NVE, length-1, out.tobyte()]) self._stack_cmd(cmd) self.sync() #self._ftdi.validate_mpsse() def read(self, length): """Read out a sequence of bits from TDO""" byte_count = length//8 bit_count = length-8*byte_count bs = BitSequence() if byte_count: bytes = self._read_bytes(byte_count) bs.append(bytes) if bit_count: bits = self._read_bits(bit_count) bs.append(bits) return bs def write(self, out, use_last=True): """Write a sequence of bits to TDI""" if isinstance(out, str): out = BitSequence(bytes_=out) elif not isinstance(out, BitSequence): out = BitSequence(out) if use_last: (out, self._last) = (out[:-1], bool(out[-1])) byte_count = len(out)//8 pos = 8*byte_count bit_count = len(out)-pos if byte_count: self._write_bytes(out[:pos]) if bit_count: self._write_bits(out[pos:]) def shift_register(self, out, use_last=False): """Shift a BitSequence into the current register and retrieve the register output""" if not isinstance(out, BitSequence): return JtagError('Expect a BitSequence') length = len(out) if use_last: (out, self._last) = (out[:-1], int(out[-1])) byte_count = len(out)//8 pos = 8*byte_count bit_count = len(out)-pos if not byte_count and not bit_count: raise JtagError("Nothing to shift") if byte_count: blen = byte_count-1 #print "RW OUT %s" % out[:pos] cmd = Array('B', [Ftdi.RW_BYTES_PVE_NVE_LSB, blen, (blen>>8)&0xff]) cmd.extend(out[:pos].tobytes(msby=True)) self._stack_cmd(cmd) #print "push %d bytes" % byte_count if bit_count: #print "RW OUT %s" % out[pos:] cmd = Array('B', [Ftdi.RW_BITS_PVE_NVE_LSB, bit_count-1]) cmd.append(out[pos:].tobyte()) self._stack_cmd(cmd) #print "push %d bits" % bit_count self.sync() bs = BitSequence() byte_count = length//8 pos = 8*byte_count bit_count = length-pos if byte_count: data = self._ftdi.read_data_bytes(byte_count, 4) if not data: raise JtagError('Unable to read data from FTDI') byteseq = BitSequence(bytes_=data, length=8*byte_count) #print "RW IN %s" % byteseq bs.append(byteseq) #print "pop %d bytes" % byte_count if bit_count: data = self._ftdi.read_data_bytes(1, 4) if not data: raise JtagError('Unable to read data from FTDI') byte = data[0] # need to shift bits as they are shifted in from the MSB in FTDI byte >>= 8-bit_count bitseq = BitSequence(byte, length=bit_count) bs.append(bitseq) #print "pop %d bits" % bit_count if len(bs) != length: raise AssertionError("Internal error") #self._ftdi.validate_mpsse() return bs def _stack_cmd(self, cmd): if not isinstance(cmd, Array): raise TypeError('Expect a byte array') if not self._ftdi: raise JtagError("FTDI controller terminated") # Currrent buffer + new command + send_immediate if (len(self._write_buff)+len(cmd)+1) >= JtagController.FTDI_PIPE_LEN: self.sync() self._write_buff.extend(cmd) def _read_bits(self, length): """Read out bits from TDO""" data = '' if length > 8: raise JtagError("Cannot fit into FTDI fifo") cmd = Array('B', [Ftdi.READ_BITS_NVE_LSB, length-1]) self._stack_cmd(cmd) self.sync() data = self._ftdi.read_data_bytes(1, 4) # need to shift bits as they are shifted in from the MSB in FTDI byte = ord(data) >> 8-bit_count bs = BitSequence(byte, length=length) #print "READ BITS %s" % (bs) return bs def _write_bits(self, out): """Output bits on TDI""" length = len(out) byte = out.tobyte() #print "WRITE BITS %s" % out cmd = Array('B', [Ftdi.WRITE_BITS_NVE_LSB, length-1, byte]) self._stack_cmd(cmd) def _read_bytes(self, length): """Read out bytes from TDO""" data = '' if length > JtagController.FTDI_PIPE_LEN: raise JtagError("Cannot fit into FTDI fifo") alen = length-1 cmd = Array('B', [Ftdi.READ_BYTES_NVE_LSB, alen&0xff, (alen>>8)&0xff]) self._stack_cmd(cmd) self.sync() data = self._ftdi.read_data_bytes(length, 4) bs = BitSequence(bytes_=data, length=8*length) #print "READ BYTES %s" % bs return bs def _write_bytes(self, out): """Output bytes on TDI""" bytes_ = out.tobytes(msby=True) # don't ask... olen = len(bytes_)-1 #print "WRITE BYTES %s" % out cmd = Array('B', [Ftdi.WRITE_BYTES_NVE_LSB, olen&0xff, (olen>>8)&0xff]) cmd.extend(bytes_) self._stack_cmd(cmd)
class BitBangController(object): PROGRAM_PIN = 0x20 SOFT_RESET_PIN = 0x40 def __init__(self, idVendor, idProduct, interface): #all pins are in high impedance self.vendor = idVendor self.product = idProduct self.interface = interface self.f = Ftdi() # print "vid: %s, pid: %s" % (str(hex(idVendor)), str(hex(idProduct))) self.f.open_bitbang(idVendor, idProduct, interface) def hiz(self): print "changing pins to high impedance" def is_in_bitbang(self): return self.f.bitbang_enabled def read_pins(self): return self.f.read_pins() def read_program_pin(self): return (self.PROGRAM_PIN & self.f.read_pins() > 0) def soft_reset_high(self): pins = self.f.read_pins() pins |= self.SOFT_RESET_PIN prog_cmd = Array('B', [0x01, pins]) self.f.write_data(prog_cmd) def soft_reset_low(self): pins = self.f.read_pins() pins &= ~(self.SOFT_RESET_PIN) prog_cmd = Array('B', [0x00, pins]) self.f.write_data(prog_cmd) def program_high(self): pins = self.f.read_pins() pins |= self.PROGRAM_PIN prog_cmd = Array('B', [0x01, pins]) self.f.write_data(prog_cmd) def program_low(self): pins = self.f.read_pins() pins &= ~(self.PROGRAM_PIN) prog_cmd = Array('B', [0x00, pins]) self.f.write_data(prog_cmd) def read_soft_reset_pin(self): return (self.SOFT_RESET_PIN & self.f.read_pins() > 0) def set_soft_reset_to_output(self): pin_dir = self.SOFT_RESET_PIN self.f.open_bitbang(self.vendor, self.product, self.interface, direction=pin_dir) def set_program_to_output(self): pin_dir = self.PROGRAM_PIN self.f.open_bitbang(self.vendor, self.product, self.interface, direction=pin_dir) def set_pins_to_input(self): self.f.open_bitbang(self.vendor, self.product, self.interface, direction=0x00) def set_pins_to_output(self): self.f.open_bitbang(self.vendor, self.product, self.interface, direction=0xFF) def pins_on(self): prog_cmd = Array('B', [0x01, 0xFF]) self.f.write_data(prog_cmd) def pins_off(self): prog_cmd = Array('B', [0x01, 0x00]) self.f.write_data(prog_cmd)
class USBDevice(Device): """ `AD2USB`_ device utilizing PyFTDI's interface. """ # Constants PRODUCT_IDS = ((0x0403, 0x6001), (0x0403, 0x6015)) """List of Vendor and Product IDs used to recognize `AD2USB`_ devices.""" DEFAULT_VENDOR_ID = PRODUCT_IDS[0][0] """Default Vendor ID used to recognize `AD2USB`_ devices.""" DEFAULT_PRODUCT_ID = PRODUCT_IDS[0][1] """Default Product ID used to recognize `AD2USB`_ devices.""" # Deprecated constants FTDI_VENDOR_ID = DEFAULT_VENDOR_ID """DEPRECATED: Vendor ID used to recognize `AD2USB`_ devices.""" FTDI_PRODUCT_ID = DEFAULT_PRODUCT_ID """DEPRECATED: Product ID used to recognize `AD2USB`_ devices.""" BAUDRATE = 115200 """Default baudrate for `AD2USB`_ devices.""" __devices = [] __detect_thread = None @classmethod def find_all(cls, vid=None, pid=None): """ Returns all FTDI devices matching our vendor and product IDs. :returns: list of devices :raises: :py:class:`~alarmdecoder.util.CommError` """ if not have_pyftdi: raise ImportError( 'The USBDevice class has been disabled due to missing requirement: pyftdi or pyusb.' ) cls.__devices = [] query = cls.PRODUCT_IDS if vid and pid: query = [(vid, pid)] try: cls.__devices = Ftdi.find_all(query, nocache=True) except (usb.core.USBError, FtdiError) as err: raise CommError( 'Error enumerating AD2USB devices: {0}'.format(str(err)), err) return cls.__devices @classmethod def devices(cls): """ Returns a cached list of `AD2USB`_ devices located on the system. :returns: cached list of devices found """ return cls.__devices @classmethod def find(cls, device=None): """ Factory method that returns the requested :py:class:`USBDevice` device, or the first device. :param device: Tuple describing the USB device to open, as returned by find_all(). :type device: tuple :returns: :py:class:`USBDevice` object utilizing the specified device :raises: :py:class:`~alarmdecoder.util.NoDeviceError` """ if not have_pyftdi: raise ImportError( 'The USBDevice class has been disabled due to missing requirement: pyftdi or pyusb.' ) cls.find_all() if len(cls.__devices) == 0: raise NoDeviceError('No AD2USB devices present.') if device is None: device = cls.__devices[0] vendor, product, sernum, ifcount, description = device return USBDevice(interface=sernum, vid=vendor, pid=product) @classmethod def start_detection(cls, on_attached=None, on_detached=None): """ Starts the device detection thread. :param on_attached: function to be called when a device is attached **Callback definition:** *def callback(thread, device)* :type on_attached: function :param on_detached: function to be called when a device is detached **Callback definition:** *def callback(thread, device)* :type on_detached: function """ if not have_pyftdi: raise ImportError( 'The USBDevice class has been disabled due to missing requirement: pyftdi or pyusb.' ) cls.__detect_thread = USBDevice.DetectThread(on_attached, on_detached) try: cls.find_all() except CommError: pass cls.__detect_thread.start() @classmethod def stop_detection(cls): """ Stops the device detection thread. """ if not have_pyftdi: raise ImportError( 'The USBDevice class has been disabled due to missing requirement: pyftdi or pyusb.' ) try: cls.__detect_thread.stop() except Exception: pass @property def interface(self): """ Retrieves the interface used to connect to the device. :returns: the interface used to connect to the device """ return self._interface @interface.setter def interface(self, value): """ Sets the interface used to connect to the device. :param value: may specify either the serial number or the device index :type value: string or int """ self._interface = value if isinstance(value, int): self._device_number = value else: self._serial_number = value @property def serial_number(self): """ Retrieves the serial number of the device. :returns: serial number of the device """ return self._serial_number @serial_number.setter def serial_number(self, value): """ Sets the serial number of the device. :param value: serial number of the device :type value: string """ self._serial_number = value @property def description(self): """ Retrieves the description of the device. :returns: description of the device """ return self._description @description.setter def description(self, value): """ Sets the description of the device. :param value: description of the device :type value: string """ self._description = value def __init__(self, interface=0, vid=None, pid=None): """ Constructor :param interface: May specify either the serial number or the device index. :type interface: string or int """ if not have_pyftdi: raise ImportError( 'The USBDevice class has been disabled due to missing requirement: pyftdi or pyusb.' ) Device.__init__(self) self._device = Ftdi() self._interface = 0 self._device_number = 0 self._serial_number = None self._vendor_id = USBDevice.DEFAULT_VENDOR_ID if vid: self._vendor_id = vid self._product_id = USBDevice.DEFAULT_PRODUCT_ID if pid: self._product_id = pid self._endpoint = 0 self._description = None self.interface = interface def open(self, baudrate=BAUDRATE, no_reader_thread=False): """ Opens the device. :param baudrate: baudrate to use :type baudrate: int :param no_reader_thread: whether or not to automatically start the reader thread. :type no_reader_thread: bool :raises: :py:class:`~alarmdecoder.util.NoDeviceError` """ # Set up defaults if baudrate is None: baudrate = USBDevice.BAUDRATE self._read_thread = Device.ReadThread(self) # Open the device and start up the thread. try: self._device.open(self._vendor_id, self._product_id, self._endpoint, self._device_number, self._serial_number, self._description) self._device.set_baudrate(baudrate) if not self._serial_number: self._serial_number = self._get_serial_number() self._id = self._serial_number except (usb.core.USBError, FtdiError) as err: raise NoDeviceError('Error opening device: {0}'.format(str(err)), err) except KeyError as err: raise NoDeviceError( 'Unsupported device. ({0:04x}:{1:04x}) You probably need a newer version of pyftdi.' .format(err[0][0], err[0][1])) else: self._running = True self.on_open() if not no_reader_thread: self._read_thread.start() return self def close(self): """ Closes the device. """ try: Device.close(self) # HACK: Probably should fork pyftdi and make this call in .close() self._device.usb_dev.attach_kernel_driver(self._device_number) except Exception: pass def fileno(self): """ File number not supported for USB devices. :raises: NotImplementedError """ raise NotImplementedError('USB devices do not support fileno()') def write(self, data): """ Writes data to the device. :param data: data to write :type data: string :raises: :py:class:`~alarmdecoder.util.CommError` """ try: self._device.write_data(data) self.on_write(data=data) except FtdiError as err: raise CommError('Error writing to device: {0}'.format(str(err)), err) def read(self): """ Reads a single character from the device. :returns: character read from the device :raises: :py:class:`~alarmdecoder.util.CommError` """ ret = None try: ret = self._device.read_data(1) except (usb.core.USBError, FtdiError) as err: raise CommError('Error reading from device: {0}'.format(str(err)), err) return ret def read_line(self, timeout=0.0, purge_buffer=False): """ Reads a line from the device. :param timeout: read timeout :type timeout: float :param purge_buffer: Indicates whether to purge the buffer prior to reading. :type purge_buffer: bool :returns: line that was read :raises: :py:class:`~alarmdecoder.util.CommError`, :py:class:`~alarmdecoder.util.TimeoutError` """ def timeout_event(): """Handles read timeout event""" timeout_event.reading = False timeout_event.reading = True if purge_buffer: self._buffer = b'' got_line, ret = False, None timer = threading.Timer(timeout, timeout_event) if timeout > 0: timer.start() try: while timeout_event.reading: buf = self._device.read_data(1) if buf != b'': ub = bytes_hack(buf) self._buffer += ub if ub == b"\n": self._buffer = self._buffer.rstrip(b"\r\n") if len(self._buffer) > 0: got_line = True break else: time.sleep(0.01) except (usb.core.USBError, FtdiError) as err: raise CommError('Error reading from device: {0}'.format(str(err)), err) else: if got_line: ret, self._buffer = self._buffer, b'' self.on_read(data=ret) else: raise TimeoutError( 'Timeout while waiting for line terminator.') finally: timer.cancel() return ret def purge(self): """ Purges read/write buffers. """ self._device.purge_buffers() def _get_serial_number(self): """ Retrieves the FTDI device serial number. :returns: string containing the device serial number """ return usb.util.get_string(self._device.usb_dev, 64, self._device.usb_dev.iSerialNumber) class DetectThread(threading.Thread): """ Thread that handles detection of added/removed devices. """ on_attached = event.Event( "This event is called when an `AD2USB`_ device has been detected.\n\n**Callback definition:** def callback(thread, device*" ) on_detached = event.Event( "This event is called when an `AD2USB`_ device has been removed.\n\n**Callback definition:** def callback(thread, device*" ) def __init__(self, on_attached=None, on_detached=None): """ Constructor :param on_attached: Function to call when a device is attached **Callback definition:** *def callback(thread, device)* :type on_attached: function :param on_detached: Function to call when a device is detached **Callback definition:** *def callback(thread, device)* :type on_detached: function """ threading.Thread.__init__(self) if on_attached: self.on_attached += on_attached if on_detached: self.on_detached += on_detached self._running = False def stop(self): """ Stops the thread. """ self._running = False def run(self): """ The actual detection process. """ self._running = True last_devices = set() while self._running: try: current_devices = set(USBDevice.find_all()) for dev in current_devices.difference(last_devices): self.on_attached(device=dev) for dev in last_devices.difference(current_devices): self.on_detached(device=dev) last_devices = current_devices except CommError: pass time.sleep(0.25)
class SpiController(object): """SPI master""" SCK_BIT = 0x01 DO_BIT = 0x02 DI_BIT = 0x04 CS_BIT = 0x08 PAYLOAD_MAX_LENGTH = 0x10000 # 16 bits max def __init__(self, silent_clock=False, cs_count=4): """Instanciate a SpiController. silent_clock should be set to avoid clocking out SCLK when all /CS signals are released. This clock beat is used to enforce a delay between /CS signal activation. When weird SPI devices are used, SCLK beating may cause trouble. In this case, silent_clock should be set but beware that SCLK line should be fitted with a pull-down resistor, as SCLK is high-Z during this short period of time. cs_count is the number of /CS lines (one per device to drive on the SPI bus) """ self._ftdi = Ftdi() self._cs_bits = ((SpiController.CS_BIT << cs_count) - 1) & \ ~(SpiController.CS_BIT - 1) self._ports = [None] * cs_count self._direction = self._cs_bits | \ SpiController.DO_BIT | \ SpiController.SCK_BIT self._cs_high = Array('B') if silent_clock: # Set SCLK as input to avoid emitting clock beats self._cs_high.extend([Ftdi.SET_BITS_LOW, self._cs_bits, self._direction&~SpiController.SCK_BIT]) # /CS to SCLK delay, use 8 clock cycles as a HW tempo self._cs_high.extend([Ftdi.WRITE_BITS_TMS_NVE, 8-1, 0xff]) # Restore idle state self._cs_high.extend([Ftdi.SET_BITS_LOW, self._cs_bits, self._direction]) self._immediate = Array('B', [Ftdi.SEND_IMMEDIATE]) self._frequency = 0.0 def configure(self, vendor, product, interface, frequency=6.0E6, loopback=False): """Configure the FTDI interface as a SPI master""" self._frequency = \ self._ftdi.open_mpsse(vendor, product, interface, direction=self._direction, initial=self._cs_bits, # /CS all high frequency=frequency, loopback=loopback) def terminate(self): """Close the FTDI interface""" if self._ftdi: self._ftdi.close() self._ftdi = None def get_port(self, cs): """Obtain a SPI port to drive a SPI device selected by cs""" if not self._ftdi: raise SpiIOError("FTDI controller not initialized") if cs >= len(self._ports): raise SpiIOError("No such SPI port") if not self._ports[cs]: cs_state = 0xFF & ~((SpiController.CS_BIT<<cs) | SpiController.SCK_BIT | SpiController.DO_BIT) cs_cmd = Array('B', [Ftdi.SET_BITS_LOW, cs_state, self._direction]) self._ports[cs] = SpiPort(self, cs_cmd) self._flush() return self._ports[cs] @property def frequency_max(self): """Returns the maximum SPI clock""" return self._ftdi.frequency_max @property def frequency(self): """Returns the current SPI clock""" return self._frequency def _exchange(self, frequency, cs_cmd, out, readlen): """Perform a half-duplex transaction with the SPI slave""" if not self._ftdi: raise SpiIOError("FTDI controller not initialized") if len(out) > SpiController.PAYLOAD_MAX_LENGTH: raise SpiIOError("Output payload is too large") if readlen > SpiController.PAYLOAD_MAX_LENGTH: raise SpiIOError("Input payload is too large") if self._frequency != frequency: freq = self._ftdi.set_frequency(frequency) # store the requested value, not the actual one (best effort) self._frequency = frequency write_cmd = struct.pack('<BH', Ftdi.WRITE_BYTES_NVE_MSB, len(out)-1) if readlen: read_cmd = struct.pack('<BH', Ftdi.READ_BYTES_NVE_MSB, readlen-1) cmd = Array('B', cs_cmd) cmd.fromstring(write_cmd) cmd.extend(out) cmd.fromstring(read_cmd) cmd.extend(self._immediate) cmd.extend(self._cs_high) self._ftdi.write_data(cmd) # USB read cycle may occur before the FTDI device has actually # sent the data, so try to read more than once if no data is # actually received data = self._ftdi.read_data_bytes(readlen, 4) else: cmd = Array('B', cs_cmd) cmd.fromstring(write_cmd) cmd.extend(out) cmd.extend(self._cs_high) self._ftdi.write_data(cmd) data = Array('B') return data def _flush(self): """Flush the HW FIFOs""" self._ftdi.write_data(self._immediate) self._ftdi.purge_buffers()
class BitBangController(object): PROGRAM_PIN = 0x20 SOFT_RESET_PIN = 0x40 def __init__(self, idVendor, idProduct, interface): #all pins are in high impedance self.vendor = idVendor self.product = idProduct self.interface = interface self.f = Ftdi() # print "vid: %s, pid: %s" % (str(hex(idVendor)), str(hex(idProduct))) self.f.open_bitbang(idVendor, idProduct, interface) def hiz(self): print "changing pins to high impedance" def is_in_bitbang(self): return self.f.bitbang_enabled def read_pins(self): return self.f.read_pins() def read_program_pin(self): return (self.PROGRAM_PIN & self.f.read_pins() > 0) def soft_reset_high(self): pins = self.f.read_pins() pins |= self.SOFT_RESET_PIN prog_cmd = Array('B', [0x01, pins]) self.f.write_data(prog_cmd) def soft_reset_low(self): pins = self.f.read_pins() pins &= ~(self.SOFT_RESET_PIN) prog_cmd = Array('B', [0x00, pins]) self.f.write_data(prog_cmd) def program_high(self): pins = self.f.read_pins() pins |= self.PROGRAM_PIN prog_cmd = Array('B', [0x01, pins]) self.f.write_data(prog_cmd) def program_low(self): pins = self.f.read_pins() pins &= ~(self.PROGRAM_PIN) prog_cmd = Array('B', [0x00, pins]) self.f.write_data(prog_cmd) def read_soft_reset_pin(self): return (self.SOFT_RESET_PIN & self.f.read_pins() > 0) def set_soft_reset_to_output(self): pin_dir = self.SOFT_RESET_PIN self.f.open_bitbang(self.vendor, self.product, self.interface, direction = pin_dir) def set_program_to_output(self): pin_dir = self.PROGRAM_PIN self.f.open_bitbang(self.vendor, self.product, self.interface, direction = pin_dir) def set_pins_to_input(self): self.f.open_bitbang(self.vendor, self.product, self.interface, direction = 0x00) def set_pins_to_output(self): self.f.open_bitbang(self.vendor, self.product, self.interface, direction = 0xFF) def pins_on(self): prog_cmd = Array('B', [0x01, 0xFF]) self.f.write_data(prog_cmd) def pins_off(self): prog_cmd = Array('B', [0x01, 0x00]) self.f.write_data(prog_cmd)
class JtagController(object): """JTAG master of an FTDI device""" TCK_BIT = 0x01 # FTDI output TDI_BIT = 0x02 # FTDI output TDO_BIT = 0x04 # FTDI input TMS_BIT = 0x08 # FTDI output TRST_BIT = 0x10 # FTDI output, not available on 2232 JTAG debugger JTAG_MASK = 0x1f FTDI_PIPE_LEN = 512 # Private API def __init__(self, trst=False, frequency=3.0E6): """ trst uses the nTRST optional JTAG line to hard-reset the TAP controller """ self._ftdi = Ftdi() self._trst = trst self._frequency = frequency self.direction = JtagController.TCK_BIT | \ JtagController.TDI_BIT | \ JtagController.TMS_BIT | \ (self._trst and JtagController.TRST_BIT or 0) self._last = None # Last deferred TDO bit self._write_buff = Array('B') def __del__(self): self.close() # Public API def configure(self, vendor, product, interface): """Configure the FTDI interface as a JTAG controller""" curfreq = self._ftdi.open_mpsse( vendor, product, interface, direction=self.direction, #initial=0x0, frequency=self._frequency) # FTDI requires to initialize all GPIOs before MPSSE kicks in cmd = Array('B', [Ftdi.SET_BITS_LOW, 0x0, self.direction]) self._ftdi.write_data(cmd) def close(self): if self._ftdi: self._ftdi.close() self._ftdi = None def purge(self): self._ftdi.purge_buffers() def reset(self, sync=False): """Reset the attached TAP controller. sync sends the command immediately (no caching) """ # we can either send a TRST HW signal or perform 5 cycles with TMS=1 # to move the remote TAP controller back to 'test_logic_reset' state # do both for now if not self._ftdi: raise JtagError("FTDI controller terminated") if self._trst: # nTRST value = 0 cmd = Array('B', [Ftdi.SET_BITS_LOW, value, self.direction]) self._ftdi.write_data(cmd) time.sleep(0.1) # nTRST should be left to the high state value = JtagController.TRST_BIT cmd = Array('B', [Ftdi.SET_BITS_LOW, value, self.direction]) self._ftdi.write_data(cmd) time.sleep(0.1) # TAP reset (even with HW reset, could be removed though) self.write_tms(BitSequence('11111')) if sync: self.sync() def sync(self): if not self._ftdi: raise JtagError("FTDI controller terminated") if self._write_buff: self._ftdi.write_data(self._write_buff) self._write_buff = Array('B') def write_tms(self, tms): """Change the TAP controller state""" if not isinstance(tms, BitSequence): raise JtagError('Expect a BitSequence') length = len(tms) if not (0 < length < 8): raise JtagError('Invalid TMS length') out = BitSequence(tms, length=8) # apply the last TDO bit if self._last is not None: out[7] = self._last # print "TMS", tms, (self._last is not None) and 'w/ Last' or '' # reset last bit self._last = None cmd = Array('B', [Ftdi.WRITE_BITS_TMS_NVE, length - 1, out.tobyte()]) self._stack_cmd(cmd) self.sync() #self._ftdi.validate_mpsse() def read(self, length): """Read out a sequence of bits from TDO""" byte_count = length // 8 bit_count = length - 8 * byte_count bs = BitSequence() if byte_count: bytes = self._read_bytes(byte_count) bs.append(bytes) if bit_count: bits = self._read_bits(bit_count) bs.append(bits) return bs def write(self, out, use_last=True): """Write a sequence of bits to TDI""" if isinstance(out, str): if len(out) > 1: self._write_bytes_raw(out[:-1]) out = out[-1] out = BitSequence(bytes_=out) elif not isinstance(out, BitSequence): out = BitSequence(out) if use_last: (out, self._last) = (out[:-1], bool(out[-1])) byte_count = len(out) // 8 pos = 8 * byte_count bit_count = len(out) - pos if byte_count: self._write_bytes(out[:pos]) if bit_count: self._write_bits(out[pos:]) def shift_register(self, out, use_last=False): """Shift a BitSequence into the current register and retrieve the register output""" if not isinstance(out, BitSequence): return JtagError('Expect a BitSequence') length = len(out) if use_last: (out, self._last) = (out[:-1], int(out[-1])) byte_count = len(out) // 8 pos = 8 * byte_count bit_count = len(out) - pos if not byte_count and not bit_count: raise JtagError("Nothing to shift") if byte_count: blen = byte_count - 1 #print "RW OUT %s" % out[:pos] cmd = Array('B', [Ftdi.RW_BYTES_PVE_NVE_LSB, blen, (blen >> 8) & 0xff]) cmd.extend(out[:pos].tobytes(msby=True)) self._stack_cmd(cmd) #print "push %d bytes" % byte_count if bit_count: #print "RW OUT %s" % out[pos:] cmd = Array('B', [Ftdi.RW_BITS_PVE_NVE_LSB, bit_count - 1]) cmd.append(out[pos:].tobyte()) self._stack_cmd(cmd) #print "push %d bits" % bit_count self.sync() bs = BitSequence() byte_count = length // 8 pos = 8 * byte_count bit_count = length - pos if byte_count: data = self._ftdi.read_data_bytes(byte_count, 4) if not data: raise JtagError('Unable to read data from FTDI') byteseq = BitSequence(bytes_=data, length=8 * byte_count) #print "RW IN %s" % byteseq bs.append(byteseq) #print "pop %d bytes" % byte_count if bit_count: data = self._ftdi.read_data_bytes(1, 4) if not data: raise JtagError('Unable to read data from FTDI') byte = data[0] # need to shift bits as they are shifted in from the MSB in FTDI byte >>= 8 - bit_count bitseq = BitSequence(byte, length=bit_count) bs.append(bitseq) #print "pop %d bits" % bit_count if len(bs) != length: raise AssertionError("Internal error") #self._ftdi.validate_mpsse() return bs def _stack_cmd(self, cmd): if not isinstance(cmd, Array): raise TypeError('Expect a byte array') if not self._ftdi: raise JtagError("FTDI controller terminated") # Currrent buffer + new command + send_immediate if (len(self._write_buff) + len(cmd) + 1) >= JtagController.FTDI_PIPE_LEN: self.sync() self._write_buff.extend(cmd) def _read_bits(self, length): """Read out bits from TDO""" data = '' if length > 8: raise JtagError("Cannot fit into FTDI fifo") cmd = Array('B', [Ftdi.READ_BITS_NVE_LSB, length - 1]) self._stack_cmd(cmd) self.sync() data = self._ftdi.read_data_bytes(1, 4) # need to shift bits as they are shifted in from the MSB in FTDI byte = ord(data) >> 8 - bit_count bs = BitSequence(byte, length=length) #print "READ BITS %s" % (bs) return bs def _write_bits(self, out): """Output bits on TDI""" length = len(out) byte = out.tobyte() #print "WRITE BITS %s" % out cmd = Array('B', [Ftdi.WRITE_BITS_NVE_LSB, length - 1, byte]) self._stack_cmd(cmd) def _read_bytes(self, length): """Read out bytes from TDO""" data = '' if length > JtagController.FTDI_PIPE_LEN: raise JtagError("Cannot fit into FTDI fifo") alen = length - 1 cmd = Array('B', [Ftdi.READ_BYTES_NVE_LSB, alen & 0xff, (alen >> 8) & 0xff]) self._stack_cmd(cmd) self.sync() data = self._ftdi.read_data_bytes(length, 4) bs = BitSequence(bytes_=data, length=8 * length) #print "READ BYTES %s" % bs return bs def _write_bytes(self, out): """Output bytes on TDI""" bytes_ = out.tobytes(msby=True) # don't ask... olen = len(bytes_) - 1 #print "WRITE BYTES %s" % out cmd = Array( 'B', [Ftdi.WRITE_BYTES_NVE_LSB, olen & 0xff, (olen >> 8) & 0xff]) cmd.extend(bytes_) self._stack_cmd(cmd) def _write_bytes_raw(self, out): """Output bytes on TDI""" olen = len(out) - 1 cmd = Array( 'B', [Ftdi.WRITE_BYTES_NVE_LSB, olen & 0xff, (olen >> 8) & 0xff]) cmd.fromstring(out) self._stack_cmd(cmd)
class SpiController(object): """SPI master. :param silent_clock: should be set to avoid clocking out SCLK when all /CS signals are released. This clock beat is used to enforce a delay between /CS signal activation. When weird SPI devices are used, SCLK beating may cause trouble. In this case, silent_clock should be set but beware that SCLK line should be fitted with a pull-down resistor, as SCLK is high-Z during this short period of time. :param cs_count: is the number of /CS lines (one per device to drive on the SPI bus) """ SCK_BIT = 0x01 DO_BIT = 0x02 DI_BIT = 0x04 CS_BIT = 0x08 PAYLOAD_MAX_LENGTH = 0x10000 # 16 bits max def __init__(self, silent_clock=False, cs_count=4): self._ftdi = Ftdi() self._cs_bits = ((SpiController.CS_BIT << cs_count) - 1) & \ ~(SpiController.CS_BIT - 1) self._ports = [None] * cs_count self._direction = self._cs_bits | \ SpiController.DO_BIT | \ SpiController.SCK_BIT self._cs_high = Array('B') if silent_clock: # Set SCLK as input to avoid emitting clock beats self._cs_high.extend([Ftdi.SET_BITS_LOW, self._cs_bits, self._direction & ~SpiController.SCK_BIT]) # /CS to SCLK delay, use 8 clock cycles as a HW tempo self._cs_high.extend([Ftdi.WRITE_BITS_TMS_NVE, 8-1, 0xff]) # Restore idle state self._cs_high.extend([Ftdi.SET_BITS_LOW, self._cs_bits, self._direction]) self._immediate = Array('B', [Ftdi.SEND_IMMEDIATE]) self._frequency = 0.0 def configure(self, vendor, product, interface, frequency=6.0E6): """Configure the FTDI interface as a SPI master""" self._frequency = \ self._ftdi.open_mpsse(vendor, product, interface, direction=self._direction, initial=self._cs_bits, # /CS all high frequency=frequency) def terminate(self): """Close the FTDI interface""" if self._ftdi: self._ftdi.close() self._ftdi = None def get_port(self, cs): """Obtain a SPI port to drive a SPI device selected by cs""" if not self._ftdi: raise SpiIOError("FTDI controller not initialized") if cs >= len(self._ports): raise SpiIOError("No such SPI port") if not self._ports[cs]: cs_state = 0xFF & ~((SpiController.CS_BIT<<cs) | SpiController.SCK_BIT | SpiController.DO_BIT) cs_cmd = Array('B', [Ftdi.SET_BITS_LOW, cs_state, self._direction]) self._ports[cs] = SpiPort(self, cs_cmd) self._flush() return self._ports[cs] @property def frequency_max(self): """Returns the maximum SPI clock""" return self._ftdi.frequency_max @property def frequency(self): """Returns the current SPI clock""" return self._frequency def _exchange(self, frequency, cs_cmd, out, readlen): """Perform a half-duplex transaction with the SPI slave""" if not self._ftdi: raise SpiIOError("FTDI controller not initialized") if len(out) > SpiController.PAYLOAD_MAX_LENGTH: raise SpiIOError("Output payload is too large") if readlen > SpiController.PAYLOAD_MAX_LENGTH: raise SpiIOError("Input payload is too large") if self._frequency != frequency: freq = self._ftdi.set_frequency(frequency) # store the requested value, not the actual one (best effort) self._frequency = frequency write_cmd = struct.pack('<BH', Ftdi.WRITE_BYTES_NVE_MSB, len(out)-1) if readlen: read_cmd = struct.pack('<BH', Ftdi.READ_BYTES_NVE_MSB, readlen-1) cmd = Array('B', cs_cmd) cmd.fromstring(write_cmd) cmd.extend(out) cmd.fromstring(read_cmd) cmd.extend(self._immediate) cmd.extend(self._cs_high) self._ftdi.write_data(cmd) # USB read cycle may occur before the FTDI device has actually # sent the data, so try to read more than once if no data is # actually received data = self._ftdi.read_data_bytes(readlen, 4) else: cmd = Array('B', cs_cmd) cmd.fromstring(write_cmd) cmd.extend(out) cmd.extend(self._cs_high) self._ftdi.write_data(cmd) data = Array('B') return data def _flush(self): """Flush the HW FIFOs""" self._ftdi.write_data(self._immediate) self._ftdi.purge_buffers()
class Tunnel_DAC(Instrument): def __init__(self, name, serial = None, channel = 'A0', numdacs=3, delay = 1e-3): ''' discover and initialize Tunnel_DAC hardware Input: serial - serial number of the FTDI converter channel - 2 character channel id the DAC is connected to; the first byte identifies the channel (A..D for current devices) the second byte identifies the bit within that channel (0..7) numdacs - number of DACs daisy-chained on that line delay - communications delay assumed between PC and the USB converter ''' logging.info(__name__+ ': Initializing instrument Tunnel_DAC') Instrument.__init__(self, name, tags=['physical']) self._conn = Ftdi() # VIDs and PIDs of converters used vps = [ (0x0403, 0x6011), # FTDI UM4232H 4ch (0x0403, 0x6014) # FTDI UM232H 1ch ] # explicitly clear device cache of UsbTools #UsbTools.USBDEVICES = [] # find all devices and obtain serial numbers devs = self._conn.find_all(vps) # filter list by serial number if provided if(serial != None): devs = [dev for dev in devs if dev[2] == serial] if(len(devs) == 0): logging.error(__name__ + ': failed to find matching FTDI devices.') elif(len(devs) > 1): logging.error(__name__ + ': more than one converter found and no serial number given.') logging.info(__name__ + ': available devices are: %s.'%str([dev[2] for dev in devs])) vid, pid, self._serial, channels, description = devs[0] # parse channel string if(len(channel) != 2): logging.error(__name__ + ': channel identifier must be a string of length 2. ex. A0, D5.') self._channel = 1 + ord(channel[0]) - ord('A') self._bit = ord(channel[1]) - ord('0') if((self._channel < 1) or (self._channel > channels)): logging.error(__name__ + ': channel %c is not supported by this device.'%(chr(ord('A')+self._channel-1))) if((self._bit < 0) or (self._bit > 7)): logging.error(__name__ + ': subchannel must be between 0 and 7, not %d.'%self._bit) # open device self._conn.open(vid, pid, interface = self._channel, serial = self._serial) logging.info(__name__ + ': using converter with serial #%s'%self._serial) self._conn.set_bitmode(0xFF, Ftdi.BITMODE_BITBANG) # 80k generates bit durations of 12.5us, 80 is magic :( # magic?: 4 from incorrect BITBANG handling of pyftdi, 2.5 from 120MHz instead of 48MHz clock of H devices # original matlab code uses 19kS/s self._conn.set_baudrate(19000/80) # house keeping self._numdacs = numdacs self._sleeptime = (10. + 16.*self._numdacs)*12.5e-6 + delay # 1st term from hardware parameters, 2nd term from USB self._minval = -5000. self._maxval = 5000. self._resolution = 16 # DAC resolution in bits self._voltages = [0.]*numdacs self.add_parameter('voltage', type=types.FloatType, flags=Instrument.FLAG_SET, channels=(1, self._numdacs), minval = self._minval, maxval = self._maxval, units='mV', format = '%.02f') # tags=['sweep'] self.add_function('set_voltages') self.add_function('commit') def _encode(self, data, channel = 0, bits_per_item = 16, big_endian = True): ''' convert binary data into line symbols the tunnel electronic DAC logic box triggers on rising signal edges and samples data after 18us. we use a line code with three bits of duration 12us, where a logical 1 is encoded as B"110" and a logical 0 is encoded as B"100". the line data is returned as a byte string with three bytes/symbol. Input: data - a vector of data entities (usually a string or list of integers) channel - a number in [0, 7] specifying the output bit on the USB-to-UART chip bits_per_item - number of bits to extract from each element of the data vector ''' # build line code for the requested channel line_1 = chr(1<<channel) line_0 = chr(0) line_code = [''.join([line_0, line_1, line_1]), ''.join([line_0, line_1, line_0])] # do actual encoding result = [] result.append(10*line_0) for item in data: for bit in (range(bits_per_item-1, -1, -1) if big_endian else range(0, bits_per_item)): result.append(line_code[1 if(item & (1<<bit)) else 0]) result.append(10*line_0) return ''.join(result) def commit(self): ''' send updated parameter values to the physical DACs via USB ''' # normalize, scale, clip voltages voltages = [-1+2*(x-self._minval)/(self._maxval-self._minval) for x in self._voltages] voltages = [max(-2**(self._resolution-1), min(2**(self._resolution-1)-1, int(2**(self._resolution-1)*x))) for x in voltages] # encode and send data = self._encode(reversed(voltages), self._bit, self._resolution) self._conn.write_data(data) # wait for the FTDI fifo to clock the data to the DACs sleep(self._sleeptime) def do_set_voltage(self, value, channel): ''' immediately update voltage on channel ch parameter checking is done by qtlab ''' self._voltages[channel-1] = value self.commit() def set_voltages(self, valuedict): ''' update voltages on several channels simultaneously todo: update instrument panel display ''' for channel, value in valuedict.iteritems(): # bounds checking & clipping if((channel < 1) or (channel > self._numdacs)): logging.error(__name__ + ': channel %d out of range.'%channel) continue value = float(value) if((value < self._minval) or (value >= self._maxval)): logging.error(__name__ + ': value %f out of range. clipping.'%value) value = max(self._minval, min(self._maxval, value)) # does not handle maxval correctly self._voltages[channel-1] = value self.commit()
class Dionysus(Olympus): """Dionysus Concrete Class that implements Dionysus specific communication functions """ def __init__(self, idVendor=0x0403, idProduct=0x8530, debug = False): Olympus.__init__(self, debug) self.vendor = idVendor self.product = idProduct self.dev = Ftdi() self._open_dev() self.name = "Dionysus" def __del__(self): self.dev.close() def _open_dev(self): """_open_dev Open an FTDI communication channel Args: Nothing Returns: Nothing Raises: Exception """ frequency = 30.0E6 #Latency can go down t 2 but when set there is a small chance that there is a crash latency = 4 self.dev.open(self.vendor, self.product, 0) # Drain input buffer self.dev.purge_buffers() # Reset # Enable MPSSE mode self.dev.set_bitmode(0x00, Ftdi.BITMODE_SYNCFF) # Configure clock frequency = self.dev._set_frequency(frequency) # Set latency timer self.dev.set_latency_timer(latency) # Set chunk size self.dev.write_data_set_chunksize(0x10000) self.dev.read_data_set_chunksize(0x10000) self.dev.set_flowctrl('hw') self.dev.purge_buffers() def read(self, device_id, address, length = 1, mem_device = False): """read read data from the Olympus image Args: device_id: Device identification number, found in the DRT address: Address of the register/memory to read mem_device: True if the device is on the memory bus length: Number of 32 bit words to read from the FPGA Returns: A byte array containing the raw data returned from Olympus Raises: OlympusCommError """ read_data = Array('B') write_data = Array('B', [0xCD, 0x02]) if mem_device: if self.debug: print "memory device" write_data = Array ('B', [0xCD, 0x12]) fmt_string = "%06X" % (length) write_data.fromstring(fmt_string.decode('hex')) offset_string = "00" if not mem_device: offset_string = "%02X" % device_id write_data.fromstring(offset_string.decode('hex')) addr_string = "%06X" % address write_data.fromstring(addr_string.decode('hex')) if self.debug: print "data read string: " + str(write_data) self.dev.purge_buffers() self.dev.write_data(write_data) timeout = time.time() + self.read_timeout rsp = Array('B') while time.time() < timeout: response = self.dev.read_data(1) if len(response) > 0: rsp = Array('B') rsp.fromstring(response) if rsp[0] == 0xDC: if self.debug: print "Got a response" break if len(rsp) > 0: if rsp[0] != 0xDC: if self.debug: print "Response not found" raise OlympusCommError("Did not find identification byte (0xDC): %s" % str(rsp)) else: if self.debug: print "No Response found" raise OlympusCommError("Timeout while waiting for a response") #I need to watch out for the modem status bytes read_count = 0 response = Array('B') rsp = Array('B') timeout = time.time() + self.read_timeout while (time.time() < timeout) and (read_count < (length * 4 + 8)): response = self.dev.read_data((length * 4 + 8 ) - read_count) temp = Array('B') temp.fromstring(response) #print "temp: %s", str(temp) if (len(temp) > 0): rsp += temp read_count = len(rsp) if self.debug: print "read length = %d, total length = %d" % (len(rsp), (length * 4 + 8)) print "time left on timeout: %d" % (timeout - time.time()) if self.debug: print "response length: " + str(length * 4 + 8) print "response status:\n\t" + str(rsp[:8]) print "response data:\n" + str(rsp[8:]) return rsp[8:] def write(self, device_id, address, data=None, mem_device = False): """write Write data to an Olympus image Args: device_id: Device identification number, found in the DRT address: Address of the register/memory to read mem_device: True if the device is on the memory bus data: Array of raw bytes to send to the device Returns: Nothing Raises: OlympusCommError """ length = len(data) / 4 # ID 01 NN NN NN OO AA AA AA DD DD DD DD # ID = ID BYTE (0xCD) # 01 = Write Command # NN = Size of write (3 bytes) # OO = Offset of device # AA = Address (4 bytes) # DD = Data (4 bytes) #create an array with the identification byte (0xCD) #and code for write (0x01) data_out = Array('B', [0xCD, 0x01]) if mem_device: if self.debug: print "memory device" data_out = Array ('B', [0xCD, 0x11]) """ print "write command:\n\t" + str(data_out[:9]) for i in range (0, len(data_out)): print str(hex(data_out[i])) + ", ", print " " """ #append the length into the frist 32 bits fmt_string = "%06X" % (length) data_out.fromstring(fmt_string.decode('hex')) offset_string = "00" if not mem_device: offset_string = "%02X" % device_id data_out.fromstring(offset_string.decode('hex')) addr_string = "%06X" % address data_out.fromstring(addr_string.decode('hex')) data_out.extend(data) """ #if (self.debug): print "data write string:\n" print "write command:\n\t" + str(data_out[:9]) for i in range (0, 9): print str(hex(data_out[i])) + ", ", print " " """ #print "write data:\n" + str(data_out[9:]) #avoid the akward stale bug self.dev.purge_buffers() self.dev.write_data(data_out) rsp = Array('B') timeout = time.time() + self.read_timeout while time.time() < timeout: response = self.dev.read_data(1) if len(response) > 0: rsp = Array('B') rsp.fromstring(response) if rsp[0] == 0xDC: if self.debug: print "Got a response" break if (len(rsp) > 0): if rsp[0] != 0xDC: if self.debug: print "Response not found" raise OlympusCommError("Did not find identification byte (0xDC): %s" % str(rsp)) else: if self.debug: print "No Response" raise OlympusCommError("Timeout while waiting for a response") response = self.dev.read_data(8) rsp = Array('B') rsp.fromstring(response) if self.debug: print "Response: " + str(rsp) def ping(self): """ping Pings the Olympus image Args: Nothing Returns: Nothing Raises: OlympusCommError """ data = Array('B') data.extend([0XCD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); if self.debug: print "Sending ping...", self.dev.write_data(data) rsp = Array('B') temp = Array('B') timeout = time.time() + self.read_timeout while time.time() < timeout: response = self.dev.read_data(5) if self.debug: print ".", rsp = Array('B') rsp.fromstring(response) temp.extend(rsp) if 0xDC in rsp: if self.debug: print "Got a response" print "Response: %s" % str(temp) break if not 0xDC in rsp: if self.debug: print "ID byte not found in response" print "temp: " + str(temp) raise OlympusCommError("Ping response did not contain ID: %s" % str(temp)) index = rsp.index(0xDC) + 1 read_data = Array('B') read_data.extend(rsp[index:]) num = 3 - index read_data.fromstring(self.dev.read_data(num)) if self.debug: print "Success!" return def reset(self): """reset Software reset the Olympus FPGA Master, this may not actually reset the entire FPGA image Args: Nothing Returns: Nothing Raises: OlympusCommError: A failure of communication is detected """ data = Array('B') data.extend([0XCD, 0x03, 0x00, 0x00, 0x00]); if self.debug: print "Sending reset..." self.dev.purge_buffers() self.dev.write_data(data) def dump_core(self): """dump_core reads the state of the wishbone master prior to a reset, useful for debugging Args: Nothing Returns: Array of 32-bit values to be parsed by core_analyzer Raises: AssertionError: This function must be overriden by a board specific implementation OlympusCommError: A failure of communication is detected """ data = Array('B') data.extend([0xCD, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); print "Sending core dump request..." self.dev.purge_buffers() self.dev.write_data(data) core_dump = Array('L') wait_time = 5 timeout = time.time() + wait_time temp = Array ('B') while time.time() < timeout: response = self.dev.read_data(1) rsp = Array('B') rsp.fromstring(response) temp.extend(rsp) if 0xDC in rsp: print "Got a response" break if not 0xDC in rsp: print "Response not found" raise OlympusCommError("Response Not Found") rsp = Array('B') read_total = 4 read_count = len(rsp) #get the number of items from the address timeout = time.time() + wait_time while (time.time() < timeout) and (read_count < read_total): response = self.dev.read_data(read_total - read_count) temp = Array('B') temp.fromstring(response) if (len(temp) > 0): rsp += temp read_count = len(rsp) print "Length of read: %d" % len(rsp) print "Data: %s" % str(rsp) count = ( rsp[1] << 16 | rsp[2] << 8 | rsp[3]) * 4 print "Number of core registers: %d" % (count / 4) #get the core dump data timeout = time.time() + wait_time read_total = count read_count = 0 temp = Array ('B') rsp = Array('B') while (time.time() < timeout) and (read_count < read_total): response = self.dev.read_data(read_total - read_count) temp = Array('B') temp.fromstring(response) if (len(temp) > 0): rsp += temp read_count = len(rsp) print "Length read: %d" % (len(rsp) / 4) print "Data: %s" % str(rsp) core_data = Array('L') for i in range (0, count, 4): print "count: %d" % i core_data.append(rsp[i] << 24 | rsp[i + 1] << 16 | rsp[i + 2] << 8 | rsp[i + 3]) #if self.debug: print "core data: " + str(core_data) return core_data def wait_for_interrupts(self, wait_time = 1): """wait_for_interrupts listen for interrupts for the specified amount of time Args: wait_time: the amount of time in seconds to wait for an interrupt Returns: True: Interrupts were detected False: No interrupts detected Raises: Nothing """ timeout = time.time() + wait_time temp = Array ('B') while time.time() < timeout: response = self.dev.read_data(1) rsp = Array('B') rsp.fromstring(response) temp.extend(rsp) if 0xDC in rsp: if self.debug: print "Got a response" break if not 0xDC in rsp: if self.debug: print "Response not found" return False read_total = 9 read_count = len(rsp) #print "read_count: %s" % str(rsp) while (time.time() < timeout) and (read_count < read_total): response = self.dev.read_data(read_total - read_count) temp = Array('B') temp.fromstring(response) #print "temp: %s", str(temp) if (len(temp) > 0): rsp += temp read_count = len(rsp) #print "read_count: %s" % str(rsp) index = rsp.index(0xDC) + 1 read_data = Array('B') read_data.extend(rsp[index:]) #print "read_data: " + str(rsp) self.interrupts = read_data[-4] << 24 | read_data[-3] << 16 | read_data[-2] << 8 | read_data[-1] if self.debug: print "interrupts: " + str(self.interrupts) return True def comm_debug(self): """comm_debug A function that the end user will probably not interract with This is here to simply debug a communication medium Args: Nothing Returns: Nothing Raises: Nothing """ #self.dev.set_dtr_rts(True, True) #self.dev.set_dtr(False) print "CTS: " + str(self.dev.get_cts()) # print "DSR: " + str(self.dev.get_dsr()) s1 = self.dev.modem_status() print "S1: " + str(s1)
class USBDevice(Device): """ `AD2USB`_ device utilizing PyFTDI's interface. """ # Constants PRODUCT_IDS = ((0x0403, 0x6001), (0x0403, 0x6015)) """List of Vendor and Product IDs used to recognize `AD2USB`_ devices.""" DEFAULT_VENDOR_ID = PRODUCT_IDS[0][0] """Default Vendor ID used to recognize `AD2USB`_ devices.""" DEFAULT_PRODUCT_ID = PRODUCT_IDS[0][1] """Default Product ID used to recognize `AD2USB`_ devices.""" # Deprecated constants FTDI_VENDOR_ID = DEFAULT_VENDOR_ID """DEPRECATED: Vendor ID used to recognize `AD2USB`_ devices.""" FTDI_PRODUCT_ID = DEFAULT_PRODUCT_ID """DEPRECATED: Product ID used to recognize `AD2USB`_ devices.""" BAUDRATE = 115200 """Default baudrate for `AD2USB`_ devices.""" __devices = [] __detect_thread = None @classmethod def find_all(cls, vid=None, pid=None): """ Returns all FTDI devices matching our vendor and product IDs. :returns: list of devices :raises: :py:class:`~alarmdecoder.util.CommError` """ if not have_pyftdi: raise ImportError('The USBDevice class has been disabled due to missing requirement: pyftdi or pyusb.') cls.__devices = [] query = cls.PRODUCT_IDS if vid and pid: query = [(vid, pid)] try: cls.__devices = Ftdi.find_all(query, nocache=True) except (usb.core.USBError, FtdiError) as err: raise CommError('Error enumerating AD2USB devices: {0}'.format(str(err)), err) return cls.__devices @classmethod def devices(cls): """ Returns a cached list of `AD2USB`_ devices located on the system. :returns: cached list of devices found """ return cls.__devices @classmethod def find(cls, device=None): """ Factory method that returns the requested :py:class:`USBDevice` device, or the first device. :param device: Tuple describing the USB device to open, as returned by find_all(). :type device: tuple :returns: :py:class:`USBDevice` object utilizing the specified device :raises: :py:class:`~alarmdecoder.util.NoDeviceError` """ if not have_pyftdi: raise ImportError('The USBDevice class has been disabled due to missing requirement: pyftdi or pyusb.') cls.find_all() if len(cls.__devices) == 0: raise NoDeviceError('No AD2USB devices present.') if device is None: device = cls.__devices[0] vendor, product, sernum, ifcount, description = device return USBDevice(interface=sernum, vid=vendor, pid=product) @classmethod def start_detection(cls, on_attached=None, on_detached=None): """ Starts the device detection thread. :param on_attached: function to be called when a device is attached **Callback definition:** *def callback(thread, device)* :type on_attached: function :param on_detached: function to be called when a device is detached **Callback definition:** *def callback(thread, device)* :type on_detached: function """ if not have_pyftdi: raise ImportError('The USBDevice class has been disabled due to missing requirement: pyftdi or pyusb.') cls.__detect_thread = USBDevice.DetectThread(on_attached, on_detached) try: cls.find_all() except CommError: pass cls.__detect_thread.start() @classmethod def stop_detection(cls): """ Stops the device detection thread. """ if not have_pyftdi: raise ImportError('The USBDevice class has been disabled due to missing requirement: pyftdi or pyusb.') try: cls.__detect_thread.stop() except Exception: pass @property def interface(self): """ Retrieves the interface used to connect to the device. :returns: the interface used to connect to the device """ return self._interface @interface.setter def interface(self, value): """ Sets the interface used to connect to the device. :param value: may specify either the serial number or the device index :type value: string or int """ self._interface = value if isinstance(value, int): self._device_number = value else: self._serial_number = value @property def serial_number(self): """ Retrieves the serial number of the device. :returns: serial number of the device """ return self._serial_number @serial_number.setter def serial_number(self, value): """ Sets the serial number of the device. :param value: serial number of the device :type value: string """ self._serial_number = value @property def description(self): """ Retrieves the description of the device. :returns: description of the device """ return self._description @description.setter def description(self, value): """ Sets the description of the device. :param value: description of the device :type value: string """ self._description = value def __init__(self, interface=0, vid=None, pid=None): """ Constructor :param interface: May specify either the serial number or the device index. :type interface: string or int """ if not have_pyftdi: raise ImportError('The USBDevice class has been disabled due to missing requirement: pyftdi or pyusb.') Device.__init__(self) self._device = Ftdi() self._interface = 0 self._device_number = 0 self._serial_number = None self._vendor_id = USBDevice.DEFAULT_VENDOR_ID if vid: self._vendor_id = vid self._product_id = USBDevice.DEFAULT_PRODUCT_ID if pid: self._product_id = pid self._endpoint = 0 self._description = None self.interface = interface def open(self, baudrate=BAUDRATE, no_reader_thread=False): """ Opens the device. :param baudrate: baudrate to use :type baudrate: int :param no_reader_thread: whether or not to automatically start the reader thread. :type no_reader_thread: bool :raises: :py:class:`~alarmdecoder.util.NoDeviceError` """ # Set up defaults if baudrate is None: baudrate = USBDevice.BAUDRATE self._read_thread = Device.ReadThread(self) # Open the device and start up the thread. try: self._device.open(self._vendor_id, self._product_id, self._endpoint, self._device_number, self._serial_number, self._description) self._device.set_baudrate(baudrate) if not self._serial_number: self._serial_number = self._get_serial_number() self._id = self._serial_number except (usb.core.USBError, FtdiError) as err: raise NoDeviceError('Error opening device: {0}'.format(str(err)), err) except KeyError as err: raise NoDeviceError('Unsupported device. ({0:04x}:{1:04x}) You probably need a newer version of pyftdi.'.format(err[0][0], err[0][1])) else: self._running = True self.on_open() if not no_reader_thread: self._read_thread.start() return self def close(self): """ Closes the device. """ try: Device.close(self) # HACK: Probably should fork pyftdi and make this call in .close() self._device.usb_dev.attach_kernel_driver(self._device_number) except Exception: pass def fileno(self): """ File number not supported for USB devices. :raises: NotImplementedError """ raise NotImplementedError('USB devices do not support fileno()') def write(self, data): """ Writes data to the device. :param data: data to write :type data: string :raises: :py:class:`~alarmdecoder.util.CommError` """ try: self._device.write_data(data) self.on_write(data=data) except FtdiError as err: raise CommError('Error writing to device: {0}'.format(str(err)), err) def read(self): """ Reads a single character from the device. :returns: character read from the device :raises: :py:class:`~alarmdecoder.util.CommError` """ ret = None try: ret = self._device.read_data(1) except (usb.core.USBError, FtdiError) as err: raise CommError('Error reading from device: {0}'.format(str(err)), err) return ret def read_line(self, timeout=0.0, purge_buffer=False): """ Reads a line from the device. :param timeout: read timeout :type timeout: float :param purge_buffer: Indicates whether to purge the buffer prior to reading. :type purge_buffer: bool :returns: line that was read :raises: :py:class:`~alarmdecoder.util.CommError`, :py:class:`~alarmdecoder.util.TimeoutError` """ def timeout_event(): """Handles read timeout event""" timeout_event.reading = False timeout_event.reading = True if purge_buffer: self._buffer = b'' got_line, ret = False, None timer = threading.Timer(timeout, timeout_event) if timeout > 0: timer.start() try: while timeout_event.reading: buf = self._device.read_data(1) if buf != b'': ub = bytes_hack(buf) self._buffer += ub if ub == b"\n": self._buffer = self._buffer.rstrip(b"\r\n") if len(self._buffer) > 0: got_line = True break else: time.sleep(0.01) except (usb.core.USBError, FtdiError) as err: raise CommError('Error reading from device: {0}'.format(str(err)), err) else: if got_line: ret, self._buffer = self._buffer, b'' self.on_read(data=ret) else: raise TimeoutError('Timeout while waiting for line terminator.') finally: timer.cancel() return ret def purge(self): """ Purges read/write buffers. """ self._device.purge_buffers() def _get_serial_number(self): """ Retrieves the FTDI device serial number. :returns: string containing the device serial number """ return usb.util.get_string(self._device.usb_dev, 64, self._device.usb_dev.iSerialNumber) class DetectThread(threading.Thread): """ Thread that handles detection of added/removed devices. """ on_attached = event.Event("This event is called when an `AD2USB`_ device has been detected.\n\n**Callback definition:** def callback(thread, device*") on_detached = event.Event("This event is called when an `AD2USB`_ device has been removed.\n\n**Callback definition:** def callback(thread, device*") def __init__(self, on_attached=None, on_detached=None): """ Constructor :param on_attached: Function to call when a device is attached **Callback definition:** *def callback(thread, device)* :type on_attached: function :param on_detached: Function to call when a device is detached **Callback definition:** *def callback(thread, device)* :type on_detached: function """ threading.Thread.__init__(self) if on_attached: self.on_attached += on_attached if on_detached: self.on_detached += on_detached self._running = False def stop(self): """ Stops the thread. """ self._running = False def run(self): """ The actual detection process. """ self._running = True last_devices = set() while self._running: try: current_devices = set(USBDevice.find_all()) for dev in current_devices.difference(last_devices): self.on_attached(device=dev) for dev in last_devices.difference(current_devices): self.on_detached(device=dev) last_devices = current_devices except CommError: pass time.sleep(0.25)