EEVblog Electronics Community Forum

Products => Test Equipment => Topic started by: rgaddi on November 29, 2018, 01:19:38 am

Title: Rigol DS4000 pass/fail info and question
Post by: rgaddi on November 29, 2018, 01:19:38 am
First, since I'm new here, I should probably lead by being useful, so the next post below will be my Python code for working with Rigol DS4000 pass/fail masks, having reverse-engineered the file format as well as that ever goes (i.e. there's nothing I know to be wrong).

That accomplished, anyone know much about using those masks in automated test environments?  What I'd like to do is stop on mask fail, have the test software detect that the mask has failed, grab and log the screen image off the scope, and start the mask running again.  What I'm finding in practice is that I can't get it running again after a fail.  Anyone know which command I should be using for that?  I feel like I've tried MASK:OPER RUN and :RUN and combinations of the both of them in various orders, with and without some pauses, and I'm just getting there.
Title: Re: Rigol DS4000 pass/fail info and question
Post by: rgaddi on November 29, 2018, 01:23:50 am
Pass/fail mask reverse-engineering, presented in Python.  Boy it would have been easier if my Rigol rep had just been willing to tell me.

Code: (python) [Select]
class PassFailData(bytearray):
    """Represents Rigol pass/fail data.
    Treated as a bytearray, the layout of the mask data is:
    * An 8 byte constant header
    * Little-endian uint16 WINDOW_ON, where 0 is no window (full-screen) and
      1 is a window
    * Little-endian uint16 WINDOW_LEFT (0-699), ignored if not WINDOW_ON
    * Little-endian uint16 WINDOW_RIGHT (0-699), ignored if not WINDOW_ON
    * 700 pairs of (LOWER, UPPER), where each is a byte from 0 (bottom of the
      screen) to 255 (top of the screen), working from the left of the screen
      on to the right.  Pairs that index left of WINDOW_LEFT or right of
      WINDOW_RIGHT should be (0, 255) so as to enforce no mask.
        arg (str, bytes): If arg is string, it's interpreted as the name of
            a .pf file to open.  If it's bytes (or a bytearray) then it's the
            raw data that such a file would contain.  The default, None,
            creates an empty mask.
    .. note::
       This is EXTREMELY experimental.  All information about the file
       format has been derived through reverse-engineering and emperical
       guessing.  Test thoroughly before any kind of production use.
    DATALEN = 1400
    # First 8 bytes of the file are constant.
    HEADER = b'\x03\x00\x00\x00x\x05\x0e\x00'
    def __init__(self, arg=None):
        # Set up the alternate views onto this array.  There is an 8 byte
        # constant header, then 3 little endian uint16s representing
        #  window on?  0:false, 1: true
        #  window left: 0-699
        #  window right: 0-699
        self[:] = bytes(self.FILELEN)
        self._params = np.frombuffer(self, dtype="<u2", count=3, offset=8)
        self._maskdata = np.frombuffer(self, dtype=np.uint8, offset=14)
        if arg is None:
            self[:len(self.HEADER)] = self.HEADER
            self.upper[:] = 0xFF
        elif isinstance(arg, str):
        elif isinstance(arg, (bytearray, bytes)):
            raise TypeError('invalid arg ' + type(arg).__name__)
    def setdata(self, data):
        if len(data) != self.FILELEN:
            raise ValueError("Incorrect mask data length")
        if not data.startswith(self.HEADER):
            log.warning("Bad mask header")
        self[:] = data
    def __repr__(self):
        wnd = self.window
        if wnd:
            return '{}({}-{})'.format(type(self).__name__, wnd[0], wnd[1])
            return '{}(all)'.format(type(self).__name__)
    def read(self, filename):
        """Read a .pf file."""
        with open(filename, 'rb') as f:
            self.setdata( + 1))

    def window(self):
        """Pass/fail window extents.
        Either None or a 2-tuple of (left, right), where both left and right
        are in the range 0-699.
        if self._params[0]:
            return tuple(self._params[1:])
            return None
    def window(self, value):
        if value is None:
            self._params[0] = 0
        elif len(value) != 2:
            raise ValueError('window must be None or 2-tuple')
        elif all(0 <= v < 700 for v in value):
            self._params[0] = 1
            self._params[1:] = value
            raise ValueError('window must have 0 <= left, right < 700')

    def lower(self):
        """Upper mask limit.
        700 data points in the range 0-255.
        return self._maskdata[0::2]
    def upper(self):
        """Upper mask limit.
        700 data points in the range 0-255.
        return self._maskdata[1::2]