diff --git a/libsigrokdecode4DSL/decoders/0-i2c/__init__.py b/libsigrokdecode4DSL/decoders/0-i2c/__init__.py new file mode 100644 index 00000000..2a36b060 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/0-i2c/__init__.py @@ -0,0 +1,25 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2012 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +I²C (Inter-Integrated Circuit) is a bidirectional, multi-master +bus using two signals (SCL = serial clock line, SDA = serial data line). +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/0-i2c/pd.py b/libsigrokdecode4DSL/decoders/0-i2c/pd.py new file mode 100644 index 00000000..dad9922d --- /dev/null +++ b/libsigrokdecode4DSL/decoders/0-i2c/pd.py @@ -0,0 +1,259 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2010-2016 Uwe Hermann +## Copyright (C) 2019 DreamSourceLab +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +# TODO: Look into arbitration, collision detection, clock synchronisation, etc. +# TODO: Implement support for inverting SDA/SCL levels (0->1 and 1->0). +# TODO: Implement support for detecting various bus errors. + +import sigrokdecode as srd + +''' +OUTPUT_PYTHON format: + +Packet: +[, ] + +: + - 'START' (START condition) + - 'START REPEAT' (Repeated START condition) + - 'ADDRESS READ' (Slave address, read) + - 'ADDRESS WRITE' (Slave address, write) + - 'DATA READ' (Data, read) + - 'DATA WRITE' (Data, write) + - 'STOP' (STOP condition) + - 'ACK' (ACK bit) + - 'NACK' (NACK bit) + - 'BITS' (: list of data/address bits and their ss/es numbers) + + is the data or address byte associated with the 'ADDRESS*' and 'DATA*' +command. Slave addresses do not include bit 0 (the READ/WRITE indication bit). +For example, a slave address field could be 0x51 (instead of 0xa2). +For 'START', 'START REPEAT', 'STOP', 'ACK', and 'NACK' is None. +''' + +# CMD: [annotation-type-index, long annotation, short annotation] +proto = { + 'START': [0, 'Start', 'S'], + 'START REPEAT': [1, 'Start repeat', 'Sr'], + 'STOP': [2, 'Stop', 'P'], + 'ACK': [3, 'ACK', 'A'], + 'NACK': [4, 'NACK', 'N'], + 'ADDRESS READ': [5, 'Address read', 'AR'], + 'ADDRESS WRITE': [6, 'Address write', 'AW'], + 'DATA READ': [7, 'Data read', 'DR'], + 'DATA WRITE': [8, 'Data write', 'DW'], +} + +class Decoder(srd.Decoder): + api_version = 3 + id = '0:i2c' + name = '0:I²C' + longname = 'Inter-Integrated Circuit' + desc = 'Two-wire, multi-master, serial bus.' + license = 'gplv2+' + inputs = ['logic'] + outputs = ['i2c'] + tags = ['Embedded/industrial'] + channels = ( + {'id': 'scl', 'type': 8, 'name': 'SCL', 'desc': 'Serial clock line'}, + {'id': 'sda', 'type': 108, 'name': 'SDA', 'desc': 'Serial data line'}, + ) + options = ( + {'id': 'address_format', 'desc': 'Displayed slave address format', + 'default': 'shifted', 'values': ('shifted', 'unshifted')}, + ) + annotations = ( + ('7', 'start', 'Start condition'), + ('6', 'repeat-start', 'Repeat start condition'), + ('1', 'stop', 'Stop condition'), + ('5', 'ack', 'ACK'), + ('0', 'nack', 'NACK'), + ('112', 'address-read', 'Address read'), + ('111', 'address-write', 'Address write'), + ('110', 'data-read', 'Data read'), + ('109', 'data-write', 'Data write'), + ('1000', 'warnings', 'Human-readable warnings'), + ) + annotation_rows = ( + ('addr-data', 'Address/Data', (0, 1, 2, 3, 4, 5, 6, 7, 8)), + ('warnings', 'Warnings', (9,)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.samplerate = None + self.ss = self.es = self.ss_byte = -1 + self.bitcount = 0 + self.databyte = 0 + self.wr = -1 + self.is_repeat_start = 0 + self.state = 'FIND START' + self.pdu_start = None + self.pdu_bits = 0 + self.bits = [] + + def metadata(self, key, value): + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def putx(self, data): + self.put(self.ss, self.es, self.out_ann, data) + + def handle_start(self): + self.ss, self.es = self.samplenum, self.samplenum + self.pdu_start = self.samplenum + self.pdu_bits = 0 + cmd = 'START REPEAT' if (self.is_repeat_start == 1) else 'START' + self.putx([proto[cmd][0], proto[cmd][1:]]) + self.state = 'FIND ADDRESS' + self.bitcount = self.databyte = 0 + self.is_repeat_start = 1 + self.wr = -1 + self.bits = [] + + # Gather 8 bits of data plus the ACK/NACK bit. + def handle_address_or_data(self, scl, sda): + self.pdu_bits += 1 + + # Address and data are transmitted MSB-first. + self.databyte <<= 1 + self.databyte |= sda + + # Remember the start of the first data/address bit. + if self.bitcount == 0: + self.ss_byte = self.samplenum + + # Store individual bits and their start/end samplenumbers. + # In the list, index 0 represents the LSB (I²C transmits MSB-first). + self.bits.insert(0, [sda, self.samplenum, self.samplenum]) + if self.bitcount > 0: + self.bits[1][2] = self.samplenum + if self.bitcount == 7: + self.bitwidth = self.bits[1][2] - self.bits[2][2] + self.bits[0][2] += self.bitwidth + + # Return if we haven't collected all 8 + 1 bits, yet. + if self.bitcount < 7: + self.bitcount += 1 + return + + d = self.databyte + if self.state == 'FIND ADDRESS': + # The READ/WRITE bit is only in address bytes, not data bytes. + self.wr = 0 if (self.databyte & 1) else 1 + if self.options['address_format'] == 'shifted': + d = d >> 1 + + bin_class = -1 + if self.state == 'FIND ADDRESS' and self.wr == 1: + cmd = 'ADDRESS WRITE' + bin_class = 1 + elif self.state == 'FIND ADDRESS' and self.wr == 0: + cmd = 'ADDRESS READ' + bin_class = 0 + elif self.state == 'FIND DATA' and self.wr == 1: + cmd = 'DATA WRITE' + bin_class = 3 + elif self.state == 'FIND DATA' and self.wr == 0: + cmd = 'DATA READ' + bin_class = 2 + + self.ss, self.es = self.ss_byte, self.samplenum + self.bitwidth + + if cmd.startswith('ADDRESS'): + self.ss, self.es = self.samplenum, self.samplenum + self.bitwidth + w = ['Write', 'Wr', 'W'] if self.wr else ['Read', 'Rd', 'R'] + self.putx([proto[cmd][0], w]) + self.ss, self.es = self.ss_byte, self.samplenum + + self.putx([proto[cmd][0], ['%s: {$}' % proto[cmd][1], '%s: {$}' % proto[cmd][2], '{$}', d]]) + + # Done with this packet. + self.bitcount = self.databyte = 0 + self.bits = [] + self.state = 'FIND ACK' + + def get_ack(self, scl, sda): + self.ss, self.es = self.samplenum, self.samplenum + self.bitwidth + cmd = 'NACK' if (sda == 1) else 'ACK' + self.putx([proto[cmd][0], proto[cmd][1:]]) + # There could be multiple data bytes in a row, so either find + # another data byte or a STOP condition next. + self.state = 'FIND DATA' + + def handle_stop(self): + cmd = 'STOP' + self.ss, self.es = self.samplenum, self.samplenum + self.putx([proto[cmd][0], proto[cmd][1:]]) + self.state = 'FIND START' + self.is_repeat_start = 0 + self.wr = -1 + self.bits = [] + + def decode(self): + while True: + # State machine. + if self.state == 'FIND START': + # Wait for a START condition (S): SCL = high, SDA = falling. + self.wait({0: 'h', 1: 'f'}) + self.handle_start() + elif self.state == 'FIND ADDRESS': + # Wait for any of the following conditions (or combinations): + # a) Data sampling of receiver: SCL = rising, and/or + # b) START condition (S): SCL = high, SDA = falling, and/or + # c) STOP condition (P): SCL = high, SDA = rising + (scl, sda) = self.wait([{0: 'r'}, {0: 'h', 1: 'f'}, {0: 'h', 1: 'r'}]) + + # Check which of the condition(s) matched and handle them. + if (self.matched & (0b1 << 0)): + self.handle_address_or_data(scl, sda) + elif (self.matched & (0b1 << 1)): + self.handle_start() + elif (self.matched & (0b1 << 2)): + self.handle_stop() + elif self.state == 'FIND DATA': + # Wait for any of the following conditions (or combinations): + # a) Data sampling of receiver: SCL = rising, and/or + # b) START condition (S): SCL = high, SDA = falling, and/or + # c) STOP condition (P): SCL = high, SDA = rising + (scl, sda) = self.wait([{0: 'r'}, {0: 'h', 1: 'f'}, {0: 'h', 1: 'r'}]) + + # Check which of the condition(s) matched and handle them. + if (self.matched & (0b1 << 0)): + self.handle_address_or_data(scl, sda) + elif (self.matched & (0b1 << 1)): + self.handle_start() + elif (self.matched & (0b1 << 2)): + self.handle_stop() + elif self.state == 'FIND ACK': + # Wait for any of the following conditions (or combinations): + # a) a data/ack bit: SCL = rising. + # b) STOP condition (P): SCL = high, SDA = rising + (scl, sda) = self.wait([{0: 'r'}, {0: 'h', 1: 'r'}]) + if (self.matched & (0b1 << 0)): + self.get_ack(scl, sda) + elif (self.matched & (0b1 << 1)): + self.handle_stop() + diff --git a/libsigrokdecode4DSL/decoders/0-spi/__init__.py b/libsigrokdecode4DSL/decoders/0-spi/__init__.py new file mode 100644 index 00000000..dc5cbc05 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/0-spi/__init__.py @@ -0,0 +1,32 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2012 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +The SPI (Serial Peripheral Interface) protocol decoder supports synchronous +SPI(-like) protocols with a clock line, a MISO and MOSI line for data +transfer in two directions, and an optional CS# pin. + +Either MISO or MOSI (but not both) can be optional. + +If CS# is supplied, data is only decoded when CS# is asserted (clock +transitions where CS# is not asserted are ignored). If CS# is not supplied, +data is decoded on every clock transition (depending on SPI mode). +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/0-spi/pd.py b/libsigrokdecode4DSL/decoders/0-spi/pd.py new file mode 100644 index 00000000..2d397f02 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/0-spi/pd.py @@ -0,0 +1,275 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2011 Gareth McMullin +## Copyright (C) 2012-2014 Uwe Hermann +## Copyright (C) 2019 DreamSourceLab +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd +from collections import namedtuple + +Data = namedtuple('Data', ['ss', 'es', 'val']) + +''' +OUTPUT_PYTHON format: + +Packet: +[, , ] + +: + - 'DATA': contains the MOSI data, contains the MISO data. + The data is _usually_ 8 bits (but can also be fewer or more bits). + Both data items are Python numbers (not strings), or None if the respective + channel was not supplied. + - 'BITS': / contain a list of bit values in this MOSI/MISO data + item, and for each of those also their respective start-/endsample numbers. + - 'CS-CHANGE': is the old CS# pin value, is the new value. + Both data items are Python numbers (0/1), not strings. At the beginning of + the decoding a packet is generated with = None and being the + initial state of the CS# pin or None if the chip select pin is not supplied. + - 'TRANSFER': / contain a list of Data() namedtuples for each + byte transferred during this block of CS# asserted time. Each Data() has + fields ss, es, and val. + +Examples: + ['CS-CHANGE', None, 1] + ['CS-CHANGE', 1, 0] + ['DATA', 0xff, 0x3a] + ['BITS', [[1, 80, 82], [1, 83, 84], [1, 85, 86], [1, 87, 88], + [1, 89, 90], [1, 91, 92], [1, 93, 94], [1, 95, 96]], + [[0, 80, 82], [1, 83, 84], [0, 85, 86], [1, 87, 88], + [1, 89, 90], [1, 91, 92], [0, 93, 94], [0, 95, 96]]] + ['DATA', 0x65, 0x00] + ['DATA', 0xa8, None] + ['DATA', None, 0x55] + ['CS-CHANGE', 0, 1] + ['TRANSFER', [Data(ss=80, es=96, val=0xff), ...], + [Data(ss=80, es=96, val=0x3a), ...]] +''' + +# Key: (CPOL, CPHA). Value: SPI mode. +# Clock polarity (CPOL) = 0/1: Clock is low/high when inactive. +# Clock phase (CPHA) = 0/1: Data is valid on the leading/trailing clock edge. +spi_mode = { + (0, 0): 0, # Mode 0 + (0, 1): 1, # Mode 1 + (1, 0): 2, # Mode 2 + (1, 1): 3, # Mode 3 +} + +class ChannelError(Exception): + pass + +class Decoder(srd.Decoder): + api_version = 3 + id = '0:spi' + name = '0:SPI' + longname = 'Serial Peripheral Interface' + desc = 'Full-duplex, synchronous, serial bus.' + license = 'gplv2+' + inputs = ['logic'] + outputs = ['spi'] + tags = ['Embedded/industrial'] + channels = ( + {'id': 'clk', 'type': 0, 'name': 'CLK', 'desc': 'Clock'}, + ) + optional_channels = ( + {'id': 'miso', 'type': 107, 'name': 'MISO', 'desc': 'Master in, slave out'}, + {'id': 'mosi', 'type': 109, 'name': 'MOSI', 'desc': 'Master out, slave in'}, + {'id': 'cs', 'type': -1, 'name': 'CS#', 'desc': 'Chip-select'}, + ) + options = ( + {'id': 'cs_polarity', 'desc': 'CS# polarity', 'default': 'active-low', + 'values': ('active-low', 'active-high')}, + {'id': 'cpol', 'desc': 'Clock polarity (CPOL)', 'default': 0, + 'values': (0, 1)}, + {'id': 'cpha', 'desc': 'Clock phase (CPHA)', 'default': 0, + 'values': (0, 1)}, + {'id': 'bitorder', 'desc': 'Bit order', + 'default': 'msb-first', 'values': ('msb-first', 'lsb-first')}, + {'id': 'wordsize', 'desc': 'Word size', 'default': 8, + 'values': tuple(range(4,129,1))}, + ) + annotations = ( + ('106', 'miso-data', 'MISO data'), + ('108', 'mosi-data', 'MOSI data'), + ) + annotation_rows = ( + ('miso-data', 'MISO data', (0,)), + ('mosi-data', 'MOSI data', (1,)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.samplerate = None + self.bitcount = 0 + self.misodata = self.mosidata = 0 + self.misobits = [] + self.mosibits = [] + self.ss_block = -1 + self.samplenum = -1 + self.ss_transfer = -1 + self.cs_was_deasserted = False + self.have_cs = self.have_miso = self.have_mosi = None + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + self.bw = (self.options['wordsize'] + 7) // 8 + + def metadata(self, key, value): + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + + def putw(self, data): + self.put(self.ss_block, self.samplenum, self.out_ann, data) + + def putdata(self): + # Pass MISO and MOSI bits and then data to the next PD up the stack. + so = self.misodata if self.have_miso else None + si = self.mosidata if self.have_mosi else None + + if self.have_miso: + ss, es = self.misobits[-1][1], self.misobits[0][2] + if self.have_mosi: + ss, es = self.mosibits[-1][1], self.mosibits[0][2] + + # Dataword annotations. + if self.have_miso: + self.put(ss, es, self.out_ann, [0, [self.misodata]]) + if self.have_mosi: + self.put(ss, es, self.out_ann, [1, [self.mosidata]]) + + def reset_decoder_state(self): + self.misodata = 0 if self.have_miso else None + self.mosidata = 0 if self.have_mosi else None + self.misobits = [] if self.have_miso else None + self.mosibits = [] if self.have_mosi else None + self.bitcount = 0 + + def cs_asserted(self, cs): + active_low = (self.options['cs_polarity'] == 'active-low') + return (cs == 0) if active_low else (cs == 1) + + def handle_bit(self, miso, mosi, clk, cs): + # If this is the first bit of a dataword, save its sample number. + if self.bitcount == 0: + self.ss_block = self.samplenum + self.cs_was_deasserted = \ + not self.cs_asserted(cs) if self.have_cs else False + + ws = self.options['wordsize'] + bo = self.options['bitorder'] + + # Receive MISO bit into our shift register. + if self.have_miso: + if bo == 'msb-first': + self.misodata |= miso << (ws - 1 - self.bitcount) + else: + self.misodata |= miso << self.bitcount + + # Receive MOSI bit into our shift register. + if self.have_mosi: + if bo == 'msb-first': + self.mosidata |= mosi << (ws - 1 - self.bitcount) + else: + self.mosidata |= mosi << self.bitcount + + # Guesstimate the endsample for this bit (can be overridden below). + es = self.samplenum + if self.bitcount > 0: + if self.have_miso: + es += self.samplenum - self.misobits[0][1] + elif self.have_mosi: + es += self.samplenum - self.mosibits[0][1] + + if self.have_miso: + self.misobits.insert(0, [miso, self.samplenum, es]) + if self.have_mosi: + self.mosibits.insert(0, [mosi, self.samplenum, es]) + + if self.bitcount > 0 and self.have_miso: + self.misobits[1][2] = self.samplenum + if self.bitcount > 0 and self.have_mosi: + self.mosibits[1][2] = self.samplenum + + self.bitcount += 1 + + # Continue to receive if not enough bits were received, yet. + if self.bitcount != ws: + return + + self.putdata() + + self.reset_decoder_state() + + def find_clk_edge(self, miso, mosi, clk, cs, first): + if self.have_cs and (first or (self.matched & (0b1 << self.have_cs))): + # Send all CS# pin value changes. + oldcs = None if first else 1 - cs + + # Reset decoder state when CS# changes (and the CS# pin is used). + self.reset_decoder_state() + + # We only care about samples if CS# is asserted. + if self.have_cs and not self.cs_asserted(cs): + return + + # Ignore sample if the clock pin hasn't changed. + if first or not (self.matched & (0b1 << 0)): + return + + # Found the correct clock edge, now get the SPI bit(s). + self.handle_bit(miso, mosi, clk, cs) + + def decode(self): + # The CLK input is mandatory. Other signals are (individually) + # optional. Yet either MISO or MOSI (or both) must be provided. + # Tell stacked decoders when we don't have a CS# signal. + if not self.has_channel(0): + raise ChannelError('CLK pin required.') + self.have_miso = self.has_channel(1) + self.have_mosi = self.has_channel(2) + if not self.have_miso and not self.have_mosi: + raise ChannelError('Either MISO or MOSI (or both) pins required.') + self.have_cs = self.has_channel(3) + + # We want all CLK changes. We want all CS changes if CS is used. + # Map 'have_cs' from boolean to an integer index. This simplifies + # evaluation in other locations. + # Sample data on rising/falling clock edge (depends on mode). + mode = spi_mode[self.options['cpol'], self.options['cpha']] + if mode == 0 or mode == 3: # Sample on rising clock edge + wait_cond = [{0: 'r'}] + else: # Sample on falling clock edge + wait_cond = [{0: 'f'}] + + if self.have_cs: + self.have_cs = len(wait_cond) + wait_cond.append({3: 'e'}) + + # "Pixel compatibility" with the v2 implementation. Grab and + # process the very first sample before checking for edges. The + # previous implementation did this by seeding old values with + # None, which led to an immediate "change" in comparison. + (clk, miso, mosi, cs) = self.wait({}) + self.find_clk_edge(miso, mosi, clk, cs, True) + + while True: + (clk, miso, mosi, cs) = self.wait(wait_cond) + self.find_clk_edge(miso, mosi, clk, cs, False) diff --git a/libsigrokdecode4DSL/decoders/0-uart/__init__.py b/libsigrokdecode4DSL/decoders/0-uart/__init__.py new file mode 100644 index 00000000..ce6136f1 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/0-uart/__init__.py @@ -0,0 +1,40 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2012 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +UART (Universal Asynchronous Receiver Transmitter) is a simple serial +communication protocol which allows two devices to talk to each other. + +This decoder should work on all "UART-like" async protocols with one +start bit (0), 5-9 databits, an (optional) parity bit, and one or more +stop bits (1), in this order. + +It can be run on one signal line (RX or TX) only, or on two lines (RX + TX). + +There are various standards for the physical layer specification of the +signals, including RS232, (TTL) UART, RS485, and others. However, the logic +level of the respective pins is only relevant when acquiring the data via +a logic analyzer (you have to select the correct logic analyzer and/or +the correct place where to probe). Once the data is in digital form and +matches the "UART" description above, this protocol decoder can work with +it though, no matter whether the source was on TTL UART levels, or RS232, +or others. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/0-uart/pd.py b/libsigrokdecode4DSL/decoders/0-uart/pd.py new file mode 100644 index 00000000..9edf2254 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/0-uart/pd.py @@ -0,0 +1,353 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2011-2014 Uwe Hermann +## Copyright (C) 2019 DreamSourceLab +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd +from common.srdhelper import bitpack +from math import floor, ceil + +''' +OUTPUT_PYTHON format: + +Packet: +[, , ] + +This is the list of s and their respective values: + - 'STARTBIT': The data is the (integer) value of the start bit (0/1). + - 'DATA': This is always a tuple containing two items: + - 1st item: the (integer) value of the UART data. Valid values + range from 0 to 511 (as the data can be up to 9 bits in size). + - 2nd item: the list of individual data bits and their ss/es numbers. + - 'PARITYBIT': The data is the (integer) value of the parity bit (0/1). + - 'STOPBIT': The data is the (integer) value of the stop bit (0 or 1). + - 'INVALID STARTBIT': The data is the (integer) value of the start bit (0/1). + - 'INVALID STOPBIT': The data is the (integer) value of the stop bit (0/1). + - 'PARITY ERROR': The data is a tuple with two entries. The first one is + the expected parity value, the second is the actual parity value. + - 'FRAME': The data is always a tuple containing two items: The (integer) + value of the UART data, and a boolean which reflects the validity of the + UART frame. + +''' + +# Given a parity type to check (odd, even, zero, one), the value of the +# parity bit, the value of the data, and the length of the data (5-9 bits, +# usually 8 bits) return True if the parity is correct, False otherwise. +# 'none' is _not_ allowed as value for 'parity_type'. +def parity_ok(parity_type, parity_bit, data, num_data_bits): + + # Handle easy cases first (parity bit is always 1 or 0). + if parity_type == 'zero': + return parity_bit == 0 + elif parity_type == 'one': + return parity_bit == 1 + + # Count number of 1 (high) bits in the data (and the parity bit itself!). + ones = bin(data).count('1') + parity_bit + + # Check for odd/even parity. + if parity_type == 'odd': + return (ones % 2) == 1 + elif parity_type == 'even': + return (ones % 2) == 0 + +class SamplerateError(Exception): + pass + +class ChannelError(Exception): + pass + +class Decoder(srd.Decoder): + api_version = 3 + id = '0:uart' + name = '0:UART' + longname = 'Universal Asynchronous Receiver/Transmitter' + desc = 'Asynchronous, serial bus.' + license = 'gplv2+' + inputs = ['logic'] + outputs = ['uart'] + tags = ['Embedded/industrial'] + channels = ( + {'id': 'rxtx', 'type': 209, 'name': 'RX/TX', 'desc': 'UART transceive line'}, + ) + options = ( + {'id': 'baudrate', 'desc': 'Baud rate', 'default': 115200}, + {'id': 'num_data_bits', 'desc': 'Data bits', 'default': 8, + 'values': tuple(range(4,129,1))}, + {'id': 'parity_type', 'desc': 'Parity type', 'default': 'none', + 'values': ('none', 'odd', 'even', 'zero', 'one')}, + {'id': 'parity_check', 'desc': 'Check parity?', 'default': 'yes', + 'values': ('yes', 'no')}, + {'id': 'num_stop_bits', 'desc': 'Stop bits', 'default': 1.0, + 'values': (0.0, 0.5, 1.0, 1.5, 2.0, 2.5)}, + {'id': 'bit_order', 'desc': 'Bit order', 'default': 'lsb-first', + 'values': ('lsb-first', 'msb-first')}, + {'id': 'format', 'desc': 'Data format', 'default': 'hex', + 'values': ('ascii', 'dec', 'hex', 'oct', 'bin')}, + {'id': 'invert', 'desc': 'Invert Signal?', 'default': 'no', + 'values': ('yes', 'no')}, + {'id': 'anno_startstop', 'desc': 'Display Start/Stop?', 'default': 'no', + 'values': ('yes', 'no')}, + ) + annotations = ( + ('108', 'data', 'data'), + ('7', 'start', 'start bits'), + ('6', 'parity-ok', 'parity OK bits'), + ('0', 'parity-err', 'parity error bits'), + ('1', 'stop', 'stop bits'), + ('1000', 'warnings', 'warnings'), + ) + annotation_rows = ( + ('data', 'RX/TX', (0, 1, 2, 3, 4)), + ('warnings', 'Warnings', (5,)), + ) + idle_state = 'WAIT FOR START BIT' + + def putx(self, data): + s, halfbit = self.startsample, self.bit_width / 2.0 + if self.options['anno_startstop'] == 'yes' : + self.put(s - floor(halfbit), self.samplenum + ceil(halfbit), self.out_ann, data) + else : + self.put(self.frame_start, self.samplenum + ceil(halfbit * (1+self.options['num_stop_bits'])), self.out_ann, data) + + def putg(self, data): + s, halfbit = self.samplenum, self.bit_width / 2.0 + self.put(s - floor(halfbit), s + ceil(halfbit), self.out_ann, data) + + def putgse(self, ss, es, data): + self.put(ss, es, self.out_ann, data) + + def __init__(self): + self.reset() + + def reset(self): + self.samplerate = None + self.samplenum = 0 + self.frame_start = -1 + self.frame_valid = None + self.startbit = -1 + self.cur_data_bit = 0 + self.datavalue = 0 + self.paritybit = -1 + self.stopbit1 = -1 + self.startsample = -1 + self.state = 'WAIT FOR START BIT' + self.databits = [] + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + self.bw = (self.options['num_data_bits'] + 7) // 8 + + def metadata(self, key, value): + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + # The width of one UART bit in number of samples. + self.bit_width = float(self.samplerate) / float(self.options['baudrate']) + + def get_sample_point(self, bitnum): + # Determine absolute sample number of a bit slot's sample point. + # bitpos is the samplenumber which is in the middle of the + # specified UART bit (0 = start bit, 1..x = data, x+1 = parity bit + # (if used) or the first stop bit, and so on). + # The samples within bit are 0, 1, ..., (bit_width - 1), therefore + # index of the middle sample within bit window is (bit_width - 1) / 2. + bitpos = self.frame_start + (self.bit_width - 1) / 2.0 + bitpos += bitnum * self.bit_width + return bitpos + + def wait_for_start_bit(self, signal): + # Save the sample number where the start bit begins. + self.frame_start = self.samplenum + self.frame_valid = True + + self.state = 'GET START BIT' + + def get_start_bit(self, signal): + self.startbit = signal + + # The startbit must be 0. If not, we report an error and wait + # for the next start bit (assuming this one was spurious). + if self.startbit != 0: + self.putg([5, ['Frame error', 'Frame err', 'FE']]) + self.frame_valid = False + es = self.samplenum + ceil(self.bit_width / 2.0) + self.state = 'WAIT FOR START BIT' + return + + self.cur_data_bit = 0 + self.datavalue = 0 + self.startsample = -1 + + if self.options['anno_startstop'] == 'yes': + self.putg([1, ['Start bit', 'Start', 'S']]) + + self.state = 'GET DATA BITS' + + def get_data_bits(self, signal): + # Save the sample number of the middle of the first data bit. + if self.startsample == -1: + self.startsample = self.samplenum + + # Store individual data bits and their start/end samplenumbers. + s, halfbit = self.samplenum, int(self.bit_width / 2) + self.databits.append([signal, s - halfbit, s + halfbit]) + + # Return here, unless we already received all data bits. + self.cur_data_bit += 1 + if self.cur_data_bit < self.options['num_data_bits']: + return + + # Convert accumulated data bits to a data value. + bits = [b[0] for b in self.databits] + if self.options['bit_order'] == 'msb-first': + bits.reverse() + self.datavalue = bitpack(bits) + self.putx([0, [self.datavalue]]) + #b = self.datavalue + #formatted = self.format_value(b) + #if formatted is not None: + # self.putx([0, [formatted]]) + + self.databits = [] + + # Advance to either reception of the parity bit, or reception of + # the STOP bits if parity is not applicable. + self.state = 'GET PARITY BIT' + if self.options['parity_type'] == 'none': + self.state = 'GET STOP BITS' + + def format_value(self, v): + # Format value 'v' according to configured options. + # Reflects the user selected kind of representation, as well as + # the number of data bits in the UART frames. + + fmt, bits = self.options['format'], self.options['num_data_bits'] + + # Assume "is printable" for values from 32 to including 126, + # below 32 is "control" and thus not printable, above 127 is + # "not ASCII" in its strict sense, 127 (DEL) is not printable, + # fall back to hex representation for non-printables. + if fmt == 'ascii': + if v in range(32, 126 + 1): + return chr(v) + hexfmt = "[{:02X}]" if bits <= 8 else "[{:03X}]" + return hexfmt.format(v) + + # Mere number to text conversion without prefix and padding + # for the "decimal" output format. + if fmt == 'dec': + return "{:d}".format(v) + + # Padding with leading zeroes for hex/oct/bin formats, but + # without a prefix for density -- since the format is user + # specified, there is no ambiguity. + if fmt == 'hex': + digits = (bits + 4 - 1) // 4 + fmtchar = "X" + elif fmt == 'oct': + digits = (bits + 3 - 1) // 3 + fmtchar = "o" + elif fmt == 'bin': + digits = bits + fmtchar = "b" + else: + fmtchar = None + if fmtchar is not None: + fmt = "{{:0{:d}{:s}}}".format(digits, fmtchar) + return fmt.format(v) + + return None + + def get_parity_bit(self, signal): + self.paritybit = signal + + if parity_ok(self.options['parity_type'], self.paritybit, + self.datavalue, self.options['num_data_bits']): + self.putg([2, ['Parity bit', 'Parity', 'P']]) + else: + # TODO: Return expected/actual parity values. + self.putg([3, ['Parity error', 'Parity err', 'PE']]) + self.frame_valid = False + + self.state = 'GET STOP BITS' + + # TODO: Currently only supports 1 stop bit. + def get_stop_bits(self, signal): + self.stopbit1 = signal + + # Stop bits must be 1. If not, we report an error. + if self.stopbit1 != 1: + self.putg([5, ['Frame error', 'Frame err', 'FE']]) + self.frame_valid = False + + if self.options['anno_startstop'] == 'yes': + self.putg([2, ['Stop bit', 'Stop', 'T']]) + + # Pass the complete UART frame to upper layers. + es = self.samplenum + ceil(self.bit_width / 2.0) + + self.state = 'WAIT FOR START BIT' + + def get_wait_cond(self, inv): + # Return condititions that are suitable for Decoder.wait(). Those + # conditions either match the falling edge of the START bit, or + # the sample point of the next bit time. + state = self.state + if state == 'WAIT FOR START BIT': + return {0: 'r' if inv else 'f'} + if state == 'GET START BIT': + bitnum = 0 + elif state == 'GET DATA BITS': + bitnum = 1 + self.cur_data_bit + elif state == 'GET PARITY BIT': + bitnum = 1 + self.options['num_data_bits'] + elif state == 'GET STOP BITS': + bitnum = 1 + self.options['num_data_bits'] + bitnum += 0 if self.options['parity_type'] == 'none' else 1 + want_num = ceil(self.get_sample_point(bitnum)) + return {'skip': want_num - self.samplenum} + + def inspect_sample(self, signal, inv): + # Inspect a sample returned by .wait() for the specified UART line. + if inv: + signal = not signal + + state = self.state + if state == 'WAIT FOR START BIT': + self.wait_for_start_bit(signal) + elif state == 'GET START BIT': + self.get_start_bit(signal) + elif state == 'GET DATA BITS': + self.get_data_bits(signal) + elif state == 'GET PARITY BIT': + self.get_parity_bit(signal) + elif state == 'GET STOP BITS': + self.get_stop_bits(signal) + + def decode(self): + if not self.samplerate: + raise SamplerateError('Cannot decode without samplerate.') + + inv = self.options['invert'] == 'yes' + + while True: + conds = self.get_wait_cond(inv) + (rxtx, ) = self.wait(conds) + if (self.matched & (0b1 << 0)): + self.inspect_sample(rxtx, inv) diff --git a/libsigrokdecode4DSL/decoders/1-i2c/__init__.py b/libsigrokdecode4DSL/decoders/1-i2c/__init__.py new file mode 100644 index 00000000..2a36b060 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/1-i2c/__init__.py @@ -0,0 +1,25 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2012 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +I²C (Inter-Integrated Circuit) is a bidirectional, multi-master +bus using two signals (SCL = serial clock line, SDA = serial data line). +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/1-i2c/pd.py b/libsigrokdecode4DSL/decoders/1-i2c/pd.py new file mode 100644 index 00000000..a163eba4 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/1-i2c/pd.py @@ -0,0 +1,295 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2010-2016 Uwe Hermann +## Copyright (C) 2019 DreamSourceLab +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +# TODO: Look into arbitration, collision detection, clock synchronisation, etc. +# TODO: Implement support for inverting SDA/SCL levels (0->1 and 1->0). +# TODO: Implement support for detecting various bus errors. + +import sigrokdecode as srd + +''' +OUTPUT_PYTHON format: + +Packet: +[, ] + +: + - 'START' (START condition) + - 'START REPEAT' (Repeated START condition) + - 'ADDRESS READ' (Slave address, read) + - 'ADDRESS WRITE' (Slave address, write) + - 'DATA READ' (Data, read) + - 'DATA WRITE' (Data, write) + - 'STOP' (STOP condition) + - 'ACK' (ACK bit) + - 'NACK' (NACK bit) + - 'BITS' (: list of data/address bits and their ss/es numbers) + + is the data or address byte associated with the 'ADDRESS*' and 'DATA*' +command. Slave addresses do not include bit 0 (the READ/WRITE indication bit). +For example, a slave address field could be 0x51 (instead of 0xa2). +For 'START', 'START REPEAT', 'STOP', 'ACK', and 'NACK' is None. +''' + +# CMD: [annotation-type-index, long annotation, short annotation] +proto = { + 'START': [0, 'Start', 'S'], + 'START REPEAT': [1, 'Start repeat', 'Sr'], + 'STOP': [2, 'Stop', 'P'], + 'ACK': [3, 'ACK', 'A'], + 'NACK': [4, 'NACK', 'N'], + 'BIT': [5, 'Bit', 'B'], + 'ADDRESS READ': [6, 'Address read', 'AR'], + 'ADDRESS WRITE': [7, 'Address write', 'AW'], + 'DATA READ': [8, 'Data read', 'DR'], + 'DATA WRITE': [9, 'Data write', 'DW'], +} + +class Decoder(srd.Decoder): + api_version = 3 + id = '1:i2c' + name = '1:I²C' + longname = 'Inter-Integrated Circuit' + desc = 'Two-wire, multi-master, serial bus.' + license = 'gplv2+' + inputs = ['logic'] + outputs = ['i2c'] + tags = ['Embedded/industrial'] + channels = ( + {'id': 'scl', 'type': 8, 'name': 'SCL', 'desc': 'Serial clock line'}, + {'id': 'sda', 'type': 108, 'name': 'SDA', 'desc': 'Serial data line'}, + ) + options = ( + {'id': 'address_format', 'desc': 'Displayed slave address format', + 'default': 'shifted', 'values': ('shifted', 'unshifted')}, + ) + annotations = ( + ('7', 'start', 'Start condition'), + ('6', 'repeat-start', 'Repeat start condition'), + ('1', 'stop', 'Stop condition'), + ('5', 'ack', 'ACK'), + ('0', 'nack', 'NACK'), + ('208', 'bit', 'Data/address bit'), + ('112', 'address-read', 'Address read'), + ('111', 'address-write', 'Address write'), + ('110', 'data-read', 'Data read'), + ('109', 'data-write', 'Data write'), + ('1000', 'warnings', 'Human-readable warnings'), + ) + annotation_rows = ( + ('bits', 'Bits', (5,)), + ('addr-data', 'Address/Data', (0, 1, 2, 3, 4, 6, 7, 8, 9)), + ('warnings', 'Warnings', (10,)), + ) + binary = ( + ('address-read', 'Address read'), + ('address-write', 'Address write'), + ('data-read', 'Data read'), + ('data-write', 'Data write'), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.samplerate = None + self.ss = self.es = self.ss_byte = -1 + self.bitcount = 0 + self.databyte = 0 + self.wr = -1 + self.is_repeat_start = 0 + self.state = 'FIND START' + self.pdu_start = None + self.pdu_bits = 0 + self.bits = [] + + def metadata(self, key, value): + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + + def start(self): + self.out_python = self.register(srd.OUTPUT_PYTHON) + self.out_ann = self.register(srd.OUTPUT_ANN) + self.out_binary = self.register(srd.OUTPUT_BINARY) + self.out_bitrate = self.register(srd.OUTPUT_META, + meta=(int, 'Bitrate', 'Bitrate from Start bit to Stop bit')) + + def putx(self, data): + self.put(self.ss, self.es, self.out_ann, data) + + def putp(self, data): + self.put(self.ss, self.es, self.out_python, data) + + def putb(self, data): + self.put(self.ss, self.es, self.out_binary, data) + + def handle_start(self): + self.ss, self.es = self.samplenum, self.samplenum + self.pdu_start = self.samplenum + self.pdu_bits = 0 + cmd = 'START REPEAT' if (self.is_repeat_start == 1) else 'START' + self.putp([cmd, None]) + self.putx([proto[cmd][0], proto[cmd][1:]]) + self.state = 'FIND ADDRESS' + self.bitcount = self.databyte = 0 + self.is_repeat_start = 1 + self.wr = -1 + self.bits = [] + + # Gather 8 bits of data plus the ACK/NACK bit. + def handle_address_or_data(self, scl, sda): + self.pdu_bits += 1 + + # Address and data are transmitted MSB-first. + self.databyte <<= 1 + self.databyte |= sda + + # Remember the start of the first data/address bit. + if self.bitcount == 0: + self.ss_byte = self.samplenum + + # Store individual bits and their start/end samplenumbers. + # In the list, index 0 represents the LSB (I²C transmits MSB-first). + self.bits.insert(0, [sda, self.samplenum, self.samplenum]) + if self.bitcount > 0: + self.bits[1][2] = self.samplenum + if self.bitcount == 7: + self.bitwidth = self.bits[1][2] - self.bits[2][2] + self.bits[0][2] += self.bitwidth + + # Return if we haven't collected all 8 + 1 bits, yet. + if self.bitcount < 7: + self.bitcount += 1 + return + + d = self.databyte + if self.state == 'FIND ADDRESS': + # The READ/WRITE bit is only in address bytes, not data bytes. + self.wr = 0 if (self.databyte & 1) else 1 + if self.options['address_format'] == 'shifted': + d = d >> 1 + + bin_class = -1 + if self.state == 'FIND ADDRESS' and self.wr == 1: + cmd = 'ADDRESS WRITE' + bin_class = 1 + elif self.state == 'FIND ADDRESS' and self.wr == 0: + cmd = 'ADDRESS READ' + bin_class = 0 + elif self.state == 'FIND DATA' and self.wr == 1: + cmd = 'DATA WRITE' + bin_class = 3 + elif self.state == 'FIND DATA' and self.wr == 0: + cmd = 'DATA READ' + bin_class = 2 + + self.ss, self.es = self.ss_byte, self.samplenum + self.bitwidth + + self.putp(['BITS', self.bits]) + self.putp([cmd, d]) + + self.putb([bin_class, bytes([d])]) + + for bit in self.bits: + self.put(bit[1], bit[2], self.out_ann, [5, ['%d' % bit[0]]]) + + if cmd.startswith('ADDRESS'): + self.ss, self.es = self.samplenum, self.samplenum + self.bitwidth + w = ['Write', 'Wr', 'W'] if self.wr else ['Read', 'Rd', 'R'] + self.putx([0, w]) + self.ss, self.es = self.ss_byte, self.samplenum + + self.putx([proto[cmd][0], ['%s: {$}' % proto[cmd][1], '%s: {$}' % proto[cmd][2], '{$}', d]]) + + # Done with this packet. + self.bitcount = self.databyte = 0 + self.bits = [] + self.state = 'FIND ACK' + + def get_ack(self, scl, sda): + self.ss, self.es = self.samplenum, self.samplenum + self.bitwidth + cmd = 'NACK' if (sda == 1) else 'ACK' + self.putp([cmd, None]) + self.putx([proto[cmd][0], proto[cmd][1:]]) + # There could be multiple data bytes in a row, so either find + # another data byte or a STOP condition next. + self.state = 'FIND DATA' + + def handle_stop(self): + # Meta bitrate + if self.samplerate: + elapsed = 1 / float(self.samplerate) * (self.samplenum - self.pdu_start + 1) + bitrate = int(1 / elapsed * self.pdu_bits) + self.put(self.ss_byte, self.samplenum, self.out_bitrate, bitrate) + + cmd = 'STOP' + self.ss, self.es = self.samplenum, self.samplenum + self.putp([cmd, None]) + self.putx([proto[cmd][0], proto[cmd][1:]]) + self.state = 'FIND START' + self.is_repeat_start = 0 + self.wr = -1 + self.bits = [] + + def decode(self): + while True: + # State machine. + if self.state == 'FIND START': + # Wait for a START condition (S): SCL = high, SDA = falling. + self.wait({0: 'h', 1: 'f'}) + self.handle_start() + elif self.state == 'FIND ADDRESS': + # Wait for any of the following conditions (or combinations): + # a) Data sampling of receiver: SCL = rising, and/or + # b) START condition (S): SCL = high, SDA = falling, and/or + # c) STOP condition (P): SCL = high, SDA = rising + (scl, sda) = self.wait([{0: 'r'}, {0: 'h', 1: 'f'}, {0: 'h', 1: 'r'}]) + + # Check which of the condition(s) matched and handle them. + if (self.matched & (0b1 << 0)): + self.handle_address_or_data(scl, sda) + elif (self.matched & (0b1 << 1)): + self.handle_start() + elif (self.matched & (0b1 << 2)): + self.handle_stop() + elif self.state == 'FIND DATA': + # Wait for any of the following conditions (or combinations): + # a) Data sampling of receiver: SCL = rising, and/or + # b) START condition (S): SCL = high, SDA = falling, and/or + # c) STOP condition (P): SCL = high, SDA = rising + (scl, sda) = self.wait([{0: 'r'}, {0: 'h', 1: 'f'}, {0: 'h', 1: 'r'}]) + + # Check which of the condition(s) matched and handle them. + if (self.matched & (0b1 << 0)): + self.handle_address_or_data(scl, sda) + elif (self.matched & (0b1 << 1)): + self.handle_start() + elif (self.matched & (0b1 << 2)): + self.handle_stop() + elif self.state == 'FIND ACK': + # Wait for any of the following conditions (or combinations): + # a) a data/ack bit: SCL = rising. + # b) STOP condition (P): SCL = high, SDA = rising + (scl, sda) = self.wait([{0: 'r'}, {0: 'h', 1: 'r'}]) + if (self.matched & (0b1 << 0)): + self.get_ack(scl, sda) + elif (self.matched & (0b1 << 1)): + self.handle_stop() + diff --git a/libsigrokdecode4DSL/decoders/1-spi/__init__.py b/libsigrokdecode4DSL/decoders/1-spi/__init__.py new file mode 100644 index 00000000..dc5cbc05 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/1-spi/__init__.py @@ -0,0 +1,32 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2012 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +The SPI (Serial Peripheral Interface) protocol decoder supports synchronous +SPI(-like) protocols with a clock line, a MISO and MOSI line for data +transfer in two directions, and an optional CS# pin. + +Either MISO or MOSI (but not both) can be optional. + +If CS# is supplied, data is only decoded when CS# is asserted (clock +transitions where CS# is not asserted are ignored). If CS# is not supplied, +data is decoded on every clock transition (depending on SPI mode). +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/1-spi/pd.py b/libsigrokdecode4DSL/decoders/1-spi/pd.py new file mode 100644 index 00000000..e17e6adb --- /dev/null +++ b/libsigrokdecode4DSL/decoders/1-spi/pd.py @@ -0,0 +1,352 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2011 Gareth McMullin +## Copyright (C) 2012-2014 Uwe Hermann +## Copyright (C) 2019 DreamSourceLab +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd +from collections import namedtuple + +Data = namedtuple('Data', ['ss', 'es', 'val']) + +''' +OUTPUT_PYTHON format: + +Packet: +[, , ] + +: + - 'DATA': contains the MOSI data, contains the MISO data. + The data is _usually_ 8 bits (but can also be fewer or more bits). + Both data items are Python numbers (not strings), or None if the respective + channel was not supplied. + - 'BITS': / contain a list of bit values in this MOSI/MISO data + item, and for each of those also their respective start-/endsample numbers. + - 'CS-CHANGE': is the old CS# pin value, is the new value. + Both data items are Python numbers (0/1), not strings. At the beginning of + the decoding a packet is generated with = None and being the + initial state of the CS# pin or None if the chip select pin is not supplied. + - 'TRANSFER': / contain a list of Data() namedtuples for each + byte transferred during this block of CS# asserted time. Each Data() has + fields ss, es, and val. + +Examples: + ['CS-CHANGE', None, 1] + ['CS-CHANGE', 1, 0] + ['DATA', 0xff, 0x3a] + ['BITS', [[1, 80, 82], [1, 83, 84], [1, 85, 86], [1, 87, 88], + [1, 89, 90], [1, 91, 92], [1, 93, 94], [1, 95, 96]], + [[0, 80, 82], [1, 83, 84], [0, 85, 86], [1, 87, 88], + [1, 89, 90], [1, 91, 92], [0, 93, 94], [0, 95, 96]]] + ['DATA', 0x65, 0x00] + ['DATA', 0xa8, None] + ['DATA', None, 0x55] + ['CS-CHANGE', 0, 1] + ['TRANSFER', [Data(ss=80, es=96, val=0xff), ...], + [Data(ss=80, es=96, val=0x3a), ...]] +''' + +# Key: (CPOL, CPHA). Value: SPI mode. +# Clock polarity (CPOL) = 0/1: Clock is low/high when inactive. +# Clock phase (CPHA) = 0/1: Data is valid on the leading/trailing clock edge. +spi_mode = { + (0, 0): 0, # Mode 0 + (0, 1): 1, # Mode 1 + (1, 0): 2, # Mode 2 + (1, 1): 3, # Mode 3 +} + +class ChannelError(Exception): + pass + +class Decoder(srd.Decoder): + api_version = 3 + id = '1:spi' + name = '1:SPI' + longname = 'Serial Peripheral Interface' + desc = 'Full-duplex, synchronous, serial bus.' + license = 'gplv2+' + inputs = ['logic'] + outputs = ['spi'] + tags = ['Embedded/industrial'] + channels = ( + {'id': 'clk', 'type': 0, 'name': 'CLK', 'desc': 'Clock'}, + ) + optional_channels = ( + {'id': 'miso', 'type': 107, 'name': 'MISO', 'desc': 'Master in, slave out'}, + {'id': 'mosi', 'type': 109, 'name': 'MOSI', 'desc': 'Master out, slave in'}, + {'id': 'cs', 'type': -1, 'name': 'CS#', 'desc': 'Chip-select'}, + ) + options = ( + {'id': 'cs_polarity', 'desc': 'CS# polarity', 'default': 'active-low', + 'values': ('active-low', 'active-high')}, + {'id': 'cpol', 'desc': 'Clock polarity (CPOL)', 'default': 0, + 'values': (0, 1)}, + {'id': 'cpha', 'desc': 'Clock phase (CPHA)', 'default': 0, + 'values': (0, 1)}, + {'id': 'bitorder', 'desc': 'Bit order', + 'default': 'msb-first', 'values': ('msb-first', 'lsb-first')}, + {'id': 'wordsize', 'desc': 'Word size', 'default': 8, + 'values': tuple(range(5,129,1))}, + {'id': 'frame', 'desc': 'Frame Decoder', 'default': 'no', + 'values': ('yes', 'no')}, + ) + annotations = ( + ('106', 'miso-data', 'MISO data'), + ('108', 'mosi-data', 'MOSI data'), + ('207', 'miso-bits', 'MISO bits'), + ('209', 'mosi-bits', 'MOSI bits'), + ('1000', 'warnings', 'Human-readable warnings'), + + ('6', 'miso-transfer', 'MISO transfer'), + ('8', 'mosi-transfer', 'MOSI transfer'), + ) + annotation_rows = ( + ('miso-bits', 'MISO bits', (2,)), + ('miso-data', 'MISO data', (0,)), + ('miso-transfer', 'MISO transfer', (5,)), + ('mosi-bits', 'MOSI bits', (3,)), + ('mosi-data', 'MOSI data', (1,)), + ('mosi-transfer', 'MOSI transfer', (6,)), + ('other', 'Other', (4,)), + ) + binary = ( + ('miso', 'MISO'), + ('mosi', 'MOSI'), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.samplerate = None + self.bitcount = 0 + self.misodata = self.mosidata = 0 + self.misobits = [] + self.mosibits = [] + self.misobytes = [] + self.mosibytes = [] + self.ss_block = -1 + self.samplenum = -1 + self.ss_transfer = -1 + self.cs_was_deasserted = False + self.have_cs = self.have_miso = self.have_mosi = None + + def start(self): + self.out_python = self.register(srd.OUTPUT_PYTHON) + self.out_ann = self.register(srd.OUTPUT_ANN) + self.out_binary = self.register(srd.OUTPUT_BINARY) + self.out_bitrate = self.register(srd.OUTPUT_META, + meta=(int, 'Bitrate', 'Bitrate during transfers')) + self.bw = (self.options['wordsize'] + 7) // 8 + + def metadata(self, key, value): + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + + def putw(self, data): + self.put(self.ss_block, self.samplenum, self.out_ann, data) + + def putdata(self, frame): + # Pass MISO and MOSI bits and then data to the next PD up the stack. + so = self.misodata if self.have_miso else None + si = self.mosidata if self.have_mosi else None + so_bits = self.misobits if self.have_miso else None + si_bits = self.mosibits if self.have_mosi else None + + if self.have_miso: + ss, es = self.misobits[-1][1], self.misobits[0][2] + bdata = so.to_bytes(self.bw, byteorder='big') + self.put(ss, es, self.out_binary, [0, bdata]) + if self.have_mosi: + ss, es = self.mosibits[-1][1], self.mosibits[0][2] + bdata = si.to_bytes(self.bw, byteorder='big') + self.put(ss, es, self.out_binary, [1, bdata]) + + self.put(ss, es, self.out_python, ['BITS', si_bits, so_bits]) + self.put(ss, es, self.out_python, ['DATA', si, so]) + + if frame: + if self.have_miso: + self.misobytes.append(Data(ss=ss, es=es, val=so)) + if self.have_mosi: + self.mosibytes.append(Data(ss=ss, es=es, val=si)) + + # Bit annotations. + if self.have_miso: + for bit in self.misobits: + self.put(bit[1], bit[2], self.out_ann, [2, ['%d' % bit[0]]]) + if self.have_mosi: + for bit in self.mosibits: + self.put(bit[1], bit[2], self.out_ann, [3, ['%d' % bit[0]]]) + + # Dataword annotations. + if self.have_miso: + self.put(ss, es, self.out_ann, [0, ['%02X' % self.misodata]]) + if self.have_mosi: + self.put(ss, es, self.out_ann, [1, ['%02X' % self.mosidata]]) + + def reset_decoder_state(self): + self.misodata = 0 if self.have_miso else None + self.mosidata = 0 if self.have_mosi else None + self.misobits = [] if self.have_miso else None + self.mosibits = [] if self.have_mosi else None + self.bitcount = 0 + + def cs_asserted(self, cs): + active_low = (self.options['cs_polarity'] == 'active-low') + return (cs == 0) if active_low else (cs == 1) + + def handle_bit(self, miso, mosi, clk, cs, frame): + # If this is the first bit of a dataword, save its sample number. + if self.bitcount == 0: + self.ss_block = self.samplenum + self.cs_was_deasserted = \ + not self.cs_asserted(cs) if self.have_cs else False + + ws = self.options['wordsize'] + bo = self.options['bitorder'] + + # Receive MISO bit into our shift register. + if self.have_miso: + if bo == 'msb-first': + self.misodata |= miso << (ws - 1 - self.bitcount) + else: + self.misodata |= miso << self.bitcount + + # Receive MOSI bit into our shift register. + if self.have_mosi: + if bo == 'msb-first': + self.mosidata |= mosi << (ws - 1 - self.bitcount) + else: + self.mosidata |= mosi << self.bitcount + + # Guesstimate the endsample for this bit (can be overridden below). + es = self.samplenum + if self.bitcount > 0: + if self.have_miso: + es += self.samplenum - self.misobits[0][1] + elif self.have_mosi: + es += self.samplenum - self.mosibits[0][1] + + if self.have_miso: + self.misobits.insert(0, [miso, self.samplenum, es]) + if self.have_mosi: + self.mosibits.insert(0, [mosi, self.samplenum, es]) + + if self.bitcount > 0 and self.have_miso: + self.misobits[1][2] = self.samplenum + if self.bitcount > 0 and self.have_mosi: + self.mosibits[1][2] = self.samplenum + + self.bitcount += 1 + + # Continue to receive if not enough bits were received, yet. + if self.bitcount != ws: + return + + self.putdata(frame) + + # Meta bitrate. + if self.samplerate: + elapsed = 1 / float(self.samplerate) + elapsed *= (self.samplenum - self.ss_block + 1) + bitrate = int(1 / elapsed * ws) + self.put(self.ss_block, self.samplenum, self.out_bitrate, bitrate) + + if self.have_cs and self.cs_was_deasserted: + self.putw([4, ['CS# was deasserted during this data word!']]) + + self.reset_decoder_state() + + def find_clk_edge(self, miso, mosi, clk, cs, first, frame): + if self.have_cs and (first or (self.matched & (0b1 << self.have_cs))): + # Send all CS# pin value changes. + oldcs = None if first else 1 - cs + self.put(self.samplenum, self.samplenum, self.out_python, + ['CS-CHANGE', oldcs, cs]) + + if frame: + if self.cs_asserted(cs): + self.ss_transfer = self.samplenum + self.misobytes = [] + self.mosibytes = [] + elif self.ss_transfer != -1: + if self.have_miso: + self.put(self.ss_transfer, self.samplenum, self.out_ann, + [5, [' '.join(format(x.val, '02X') for x in self.misobytes)]]) + if self.have_mosi: + self.put(self.ss_transfer, self.samplenum, self.out_ann, + [6, [' '.join(format(x.val, '02X') for x in self.mosibytes)]]) + self.put(self.ss_transfer, self.samplenum, self.out_python, + ['TRANSFER', self.mosibytes, self.misobytes]) + + # Reset decoder state when CS# changes (and the CS# pin is used). + self.reset_decoder_state() + + # We only care about samples if CS# is asserted. + if self.have_cs and not self.cs_asserted(cs): + return + + # Ignore sample if the clock pin hasn't changed. + if first or not (self.matched & (0b1 << 0)): + return + + # Found the correct clock edge, now get the SPI bit(s). + self.handle_bit(miso, mosi, clk, cs, frame) + + def decode(self): + # The CLK input is mandatory. Other signals are (individually) + # optional. Yet either MISO or MOSI (or both) must be provided. + # Tell stacked decoders when we don't have a CS# signal. + if not self.has_channel(0): + raise ChannelError('CLK pin required.') + self.have_miso = self.has_channel(1) + self.have_mosi = self.has_channel(2) + if not self.have_miso and not self.have_mosi: + raise ChannelError('Either MISO or MOSI (or both) pins required.') + self.have_cs = self.has_channel(3) + if not self.have_cs: + self.put(0, 0, self.out_python, ['CS-CHANGE', None, None]) + + frame = self.options['frame'] == 'yes' + + # We want all CLK changes. We want all CS changes if CS is used. + # Map 'have_cs' from boolean to an integer index. This simplifies + # evaluation in other locations. + # Sample data on rising/falling clock edge (depends on mode). + mode = spi_mode[self.options['cpol'], self.options['cpha']] + if mode == 0 or mode == 3: # Sample on rising clock edge + wait_cond = [{0: 'r'}] + else: # Sample on falling clock edge + wait_cond = [{0: 'f'}] + + if self.have_cs: + self.have_cs = len(wait_cond) + wait_cond.append({3: 'e'}) + + # "Pixel compatibility" with the v2 implementation. Grab and + # process the very first sample before checking for edges. The + # previous implementation did this by seeding old values with + # None, which led to an immediate "change" in comparison. + (clk, miso, mosi, cs) = self.wait({}) + self.find_clk_edge(miso, mosi, clk, cs, True, frame) + + while True: + (clk, miso, mosi, cs) = self.wait(wait_cond) + self.find_clk_edge(miso, mosi, clk, cs, False, frame) diff --git a/libsigrokdecode4DSL/decoders/1-uart/__init__.py b/libsigrokdecode4DSL/decoders/1-uart/__init__.py new file mode 100644 index 00000000..ce6136f1 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/1-uart/__init__.py @@ -0,0 +1,40 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2012 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +UART (Universal Asynchronous Receiver Transmitter) is a simple serial +communication protocol which allows two devices to talk to each other. + +This decoder should work on all "UART-like" async protocols with one +start bit (0), 5-9 databits, an (optional) parity bit, and one or more +stop bits (1), in this order. + +It can be run on one signal line (RX or TX) only, or on two lines (RX + TX). + +There are various standards for the physical layer specification of the +signals, including RS232, (TTL) UART, RS485, and others. However, the logic +level of the respective pins is only relevant when acquiring the data via +a logic analyzer (you have to select the correct logic analyzer and/or +the correct place where to probe). Once the data is in digital form and +matches the "UART" description above, this protocol decoder can work with +it though, no matter whether the source was on TTL UART levels, or RS232, +or others. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/1-uart/pd.py b/libsigrokdecode4DSL/decoders/1-uart/pd.py new file mode 100644 index 00000000..f3d6181c --- /dev/null +++ b/libsigrokdecode4DSL/decoders/1-uart/pd.py @@ -0,0 +1,441 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2011-2014 Uwe Hermann +## Copyright (C) 2019 DreamSourceLab +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd +from common.srdhelper import bitpack +from math import floor, ceil + +''' +OUTPUT_PYTHON format: + +Packet: +[, , ] + +This is the list of s and their respective values: + - 'STARTBIT': The data is the (integer) value of the start bit (0/1). + - 'DATA': This is always a tuple containing two items: + - 1st item: the (integer) value of the UART data. Valid values + range from 0 to 511 (as the data can be up to 9 bits in size). + - 2nd item: the list of individual data bits and their ss/es numbers. + - 'PARITYBIT': The data is the (integer) value of the parity bit (0/1). + - 'STOPBIT': The data is the (integer) value of the stop bit (0 or 1). + - 'INVALID STARTBIT': The data is the (integer) value of the start bit (0/1). + - 'INVALID STOPBIT': The data is the (integer) value of the stop bit (0/1). + - 'PARITY ERROR': The data is a tuple with two entries. The first one is + the expected parity value, the second is the actual parity value. + - 'BREAK': The data is always 0. + - 'FRAME': The data is always a tuple containing two items: The (integer) + value of the UART data, and a boolean which reflects the validity of the + UART frame. + +''' + +# Given a parity type to check (odd, even, zero, one), the value of the +# parity bit, the value of the data, and the length of the data (5-9 bits, +# usually 8 bits) return True if the parity is correct, False otherwise. +# 'none' is _not_ allowed as value for 'parity_type'. +def parity_ok(parity_type, parity_bit, data, num_data_bits): + + # Handle easy cases first (parity bit is always 1 or 0). + if parity_type == 'zero': + return parity_bit == 0 + elif parity_type == 'one': + return parity_bit == 1 + + # Count number of 1 (high) bits in the data (and the parity bit itself!). + ones = bin(data).count('1') + parity_bit + + # Check for odd/even parity. + if parity_type == 'odd': + return (ones % 2) == 1 + elif parity_type == 'even': + return (ones % 2) == 0 + +class SamplerateError(Exception): + pass + +class ChannelError(Exception): + pass + +class Decoder(srd.Decoder): + api_version = 3 + id = '1:uart' + name = '1:UART' + longname = 'Universal Asynchronous Receiver/Transmitter' + desc = 'Asynchronous, serial bus.' + license = 'gplv2+' + inputs = ['logic'] + outputs = ['uart'] + tags = ['Embedded/industrial'] + channels = ( + {'id': 'rxtx', 'type': 209, 'name': 'RX/TX', 'desc': 'UART transceive line'}, + ) + options = ( + {'id': 'baudrate', 'desc': 'Baud rate', 'default': 115200}, + {'id': 'num_data_bits', 'desc': 'Data bits', 'default': 8, + 'values': tuple(range(4,129,1))}, + {'id': 'parity_type', 'desc': 'Parity type', 'default': 'none', + 'values': ('none', 'odd', 'even', 'zero', 'one')}, + {'id': 'parity_check', 'desc': 'Check parity?', 'default': 'yes', + 'values': ('yes', 'no')}, + {'id': 'num_stop_bits', 'desc': 'Stop bits', 'default': 1.0, + 'values': (0.0, 0.5, 1.0, 1.5, 2.0, 2.5)}, + {'id': 'bit_order', 'desc': 'Bit order', 'default': 'lsb-first', + 'values': ('lsb-first', 'msb-first')}, + {'id': 'format', 'desc': 'Data format', 'default': 'hex', + 'values': ('ascii', 'dec', 'hex', 'oct', 'bin')}, + {'id': 'invert', 'desc': 'Invert Signal?', 'default': 'no', + 'values': ('yes', 'no')}, + {'id': 'anno_startstop', 'desc': 'Display Start/Stop?', 'default': 'yes', + 'values': ('yes', 'no')}, + ) + annotations = ( + ('108', 'data', 'data'), + ('7', 'start', 'start bits'), + ('6', 'parity-ok', 'parity OK bits'), + ('0', 'parity-err', 'parity error bits'), + ('1', 'stop', 'stop bits'), + ('1000', 'warnings', 'warnings'), + ('209', 'data-bits', 'data bits'), + ('10', 'break', 'break'), + ) + annotation_rows = ( + ('data', 'RX/TX', (0, 1, 2, 3, 4)), + ('data-bits', 'Bits', (6,)), + ('warnings', 'Warnings', (5,)), + ('break', 'break', (7,)), + ) + binary = ( + ('rxtx', 'RX/TX dump'), + ) + idle_state = 'WAIT FOR START BIT' + + def putx(self, data): + s, halfbit = self.startsample, self.bit_width / 2.0 + if self.options['anno_startstop'] == 'yes' : + self.put(s - floor(halfbit), self.samplenum + ceil(halfbit), self.out_ann, data) + else : + self.put(self.frame_start, self.samplenum + ceil(halfbit * (1+self.options['num_stop_bits'])), self.out_ann, data) + + def putpx(self, data): + s, halfbit = self.startsample, self.bit_width / 2.0 + self.put(s - floor(halfbit), self.samplenum + ceil(halfbit), self.out_python, data) + + def putg(self, data): + s, halfbit = self.samplenum, self.bit_width / 2.0 + self.put(s - floor(halfbit), s + ceil(halfbit), self.out_ann, data) + + def putp(self, data): + s, halfbit = self.samplenum, self.bit_width / 2.0 + self.put(s - floor(halfbit), s + ceil(halfbit), self.out_python, data) + + def putgse(self, ss, es, data): + self.put(ss, es, self.out_ann, data) + + def putpse(self, ss, es, data): + self.put(ss, es, self.out_python, data) + + def putbin(self, data): + s, halfbit = self.startsample, self.bit_width / 2.0 + self.put(s - floor(halfbit), self.samplenum + ceil(halfbit), self.out_binary, data) + + + def __init__(self): + self.reset() + + def reset(self): + self.samplerate = None + self.samplenum = 0 + self.frame_start = -1 + self.frame_valid = None + self.startbit = -1 + self.cur_data_bit = 0 + self.datavalue = 0 + self.paritybit = -1 + self.stopbit1 = -1 + self.startsample = -1 + self.state = 'WAIT FOR START BIT' + self.databits = [] + self.break_start = None + + def start(self): + self.out_python = self.register(srd.OUTPUT_PYTHON) + self.out_binary = self.register(srd.OUTPUT_BINARY) + self.out_ann = self.register(srd.OUTPUT_ANN) + self.bw = (self.options['num_data_bits'] + 7) // 8 + + def metadata(self, key, value): + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + # The width of one UART bit in number of samples. + self.bit_width = float(self.samplerate) / float(self.options['baudrate']) + + def get_sample_point(self, bitnum): + # Determine absolute sample number of a bit slot's sample point. + # bitpos is the samplenumber which is in the middle of the + # specified UART bit (0 = start bit, 1..x = data, x+1 = parity bit + # (if used) or the first stop bit, and so on). + # The samples within bit are 0, 1, ..., (bit_width - 1), therefore + # index of the middle sample within bit window is (bit_width - 1) / 2. + bitpos = self.frame_start + (self.bit_width - 1) / 2.0 + bitpos += bitnum * self.bit_width + return bitpos + + def wait_for_start_bit(self, signal): + # Save the sample number where the start bit begins. + self.frame_start = self.samplenum + self.frame_valid = True + + self.state = 'GET START BIT' + + def get_start_bit(self, signal): + self.startbit = signal + + # The startbit must be 0. If not, we report an error and wait + # for the next start bit (assuming this one was spurious). + if self.startbit != 0: + self.putp(['INVALID STARTBIT', 0, self.startbit]) + self.putg([5, ['Frame error', 'Frame err', 'FE']]) + self.frame_valid = False + es = self.samplenum + ceil(self.bit_width / 2.0) + self.putpse(self.frame_start, es, ['FRAME', 0, + (self.datavalue, self.frame_valid)]) + self.state = 'WAIT FOR START BIT' + return + + self.cur_data_bit = 0 + self.datavalue = 0 + self.startsample = -1 + + self.putp(['STARTBIT', 0, self.startbit]) + if self.options['anno_startstop'] == 'yes': + self.putg([1, ['Start bit', 'Start', 'S']]) + + self.state = 'GET DATA BITS' + + def get_data_bits(self, signal): + # Save the sample number of the middle of the first data bit. + if self.startsample == -1: + self.startsample = self.samplenum + + self.putg([6, ['%d' % signal]]) + + # Store individual data bits and their start/end samplenumbers. + s, halfbit = self.samplenum, int(self.bit_width / 2) + self.databits.append([signal, s - halfbit, s + halfbit]) + + # Return here, unless we already received all data bits. + self.cur_data_bit += 1 + if self.cur_data_bit < self.options['num_data_bits']: + return + + # Convert accumulated data bits to a data value. + bits = [b[0] for b in self.databits] + if self.options['bit_order'] == 'msb-first': + bits.reverse() + self.datavalue = bitpack(bits) + self.putpx(['DATA', 0, (self.datavalue, self.databits)]) + + self.putx([0, [self.datavalue]]) + + b = self.datavalue + #formatted = self.format_value(b) + #if formatted is not None: + # self.putx([0, [formatted]]) + + bdata = b.to_bytes(self.bw, byteorder='big') + self.putbin([0, bdata]) + self.putbin([1, bdata]) + + self.databits = [] + + # Advance to either reception of the parity bit, or reception of + # the STOP bits if parity is not applicable. + self.state = 'GET PARITY BIT' + if self.options['parity_type'] == 'none': + self.state = 'GET STOP BITS' + + def format_value(self, v): + # Format value 'v' according to configured options. + # Reflects the user selected kind of representation, as well as + # the number of data bits in the UART frames. + + fmt, bits = self.options['format'], self.options['num_data_bits'] + + # Assume "is printable" for values from 32 to including 126, + # below 32 is "control" and thus not printable, above 127 is + # "not ASCII" in its strict sense, 127 (DEL) is not printable, + # fall back to hex representation for non-printables. + if fmt == 'ascii': + if v in range(32, 126 + 1): + return chr(v) + hexfmt = "[{:02X}]" if bits <= 8 else "[{:03X}]" + return hexfmt.format(v) + + # Mere number to text conversion without prefix and padding + # for the "decimal" output format. + if fmt == 'dec': + return "{:d}".format(v) + + # Padding with leading zeroes for hex/oct/bin formats, but + # without a prefix for density -- since the format is user + # specified, there is no ambiguity. + if fmt == 'hex': + digits = (bits + 4 - 1) // 4 + fmtchar = "X" + elif fmt == 'oct': + digits = (bits + 3 - 1) // 3 + fmtchar = "o" + elif fmt == 'bin': + digits = bits + fmtchar = "b" + else: + fmtchar = None + if fmtchar is not None: + fmt = "{{:0{:d}{:s}}}".format(digits, fmtchar) + return fmt.format(v) + + return None + + def get_parity_bit(self, signal): + self.paritybit = signal + + if parity_ok(self.options['parity_type'], self.paritybit, + self.datavalue, self.options['num_data_bits']): + self.putp(['PARITYBIT', 0, self.paritybit]) + self.putg([2, ['Parity bit', 'Parity', 'P']]) + else: + # TODO: Return expected/actual parity values. + self.putp(['PARITY ERROR', 0, (0, 1)]) # FIXME: Dummy tuple... + self.putg([3, ['Parity error', 'Parity err', 'PE']]) + self.frame_valid = False + + self.state = 'GET STOP BITS' + + # TODO: Currently only supports 1 stop bit. + def get_stop_bits(self, signal): + self.stopbit1 = signal + + # Stop bits must be 1. If not, we report an error. + if self.stopbit1 != 1: + self.putp(['INVALID STOPBIT', 0, self.stopbit1]) + self.putg([5, ['Frame error', 'Frame err', 'FE']]) + self.frame_valid = False + + self.putp(['STOPBIT', 0, self.stopbit1]) + if self.options['anno_startstop'] == 'yes': + self.putg([2, ['Stop bit', 'Stop', 'T']]) + + # Pass the complete UART frame to upper layers. + es = self.samplenum + ceil(self.bit_width / 2.0) + self.putpse(self.frame_start, es, ['FRAME', 0, + (self.datavalue, self.frame_valid)]) + + self.state = 'WAIT FOR START BIT' + + def handle_break(self): + self.putpse(self.frame_start, self.samplenum, + ['BREAK', 0, 0]) + self.putgse(self.frame_start, self.samplenum, + [7, ['Break condition', 'Break', 'Brk', 'B']]) + self.state = 'WAIT FOR START BIT' + + def get_wait_cond(self, inv): + # Return condititions that are suitable for Decoder.wait(). Those + # conditions either match the falling edge of the START bit, or + # the sample point of the next bit time. + state = self.state + if state == 'WAIT FOR START BIT': + return {0: 'r' if inv else 'f'} + if state == 'GET START BIT': + bitnum = 0 + elif state == 'GET DATA BITS': + bitnum = 1 + self.cur_data_bit + elif state == 'GET PARITY BIT': + bitnum = 1 + self.options['num_data_bits'] + elif state == 'GET STOP BITS': + bitnum = 1 + self.options['num_data_bits'] + bitnum += 0 if self.options['parity_type'] == 'none' else 1 + want_num = ceil(self.get_sample_point(bitnum)) + return {'skip': want_num - self.samplenum} + + def inspect_sample(self, signal, inv): + # Inspect a sample returned by .wait() for the specified UART line. + if inv: + signal = not signal + + state = self.state + if state == 'WAIT FOR START BIT': + self.wait_for_start_bit(signal) + elif state == 'GET START BIT': + self.get_start_bit(signal) + elif state == 'GET DATA BITS': + self.get_data_bits(signal) + elif state == 'GET PARITY BIT': + self.get_parity_bit(signal) + elif state == 'GET STOP BITS': + self.get_stop_bits(signal) + + def inspect_edge(self, signal, inv): + # Inspect edges, independently from traffic, to detect break conditions. + if inv: + signal = not signal + if not signal: + # Signal went low. Start another interval. + self.break_start = self.samplenum + return + # Signal went high. Was there an extended period with low signal? + if self.break_start is None: + return + diff = self.samplenum - self.break_start + if diff >= self.break_min_sample_count: + self.handle_break() + self.break_start = None + + def decode(self): + if not self.samplerate: + raise SamplerateError('Cannot decode without samplerate.') + + inv = self.options['invert'] == 'yes' + cond_data_idx = None + + # Determine the number of samples for a complete frame's time span. + # A period of low signal (at least) that long is a break condition. + frame_samples = 1 # START + frame_samples += self.options['num_data_bits'] + frame_samples += 0 if self.options['parity_type'] == 'none' else 1 + frame_samples += self.options['num_stop_bits'] + frame_samples *= self.bit_width + self.break_min_sample_count = ceil(frame_samples) + cond_edge_idx = None + + while True: + conds = [] + + cond_data_idx = len(conds) + conds.append(self.get_wait_cond(inv)) + cond_edge_idx = len(conds) + conds.append({0: 'e'}) + + (rxtx, ) = self.wait(conds) + if cond_data_idx is not None and (self.matched & (0b1 << cond_data_idx)): + self.inspect_sample(rxtx, inv) + if cond_edge_idx is not None and (self.matched & (0b1 << cond_edge_idx)): + self.inspect_edge(rxtx, inv) diff --git a/libsigrokdecode4DSL/decoders/a7105/__init__.py b/libsigrokdecode4DSL/decoders/a7105/__init__.py new file mode 100644 index 00000000..a011c987 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/a7105/__init__.py @@ -0,0 +1,25 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 Jens Steinhauser +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This decoder stacks on top of the 'spi' PD and decodes the protocol spoken +by the AMICCOM A7105 2.4GHz transceiver chips. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/a7105/pd.py b/libsigrokdecode4DSL/decoders/a7105/pd.py new file mode 100644 index 00000000..d441c666 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/a7105/pd.py @@ -0,0 +1,356 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2020 Richard Li +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd + +class ChannelError(Exception): + pass + +regs = { +# addr: ('name', size) + 0x00: ('MODE', 1), + 0x01: ('MODE_CTRL', 1), + 0x02: ('CALC', 1), + 0x03: ('FIFO_I', 1), + 0x04: ('FIFO_II', 1), + 0x05: ('FIFO_DATA', 1), + 0x06: ('ID_DATA', 1), + 0x07: ('RC_OSC_I', 1), + 0x08: ('RC_OSC_II', 1), + 0x09: ('RC_OSC_III', 1), + 0x0a: ('CKO_PIN', 1), + 0x0b: ('GPIO1_PIN_I', 1), + 0x0c: ('GPIO2_PIN_II', 1), + 0x0d: ('CLOCK', 1), + 0x0e: ('DATA_RATE', 1), + 0x0f: ('PLL_I', 1), + 0x10: ('PLL_II', 1), + 0x11: ('PLL_III', 1), + 0x12: ('PLL_IV', 1), + 0x13: ('PLL_V', 1), + 0x14: ('TX_I', 1), + 0x15: ('TX_II', 1), + 0x16: ('DELAY_I', 1), + 0x17: ('DELAY_II', 1), + 0x18: ('RX', 1), + 0x19: ('RX_GAIN_I', 1), + 0x1a: ('RX_GAIN_II', 1), + 0x1b: ('RX_GAIN_III', 1), + 0x1c: ('RX_GAIN_IV', 1), + 0x1d: ('RSSI_THRES', 1), + 0x1e: ('ADC', 1), + 0x1f: ('CODE_I', 1), + 0x20: ('CODE_II', 1), + 0x21: ('CODE_III', 1), + 0x22: ('IF_CAL_I', 1), + 0x23: ('IF_CAL_II', 1), + 0x24: ('VCO_CURR_CAL', 1), + 0x25: ('VCO_SB_CALC_I', 1), + 0x26: ('VCO_SB_CALC_II', 1), + 0x27: ('BATT_DETECT', 1), + 0x28: ('TX_TEST', 1), + 0x29: ('RX_DEM_TEST_I', 1), + 0x2a: ('RX_DEM_TEST_II', 1), + 0x2b: ('CPC', 1), + 0x2c: ('CRYSTAL_TEST', 1), + 0x2d: ('PLL_TEST', 1), + 0x2e: ('VCO_TEST_I', 1), + 0x2f: ('VCO_TEST_II', 1), + 0x30: ('IFAT', 1), + 0x31: ('RSCALE', 1), + 0x32: ('FILTER_TEST', 1), + 0x33: ('UNKNOWN', 1), +} + +class Decoder(srd.Decoder): + api_version = 3 + id = 'a7105' + name = 'A7105' + longname = 'AMICCOM A7105' + desc = '2.4GHz FSK/GFSK Transceiver with 2K ~ 500Kbps data rate.' + license = 'gplv2+' + inputs = ['spi'] + outputs = [] + tags = ['IC', 'Wireless/RF'] + options = ( + {'id': 'hex_display', 'desc': 'Display payload in Hex', 'default': 'yes', + 'values': ('yes', 'no')}, + ) + annotations = ( + # Sent from the host to the chip. + ('cmd', 'Commands sent to the device'), + ('tx-data', 'Payload sent to the device'), + + # Returned by the chip. + ('rx-data', 'Payload read from the device'), + + ('warning', 'Warnings'), + ) + ann_cmd = 0 + ann_tx = 1 + ann_rx = 2 + ann_warn = 3 + annotation_rows = ( + ('commands', 'Commands', (ann_cmd, ann_tx, ann_rx)), + ('warnings', 'Warnings', (ann_warn,)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.next() + self.requirements_met = True + self.cs_was_released = False + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def warn(self, pos, msg): + '''Put a warning message 'msg' at 'pos'.''' + self.put(pos[0], pos[1], self.out_ann, [self.ann_warn, [msg]]) + + def putp(self, pos, ann, msg): + '''Put an annotation message 'msg' at 'pos'.''' + self.put(pos[0], pos[1], self.out_ann, [ann, [msg]]) + + def next(self): + '''Resets the decoder after a complete command was decoded.''' + # 'True' for the first byte after CS went low. + self.first = True + + # The current command, and the minimum and maximum number + # of data bytes to follow. + self.cmd = None + self.min = 0 + self.max = 0 + + # Used to collect the bytes after the command byte + # (and the start/end sample number). + self.mb = [] + self.mb_s = -1 + self.mb_e = -1 + + def mosi_bytes(self): + '''Returns the collected MOSI bytes of a multi byte command.''' + return [b[0] for b in self.mb] + + def miso_bytes(self): + '''Returns the collected MISO bytes of a multi byte command.''' + return [b[1] for b in self.mb] + + def decode_command(self, pos, b): + '''Decodes the command byte 'b' at position 'pos' and prepares + the decoding of the following data bytes.''' + c = self.parse_command(b) + if c is None: + self.warn(pos, 'unknown command') + return + + self.cmd, self.dat, self.min, self.max = c + + if self.cmd in ('W_REGISTER', 'R_REGISTER'): + # Don't output anything now, the command is merged with + # the data bytes following it. + self.mb_s = pos[0] + else: + self.putp(pos, self.ann_cmd, self.format_command()) + + def format_command(self): + '''Returns the label for the current command.''' + return 'Cmd {}'.format(self.cmd) + + def parse_command(self, b): + '''Parses the command byte. + + Returns a tuple consisting of: + - the name of the command + - additional data needed to dissect the following bytes + - minimum number of following bytes + - maximum number of following bytes + ''' + + if b == 0x05: + return ('W_TX_FIFO', None, 1, 32) + elif b == 0x45: + return ('R_RX_FIFO', None, 1, 32) + if b == 0x06: + return ('W_ID', None, 1, 4) + elif b == 0x46: + return ('R_ID', None, 1, 4) + elif (b & 0b10000000) == 0: + if (b & 0b01000000) == 0: + c = 'W_REGISTER' + else: + c = 'R_REGISTER' + d = b & 0b00111111 + return (c, d, 1, 1) + + else: + cmd = b & 0b11110000 + if cmd == 0b10000000: + return ('SLEEP_MODE', None, 0, 0) + if cmd == 0b10010000: + return ('IDLE_MODE', None, 0, 0) + if cmd == 0b10100000: + return ('STANDBY_MODE', None, 0, 0) + if cmd == 0b10110000: + return ('PLL_MODE', None, 0, 0) + if cmd == 0b11000000: + return ('RX_MODE', None, 0, 0) + if cmd == 0b11010000: + return ('TX_MODE', None, 0, 0) + if cmd == 0b11100000: + return ('FIFO_WRITE_PTR_RESET', None, 0, 0) + if cmd == 0b11110000: + return ('FIFO_READ_PTR_RESET', None, 0, 0) + + def decode_register(self, pos, ann, regid, data): + '''Decodes a register. + + pos -- start and end sample numbers of the register + ann -- is the annotation number that is used to output the register. + regid -- may be either an integer used as a key for the 'regs' + dictionary, or a string directly containing a register name.' + data -- is the register content. + ''' + + if type(regid) == int: + # Get the name of the register. + if regid not in regs: + self.warn(pos, 'unknown register') + return + name = regs[regid][0] + else: + name = regid + + # Multi byte register come LSByte first. + data = reversed(data) + + label = '{}: {}'.format(self.format_command(), name) + + self.decode_mb_data(pos, ann, data, label, True) + + def decode_mb_data(self, pos, ann, data, label, always_hex): + '''Decodes the data bytes 'data' of a multibyte command at position + 'pos'. The decoded data is prefixed with 'label'. If 'always_hex' is + True, all bytes are decoded as hex codes, otherwise only non + printable characters are escaped.''' + + if always_hex: + def escape(b): + return '{:02X}'.format(b) + else: + def escape(b): + c = chr(b) + if not str.isprintable(c): + return '\\x{:02X}'.format(b) + return c + + data = ''.join([escape(b) for b in data]) + text = '{} = "{}"'.format(label, data.strip()) + self.putp(pos, ann, text) + + def finish_command(self, pos): + '''Decodes the remaining data bytes at position 'pos'.''' + + always_hex = self.options['hex_display'] == 'yes' + if self.cmd == 'R_REGISTER': + self.decode_register(pos, self.ann_cmd, + self.dat, self.miso_bytes()) + elif self.cmd == 'W_REGISTER': + self.decode_register(pos, self.ann_cmd, + self.dat, self.mosi_bytes()) + elif self.cmd == 'R_RX_FIFO': + self.decode_mb_data(pos, self.ann_rx, + self.miso_bytes(), 'RX FIFO', always_hex) + elif self.cmd == 'W_TX_FIFO': + self.decode_mb_data(pos, self.ann_tx, + self.mosi_bytes(), 'TX FIFO', always_hex) + elif self.cmd == 'R_ID': + self.decode_mb_data(pos, self.ann_rx, + self.miso_bytes(), 'R ID', always_hex) + elif self.cmd == 'W_ID': + self.decode_mb_data(pos, self.ann_tx, + self.mosi_bytes(), 'W ID', always_hex) + + def decode(self, ss, es, data): + if not self.requirements_met: + return + + ptype, data1, data2 = data + + if ptype == 'TRANSFER': + if self.cmd: + # Check if we got the minimum number of data bytes + # after the command byte. + if len(self.mb) < self.min: + self.warn((ss, ss), 'missing data bytes') + elif self.mb: + self.finish_command((self.mb_s, self.mb_e)) + + self.next() + self.cs_was_released = True + elif ptype == 'CS-CHANGE': + if data1 is None: + if data2 is None: + self.requirements_met = False + raise ChannelError('CS# pin required.') + elif data2 == 1: + self.cs_was_released = True + + if data1 == 0 and data2 == 1: + # Rising edge, the complete command is transmitted, process + # the bytes that were send after the command byte. + if self.cmd: + # Check if we got the minimum number of data bytes + # after the command byte. + if len(self.mb) < self.min: + self.warn((ss, ss), 'missing data bytes') + elif self.mb: + self.finish_command((self.mb_s, self.mb_e)) + + self.next() + self.cs_was_released = True + elif ptype == 'DATA' and self.cs_was_released: + mosi, miso = data1, data2 + pos = (ss, es) + + if miso is None and mosi is None: + self.requirements_met = False + raise ChannelError('Either MISO or MOSI pins required (3 wires SPI).') + + if miso is None: + miso = mosi + if mosi is None: + mosi = miso + + if self.first: + self.first = False + # First byte is always the command. + self.decode_command(pos, mosi) + else: + if not self.cmd or len(self.mb) >= self.max: + self.warn(pos, 'excess byte') + else: + # Collect the bytes after the command byte. + if self.mb_s == -1: + self.mb_s = ss + self.mb_e = es + self.mb.append((mosi, miso)) diff --git a/libsigrokdecode4DSL/decoders/ac97/__init__.py b/libsigrokdecode4DSL/decoders/ac97/__init__.py new file mode 100644 index 00000000..8b96e8aa --- /dev/null +++ b/libsigrokdecode4DSL/decoders/ac97/__init__.py @@ -0,0 +1,36 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2017 Gerhard Sittig +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +AC'97 (Audio Codec '97) was specifically designed by Intel for audio and +modem I/O functionality in mainstream PC systems. See the specification in +http://download.intel.com/support/motherboards/desktop/sb/ac97_r23.pdf + +AC'97 communicates full duplex data (SDATA_IN, SDATA_OUT), where bits +are clocked by the BIT_CLK and frames are signalled by the SYNC signals. +A low active RESET# line completes the set of signals. + +Frames repeat at a nominal frequency of 48kHz, and consist of 256 bits +each. One 16bit slot contains management information, twelve 20bit slots +follow which carry data for three management and nine audio/modem channels. +Optionally two slots of one frame can get combined for higher resolution +on fewer channels, or double data rate. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/ac97/pd.py b/libsigrokdecode4DSL/decoders/ac97/pd.py new file mode 100644 index 00000000..3f79eefa --- /dev/null +++ b/libsigrokdecode4DSL/decoders/ac97/pd.py @@ -0,0 +1,505 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2017 Gerhard Sittig +## Copyright (C) 2019 DreamSourceLab +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +# This implementation is incomplete. TODO items: +# - Support the optional RESET# pin, detect cold and warm reset. +# - Split slot values into audio samples of their respective width and +# frequency (either on user provided parameters, or from inspection of +# decoded register access). + +import sigrokdecode as srd + +class ChannelError(Exception): + pass + +class Pins: + (SYNC, BIT_CLK, SDATA_OUT, SDATA_IN, RESET) = range(5) + +class Ann: + ( + BITS_OUT, BITS_IN, + SLOT_OUT_RAW, SLOT_OUT_TAG, SLOT_OUT_ADDR, SLOT_OUT_DATA, + SLOT_OUT_03, SLOT_OUT_04, SLOT_OUT_05, SLOT_OUT_06, + SLOT_OUT_07, SLOT_OUT_08, SLOT_OUT_09, SLOT_OUT_10, + SLOT_OUT_11, SLOT_OUT_IO, + SLOT_IN_RAW, SLOT_IN_TAG, SLOT_IN_ADDR, SLOT_IN_DATA, + SLOT_IN_03, SLOT_IN_04, SLOT_IN_05, SLOT_IN_06, + SLOT_IN_07, SLOT_IN_08, SLOT_IN_09, SLOT_IN_10, + SLOT_IN_11, SLOT_IN_IO, + WARN, ERROR, + ) = range(32) + ( + BIN_FRAME_OUT, + BIN_FRAME_IN, + BIN_SLOT_RAW_OUT, + BIN_SLOT_RAW_IN, + ) = range(4) + +class Decoder(srd.Decoder): + api_version = 3 + id = 'ac97' + name = "AC '97" + longname = "Audio Codec '97" + desc = 'Audio and modem control for PC systems.' + license = 'gplv2+' + inputs = ['logic'] + outputs = [] + tags = ['Audio', 'PC'] + channels = ( + {'id': 'sync', 'name': 'SYNC', 'desc': 'Frame synchronization'}, + {'id': 'clk', 'name': 'BIT_CLK', 'desc': 'Data bits clock'}, + ) + optional_channels = ( + {'id': 'out', 'name': 'SDATA_OUT', 'desc': 'Data output'}, + {'id': 'in', 'name': 'SDATA_IN', 'desc': 'Data input'}, + {'id': 'rst', 'name': 'RESET#', 'desc': 'Reset line'}, + ) + annotations = ( + ('bit-out', 'Output bits'), + ('bit-in', 'Input bits'), + ('slot-out-raw', 'Output raw value'), + ('slot-out-tag', 'Output TAG'), + ('slot-out-cmd-addr', 'Output command address'), + ('slot-out-cmd-data', 'Output command data'), + ('slot-out-03', 'Output slot 3'), + ('slot-out-04', 'Output slot 4'), + ('slot-out-05', 'Output slot 5'), + ('slot-out-06', 'Output slot 6'), + ('slot-out-07', 'Output slot 7'), + ('slot-out-08', 'Output slot 8'), + ('slot-out-09', 'Output slot 9'), + ('slot-out-10', 'Output slot 10'), + ('slot-out-11', 'Output slot 11'), + ('slot-out-io-ctrl', 'Output I/O control'), + ('slot-in-raw', 'Input raw value'), + ('slot-in-tag', 'Input TAG'), + ('slot-in-sts-addr', 'Input status address'), + ('slot-in-sts-data', 'Input status data'), + ('slot-in-03', 'Input slot 3'), + ('slot-in-04', 'Input slot 4'), + ('slot-in-05', 'Input slot 5'), + ('slot-in-06', 'Input slot 6'), + ('slot-in-07', 'Input slot 7'), + ('slot-in-08', 'Input slot 8'), + ('slot-in-09', 'Input slot 9'), + ('slot-in-10', 'Input slot 10'), + ('slot-in-11', 'Input slot 11'), + ('slot-in-io-sts', 'Input I/O status'), + # TODO: Add more annotation classes: + # TAG: 'ready', 'valid', 'id', 'rsv' + # CMD ADDR: 'r/w', 'addr', 'unused' + # CMD DATA: 'data', 'unused' + # 3-11: 'data', 'unused', 'double data' + ('warning', 'Warning'), + ('error', 'Error'), + ) + annotation_rows = ( + ('bits-out', 'Output bits', (Ann.BITS_OUT,)), + ('slots-out-raw', 'Output numbers', (Ann.SLOT_OUT_RAW,)), + ('slots-out', 'Output slots', ( + Ann.SLOT_OUT_TAG, Ann.SLOT_OUT_ADDR, Ann.SLOT_OUT_DATA, + Ann.SLOT_OUT_03, Ann.SLOT_OUT_04, Ann.SLOT_OUT_05, Ann.SLOT_OUT_06, + Ann.SLOT_OUT_07, Ann.SLOT_OUT_08, Ann.SLOT_OUT_09, Ann.SLOT_OUT_10, + Ann.SLOT_OUT_11, Ann.SLOT_OUT_IO,)), + ('bits-in', 'Input bits', (Ann.BITS_IN,)), + ('slots-in-raw', 'Input numbers', (Ann.SLOT_IN_RAW,)), + ('slots-in', 'Input slots', ( + Ann.SLOT_IN_TAG, Ann.SLOT_IN_ADDR, Ann.SLOT_IN_DATA, + Ann.SLOT_IN_03, Ann.SLOT_IN_04, Ann.SLOT_IN_05, Ann.SLOT_IN_06, + Ann.SLOT_IN_07, Ann.SLOT_IN_08, Ann.SLOT_IN_09, Ann.SLOT_IN_10, + Ann.SLOT_IN_11, Ann.SLOT_IN_IO,)), + ('warnings', 'Warnings', (Ann.WARN,)), + ('errors', 'Errors', (Ann.ERROR,)), + ) + binary = ( + ('frame-out', 'Frame bits, output data'), + ('frame-in', 'Frame bits, input data'), + ('slot-raw-out', 'Raw slot bits, output data'), + ('slot-raw-in', 'Raw slot bits, input data'), + # TODO: Which (other) binary classes to implement? + # - Are binary annotations per audio slot useful? + # - Assume 20bit per slot, in 24bit units? Or assume 16bit + # audio samples? Observe register access and derive width + # of the audio data? Dump channels 3-11 or 1-12? + ) + + def putx(self, ss, es, cls, data): + self.put(ss, es, self.out_ann, [cls, data]) + + def putf(self, frombit, bitcount, cls, data): + ss = self.frame_ss_list[frombit] + es = self.frame_ss_list[frombit + bitcount] + self.putx(ss, es, cls, data) + + def putb(self, frombit, bitcount, cls, data): + ss = self.frame_ss_list[frombit] + es = self.frame_ss_list[frombit + bitcount] + self.put(ss, es, self.out_binary, [cls, data]) + + def __init__(self): + self.out_binary = None + self.out_ann = None + self.reset() + + def reset(self): + self.frame_ss_list = None + self.frame_slot_lens = [0, 16] + [16 + 20 * i for i in range(1, 13)] + self.frame_total_bits = self.frame_slot_lens[-1] + self.handle_slots = { + 0: self.handle_slot_00, + 1: self.handle_slot_01, + 2: self.handle_slot_02, + } + + def start(self): + if not self.out_binary: + self.out_binary = self.register(srd.OUTPUT_BINARY) + if not self.out_ann: + self.out_ann = self.register(srd.OUTPUT_ANN) + + def metadata(self, key, value): + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + + def bits_to_int(self, bits): + # Convert MSB-first bit sequence to integer value. + if not bits: + return 0 + count = len(bits) + value = sum([2 ** (count - 1 - i) for i in range(count) if bits[i]]) + return value + + def bits_to_bin_ann(self, bits): + # Convert MSB-first bit sequence to binary annotation data. + # It's assumed that the number of bits does not (in useful ways) + # fit into an integer, and we need to create an array of bytes + # from the data afterwards, anyway. Hence the separate routine + # and the conversion of eight bits each. + out = [] + count = len(bits) + while count > 0: + count -= 8 + by, bits = bits[:8], bits[8:] + by = self.bits_to_int(by) + out.append(by) + out = bytes(out) + return out + + def int_to_nibble_text(self, value, bitcount): + # Convert number to hex digits for given bit count. + digits = (bitcount + 3) // 4 + text = '{{:0{:d}x}}'.format(digits).format(value) + return text + + def get_bit_field(self, data, size, off, count): + shift = size - off - count + data >>= shift + mask = (1 << count) - 1 + data &= mask + return data + + def flush_frame_bits(self): + # Flush raw frame bits to binary annotation. + anncls = Ann.BIN_FRAME_OUT + data = self.frame_bits_out[:] + count = len(data) + data = self.bits_to_bin_ann(data) + self.putb(0, count, anncls, data) + + anncls = Ann.BIN_FRAME_IN + data = self.frame_bits_in[:] + count = len(data) + data = self.bits_to_bin_ann(data) + self.putb(0, count, anncls, data) + + def start_frame(self, ss): + # Mark the start of a frame. + if self.frame_ss_list: + # Flush bits if we had a frame before the frame which is + # starting here. + self.flush_frame_bits() + self.frame_ss_list = [ss] + self.frame_bits_out = [] + self.frame_bits_in = [] + self.frame_slot_data_out = [] + self.frame_slot_data_in = [] + self.have_slots = {True: None, False: None} + + def handle_slot_dummy(self, slotidx, bitidx, bitcount, is_out, data): + # Handle slot x, default/fallback handler. + # Only process data of slots 1-12 when slot 0 says "valid". + if not self.have_slots[is_out]: + return + if not self.have_slots[is_out][slotidx]: + return + + # Emit a naive annotation with just the data bits that we saw + # for the slot (hex nibbles for density). For audio data this + # can be good enough. Slots with special meaning should not end + # up calling the dummy handler. + text = self.int_to_nibble_text(data, bitcount) + anncls = Ann.SLOT_OUT_TAG if is_out else Ann.SLOT_IN_TAG + self.putf(bitidx, bitcount, anncls + slotidx, [text]) + + # Emit binary output for the data that is contained in slots + # which end up calling the default handler. This transparently + # should translate to "the slots with audio data", as other + # slots which contain management data should have their specific + # handler routines. In the present form, this approach might be + # good enough to get a (header-less) audio stream for typical + # setups where only line-in or line-out are in use. + # + # TODO: Improve this early prototype implementation. For now the + # decoder just exports the upper 16 bits of each audio channel + # that happens to be valid. For an improved implementation, it + # either takes user provided specs or more smarts like observing + # register access (if the capture includes it). + anncls = Ann.BIN_SLOT_RAW_OUT if is_out else Ann.BIN_SLOT_RAW_IN + data_bin = data >> 4 + data_bin &= 0xffff + data_bin = data_bin.to_bytes(2, byteorder = 'big') + self.putb(bitidx, bitcount, anncls, data_bin) + + def handle_slot_00(self, slotidx, bitidx, bitcount, is_out, data): + # Handle slot 0, TAG. + slotpos = self.frame_slot_lens[slotidx] + fieldoff = 0 + anncls = Ann.SLOT_OUT_TAG if is_out else Ann.SLOT_IN_TAG + + fieldlen = 1 + ready = self.get_bit_field(data, bitcount, fieldoff, fieldlen) + text = ['READY: 1', 'READY', 'RDY', 'R'] if ready else ['ready: 0', 'rdy', '-'] + self.putf(slotpos + fieldoff, fieldlen, anncls, text) + fieldoff += fieldlen + + fieldlen = 12 + valid = self.get_bit_field(data, bitcount, fieldoff, fieldlen) + text = ['VALID: {:3x}'.format(valid), '{:3x}'.format(valid)] + self.putf(slotpos + fieldoff, fieldlen, anncls, text) + have_slots = [True] + [False] * 12 + for idx in range(12): + have_slots[idx + 1] = bool(valid & (1 << (11 - idx))) + self.have_slots[is_out] = have_slots + fieldoff += fieldlen + + fieldlen = 1 + rsv = self.get_bit_field(data, bitcount, fieldoff, fieldlen) + if rsv != 0: + text = ['reserved bit error', 'rsv error', 'rsv'] + self.putf(slotpos + fieldoff, fieldlen, Ann.ERROR, text) + fieldoff += fieldlen + + # TODO: Will input slot 0 have a Codec ID, or 3 reserved bits? + fieldlen = 2 + codec = self.get_bit_field(data, bitcount, fieldoff, fieldlen) + text = ['CODEC: {:1x}'.format(codec), '{:1x}'.format(codec)] + self.putf(slotpos + fieldoff, fieldlen, anncls, text) + fieldoff += fieldlen + + def handle_slot_01(self, slotidx, bitidx, bitcount, is_out, data): + # Handle slot 1, command/status address. + slotpos = self.frame_slot_lens[slotidx] + if not self.have_slots[is_out]: + return + if not self.have_slots[is_out][slotidx]: + return + fieldoff = 0 + anncls = Ann.SLOT_OUT_TAG if is_out else Ann.SLOT_IN_TAG + anncls += slotidx + + fieldlen = 1 + if is_out: + is_read = self.get_bit_field(data, bitcount, fieldoff, fieldlen) + text = ['READ', 'RD', 'R'] if is_read else ['WRITE', 'WR', 'W'] + self.putf(slotpos + fieldoff, fieldlen, anncls, text) + # TODO: Check for the "atomic" constraint? Some operations + # involve address _and_ data, which cannot be spread across + # several frames. Slot 0 and 1 _must_ be provided within the + # same frame (the test should occur in the handler for slot + # 2 of course, in slot 1 we don't know what will follow). + else: + rsv = self.get_bit_field(data, bitcount, fieldoff, fieldlen) + if rsv != 0: + text = ['reserved bit error', 'rsv error', 'rsv'] + self.putf(slotpos + fieldoff, fieldlen, Ann.ERROR, text) + fieldoff += fieldlen + + fieldlen = 7 + regaddr = self.get_bit_field(data, bitcount, fieldoff, fieldlen) + # TODO: Present 0-63 or 0-126 as the address of the 16bit register? + text = ['ADDR: {:2x}'.format(regaddr), '{:2x}'.format(regaddr)] + self.putf(slotpos + fieldoff, fieldlen, anncls, text) + if regaddr & 0x01: + text = ['odd register address', 'odd reg addr', 'odd addr', 'odd'] + self.putf(slotpos + fieldoff, fieldlen, Ann.ERROR, text) + fieldoff += fieldlen + + # Strictly speaking there are 10 data request bits and 2 reserved + # bits for input slots, and 12 reserved bits for output slots. We + # test for 10 and 2 bits, to simplify the logic. Only in case of + # non-zero reserved bits for outputs this will result in "a little + # strange" an annotation. This is a cosmetic issue, we don't mind. + fieldlen = 10 + reqdata = self.get_bit_field(data, bitcount, fieldoff, fieldlen) + if is_out and reqdata != 0: + text = ['reserved bit error', 'rsv error', 'rsv'] + self.putf(slotpos + fieldoff, fieldlen, Ann.ERROR, text) + if not is_out: + text = ['REQ: {:3x}'.format(reqdata), '{:3x}'.format(reqdata)] + self.putf(slotpos + fieldoff, fieldlen, anncls, text) + fieldoff += fieldlen + + fieldlen = 2 + rsv = self.get_bit_field(data, bitcount, fieldoff, fieldlen) + if rsv != 0: + text = ['reserved bit error', 'rsv error', 'rsv'] + self.putf(slotpos + fieldoff, fieldlen, Ann.ERROR, text) + fieldoff += fieldlen + + def handle_slot_02(self, slotidx, bitidx, bitcount, is_out, data): + # Handle slot 2, command/status data. + slotpos = self.frame_slot_lens[slotidx] + if not self.have_slots[is_out]: + return + if not self.have_slots[is_out][slotidx]: + return + fieldoff = 0 + anncls = Ann.SLOT_OUT_TAG if is_out else Ann.SLOT_IN_TAG + anncls += slotidx + + fieldlen = 16 + rwdata = self.get_bit_field(data, bitcount, fieldoff, fieldlen) + # TODO: Check for zero output data when the operation is a read. + # TODO: Check for the "atomic" constraint. + text = ['DATA: {:4x}'.format(rwdata), '{:4x}'.format(rwdata)] + self.putf(slotpos + fieldoff, fieldlen, anncls, text) + fieldoff += fieldlen + + fieldlen = 4 + rsv = self.get_bit_field(data, bitcount, fieldoff, fieldlen) + if rsv != 0: + text = ['reserved bits error', 'rsv error', 'rsv'] + self.putf(slotpos + fieldoff, fieldlen, Ann.ERROR, text) + fieldoff += fieldlen + + # TODO: Implement other slots. + # - 1: cmd/status addr (check status vs command) + # - 2: cmd/status data (check status vs command) + # - 3-11: audio out/in + # - 12: io control/status (modem GPIO(?)) + + def handle_slot(self, slotidx, data_out, data_in): + # Process a received slot of a frame. + func = self.handle_slots.get(slotidx, self.handle_slot_dummy) + bitidx = self.frame_slot_lens[slotidx] + bitcount = self.frame_slot_lens[slotidx + 1] - bitidx + if data_out is not None: + func(slotidx, bitidx, bitcount, True, data_out) + if data_in is not None: + func(slotidx, bitidx, bitcount, False, data_in) + + def handle_bits(self, ss, es, bit_out, bit_in): + # Process a received pair of bits. + # Emit the bits' annotations. Only interpret the data when we + # are in a frame (have seen the start of the frame, and don't + # exceed the expected number of bits in a frame). + if bit_out is not None: + self.putx(ss, es, Ann.BITS_OUT, ['{:d}'.format(bit_out)]) + if bit_in is not None: + self.putx(ss, es, Ann.BITS_IN, ['{:d}'.format(bit_in)]) + if self.frame_ss_list is None: + return + self.frame_ss_list.append(es) + have_len = len(self.frame_ss_list) - 1 + if have_len > self.frame_total_bits: + return + + # Accumulate the bits within the frame, until one slot of the + # frame has become available. + slot_idx = 0 + if bit_out is not None: + self.frame_bits_out.append(bit_out) + slot_idx = len(self.frame_slot_data_out) + if bit_in is not None: + self.frame_bits_in.append(bit_in) + slot_idx = len(self.frame_slot_data_in) + want_len = self.frame_slot_lens[slot_idx + 1] + if have_len != want_len: + return + prev_len = self.frame_slot_lens[slot_idx] + + # Convert bits to integer values. This shall simplify extraction + # of bit fields in multiple other locations. + slot_data_out = None + if bit_out is not None: + slot_bits = self.frame_bits_out[prev_len:] + slot_data = self.bits_to_int(slot_bits) + self.frame_slot_data_out.append(slot_data) + slot_data_out = slot_data + slot_data_in = None + if bit_in is not None: + slot_bits = self.frame_bits_in[prev_len:] + slot_data = self.bits_to_int(slot_bits) + self.frame_slot_data_in.append(slot_data) + slot_data_in = slot_data + + # Emit simple annotations for the integer values, until upper + # layer decode stages will be implemented. + slot_len = have_len - prev_len + slot_ss = self.frame_ss_list[prev_len] + slot_es = self.frame_ss_list[have_len] + if slot_data_out is not None: + slot_text = self.int_to_nibble_text(slot_data_out, slot_len) + self.putx(slot_ss, slot_es, Ann.SLOT_OUT_RAW, [slot_text]) + if slot_data_in is not None: + slot_text = self.int_to_nibble_text(slot_data_in, slot_len) + self.putx(slot_ss, slot_es, Ann.SLOT_IN_RAW, [slot_text]) + + self.handle_slot(slot_idx, slot_data_out, slot_data_in) + + def decode(self): + have_sdo = self.has_channel(Pins.SDATA_OUT) + have_sdi = self.has_channel(Pins.SDATA_IN) + if not have_sdo and not have_sdi: + raise ChannelError('Either SDATA_OUT or SDATA_IN (or both) are required.') + have_reset = self.has_channel(Pins.RESET) + + # Data is sampled at falling CLK edges. Annotations need to span + # the period between rising edges. SYNC rises one cycle _before_ + # the start of a frame. Grab the earliest SYNC sample we can get + # and advance to the start of a bit time. Then keep getting the + # samples and the end of all subsequent bit times. + prev_sync = [None, None, None] + (sync, bit_clk, sdata_out, sdata_in, reset) = self.wait({Pins.BIT_CLK: 'e'}) + if bit_clk == 0: + prev_sync[-1] = sync + (sync, bit_clk, sdata_out, sdata_in, reset) = self.wait({Pins.BIT_CLK: 'r'}) + bit_ss = self.samplenum + while True: + (sync, bit_clk, sdata_out, sdata_in, reset) = self.wait({Pins.BIT_CLK: 'f'}) + prev_sync.pop(0) + prev_sync.append(sync) + self.wait({Pins.BIT_CLK: 'r'}) + if prev_sync[0] == 0 and prev_sync[1] == 1: + self.start_frame(bit_ss) + self.handle_bits(bit_ss, self.samplenum, + sdata_out if have_sdo else None, + sdata_in if have_sdi else None) + bit_ss = self.samplenum diff --git a/libsigrokdecode4DSL/decoders/ad5626/__init__.py b/libsigrokdecode4DSL/decoders/ad5626/__init__.py new file mode 100644 index 00000000..5f67799b --- /dev/null +++ b/libsigrokdecode4DSL/decoders/ad5626/__init__.py @@ -0,0 +1,25 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2020 Analog Devices Inc. +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 3 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This decoder stacks on top of the 'spi' PD and decodes the +Analog Devices AD5626 protocol. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/ad5626/pd.py b/libsigrokdecode4DSL/decoders/ad5626/pd.py new file mode 100644 index 00000000..cffee83d --- /dev/null +++ b/libsigrokdecode4DSL/decoders/ad5626/pd.py @@ -0,0 +1,62 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2020 Analog Devices Inc. +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 3 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd + +class Decoder(srd.Decoder): + api_version = 3 + id = 'ad5626' + name = 'AD5626' + longname = 'Analog Devices AD5626' + desc = 'Analog Devices AD5626 12-bit nanoDAC.' + license = 'gplv2+' + inputs = ['spi'] + outputs = [] + tags = ['IC', 'Analog/digital'] + annotations = ( + ('voltage', 'Voltage'), + ) + + def __init__(self,): + self.reset() + + def reset(self): + self.data = 0 + self.ss = 0 + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def decode(self, ss, es, data): + ptype = data[0] + + if ptype == 'CS-CHANGE': + cs_old, cs_new = data[1:] + if cs_old is not None and cs_old == 0 and cs_new == 1: + self.data >>= 1 + self.data /= 1000 + self.put(self.ss, es, self.out_ann, [0, ['%.3fV' % self.data]]) + self.data = 0 + elif cs_old is not None and cs_old == 1 and cs_new == 0: + self.ss = ss + elif ptype == 'BITS': + mosi = data[1] + for bit in reversed(mosi): + self.data = self.data | bit[0] + self.data <<= 1 diff --git a/libsigrokdecode4DSL/decoders/ad79x0/__init__.py b/libsigrokdecode4DSL/decoders/ad79x0/__init__.py new file mode 100644 index 00000000..0e81f17b --- /dev/null +++ b/libsigrokdecode4DSL/decoders/ad79x0/__init__.py @@ -0,0 +1,25 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2020 Analog Devices Inc. +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 3 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This decoder stacks on top of the 'spi' PD and decodes the +Analog Devices AD7910/AD7920 protocol. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/ad79x0/pd.py b/libsigrokdecode4DSL/decoders/ad79x0/pd.py new file mode 100644 index 00000000..3d7ab731 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/ad79x0/pd.py @@ -0,0 +1,137 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2020 Analog Devices Inc. +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 3 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd + +modes = { + 0: ['Normal Mode', 'Normal', 'Norm', 'N'], + 1: ['Power Down Mode', 'Power Down', 'PD'], + 2: ['Power Up Mode', 'Power Up', 'PU'], +} + +input_voltage_format = ['%.6fV', '%.2fV'] + +validation = { + 'invalid': ['Invalid data', 'Invalid', 'N/A'], + 'incomplete': ['Incomplete conversion', 'Incomplete', 'I'], + 'complete': ['Complete conversion', 'Complete', 'C'], +} + +class Decoder(srd.Decoder): + api_version = 3 + id = 'ad79x0' + name = 'AD79x0' + longname = 'Analog Devices AD79x0' + desc = 'Analog Devices AD7910/AD7920 12-bit ADC.' + license = 'gplv2+' + inputs = ['spi'] + outputs = [] + tags = ['IC', 'Analog/digital'] + annotations = ( + ('mode', 'Mode'), + ('voltage', 'Voltage'), + ('validation', 'Validation'), + ) + annotation_rows = ( + ('modes', 'Modes', (0,)), + ('voltages', 'Voltages', (1,)), + ('data_validation', 'Data validation', (2,)), + ) + options = ( + {'id': 'vref', 'desc': 'Reference voltage (V)', 'default': 1.5}, + ) + + def __init__(self,): + self.reset() + + def reset(self): + self.samplerate = 0 + self.samples_bit = -1 + self.ss = -1 + self.start_sample = 0 + self.previous_state = 0 + self.data = 0 + + def metadata(self, key, value): + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def put_validation(self, pos, msg): + self.put(pos[0], pos[1], self.out_ann, [2, validation[msg]]) + + def put_data(self, pos, input_voltage): + ann = [] + for format in input_voltage_format: + ann.append(format % input_voltage) + self.put(pos[0], pos[1], self.out_ann, [1, ann]) + + def put_mode(self, pos, msg): + self.put(pos[0], pos[1], self.out_ann, [0, modes[msg]]) + + def decode(self, ss, es, data): + ptype = data[0] + + if ptype == 'CS-CHANGE': + cs_old, cs_new = data[1:] + if cs_old is not None and cs_old == 0 and cs_new == 1: + if self.samples_bit == -1: + return + self.data >>= 1 + nb_bits = (ss - self.ss) // self.samples_bit + if nb_bits >= 10: + if self.data == 0xFFF: + self.put_mode([self.start_sample, es], 2) + self.previous_state = 0 + self.put_validation([self.start_sample, es], 'invalid') + else: + self.put_mode([self.start_sample, es], 0) + if nb_bits == 16: + self.put_validation([self.start_sample, es], 'complete') + elif nb_bits < 16: + self.put_validation([self.start_sample, es], 'incomplete') + vin = (self.data / ((2**12) - 1)) * self.options['vref'] + self.put_data([self.start_sample, es], vin) + elif nb_bits < 10: + self.put_mode([self.start_sample, es], 1) + self.previous_state = 1 + self.put_validation([self.start_sample, es], 'invalid') + + self.ss = -1 + self.samples_bit = -1 + self.data = 0 + elif cs_old is not None and cs_old == 1 and cs_new == 0: + self.start_sample = ss + self.samples_bit = -1 + + elif ptype == 'BITS': + if data[2] is None: + return + miso = data[2] + if self.samples_bit == -1: + self.samples_bit = miso[0][2] - miso[0][1] + + if self.ss == -1: + self.ss = ss + + for bit in reversed(miso): + self.data = self.data | bit[0] + self.data <<= 1 diff --git a/libsigrokdecode4DSL/decoders/ade77xx/__init__.py b/libsigrokdecode4DSL/decoders/ade77xx/__init__.py new file mode 100644 index 00000000..cbe8689d --- /dev/null +++ b/libsigrokdecode4DSL/decoders/ade77xx/__init__.py @@ -0,0 +1,32 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2017 Karl Palsson +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + +''' +This decoder stacks on top of the 'spi' PD and decodes Analog Devices +ADE77xx command/responses. + +The ADE77xx is a "Poly Phase Multifunction Energy Metering IC with Per Phase +Information". + +This PD has been tested with an ADE7758 so far, support for other devices +from the ADE77xx series can be added in the future. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/ade77xx/lists.py b/libsigrokdecode4DSL/decoders/ade77xx/lists.py new file mode 100644 index 00000000..f556389d --- /dev/null +++ b/libsigrokdecode4DSL/decoders/ade77xx/lists.py @@ -0,0 +1,102 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2017 Karl Palsson +## +## Permission is hereby granted, free of charge, to any person obtaining a copy +## of this software and associated documentation files (the "Software"), to deal +## in the Software without restriction, including without limitation the rights +## to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +## copies of the Software, and to permit persons to whom the Software is +## furnished to do so, subject to the following conditions: +## +## The above copyright notice and this permission notice shall be included in all +## copies or substantial portions of the Software. +## +## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +## IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +## FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +## AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +## LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +## OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +## SOFTWARE. + +from collections import OrderedDict + +# Generated from datasheet rev E, using tabula. +regs = OrderedDict([ + (0x1, ('AWATTHR', 'Watt-Hour Accumulation Register for Phase A. Active power is accumulated over time in this read-only register. The AWATTHR register can hold a maximum of 0.52 seconds of active energy information with full-scale analog inputs before it overflows (see the Active Energy Calculation section). Bit 0 and Bit 1 of the COMPMODE register determine how the active energy is processed from the six analog inputs.', 'R', 16, 'S', 0x0)), + (0x2, ('BWATTHR', 'Watt-Hour Accumulation Register for Phase B.', 'R', 16, 'S', 0x0)), + (0x3, ('CWATTHR', 'Watt-Hour Accumulation Register for Phase C.', 'R', 16, 'S', 0x0)), + (0x4, ('AVARHR', 'VAR-Hour Accumulation Register for Phase A. Reactive power is accumulated over time in this read-only register. The AVARHR register can hold a maximum of 0.52 seconds of reactive energy information with full-scale analog inputs before it overflows (see the Reactive Energy Calculation section). Bit 0 and Bit 1 of the COMPMODE register determine how the reactive energy is processed from the six analog inputs.', 'R', 16, 'S', 0x0)), + (0x5, ('BVARHR', 'VAR-Hour Accumulation Register for Phase B.', 'R', 16, 'S', 0x0)), + (0x6, ('CVARHR', 'VAR-Hour Accumulation Register for Phase C.', 'R', 16, 'S', 0x0)), + (0x7, ('AVAHR', 'VA-Hour Accumulation Register for Phase A. Apparent power is accumulated over time in this read-only register. The AVAHR register can hold a maximum of 1.15 seconds of apparent energy information with full-scale analog inputs before it overflows (see the Apparent Energy Calculation section). Bit 0 and Bit 1 of the COMPMODE register determine how the apparent energy is processed from the six analog inputs.', 'R', 16, 'S', 0x0)), + (0x8, ('BVAHR', 'VA-Hour Accumulation Register for Phase B.', 'R', 16, 'S', 0x0)), + (0x9, ('CVAHR', 'VA-Hour Accumulation Register for Phase C.', 'R', 16, 'S', 0x0)), + (0xa, ('AIRMS', 'Phase A Current Channel RMS Register. The register contains the rms component of the Phase A input of the current channel. The source is selected by data bits in the mode register.', 'R', 24, 'S', 0x0)), + (0xb, ('BIRMS', 'Phase B Current Channel RMS Register.', 'R', 24, 'S', 0x0)), + (0xc, ('CIRMS', 'Phase C Current Channel RMS Register.', 'R', 24, 'S', 0x0)), + (0xd, ('AVRMS', 'Phase A Voltage Channel RMS Register.', 'R', 24, 'S', 0x0)), + (0xe, ('BVRMS', 'Phase B Voltage Channel RMS Register.', 'R', 24, 'S', 0x0)), + (0xf, ('CVRMS', 'Phase C Voltage Channel RMS Register.', 'R', 24, 'S', 0x0)), + (0x10, ('FREQ', 'Frequency of the Line Input Estimated by the Zero-Crossing Processing. It can also display the period of the line input. Bit 7 of the LCYCMODE register determines if the reading is frequency or period. Default is frequency. Data Bit 0 and Bit 1 of the MMODE register determine the voltage channel used for the frequency or period calculation.', 'R', 12, 'U', 0x0)), + (0x11, ('TEMP', 'Temperature Register. This register contains the result of the latest temperature conversion. Refer to the Temperature Measurement section for details on how to interpret the content of this register.', 'R', 8, 'S', 0x0)), + (0x12, ('WFORM', 'Waveform Register. This register contains the digitized waveform of one of the six analog inputs or the digitized power waveform. The source is selected by Data Bit 0 to Bit 4 in the WAVMODE register.', 'R', 24, 'S', 0x0)), + (0x13, ('OPMODE', 'Operational Mode Register. This register defines the general configuration of the ADE7758 (see Table 18).', 'R/W', 8, 'U', 0x4)), + (0x14, ('MMODE', 'Measurement Mode Register. This register defines the channel used for period and peak detection measurements (see Table 19).', 'R/W', 8, 'U', 0xfc)), + (0x15, ('WAVMODE', 'Waveform Mode Register. This register defines the channel and sampling frequency used in the waveform sampling mode (see Table 20).', 'R/W', 8, 'U', 0x0)), + (0x16, ('COMPMODE', 'Computation Mode Register. This register configures the formula applied for the energy and line active energy measurements (see Table 22).', 'R/W', 8, 'U', 0x1c)), + (0x17, ('LCYCMODE', 'Line Cycle Mode Register. This register configures the line cycle accumulation mode for WATT-HR', 'R/W', 8, 'U', 0x78)), + (0x18, ('Mask', 'IRQ Mask Register. It determines if an interrupt event generates an active-low output at the IRQ pin (see the Interrupts section).', 'R/W', 24, 'U', 0x0)), + (0x19, ('Status', 'IRQ Status Register. This register contains information regarding the source of the ADE7758 interrupts (see the Interrupts section).', 'R', 24, 'U', 0x0)), + (0x1a, ('RSTATUS', 'IRQ Reset Status Register. Same as the STATUS register, except that its contents are reset to 0 (all flags cleared) after a read operation.', 'R', 24, 'U', 0x0)), + (0x1b, ('ZXTOUT', 'Zero-Cross Timeout Register. If no zero crossing is detected within the time period specified by this register', 'R/W', 16, 'U', 0xffff)), + (0x1c, ('LINECYC', 'Line Cycle Register. The content of this register sets the number of half-line cycles that the active', 'R/W', 16, 'U', 0xffff)), + (0x1d, ('SAGCYC', 'SAG Line Cycle Register. This register specifies the number of consecutive half-line cycles where voltage channel input may fall below a threshold level. This register is common to the three line voltage SAG detection. The detection threshold is specified by the SAGLVL register (see the Line Voltage SAG Detection section).', 'R/W', 8, 'U', 0xff)), + (0x1e, ('SAGLVL', 'SAG Voltage Level. This register specifies the detection threshold for the SAG event. This register is common to all three phases’ line voltage SAG detections. See the description of the SAGCYC register for details.', 'R/W', 8, 'U', 0x0)), + (0x1f, ('VPINTLVL', 'Voltage Peak Level Interrupt Threshold Register. This register sets the level of the voltage peak detection. Bit 5 to Bit 7 of the MMODE register determine which phases are to be monitored. If the selected voltage phase exceeds this level', 'R/W', 8, 'U', 0xff)), + (0x20, ('IPINTLVL', 'Current Peak Level Interrupt Threshold Register. This register sets the level of the current peak detection. Bit 5 to Bit 7 of the MMODE register determine which phases are to be monitored. If the selected current phase exceeds this level', 'R/W', 8, 'U', 0xff)), + (0x21, ('VPEAK', 'Voltage Peak Register. This register contains the value of the peak voltage waveform that has occurred within a fixed number of half-line cycles. The number of half-line cycles is set by the LINECYC register.', 'R', 8, 'U', 0x0)), + (0x22, ('IPEAK', 'Current Peak Register. This register holds the value of the peak current waveform that has occurred within a fixed number of half-line cycles. The number of half-line cycles is set by the LINECYC register.', 'R', 8, 'U', 0x0)), + (0x23, ('Gain', 'PGA Gain Register. This register is used to adjust the gain selection for the PGA in the current and voltage channels (see the Analog Inputs section).', 'R/W', 8, 'U', 0x0)), + (0x24, ('AVRMSGAIN', 'Phase A VRMS Gain Register. The range of the voltage rms calculation can be adjusted by writing to this register. It has an adjustment range of ±50% with a resolution of 0.0244%/LSB.', 'R/W', 12, 'S', 0x0)), + (0x25, ('BVRMSGAIN', 'Phase B VRMS Gain Register.', 'R/W', 12, 'S', 0x0)), + (0x26, ('CVRMSGAIN', 'Phase C VRMS Gain Register.', 'R/W', 12, 'S', 0x0)), + (0x27, ('AIGAIN', 'Phase A Current Gain Register. This register is not recommended to be used and it should be kept at 0', 'R/W', 12, 'S', 0x0)), + (0x28, ('BIGAIN', 'Phase B Current Gain Register. This register is not recommended to be used and it should be kept at 0', 'R/W', 12, 'S', 0x0)), + (0x29, ('CIGAIN', 'Phase C Current Gain Register. This register is not recommended to be used and it should be kept at 0', 'R/W', 12, 'S', 0x0)), + (0x2a, ('AWG', 'Phase A Watt Gain Register. The range of the watt calculation can be adjusted by writing to this register. It has an adjustment range of ±50% with a resolution of 0.0244%/LSB.', 'R/W', 12, 'S', 0x0)), + (0x2b, ('BWG', 'Phase B Watt Gain Register.', 'R/W', 12, 'S', 0x0)), + (0x2c, ('CWG', 'Phase C Watt Gain Register.', 'R/W', 12, 'S', 0x0)), + (0x2d, ('AVARG', 'Phase A VAR Gain Register. The range of the VAR calculation can be adjusted by writing to this register. It has an adjustment range of ±50% with a resolution of 0.0244%/LSB.', 'R/W', 12, 'S', 0x0)), + (0x2e, ('BVARG', 'Phase B VAR Gain Register.', 'R/W', 12, 'S', 0x0)), + (0x2f, ('CVARG', 'Phase C VAR Gain Register.', 'R/W', 12, 'S', 0x0)), + (0x30, ('AVAG', 'Phase A VA Gain Register. The range of the VA calculation can be adjusted by writing to this register. It has an adjustment range of ±50% with a resolution of 0.0244%/LSB.', 'R/W', 12, 'S', 0x0)), + (0x31, ('BVAG', 'Phase B VA Gain Register.', 'R/W', 12, 'S', 0x0)), + (0x32, ('CVAG', 'Phase C VA Gain Register.', 'R/W', 12, 'S', 0x0)), + (0x33, ('AVRMSOS', 'Phase A Voltage RMS Offset Correction Register.', 'R/W', 12, 'S', 0x0)), + (0x34, ('BVRMSOS', 'Phase B Voltage RMS Offset Correction Register.', 'R/W', 12, 'S', 0x0)), + (0x35, ('CVRMSOS', 'Phase C Voltage RMS Offset Correction Register.', 'R/W', 12, 'S', 0x0)), + (0x36, ('AIRMSOS', 'Phase A Current RMS Offset Correction Register.', 'R/W', 12, 'S', 0x0)), + (0x37, ('BIRMSOS', 'Phase B Current RMS Offset Correction Register.', 'R/W', 12, 'S', 0x0)), + (0x38, ('CIRMSOS', 'Phase C Current RMS Offset Correction Register.', 'R/W', 12, 'S', 0x0)), + (0x39, ('AWATTOS', 'Phase A Watt Offset Calibration Register.', 'R/W', 12, 'S', 0x0)), + (0x3a, ('BWATTOS', 'Phase B Watt Offset Calibration Register.', 'R/W', 12, 'S', 0x0)), + (0x3b, ('CWATTOS', 'Phase C Watt Offset Calibration Register.', 'R/W', 12, 'S', 0x0)), + (0x3c, ('AVAROS', 'Phase A VAR Offset Calibration Register.', 'R/W', 12, 'S', 0x0)), + (0x3d, ('BVAROS', 'Phase B VAR Offset Calibration Register.', 'R/W', 12, 'S', 0x0)), + (0x3e, ('CVAROS', 'Phase C VAR Offset Calibration Register.', 'R/W', 12, 'S', 0x0)), + (0x3f, ('APHCAL', 'Phase A Phase Calibration Register. The phase relationship between the current and voltage channel can be adjusted by writing to this signed 7-bit register (see the Phase Compensation section).', 'R/W', 7, 'S', 0x0)), + (0x40, ('BPHCAL', 'Phase B Phase Calibration Register.', 'R/W', 7, 'S', 0x0)), + (0x41, ('CPHCAL', 'Phase C Phase Calibration Register.', 'R/W', 7, 'S', 0x0)), + (0x42, ('WDIV', 'Active Energy Register Divider.', 'R/W', 8, 'U', 0x0)), + (0x43, ('VARDIV', 'Reactive Energy Register Divider.', 'R/W', 8, 'U', 0x0)), + (0x44, ('VADIV', 'Apparent Energy Register Divider.', 'R/W', 8, 'U', 0x0)), + (0x45, ('APCFNUM', 'Active Power CF Scaling Numerator Register. The content of this register is used in the numerator of the APCF output scaling calculation. Bits [15:13] indicate reverse polarity active power measurement for Phase A', 'R/W', 16, 'U', 0x0)), + (0x46, ('APCFDEN', 'Active Power CF Scaling Denominator Register. The content of this register is used in the denominator of the APCF output scaling.', 'R/W', 12, 'U', 0x3f)), + (0x47, ('VARCFNUM', 'Reactive Power CF Scaling Numerator Register. The content of this register is used in the numerator of the VARCF output scaling. Bits [15:13] indicate reverse polarity reactive power measurement for Phase A', 'R/W', 16, 'U', 0x0)), + (0x48, ('VARCFDEN', 'Reactive Power CF Scaling Denominator Register. The content of this register is used in the denominator of the VARCF output scaling.', 'R/W', 12, 'U', 0x3f)), + (0x7e, ('CHKSUM', 'Checksum Register. The content of this register represents the sum of all the ones in the last register read from the SPI port.', 'R', 8, 'U', None)), + (0x7f, ('Version', 'Version of the Die.', 'R', 8, 'U', None)), +]) diff --git a/libsigrokdecode4DSL/decoders/ade77xx/pd.py b/libsigrokdecode4DSL/decoders/ade77xx/pd.py new file mode 100644 index 00000000..5a24a25e --- /dev/null +++ b/libsigrokdecode4DSL/decoders/ade77xx/pd.py @@ -0,0 +1,131 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2017 Karl Palsson +## +## Permission is hereby granted, free of charge, to any person obtaining a copy +## of this software and associated documentation files (the "Software"), to deal +## in the Software without restriction, including without limitation the rights +## to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +## copies of the Software, and to permit persons to whom the Software is +## furnished to do so, subject to the following conditions: +## +## The above copyright notice and this permission notice shall be included in all +## copies or substantial portions of the Software. +## +## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +## IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +## FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +## AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +## LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +## OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +## SOFTWARE. + +import math +import sigrokdecode as srd +from .lists import * + +class Decoder(srd.Decoder): + api_version = 3 + id = 'ade77xx' + name = 'ADE77xx' + longname = 'Analog Devices ADE77xx' + desc = 'Poly phase multifunction energy metering IC protocol.' + license = 'mit' + inputs = ['spi'] + outputs = [] + tags = ['Analog/digital', 'IC', 'Sensor'] + annotations = ( + ('read', 'Register read commands'), + ('write', 'Register write commands'), + ('warning', 'Warnings'), + ) + annotation_rows = ( + ('read', 'Read', (0,)), + ('write', 'Write', (1,)), + ('warnings', 'Warnings', (2,)), + ) + + def reset_data(self): + self.expected = 0 + self.mosi_bytes, self.miso_bytes = [], [] + + def __init__(self): + self.reset() + + def reset(self): + self.ss_cmd, self.es_cmd = 0, 0 + self.reset_data() + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def putx(self, data): + self.put(self.ss_cmd, self.es_cmd, self.out_ann, data) + + def put_warn(self, pos, msg): + self.put(pos[0], pos[1], self.out_ann, [2, [msg]]) + + def decode(self, ss, es, data): + ptype = data[0] + if ptype == 'CS-CHANGE': + # Bear in mind, that CS is optional according to the datasheet. + # If we transition high mid-stream, toss out our data and restart. + cs_old, cs_new = data[1:] + if cs_old is not None and cs_old == 0 and cs_new == 1: + if len(self.mosi_bytes) > 0 and len(self.mosi_bytes[1:]) < self.expected: + # Mark short read/write for reg at least! + self.es_cmd = es + write, reg = self.cmd & 0x80, self.cmd & 0x7f + rblob = regs.get(reg) + idx = 1 if write else 0 + self.putx([idx, ['%s: %s' % (rblob[0], "SHORT")]]) + self.put_warn([self.ss_cmd, es], "Short transfer!") + self.reset_data() + return + + # Don't care about anything else. + if ptype != 'DATA': + return + mosi, miso = data[1:] + + if len(self.mosi_bytes) == 0: + self.ss_cmd = ss + self.mosi_bytes.append(mosi) + self.miso_bytes.append(miso) + + # A transfer is 2-4 bytes, (command + 1..3 byte reg). + if len(self.mosi_bytes) < 2: + return + + self.cmd = self.mosi_bytes[0] + write, reg = self.cmd & 0x80, self.cmd & 0x7f + rblob = regs.get(reg) + if not rblob: + # If you don't have CS, this will _destroy_ comms! + self.put_warn([self.ss_cmd, es], 'Unknown register!') + return + + self.expected = math.ceil(rblob[3] / 8) + if len(self.mosi_bytes[1:]) != self.expected: + return + valo, vali = None, None + self.es_cmd = es + if self.expected == 3: + valo = self.mosi_bytes[1] << 16 | self.mosi_bytes[2] << 8 | \ + self.mosi_bytes[3] + vali = self.miso_bytes[1] << 16 | self.miso_bytes[2] << 8 | \ + self.miso_bytes[3] + elif self.expected == 2: + valo = self.mosi_bytes[1] << 8 | self.mosi_bytes[2] + vali = self.miso_bytes[1] << 8 | self.miso_bytes[2] + elif self.expected == 1: + valo = self.mosi_bytes[1] + vali = self.miso_bytes[1] + + if write: + self.putx([1, ['%s: %#x' % (rblob[0], valo)]]) + else: + self.putx([0, ['%s: %#x' % (rblob[0], vali)]]) + + self.reset_data() diff --git a/libsigrokdecode4DSL/decoders/adf435x/__init__.py b/libsigrokdecode4DSL/decoders/adf435x/__init__.py new file mode 100644 index 00000000..1c89e2dc --- /dev/null +++ b/libsigrokdecode4DSL/decoders/adf435x/__init__.py @@ -0,0 +1,29 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2017 Joel Holdsworth +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This decoder stacks on top of the 'spi' PD and decodes the protocol spoken +by the Analog Devices ADF4350 and ADF4351 RF synthesizer chips. + +Details: +http://www.analog.com/media/en/technical-documentation/data-sheets/ADF4350.pdf +http://www.analog.com/media/en/technical-documentation/data-sheets/ADF4351.pdf +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/adf435x/pd.py b/libsigrokdecode4DSL/decoders/adf435x/pd.py new file mode 100644 index 00000000..f6c6e6e0 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/adf435x/pd.py @@ -0,0 +1,144 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2017 Joel Holdsworth +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 3 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd + +def disabled_enabled(v): + return ['Disabled', 'Enabled'][v] + +def output_power(v): + return '%+ddBm' % [-4, -1, 2, 5][v] + +regs = { +# reg: name offset width parser + 0: [ + ('FRAC', 3, 12, None), + ('INT', 15, 16, lambda v: 'Not Allowed' if v < 32 else v) + ], + 1: [ + ('MOD', 3, 12, None), + ('Phase', 15, 12, None), + ('Prescalar', 27, 1, lambda v: ['4/5', '8/9'][v]), + ('Phase Adjust', 28, 1, lambda v: ['Off', 'On'][v]), + ], + 2: [ + ('Counter Reset', 3, 1, disabled_enabled), + ('Charge Pump Three-State', 4, 1, disabled_enabled), + ('Power-Down', 5, 1, disabled_enabled), + ('PD Polarity', 6, 1, lambda v: ['Negative', 'Positive'][v]), + ('LDP', 7, 1, lambda v: ['10ns', '6ns'][v]), + ('LDF', 8, 1, lambda v: ['FRAC-N', 'INT-N'][v]), + ('Charge Pump Current Setting', 9, 4, lambda v: '%0.2fmA @ 5.1kΩ' % + [0.31, 0.63, 0.94, 1.25, 1.56, 1.88, 2.19, 2.50, + 2.81, 3.13, 3.44, 3.75, 4.06, 4.38, 4.69, 5.00][v]), + ('Double Buffer', 13, 1, disabled_enabled), + ('R Counter', 14, 10, None), + ('RDIV2', 24, 1, disabled_enabled), + ('Reference Doubler', 25, 1, disabled_enabled), + ('MUXOUT', 26, 3, lambda v: + ['Three-State Output', 'DVdd', 'DGND', 'R Counter Output', 'N Divider Output', + 'Analog Lock Detect', 'Digital Lock Detect', 'Reserved'][v]), + ('Low Noise and Low Spur Modes', 29, 2, lambda v: + ['Low Noise Mode', 'Reserved', 'Reserved', 'Low Spur Mode'][v]) + ], + 3: [ + ('Clock Divider', 3, 12, None), + ('Clock Divider Mode', 15, 2, lambda v: + ['Clock Divider Off', 'Fast Lock Enable', 'Resync Enable', 'Reserved'][v]), + ('CSR Enable', 18, 1, disabled_enabled), + ('Charge Cancellation', 21, 1, disabled_enabled), + ('ABP', 22, 1, lambda v: ['6ns (FRAC-N)', '3ns (INT-N)'][v]), + ('Band Select Clock Mode', 23, 1, lambda v: ['Low', 'High'][v]) + ], + 4: [ + ('Output Power', 3, 2, output_power), + ('Output Enable', 5, 1, disabled_enabled), + ('AUX Output Power', 6, 2, output_power), + ('AUX Output Select', 8, 1, lambda v: ['Divided Output', 'Fundamental'][v]), + ('AUX Output Enable', 9, 1, disabled_enabled), + ('MTLD', 10, 1, disabled_enabled), + ('VCO Power-Down', 11, 1, lambda v: + 'VCO Powered ' + ('Down' if v == 1 else 'Up')), + ('Band Select Clock Divider', 12, 8, None), + ('RF Divider Select', 20, 3, lambda v: '÷' + str(2**v)), + ('Feedback Select', 23, 1, lambda v: ['Divided', 'Fundamental'][v]), + ], + 5: [ + ('LD Pin Mode', 22, 2, lambda v: + ['Low', 'Digital Lock Detect', 'Low', 'High'][v]) + ] +} + +ANN_REG = 0 + +class Decoder(srd.Decoder): + api_version = 3 + id = 'adf435x' + name = 'ADF435x' + longname = 'Analog Devices ADF4350/1' + desc = 'Wideband synthesizer with integrated VCO.' + license = 'gplv3+' + inputs = ['spi'] + outputs = [] + tags = ['Clock/timing', 'IC', 'Wireless/RF'] + annotations = ( + # Sent from the host to the chip. + ('register', 'Register written to the device'), + ) + annotation_rows = ( + ('registers', 'Register writes', (ANN_REG,)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.bits = [] + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def decode_bits(self, offset, width): + return (sum([(1 << i) if self.bits[offset + i][0] else 0 for i in range(width)]), + (self.bits[offset + width - 1][1], self.bits[offset][2])) + + def decode_field(self, name, offset, width, parser): + val, pos = self.decode_bits(offset, width) + self.put(pos[0], pos[1], self.out_ann, [ANN_REG, + ['%s: %s' % (name, parser(val) if parser else str(val))]]) + return val + + def decode(self, ss, es, data): + + ptype, data1, data2 = data + + if ptype == 'CS-CHANGE': + if data1 == 1: + if len(self.bits) == 32: + reg_value, reg_pos = self.decode_bits(0, 3) + self.put(reg_pos[0], reg_pos[1], self.out_ann, [ANN_REG, + ['Register: %d' % reg_value, 'Reg: %d' % reg_value, + '[%d]' % reg_value]]) + if reg_value < len(regs): + field_descs = regs[reg_value] + for field_desc in field_descs: + field = self.decode_field(*field_desc) + self.bits = [] + if ptype == 'BITS': + self.bits = data1 + self.bits diff --git a/libsigrokdecode4DSL/decoders/adns5020/__init__.py b/libsigrokdecode4DSL/decoders/adns5020/__init__.py new file mode 100644 index 00000000..e519da44 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/adns5020/__init__.py @@ -0,0 +1,27 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2015 Karl Palsson +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This decoder stacks on top of the 'spi' PD and decodes ADNS-5020 optical mouse +sensor commands and data. + +Use MOSI for the SDIO shared line. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/adns5020/pd.py b/libsigrokdecode4DSL/decoders/adns5020/pd.py new file mode 100644 index 00000000..9ac778e0 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/adns5020/pd.py @@ -0,0 +1,116 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2015 Karl Palsson +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd + +regs = { + 0: 'Product_ID', + 1: 'Revision_ID', + 2: 'Motion', + 3: 'Delta_X', + 4: 'Delta_Y', + 5: 'SQUAL', + 6: 'Shutter_Upper', + 7: 'Shutter_Lower', + 8: 'Maximum_Pixel', + 9: 'Pixel_Sum', + 0xa: 'Minimum_Pixel', + 0xb: 'Pixel_Grab', + 0xd: 'Mouse_Control', + 0x3a: 'Chip_Reset', + 0x3f: 'Inv_Rev_ID', + 0x63: 'Motion_Burst', +} + +class Decoder(srd.Decoder): + api_version = 3 + id = 'adns5020' + name = 'ADNS-5020' + longname = 'Avago ADNS-5020' + desc = 'Bidirectional optical mouse sensor protocol.' + license = 'gplv2+' + inputs = ['spi'] + outputs = [] + tags = ['IC', 'PC', 'Sensor'] + annotations = ( + ('read', 'Register read commands'), + ('write', 'Register write commands'), + ('warning', 'Warnings'), + ) + annotation_rows = ( + ('read', 'Read', (0,)), + ('write', 'Write', (1,)), + ('warnings', 'Warnings', (2,)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.ss_cmd, self.es_cmd = 0, 0 + self.mosi_bytes = [] + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def putx(self, data): + self.put(self.ss_cmd, self.es_cmd, self.out_ann, data) + + def put_warn(self, pos, msg): + self.put(pos[0], pos[1], self.out_ann, [2, [msg]]) + + def decode(self, ss, es, data): + ptype = data[0] + if ptype == 'CS-CHANGE': + # If we transition high mid-stream, toss out our data and restart. + cs_old, cs_new = data[1:] + if cs_old is not None and cs_old == 0 and cs_new == 1: + if len(self.mosi_bytes) not in [0, 2]: + self.put_warn([self.ss_cmd, es], 'Misplaced CS#!') + self.mosi_bytes = [] + return + + # Don't care about anything else. + if ptype != 'DATA': + return + mosi, miso = data[1:] + + self.ss, self.es = ss, es + + if len(self.mosi_bytes) == 0: + self.ss_cmd = ss + self.mosi_bytes.append(mosi) + + # Writes/reads are mostly two transfers (burst mode is different). + if len(self.mosi_bytes) != 2: + return + + self.es_cmd = es + cmd, arg = self.mosi_bytes + write = cmd & 0x80 + reg = cmd & 0x7f + reg_desc = regs.get(reg, 'Reserved %#x' % reg) + if reg > 0x63: + reg_desc = 'Unknown' + if write: + self.putx([1, ['%s: %#x' % (reg_desc, arg)]]) + else: + self.putx([0, ['%s: %d' % (reg_desc, arg)]]) + + self.mosi_bytes = [] diff --git a/libsigrokdecode4DSL/decoders/adxl345/__init__.py b/libsigrokdecode4DSL/decoders/adxl345/__init__.py new file mode 100644 index 00000000..e46bce9f --- /dev/null +++ b/libsigrokdecode4DSL/decoders/adxl345/__init__.py @@ -0,0 +1,26 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2020 Analog Devices Inc. +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + +''' +This decoder stacks on top of the 'spi' PD and decodes the +Analog Devices ADXL345 protocol. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/adxl345/lists.py b/libsigrokdecode4DSL/decoders/adxl345/lists.py new file mode 100644 index 00000000..c22ef3e0 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/adxl345/lists.py @@ -0,0 +1,96 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2020 Analog Devices Inc. +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + +error_messages = { + 'interrupt': ['Interrupt'], + 'undesirable': ['Undesirable behavior'], + 'dis_single': ['Disable single tap'], + 'dis_double': ['Disable double tap'], + 'dis_single_double': ['Disable single/double tap'], +} + +rate_code = { + 0x00: 0.1, + 0x01: 0.2, + 0x02: 0.39, + 0x03: 0.78, + 0x04: 1.56, + 0x05: 3.13, + 0x06: 6.25, + 0x07: 12.5, + 0x08: 25, + 0x09: 50, + 0x0A: 100, + 0x0B: 200, + 0x0C: 400, + 0x0D: 800, + 0x0E: 1600, + 0x0F: 3200, +} + +fifo_modes = { + 0x00: 'Bypass', + 0x01: 'FIFO', + 0x02: 'Stream', + 0x03: 'Trigger', +} + +operations = { + 0x00: ['WRITE REG', 'WRITE', 'W'], + 0x01: ['READ REG', 'READ', 'R'], +} + +number_bytes = { + 0x00: ['SINGLE BYTE', 'SING BYTE', '1 BYTE', '1B'], + 0x01: ['MULTIPLE BYTES', 'MULTI BYTES', 'n*BYTES', 'n*B'], +} + +registers = { + 0x00: ['DEVID', 'DID', 'ID'], + 0x1D: ['THRESH_TAP', 'TH_TAP', 'TH_T'], + 0x1E: ['OFSX', 'OFX'], + 0x1F: ['OFSY', 'OFY'], + 0x20: ['OFSZ', 'OFZ'], + 0x21: ['DUR'], + 0x22: ['Latent', 'Lat'], + 0x23: ['Window', 'Win'], + 0x24: ['THRESH_ACT', 'TH_ACT', 'TH_A'], + 0x25: ['THRESH_INACT', 'TH_INACT', 'TH_I'], + 0x26: ['TIME_INACT', 'TI_INACT', 'TI_I'], + 0x27: ['ACT_INACT_CTL', 'ACT_I_CTL', 'A_I_C'], + 0x28: ['THRESH_FF', 'TH_FF'], + 0x29: ['TIME_FF', 'TI_FF'], + 0x2A: ['TAP_AXES', 'TAP_AX', 'TP_AX'], + 0x2B: ['ACT_TAP_STATUS', 'ACT_TAP_STAT', 'ACT_TP_ST', 'A_T_S'], + 0x2C: ['BW_RATE', 'BW_R'], + 0x2D: ['POWER_CTL', 'PW_CTL', 'PW_C'], + 0x2E: ['INT_ENABLE', 'INT_EN', 'I_EN'], + 0x2F: ['INT_MAP', 'I_M'], + 0x30: ['INT_SOURCE', 'INT_SRC', 'I_SRC', 'I_S'], + 0x31: ['DATA_FORMAT', 'DATA_FRM', 'D_FRM', 'D_F'], + 0x32: ['DATAX0', 'DX0', 'X0'], + 0x33: ['DATAX1', 'DX1', 'X1'], + 0x34: ['DATAY0', 'DY0', 'Y0'], + 0x35: ['DATAY1', 'DY1', 'Y1'], + 0x36: ['DATAZ0', 'DZ0', 'Z0'], + 0x37: ['DATAZ1', 'DZ1', 'Z1'], + 0x38: ['FIFO_CTL', 'FIF_CT', 'F_C'], + 0x39: ['FIFO_STATUS', 'FIFO_STAT', 'FIF_ST', 'F_S'], +} diff --git a/libsigrokdecode4DSL/decoders/adxl345/pd.py b/libsigrokdecode4DSL/decoders/adxl345/pd.py new file mode 100644 index 00000000..2d53e4c0 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/adxl345/pd.py @@ -0,0 +1,453 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2020 Analog Devices Inc. +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 3 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd +from common.srdhelper import SrdIntEnum +from .lists import * + +WORD_SIZE = 8 + +class Channel(): + MISO, MOSI = range(2) + +class Operation(): + READ, WRITE = range(2) + +class BitType(): + ENABLE = {1: ['Enable %s', 'En %s', '%s '], 0: ['Disable %s', 'Dis %s', '!%s '],} + SOURCE = {1: ['Involve %s', 'Inv %s', '%s'], 0: ['Not involve %s', 'Not inv %s', '!%s'],} + INTERRUPT = {1: ['INT2 %s', 'I2: %s '], 0: ['INT1 %s', 'I1:%s '],} + AC_DC = {1: ['%s ac', 'ac'], 0: ['%s dc', 'dc'],} + UNUSED = {1: ['N/A'], 0: ['N/A'],} + OTHER = 0 + +class Bit(): + def __init__(self, name, type, values=None): + self.value = 0 + self.name = name + self.type = type + self.values = values + + def set_value(self, value): + self.value = value + + def get_bit_annotation(self): + if self.type == BitType.OTHER: + annotation = self.values[self.value].copy() + else: + annotation = self.type[self.value].copy() + + for index in range(len(annotation)): + if '%s' in annotation[index]: + annotation[index] = str(annotation[index] % self.name) + return annotation + +Ann = SrdIntEnum.from_str('Ann', 'READ WRITE MB REG_ADDRESS REG_DATA WARNING') + +St = SrdIntEnum.from_str('St', 'IDLE ADDRESS_BYTE DATA') + +class Decoder(srd.Decoder): + api_version = 3 + id = 'adxl345' + name = 'ADXL345' + longname = 'Analog Devices ADXL345' + desc = 'Analog Devices ADXL345 3-axis accelerometer.' + license = 'gplv2+' + inputs = ['spi'] + outputs = [] + tags = ['IC', 'Sensor'] + annotations = ( + ('read', 'Read'), + ('write', 'Write'), + ('mb', 'Multiple bytes'), + ('reg-address', 'Register address'), + ('reg-data', 'Register data'), + ('warning', 'Warning'), + ) + annotation_rows = ( + ('reg', 'Registers', (Ann.READ, Ann.WRITE, Ann.MB, Ann.REG_ADDRESS)), + ('data', 'Data', (Ann.REG_DATA, Ann.WARNING)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.mosi, self.miso = [], [] + self.reg = [] + self.operation = None + self.address = 0 + self.data = -1 + self.state = St.IDLE + self.ss, self.es = -1, -1 + self.samples_per_bit = 0 + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def putx(self, data): + self.put(self.ss, self.es, self.out_ann, data) + + def putb(self, data, index): + start = self.ss + (self.samples_per_bit * index) + self.put(start, start + self.samples_per_bit, self.out_ann, data) + + def putbs(self, data, start_index, stop_index): + start_index = self.reverse_bit_index(start_index, WORD_SIZE) + stop_index = self.reverse_bit_index(stop_index, WORD_SIZE) + start = self.ss + (self.samples_per_bit * start_index) + stop = start + (self.samples_per_bit * (stop_index - start_index + 1)) + self.put(start, stop, self.out_ann, data) + + def handle_reg_with_scaling_factor(self, data, factor, name, unit, error_msg): + if data == 0 and error_msg is not None: + self.putx([Ann.WARNING, error_msg]) + else: + result = (data * factor) / 1000 + self.putx([Ann.REG_DATA, ['%s: %f %s' % (name, result, unit), '%f %s' % (result, unit)]]) + + def handle_reg_bit_msg(self, bit, index, en_msg, dis_msg): + self.putb([Ann.REG_DATA, [en_msg if bit else dis_msg]], index) + + def interpret_bits(self, data, bits): + bits_values = [] + for offset in range(8): + bits_values.insert(0, (data & (1 << offset)) >> offset) + + for index in range(len(bits)): + if bits[index] is None: + continue + bit = bits[index] + bit.set_value(bits_values[index]) + self.putb([Ann.REG_DATA, bit.get_bit_annotation()], index) + + return list(reversed(bits_values)) + + def reverse_bit_index(self, index, word_size): + return word_size - index - 1 + + def get_decimal_number(self, bits, start_index, stop_index): + number = 0 + interval = range(start_index, stop_index + 1, 1) + for index, offset in zip(interval, range(len(interval))): + bit = bits[index] + number = number | (bit << offset) + return number + + def get_axis_value(self, data, axis): + if self.data != - 1: + data <<= 8 + self.data |= data + self.put(self.start_index, self.es, self.out_ann, + [Ann.REG_DATA, ['%s: 0x%04X' % (axis, self.data), str(data)]]) + self.data = -1 + else: + self.putx([Ann.REG_DATA, [str(data)]]) + + def handle_reg_0x1d(self, data): + self.handle_reg_with_scaling_factor(data, 62.5, 'Threshold', 'g', + error_messages['undesirable']) + + def handle_reg_0x1e(self, data): + self.handle_reg_with_scaling_factor(data, 15.6, 'OFSX', 'g', None) + + def handle_reg_0x1f(self, data): + self.handle_reg_with_scaling_factor(data, 15.6, 'OFSY', 'g', None) + + def handle_reg_0x20(self, data): + self.handle_reg_with_scaling_factor(data, 15.6, 'OFSZ', 'g', None) + + def handle_reg_0x21(self, data): + self.handle_reg_with_scaling_factor(data, 0.625, 'Duration', 's', + error_messages['dis_single_double']) + + def handle_reg_0x22(self, data): + self.handle_reg_with_scaling_factor(data, 1.25, 'Latency', 's', + error_messages['dis_double']) + + def handle_reg_0x23(self, data): + self.handle_reg_with_scaling_factor(data, 1.25, 'Window', 's', + error_messages['dis_double']) + + def handle_reg_0x24(self, data): + self.handle_reg_0x1d(data) + + def handle_reg_0x25(self, data): + self.handle_reg_0x1d(data) + + def handle_reg_0x26(self, data): + self.handle_reg_with_scaling_factor(data, 1000, 'Time', 's', + error_messages['interrupt']) + + def handle_reg_0x27(self, data): + bits = [Bit('ACT', BitType.AC_DC), + Bit('ACT_X', BitType.ENABLE), + Bit('ACT_Y', BitType.ENABLE), + Bit('ACT_Z', BitType.ENABLE), + Bit('INACT', BitType.AC_DC), + Bit('INACT_X', BitType.ENABLE), + Bit('INACT_Y', BitType.ENABLE), + Bit('INACT_Z', BitType.ENABLE)] + self.interpret_bits(data, bits) + + def handle_reg_0x28(self, data): + self.handle_reg_0x1d(data) + + def handle_reg_0x29(self, data): + self.handle_reg_with_scaling_factor(data, 5, 'Time', 's', + error_messages['undesirable']) + + def handle_reg_0x2a(self, data): + bits = [Bit('', BitType.UNUSED), + Bit('', BitType.UNUSED), + Bit('', BitType.UNUSED), + Bit('', BitType.UNUSED), + Bit('', BitType.OTHER, {1: ['Suppressed', 'Suppr', 'S'], + 0: ['Unsuppressed', 'Unsuppr', 'Uns'],}), + Bit('TAP_X', BitType.ENABLE), + Bit('TAP_Y', BitType.ENABLE), + Bit('TAP_Z', BitType.ENABLE)] + self.interpret_bits(data, bits) + + def handle_reg_0x2b(self, data): + bits = [Bit('', BitType.UNUSED), + Bit('ACT_X', BitType.SOURCE), + Bit('ACT_Y', BitType.SOURCE), + Bit('ACT_Z', BitType.SOURCE), + Bit('', BitType.OTHER, {1: ['Asleep', 'Asl'], + 0: ['Not asleep', 'Not asl', '!Asl'],}), + Bit('TAP_X', BitType.SOURCE), + Bit('TAP_Y', BitType.SOURCE), + Bit('TAP_Z', BitType.SOURCE)] + self.interpret_bits(data, bits) + + def handle_reg_0x2c(self, data): + bits = [Bit('', BitType.UNUSED), + Bit('', BitType.UNUSED), + Bit('', BitType.UNUSED), + Bit('', BitType.OTHER, {1: ['Reduce power', 'Reduce pw', 'Red pw'], 0: ['Normal operation', 'Normal op', 'Norm op'],})] + bits_values = self.interpret_bits(data, bits) + + start_index, stop_index = 0, 3 + rate = self.get_decimal_number(bits_values, start_index, stop_index) + self.putbs([Ann.REG_DATA, ['%f' % rate_code[rate]]], stop_index, start_index) + + def handle_reg_0x2d(self, data): + bits = [Bit('', BitType.UNUSED), + Bit('', BitType.UNUSED), + Bit('', BitType.OTHER, {1: ['Link'], 0: ['Unlink'], }), + Bit('AUTO_SLEEP', BitType.ENABLE), + Bit('', BitType.OTHER, {1: ['Measurement mode', 'Measurement', 'Meas'], 0: ['Standby mode', 'Standby'], }), + Bit('', BitType.OTHER, {1: ['Sleep mode', 'Sleep', 'Slp'], 0: ['Normal mode', 'Normal', 'Nrm'],})] + bits_values = self.interpret_bits(data, bits) + + start_index, stop_index = 0, 1 + wakeup = self.get_decimal_number(bits_values, start_index, stop_index) + frequency = 2 ** (~wakeup & 0x03) + self.putbs([Ann.REG_DATA, ['%d Hz' % frequency]], stop_index, start_index) + + def handle_reg_0x2e(self, data): + bits = [Bit('DATA_READY', BitType.ENABLE), + Bit('SINGLE_TAP', BitType.ENABLE), + Bit('DOUBLE_TAP', BitType.ENABLE), + Bit('Activity', BitType.ENABLE), + Bit('Inactivity', BitType.ENABLE), + Bit('FREE_FALL', BitType.ENABLE), + Bit('Watermark', BitType.ENABLE), + Bit('Overrun', BitType.ENABLE)] + self.interpret_bits(data, bits) + + def handle_reg_0x2f(self, data): + bits = [Bit('DATA_READY', BitType.INTERRUPT), + Bit('SINGLE_TAP', BitType.INTERRUPT), + Bit('DOUBLE_TAP', BitType.INTERRUPT), + Bit('Activity', BitType.INTERRUPT), + Bit('Inactivity', BitType.INTERRUPT), + Bit('FREE_FALL', BitType.INTERRUPT), + Bit('Watermark', BitType.INTERRUPT), + Bit('Overrun', BitType.INTERRUPT)] + self.interpret_bits(data, bits) + + def handle_reg_0x30(self, data): + bits = [Bit('DATA_READY', BitType.SOURCE), + Bit('SINGLE_TAP', BitType.SOURCE), + Bit('DOUBLE_TAP', BitType.SOURCE), + Bit('Activity', BitType.SOURCE), + Bit('Inactivity', BitType.SOURCE), + Bit('FREE_FALL', BitType.SOURCE), + Bit('Watermark', BitType.SOURCE), + Bit('Overrun', BitType.SOURCE)] + self.interpret_bits(data, bits) + + def handle_reg_0x31(self, data): + bits = [Bit('SELF_TEST', BitType.ENABLE), + Bit('', BitType.OTHER, {1: ['3-wire SPI', '3-SPI'], 0: ['4-wire SPI', '4-SPI'],}), + Bit('', BitType.OTHER, {1: ['INT ACT LOW', 'INT LOW'], 0: ['INT ACT HIGH', 'INT HIGH'],}), + Bit('', BitType.UNUSED), + Bit('', BitType.OTHER, {1: ['Full resolution', 'Full res'], 0: ['10-bit mode', '10-bit'],}), + Bit('', BitType.OTHER, {1: ['MSB mode', 'MSB'], 0: ['LSB mode', 'LSB'],})] + bits_values = self.interpret_bits(data, bits) + + start_index, stop_index = 0, 1 + range_g = self.get_decimal_number(bits_values, start_index, stop_index) + result = 2 ** (range_g + 1) + self.putbs([Ann.REG_DATA, ['+/-%d g' % result]], stop_index, start_index) + + def handle_reg_0x32(self, data): + self.data = data + self.putx([Ann.REG_DATA, [str(data)]]) + + def handle_reg_0x33(self, data): + self.get_axis_value(data, 'X') + + def handle_reg_0x34(self, data): + self.handle_reg_0x32(data) + + def handle_reg_0x35(self, data): + self.get_axis_value(data, 'Y') + + def handle_reg_0x36(self, data): + self.handle_reg_0x32(data) + + def handle_reg_0x37(self, data): + self.get_axis_value(data, 'Z') + + def handle_reg_0x38(self, data): + bits = [None, + None, + Bit('', BitType.OTHER, {1: ['Trig-INT2', 'INT2'], 0: ['Trig-INT1', 'INT1'], })] + bits_values = self.interpret_bits(data, bits) + + start_index, stop_index = 6, 7 + fifo = self.get_decimal_number(bits_values, start_index, stop_index) + self.putbs([Ann.REG_DATA, [fifo_modes[fifo]]], stop_index, start_index) + + start_index, stop_index = 0, 4 + samples = self.get_decimal_number(bits_values, start_index, stop_index) + self.putbs([Ann.REG_DATA, ['Samples: %d' % samples, '%d' % samples]], stop_index, start_index) + + def handle_reg_0x39(self, data): + bits = [Bit('', BitType.OTHER, {1: ['Triggered', 'Trigg'], 0: ['Not triggered', 'Not trigg'],}), + Bit('', BitType.UNUSED)] + bits_values = self.interpret_bits(data, bits) + + start_index, stop_index = 0, 5 + entries = self.get_decimal_number(bits_values, start_index, stop_index) + self.putbs([Ann.REG_DATA, ['Entries: %d' % entries, '%d' % entries]], stop_index, start_index) + + def get_bit(self, channel): + if (channel == Channel.MOSI and self.mosi is None) or \ + (channel == Channel.MISO and self.miso is None): + raise Exception('No available data') + + mosi_bit, miso_bit = 0, 0 + if self.miso is not None: + if len(self.mosi) < 0: + raise Exception('No available data') + miso_bit = self.miso.pop(0) + if self.miso is not None: + if len(self.miso) < 0: + raise Exception('No available data') + mosi_bit = self.mosi.pop(0) + + if channel == Channel.MOSI: + return mosi_bit + return miso_bit + + def decode(self, ss, es, data): + ptype = data[0] + + if ptype == 'CS-CHANGE': + cs_old, cs_new = data[1:] + if cs_old is not None and cs_old == 1 and cs_new == 0: + self.ss, self.es = ss, es + self.state = St.ADDRESS_BYTE + else: + self.state = St.IDLE + + elif ptype == 'BITS': + if data[1] is not None: + self.mosi = list(reversed(data[1])) + if data[2] is not None: + self.miso = list(reversed(data[2])) + + if self.mosi is None and self.miso is None: + return + + if self.state == St.ADDRESS_BYTE: + # OPERATION BIT + op_bit = self.get_bit(Channel.MOSI) + self.put(op_bit[1], op_bit[2], self.out_ann, + [Ann.READ if op_bit[0] else Ann.WRITE, operations[op_bit[0]]]) + self.operation = Operation.READ if op_bit[0] else Operation.WRITE + # MULTIPLE-BYTE BIT + mb_bit = self.get_bit(Channel.MOSI) + self.put(mb_bit[1], mb_bit[2], self.out_ann, [Ann.MB, number_bytes[mb_bit[0]]]) + + # REGISTER 6-BIT ADDRESS + self.address = 0 + start_sample = self.mosi[0][1] + addr_bit = [] + for i in range(6): + addr_bit = self.get_bit(Channel.MOSI) + self.address |= addr_bit[0] + self.address <<= 1 + self.address >>= 1 + self.put(start_sample, addr_bit[2], self.out_ann, + [Ann.REG_ADDRESS, ['ADDRESS: 0x%02X' % self.address, 'ADDR: 0x%02X' + % self.address, '0x%02X' % self.address]]) + self.ss = -1 + self.state = St.DATA + + elif self.state == St.DATA: + self.reg.extend(self.mosi if self.operation == Operation.WRITE else self.miso) + + self.mosi, self.miso = [], [] + if self.ss == -1: + self.ss, self.es = self.reg[0][1], es + self.samples_per_bit = self.reg[0][2] - self.ss + + if len(self.reg) < 8: + return + else: + reg_value = 0 + reg_bit = [] + for offset in range(7, -1, -1): + reg_bit = self.reg.pop(0) + + mask = reg_bit[0] << offset + reg_value |= mask + + if self.address < 0x00 or self.address > 0x39: + return + + if self.address in [0x32, 0x34, 0x36]: + self.start_index = self.ss + + if 0x1D > self.address >= 0x00: + self.put(self.ss, reg_bit[2], self.out_ann, [Ann.REG_ADDRESS, [str(self.address)]]) + self.put(self.ss, reg_bit[2], self.out_ann, [Ann.REG_DATA, [str(reg_value)]]) + else: + self.put(self.ss, reg_bit[2], self.out_ann, [Ann.REG_ADDRESS, registers[self.address]]) + handle_reg = getattr(self, 'handle_reg_0x%02x' % self.address) + handle_reg(reg_value) + + self.reg = [] + self.address += 1 + self.ss = -1 diff --git a/libsigrokdecode4DSL/decoders/am230x/__init__.py b/libsigrokdecode4DSL/decoders/am230x/__init__.py new file mode 100644 index 00000000..280c8856 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/am230x/__init__.py @@ -0,0 +1,36 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 Johannes Roemer +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This decoder handles the proprietary single wire communication protocol used +by the Aosong AM230x/DHTxx/RHTxx series of digital humidity and temperature +sensors. + +Sample rate: +A sample rate of at least 200kHz is recommended to properly detect all the +elements of the protocol. + +Options: +The AM230x and DHTxx/RHTxx digital humidity and temperature sensors use the +same single-wire protocol with different encoding of the measured values. +Therefore the option 'device' must be used to properly decode the +communication of the respective sensor. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/am230x/pd.py b/libsigrokdecode4DSL/decoders/am230x/pd.py new file mode 100644 index 00000000..fbc68d39 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/am230x/pd.py @@ -0,0 +1,229 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 Johannes Roemer +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd + +# Define valid timing values (in microseconds). +timing = { + 'START LOW' : {'min': 750, 'max': 25000}, + 'START HIGH' : {'min': 10, 'max': 10000}, + 'RESPONSE LOW' : {'min': 50, 'max': 90}, + 'RESPONSE HIGH' : {'min': 50, 'max': 90}, + 'BIT LOW' : {'min': 45, 'max': 90}, + 'BIT 0 HIGH' : {'min': 20, 'max': 35}, + 'BIT 1 HIGH' : {'min': 65, 'max': 80}, +} + +class SamplerateError(Exception): + pass + +class Decoder(srd.Decoder): + api_version = 3 + id = 'am230x' + name = 'AM230x' + longname = 'Aosong AM230x/DHTxx/RHTxx' + desc = 'Aosong AM230x/DHTxx/RHTxx humidity/temperature sensor.' + license = 'gplv2+' + inputs = ['logic'] + outputs = [] + tags = ['IC', 'Sensor'] + channels = ( + {'id': 'sda', 'name': 'SDA', 'desc': 'Single wire serial data line'}, + ) + options = ( + {'id': 'device', 'desc': 'Device type', + 'default': 'am230x', 'values': ('am230x/rht', 'dht11')}, + ) + annotations = ( + ('start', 'Start'), + ('response', 'Response'), + ('bit', 'Bit'), + ('end', 'End'), + ('byte', 'Byte'), + ('humidity', 'Relative humidity in percent'), + ('temperature', 'Temperature in degrees Celsius'), + ('checksum', 'Checksum'), + ) + annotation_rows = ( + ('bits', 'Bits', (0, 1, 2, 3)), + ('bytes', 'Bytes', (4,)), + ('results', 'Results', (5, 6, 7)), + ) + + def putfs(self, data): + self.put(self.fall, self.samplenum, self.out_ann, data) + + def putb(self, data): + self.put(self.bytepos[-1], self.samplenum, self.out_ann, data) + + def putv(self, data): + self.put(self.bytepos[-2], self.samplenum, self.out_ann, data) + + def reset_variables(self): + self.state = 'WAIT FOR START LOW' + self.fall = 0 + self.rise = 0 + self.bits = [] + self.bytepos = [] + + def is_valid(self, name): + dt = 0 + if name.endswith('LOW'): + dt = self.samplenum - self.fall + elif name.endswith('HIGH'): + dt = self.samplenum - self.rise + if dt >= self.cnt[name]['min'] and dt <= self.cnt[name]['max']: + return True + return False + + def bits2num(self, bitlist): + number = 0 + for i in range(len(bitlist)): + number += bitlist[-1 - i] * 2**i + return number + + def calculate_humidity(self, bitlist): + h = 0 + if self.options['device'] == 'dht11': + h = self.bits2num(bitlist[0:8]) + else: + h = self.bits2num(bitlist) / 10 + return h + + def calculate_temperature(self, bitlist): + t = 0 + if self.options['device'] == 'dht11': + t = self.bits2num(bitlist[0:8]) + else: + t = self.bits2num(bitlist[1:]) / 10 + if bitlist[0] == 1: + t = -t + return t + + def calculate_checksum(self, bitlist): + checksum = 0 + for i in range(8, len(bitlist) + 1, 8): + checksum += self.bits2num(bitlist[i-8:i]) + return checksum % 256 + + def __init__(self): + self.reset() + + def reset(self): + self.samplerate = None + self.reset_variables() + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def metadata(self, key, value): + if key != srd.SRD_CONF_SAMPLERATE: + return + self.samplerate = value + # Convert microseconds to sample counts. + self.cnt = {} + for e in timing: + self.cnt[e] = {} + for t in timing[e]: + self.cnt[e][t] = timing[e][t] * self.samplerate / 1000000 + + def handle_byte(self, bit): + self.bits.append(bit) + self.putfs([2, ['Bit: %d' % bit, '%d' % bit]]) + self.fall = self.samplenum + self.state = 'WAIT FOR BIT HIGH' + if len(self.bits) % 8 == 0: + byte = self.bits2num(self.bits[-8:]) + self.putb([4, ['Byte: %#04x' % byte, '%#04x' % byte]]) + if len(self.bits) == 16: + h = self.calculate_humidity(self.bits[-16:]) + self.putv([5, ['Humidity: %.1f %%' % h, 'RH = %.1f %%' % h]]) + elif len(self.bits) == 32: + t = self.calculate_temperature(self.bits[-16:]) + self.putv([6, ['Temperature: %.1f °C' % t, 'T = %.1f °C' % t]]) + elif len(self.bits) == 40: + parity = self.bits2num(self.bits[-8:]) + if parity == self.calculate_checksum(self.bits[0:32]): + self.putb([7, ['Checksum: OK', 'OK']]) + else: + self.putb([7, ['Checksum: not OK', 'NOK']]) + self.state = 'WAIT FOR END' + self.bytepos.append(self.samplenum) + + def decode(self): + if not self.samplerate: + raise SamplerateError('Cannot decode without samplerate.') + while True: + # State machine. + if self.state == 'WAIT FOR START LOW': + self.wait({0: 'f'}) + self.fall = self.samplenum + self.state = 'WAIT FOR START HIGH' + elif self.state == 'WAIT FOR START HIGH': + self.wait({0: 'r'}) + if self.is_valid('START LOW'): + self.rise = self.samplenum + self.state = 'WAIT FOR RESPONSE LOW' + else: + self.reset_variables() + elif self.state == 'WAIT FOR RESPONSE LOW': + self.wait({0: 'f'}) + if self.is_valid('START HIGH'): + self.putfs([0, ['Start', 'S']]) + self.fall = self.samplenum + self.state = 'WAIT FOR RESPONSE HIGH' + else: + self.reset_variables() + elif self.state == 'WAIT FOR RESPONSE HIGH': + self.wait({0: 'r'}) + if self.is_valid('RESPONSE LOW'): + self.rise = self.samplenum + self.state = 'WAIT FOR FIRST BIT' + else: + self.reset_variables() + elif self.state == 'WAIT FOR FIRST BIT': + self.wait({0: 'f'}) + if self.is_valid('RESPONSE HIGH'): + self.putfs([1, ['Response', 'R']]) + self.fall = self.samplenum + self.bytepos.append(self.samplenum) + self.state = 'WAIT FOR BIT HIGH' + else: + self.reset_variables() + elif self.state == 'WAIT FOR BIT HIGH': + self.wait({0: 'r'}) + if self.is_valid('BIT LOW'): + self.rise = self.samplenum + self.state = 'WAIT FOR BIT LOW' + else: + self.reset_variables() + elif self.state == 'WAIT FOR BIT LOW': + self.wait({0: 'f'}) + if self.is_valid('BIT 0 HIGH'): + bit = 0 + elif self.is_valid('BIT 1 HIGH'): + bit = 1 + else: + self.reset_variables() + continue + self.handle_byte(bit) + elif self.state == 'WAIT FOR END': + self.wait({0: 'r'}) + self.putfs([3, ['End', 'E']]) + self.reset_variables() diff --git a/libsigrokdecode4DSL/decoders/amulet_ascii/__init__.py b/libsigrokdecode4DSL/decoders/amulet_ascii/__init__.py new file mode 100644 index 00000000..7d2c8c35 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/amulet_ascii/__init__.py @@ -0,0 +1,28 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2019 Vesa-Pekka Palmu +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This decoder stacks on top of the 'uart' PD and decodes the ASCII protocol +for Amulet LCD display controllers. + +Currently the decoder treats both RX and TX the same way, decoding all +message types. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/amulet_ascii/lists.py b/libsigrokdecode4DSL/decoders/amulet_ascii/lists.py new file mode 100644 index 00000000..92e27a95 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/amulet_ascii/lists.py @@ -0,0 +1,73 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2019 Vesa-Pekka Palmu +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +from collections import OrderedDict + +# OrderedDict which maps command IDs to their names and descriptions. +cmds = OrderedDict([ + (0xA0, ('PAGE', 'Jump to page')), + (0xD0, ('GBV', 'Get byte variable')), + (0xD1, ('GWV', 'Get word variable')), + (0xD2, ('GSV', 'Get string variable')), + (0xD3, ('GLV', 'Get label variable')), + (0xD4, ('GRPC', 'Get RPC buffer')), + (0xD5, ('SBV', 'Set byte variable')), + (0xD6, ('SWV', 'Set word variable')), + (0xD7, ('SSV', 'Set string variable')), + (0xD8, ('RPC', 'Invoke RPC')), + (0xD9, ('LINE', 'Draw line')), + (0xDA, ('RECT', 'Draw rectangle')), + (0xDB, ('FRECT', 'Draw filled rectangle')), + (0xDC, ('PIXEL', 'Draw pixel')), + (0xDD, ('GBVA', 'Get byte variable array')), + (0xDE, ('GWVA', 'Get word variable array')), + (0xDF, ('SBVA', 'Set byte variable array')), + (0xE0, ('GBVR', 'Get byte variable reply')), + (0xE1, ('GWVR', 'Get word variable reply')), + (0xE2, ('GSVR', 'Get string variable reply')), + (0xE3, ('GLVR', 'Get label variable reply')), + (0xE4, ('GRPCR', 'Get RPC buffer reply')), + (0xE5, ('SBVR', 'Set byte variable reply')), + (0xE6, ('SWVR', 'Set word variable reply')), + (0xE7, ('SSVR', 'Set string variable reply')), + (0xE8, ('RPCR', 'Invoke RPC reply')), + (0xE9, ('LINER', 'Draw line reply')), + (0xEA, ('RECTR', 'Draw rectangle')), + (0xEB, ('FRECTR', 'Draw filled rectangle reply')), + (0xEC, ('PIXELR', 'Draw pixel reply')), + (0xED, ('GBVAR', 'Get byte variable array reply')), + (0xEE, ('GWVAR', 'Get word variable array reply')), + (0xEF, ('SBVAR', 'Set byte variable array reply')), + (0xF0, ('ACK', 'Acknowledgment')), + (0xF1, ('NACK', 'Negative acknowledgment')), + (0xF2, ('SWVA', 'Set word variable array')), + (0xF3, ('SWVAR', 'Set word variable array reply')), + (0xF4, ('GCV', 'Get color variable')), + (0xF5, ('GCVR', 'Get color variable reply')), + (0xF6, ('SCV', 'Set color variable')), + (0xF7, ('SCVR', 'Set color variable reply')), +]) + +cmds_with_high_bytes = [ + 0xA0, # PAGE - Page change + 0xD7, # SVV - Set string variable + 0xE7, # SVVR - Set string variable reply + 0xE2, # GSVR - Get string variable reply + 0xE3, # GLVR - Get label variable reply +] diff --git a/libsigrokdecode4DSL/decoders/amulet_ascii/pd.py b/libsigrokdecode4DSL/decoders/amulet_ascii/pd.py new file mode 100644 index 00000000..71953234 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/amulet_ascii/pd.py @@ -0,0 +1,699 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2019 Vesa-Pekka Palmu +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 3 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd +from math import ceil +from common.srdhelper import SrdIntEnum +from .lists import * + +L = len(cmds) +RX = 0 +TX = 1 + +Ann = SrdIntEnum.from_list('Ann', + [c[0] for c in cmds.values()] + ['BIT', 'FIELD', 'WARN']) + +def cmd_annotation_classes(): + return tuple([tuple([cmd[0].lower(), cmd[1]]) for cmd in cmds.values()]) + +class Decoder(srd.Decoder): + api_version = 3 + id = 'amulet_ascii' + name = 'Amulet ASCII' + longname = 'Amulet LCD ASCII' + desc = 'Amulet Technologies LCD controller ASCII protocol.' + license = 'gplv3+' + inputs = ['uart'] + outputs = [] + tags = ['Display'] + annotations = cmd_annotation_classes() + ( + ('bit', 'Bit'), + ('field', 'Field'), + ('warning', 'Warning'), + ) + annotation_rows = ( + ('bits', 'Bits', (L + 0,)), + ('fields', 'Fields', (L + 1,)), + ('commands', 'Commands', tuple(range(L))), + ('warnings', 'Warnings', (L + 2,)), + ) + options = ( + {'id': 'ms_chan', 'desc': 'Master -> slave channel', + 'default': 'RX', 'values': ('RX', 'TX')}, + {'id': 'sm_chan', 'desc': 'Slave -> master channel', + 'default': 'TX', 'values': ('RX', 'TX')}, + ) + + def __init__(self): + self.reset() + + def reset(self): + self.state = None + self.cmdstate = None + + # Build dict mapping command keys to handler functions. Each + # command in 'cmds' (defined in lists.py) has a matching + # handler self.handle_. + def get_handler(cmd): + s = 'handle_%s' % cmds[cmd][0].lower().replace('/', '_') + return getattr(self, s) + self.cmd_handlers = dict((cmd, get_handler(cmd)) for cmd in cmds.keys()) + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def putx(self, data): + # Simplification, most annotations span exactly one SPI byte/packet. + self.put(self.ss, self.es, self.out_ann, data) + + def putf(self, data): + self.put(self.ss_field, self.es_field, self.out_ann, data) + + def putc(self, data): + self.put(self.ss_cmd, self.es_cmd, self.out_ann, data) + + def cmd_ann_list(self): + x, s = cmds[self.state][0], cmds[self.state][1] + return ['Command: %s (%s)' % (s, x), 'Command: %s' % s, + 'Cmd: %s' % s, 'Cmd: %s' % x, x] + + def emit_cmd_byte(self): + self.ss_cmd = self.ss + self.putx([Ann.FIELD, self.cmd_ann_list()]) + + def emit_addr_bytes(self, pdata): + if self.cmdstate == 2: + self.ss_field = self.ss + self.addr = chr(pdata) + self.putx([Ann.BIT, ['Address high nibble: %c' % pdata, + 'Addr high 0x%c' % pdata, 'Addr h 0x%c' % pdata]]) + elif self.cmdstate == 3: + self.es_field = self.es + self.addr += chr(pdata) + self.addr = int(self.addr, 16) + self.putx([Ann.BIT, ['Address low nibble: %c' % pdata, + 'Addr low 0x%c' % pdata, 'Addr l 0x%c' % pdata]]) + self.putf([Ann.FIELD, ['Address: 0x%02X' % self.addr, + 'Addr: 0x%02X' % self.addr, '0x%02X' % self.addr]]) + + def emit_cmd_end(self, data): + self.es_cmd = self.es + self.putc(data) + self.state = None + + def handle_read(self, data): + if self.cmdstate == 1: + self.emit_cmd_byte() + self.addr = 0 + elif self.cmdstate == 2: + self.emit_addr_bytes(pdata) + elif self.cmdstate == 3: + self.emit_addr_bytes(pdata) + self.cmdstate += 1 + + def handle_set_common(self, pdata): + if self.cmdstate == 1: + self.addr = 0 + self.emit_addr_bytes(pdata) + + def emit_not_implemented(self, data): + self.es_cmd = self.es + self.putc([Ann.WARN, ['Command not decoded', 'Not decoded']]) + self.emit_cmd_end(data) + + def handle_string(self, pdata, ann_class): + # TODO: unicode / string modifiers... + self.handle_set_common(pdata) + if self.cmdstate == 4: + self.ss_field = self.ss + self.value = '' + if pdata == 0x00: + # Null terminated string ends. + self.es_field = self.es + self.putx([Ann.BIT, ['NULL']]) + self.putf([Ann.FIELD, ['Value: %s' % self.value, + 'Val: %s' % self.value, '%s' % self.value]]) + self.emit_cmd_end([ann_class, self.cmd_ann_list()]) + return + if self.cmdstate > 3: + self.value += chr(pdata) + self.putx([Ann.BIT, ['%c' % pdata]]) + self.cmdstate += 1 + + # Command handlers + + # Page change 0xA0, 0x02, index_high, index_low, checksum + def handle_page(self, pdata): + if self.cmdstate == 2: + if pdata == 0x02: + self.ss_field = self.ss_cmd + self.es_field = self.es + self.putf([Ann.FIELD, self.cmd_ann_list()]) + self.checksum = 0xA0 + 0x02 + else: + self.putx([Ann.WARN, ['Illegal second byte for page change', + 'Illegal byte']]) + self.state = None + elif self.cmdstate == 3: + self.ss_field = self.ss + self.checksum += pdata + self.page[0] = pdata + elif self.cmdstate == 4: + self.checksum += pdata + self.page[1] = pdata + self.es_field = self.es + if self.page[0] == self.page [1] == 0xFF: + # Soft reset trigger + self.putf(Ann.WARN, ['Soft reset', 'Reset']) + else: + page = chr(self.page[0]) + chr(self.page[1]) + self.putf(Ann.FIELD, ['Page index: 0x%s' % page, + 'Page: 0x%s' % page, '0x%s' % page]) + elif self.cmdstate == 5: + self.checksum += pdata + if (self.checksum & 0xFF) != 0: + self.putx([Ann.WARN, ['Checksum error', 'Error', 'ERR']]) + else: + self.putx([Ann.FIELD, ['Checksum OK', 'OK']]) + self.emit_cmd_end(Ann.PAGE) + self.cmdstate += 1 + + # Value reads: command byte, address high nibble, address low nibble + + # Get byte value + def handle_gbv(self, pdata): + self.handle_read(pdata) + self.emit_cmd_end([Ann.GBV, self.cmd_ann_list()]) + + # Get word value + def handle_gwv(self, pdata): + self.handle_read(pdata) + self.emit_cmd_end([Ann.GWV, self.cmd_ann_list()]) + + # Get string value + def handle_gsv(self, pdata): + self.handle_read(pdata) + self.emit_cmd_end([Ann.GSV, self.cmd_ann_list()]) + + # Get label value + def handle_glv(self, pdata): + self.handle_read(pdata) + self.emit_cmd_end([Ann.GLV, self.cmd_ann_list()]) + + # Get RPC buffer + def handle_grpc(self, pdata): + if self.cmdstate == 2: + self.ss_field = self.ss + self.flags = int(chr(pdata), 16) << 4 + elif self.cmdstate == 3: + self.flags += int(chr(pdata), 16) + self.es_field = self.es + self.putf([Ann.FIELD, ['RPC flag: 0x%02X' % self.flags]]) + self.emit_cmd_end([Ann.GRPC, self.cmd_ann_list()]) + + # Get byte value array + def handle_gbva(self, pdata): + self.handle_read(pdata) + self.emit_cmd_end([Ann.GBVA, self.cmd_ann_list()]) + + # Get word value array + def handle_gwva(self, pdata): + self.handle_read(pdata) + self.emit_cmd_end([Ann.GWVA, self.cmd_ann_list()]) + + # Get color variable + def handle_gcv(self, pdata): + self.handle_read(pdata) + self.emit_cmd_end([Ann.GCV, self.cmd_ann_list()]) + + # Value setters: command byte, address high nibble, address low nibble, data bytes + + # Set byte value data = high nibble, low nibble + def handle_sbv(self, pdata): + self.handle_set_common(pdata) + if self.cmdstate == 4: + self.ss_field = self.ss + self.value = chr(pdata) + elif self.cmdstate == 5: + self.value += chr(pdata) + self.es_field = self.es + self.putf([Ann.FIELD, ['Value: 0x%s' % self.value, + 'Val: 0x%s' % self.value, '0x%s' % self.value]]) + self.emit_cmd_end([Ann.SBV, self.cmd_ann_list()]) + self.cmdstate += 1 + + # Set word value, msb high, msb low, lsb high, lsb low + def handle_swv(self, pdata): + self.handle_set_common(pdata) + if self.cmdstate > 3: + nibble = self.cmdstate - 4 + if nibble == 0: + self.ss_field = self.ss + self.value = 0 + self.value += int(chr(pdata), 16) << 12 - (4 * nibble) + if nibble == 3: + self.es_field = self.es + self.putf([Ann.FIELD, ['Value: 0x%04x' % self.value, + 'Val: 0x%04x' % self.value, '0x%04x' % self.value]]) + self.emit_cmd_end([Ann.SWV, self.cmd_ann_list()]) + return + self.cmdstate += 1 + + # Set string value, null terminated utf8 strings + def handle_ssv(self, pdata): + self.handle_string(pdata, Ann.SSV) + + # Set byte value array + def handle_sbva(self, pdata): + nibble = (self.cmdstate - 3) % 2 + if self.cmdstate == 2: + self.addr = int(chr(pdata), 16) << 4 + self.ss_field = self.ss + self.putx([Ann.BIT, ['Address high nibble: %c' % pdata, + 'Addr high 0x%c' % pdata, '0x%c' % pdata]]) + elif self.cmdstate == 3: + self.addr += int(chr(pdata), 16) + self.es_field = self.ss + self.putx([Ann.BIT, ['Address low nibble: %c' % pdata, + 'Addr low 0x%c' % pdata, '0x%c' % pdata]]) + self.putf([Ann.FIELD, ['Address: 0x%02X' % self.addr, + 'Addr: 0x%02X' % self.addr, '0x%02X' % self.addr]]) + elif stage == 2: + if pdata == 0x00: + # Null terminated list + self.emit_cmd_end([Ann.SBVA, self.cmd_ann_list()]) + return + self.value = int(chr(pdata), 16) << 4 + else: + self.value += int(chr(pdata), 16) + self.es_field = self.es + self.putf([Ann.FIELD, ['Value 0x%02X' % self.value, + '0x%02X' % self.value]]) + self.cmdstate += 1 + + # Set word value array + def handle_swva(self, pdata): + nibble = (self.cmdstate - 3) % 4 + if self.cmdstate == 2: + self.addr = int(chr(pdata), 16) << 4 + self.ss_field = self.ss + self.putx([Ann.BIT, ['Address high nibble: %c' % pdata, + 'Addr high 0x%c' % pdata, '0x%c' % pdata]]) + elif self.cmdstate == 3: + self.addr += int(chr(pdata), 16) + self.es_field = self.ss + self.putx([Ann.BIT, ['Address low nibble: %c' % pdata, + 'Addr low 0x%c' % pdata, '0x%c' % pdata]]) + self.putf([Ann.FIELD, ['Address: 0x%02X' % self.addr, + 'Addr: 0x%02X' % self.addr, '0x%02X' % self.addr]]) + self.value = 0 + else: + self.value += int(chr(pdata), 16) << 12 - (4 * nibble) + if nibble == 0: + if pdata == 0x00: + # Null terminated list + self.emit_cmd_end([Ann.SWVA, self.cmd_ann_list()]) + return + self.ss_field = self.ss + if nibble == 3: + self.es_field = self.es + self.putf([Ann.FIELD, ['Value 0x%04X' % self.value, + '0x%04X' % self.value]]) + self.cmdstate += 1 + + # Set color variable + def handle_scv(self, pdata): + if self.cmdstate == 8: + self.emit_not_implemented([Ann.SCV, self.cmd_ann_list()]) + self.cmdstate += 1 + + # RPC trigger + def handle_rpc(self, pdata): + self.handle_read(pdata) + self.emit_cmd_end([Ann.RPC, self.cmd_ann_list()]) + + # Drawing + + # Decode pair of (x,y) 16bit coordinates + def decode_coords(self, pdata): + if self.cmdstate == 1: + self.coords[0] = 0 + self.coords[1] = 0 + self.coords[2] = 0 + self.coords[3] = 0 + if self.cmdstate < 18: + # Coordinates + nibble = (self.cmdstate - 1) % 4 + i = (self.cmdstate - 1) / 4 + self.coords[i] += int(chr(pdata), 16) << 12 - (4 * nibble) + if nibble == 0: + self.ss_field = self.ss + elif nibble == 3: + self.es_field = self.es + self.putf([Ann.FIELD, ['Coordinate 0x%04X' % self.coords[i]], + ['0x%04X' % self.coords[i]]]) + + # TODO: There are actually two protocol revisions for drawing. + # Both use 4 bytes for 16bit x and y pairs for start and end. + # The older follows this by a pattern selector and then line weight. + # Newer version has 6 bytes for 8bit RGB color... + + # Draw line + def handle_line(self, pdata): + decode_coords(pdata) + if self.cmdstate == 18: + self.es_cmd = self.es + self.putc([Ann.LINE, self.cmd_ann_list()]) + self.putc([Ann.WARN, ['Line pattern / Color not implemented']]) + self.state = None + self.cmdstate += 1 + + # Draw rectange + def handle_rect(self, pdata): + decode_coords(pdata) + if self.cmdstate == 18: + self.es_cmd = self.es + self.putc([Ann.RECT, self.cmd_ann_list()]) + self.putc([Ann.WARN, ['Line pattern / Color not implemented']]) + self.state = None + self.cmdstate += 1 + + # Draw filled rectangle + def handle_frect(self, pdata): + decode_coords(pdata) + if self.cmdstate == 18: + self.es_cmd = self.es + self.putc([Ann.FRECT, self.cmd_ann_list()]) + self.putc([Ann.WARN, ['Fill pattern / Color not implemented']]) + self.state = None + self.cmdstate += 1 + + # Draw pixel + def handle_pixel(self, pdata): + self.es_cmd = self.es + self.putc([Ann.WARN, ['Draw pixel documentation is missing.', 'Undocumented']]) + self.state = None + + # Replies + def handle_gbvr(self, pdata): + self.emit_add_bytes(pdata) + if self.cmdstate == 4: + self.ss_field = self.ss + self.value = int(chr(pdata), 16) << 4 + self.putx([Ann.BIT, ['High nibble 0x%s' % pdata, '0x%s' % pdata]]) + elif self.cmdstate == 5: + self.value += int(chr(pdata), 16) + self.putx([Ann.BIT, ['Low nibble 0x%s' % pdata, '0x%s' % pdata]]) + self.es_field = self.es + self.putf([Ann.FIELD, ['Value: 0x%02X' % self.value, + '0x%02X' % self.value]]) + self.emit_cmd_end([Ann.GBVR, self.cmd_ann_list()]) + self.cmdstate += 1 + + def handle_gwvr(self, pdata): + self.emit_add_bytes(pdata) + if self.cmdstate > 3: + nibble = self.cmdstate - 3 + if nibble == 0: + self.value = 0 + self.ss_field = self.ss + self.value += int(chr(pdata), 16) << 12 - (4 * nibble) + self.putx([Ann.BIT, ['0x%s' % pdata]]) + if nibble == 3: + self.putf([Ann.FIELD, ['Value: 0x%04x' % self.value, + '0x%04X' % self.value]]) + self.es_cmd = self.ss + self.emit_cmd_end([Ann.GWVR, self.cmd_ann_list()]) + self.cmdstate += 1 + + def handle_gsvr(self, pdata): + self.handle_string(pdata, Ann.GSVR) + + def handle_glvr(self, pdata): + self.handle_string(pdata, Ann.GLVR) + + def handle_grpcr(self, pdata): + self.handle_addr(pdata) + if self.cmdstate > 3: + nibble = (self.cmdstate - 3) % 2 + if nibble == 0: + if pdata == 0x00: + self.emit_cmd_end([Ann.GRPCR, self.cmd_ann_list()]) + return + self.value = int(chr(pdata), 16) << 4 + self.ss_field = self.ss + self.putx([Ann.BIT, ['0x%s' % pdata]]) + if nibble == 2: + self.value += int(chr(pdata), 16) + self.es_field = self.es + self.putx([Ann.BIT, ['0x%s' % pdata]]) + self.putf([Ann.FIELD, ['0x%02X' % self.value]]) + self.cmdstate += 1 + + def handle_sbvr(self, pdata): + self.handle_set_common(pdata) + if self.cmdstate == 4: + self.ss_field = self.ss + self.value = chr(pdata) + elif self.cmdstate == 5: + self.value += chr(pdata) + self.es_field = self.es + self.putf([Ann.FIELD, ['Value: 0x%s' % self.value, + 'Val: 0x%s' % self.value, '0x%s' % self.value]]) + self.emit_cmd_end([Ann.SBVR, self.cmd_ann_list()]) + self.cmdstate += 1 + + def handle_swvr(self, pdata): + self.handle_set_common(pdata) + if self.cmdstate == 4: + self.ss_field = self.ss + self.value = (pdata - 0x30) << 4 + elif self.cmdstate == 5: + self.value += (pdata - 0x30) + self.value = self.value << 8 + elif self.cmdstate == 6: + self.value += (pdata - 0x30) << 4 + elif self.cmdstate == 7: + self.value += (pdata - 0x30) + self.es_field = self.es + self.putf([Ann.FIELD, ['Value: 0x%04x' % self.value, + 'Val: 0x%04x' % self.value, '0x%04x' % self.value]]) + self.emit_cmd_end([Ann.SWVR, self.cmd_ann_list()]) + self.state = None + self.cmdstate += 1 + + def handle_ssvr(self, pdata): + self.handle_string(pdata, Ann.SSVR) + + def handle_rpcr(self, pdata): + self.handle_read(pdata) + self.emit_cmd_end([Ann.RPCR, self.cmd_ann_list()]) + + def handle_liner(self, pdata): + decode_coords(pdata) + if self.cmdstate == 18: + self.es_cmd = self.es + self.putc([Ann.LINER, self.cmd_ann_list()]) + self.putc([Ann.WARN, ['Line pattern / Color not implemented']]) + self.state = None + self.cmdstate += 1 + + def handle_rectr(self, pdata): + decode_coords(pdata) + if self.cmdstate == 18: + self.es_cmd = self.es + self.putc([Ann.RECTR, self.cmd_ann_list()]) + self.putc([Ann.WARN, ['Line pattern / Color not implemented']]) + self.state = None + self.cmdstate += 1 + + def handle_frectr(self, pdata): + decode_coords(pdata) + if self.cmdstate == 18: + self.es_cmd = self.es + self.putc([Ann.FRECTR, self.cmd_ann_list()]) + self.putc([Ann.WARN, ['Line pattern / Color not implemented']]) + self.state = None + self.cmdstate += 1 + + def handle_pixelr(self, pdata): + self.es_cmd = self.es + self.putc([Ann.WARN,['Draw pixel documentation is missing.', 'Undocumented']]) + self.state = None + + def handle_gbvar(self, pdata): + nibble = (self.cmdstate - 3) % 2 + if self.cmdstate == 2: + self.addr = int(chr(pdata), 16) << 4 + self.ss_field = self.ss + self.putx([Ann.BIT, ['Address high nibble: %c' % pdata, + 'Addr high 0x%c' % pdata, '0x%c' % pdata]]) + elif self.cmdstate == 3: + self.addr += int(chr(pdata), 16) + self.es_field = self.ss + self.putx([Ann.BIT, ['Address low nibble: %c' % pdata, + 'Addr low 0x%c' % pdata, '0x%c' % pdata]]) + self.putf([Ann.FIELD, ['Address: 0x%02X' % self.addr, + 'Addr: 0x%02X' % self.addr, '0x%02X' % self.addr]]) + elif stage == 2: + if pdata == 0x00: + # Null terminated list + self.emit_cmd_end([Ann.GBVAR, self.cmd_ann_list()]) + return + self.value = int(chr(pdata), 16) << 4 + else: + self.value += int(chr(pdata), 16) + self.es_field = self.es + self.putf([Ann.FIELD, ['Value 0x%02X' % self.value, + '0x%02X' % self.value]]) + self.cmdstate += 1 + + def handle_gwvar(self, pdata): + nibble = (self.cmdstate - 3) % 4 + if self.cmdstate == 2: + self.addr = int(chr(pdata), 16) << 4 + self.ss_field = self.ss + self.putx([Ann.BIT, ['Address high nibble: %c' % pdata, + 'Addr high 0x%c' % pdata, '0x%c' % pdata]]) + elif self.cmdstate == 3: + self.addr += int(chr(pdata), 16) + self.es_field = self.ss + self.putx([Ann.BIT, ['Address low nibble: %c' % pdata, + 'Addr low 0x%c' % pdata, '0x%c' % pdata]]) + self.putf([Ann.FIELD, ['Address: 0x%02X' % self.addr, + 'Addr: 0x%02X' % self.addr, '0x%02X' % self.addr]]) + self.value = 0 + else: + self.value += int(chr(pdata), 16) << 12 - (4 * nibble) + if nibble == 0: + if pdata == 0x00: + # Null terminated list + self.emit_cmd_end([Ann.GWVAR, self.cmd_ann_list()]) + return + self.ss_field = self.ss + if nibble == 3: + self.es_field = self.es + self.putf([Ann.FIELD, ['Value 0x%04X' % self.value, + '0x%04X' % self.value]]) + self.cmdstate += 1 + + # Get byte variable array reply + def handle_sbvar(self, pdata): + nibble = (self.cmdstate - 3) % 2 + if self.cmdstate == 2: + self.addr = int(chr(pdata), 16) << 4 + self.ss_field = self.ss + self.putx([Ann.BIT, ['Address high nibble: %c' % pdata, + 'Addr high 0x%c' % pdata, '0x%c' % pdata]]) + elif self.cmdstate == 3: + self.addr += int(chr(pdata), 16) + self.es_field = self.ss + self.putx([Ann.BIT, ['Address low nibble: %c' % pdata, + 'Addr low 0x%c' % pdata, '0x%c' % pdata]]) + self.putf([Ann.FIELD, ['Address: 0x%02X' % self.addr, + 'Addr: 0x%02X' % self.addr, '0x%02X' % self.addr]]) + elif stage == 2: + if pdata == 0x00: + # Null terminated list + self.emit_cmd_end([Ann.SBVAR, self.cmd_ann_list()]) + return + self.value = int(chr(pdata), 16) << 4 + else: + self.value += int(chr(pdata), 16) + self.es_field = self.es + self.putf([Ann.FIELD, ['Value 0x%02X' % self.value, + '0x%02X' % self.value]]) + self.cmdstate += 1 + + # Set word variable array reply + def handle_swvar(self, pdata): + nibble = (self.cmdstate - 3) % 4 + if self.cmdstate == 2: + self.addr = int(chr(pdata), 16) << 4 + self.ss_field = self.ss + self.putx([Ann.BIT, ['Address high nibble: %c' % pdata, + 'Addr high 0x%c' % pdata, '0x%c' % pdata]]) + elif self.cmdstate == 3: + self.addr += int(chr(pdata), 16) + self.es_field = self.ss + self.putx([Ann.BIT, ['Address low nibble: %c' % pdata, + 'Addr low 0x%c' % pdata, '0x%c' % pdata]]) + self.putf([Ann.FIELD, ['Address: 0x%02X' % self.addr, + 'Addr: 0x%02X' % self.addr, '0x%02X' % self.addr]]) + self.value = 0 + else: + self.value += int(chr(pdata), 16) << 12 - (4 * nibble) + if nibble == 0: + if pdata == 0x00: + # Null terminated list + self.emit_cmd_end([Ann.SWVAR, self.cmd_ann_list()]) + return + self.ss_field = self.ss + if nibble == 3: + self.es_field = self.es + self.putf([Ann.FIELD, ['Value 0x%04X' % self.value, + '0x%04X' % self.value]]) + self.cmdstate += 1 + + def handle_gcvr(self, pdata): + if self.cmdstate == 8: + self.emit_not_implemented([Ann.SCV, self.cmd_ann_list()]) + self.cmdstate += 1 + + def handle_scvr(self, pdata): + if self.cmdstate == 8: + self.emit_not_implemented([Ann.SCV, self.cmd_ann_list()]) + self.cmdstate += 1 + + # ACK & NACK + + def handle_ack(self, pdata): + self.putx([Ann.ACK, self.cmd_ann_list()]) + self.state = None + + def handle_nack(self, pdata): + self.putx([Ann.NACK, self.cmd_ann_list()]) + self.state = None + + def decode(self, ss, es, data): + ptype, rxtx, pdata = data + + self.ss, self.es = ss, es + + if ptype != 'DATA': + return + + # Handle commands. + try: + abort_current = (0xD0 <= pdata[0] <= 0xF7) and \ + (not (self.state in cmds_with_high_bytes)) and \ + self.state != None + if abort_current: + self.putx([Ann.WARN, ['Command aborted by invalid byte', 'Abort']]) + self.state = pdata[0] + self.emit_cmd_byte() + self.cmdstate = 1 + if self.state is None: + self.state = pdata[0] + self.emit_cmd_byte() + self.cmdstate = 1 + self.cmd_handlers[self.state](pdata[0]) + except KeyError: + self.putx([Ann.WARN, ['Unknown command: 0x%02x' % pdata[0]]]) + self.state = None diff --git a/libsigrokdecode4DSL/decoders/arm_etmv3/__init__.py b/libsigrokdecode4DSL/decoders/arm_etmv3/__init__.py new file mode 100644 index 00000000..617063ca --- /dev/null +++ b/libsigrokdecode4DSL/decoders/arm_etmv3/__init__.py @@ -0,0 +1,25 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2015 Petteri Aimonen +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This decoder stacks on top of the 'uart' PD and decodes packets of +the ARMv7m Embedded Trace Macroblock v3.x. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/arm_etmv3/pd.py b/libsigrokdecode4DSL/decoders/arm_etmv3/pd.py new file mode 100644 index 00000000..6649b46e --- /dev/null +++ b/libsigrokdecode4DSL/decoders/arm_etmv3/pd.py @@ -0,0 +1,567 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2015 Petteri Aimonen +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd +import subprocess +import re + +# See ETMv3 Signal Protocol table 7-11: 'Encoding of Exception[8:0]'. +exc_names = [ + 'No exception', 'IRQ1', 'IRQ2', 'IRQ3', 'IRQ4', 'IRQ5', 'IRQ6', 'IRQ7', + 'IRQ0', 'UsageFault', 'NMI', 'SVC', 'DebugMon', 'MemManage', 'PendSV', + 'SysTick', 'Reserved', 'Reset', 'BusFault', 'Reserved', 'Reserved' +] + +for i in range(8, 496): + exc_names.append('IRQ%d' % i) + +def parse_varint(bytes_): + '''Parse an integer where the top bit is the continuation bit. + Returns value and number of parsed bytes.''' + v = 0 + for i, b in enumerate(bytes_): + v |= (b & 0x7F) << (i * 7) + if b & 0x80 == 0: + return v, i+1 + return v, len(bytes_) + +def parse_uint(bytes_): + '''Parse little-endian integer.''' + v = 0 + for i, b in enumerate(bytes_): + v |= b << (i * 8) + return v + +def parse_exc_info(bytes_): + '''Parse exception information bytes from a branch packet.''' + if len(bytes_) < 1: + return None + + excv, exclen = parse_varint(bytes_) + if bytes_[exclen - 1] & 0x80 != 0x00: + return None # Exception info not complete. + + if exclen == 2 and excv & (1 << 13): + # Exception byte 1 was skipped, fix up the decoding. + excv = (excv & 0x7F) | ((excv & 0x3F80) << 7) + + ns = excv & 1 + exc = ((excv >> 1) & 0x0F) | ((excv >> 7) & 0x1F0) + cancel = (excv >> 5) & 1 + altisa = (excv >> 6) & 1 + hyp = (excv >> 12) & 1 + resume = (excv >> 14) & 0x0F + return (ns, exc, cancel, altisa, hyp, resume) + +def parse_branch_addr(bytes_, ref_addr, cpu_state, branch_enc): + '''Parse encoded branch address. + Returns addr, addrlen, cpu_state, exc_info. + Returns None if packet is not yet complete''' + + addr, addrlen = parse_varint(bytes_) + + if bytes_[addrlen - 1] & 0x80 != 0x00: + return None # Branch address not complete. + + addr_bits = 7 * addrlen + + have_exc_info = False + if branch_enc == 'original': + if addrlen == 5 and bytes_[4] & 0x40: + have_exc_info = True + elif branch_enc == 'alternative': + addr_bits -= 1 # Top bit of address indicates exc_info. + if addrlen >= 2 and addr & (1 << addr_bits): + have_exc_info = True + addr &= ~(1 << addr_bits) + + exc_info = None + if have_exc_info: + exc_info = parse_exc_info(bytes_[addrlen:]) + if exc_info is None: + return None # Exception info not complete. + + if addrlen == 5: + # Possible change in CPU state. + if bytes_[4] & 0xB8 == 0x08: + cpu_state = 'arm' + elif bytes_[4] & 0xB0 == 0x10: + cpu_state = 'thumb' + elif bytes_[4] & 0xA0 == 0x20: + cpu_state = 'jazelle' + else: + raise NotImplementedError('Unhandled branch byte 4: 0x%02x' % bytes_[4]) + + # Shift the address according to current CPU state. + if cpu_state == 'arm': + addr = (addr & 0xFFFFFFFE) << 1 + addr_bits += 1 + elif cpu_state == 'thumb': + addr = addr & 0xFFFFFFFE + elif cpu_state == 'jazelle': + addr = (addr & 0xFFFFFFFFE) >> 1 + addr_bits -= 1 + else: + raise NotImplementedError('Unhandled state: ' + cpu_state) + + # If the address wasn't full, fill in with the previous address. + if addrlen < 5: + addr |= ref_addr & (0xFFFFFFFF << addr_bits) + + return addr, addrlen, cpu_state, exc_info + +class Decoder(srd.Decoder): + api_version = 3 + id = 'arm_etmv3' + name = 'ARM ETMv3' + longname = 'ARM Embedded Trace Macroblock v3' + desc = 'ARM ETM v3 instruction trace protocol.' + license = 'gplv2+' + inputs = ['uart'] + outputs = [] + tags = ['Debug/trace'] + annotations = ( + ('trace', 'Trace info'), + ('branch', 'Branches'), + ('exception', 'Exceptions'), + ('execution', 'Instruction execution'), + ('data', 'Data access'), + ('pc', 'Program counter'), + ('instr_e', 'Executed instructions'), + ('instr_n', 'Not executed instructions'), + ('source', 'Source code'), + ('location', 'Current location'), + ('function', 'Current function'), + ) + annotation_rows = ( + ('trace', 'Trace info', (0,)), + ('flow', 'Code flow', (1, 2, 3,)), + ('data', 'Data access', (4,)), + ('pc', 'Program counter', (5,)), + ('instruction', 'Instructions', (6, 7,)), + ('source', 'Source code', (8,)), + ('location', 'Current location', (9,)), + ('function', 'Current function', (10,)), + ) + options = ( + {'id': 'objdump', 'desc': 'objdump path', + 'default': 'arm-none-eabi-objdump'}, + {'id': 'objdump_opts', 'desc': 'objdump options', + 'default': '-lSC'}, + {'id': 'elffile', 'desc': '.elf path', + 'default': ''}, + {'id': 'branch_enc', 'desc': 'Branch encoding', + 'default': 'alternative', 'values': ('alternative', 'original')}, + ) + + def __init__(self): + self.reset() + + def reset(self): + self.buf = [] + self.syncbuf = [] + self.prevsample = 0 + self.last_branch = 0 + self.cpu_state = 'arm' + self.current_pc = 0 + self.current_loc = None + self.current_func = None + self.next_instr_lookup = {} + self.file_lookup = {} + self.func_lookup = {} + self.disasm_lookup = {} + self.source_lookup = {} + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + self.load_objdump() + + def load_objdump(self): + '''Parse disassembly obtained from objdump into two tables: + next_instr_lookup: Find the next PC addr from current PC. + disasm_lookup: Find the instruction text from current PC. + source_lookup: Find the source code line from current PC. + ''' + if not (self.options['objdump'] and self.options['elffile']): + return + + opts = [self.options['objdump']] + opts += self.options['objdump_opts'].split() + opts += [self.options['elffile']] + + try: + disasm = subprocess.check_output(opts) + except subprocess.CalledProcessError: + return + + disasm = disasm.decode('utf-8', 'replace') + + instpat = re.compile('\s*([0-9a-fA-F]+):\t+([0-9a-fA-F ]+)\t+([a-zA-Z][^;]+)\s*;?.*') + branchpat = re.compile('(b|bl|b..|bl..|cbnz|cbz)(?:\.[wn])?\s+(?:r[0-9]+,\s*)?([0-9a-fA-F]+)') + filepat = re.compile('[^\s]+[/\\\\]([a-zA-Z0-9._-]+:[0-9]+)(?:\s.*)?') + funcpat = re.compile('[0-9a-fA-F]+\s*<([^>]+)>:.*') + + prev_src = '' + prev_file = '' + prev_func = '' + + for line in disasm.split('\n'): + m = instpat.match(line) + if m: + addr = int(m.group(1), 16) + raw = m.group(2) + disas = m.group(3).strip().replace('\t', ' ') + self.disasm_lookup[addr] = disas + self.source_lookup[addr] = prev_src + self.file_lookup[addr] = prev_file + self.func_lookup[addr] = prev_func + + # Next address in direct sequence. + ilen = len(raw.replace(' ', '')) // 2 + next_n = addr + ilen + + # Next address if branch is taken. + bm = branchpat.match(disas) + if bm: + next_e = int(bm.group(2), 16) + else: + next_e = next_n + + self.next_instr_lookup[addr] = (next_n, next_e) + else: + m = funcpat.match(line) + if m: + prev_func = m.group(1) + prev_src = None + else: + m = filepat.match(line) + if m: + prev_file = m.group(1) + prev_src = None + else: + prev_src = line.strip() + + def flush_current_loc(self): + if self.current_loc is not None: + ss, es, loc, src = self.current_loc + if loc: + self.put(ss, es, self.out_ann, [9, [loc]]) + if src: + self.put(ss, es, self.out_ann, [8, [src]]) + self.current_loc = None + + def flush_current_func(self): + if self.current_func is not None: + ss, es, func = self.current_func + if func: + self.put(ss, es, self.out_ann, [10, [func]]) + self.current_func = None + + def instructions_executed(self, exec_status): + '''Advance program counter based on executed instructions. + Argument is a list of False for not executed and True for executed + instructions. + ''' + + if len(exec_status) == 0: + return + + tdelta = max(1, (self.prevsample - self.startsample) / len(exec_status)) + + for i, exec_status in enumerate(exec_status): + pc = self.current_pc + default_next = pc + 2 if self.cpu_state == 'thumb' else pc + 4 + target_n, target_e = self.next_instr_lookup.get(pc, (default_next, default_next)) + ss = self.startsample + round(tdelta * i) + es = self.startsample + round(tdelta * (i+1)) + + self.put(ss, es, self.out_ann, + [5, ['PC 0x%08x' % pc, '0x%08x' % pc, '%08x' % pc]]) + + new_loc = self.file_lookup.get(pc) + new_src = self.source_lookup.get(pc) + new_dis = self.disasm_lookup.get(pc) + new_func = self.func_lookup.get(pc) + + # Report source line only when it changes. + if self.current_loc is not None: + if new_loc != self.current_loc[2] or new_src != self.current_loc[3]: + self.flush_current_loc() + + if self.current_loc is None: + self.current_loc = [ss, es, new_loc, new_src] + else: + self.current_loc[1] = es + + # Report function name only when it changes. + if self.current_func is not None: + if new_func != self.current_func[2]: + self.flush_current_func() + + if self.current_func is None: + self.current_func = [ss, es, new_func] + else: + self.current_func[1] = es + + # Report instruction every time. + if new_dis: + if exec_status: + a = [6, ['Executed: ' + new_dis, new_dis, new_dis.split()[0]]] + else: + a = [7, ['Not executed: ' + new_dis, new_dis, new_dis.split()[0]]] + self.put(ss, es, self.out_ann, a) + + if exec_status: + self.current_pc = target_e + else: + self.current_pc = target_n + + def get_packet_type(self, byte): + '''Identify packet type based on its first byte. + See ARM IHI0014Q section "ETMv3 Signal Protocol" "Packet Types" + ''' + if byte & 0x01 == 0x01: + return 'branch' + elif byte == 0x00: + return 'a_sync' + elif byte == 0x04: + return 'cyclecount' + elif byte == 0x08: + return 'i_sync' + elif byte == 0x0C: + return 'trigger' + elif byte & 0xF3 in (0x20, 0x40, 0x60): + return 'ooo_data' + elif byte == 0x50: + return 'store_failed' + elif byte == 0x70: + return 'i_sync' + elif byte & 0xDF in (0x54, 0x58, 0x5C): + return 'ooo_place' + elif byte == 0x3C: + return 'vmid' + elif byte & 0xD3 == 0x02: + return 'data' + elif byte & 0xFB == 0x42: + return 'timestamp' + elif byte == 0x62: + return 'data_suppressed' + elif byte == 0x66: + return 'ignore' + elif byte & 0xEF == 0x6A: + return 'value_not_traced' + elif byte == 0x6E: + return 'context_id' + elif byte == 0x76: + return 'exception_exit' + elif byte == 0x7E: + return 'exception_entry' + elif byte & 0x81 == 0x80: + return 'p_header' + else: + return 'unknown' + + def fallback(self, buf): + ptype = self.get_packet_type(buf[0]) + return [0, ['Unhandled ' + ptype + ': ' + ' '.join(['%02x' % b for b in buf])]] + + def handle_a_sync(self, buf): + if buf[-1] == 0x80: + return [0, ['Synchronization']] + + def handle_exception_exit(self, buf): + return [2, ['Exception exit']] + + def handle_exception_entry(self, buf): + return [2, ['Exception entry']] + + def handle_i_sync(self, buf): + contextid_bytes = 0 # This is the default ETM config. + + if len(buf) < 6: + return None # Packet definitely not full yet. + + if buf[0] == 0x08: # No cycle count. + cyclecount = None + idx = 1 + contextid_bytes # Index to info byte. + elif buf[0] == 0x70: # With cycle count. + cyclecount, cyclen = parse_varint(buf[1:6]) + idx = 1 + cyclen + contextid_bytes + + if len(buf) <= idx + 4: + return None + infobyte = buf[idx] + addr = parse_uint(buf[idx+1:idx+5]) + + reasoncode = (infobyte >> 5) & 3 + reason = ('Periodic', 'Tracing enabled', 'After overflow', 'Exit from debug')[reasoncode] + jazelle = (infobyte >> 4) & 1 + nonsec = (infobyte >> 3) & 1 + altisa = (infobyte >> 2) & 1 + hypervisor = (infobyte >> 1) & 1 + thumb = addr & 1 + addr &= 0xFFFFFFFE + + if reasoncode == 0 and self.current_pc != addr: + self.put(self.startsample, self.prevsample, self.out_ann, + [0, ['WARN: Unexpected PC change 0x%08x -> 0x%08x' % \ + (self.current_pc, addr)]]) + elif reasoncode != 0: + # Reset location when the trace has been interrupted. + self.flush_current_loc() + self.flush_current_func() + + self.last_branch = addr + self.current_pc = addr + + if jazelle: + self.cpu_state = 'jazelle' + elif thumb: + self.cpu_state = 'thumb' + else: + self.cpu_state = 'arm' + + cycstr = '' + if cyclecount is not None: + cycstr = ', cyclecount %d' % cyclecount + + if infobyte & 0x80: # LSIP packet + self.put(self.startsample, self.prevsample, self.out_ann, + [0, ['WARN: LSIP I-Sync packet not implemented']]) + + return [0, ['I-Sync: %s, PC 0x%08x, %s state%s' % \ + (reason, addr, self.cpu_state, cycstr), \ + 'I-Sync: %s 0x%08x' % (reason, addr)]] + + def handle_trigger(self, buf): + return [0, ['Trigger event', 'Trigger']] + + def handle_p_header(self, buf): + # Only non cycle-accurate mode supported. + if buf[0] & 0x83 == 0x80: + n = (buf[0] >> 6) & 1 + e = (buf[0] >> 2) & 15 + + self.instructions_executed([1] * e + [0] * n) + + if n: + return [3, ['%d instructions executed, %d skipped due to ' \ + 'condition codes' % (e, n), + '%d ins exec, %d skipped' % (e, n), + '%dE,%dN' % (e, n)]] + else: + return [3, ['%d instructions executed' % e, + '%d ins exec' % e, '%dE' % e]] + elif buf[0] & 0xF3 == 0x82: + i1 = (buf[0] >> 3) & 1 + i2 = (buf[0] >> 2) & 1 + self.instructions_executed([not i1, not i2]) + txt1 = ('executed', 'skipped') + txt2 = ('E', 'S') + return [3, ['Instruction 1 %s, instruction 2 %s' % (txt1[i1], txt1[i2]), + 'I1 %s, I2 %s' % (txt2[i1], txt2[i2]), + '%s,%s' % (txt2[i1], txt2[i2])]] + else: + return self.fallback(buf) + + def handle_branch(self, buf): + if buf[-1] & 0x80 != 0x00: + return None # Not complete yet. + + brinfo = parse_branch_addr(buf, self.last_branch, self.cpu_state, + self.options['branch_enc']) + + if brinfo is None: + return None # Not complete yet. + + addr, addrlen, cpu_state, exc_info = brinfo + self.last_branch = addr + self.current_pc = addr + + txt = '' + + if cpu_state != self.cpu_state: + txt += ', to %s state' % cpu_state + self.cpu_state = cpu_state + + annidx = 1 + + if exc_info: + annidx = 2 + ns, exc, cancel, altisa, hyp, resume = exc_info + if ns: + txt += ', to non-secure state' + if exc: + if exc < len(exc_names): + txt += ', exception %s' % exc_names[exc] + else: + txt += ', exception 0x%02x' % exc + if cancel: + txt += ', instr cancelled' + if altisa: + txt += ', to AltISA' + if hyp: + txt += ', to hypervisor' + if resume: + txt += ', instr resume 0x%02x' % resume + + return [annidx, ['Branch to 0x%08x%s' % (addr, txt), + 'B 0x%08x%s' % (addr, txt)]] + + def decode(self, ss, es, data): + ptype, rxtx, pdata = data + + if ptype != 'DATA': + return + + # Reset packet if there is a long pause between bytes. + # This helps getting the initial synchronization. + self.byte_len = es - ss + if ss - self.prevsample > 16 * self.byte_len: + self.flush_current_loc() + self.flush_current_func() + self.buf = [] + self.prevsample = es + + self.buf.append(pdata[0]) + + # Store the start time of the packet. + if len(self.buf) == 1: + self.startsample = ss + + # Keep separate buffer for detection of sync packets. + # Sync packets override everything else, so that we can regain sync + # even if some packets are corrupted. + self.syncbuf = self.syncbuf[-4:] + [pdata[0]] + if self.syncbuf == [0x00, 0x00, 0x00, 0x00, 0x80]: + self.buf = self.syncbuf + self.syncbuf = [] + + # See if it is ready to be decoded. + ptype = self.get_packet_type(self.buf[0]) + if hasattr(self, 'handle_' + ptype): + func = getattr(self, 'handle_' + ptype) + data = func(self.buf) + else: + data = self.fallback(self.buf) + + if data is not None: + if data: + self.put(self.startsample, es, self.out_ann, data) + self.buf = [] diff --git a/libsigrokdecode4DSL/decoders/arm_itm/__init__.py b/libsigrokdecode4DSL/decoders/arm_itm/__init__.py new file mode 100644 index 00000000..1733d36d --- /dev/null +++ b/libsigrokdecode4DSL/decoders/arm_itm/__init__.py @@ -0,0 +1,25 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2015 Petteri Aimonen +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This decoder stacks on top of the 'uart' or 'arm_tpiu' PD and decodes the +ARM Cortex-M processor trace data from Instrumentation Trace Macroblock. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/arm_itm/pd.py b/libsigrokdecode4DSL/decoders/arm_itm/pd.py new file mode 100644 index 00000000..64149787 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/arm_itm/pd.py @@ -0,0 +1,373 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2015 Petteri Aimonen +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd +import string +import subprocess +import re + +ARM_EXCEPTIONS = { + 0: 'Thread', + 1: 'Reset', + 2: 'NMI', + 3: 'HardFault', + 4: 'MemManage', + 5: 'BusFault', + 6: 'UsageFault', + 11: 'SVCall', + 12: 'Debug Monitor', + 14: 'PendSV', + 15: 'SysTick', +} + +class Decoder(srd.Decoder): + api_version = 3 + id = 'arm_itm' + name = 'ARM ITM' + longname = 'ARM Instrumentation Trace Macroblock' + desc = 'ARM Cortex-M / ARMv7m ITM trace protocol.' + license = 'gplv2+' + inputs = ['uart'] + outputs = [] + tags = ['Debug/trace'] + options = ( + {'id': 'objdump', 'desc': 'objdump path', + 'default': 'arm-none-eabi-objdump'}, + {'id': 'objdump_opts', 'desc': 'objdump options', + 'default': '-lSC'}, + {'id': 'elffile', 'desc': '.elf path', + 'default': ''}, + ) + annotations = ( + ('trace', 'Trace information'), + ('timestamp', 'Timestamp'), + ('software', 'Software message'), + ('dwt_event', 'DWT event'), + ('dwt_watchpoint', 'DWT watchpoint'), + ('dwt_exc', 'Exception trace'), + ('dwt_pc', 'Program counter'), + ('mode_thread', 'Current mode: thread'), + ('mode_irq', 'Current mode: IRQ'), + ('mode_exc', 'Current mode: Exception'), + ('location', 'Current location'), + ('function', 'Current function'), + ) + annotation_rows = ( + ('trace', 'Trace information', (0, 1)), + ('software', 'Software trace', (2,)), + ('dwt_event', 'DWT event', (3,)), + ('dwt_watchpoint', 'DWT watchpoint', (4,)), + ('dwt_exc', 'Exception trace', (5,)), + ('dwt_pc', 'Program counter', (6,)), + ('mode', 'Current mode', (7, 8, 9)), + ('location', 'Current location', (10,)), + ('function', 'Current function', (11,)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.buf = [] + self.syncbuf = [] + self.swpackets = {} + self.prevsample = 0 + self.dwt_timestamp = 0 + self.current_mode = None + self.file_lookup = {} + self.func_lookup = {} + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + self.load_objdump() + + def load_objdump(self): + '''Parse disassembly obtained from objdump into a lookup tables''' + if not (self.options['objdump'] and self.options['elffile']): + return + + opts = [self.options['objdump']] + opts += self.options['objdump_opts'].split() + opts += [self.options['elffile']] + + try: + disasm = subprocess.check_output(opts) + except subprocess.CalledProcessError: + return + + disasm = disasm.decode('utf-8', 'replace') + + instpat = re.compile('\s*([0-9a-fA-F]+):\t+([0-9a-fA-F ]+)\t+([a-zA-Z][^;]+)\s*;?.*') + filepat = re.compile('[^\s]+[/\\\\]([a-zA-Z0-9._-]+:[0-9]+)(?:\s.*)?') + funcpat = re.compile('[0-9a-fA-F]+\s*<([^>]+)>:.*') + + prev_file = '' + prev_func = '' + + for line in disasm.split('\n'): + m = instpat.match(line) + if m: + addr = int(m.group(1), 16) + self.file_lookup[addr] = prev_file + self.func_lookup[addr] = prev_func + else: + m = funcpat.match(line) + if m: + prev_func = m.group(1) + else: + m = filepat.match(line) + if m: + prev_file = m.group(1) + + def get_packet_type(self, byte): + '''Identify packet type based on its first byte. + See ARMv7-M_ARM.pdf section "Debug ITM and DWT" "Packet Types" + ''' + if byte & 0x7F == 0: + return 'sync' + elif byte == 0x70: + return 'overflow' + elif byte & 0x0F == 0 and byte & 0xF0 != 0: + return 'timestamp' + elif byte & 0x0F == 0x08: + return 'sw_extension' + elif byte & 0x0F == 0x0C: + return 'hw_extension' + elif byte & 0x0F == 0x04: + return 'reserved' + elif byte & 0x04 == 0x00: + return 'software' + else: + return 'hardware' + + def mode_change(self, new_mode): + if self.current_mode is not None: + start, mode = self.current_mode + if mode.startswith('Thread'): + ann_idx = 7 + elif mode.startswith('IRQ'): + ann_idx = 8 + else: + ann_idx = 9 + self.put(start, self.startsample, self.out_ann, [ann_idx, [mode]]) + + if new_mode is None: + self.current_mode = None + else: + self.current_mode = (self.startsample, new_mode) + + def location_change(self, pc): + new_loc = self.file_lookup.get(pc) + new_func = self.func_lookup.get(pc) + ss = self.startsample + es = self.prevsample + + if new_loc is not None: + self.put(ss, es, self.out_ann, [10, [new_loc]]) + + if new_func is not None: + self.put(ss, es, self.out_ann, [11, [new_func]]) + + def fallback(self, buf): + ptype = self.get_packet_type(buf[0]) + return [0, [('Unhandled %s: ' % ptype) + ' '.join(['%02x' % b for b in buf])]] + + def handle_overflow(self, buf): + return [0, ['Overflow']] + + def handle_hardware(self, buf): + '''Handle packets from hardware source, i.e. DWT block.''' + plen = (0, 1, 2, 4)[buf[0] & 0x03] + pid = buf[0] >> 3 + if len(buf) != plen + 1: + return None # Not complete yet. + + if pid == 0: + text = 'DWT events:' + if buf[1] & 0x20: + text += ' Cyc' + if buf[1] & 0x10: + text += ' Fold' + if buf[1] & 0x08: + text += ' LSU' + if buf[1] & 0x04: + text += ' Sleep' + if buf[1] & 0x02: + text += ' Exc' + if buf[1] & 0x01: + text += ' CPI' + return [3, [text]] + elif pid == 1: + excnum = ((buf[2] & 1) << 8) | buf[1] + event = (buf[2] >> 4) + excstr = ARM_EXCEPTIONS.get(excnum, 'IRQ %d' % (excnum - 16)) + if event == 1: + self.mode_change(excstr) + return [5, ['Enter: ' + excstr, 'E ' + excstr]] + elif event == 2: + self.mode_change(None) + return [5, ['Exit: ' + excstr, 'X ' + excstr]] + elif event == 3: + self.mode_change(excstr) + return [5, ['Resume: ' + excstr, 'R ' + excstr]] + elif pid == 2: + pc = buf[1] | (buf[2] << 8) | (buf[3] << 16) | (buf[4] << 24) + self.location_change(pc) + return [6, ['PC: 0x%08x' % pc]] + elif (buf[0] & 0xC4) == 0x84: + comp = (buf[0] & 0x30) >> 4 + what = 'Read' if (buf[0] & 0x08) == 0 else 'Write' + if plen == 1: + data = '0x%02x' % (buf[1]) + elif plen == 2: + data = '0x%04x' % (buf[1] | (buf[2] << 8)) + else: + data = '0x%08x' % (buf[1] | (buf[2] << 8) | (buf[3] << 16) | (buf[4] << 24)) + return [4, ['Watchpoint %d: %s data %s' % (comp, what, data), + 'WP%d: %s %s' % (comp, what[0], data)]] + elif (buf[0] & 0xCF) == 0x47: + comp = (buf[0] & 0x30) >> 4 + addr = buf[1] | (buf[2] << 8) | (buf[3] << 16) | (buf[4] << 24) + self.location_change(addr) + return [4, ['Watchpoint %d: PC 0x%08x' % (comp, addr), + 'WP%d: PC 0x%08x' % (comp, addr)]] + elif (buf[0] & 0xCF) == 0x4E: + comp = (buf[0] & 0x30) >> 4 + offset = buf[1] | (buf[2] << 8) + return [4, ['Watchpoint %d: address 0x????%04x' % (comp, offset), + 'WP%d: A 0x%04x' % (comp, offset)]] + + return self.fallback(buf) + + def handle_software(self, buf): + '''Handle packets generated by software running on the CPU.''' + plen = (0, 1, 2, 4)[buf[0] & 0x03] + pid = buf[0] >> 3 + if len(buf) != plen + 1: + return None # Not complete yet. + + if plen == 1 and chr(buf[1]) in string.printable: + self.add_delayed_sw(pid, chr(buf[1])) + return [] # Handled but no data to output. + + self.push_delayed_sw() + + if plen == 1: + return [2, ['%d: 0x%02x' % (pid, buf[1])]] + elif plen == 2: + return [2, ['%d: 0x%02x%02x' % (pid, buf[2], buf[1])]] + elif plen == 4: + return [2, ['%d: 0x%02x%02x%02x%02x' % (pid, buf[4], buf[3], buf[2], buf[1])]] + + def handle_timestamp(self, buf): + '''Handle timestamp packets, which indicate the time of some DWT event packet.''' + if buf[-1] & 0x80 != 0: + return None # Not complete yet. + + if buf[0] & 0x80 == 0: + tc = 0 + ts = buf[0] >> 4 + else: + tc = (buf[0] & 0x30) >> 4 + ts = buf[1] & 0x7F + if len(buf) > 2: + ts |= (buf[2] & 0x7F) << 7 + if len(buf) > 3: + ts |= (buf[3] & 0x7F) << 14 + if len(buf) > 4: + ts |= (buf[4] & 0x7F) << 21 + + self.dwt_timestamp += ts + + if tc == 0: + msg = '(exact)' + elif tc == 1: + msg = '(timestamp delayed)' + elif tc == 2: + msg = '(event delayed)' + elif tc == 3: + msg = '(event and timestamp delayed)' + + return [1, ['Timestamp: %d %s' % (self.dwt_timestamp, msg)]] + + def add_delayed_sw(self, pid, c): + '''We join printable characters from software source so that printed + strings are easy to read. Joining is done by PID so that different + sources do not get confused with each other.''' + if self.swpackets.get(pid) is not None: + self.swpackets[pid][1] = self.prevsample + self.swpackets[pid][2] += c + else: + self.swpackets[pid] = [self.startsample, self.prevsample, c] + + def push_delayed_sw(self): + for pid, packet in self.swpackets.items(): + if packet is None: + continue + ss, prevtime, text = packet + # Heuristic criterion: Text has ended if at least 16 byte + # durations after previous received byte. Actual delay depends + # on printf implementation on target. + if self.prevsample - prevtime > 16 * self.byte_len: + self.put(ss, prevtime, self.out_ann, [2, ['%d: "%s"' % (pid, text)]]) + self.swpackets[pid] = None + + def decode(self, ss, es, data): + ptype, rxtx, pdata = data + + # For now, ignore all UART packets except the actual data packets. + if ptype != 'DATA': + return + + self.byte_len = es - ss + + # Reset packet if there is a long pause between bytes. + # TPIU framing can introduce small pauses, but more than 1 frame + # should reset packet. + if ss - self.prevsample > 16 * self.byte_len: + self.push_delayed_sw() + self.buf = [] + self.prevsample = es + + # Build up the current packet byte by byte. + self.buf.append(pdata[0]) + + # Store the start time of the packet. + if len(self.buf) == 1: + self.startsample = ss + + # Keep separate buffer for detection of sync packets. + # Sync packets override everything else, so that we can regain sync + # even if some packets are corrupted. + self.syncbuf = self.syncbuf[-5:] + [pdata[0]] + if self.syncbuf == [0, 0, 0, 0, 0, 0x80]: + self.buf = self.syncbuf + + # See if it is ready to be decoded. + ptype = self.get_packet_type(self.buf[0]) + if hasattr(self, 'handle_' + ptype): + func = getattr(self, 'handle_' + ptype) + data = func(self.buf) + else: + data = self.fallback(self.buf) + + if data is not None: + if data: + self.put(self.startsample, es, self.out_ann, data) + self.buf = [] diff --git a/libsigrokdecode4DSL/decoders/arm_tpiu/__init__.py b/libsigrokdecode4DSL/decoders/arm_tpiu/__init__.py new file mode 100644 index 00000000..ce9c3744 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/arm_tpiu/__init__.py @@ -0,0 +1,28 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2015 Petteri Aimonen +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This decoder stacks on top of the 'uart' PD and decodes the frame format +of ARMv7m Trace Port Interface Unit. + +It filters the data coming from various trace sources (such as ARMv7m ITM +and ETM blocks) into separate streams that can be further decoded by other PDs. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/arm_tpiu/pd.py b/libsigrokdecode4DSL/decoders/arm_tpiu/pd.py new file mode 100644 index 00000000..29b4605f --- /dev/null +++ b/libsigrokdecode4DSL/decoders/arm_tpiu/pd.py @@ -0,0 +1,131 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2015 Petteri Aimonen +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd + +class Decoder(srd.Decoder): + api_version = 3 + id = 'arm_tpiu' + name = 'ARM TPIU' + longname = 'ARM Trace Port Interface Unit' + desc = 'Filter TPIU formatted trace data into separate streams.' + license = 'gplv2+' + inputs = ['uart'] + outputs = ['uart'] # Emulate uart output so that arm_itm/arm_etm can stack. + tags = ['Debug/trace'] + options = ( + {'id': 'stream', 'desc': 'Stream index', 'default': 1}, + {'id': 'sync_offset', 'desc': 'Initial sync offset', 'default': 0}, + ) + annotations = ( + ('stream', 'Current stream'), + ('data', 'Stream data'), + ) + annotation_rows = ( + ('stream', 'Current stream', (0,)), + ('data', 'Stream data', (1,)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.buf = [] + self.syncbuf = [] + self.prevsample = 0 + self.stream = 0 + self.ss_stream = None + self.bytenum = 0 + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + self.out_python = self.register(srd.OUTPUT_PYTHON) + + def stream_changed(self, ss, stream): + if self.stream != stream: + if self.stream != 0: + self.put(self.ss_stream, ss, self.out_ann, + [0, ['Stream %d' % self.stream, 'S%d' % self.stream]]) + self.stream = stream + self.ss_stream = ss + + def emit_byte(self, ss, es, byte): + if self.stream == self.options['stream']: + self.put(ss, es, self.out_ann, [1, ['0x%02x' % byte]]) + self.put(ss, es, self.out_python, ['DATA', 0, (byte, [])]) + + def process_frame(self, buf): + # Byte 15 contains the lowest bits of bytes 0, 2, ... 14. + lowbits = buf[15][2] + + for i in range(0, 15, 2): + # Odd bytes can be stream ID or data. + delayed_stream_change = None + lowbit = (lowbits >> (i // 2)) & 0x01 + if buf[i][2] & 0x01 != 0: + if lowbit: + delayed_stream_change = buf[i][2] >> 1 + else: + self.stream_changed(buf[i][0], buf[i][2] >> 1) + else: + byte = buf[i][2] | lowbit + self.emit_byte(buf[i][0], buf[i][1], byte) + + # Even bytes are always data. + if i < 14: + self.emit_byte(buf[i+1][0], buf[i+1][1], buf[i+1][2]) + + # The stream change can be delayed to occur after the data byte. + if delayed_stream_change is not None: + self.stream_changed(buf[i+1][1], delayed_stream_change) + + def decode(self, ss, es, data): + ptype, rxtx, pdata = data + + if ptype != 'DATA': + return + + # Reset packet if there is a long pause between bytes. + self.byte_len = es - ss + if ss - self.prevsample > self.byte_len: + self.buf = [] + self.prevsample = es + + self.buf.append((ss, es, pdata[0])) + self.bytenum += 1 + + # Allow skipping N first bytes of the data. By adjusting the sync + # value, one can get initial synchronization as soon as the trace + # starts. + if self.bytenum < self.options['sync_offset']: + self.buf = [] + return + + # Keep separate buffer for detection of sync packets. + # Sync packets override everything else, so that we can regain sync + # even if some packets are corrupted. + self.syncbuf = self.syncbuf[-3:] + [pdata[0]] + if self.syncbuf == [0xFF, 0xFF, 0xFF, 0x7F]: + self.buf = [] + self.syncbuf = [] + return + + if len(self.buf) == 16: + self.process_frame(self.buf) + self.buf = [] diff --git a/libsigrokdecode4DSL/decoders/atsha204a/__init__.py b/libsigrokdecode4DSL/decoders/atsha204a/__init__.py new file mode 100644 index 00000000..fd0f4288 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/atsha204a/__init__.py @@ -0,0 +1,30 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2018 Michalis Pappas +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This decoder stacks on top of the 'i2c' PD and decodes the +Microchip ATSHA204A and ATECC508A crypto authentication protocol. + +The decoder might also support the following devices (untested): + * ATSHA204 + * ATECC108 + * ATECC108A +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/atsha204a/pd.py b/libsigrokdecode4DSL/decoders/atsha204a/pd.py new file mode 100644 index 00000000..c666332a --- /dev/null +++ b/libsigrokdecode4DSL/decoders/atsha204a/pd.py @@ -0,0 +1,323 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2018 Michalis Pappas +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd + +WORD_ADDR_RESET = 0x00 +WORD_ADDR_SLEEP = 0x01 +WORD_ADDR_IDLE = 0x02 +WORD_ADDR_COMMAND = 0x03 + +WORD_ADDR = {0x00: 'RESET', 0x01: 'SLEEP', 0x02: 'IDLE', 0x03: 'COMMAND'} + +OPCODE_COUNTER = 0x24 +OPCODE_DERIVE_KEY = 0x1c +OPCODE_DEV_REV = 0x30 +OPCODE_ECDH = 0x43 +OPCODE_GEN_DIG = 0x15 +OPCODE_GEN_KEY = 0x40 +OPCODE_HMAC = 0x11 +OPCODE_CHECK_MAC = 0x28 +OPCODE_LOCK = 0x17 +OPCODE_MAC = 0x08 +OPCODE_NONCE = 0x16 +OPCODE_PAUSE = 0x01 +OPCODE_PRIVWRITE = 0x46 +OPCODE_RANDOM = 0x1b +OPCODE_READ = 0x02 +OPCODE_SHA = 0x47 +OPCODE_SIGN = 0x41 +OPCODE_UPDATE_EXTRA = 0x20 +OPCODE_VERIFY = 0x45 +OPCODE_WRITE = 0x12 + +OPCODES = { + 0x01: 'Pause', + 0x02: 'Read', + 0x08: 'MAC', + 0x11: 'HMAC', + 0x12: 'Write', + 0x15: 'GenDig', + 0x16: 'Nonce', + 0x17: 'Lock', + 0x1b: 'Random', + 0x1c: 'DeriveKey', + 0x20: 'UpdateExtra', + 0x24: 'Counter', + 0x28: 'CheckMac', + 0x30: 'DevRev', + 0x40: 'GenKey', + 0x41: 'Sign', + 0x43: 'ECDH', + 0x45: 'Verify', + 0x46: 'PrivWrite', + 0x47: 'SHA', +} + +ZONE_CONFIG = 0x00 +ZONE_OTP = 0x01 +ZONE_DATA = 0x02 + +ZONES = {0x00: 'CONFIG', 0x01: 'OTP', 0x02: 'DATA'} + +STATUS_SUCCESS = 0x00 +STATUS_CHECKMAC_FAIL = 0x01 +STATUS_PARSE_ERROR = 0x03 +STATUS_EXECUTION_ERROR = 0x0f +STATUS_READY = 0x11 +STATUS_CRC_COMM_ERROR = 0xff + +STATUS = { + 0x00: 'Command success', + 0x01: 'Checkmac failure', + 0x03: 'Parse error', + 0x0f: 'Execution error', + 0x11: 'Ready', + 0xff: 'CRC / communications error', +} + +class Decoder(srd.Decoder): + api_version = 3 + id = 'atsha204a' + name = 'ATSHA204A' + longname = 'Microchip ATSHA204A' + desc = 'Microchip ATSHA204A family crypto authentication protocol.' + license = 'gplv2+' + inputs = ['i2c'] + outputs = [] + tags = ['Security/crypto', 'IC', 'Memory'] + annotations = ( + ('waddr', 'Word address'), + ('count', 'Count'), + ('opcode', 'Opcode'), + ('param1', 'Param1'), + ('param2', 'Param2'), + ('data', 'Data'), + ('crc', 'CRC'), + ('status', 'Status'), + ('warning', 'Warning'), + ) + annotation_rows = ( + ('frame', 'Frame', (0, 1, 2, 3, 4, 5, 6)), + ('status', 'Status', (7,)), + ('warnings', 'Warnings', (8,)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.state = 'IDLE' + self.waddr = self.opcode = -1 + self.ss_block = self.es_block = 0 + self.bytes = [] + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def output_tx_bytes(self): + b = self.bytes + if len(b) < 1: # Ignore wakeup. + return + self.waddr = b[0][2] + self.put_waddr(b[0]) + if self.waddr == WORD_ADDR_COMMAND: + count = b[1][2] + self.put_count(b[1]) + if len(b) - 1 != count: + self.put_warning(b[0][0], b[-1][1], + 'Invalid frame length: Got {}, expecting {} '.format( + len(b) - 1, count)) + return + self.opcode = b[2][2] + self.put_opcode(b[2]) + self.put_param1(b[3]) + self.put_param2([b[4], b[5]]) + self.put_data(b[6:-2]) + self.put_crc([b[-2], b[-1]]) + + def output_rx_bytes(self): + b = self.bytes + count = b[0][2] + self.put_count(b[0]) + if self.waddr == WORD_ADDR_RESET: + self.put_data([b[1]]) + self.put_crc([b[2], b[3]]) + self.put_status(b[0][0], b[-1][1], b[1][2]) + elif self.waddr == WORD_ADDR_COMMAND: + if count == 4: # Status / Error. + self.put_data([b[1]]) + self.put_crc([b[2], b[3]]) + self.put_status(b[0][0], b[-1][1], b[1][2]) + else: + self.put_data(b[1:-2]) + self.put_crc([b[-2], b[-1]]) + + def putx(self, s, data): + self.put(s[0], s[1], self.out_ann, data) + + def puty(self, s, data): + self.put(s[0][0], s[1][1], self.out_ann, data) + + def putz(self, ss, es, data): + self.put(ss, es, self.out_ann, data) + + def put_waddr(self, s): + self.putx(s, [0, ['Word addr: %s' % WORD_ADDR[s[2]]]]) + + def put_count(self, s): + self.putx(s, [1, ['Count: %s' % s[2]]]) + + def put_opcode(self, s): + self.putx(s, [2, ['Opcode: %s' % OPCODES[s[2]]]]) + + def put_param1(self, s): + op = self.opcode + if op in (OPCODE_CHECK_MAC, OPCODE_COUNTER, OPCODE_DEV_REV, \ + OPCODE_ECDH, OPCODE_GEN_KEY, OPCODE_HMAC, OPCODE_MAC, \ + OPCODE_NONCE, OPCODE_RANDOM, OPCODE_SHA, OPCODE_SIGN, \ + OPCODE_VERIFY): + self.putx(s, [3, ['Mode: %02X' % s[2]]]) + elif op == OPCODE_DERIVE_KEY: + self.putx(s, [3, ['Random: %s' % s[2]]]) + elif op == OPCODE_PRIVWRITE: + self.putx(s, [3, ['Encrypted: {}'.format('Yes' if s[2] & 0x40 else 'No')]]) + elif op == OPCODE_GEN_DIG: + self.putx(s, [3, ['Zone: %s' % ZONES[s[2]]]]) + elif op == OPCODE_LOCK: + self.putx(s, [3, ['Zone: {}, Summary: {}'.format( + 'DATA/OTP' if s[2] else 'CONFIG', + 'Ignored' if s[2] & 0x80 else 'Used')]]) + elif op == OPCODE_PAUSE: + self.putx(s, [3, ['Selector: %02X' % s[2]]]) + elif op == OPCODE_READ: + self.putx(s, [3, ['Zone: {}, Length: {}'.format(ZONES[s[2] & 0x03], + '32 bytes' if s[2] & 0x90 else '4 bytes')]]) + elif op == OPCODE_WRITE: + self.putx(s, [3, ['Zone: {}, Encrypted: {}, Length: {}'.format(ZONES[s[2] & 0x03], + 'Yes' if s[2] & 0x40 else 'No', '32 bytes' if s[2] & 0x90 else '4 bytes')]]) + else: + self.putx(s, [3, ['Param1: %02X' % s[2]]]) + + def put_param2(self, s): + op = self.opcode + if op == OPCODE_DERIVE_KEY: + self.puty(s, [4, ['TargetKey: {:02x} {:02x}'.format(s[1][2], s[0][2])]]) + elif op in (OPCODE_COUNTER, OPCODE_ECDH, OPCODE_GEN_KEY, OPCODE_PRIVWRITE, \ + OPCODE_SIGN, OPCODE_VERIFY): + self.puty(s, [4, ['KeyID: {:02x} {:02x}'.format(s[1][2], s[0][2])]]) + elif op in (OPCODE_NONCE, OPCODE_PAUSE, OPCODE_RANDOM): + self.puty(s, [4, ['Zero: {:02x} {:02x}'.format(s[1][2], s[0][2])]]) + elif op in (OPCODE_HMAC, OPCODE_MAC, OPCODE_CHECK_MAC, OPCODE_GEN_DIG): + self.puty(s, [4, ['SlotID: {:02x} {:02x}'.format(s[1][2], s[0][2])]]) + elif op == OPCODE_LOCK: + self.puty(s, [4, ['Summary: {:02x} {:02x}'.format(s[1][2], s[0][2])]]) + elif op in (OPCODE_READ, OPCODE_WRITE): + self.puty(s, [4, ['Address: {:02x} {:02x}'.format(s[1][2], s[0][2])]]) + elif op == OPCODE_UPDATE_EXTRA: + self.puty(s, [4, ['NewValue: {:02x}'.format(s[0][2])]]) + else: + self.puty(s, [4, ['-']]) + + def put_data(self, s): + if len(s) == 0: + return + op = self.opcode + if op == OPCODE_CHECK_MAC: + self.putz(s[0][0], s[31][1], [5, ['ClientChal: %s' % ' '.join(format(i[2], '02x') for i in s[0:32])]]) + self.putz(s[32][0], s[63][1], [5, ['ClientResp: %s' % ' '.join(format(i[2], '02x') for i in s[32:64])]]) + self.putz(s[64][0], s[76][1], [5, ['OtherData: %s' % ' '.join(format(i[2], '02x') for i in s[64:77])]]) + elif op == OPCODE_DERIVE_KEY: + self.putz(s[0][0], s[31][1], [5, ['MAC: %s' % ' '.join(format(i[2], '02x') for i in s)]]) + elif op == OPCODE_ECDH: + self.putz(s[0][0], s[31][1], [5, ['Pub X: %s' % ' '.join(format(i[2], '02x') for i in s[0:32])]]) + self.putz(s[32][0], s[63][1], [5, ['Pub Y: %s' % ' '.join(format(i[2], '02x') for i in s[32:64])]]) + elif op in (OPCODE_GEN_DIG, OPCODE_GEN_KEY): + self.putz(s[0][0], s[3][1], [5, ['OtherData: %s' % ' '.join(format(i[2], '02x') for i in s)]]) + elif op == OPCODE_MAC: + self.putz(s[0][0], s[31][1], [5, ['Challenge: %s' % ' '.join(format(i[2], '02x') for i in s)]]) + elif op == OPCODE_PRIVWRITE: + if len(s) > 36: # Key + MAC. + self.putz(s[0][0], s[-35][1], [5, ['Value: %s' % ' '.join(format(i[2], '02x') for i in s)]]) + self.putz(s[-32][0], s[-1][1], [5, ['MAC: %s' % ' '.join(format(i[2], '02x') for i in s)]]) + else: # Just value. + self.putz(s[0][0], s[-1][1], [5, ['Value: %s' % ' '.join(format(i[2], '02x') for i in s)]]) + elif op == OPCODE_VERIFY: + if len(s) >= 64: # ECDSA components (always present) + self.putz(s[0][0], s[31][1], [5, ['ECDSA R: %s' % ' '.join(format(i[2], '02x') for i in s[0:32])]]) + self.putz(s[32][0], s[63][1], [5, ['ECDSA S: %s' % ' '.join(format(i[2], '02x') for i in s[32:64])]]) + if len(s) == 83: # OtherData (follow ECDSA components in validate / invalidate mode) + self.putz(s[64][0], s[82][1], [5, ['OtherData: %s' % ' '.join(format(i[2], '02x') for i in s[64:83])]]) + if len(s) == 128: # Public key components (follow ECDSA components in external mode) + self.putz(s[64][0], s[95][1], [5, ['Pub X: %s' % ' '.join(format(i[2], '02x') for i in s[64:96])]]) + self.putz(s[96][0], s[127][1], [5, ['Pub Y: %s' % ' '.join(format(i[2], '02x') for i in s[96:128])]]) + elif op == OPCODE_WRITE: + if len(s) > 32: # Value + MAC. + self.putz(s[0][0], s[-31][1], [5, ['Value: %s' % ' '.join(format(i[2], '02x') for i in s)]]) + self.putz(s[-32][0], s[-1][1], [5, ['MAC: %s' % ' '.join(format(i[2], '02x') for i in s)]]) + else: # Just value. + self.putz(s[0][0], s[-1][1], [5, ['Value: %s' % ' '.join(format(i[2], '02x') for i in s)]]) + else: + self.putz(s[0][0], s[-1][1], [5, ['Data: %s' % ' '.join(format(i[2], '02x') for i in s)]]) + + def put_crc(self, s): + self.puty(s, [6, ['CRC: {:02X} {:02X}'.format(s[0][2], s[1][2])]]) + + def put_status(self, ss, es, status): + self.putz(ss, es, [7, ['Status: %s' % STATUS[status]]]) + + def put_warning(self, ss, es, msg): + self.putz(ss, es, [8, ['Warning: %s' % msg]]) + + def decode(self, ss, es, data): + cmd, databyte = data + # State machine. + if self.state == 'IDLE': + # Wait for an I²C START condition. + if cmd != 'START': + return + self.state = 'GET SLAVE ADDR' + self.ss_block = ss + elif self.state == 'GET SLAVE ADDR': + # Wait for an address read/write operation. + if cmd == 'ADDRESS READ': + self.state = 'READ REGS' + elif cmd == 'ADDRESS WRITE': + self.state = 'WRITE REGS' + elif self.state == 'READ REGS': + if cmd == 'DATA READ': + self.bytes.append([ss, es, databyte]) + elif cmd == 'STOP': + self.es_block = es + # Reset the opcode before received data, as this causes + # responses to be displayed incorrectly. + self.opcode = -1 + if len(self.bytes) > 0: + self.output_rx_bytes() + self.waddr = -1 + self.bytes = [] + self.state = 'IDLE' + elif self.state == 'WRITE REGS': + if cmd == 'DATA WRITE': + self.bytes.append([ss, es, databyte]) + elif cmd == 'STOP': + self.es_block = es + self.output_tx_bytes() + self.bytes = [] + self.state = 'IDLE' diff --git a/libsigrokdecode4DSL/decoders/aud/__init__.py b/libsigrokdecode4DSL/decoders/aud/__init__.py new file mode 100644 index 00000000..10b74234 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/aud/__init__.py @@ -0,0 +1,31 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2016 fenugrec +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This protocol decoder decodes the AUD (Advanced User Debugger) interface +of certain Renesas / Hitachi microcontrollers, when set in Branch Trace mode. + +AUD has two modes, this PD currently only supports "Branch Trace" mode. + +Details: +http://www.renesas.eu/products/mpumcu/superh/sh7050/sh7058/Documentation.jsp +("rej09b0046 - SH7058 Hardware manual") +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/aud/pd.py b/libsigrokdecode4DSL/decoders/aud/pd.py new file mode 100644 index 00000000..ad93634f --- /dev/null +++ b/libsigrokdecode4DSL/decoders/aud/pd.py @@ -0,0 +1,107 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2016 fenugrec +## Copyright (C) 2019 DreamSourceLab +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +# TODO: +# - Annotations are very crude and could be improved. +# - Annotate every nibble? Would give insight on interrupted shifts. +# - Annotate invalid "command" nibbles while SYNC==1? + +import sigrokdecode as srd + +class Decoder(srd.Decoder): + api_version = 3 + id = 'aud' + name = 'AUD' + longname = 'Advanced User Debugger' + desc = 'Renesas/Hitachi Advanced User Debugger (AUD) protocol.' + license = 'gplv2+' + inputs = ['logic'] + outputs = [] + tags = ['Debug/trace'] + channels = ( + {'id': 'audck', 'name': 'AUDCK', 'desc': 'AUD clock'}, + {'id': 'naudsync', 'name': 'nAUDSYNC', 'desc': 'AUD sync'}, + {'id': 'audata3', 'name': 'AUDATA3', 'desc': 'AUD data line 3'}, + {'id': 'audata2', 'name': 'AUDATA2', 'desc': 'AUD data line 2'}, + {'id': 'audata1', 'name': 'AUDATA1', 'desc': 'AUD data line 1'}, + {'id': 'audata0', 'name': 'AUDATA0', 'desc': 'AUD data line 0'}, + ) + annotations = ( + ('dest', 'Destination address'), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.ncnt = 0 + self.nmax = 0 + self.addr = 0 + self.lastaddr = 0 + self.ss = 0 + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def putx(self, data): + self.put(self.ss, self.samplenum, self.out_ann, data) + + def handle_clk_edge(self, clk, sync, datapins): + # Reconstruct nibble. + nib = 0 + for i in range(4): + nib |= datapins[3-i] << i + + # sync == 1: annotate if finished; update cmd. + # TODO: Annotate idle level (nibble = 0x03 && SYNC=1). + if sync == 1: + if (self.ncnt == self.nmax) and (self.nmax != 0): + # Done shifting an address: annotate. + self.putx([0, ['0x%08X' % self.addr]]) + self.lastaddr = self.addr + + self.ncnt = 0 + self.addr = self.lastaddr + self.ss = self.samplenum + if nib == 0x08: + self.nmax = 1 + elif nib == 0x09: + self.nmax = 2 + elif nib == 0x0a: + self.nmax = 4 + elif nib == 0x0b: + self.nmax = 8 + else: + # Undefined or idle. + self.nmax = 0 + else: + # sync == 0, valid cmd: start or continue shifting in nibbles. + if (self.nmax > 0): + # Clear tgt nibble. + self.addr &= ~(0x0F << (self.ncnt * 4)) + # Set nibble. + self.addr |= nib << (self.ncnt * 4) + self.ncnt += 1 + + def decode(self): + while True: + (clk, sync, d3, d2, d1, d0) = self.wait({0: 'r'}) + d = (d3, d2, d1, d0) + self.handle_clk_edge(clk, sync, d) diff --git a/libsigrokdecode4DSL/decoders/avr_isp/__init__.py b/libsigrokdecode4DSL/decoders/avr_isp/__init__.py new file mode 100644 index 00000000..e3d90525 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/avr_isp/__init__.py @@ -0,0 +1,25 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2012 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This decoder stacks on top of the 'spi' PD and decodes the In-System +Programming (ISP) protocol of some Atmel AVR 8-bit microcontrollers. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/avr_isp/parts.py b/libsigrokdecode4DSL/decoders/avr_isp/parts.py new file mode 100644 index 00000000..0767789a --- /dev/null +++ b/libsigrokdecode4DSL/decoders/avr_isp/parts.py @@ -0,0 +1,41 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2012 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +# Device code addresses: +# 0x00: vendor code, 0x01: part family + flash size, 0x02: part number + +# Vendor code +vendor_code = { + 0x1e: 'Atmel', + 0x00: 'Device locked', +} + +# (Part family + flash size, part number) +part = { + (0x90, 0x01): 'AT90S1200', + (0x91, 0x01): 'AT90S2313', + (0x92, 0x01): 'AT90S4414', + (0x92, 0x05): 'ATmega48', # 4kB flash + (0x93, 0x01): 'AT90S8515', + (0x93, 0x0a): 'ATmega88', # 8kB flash + (0x94, 0x06): 'ATmega168', # 16kB flash + (0xff, 0xff): 'Device code erased, or target missing', + (0x01, 0x02): 'Device locked', + # TODO: Lots more entries. +} diff --git a/libsigrokdecode4DSL/decoders/avr_isp/pd.py b/libsigrokdecode4DSL/decoders/avr_isp/pd.py new file mode 100644 index 00000000..a0719b73 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/avr_isp/pd.py @@ -0,0 +1,213 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2012-2014 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd +from .parts import * + +VENDOR_CODE_ATMEL = 0x1e + +class Decoder(srd.Decoder): + api_version = 3 + id = 'avr_isp' + name = 'AVR ISP' + longname = 'AVR In-System Programming' + desc = 'Atmel AVR In-System Programming (ISP) protocol.' + license = 'gplv2+' + inputs = ['spi'] + outputs = [] + tags = ['Debug/trace'] + annotations = ( + ('pe', 'Programming enable'), + ('rsb0', 'Read signature byte 0'), + ('rsb1', 'Read signature byte 1'), + ('rsb2', 'Read signature byte 2'), + ('ce', 'Chip erase'), + ('rfb', 'Read fuse bits'), + ('rhfb', 'Read high fuse bits'), + ('refb', 'Read extended fuse bits'), + ('warnings', 'Warnings'), + ('dev', 'Device'), + ) + annotation_rows = ( + ('bits', 'Bits', ()), + ('commands', 'Commands', tuple(range(7 + 1))), + ('warnings', 'Warnings', (8,)), + ('dev', 'Device', (9,)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.state = 'IDLE' + self.mosi_bytes, self.miso_bytes = [], [] + self.ss_cmd, self.es_cmd = 0, 0 + self.xx, self.yy, self.zz, self.mm = 0, 0, 0, 0 + self.ss_device = None + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def putx(self, data): + self.put(self.ss_cmd, self.es_cmd, self.out_ann, data) + + def handle_cmd_programming_enable(self, cmd, ret): + # Programming enable. + # Note: The chip doesn't send any ACK for 'Programming enable'. + self.putx([0, ['Programming enable']]) + + # Sanity check on reply. + if ret[1:4] != [0xac, 0x53, cmd[2]]: + self.putx([8, ['Warning: Unexpected bytes in reply!']]) + + def handle_cmd_read_signature_byte_0x00(self, cmd, ret): + # Signature byte 0x00: vendor code. + self.vendor_code = ret[3] + v = vendor_code[self.vendor_code] + self.putx([1, ['Vendor code: 0x%02x (%s)' % (ret[3], v)]]) + + # Store for later. + self.xx = cmd[1] # Same as ret[2]. + self.yy = cmd[3] + self.zz = ret[0] + + # Sanity check on reply. + if ret[1] != 0x30 or ret[2] != cmd[1]: + self.putx([8, ['Warning: Unexpected bytes in reply!']]) + + # Sanity check for the vendor code. + if self.vendor_code != VENDOR_CODE_ATMEL: + self.putx([8, ['Warning: Vendor code was not 0x1e (Atmel)!']]) + + def handle_cmd_read_signature_byte_0x01(self, cmd, ret): + # Signature byte 0x01: part family and memory size. + self.part_fam_flash_size = ret[3] + self.putx([2, ['Part family / memory size: 0x%02x' % ret[3]]]) + + # Store for later. + self.mm = cmd[3] + self.ss_device = self.ss_cmd + + # Sanity check on reply. + if ret[1] != 0x30 or ret[2] != cmd[1] or ret[0] != self.yy: + self.putx([8, ['Warning: Unexpected bytes in reply!']]) + + def handle_cmd_read_signature_byte_0x02(self, cmd, ret): + # Signature byte 0x02: part number. + self.part_number = ret[3] + self.putx([3, ['Part number: 0x%02x' % ret[3]]]) + + p = part[(self.part_fam_flash_size, self.part_number)] + data = [9, ['Device: Atmel %s' % p]] + self.put(self.ss_device, self.es_cmd, self.out_ann, data) + + # Sanity check on reply. + if ret[1] != 0x30 or ret[2] != self.xx or ret[0] != self.mm: + self.putx([8, ['Warning: Unexpected bytes in reply!']]) + + self.xx, self.yy, self.zz, self.mm = 0, 0, 0, 0 + + def handle_cmd_chip_erase(self, cmd, ret): + # Chip erase (erases both flash an EEPROM). + # Upon successful chip erase, the lock bits will also be erased. + # The only way to end a Chip Erase cycle is to release RESET#. + self.putx([4, ['Chip erase']]) + + # TODO: Check/handle RESET#. + + # Sanity check on reply. + bit = (ret[2] & (1 << 7)) >> 7 + if ret[1] != 0xac or bit != 1 or ret[3] != cmd[2]: + self.putx([8, ['Warning: Unexpected bytes in reply!']]) + + def handle_cmd_read_fuse_bits(self, cmd, ret): + # Read fuse bits. + self.putx([5, ['Read fuse bits: 0x%02x' % ret[3]]]) + + # TODO: Decode fuse bits. + # TODO: Sanity check on reply. + + def handle_cmd_read_fuse_high_bits(self, cmd, ret): + # Read fuse high bits. + self.putx([6, ['Read fuse high bits: 0x%02x' % ret[3]]]) + + # TODO: Decode fuse bits. + # TODO: Sanity check on reply. + + def handle_cmd_read_extended_fuse_bits(self, cmd, ret): + # Read extended fuse bits. + self.putx([7, ['Read extended fuse bits: 0x%02x' % ret[3]]]) + + # TODO: Decode fuse bits. + # TODO: Sanity check on reply. + + def handle_command(self, cmd, ret): + if cmd[:2] == [0xac, 0x53]: + self.handle_cmd_programming_enable(cmd, ret) + elif cmd[0] == 0xac and (cmd[1] & (1 << 7)) == (1 << 7): + self.handle_cmd_chip_erase(cmd, ret) + elif cmd[:3] == [0x50, 0x00, 0x00]: + self.handle_cmd_read_fuse_bits(cmd, ret) + elif cmd[:3] == [0x58, 0x08, 0x00]: + self.handle_cmd_read_fuse_high_bits(cmd, ret) + elif cmd[:3] == [0x50, 0x08, 0x00]: + self.handle_cmd_read_extended_fuse_bits(cmd, ret) + elif cmd[0] == 0x30 and cmd[2] == 0x00: + self.handle_cmd_read_signature_byte_0x00(cmd, ret) + elif cmd[0] == 0x30 and cmd[2] == 0x01: + self.handle_cmd_read_signature_byte_0x01(cmd, ret) + elif cmd[0] == 0x30 and cmd[2] == 0x02: + self.handle_cmd_read_signature_byte_0x02(cmd, ret) + else: + c = '%02x %02x %02x %02x' % tuple(cmd) + r = '%02x %02x %02x %02x' % tuple(ret) + self.putx([0, ['Unknown command: %s (reply: %s)!' % (c, r)]]) + + def decode(self, ss, es, data): + ptype, mosi, miso = data + + # For now, only use DATA and BITS packets. + if ptype not in ('DATA', 'BITS'): + return + + # Store the individual bit values and ss/es numbers. The next packet + # is guaranteed to be a 'DATA' packet belonging to this 'BITS' one. + if ptype == 'BITS': + self.miso_bits, self.mosi_bits = miso, mosi + return + + self.ss, self.es = ss, es + + if len(self.mosi_bytes) == 0: + self.ss_cmd = ss + + # Append new bytes. + self.mosi_bytes.append(mosi) + self.miso_bytes.append(miso) + + # All commands consist of 4 bytes. + if len(self.mosi_bytes) < 4: + return + + self.es_cmd = es + + self.handle_command(self.mosi_bytes, self.miso_bytes) + + self.mosi_bytes = [] + self.miso_bytes = [] diff --git a/libsigrokdecode4DSL/decoders/avr_pdi/__init__.py b/libsigrokdecode4DSL/decoders/avr_pdi/__init__.py new file mode 100644 index 00000000..1c61dea7 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/avr_pdi/__init__.py @@ -0,0 +1,42 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2016 Gerhard Sittig +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +PDI (Program and Debug Interface) is an Atmel proprietary interface for +external programming and on-chip debugging of the device. + +See the Atmel Application Note AVR1612 "PDI programming driver" and the +"Program and Debug Interface" section in the Xmega A manual for details. + +The protocol uses two pins: the RESET pin and one dedicated DATA pin. +The RESET pin provides a clock, the DATA pin communicates serial frames +with a start bit, eight data bits, an even parity bit, and two stop bits. +Data communication is bidirectional and half duplex, the device will +provide response data after reception of a respective request. + +Protocol frames communicate opcodes and their arguments, which provides +random and sequential access to the device's address space. By accessing +the registers of internal peripherals, especially the NVM controller, +it's possible to identify the device, read from and write to several +kinds of memory (signature rows, fuses and lock bits, internal flash and +EEPROM, memory mapped peripherals), and to control execution of software +on the device. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/avr_pdi/pd.py b/libsigrokdecode4DSL/decoders/avr_pdi/pd.py new file mode 100644 index 00000000..45705950 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/avr_pdi/pd.py @@ -0,0 +1,576 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2011-2014 Uwe Hermann +## Copyright (C) 2016 Gerhard Sittig +## Copyright (C) 2019 DreamSourceLab +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +# Note the implementation details: +# +# Although the Atmel literature suggests (does not explicitly mandate, +# but shows in diagrams) that two stop bits are used in the protocol, +# the decoder loses synchronization with ATxmega generated responses +# when it expects more than one stop bit. Since the chip's hardware is +# fixed, this is not an implementation error in some programmer software. +# Since this is a protocol decoder which does not participate in the +# communication (does not actively send data), we can read the data +# stream with one stop bit, and transparently keep working when two +# are used. +# +# Annotations in the UART fields level differ from Atmel literature. +# Wrong parity bits are referred to as "parity error". Low stop bits are +# referred to as "frame error". +# +# The PDI component in the device starts disabled. Enabling PDI +# communication is done by raising DATA and clocking RESET with a +# minimum frequency. PDI communication automatically gets disabled when +# RESET "is inactive" for a certain period of time. The specific timing +# conditions are rather fuzzy in the literature (phrased weakly), and +# are device dependent (refer to the minumum RESET pulse width). This +# protocol decoder implementation internally prepares for but currently +# does not support these enable and disable phases. On the one hand it +# avoids excess external dependencies or wrong results for legal input +# data. On the other hand the decoder works when input streams start in +# the middle of an established connection. +# +# Communication peers detect physical collisions. The decoder can't. +# Upon collisions, a peer will cease any subsequent transmission, until +# a BREAK is seen. Synchronization can get enforced by sending two BREAK +# conditions. The first will cause a collision, the second will re-enable +# the peer. The decoder has no concept of physical collisions. It stops +# the interpretation of instructions when BREAK is seen, and assumes +# that a new instruction will start after BREAK. +# +# This protocol decoder only supports PDI communication over UART frames. +# It lacks support for PDI over JTAG. This would require separation into +# multiple protocol decoder layers (UART physical, JTAG physical, PDI +# instructions, optionally device support on top of PDI. There is some +# more potential for future extensions: +# - The JTAG physical has dedicated TX and RX directions. This decoder +# only picks up communicated bytes but does not check which "line" +# they are communicated on (not applicable to half duplex UART). +# - PDI over JTAG uses "special frame error" conditions to communicate +# additional symbols: BREAK (0xBB with parity 1), DELAY (0xDB with +# parity 1), and EMPTY (0xEB with parity 1). +# - Another "device support" layer might interpret device specific +# timings, and might map addresses used in memory access operations +# to component names, or even register names and bit fields(?). It's +# quite deep a rabbithole though... + +import sigrokdecode as srd +from collections import namedtuple + +class Ann: + '''Annotation and binary output classes.''' + ( + BIT, START, DATA, PARITY_OK, PARITY_ERR, + STOP_OK, STOP_ERR, BREAK, + OPCODE, DATA_PROG, DATA_DEV, PDI_BREAK, + ENABLE, DISABLE, COMMAND, + ) = range(15) + ( + BIN_BYTES, + ) = range(1) + +Bit = namedtuple('Bit', 'val ss es') + +class PDI: + '''PDI protocol instruction opcodes, and operand formats.''' + ( + OP_LDS, OP_LD, OP_STS, OP_ST, + OP_LDCS, OP_REPEAT, OP_STCS, OP_KEY, + ) = range(8) + pointer_format_nice = [ + '*(ptr)', + '*(ptr++)', + 'ptr', + 'ptr++ (rsv)', + ] + pointer_format_terse = [ + '*p', + '*p++', + 'p', + '(rsv)', + ] + ctrl_reg_name = { + 0: 'status', + 1: 'reset', + 2: 'ctrl', + } + +class Decoder(srd.Decoder): + api_version = 3 + id = 'avr_pdi' + name = 'AVR PDI' + longname = 'Atmel Program and Debug Interface' + desc = 'Atmel ATxmega Program and Debug Interface (PDI) protocol.' + license = 'gplv2+' + inputs = ['logic'] + outputs = [] + tags = ['Debug/trace'] + channels = ( + {'id': 'reset', 'name': 'RESET', 'desc': 'RESET / PDI_CLK'}, + {'id': 'data', 'name': 'DATA', 'desc': 'PDI_DATA'}, + ) + annotations = ( + ('uart-bit', 'UART bit'), + ('start-bit', 'Start bit'), + ('data-bit', 'Data bit'), + ('parity-ok', 'Parity OK bit'), + ('parity-err', 'Parity error bit'), + ('stop-ok', 'Stop OK bit'), + ('stop-err', 'Stop error bit'), + ('break', 'BREAK condition'), + ('opcode', 'Instruction opcode'), + ('data-prog', 'Programmer data'), + ('data-dev', 'Device data'), + ('pdi-break', 'BREAK at PDI level'), + ('enable', 'Enable PDI'), + ('disable', 'Disable PDI'), + ('cmd-data', 'PDI command with data'), + ) + annotation_rows = ( + ('uart_bits', 'UART bits', (Ann.BIT,)), + ('uart_fields', 'UART fields', (Ann.START, Ann.DATA, Ann.PARITY_OK, + Ann.PARITY_ERR, Ann.STOP_OK, Ann.STOP_ERR, Ann.BREAK)), + ('pdi_fields', 'PDI fields', (Ann.OPCODE, Ann.DATA_PROG, Ann.DATA_DEV, + Ann.PDI_BREAK)), + ('pdi_cmds', 'PDI Cmds', (Ann.ENABLE, Ann.DISABLE, Ann.COMMAND)), + ) + binary = ( + ('bytes', 'PDI protocol bytes'), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.samplerate = None + self.clear_state() + + def clear_state(self): + # Track bit times and bit values. + self.ss_last_fall = None + self.data_sample = None + self.ss_curr_fall = None + # Collect UART frame bits into byte values. + self.bits = [] + self.zero_count = 0 + self.zero_ss = None + self.break_ss = None + self.break_es = None + self.clear_insn() + + def clear_insn(self): + # Collect instructions and their arguments, + # properties of the current instructions. + self.insn_rep_count = 0 + self.insn_opcode = None + self.insn_wr_counts = [] + self.insn_rd_counts = [] + # Accumulation of data items as bytes pass by. + self.insn_dat_bytes = [] + self.insn_dat_count = 0 + self.insn_ss_data = None + # Next layer "commands", instructions plus operands. + self.cmd_ss = None + self.cmd_insn_parts_nice = [] + self.cmd_insn_parts_terse = [] + + def metadata(self, key, value): + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + self.out_binary = self.register(srd.OUTPUT_BINARY) + + def put_ann_bit(self, bit_nr, ann_idx): + b = self.bits[bit_nr] + self.put(b.ss, b.es, self.out_ann, [ann_idx, [str(b.val)]]) + + def put_ann_data(self, bit_nr, ann_data): + b = self.bits[bit_nr] + self.put(b.ss, b.es, self.out_ann, ann_data) + + def put_ann_row_val(self, ss, es, row, value): + self.put(ss, es, self.out_ann, [row, value]) + + def put_bin_bytes(self, ss, es, row, value): + self.put(ss, es, self.out_binary, [row, value]) + + def handle_byte(self, ss, es, byteval): + '''Handle a byte at the PDI protocol layer.''' + + # Handle BREAK conditions, which will abort any + # potentially currently executing instruction. + is_break = byteval is None + if is_break: + self.cmd_insn_parts_nice.append('BREAK') + self.cmd_insn_parts_terse.append('BRK') + self.insn_rep_count = 0 + # Will FALLTHROUGH to "end of instruction" below. + + # Decode instruction opcodes and argument sizes + # from the first byte of a transaction. + if self.insn_opcode is None and not is_break: + opcode = (byteval & 0xe0) >> 5 + arg30 = byteval & 0x0f + arg32 = (byteval & 0x0c) >> 2 + arg10 = byteval & 0x03 + self.insn_opcode = opcode + self.cmd_ss = ss + mnemonics = None + if opcode == PDI.OP_LDS: + # LDS: load data, direct addressing. + # Writes an address, reads a data item. + width_addr = arg32 + 1 + width_data = arg10 + 1 + self.insn_wr_counts = [width_addr] + self.insn_rd_counts = [width_data] + mnemonics = [ + 'Insn: LDS a{:d}, m{:d}'.format(width_addr, width_data), + 'LDS a{:d}, m{:d}'.format(width_addr, width_data), 'LDS', + ] + self.cmd_insn_parts_nice = ['LDS'] + self.cmd_insn_parts_terse = ['LDS'] + elif opcode == PDI.OP_LD: + # LD: load data, indirect addressing. + # Reads a data item, with optional repeat. + ptr_txt = PDI.pointer_format_nice[arg32] + ptr_txt_terse = PDI.pointer_format_terse[arg32] + width_data = arg10 + 1 + self.insn_wr_counts = [] + self.insn_rd_counts = [width_data] + if self.insn_rep_count: + self.insn_rd_counts.extend(self.insn_rep_count * [width_data]) + self.insn_rep_count = 0 + mnemonics = [ + 'Insn: LD {:s} m{:d}'.format(ptr_txt, width_data), + 'LD {:s} m{:d}'.format(ptr_txt, width_data), 'LD', + ] + self.cmd_insn_parts_nice = ['LD', ptr_txt] + self.cmd_insn_parts_terse = ['LD', ptr_txt_terse] + elif opcode == PDI.OP_STS: + # STS: store data, direct addressing. + # Writes an address, writes a data item. + width_addr = arg32 + 1 + width_data = arg10 + 1 + self.insn_wr_counts = [width_addr, width_data] + self.insn_rd_counts = [] + mnemonics = [ + 'Insn: STS a{:d}, i{:d}'.format(width_addr, width_data), + 'STS a{:d}, i{:d}'.format(width_addr, width_data), 'STS', + ] + self.cmd_insn_parts_nice = ['STS'] + self.cmd_insn_parts_terse = ['STS'] + elif opcode == PDI.OP_ST: + # ST: store data, indirect addressing. + # Writes a data item, with optional repeat. + ptr_txt = PDI.pointer_format_nice[arg32] + ptr_txt_terse = PDI.pointer_format_terse[arg32] + width_data = arg10 + 1 + self.insn_wr_counts = [width_data] + self.insn_rd_counts = [] + if self.insn_rep_count: + self.insn_wr_counts.extend(self.insn_rep_count * [width_data]) + self.insn_rep_count = 0 + mnemonics = [ + 'Insn: ST {:s} i{:d}'.format(ptr_txt, width_data), + 'ST {:s} i{:d}'.format(ptr_txt, width_data), 'ST', + ] + self.cmd_insn_parts_nice = ['ST', ptr_txt] + self.cmd_insn_parts_terse = ['ST', ptr_txt_terse] + elif opcode == PDI.OP_LDCS: + # LDCS: load control/status. + # Loads exactly one byte. + reg_num = arg30 + reg_txt = PDI.ctrl_reg_name.get(reg_num, 'r{:d}'.format(reg_num)) + reg_txt_terse = '{:d}'.format(reg_num) + self.insn_wr_counts = [] + self.insn_rd_counts = [1] + mnemonics = [ + 'Insn: LDCS {:s}, m1'.format(reg_txt), + 'LDCS {:s}, m1'.format(reg_txt), 'LDCS', + ] + self.cmd_insn_parts_nice = ['LDCS', reg_txt] + self.cmd_insn_parts_terse = ['LDCS', reg_txt_terse] + elif opcode == PDI.OP_STCS: + # STCS: store control/status. + # Writes exactly one byte. + reg_num = arg30 + reg_txt = PDI.ctrl_reg_name.get(reg_num, 'r{:d}'.format(reg_num)) + reg_txt_terse = '{:d}'.format(reg_num) + self.insn_wr_counts = [1] + self.insn_rd_counts = [] + mnemonics = [ + 'Insn: STCS {:s}, i1'.format(reg_txt), + 'STCS {:s}, i1'.format(reg_txt), 'STCS', + ] + self.cmd_insn_parts_nice = ['STCS', reg_txt] + self.cmd_insn_parts_terse = ['STCS', reg_txt_terse] + elif opcode == PDI.OP_REPEAT: + # REPEAT: sets repeat count for the next instruction. + # Reads repeat count from following bytes. + width_data = arg10 + 1 + self.insn_wr_counts = [width_data] + self.insn_rd_counts = [] + mnemonics = [ + 'Insn: REPEAT i{:d}'.format(width_data), + 'REPEAT i{:d}'.format(width_data), 'REP', + ] + self.cmd_insn_parts_nice = ['REPEAT'] + self.cmd_insn_parts_terse = ['REP'] + elif opcode == PDI.OP_KEY: + # KEY: set activation key (enables PDIBUS mmap access). + # Writes a sequence of 8 bytes, fixed length. + width_data = 8 + self.insn_wr_counts = [width_data] + self.insn_rd_counts = [] + mnemonics = [ + 'Insn: KEY i{:d}'.format(width_data), + 'KEY i{:d}'.format(width_data), 'KEY', + ] + self.cmd_insn_parts_nice = ['KEY'] + self.cmd_insn_parts_terse = ['KEY'] + + # Emit an annotation for the instruction opcode. + self.put_ann_row_val(ss, es, Ann.OPCODE, mnemonics) + + # Prepare to write/read operands/data bytes. + self.insn_dat_bytes = [] + if self.insn_wr_counts: + self.insn_dat_count = self.insn_wr_counts[0] + return + if self.insn_rd_counts: + self.insn_dat_count = self.insn_rd_counts[0] + return + # FALLTHROUGH. + # When there are no operands or data bytes to read, + # then fall through to the end of the instruction + # handling below (which emits annotations). + + # Read bytes which carry operands (addresses, immediates) + # or data values for memory access. + if self.insn_dat_count and not is_break: + + # Accumulate received bytes until another multi byte + # data item is complete. + if not self.insn_dat_bytes: + self.insn_ss_data = ss + self.insn_dat_bytes.append(byteval) + self.insn_dat_count -= 1 + if self.insn_dat_count: + return + + # Determine the data item's duration and direction, + # "consume" its length spec (to simplify later steps). + data_ss = self.insn_ss_data + data_es = es + if self.insn_wr_counts: + data_ann = Ann.DATA_PROG + data_width = self.insn_wr_counts.pop(0) + elif self.insn_rd_counts: + data_ann = Ann.DATA_DEV + data_width = self.insn_rd_counts.pop(0) + + # PDI communicates multi-byte data items in little endian + # order. Get a nice textual representation of the number, + # wide and narrow for several zoom levels. + self.insn_dat_bytes.reverse() + data_txt_digits = ''.join(['{:02x}'.format(b) for b in self.insn_dat_bytes]) + data_txt_hex = '0x' + data_txt_digits + data_txt_prefix = 'Data: ' + data_txt_hex + data_txts = [data_txt_prefix, data_txt_hex, data_txt_digits] + self.insn_dat_bytes = [] + + # Emit an annotation for the data value. + self.put_ann_row_val(data_ss, data_es, data_ann, data_txts) + + # Collect detailled information which describes the whole + # command when combined (for a next layer annotation, + # spanning the complete command). + self.cmd_insn_parts_nice.append(data_txt_hex) + self.cmd_insn_parts_terse.append(data_txt_digits) + + # Send out write data first until exhausted, + # then drain expected read data. + if self.insn_wr_counts: + self.insn_dat_count = self.insn_wr_counts[0] + return + if self.insn_rd_counts: + self.insn_dat_count = self.insn_rd_counts[0] + return + + # FALLTHROUGH. + # When all operands and data bytes were seen, + # terminate the inspection of the instruction. + + # Postprocess the instruction after its operands were seen. + cmd_es = es + cmd_txt_nice = ' '.join(self.cmd_insn_parts_nice) + cmd_txt_terse = ' '.join(self.cmd_insn_parts_terse) + cmd_txts = [cmd_txt_nice, cmd_txt_terse] + self.put_ann_row_val(self.cmd_ss, cmd_es, Ann.COMMAND, cmd_txts) + if self.insn_opcode == PDI.OP_REPEAT and not is_break: + # The last communicated data item is the repeat + # count for the next instruction (i.e. it will + # execute N+1 times when "REPEAT N" is specified). + count = int(self.cmd_insn_parts_nice[-1], 0) + self.insn_rep_count = count + + # Have the state for instruction decoding cleared, but make sure + # to carry over REPEAT count specs between instructions. They + # start out as zero, will be setup by REPEAT instructions, need + # to get passed to the instruction which follows REPEAT. The + # instruction which sees a non-zero repeat count which will + # consume the counter and drop it to zero, then the counter + # remains at zero until the next REPEAT instruction. + save_rep_count = self.insn_rep_count + self.clear_insn() + self.insn_rep_count = save_rep_count + + def handle_bits(self, ss, es, bitval): + '''Handle a bit at the UART layer.''' + + # Concentrate annotation literals here for easier maintenance. + ann_class_text = { + Ann.START: ['Start bit', 'Start', 'S'], + Ann.PARITY_OK: ['Parity OK', 'Par OK', 'P'], + Ann.PARITY_ERR: ['Parity error', 'Par ERR', 'PE'], + Ann.STOP_OK: ['Stop bit', 'Stop', 'T'], + Ann.STOP_ERR: ['Stop bit error', 'Stop ERR', 'TE'], + Ann.BREAK: ['Break condition', 'BREAK', 'BRK'], + } + def put_uart_field(bitpos, annclass): + self.put_ann_data(bitpos, [annclass, ann_class_text[annclass]]) + + # The number of bits which form one UART frame. Note that + # the decoder operates with only one stop bit. + frame_bitcount = 1 + 8 + 1 + 1 + + # Detect adjacent runs of all-zero bits. This is meant + # to cope when BREAK conditions appear at any arbitrary + # position, it need not be "aligned" to an UART frame. + if bitval == 1: + self.zero_count = 0 + elif bitval == 0: + if not self.zero_count: + self.zero_ss = ss + self.zero_count += 1 + if self.zero_count == frame_bitcount: + self.break_ss = self.zero_ss + + # BREAK conditions are _at_minimum_ the length of a UART frame, but + # can span an arbitrary number of bit times. Track the "end sample" + # value of the last low bit we have seen, and emit the annotation only + # after the line went idle (high) again. Pass BREAK to the upper layer + # as well. When the line is low, BREAK still is pending. When the line + # is high, the current bit cannot be START, thus return from here. + if self.break_ss is not None: + if bitval == '0': + self.break_es = es + return + self.put(self.break_ss, self.break_es, self.out_ann, + [Ann.BREAK, ann_class_text[Ann.BREAK]]) + self.handle_byte(self.break_ss, self.break_es, None) + self.break_ss = None + self.break_es = None + self.bits = [] + return + + # Ignore high bits when waiting for START. + if not self.bits and bitval == 1: + return + + # Store individual bits and their start/end sample numbers, + # until a complete frame was received. + self.bits.append(Bit(bitval, ss, es)) + if len(self.bits) < frame_bitcount: + return + + # Get individual fields of the UART frame. + bits_num = sum([b.val << pos for pos, b in enumerate(self.bits)]) + if False: + # This logic could detect BREAK conditions which are aligned to + # UART frames. Which was obsoleted by the above detection at + # arbitrary positions. The code still can be useful to detect + # "other kinds of frame errors" which carry valid symbols for + # upper layers (the Atmel literature suggests "break", "delay", + # and "empty" symbols when PDI is communicated over different + # physical layers). + if bits_num == 0: # BREAK + self.break_ss = self.bits[0].ss + self.break_es = es + self.bits = [] + return + start_bit = bits_num & 0x01; bits_num >>= 1 + data_val = bits_num & 0xff; bits_num >>= 8 + data_text = '{:02x}'.format(data_val) + parity_bit = bits_num & 0x01; bits_num >>= 1 + stop_bit = bits_num & 0x01; bits_num >>= 1 + + # Check for frame errors. START _must_ have been low + # according to the above accumulation logic. + parity_ok = (bin(data_val).count('1') + parity_bit) % 2 == 0 + stop_ok = stop_bit == 1 + valid_frame = parity_ok and stop_ok + + # Emit annotations. + for idx in range(frame_bitcount): + self.put_ann_bit(idx, Ann.BIT) + put_uart_field(0, Ann.START) + self.put(self.bits[1].ss, self.bits[8].es, self.out_ann, + [Ann.DATA, ['Data: ' + data_text, 'D: ' + data_text, data_text]]) + put_uart_field(9, Ann.PARITY_OK if parity_ok else Ann.PARITY_ERR) + put_uart_field(10, Ann.STOP_OK if stop_ok else Ann.STOP_ERR) + + # Emit binary data stream. Have bytes interpreted at higher layers. + if valid_frame: + byte_ss, byte_es = self.bits[0].ss, self.bits[-1].es + self.put_bin_bytes(byte_ss, byte_es, Ann.BIN_BYTES, bytes([data_val])) + self.handle_byte(byte_ss, byte_es, data_val) + + # Reset internal state for the next frame. + self.bits = [] + + def handle_clk_edge(self, clock_pin, data_pin): + # Sample the data line on rising clock edges. Always, for TX and for + # RX bytes alike. + if clock_pin == 1: + self.data_sample = data_pin + return + + # Falling clock edges are boundaries for bit slots. Inspect previously + # sampled bits on falling clock edges, when the start and end sample + # numbers were determined. Only inspect bit slots of known clock + # periods (avoid interpreting the DATA line when the "enabled" state + # has not yet been determined). + self.ss_last_fall = self.ss_curr_fall + self.ss_curr_fall = self.samplenum + if self.ss_last_fall is None: + return + + # Have the past bit slot processed. + bit_ss, bit_es = self.ss_last_fall, self.ss_curr_fall + bit_val = self.data_sample + self.handle_bits(bit_ss, bit_es, bit_val) + + def decode(self): + while True: + (clock_pin, data_pin) = self.wait({0: 'e'}) + self.handle_clk_edge(clock_pin, data_pin) diff --git a/libsigrokdecode4DSL/decoders/caliper/__init__.py b/libsigrokdecode4DSL/decoders/caliper/__init__.py new file mode 100644 index 00000000..44dab08b --- /dev/null +++ b/libsigrokdecode4DSL/decoders/caliper/__init__.py @@ -0,0 +1,36 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2020 Tomas Mudrunka +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This decoder interprets the digital output of cheap generic calipers +(usually made in China), and shows the measured value in millimeters +or inches. + +Notice that these devices often communicate on voltage levels below +3.3V and may require additional circuitry to capture the signal. + +This decoder does not work for calipers using the Digimatic protocol +(eg. Mitutoyo and similar brands). + +For more information see: +http://www.shumatech.com/support/chinese_scales.htm +https://www.instructables.com/id/Reading-Digital-Callipers-with-an-Arduino-USB/ +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/caliper/pd.py b/libsigrokdecode4DSL/decoders/caliper/pd.py new file mode 100644 index 00000000..20a2a555 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/caliper/pd.py @@ -0,0 +1,146 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2020 Tomas Mudrunka +## +## Permission is hereby granted, free of charge, to any person obtaining a copy +## of this software and associated documentation files (the "Software"), to deal +## in the Software without restriction, including without limitation the rights +## to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +## copies of the Software, and to permit persons to whom the Software is +## furnished to do so, subject to the following conditions: +## +## The above copyright notice and this permission notice shall be included in all +## copies or substantial portions of the Software. +## +## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +## IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +## FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +## AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +## LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +## OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +## SOFTWARE. + +import sigrokdecode as srd +from common.srdhelper import bitpack + +# Millimeters per inch. +mm_per_inch = 25.4 + +class Decoder(srd.Decoder): + api_version = 3 + id = 'caliper' + name = 'Caliper' + longname = 'Digital calipers' + desc = 'Protocol of cheap generic digital calipers.' + license = 'mit' + inputs = ['logic'] + outputs = [] + channels = ( + {'id': 'clk', 'name': 'CLK', 'desc': 'Serial clock line'}, + {'id': 'data', 'name': 'DATA', 'desc': 'Serial data line'}, + ) + options = ( + {'id': 'timeout_ms', 'desc': 'Packet timeout in ms, 0 to disable', + 'default': 10}, + {'id': 'unit', 'desc': 'Convert units', 'default': 'keep', + 'values': ('keep', 'mm', 'inch')}, + {'id': 'changes', 'desc': 'Changes only', 'default': 'no', + 'values': ('no', 'yes')}, + ) + tags = ['Analog/digital', 'Sensor'] + annotations = ( + ('measurement', 'Measurement'), + ('warning', 'Warning'), + ) + annotation_rows = ( + ('measurements', 'Measurements', (0,)), + ('warnings', 'Warnings', (1,)), + ) + + def metadata(self, key, value): + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + + def __init__(self): + self.reset() + + def reset(self): + self.ss, self.es = 0, 0 + self.number_bits = [] + self.flags_bits = [] + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def putg(self, ss, es, cls, data): + self.put(ss, es, self.out_ann, [cls, data]) + + def decode(self): + last_sent = None + timeout_ms = self.options['timeout_ms'] + want_unit = self.options['unit'] + show_all = self.options['changes'] == 'no' + wait_cond = [{0: 'r'}] + if timeout_ms: + snum_per_ms = self.samplerate / 1000 + timeout_snum = timeout_ms * snum_per_ms + wait_cond.append({'skip': round(timeout_snum)}) + while True: + # Sample data at the rising clock edge. Optionally timeout + # after inactivity for a user specified period. Present the + # number of unprocessed bits to the user for diagnostics. + clk, data = self.wait(wait_cond) + if timeout_ms and not self.matched[0]: + if self.number_bits or self.flags_bits: + count = len(self.number_bits) + len(self.flags_bits) + self.putg(self.ss, self.samplenum, 1, [ + 'timeout with {} bits in buffer'.format(count), + 'timeout ({} bits)'.format(count), + 'timeout', + ]) + self.reset() + continue + + # Store position of first bit and last activity. + # Shift in measured number and flag bits. + if not self.ss: + self.ss = self.samplenum + self.es = self.samplenum + if len(self.number_bits) < 16: + self.number_bits.append(data) + continue + if len(self.flags_bits) < 8: + self.flags_bits.append(data) + if len(self.flags_bits) < 8: + continue + + # Get raw values from received data bits. Run the number + # conversion, controlled by flags and/or user specs. + negative = bool(self.flags_bits[4]) + is_inch = bool(self.flags_bits[7]) + number = bitpack(self.number_bits) + if negative: + number = -number + if is_inch: + number /= 2000 + if want_unit == 'mm': + number *= mm_per_inch + is_inch = False + else: + number /= 100 + if want_unit == 'inch': + number = round(number / mm_per_inch, 4) + is_inch = True + unit = 'in' if is_inch else 'mm' + + # Construct and emit an annotation. + if show_all or (number, unit) != last_sent: + self.putg(self.ss, self.es, 0, [ + '{number}{unit}'.format(**locals()), + '{number}'.format(**locals()), + ]) + last_sent = (number, unit) + + # Reset internal state for the start of the next packet. + self.reset() diff --git a/libsigrokdecode4DSL/decoders/can/__init__.py b/libsigrokdecode4DSL/decoders/can/__init__.py new file mode 100644 index 00000000..47f571d6 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/can/__init__.py @@ -0,0 +1,29 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2012 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +CAN (Controller Area Network) is a field bus protocol for distributed +real-time control. + +This decoder assumes that a single CAN_RX line is sampled (e.g. on +the digital output side of a CAN transceiver IC such as the Microchip +MCP-2515DM-BM). +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/can/pd.py b/libsigrokdecode4DSL/decoders/can/pd.py new file mode 100644 index 00000000..3e630ee1 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/can/pd.py @@ -0,0 +1,415 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2012-2013 Uwe Hermann +## Copyright (C) 2019 DreamSourceLab +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd + +class SamplerateError(Exception): + pass + +class Decoder(srd.Decoder): + api_version = 3 + id = 'can' + name = 'CAN' + longname = 'Controller Area Network' + desc = 'Field bus protocol for distributed realtime control.' + license = 'gplv2+' + inputs = ['logic'] + outputs = [] + tags = ['Automotive'] + channels = ( + {'id': 'can_rx', 'name': 'CAN RX', 'desc': 'CAN bus line'}, + ) + options = ( + {'id': 'bitrate', 'desc': 'Bitrate (bits/s)', 'default': 1000000}, + {'id': 'sample_point', 'desc': 'Sample point (%)', 'default': 70.0}, + ) + annotations = ( + ('data', 'CAN payload data'), + ('sof', 'Start of frame'), + ('eof', 'End of frame'), + ('id', 'Identifier'), + ('ext-id', 'Extended identifier'), + ('full-id', 'Full identifier'), + ('ide', 'Identifier extension bit'), + ('reserved-bit', 'Reserved bit 0 and 1'), + ('rtr', 'Remote transmission request'), + ('srr', 'Substitute remote request'), + ('dlc', 'Data length count'), + ('crc-sequence', 'CRC sequence'), + ('crc-delimiter', 'CRC delimiter'), + ('ack-slot', 'ACK slot'), + ('ack-delimiter', 'ACK delimiter'), + ('stuff-bit', 'Stuff bit'), + ('warnings', 'Human-readable warnings'), + ('bit', 'Bit'), + ) + annotation_rows = ( + ('bits', 'Bits', (15, 17)), + ('fields', 'Fields', tuple(range(15))), + ('warnings', 'Warnings', (16,)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.samplerate = None + self.reset_variables() + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def metadata(self, key, value): + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + self.bit_width = float(self.samplerate) / float(self.options['bitrate']) + self.sample_point = (self.bit_width / 100.0) * self.options['sample_point'] + + # Generic helper for CAN bit annotations. + def putg(self, ss, es, data): + left, right = int(self.sample_point), int(self.bit_width - self.sample_point) + self.put(ss - left, es + right, self.out_ann, data) + + # Single-CAN-bit annotation using the current samplenum. + def putx(self, data): + self.putg(self.samplenum, self.samplenum, data) + + # Single-CAN-bit annotation using the samplenum of CAN bit 12. + def put12(self, data): + self.putg(self.ss_bit12, self.ss_bit12, data) + + # Multi-CAN-bit annotation from self.ss_block to current samplenum. + def putb(self, data): + self.putg(self.ss_block, self.samplenum, data) + + def reset_variables(self): + self.state = 'IDLE' + self.sof = self.frame_type = self.dlc = None + self.rawbits = [] # All bits, including stuff bits + self.bits = [] # Only actual CAN frame bits (no stuff bits) + self.curbit = 0 # Current bit of CAN frame (bit 0 == SOF) + self.last_databit = 999 # Positive value that bitnum+x will never match + self.ss_block = None + self.ss_bit12 = None + self.ss_databytebits = [] + + # Poor man's clock synchronization. Use signal edges which change to + # dominant state in rather simple ways. This naive approach is neither + # aware of the SYNC phase's width nor the specific location of the edge, + # but improves the decoder's reliability when the input signal's bitrate + # does not exactly match the nominal rate. + def dom_edge_seen(self, force = False): + self.dom_edge_snum = self.samplenum + self.dom_edge_bcount = self.curbit + + def bit_sampled(self): + # EMPTY + pass + + # Determine the position of the next desired bit's sample point. + def get_sample_point(self, bitnum): + samplenum = self.dom_edge_snum + samplenum += int(self.bit_width * (bitnum - self.dom_edge_bcount)) + samplenum += int(self.sample_point) + return samplenum + + def is_stuff_bit(self): + # CAN uses NRZ encoding and bit stuffing. + # After 5 identical bits, a stuff bit of opposite value is added. + # But not in the CRC delimiter, ACK, and end of frame fields. + if len(self.bits) > self.last_databit + 17: + return False + last_6_bits = self.rawbits[-6:] + if last_6_bits not in ([0, 0, 0, 0, 0, 1], [1, 1, 1, 1, 1, 0]): + return False + + # Stuff bit. Keep it in self.rawbits, but drop it from self.bits. + self.bits.pop() # Drop last bit. + return True + + def is_valid_crc(self, crc_bits): + return True # TODO + + def decode_error_frame(self, bits): + pass # TODO + + def decode_overload_frame(self, bits): + pass # TODO + + # Both standard and extended frames end with CRC, CRC delimiter, ACK, + # ACK delimiter, and EOF fields. Handle them in a common function. + # Returns True if the frame ended (EOF), False otherwise. + def decode_frame_end(self, can_rx, bitnum): + + # Remember start of CRC sequence (see below). + if bitnum == (self.last_databit + 1): + self.ss_block = self.samplenum + + # CRC sequence (15 bits) + elif bitnum == (self.last_databit + 15): + x = self.last_databit + 1 + crc_bits = self.bits[x:x + 15 + 1] + self.crc = int(''.join(str(d) for d in crc_bits), 2) + self.putb([11, ['CRC sequence: 0x%04x' % self.crc, + 'CRC: 0x%04x' % self.crc, 'CRC']]) + if not self.is_valid_crc(crc_bits): + self.putb([16, ['CRC is invalid']]) + + # CRC delimiter bit (recessive) + elif bitnum == (self.last_databit + 16): + self.putx([12, ['CRC delimiter: %d' % can_rx, + 'CRC d: %d' % can_rx, 'CRC d']]) + if can_rx != 1: + self.putx([16, ['CRC delimiter must be a recessive bit']]) + + # ACK slot bit (dominant: ACK, recessive: NACK) + elif bitnum == (self.last_databit + 17): + ack = 'ACK' if can_rx == 0 else 'NACK' + self.putx([13, ['ACK slot: %s' % ack, 'ACK s: %s' % ack, 'ACK s']]) + + # ACK delimiter bit (recessive) + elif bitnum == (self.last_databit + 18): + self.putx([14, ['ACK delimiter: %d' % can_rx, + 'ACK d: %d' % can_rx, 'ACK d']]) + if can_rx != 1: + self.putx([16, ['ACK delimiter must be a recessive bit']]) + + # Remember start of EOF (see below). + elif bitnum == (self.last_databit + 19): + self.ss_block = self.samplenum + + # End of frame (EOF), 7 recessive bits + elif bitnum == (self.last_databit + 25): + self.putb([2, ['End of frame', 'EOF', 'E']]) + if self.rawbits[-7:] != [1, 1, 1, 1, 1, 1, 1]: + self.putb([16, ['End of frame (EOF) must be 7 recessive bits']]) + self.reset_variables() + return True + + return False + + # Returns True if the frame ended (EOF), False otherwise. + def decode_standard_frame(self, can_rx, bitnum): + + # Bit 14: RB0 (reserved bit) + # Has to be sent dominant, but receivers should accept recessive too. + if bitnum == 14: + self.putx([7, ['Reserved bit 0: %d' % can_rx, + 'RB0: %d' % can_rx, 'RB0']]) + + # Bit 12: Remote transmission request (RTR) bit + # Data frame: dominant, remote frame: recessive + # Remote frames do not contain a data field. + rtr = 'remote' if self.bits[12] == 1 else 'data' + self.put12([8, ['Remote transmission request: %s frame' % rtr, + 'RTR: %s frame' % rtr, 'RTR']]) + + # Remember start of DLC (see below). + elif bitnum == 15: + self.ss_block = self.samplenum + + # Bits 15-18: Data length code (DLC), in number of bytes (0-8). + elif bitnum == 18: + self.dlc = int(''.join(str(d) for d in self.bits[15:18 + 1]), 2) + self.putb([10, ['Data length code: %d' % self.dlc, + 'DLC: %d' % self.dlc, 'DLC']]) + self.last_databit = 18 + (self.dlc * 8) + if self.dlc > 8: + self.putb([16, ['Data length code (DLC) > 8 is not allowed']]) + + # Remember all databyte bits, except the very last one. + elif bitnum in range(19, self.last_databit): + self.ss_databytebits.append(self.samplenum) + + # Bits 19-X: Data field (0-8 bytes, depending on DLC) + # The bits within a data byte are transferred MSB-first. + elif bitnum == self.last_databit: + self.ss_databytebits.append(self.samplenum) # Last databyte bit. + for i in range(self.dlc): + x = 18 + (8 * i) + 1 + b = int(''.join(str(d) for d in self.bits[x:x + 8]), 2) + ss = self.ss_databytebits[i * 8] + es = self.ss_databytebits[((i + 1) * 8) - 1] + self.putg(ss, es, [0, ['Data byte %d: 0x%02x' % (i, b), + 'DB %d: 0x%02x' % (i, b), 'DB']]) + self.ss_databytebits = [] + + elif bitnum > self.last_databit: + return self.decode_frame_end(can_rx, bitnum) + + return False + + # Returns True if the frame ended (EOF), False otherwise. + def decode_extended_frame(self, can_rx, bitnum): + + # Remember start of EID (see below). + if bitnum == 14: + self.ss_block = self.samplenum + + # Bits 14-31: Extended identifier (EID[17..0]) + elif bitnum == 31: + self.eid = int(''.join(str(d) for d in self.bits[14:]), 2) + s = '%d (0x%x)' % (self.eid, self.eid) + self.putb([4, ['Extended Identifier: %s' % s, + 'Extended ID: %s' % s, 'Extended ID', 'EID']]) + + self.fullid = self.id << 18 | self.eid + s = '%d (0x%x)' % (self.fullid, self.fullid) + self.putb([5, ['Full Identifier: %s' % s, 'Full ID: %s' % s, + 'Full ID', 'FID']]) + + # Bit 12: Substitute remote request (SRR) bit + self.put12([9, ['Substitute remote request: %d' % self.bits[12], + 'SRR: %d' % self.bits[12], 'SRR']]) + + # Bit 32: Remote transmission request (RTR) bit + # Data frame: dominant, remote frame: recessive + # Remote frames do not contain a data field. + if bitnum == 32: + rtr = 'remote' if can_rx == 1 else 'data' + self.putx([8, ['Remote transmission request: %s frame' % rtr, + 'RTR: %s frame' % rtr, 'RTR']]) + + # Bit 33: RB1 (reserved bit) + elif bitnum == 33: + self.putx([7, ['Reserved bit 1: %d' % can_rx, + 'RB1: %d' % can_rx, 'RB1']]) + + # Bit 34: RB0 (reserved bit) + elif bitnum == 34: + self.putx([7, ['Reserved bit 0: %d' % can_rx, + 'RB0: %d' % can_rx, 'RB0']]) + + # Remember start of DLC (see below). + elif bitnum == 35: + self.ss_block = self.samplenum + + # Bits 35-38: Data length code (DLC), in number of bytes (0-8). + elif bitnum == 38: + self.dlc = int(''.join(str(d) for d in self.bits[35:38 + 1]), 2) + self.putb([10, ['Data length code: %d' % self.dlc, + 'DLC: %d' % self.dlc, 'DLC']]) + self.last_databit = 38 + (self.dlc * 8) + + # Remember all databyte bits, except the very last one. + elif bitnum in range(39, self.last_databit): + self.ss_databytebits.append(self.samplenum) + + # Bits 39-X: Data field (0-8 bytes, depending on DLC) + # The bits within a data byte are transferred MSB-first. + elif bitnum == self.last_databit: + self.ss_databytebits.append(self.samplenum) # Last databyte bit. + for i in range(self.dlc): + x = 38 + (8 * i) + 1 + b = int(''.join(str(d) for d in self.bits[x:x + 8]), 2) + ss = self.ss_databytebits[i * 8] + es = self.ss_databytebits[((i + 1) * 8) - 1] + self.putg(ss, es, [0, ['Data byte %d: 0x%02x' % (i, b), + 'DB %d: 0x%02x' % (i, b), 'DB']]) + self.ss_databytebits = [] + + elif bitnum > self.last_databit: + return self.decode_frame_end(can_rx, bitnum) + + return False + + def handle_bit(self, can_rx): + self.rawbits.append(can_rx) + self.bits.append(can_rx) + + # Get the index of the current CAN frame bit (without stuff bits). + bitnum = len(self.bits) - 1 + + # If this is a stuff bit, remove it from self.bits and ignore it. + if self.is_stuff_bit(): + self.putx([15, [str(can_rx)]]) + self.curbit += 1 # Increase self.curbit (bitnum is not affected). + return + else: + self.putx([17, [str(can_rx)]]) + + # Bit 0: Start of frame (SOF) bit + if bitnum == 0: + self.putx([1, ['Start of frame', 'SOF', 'S']]) + if can_rx != 0: + self.putx([16, ['Start of frame (SOF) must be a dominant bit']]) + + # Remember start of ID (see below). + elif bitnum == 1: + self.ss_block = self.samplenum + + # Bits 1-11: Identifier (ID[10..0]) + # The bits ID[10..4] must NOT be all recessive. + elif bitnum == 11: + self.id = int(''.join(str(d) for d in self.bits[1:]), 2) + s = '%d (0x%x)' % (self.id, self.id), + self.putb([3, ['Identifier: %s' % s, 'ID: %s' % s, 'ID']]) + if (self.id & 0x7f0) == 0x7f0: + self.putb([16, ['Identifier bits 10..4 must not be all recessive']]) + + # RTR or SRR bit, depending on frame type (gets handled later). + elif bitnum == 12: + # self.putx([0, ['RTR/SRR: %d' % can_rx]]) # Debug only. + self.ss_bit12 = self.samplenum + + # Bit 13: Identifier extension (IDE) bit + # Standard frame: dominant, extended frame: recessive + elif bitnum == 13: + ide = self.frame_type = 'standard' if can_rx == 0 else 'extended' + self.putx([6, ['Identifier extension bit: %s frame' % ide, + 'IDE: %s frame' % ide, 'IDE']]) + + # Bits 14-X: Frame-type dependent, passed to the resp. handlers. + elif bitnum >= 14: + if self.frame_type == 'standard': + done = self.decode_standard_frame(can_rx, bitnum) + else: + done = self.decode_extended_frame(can_rx, bitnum) + + # The handlers return True if a frame ended (EOF). + if done: + return + + # After a frame there are 3 intermission bits (recessive). + # After these bits, the bus is considered free. + + self.curbit += 1 + + def decode(self): + if not self.samplerate: + raise SamplerateError('Cannot decode without samplerate.') + + while True: + # State machine. + if self.state == 'IDLE': + # Wait for a dominant state (logic 0) on the bus. + (can_rx,) = self.wait({0: 'l'}) + self.sof = self.samplenum + self.dom_edge_seen(force = True) + self.state = 'GET BITS' + elif self.state == 'GET BITS': + # Wait until we're in the correct bit/sampling position. + pos = self.get_sample_point(self.curbit) + (can_rx,) = self.wait([{'skip': pos - self.samplenum}, {0: 'f'}]) + if (self.matched & (0b1 << 1)): + self.dom_edge_seen() + if (self.matched & (0b1 << 0)): + self.handle_bit(can_rx) + self.bit_sampled() diff --git a/libsigrokdecode4DSL/decoders/cc1101/__init__.py b/libsigrokdecode4DSL/decoders/cc1101/__init__.py new file mode 100644 index 00000000..68fc7987 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/cc1101/__init__.py @@ -0,0 +1,28 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2019 Marco Geisler +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This decoder stacks on top of the 'spi' PD and decodes the protocol spoken +by the Texas Instruments low-power sub-1GHz RF transceiver chips. + +Details: +http://www.ti.com/lit/ds/symlink/cc1101.pdf +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/cc1101/lists.py b/libsigrokdecode4DSL/decoders/cc1101/lists.py new file mode 100644 index 00000000..dfb9b07f --- /dev/null +++ b/libsigrokdecode4DSL/decoders/cc1101/lists.py @@ -0,0 +1,115 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2019 Marco Geisler +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +regs = { +# addr: 'name' + 0x00: 'IOCFG2', + 0x01: 'IOCFG1', + 0x02: 'IOCFG0', + 0x03: 'FIFOTHR', + 0x04: 'SYNC1', + 0x05: 'SYNC0', + 0x06: 'PKTLEN', + 0x07: 'PKTCTRL1', + 0x08: 'PKTCTRL0', + 0x09: 'ADDR', + 0x0A: 'CHANNR', + 0x0B: 'FSCTRL1', + 0x0C: 'FSCTRL0', + 0x0D: 'FREQ2', + 0x0E: 'FREQ1', + 0x0F: 'FREQ0', + 0x10: 'MDMCFG4', + 0x11: 'MDMCFG3', + 0x12: 'MDMCFG2', + 0x13: 'MDMCFG1', + 0x14: 'MDMCFG0', + 0x15: 'DEVIATN', + 0x16: 'MCSM2', + 0x17: 'MCSM1', + 0x18: 'MCSM0', + 0x19: 'FOCCFG', + 0x1A: 'BSCFG', + 0x1B: 'AGCTRL2', + 0x1C: 'AGCTRL1', + 0x1D: 'AGCTRL0', + 0x1E: 'WOREVT1', + 0x1F: 'WOREVT0', + 0x20: 'WORCTRL', + 0x21: 'FREND1', + 0x22: 'FREND0', + 0x23: 'FSCAL3', + 0x24: 'FSCAL2', + 0x25: 'FSCAL1', + 0x26: 'FSCAL0', + 0x27: 'RCCTRL1', + 0x28: 'RCCTRL0', + 0x29: 'FSTEST', + 0x2A: 'PTEST', + 0x2B: 'AGCTEST', + 0x2C: 'TEST2', + 0x2D: 'TEST1', + 0x2E: 'TEST0', + 0x30: 'PARTNUM', + 0x31: 'VERSION', + 0x32: 'FREQEST', + 0x33: 'LQI', + 0x34: 'RSSI', + 0x35: 'MARCSTATE', + 0x36: 'WORTIME1', + 0x37: 'WORTIME0', + 0x38: 'PKTSTATUS', + 0x39: 'VCO_VC_DAC', + 0x3A: 'TXBYTES', + 0x3B: 'RXBYTES', + 0x3C: 'RCCTRL1_STATUS', + 0x3D: 'RCCTRL0_STATUS', + 0x3E: 'PATABLE', + 0x3F: 'FIFO' +} + +strobes = { +# addr: 'name' + 0x30: 'SRES', + 0x31: 'SFSTXON', + 0x32: 'SXOFF', + 0x33: 'SCAL', + 0x34: 'SRX', + 0x35: 'STX', + 0x36: 'SIDLE', + 0x37: '', + 0x38: 'SWOR', + 0x39: 'SPWD', + 0x3A: 'SFRX', + 0x3B: 'SFTX', + 0x3C: 'SWORRST', + 0x3D: 'SNOP' +} + +status_reg_states = { +# value: 'state name' + 0b000: 'IDLE', + 0b001: 'RX', + 0b010: 'TX', + 0b011: 'FSTXON', + 0b100: 'CALIBRATE', + 0b101: 'SETTLING', + 0b110: 'RXFIFO_OVERFLOW', + 0b111: 'TXFIFO_OVERFLOW' +} diff --git a/libsigrokdecode4DSL/decoders/cc1101/pd.py b/libsigrokdecode4DSL/decoders/cc1101/pd.py new file mode 100644 index 00000000..8407b510 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/cc1101/pd.py @@ -0,0 +1,296 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2019 Marco Geisler +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd +from collections import namedtuple +from .lists import * + +ANN_STROBE, ANN_SINGLE_READ, ANN_SINGLE_WRITE, ANN_BURST_READ, \ + ANN_BURST_WRITE, ANN_STATUS_READ, ANN_STATUS, ANN_WARN = range(8) + +Pos = namedtuple('Pos', ['ss', 'es']) +Data = namedtuple('Data', ['mosi', 'miso']) + +class ChannelError(Exception): + pass + +class Decoder(srd.Decoder): + api_version = 3 + id = 'cc1101' + name = 'CC1101' + longname = 'Texas Instruments CC1101' + desc = 'Low-power sub-1GHz RF transceiver chip.' + license = 'gplv2+' + inputs = ['spi'] + outputs = [] + tags = ['IC', 'Wireless/RF'] + annotations = ( + ('strobe', 'Command strobe'), + ('single_read', 'Single register read'), + ('single_write', 'Single register write'), + ('burst_read', 'Burst register read'), + ('burst_write', 'Burst register write'), + ('status', 'Status register'), + ('warning', 'Warning'), + ) + annotation_rows = ( + ('cmd', 'Commands', (ANN_STROBE,)), + ('data', 'Data', (ANN_SINGLE_READ, ANN_SINGLE_WRITE, ANN_BURST_READ, + ANN_BURST_WRITE, ANN_STATUS_READ)), + ('status', 'Status register', (ANN_STATUS,)), + ('warnings', 'Warnings', (ANN_WARN,)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.next() + self.requirements_met = True + self.cs_was_released = False + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def warn(self, pos, msg): + '''Put a warning message 'msg' at 'pos'.''' + self.put(pos.ss, pos.es, self.out_ann, [ANN_WARN, [msg]]) + + def putp(self, pos, ann, msg): + '''Put an annotation message 'msg' at 'pos'.''' + self.put(pos.ss, pos.es, self.out_ann, [ann, [msg]]) + + def putp2(self, pos, ann, msg1, msg2): + '''Put an annotation message 'msg' at 'pos'.''' + self.put(pos.ss, pos.es, self.out_ann, [ann, [msg1, msg2]]) + + def next(self): + '''Resets the decoder after a complete command was decoded.''' + # 'True' for the first byte after CS# went low. + self.first = True + + # The current command, and the minimum and maximum number + # of data bytes to follow. + self.cmd = None + self.min = 0 + self.max = 0 + + # Used to collect the bytes after the command byte + # (and the start/end sample number). + self.mb = [] + self.ss_mb = -1 + self.es_mb = -1 + + def mosi_bytes(self): + '''Returns the collected MOSI bytes of a multi byte command.''' + return [b.mosi for b in self.mb] + + def miso_bytes(self): + '''Returns the collected MISO bytes of a multi byte command.''' + return [b.miso for b in self.mb] + + def decode_command(self, pos, b): + '''Decodes the command byte 'b' at position 'pos' and prepares + the decoding of the following data bytes.''' + c = self.parse_command(b) + if c is None: + self.warn(pos, 'unknown command') + return + + self.cmd, self.dat, self.min, self.max = c + + if self.cmd == 'Strobe': + self.putp(pos, ANN_STROBE, self.format_command()) + else: + # Don't output anything now, the command is merged with + # the data bytes following it. + self.ss_mb = pos.ss + + def format_command(self): + '''Returns the label for the current command.''' + if self.cmd in ('Read', 'Burst read', 'Write', 'Burst write', 'Status read'): + return self.cmd + if self.cmd == 'Strobe': + reg = strobes.get(self.dat, 'unknown strobe') + return '{} {}'.format(self.cmd, reg) + else: + return 'TODO Cmd {}'.format(self.cmd) + + def parse_command(self, b): + '''Parses the command byte. + + Returns a tuple consisting of: + - the name of the command + - additional data needed to dissect the following bytes + - minimum number of following bytes + - maximum number of following bytes (None for infinite) + ''' + + addr = b & 0x3F + if (addr < 0x30) or (addr == 0x3E) or (addr == 0x3F): + if (b & 0xC0) == 0x00: + return ('Write', addr, 1, 1) + if (b & 0xC0) == 0x40: + return ('Burst write', addr, 1, 99999) + if (b & 0xC0) == 0x80: + return ('Read', addr, 1, 1) + if (b & 0xC0) == 0xC0: + return ('Burst read', addr, 1, 99999) + else: + self.warn(pos, 'unknown address/command combination') + else: + if (b & 0x40) == 0x00: + return ('Strobe', addr, 0, 0) + if (b & 0xC0) == 0xC0: + return ('Status read', addr, 1, 99999) + else: + self.warn(pos, 'unknown address/command combination') + + def decode_reg(self, pos, ann, regid, data): + '''Decodes a register. + + pos -- start and end sample numbers of the register + ann -- the annotation number that is used to output the register. + regid -- may be either an integer used as a key for the 'regs' + dictionary, or a string directly containing a register name.' + data -- the register content. + ''' + + if type(regid) == int: + # Get the name of the register. + if regid not in regs: + self.warn(pos, 'unknown register') + return + name = '{} ({:02X})'.format(regs[regid], regid) + else: + name = regid + + if regid == 'STATUS' and ann == ANN_STATUS: + label = 'Status' + self.decode_status_reg(pos, ann, data, label) + else: + if self.cmd in ('Write', 'Read', 'Status read', 'Burst read', 'Burst write'): + label = '{}: {}'.format(self.format_command(), name) + else: + label = 'Reg ({}) {}'.format(self.cmd, name) + self.decode_mb_data(pos, ann, data, label) + + def decode_status_reg(self, pos, ann, data, label): + '''Decodes the data bytes 'data' of a status register at position + 'pos'. The decoded data is prefixed with 'label'.''' + status = data[0] + # bit 7 --> CHIP_RDYn + if status & 0b10000000 == 0b10000000: + longtext_chiprdy = 'CHIP_RDYn is high! ' + else: + longtext_chiprdy = '' + # bits 6:4 --> STATE + state = (status & 0x70) >> 4 + longtext_state = 'STATE is {}, '.format(status_reg_states[state]) + # bits 3:0 --> FIFO_BYTES_AVAILABLE + fifo_bytes = status & 0x0F + if self.cmd in ('Single read', 'Status read', 'Burst read'): + longtext_fifo = '{} bytes available in RX FIFO'.format(fifo_bytes) + else: + longtext_fifo = '{} bytes free in TX FIFO'.format(fifo_bytes) + + text = '{} = {:02X}'.format(label, status) + longtext = ''.join([text, '; ', longtext_chiprdy, longtext_state, longtext_fifo]) + self.putp2(pos, ann, longtext, text) + + def decode_mb_data(self, pos, ann, data, label): + '''Decodes the data bytes 'data' of a multibyte command at position + 'pos'. The decoded data is prefixed with 'label'.''' + + def escape(b): + return '{:02X}'.format(b) + + data = ' '.join([escape(b) for b in data]) + text = '{} = {}'.format(label, data) + self.putp(pos, ann, text) + + def finish_command(self, pos): + '''Decodes the remaining data bytes at position 'pos'.''' + + if self.cmd == 'Write': + self.decode_reg(pos, ANN_SINGLE_WRITE, self.dat, self.mosi_bytes()) + elif self.cmd == 'Burst write': + self.decode_reg(pos, ANN_BURST_WRITE, self.dat, self.mosi_bytes()) + elif self.cmd == 'Read': + self.decode_reg(pos, ANN_SINGLE_READ, self.dat, self.miso_bytes()) + elif self.cmd == 'Burst read': + self.decode_reg(pos, ANN_BURST_READ, self.dat, self.miso_bytes()) + elif self.cmd == 'Strobe': + self.decode_reg(pos, ANN_STROBE, self.dat, self.mosi_bytes()) + elif self.cmd == 'Status read': + self.decode_reg(pos, ANN_STATUS_READ, self.dat, self.miso_bytes()) + else: + self.warn(pos, 'unhandled command') + + def decode(self, ss, es, data): + if not self.requirements_met: + return + + ptype, data1, data2 = data + + if ptype == 'CS-CHANGE': + if data1 is None: + if data2 is None: + self.requirements_met = False + raise ChannelError('CS# pin required.') + elif data2 == 1: + self.cs_was_released = True + + if data1 == 0 and data2 == 1: + # Rising edge, the complete command is transmitted, process + # the bytes that were sent after the command byte. + if self.cmd: + # Check if we got the minimum number of data bytes + # after the command byte. + if len(self.mb) < self.min: + self.warn((ss, ss), 'missing data bytes') + elif self.mb: + self.finish_command(Pos(self.ss_mb, self.es_mb)) + + self.next() + self.cs_was_released = True + + elif ptype == 'DATA' and self.cs_was_released: + mosi, miso = data1, data2 + pos = Pos(ss, es) + + if miso is None or mosi is None: + self.requirements_met = False + raise ChannelError('Both MISO and MOSI pins required.') + + if self.first: + self.first = False + # First MOSI byte is always the command. + self.decode_command(pos, mosi) + # First MISO byte is always the status register. + self.decode_reg(pos, ANN_STATUS, 'STATUS', [miso]) + else: + if not self.cmd or len(self.mb) >= self.max: + self.warn(pos, 'excess byte') + else: + # Collect the bytes after the command byte. + if self.ss_mb == -1: + self.ss_mb = ss + self.es_mb = es + self.mb.append(Data(mosi, miso)) diff --git a/libsigrokdecode4DSL/decoders/cec/__init__.py b/libsigrokdecode4DSL/decoders/cec/__init__.py new file mode 100644 index 00000000..4138b62b --- /dev/null +++ b/libsigrokdecode4DSL/decoders/cec/__init__.py @@ -0,0 +1,25 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2018 Jorge Solla Rubiales +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +The Consumer Electronics Control (CEC) protocol allows users to command and +control devices connected through HDMI. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/cec/pd.py b/libsigrokdecode4DSL/decoders/cec/pd.py new file mode 100644 index 00000000..363a0497 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/cec/pd.py @@ -0,0 +1,312 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2018 Jorge Solla Rubiales +## Copyright (C) 2019 DreamSourceLab +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program. If not, see . +## + +import sigrokdecode as srd +from .protocoldata import * + +# Pulse types +class Pulse: + INVALID, START, ZERO, ONE = range(4) + +# Protocol stats +class Stat: + WAIT_START, GET_BITS, WAIT_EOM, WAIT_ACK = range(4) + +# Pulse times in milliseconds +timing = { + Pulse.START: { + 'low': { 'min': 3.5, 'max': 3.9 }, + 'total': { 'min': 4.3, 'max': 4.7 } + }, + Pulse.ZERO: { + 'low': { 'min': 1.3, 'max': 1.7 }, + 'total': { 'min': 2.05, 'max': 2.75 } + }, + Pulse.ONE: { + 'low': { 'min': 0.4, 'max': 0.8 }, + 'total': { 'min': 2.05, 'max': 2.75 } + } +} + +class ChannelError(Exception): + pass + +class Decoder(srd.Decoder): + api_version = 3 + id = 'cec' + name = 'CEC' + longname = 'HDMI-CEC' + desc = 'HDMI Consumer Electronics Control (CEC) protocol.' + license = 'gplv2+' + inputs = ['logic'] + outputs = [] + tags = ['Display', 'PC'] + channels = ( + {'id': 'cec', 'name': 'CEC', 'desc': 'CEC bus data'}, + ) + annotations = ( + ('st', 'Start'), + ('eom-0', 'End of message'), + ('eom-1', 'Message continued'), + ('nack', 'ACK not set'), + ('ack', 'ACK set'), + ('bits', 'Bits'), + ('bytes', 'Bytes'), + ('frames', 'Frames'), + ('sections', 'Sections'), + ('warnings', 'Warnings') + ) + annotation_rows = ( + ('bits', 'Bits', (0, 1, 2, 3, 4, 5)), + ('bytes', 'Bytes', (6,)), + ('frames', 'Frames', (7,)), + ('sections', 'Sections', (8,)), + ('warnings', 'Warnings', (9,)) + ) + + def __init__(self): + self.reset() + + def precalculate(self): + # Restrict max length of ACK/NACK labels to 2 BIT pulses. + bit_time = timing[Pulse.ZERO]['total']['min'] * 2 + self.max_ack_len_samples = round((bit_time / 1000) * self.samplerate) + + def reset(self): + self.stat = Stat.WAIT_START + self.samplerate = None + self.fall_start = None + self.fall_end = None + self.rise = None + self.reset_frame_vars() + + def reset_frame_vars(self): + self.eom = None + self.bit_count = 0 + self.byte_count = 0 + self.byte = 0 + self.byte_start = None + self.frame_start = None + self.frame_end = None + self.is_nack = 0 + self.cmd_bytes = [] + + def metadata(self, key, value): + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + self.precalculate() + + def handle_frame(self, is_nack): + if self.fall_start is None or self.fall_end is None: + return + + i = 0 + string = '' + while i < len(self.cmd_bytes): + string += '{:02x}'.format(self.cmd_bytes[i]['val']) + if i != (len(self.cmd_bytes) - 1): + string += ':' + i += 1 + + self.put(self.frame_start, self.frame_end, self.out_ann, [7, [string]]) + + i = 0 + operands = 0 + string = '' + while i < len(self.cmd_bytes): + if i == 0: # Parse header + (src, dst) = decode_header(self.cmd_bytes[i]['val']) + string = 'HDR: ' + src + ', ' + dst + elif i == 1: # Parse opcode + string += ' | OPC: ' + opcodes.get(self.cmd_bytes[i]['val'], 'Invalid') + else: # Parse operands + if operands == 0: + string += ' | OPS: ' + operands += 1 + string += '0x{:02x}'.format(self.cmd_bytes[i]['val']) + if i != len(self.cmd_bytes) - 1: + string += ', ' + i += 1 + + # Header only commands are PINGS + if i == 1: + string += ' | OPC: PING' if self.eom else ' | OPC: NONE. Aborted cmd' + + # Add extra information (ack of the command from the destination) + string += ' | R: NACK' if is_nack else ' | R: ACK' + + self.put(self.frame_start, self.frame_end, self.out_ann, [8, [string]]) + + def process(self): + zero_time = ((self.rise - self.fall_start) / self.samplerate) * 1000.0 + total_time = ((self.fall_end - self.fall_start) / self.samplerate) * 1000.0 + pulse = Pulse.INVALID + + # VALIDATION: Identify pulse based on length of the low period + for key in timing: + if zero_time >= timing[key]['low']['min'] and zero_time <= timing[key]['low']['max']: + pulse = key + break + + # VALIDATION: Invalid pulse + if pulse == Pulse.INVALID: + self.stat = Stat.WAIT_START + self.put(self.fall_start, self.fall_end, self.out_ann, [9, ['Invalid pulse: Wrong timing']]) + return + + # VALIDATION: If waiting for start, discard everything else + if self.stat == Stat.WAIT_START and pulse != Pulse.START: + self.put(self.fall_start, self.fall_end, self.out_ann, [9, ['Expected START: BIT found']]) + return + + # VALIDATION: If waiting for ACK or EOM, only BIT pulses (0/1) are expected + if (self.stat == Stat.WAIT_ACK or self.stat == Stat.WAIT_EOM) and pulse == Pulse.START: + self.put(self.fall_start, self.fall_end, self.out_ann, [9, ['Expected BIT: START received)']]) + self.stat = Stat.WAIT_START + + # VALIDATION: ACK bit pulse remains high till the next frame (if any): Validate only min time of the low period + if self.stat == Stat.WAIT_ACK and pulse != Pulse.START: + if total_time < timing[pulse]['total']['min']: + pulse = Pulse.INVALID + self.put(self.fall_start, self.fall_end, self.out_ann, [9, ['ACK pulse below minimun time']]) + self.stat = Stat.WAIT_START + return + + # VALIDATION / PING FRAME DETECTION: Initiator doesn't sets the EOM = 1 but stops sending when ack doesn't arrive + if self.stat == Stat.GET_BITS and pulse == Pulse.START: + # Make sure we received a complete byte to consider it a valid ping + if self.bit_count == 0: + self.handle_frame(self.is_nack) + else: + self.put(self.frame_start, self.samplenum, self.out_ann, [9, ['ERROR: Incomplete byte received']]) + + # Set wait start so we receive next frame + self.stat = Stat.WAIT_START + + # VALIDATION: Check timing of the BIT (0/1) pulse in any other case (not waiting for ACK) + if self.stat != Stat.WAIT_ACK and pulse != Pulse.START: + if total_time < timing[pulse]['total']['min'] or total_time > timing[pulse]['total']['max']: + self.put(self.fall_start, self.fall_end, self.out_ann, [9, ['Bit pulse exceeds total pulse timespan']]) + pulse = Pulse.INVALID + self.stat = Stat.WAIT_START + return + + if pulse == Pulse.ZERO: + bit = 0 + elif pulse == Pulse.ONE: + bit = 1 + + # STATE: WAIT START + if self.stat == Stat.WAIT_START: + self.stat = Stat.GET_BITS + self.reset_frame_vars() + self.put(self.fall_start, self.fall_end, self.out_ann, [0, ['ST']]) + + # STATE: GET BITS + elif self.stat == Stat.GET_BITS: + # Reset stats on first bit + if self.bit_count == 0: + self.byte_start = self.fall_start + self.byte = 0 + + # If 1st byte of the datagram save its sample num + if len(self.cmd_bytes) == 0: + self.frame_start = self.fall_start + + self.byte += (bit << (7 - self.bit_count)) + self.bit_count += 1 + self.put(self.fall_start, self.fall_end, self.out_ann, [5, [str(bit)]]) + + if self.bit_count == 8: + self.bit_count = 0 + self.byte_count += 1 + self.stat = Stat.WAIT_EOM + self.put(self.byte_start, self.samplenum, self.out_ann, [6, ['0x{:02x}'.format(self.byte)]]) + self.cmd_bytes.append({'st': self.byte_start, 'ed': self.samplenum, 'val': self.byte}) + + # STATE: WAIT EOM + elif self.stat == Stat.WAIT_EOM: + self.eom = bit + self.frame_end = self.fall_end + + a = [2, ['EOM=Y']] if self.eom else [1, ['EOM=N']] + self.put(self.fall_start, self.fall_end, self.out_ann, a) + + self.stat = Stat.WAIT_ACK + + # STATE: WAIT ACK + elif self.stat == Stat.WAIT_ACK: + # If a frame with broadcast destination is being sent, the ACK is + # inverted: a 0 is considered a NACK, therefore we invert the value + # of the bit here, so we match the real meaning of it. + if (self.cmd_bytes[0]['val'] & 0x0F) == 0x0F: + bit = ~bit & 0x01 + + if (self.fall_end - self.fall_start) > self.max_ack_len_samples: + ann_end = self.fall_start + self.max_ack_len_samples + else: + ann_end = self.fall_end + + if bit: + # Any NACK detected in the frame is enough to consider the + # whole frame NACK'd. + self.is_nack = 1 + self.put(self.fall_start, ann_end, self.out_ann, [3, ['NACK']]) + else: + self.put(self.fall_start, ann_end, self.out_ann, [4, ['ACK']]) + + # After ACK bit, wait for new datagram or continue reading current + # one based on EOM value. + if self.eom or self.is_nack: + self.stat = Stat.WAIT_START + self.handle_frame(self.is_nack) + else: + self.stat = Stat.GET_BITS + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def decode(self): + if not self.samplerate: + raise SamplerateError('Cannot decode without samplerate.') + + # Wait for first falling edge. + self.wait({0: 'f'}) + self.fall_end = self.samplenum + + while True: + self.wait({0: 'r'}) + self.rise = self.samplenum + + if self.stat == Stat.WAIT_ACK: + self.wait([{0: 'f'}, {'skip': self.max_ack_len_samples}]) + else: + self.wait([{0: 'f'}]) + + self.fall_start = self.fall_end + self.fall_end = self.samplenum + self.process() + + # If there was a timeout while waiting for ACK: RESYNC. + # Note: This is an expected situation as no new falling edge will + # happen until next frame is transmitted. + if self.matched == 0b10: + self.wait({0: 'f'}) + self.fall_end = self.samplenum diff --git a/libsigrokdecode4DSL/decoders/cec/protocoldata.py b/libsigrokdecode4DSL/decoders/cec/protocoldata.py new file mode 100644 index 00000000..78c3b6f5 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/cec/protocoldata.py @@ -0,0 +1,117 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2018 Jorge Solla Rubiales +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program. If not, see . +## + +logical_adresses = [ + 'TV', + 'Recording_1', + 'Recording_2', + 'Tuner_1', + 'Playback_1', + 'AudioSystem', + 'Tuner2', + 'Tuner3', + 'Playback_2', + 'Recording_3', + 'Tuner_4', + 'Playback_3', + 'Backup_1', + 'Backup_2', + 'FreeUse', +] + +# List taken from LibCEC. +opcodes = { + 0x82: 'ACTIVE_SOURCE', + 0x04: 'IMAGE_VIEW_ON', + 0x0D: 'TEXT_VIEW_ON', + 0x9D: 'INACTIVE_SOURCE', + 0x85: 'REQUEST_ACTIVE_SOURCE', + 0x80: 'ROUTING_CHANGE', + 0x81: 'ROUTING_INFORMATION', + 0x86: 'SET_STREAM_PATH', + 0x36: 'STANDBY', + 0x0B: 'RECORD_OFF', + 0x09: 'RECORD_ON', + 0x0A: 'RECORD_STATUS', + 0x0F: 'RECORD_TV_SCREEN', + 0x33: 'CLEAR_ANALOGUE_TIMER', + 0x99: 'CLEAR_DIGITAL_TIMER', + 0xA1: 'CLEAR_EXTERNAL_TIMER', + 0x34: 'SET_ANALOGUE_TIMER', + 0x97: 'SET_DIGITAL_TIMER', + 0xA2: 'SET_EXTERNAL_TIMER', + 0x67: 'SET_TIMER_PROGRAM_TITLE', + 0x43: 'TIMER_CLEARED_STATUS', + 0x35: 'TIMER_STATUS', + 0x9E: 'CEC_VERSION', + 0x9F: 'GET_CEC_VERSION', + 0x83: 'GIVE_PHYSICAL_ADDRESS', + 0x91: 'GET_MENU_LANGUAGE', + 0x84: 'REPORT_PHYSICAL_ADDRESS', + 0x32: 'SET_MENU_LANGUAGE', + 0x42: 'DECK_CONTROL', + 0x1B: 'DECK_STATUS', + 0x1A: 'GIVE_DECK_STATUS', + 0x41: 'PLAY', + 0x08: 'GIVE_TUNER_DEVICE_STATUS', + 0x92: 'SELECT_ANALOGUE_SERVICE', + 0x93: 'SELECT_DIGITAL_SERVICE', + 0x07: 'TUNER_DEVICE_STATUS', + 0x06: 'TUNER_STEP_DECREMENT', + 0x05: 'TUNER_STEP_INCREMENT', + 0x87: 'DEVICE_VENDOR_ID', + 0x8C: 'GIVE_DEVICE_VENDOR_ID', + 0x89: 'VENDOR_COMMAND', + 0xA0: 'VENDOR_COMMAND_WITH_ID', + 0x8A: 'VENDOR_REMOTE_BUTTON_DOWN', + 0x8B: 'VENDOR_REMOTE_BUTTON_UP', + 0x64: 'SET_OSD_STRING', + 0x46: 'GIVE_OSD_NAME', + 0x47: 'SET_OSD_NAME', + 0x8D: 'MENU_REQUEST', + 0x8E: 'MENU_STATUS', + 0x44: 'USER_CONTROL_PRESSED', + 0x45: 'USER_CONTROL_RELEASE', + 0x8F: 'GIVE_DEVICE_POWER_STATUS', + 0x90: 'REPORT_POWER_STATUS', + 0x00: 'FEATURE_ABORT', + 0xFF: 'ABORT', + 0x71: 'GIVE_AUDIO_STATUS', + 0x7D: 'GIVE_SYSTEM_AUDIO_MODE_STATUS', + 0x7A: 'REPORT_AUDIO_STATUS', + 0x72: 'SET_SYSTEM_AUDIO_MODE', + 0x70: 'SYSTEM_AUDIO_MODE_REQUEST', + 0x7E: 'SYSTEM_AUDIO_MODE_STATUS', + 0x9A: 'SET_AUDIO_RATE', +} + +def resolve_logical_address(id_, is_initiator): + if id_ < 0 or id_ > 0x0F: + return 'Invalid' + + # Special handling of 0x0F. + if id_ == 0x0F: + return 'Unregistered' if is_initiator else 'Broadcast' + + return logical_adresses[id_] + +def decode_header(header): + src = (header & 0xF0) >> 4 + dst = (header & 0x0F) + return (resolve_logical_address(src, 1), resolve_logical_address(dst, 0)) diff --git a/libsigrokdecode4DSL/decoders/cfp/__init__.py b/libsigrokdecode4DSL/decoders/cfp/__init__.py new file mode 100644 index 00000000..351e893b --- /dev/null +++ b/libsigrokdecode4DSL/decoders/cfp/__init__.py @@ -0,0 +1,34 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2018 Elias Oenal +## All rights reserved. +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are met: +## +## 1. Redistributions of source code must retain the above copyright notice, +## this list of conditions and the following disclaimer. +## 2. Redistributions in binary form must reproduce the above copyright notice, +## this list of conditions and the following disclaimer in the documentation +## and/or other materials provided with the distribution. +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +## AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +## IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +## ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +## LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +## CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +## SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +## INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +## CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +## ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +## POSSIBILITY OF SUCH DAMAGE. +## + +''' +This decoder stacks on top of the 'mdio' PD and decodes the CFP 100G +pluggable transceiver protocol. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/cfp/pd.py b/libsigrokdecode4DSL/decoders/cfp/pd.py new file mode 100644 index 00000000..9638ba19 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/cfp/pd.py @@ -0,0 +1,110 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2018 Elias Oenal +## All rights reserved. +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are met: +## +## 1. Redistributions of source code must retain the above copyright notice, +## this list of conditions and the following disclaimer. +## 2. Redistributions in binary form must reproduce the above copyright notice, +## this list of conditions and the following disclaimer in the documentation +## and/or other materials provided with the distribution. +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +## AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +## IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +## ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +## LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +## CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +## SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +## INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +## CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +## ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +## POSSIBILITY OF SUCH DAMAGE. +## + +import sigrokdecode as srd + +MODULE_ID = { + 0x00: 'Unknown or unspecified', + 0x01: 'GBIC', + 0x02: 'Module/connector soldered to motherboard', + 0x03: 'SFP', + 0x04: '300 pin XSBI', + 0x05: 'XENPAK', + 0x06: 'XFP', + 0x07: 'XFF', + 0x08: 'XFP-E', + 0x09: 'XPAK', + 0x0a: 'X2', + 0x0B: 'DWDM-SFP', + 0x0C: 'QSFP', + 0x0D: 'QSFP+', + 0x0E: 'CFP', + 0x0F: 'CXP (TBD)', + 0x11: 'CFP2', + 0x12: 'CFP4', +} + +class Decoder(srd.Decoder): + api_version = 3 + id = 'cfp' + name = 'CFP' + longname = '100 Gigabit C form-factor pluggable' + desc = '100 Gigabit C form-factor pluggable (CFP) protocol.' + license = 'BSD' + inputs = ['mdio'] + outputs = [] + tags = ['Networking'] + annotations = ( + ('register', 'Register'), + ('decode', 'Decode'), + ) + annotation_rows = ( + ('registers', 'Registers', (0,)), + ('decodes', 'Decodes', (1,)), + ) + + def __init__(self): + self.reset() + + def reset(self): + pass + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def putx(self, data): + self.put(self.ss, self.es, self.out_ann, data) + + def decode(self, ss, es, data): + self.ss, self.es = ss, es + for (clause45, clause45_addr, is_read, portad, devad, reg) in data: + if not is_read: + continue + if clause45_addr in range(0x8000, 0x807F + 1): + self.putx([0, ['CFP NVR 1: Basic ID register', 'NVR1']]) + if clause45_addr == 0x8000: + self.putx([1, ['Module identifier: %s' % \ + MODULE_ID.get(reg, 'Reserved')]]) + elif clause45_addr in range(0x8080, 0x80FF + 1): + self.putx([0, ['CFP NVR 2: Extended ID register', 'NVR2']]) + elif clause45_addr in range(0x8100, 0x817F + 1): + self.putx([0, ['CFP NVR 3: Network lane specific register', 'NVR3']]) + elif clause45_addr in range(0x8180, 0x81FF + 1): + self.putx([0, ['CFP NVR 4', 'NVR4']]) + elif clause45_addr in range(0x8400, 0x847F + 1): + self.putx([0, ['Vendor NVR 1: Vendor data register', 'V-NVR1']]) + elif clause45_addr in range(0x8480, 0x84FF + 1): + self.putx([0, ['Vendor NVR 2: Vendor data register', 'V-NVR2']]) + elif clause45_addr in range(0x8800, 0x887F + 1): + self.putx([0, ['User NVR 1: User data register', 'U-NVR1']]) + elif clause45_addr in range(0x8880, 0x88FF + 1): + self.putx([0, ['User NVR 2: User data register', 'U-NVR2']]) + elif clause45_addr in range(0xA000, 0xA07F + 1): + self.putx([0, ['CFP Module VR 1: CFP Module level control and DDM register', 'Mod-VR1']]) + elif clause45_addr in range(0xA080, 0xA0FF + 1): + self.putx([0, ['MLG VR 1: MLG Management Interface register', 'MLG-VR1']]) diff --git a/libsigrokdecode4DSL/decoders/cjtag-oscan0/__init__.py b/libsigrokdecode4DSL/decoders/cjtag-oscan0/__init__.py new file mode 100644 index 00000000..46444052 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/cjtag-oscan0/__init__.py @@ -0,0 +1,37 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2012-2015 Uwe Hermann +## Copyright (C) 2019 Zhiyuan Wan +## Copyright (C) 2019 Kongou Hikari +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + +''' +JTAG (Joint Test Action Group), a.k.a. "IEEE 1149.1: Standard Test Access Port +and Boundary-Scan Architecture", is a protocol used for testing, debugging, +and flashing various digital ICs. + +Details: +https://en.wikipedia.org/wiki/Joint_Test_Action_Group +http://focus.ti.com/lit/an/ssya002c/ssya002c.pdf + +This decoders handles a tiny part of IEEE 1149.7, the so called CJTAG OSCAN1 +format +http://developers-club.com/posts/237885/ +''' + +from .pd import Decoder \ No newline at end of file diff --git a/libsigrokdecode4DSL/decoders/cjtag-oscan0/pd.py b/libsigrokdecode4DSL/decoders/cjtag-oscan0/pd.py new file mode 100644 index 00000000..44cf89f0 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/cjtag-oscan0/pd.py @@ -0,0 +1,390 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2012-2015 Uwe Hermann +## Copyright (C) 2019 DreamSourceLab +## Copyright (C) 2019 Zhiyuan Wan +## Copyright (C) 2019 Kongou Hikari +## +## Version: +## Modified by Shiqiu Nie(369614718@qq.com) +## Date: 2017-01-11 +## Descript: +## 1. 2017-01-10 Fixed TDI/TDO data decode, when JTAG TAP run into +## SHIFT-IR/SHIFT-DR status,the first bit is not a valid bit. +## 2. 2017-01-11 Fixed decode when shift only one bit. +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd + +''' +OUTPUT_PYTHON format: + +Packet: +[, ] + +: + - 'NEW STATE': is the new state of the JTAG state machine. + Valid values: 'TEST-LOGIC-RESET', 'RUN-TEST/IDLE', 'SELECT-DR-SCAN', + 'CAPTURE-DR', 'SHIFT-DR', 'EXIT1-DR', 'PAUSE-DR', 'EXIT2-DR', 'UPDATE-DR', + 'SELECT-IR-SCAN', 'CAPTURE-IR', 'SHIFT-IR', 'EXIT1-IR', 'PAUSE-IR', + 'EXIT2-IR', 'UPDATE-IR'. + - 'IR TDI': Bitstring that was clocked into the IR register. + - 'IR TDO': Bitstring that was clocked out of the IR register. + - 'DR TDI': Bitstring that was clocked into the DR register. + - 'DR TDO': Bitstring that was clocked out of the DR register. + +All bitstrings are a list consisting of two items. The first is a sequence +of '1' and '0' characters (the right-most character is the LSB. Example: +'01110001', where 1 is the LSB). The second item is a list of ss/es values +for each bit that is in the bitstring. +''' + +jtag_states = [ + # Intro "tree" + 'TEST-LOGIC-RESET', 'RUN-TEST/IDLE', + # DR "tree" + 'SELECT-DR-SCAN', 'CAPTURE-DR', 'UPDATE-DR', 'PAUSE-DR', + 'SHIFT-DR', 'EXIT1-DR', 'EXIT2-DR', + # IR "tree" + 'SELECT-IR-SCAN', 'CAPTURE-IR', 'UPDATE-IR', 'PAUSE-IR', + 'SHIFT-IR', 'EXIT1-IR', 'EXIT2-IR', +] + +class Decoder(srd.Decoder): + api_version = 3 + id = 'cjtag_oscan1' + name = 'cJTAG OScan1' + longname = 'Compact Joint Test Action Group (IEEE 1149.7)' + desc = 'Protocol for testing, debugging, and flashing ICs, Now this plugin has no ZBS support, it only supports Oscan1 format.' + license = 'gplv2+' + inputs = ['logic'] + outputs = ['jtag'] + tags = ['Debug/trace'] + channels = ( + {'id': 'tdi', 'name': 'TDI', 'desc': 'Test data input'}, + {'id': 'tdo', 'name': 'TDO', 'desc': 'Test data output'}, + {'id': 'tck', 'name': 'TCK', 'desc': 'Test clock'}, + {'id': 'tms', 'name': 'TMS', 'desc': 'Test mode select'}, + ) + optional_channels = ( + {'id': 'trst', 'name': 'TRST#', 'desc': 'Test reset'}, + {'id': 'srst', 'name': 'SRST#', 'desc': 'System reset'}, + {'id': 'rtck', 'name': 'RTCK', 'desc': 'Return clock signal'}, + ) + annotations = tuple([tuple([s.lower(), s]) for s in jtag_states]) + ( \ + ('bit-tdi', 'Bit (TDI)'), + ('bit-tdo', 'Bit (TDO)'), + ('bitstring-tdi', 'Bitstring (TDI)'), + ('bitstring-tdo', 'Bitstring (TDO)'), + ('bit-tms', 'Bit (TMS)'), + ('state-tapc', 'TAPC State'), + ) + annotation_rows = ( + ('bits-tdi', 'Bits (TDI)', (16,)), + ('bits-tdo', 'Bits (TDO)', (17,)), + ('bitstrings-tdi', 'Bitstring (TDI)', (18,)), + ('bitstrings-tdo', 'Bitstring (TDO)', (19,)), + ('bit-tms', 'Bit (TMS)', (20,)), + ('state-tapc', 'TAPC State', (21,)), + ('states', 'States', tuple(range(15 + 1))), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.state = 'TEST-LOGIC-RESET' + # self.state = 'RUN-TEST/IDLE' + self.cjtagstate = '4-WIRE' + self.oldcjtagstate = None + self.escape_edges = 0 + self.oaclen = 0 + self.oldtms = 0 + self.oacp = 0 + self.oscan1cycle = 0 + self.oldstate = None + self.bits_tdi = [] + self.bits_tdo = [] + self.bits_samplenums_tdi = [] + self.bits_samplenums_tdo = [] + self.ss_item = self.es_item = None + self.ss_bitstring = self.es_bitstring = None + self.saved_item = None + self.first = True + self.first_bit = True + self.bits_cnt = 0 + self.data_ready = False + + def start(self): + self.out_python = self.register(srd.OUTPUT_PYTHON) + self.out_ann = self.register(srd.OUTPUT_ANN) + + def putx(self, data): + self.put(self.ss_item, self.es_item, self.out_ann, data) + + def putp(self, data): + self.put(self.ss_item, self.es_item, self.out_python, data) + + def putx_bs(self, data): + self.put(self.ss_bitstring, self.es_bitstring, self.out_ann, data) + + def putp_bs(self, data): + self.put(self.ss_bitstring, self.es_bitstring, self.out_python, data) + + def advance_state_machine(self, tms): + self.oldstate = self.state + + if self.cjtagstate.startswith("CJTAG-"): + self.oacp = self.oacp + 1 + if (self.oacp > 4 and self.oaclen == 12): + self.cjtagstate = 'CJTAG-EC' + + if (self.oacp == 8 and tms == 0): + self.oaclen = 36 + if (self.oacp > 8 and self.oaclen == 36): + self.cjtagstate = 'CJTAG-SPARE' + if (self.oacp > 13 and self.oaclen == 36): + self.cjtagstate = 'CJTAG-TPDEL' + if (self.oacp > 16 and self.oaclen == 36): + self.cjtagstate = 'CJTAG-TPREV' + if (self.oacp > 18 and self.oaclen == 36): + self.cjtagstate = 'CJTAG-TPST' + if (self.oacp > 23 and self.oaclen == 36): + self.cjtagstate = 'CJTAG-RDYC' + if (self.oacp > 25 and self.oaclen == 36): + self.cjtagstate = 'CJTAG-DLYC' + if (self.oacp > 27 and self.oaclen == 36): + self.cjtagstate = 'CJTAG-SCNFMT' + + if (self.oacp > 8 and self.oaclen == 12): + self.cjtagstate = 'CJTAG-CP' + if (self.oacp > 32 and self.oaclen == 36): + self.cjtagstate = 'CJTAG-CP' + + if (self.oacp > self.oaclen): + self.cjtagstate = 'OSCAN1' + self.oscan1cycle = 1 + self.state = 'TEST-LOGIC-RESET' # Because Nuclei cJTAG device asserts a reset during cJTAG online activating. + + else : + # Intro "tree" + if self.state == 'TEST-LOGIC-RESET': + self.state = 'TEST-LOGIC-RESET' if (tms) else 'RUN-TEST/IDLE' + elif self.state == 'RUN-TEST/IDLE': + self.state = 'SELECT-DR-SCAN' if (tms) else 'RUN-TEST/IDLE' + + # DR "tree" + elif self.state == 'SELECT-DR-SCAN': + self.state = 'SELECT-IR-SCAN' if (tms) else 'CAPTURE-DR' + elif self.state == 'CAPTURE-DR': + self.state = 'EXIT1-DR' if (tms) else 'SHIFT-DR' + elif self.state == 'SHIFT-DR': + self.state = 'EXIT1-DR' if (tms) else 'SHIFT-DR' + elif self.state == 'EXIT1-DR': + self.state = 'UPDATE-DR' if (tms) else 'PAUSE-DR' + elif self.state == 'PAUSE-DR': + self.state = 'EXIT2-DR' if (tms) else 'PAUSE-DR' + elif self.state == 'EXIT2-DR': + self.state = 'UPDATE-DR' if (tms) else 'SHIFT-DR' + elif self.state == 'UPDATE-DR': + self.state = 'SELECT-DR-SCAN' if (tms) else 'RUN-TEST/IDLE' + + # IR "tree" + elif self.state == 'SELECT-IR-SCAN': + self.state = 'TEST-LOGIC-RESET' if (tms) else 'CAPTURE-IR' + elif self.state == 'CAPTURE-IR': + self.state = 'EXIT1-IR' if (tms) else 'SHIFT-IR' + elif self.state == 'SHIFT-IR': + self.state = 'EXIT1-IR' if (tms) else 'SHIFT-IR' + elif self.state == 'EXIT1-IR': + self.state = 'UPDATE-IR' if (tms) else 'PAUSE-IR' + elif self.state == 'PAUSE-IR': + self.state = 'EXIT2-IR' if (tms) else 'PAUSE-IR' + elif self.state == 'EXIT2-IR': + self.state = 'UPDATE-IR' if (tms) else 'SHIFT-IR' + elif self.state == 'UPDATE-IR': + self.state = 'SELECT-DR-SCAN' if (tms) else 'RUN-TEST/IDLE' + + def handle_rising_tck_edge(self, tdi, tdo, tck, tms, trst, srst, rtck): + # Rising TCK edges always advance the state machine. + + self.advance_state_machine(tms) + + if self.first: + # Save the start sample and item for later (no output yet). + self.ss_item = self.samplenum + self.first = False + else: + # Output the saved item (from the last CLK edge to the current). + self.es_item = self.samplenum + # Output the old state (from last rising TCK edge to current one). + self.putx([jtag_states.index(self.oldstate), [self.oldstate]]) + self.putp(['NEW STATE', self.state]) + self.putx([21, [self.oldcjtagstate]]) + if(self.oldcjtagstate.startswith("CJTAG-")): + self.putx([20, [str(self.oldtms)]]) + #self.putx([20, [str(tms)]]) + #self.putx([16, [str(tdi)]]) + #self.putx([17, [str(tdo)]]) + self.oldtms = tms + # Upon SHIFT-IR/SHIFT-DR collect the current TDI/TDO values. + if self.state.startswith('SHIFT-'): + #if self.first_bit: + #self.ss_bitstring = self.samplenum + # self.first_bit = False + + #else: + if self.bits_cnt > 0: + if self.bits_cnt == 1: + self.ss_bitstring = self.samplenum + + if self.bits_cnt > 1: + self.putx([16, [str(self.bits_tdi[0])]]) + self.putx([17, [str(self.bits_tdo[0])]]) + # Use self.samplenum as ES of the previous bit. + self.bits_samplenums_tdi[0][1] = self.samplenum + self.bits_samplenums_tdo[0][1] = self.samplenum + + self.bits_tdi.insert(0, tdi) + self.bits_tdo.insert(0, tdo) + + # Use self.samplenum as SS of the current bit. + self.bits_samplenums_tdi.insert(0, [self.samplenum, -1]) + self.bits_samplenums_tdo.insert(0, [self.samplenum, -1]) + + self.bits_cnt = self.bits_cnt + 1 + + # Output all TDI/TDO bits if we just switched from SHIFT-* to EXIT1-*. + if self.oldstate.startswith('SHIFT-') and \ + self.state.startswith('EXIT1-'): + + #self.es_bitstring = self.samplenum + if self.bits_cnt > 0: + if self.bits_cnt == 1: # Only shift one bit + self.ss_bitstring = self.samplenum + self.bits_tdi.insert(0, tdi) + self.bits_tdo.insert(0, tdo) + ## Use self.samplenum as SS of the current bit. + self.bits_samplenums_tdi.insert(0, [self.samplenum, -1]) + self.bits_samplenums_tdo.insert(0, [self.samplenum, -1]) + else: + ### ---------------------------------------------------------------- + self.putx([16, [str(self.bits_tdi[0])]]) + self.putx([17, [str(self.bits_tdo[0])]]) + ### Use self.samplenum as ES of the previous bit. + self.bits_samplenums_tdi[0][1] = self.samplenum + self.bits_samplenums_tdo[0][1] = self.samplenum + + self.bits_tdi.insert(0, tdi) + self.bits_tdo.insert(0, tdo) + + ## Use self.samplenum as SS of the current bit. + self.bits_samplenums_tdi.insert(0, [self.samplenum, -1]) + self.bits_samplenums_tdo.insert(0, [self.samplenum, -1]) + ## ---------------------------------------------------------------- + + self.data_ready = True + + self.first_bit = True + self.bits_cnt = 0 + if self.oldstate.startswith('EXIT'):# and \ + #self.state.startswith('PAUSE-'): + if self.data_ready: + self.data_ready = False + self.es_bitstring = self.samplenum + t = self.state[-2:] + ' TDI' + b = ''.join(map(str, self.bits_tdi)) + h = ' (0x%X' % int('0b' + b, 2) + ')' + s = t + ': ' + h + ', ' + str(len(self.bits_tdi)) + ' bits' #b + + self.putx_bs([18, [s]]) + self.bits_samplenums_tdi[0][1] = self.samplenum # ES of last bit. + self.putp_bs([t, [b, self.bits_samplenums_tdi]]) + self.putx([16, [str(self.bits_tdi[0])]]) # Last bit. + self.bits_tdi = [] + self.bits_samplenums_tdi = [] + + t = self.state[-2:] + ' TDO' + b = ''.join(map(str, self.bits_tdo)) + h = ' (0x%X' % int('0b' + b, 2) + ')' + s = t + ': ' + h + ', ' + str(len(self.bits_tdo)) + ' bits' #+ b + self.putx_bs([19, [s]]) + self.bits_samplenums_tdo[0][1] = self.samplenum # ES of last bit. + self.putp_bs([t, [b, self.bits_samplenums_tdo]]) + self.putx([17, [str(self.bits_tdo[0])]]) # Last bit. + self.bits_tdo = [] + self.bits_samplenums_tdo = [] + + #self.first_bit = True + #self.bits_cnt = 0 + + #self.ss_bitstring = self.samplenum + + self.ss_item = self.samplenum + + def handle_tms_edge(self, tck, tms): + self.escape_edges = self.escape_edges + 1 + #print ("Detected escape sequence " + str(self.escape_edges)) + #self.es_item = self.samplenum + #self.putx([20, [str(self.escape_edges)]]) + def handle_tapc_state(self, tck, tms): + self.oldcjtagstate = self.cjtagstate + + if self.escape_edges >= 8: + self.cjtagstate = '4-WIRE' + if self.escape_edges == 6 : #| self.escape_edges == 7: + self.cjtagstate = 'CJTAG-OAC' + self.oacp = 0 + self.oaclen = 12 + + self.escape_edges = 0 + + def decode(self): + tdi_real = 0 + tms_real = 0 + tdo_real = 0 + + while True: + # Wait for a rising edge on TCK. + + (tdi, tdo, tck, tms, trst, srst, rtck) = self.wait({2: 'r'}) + self.handle_tapc_state(tck, tms) + + if(self.cjtagstate == 'OSCAN1'): + if(self.oscan1cycle == 0): #nTDI + if(tms == 0): + tdi_real = 1 + else: + tdi_real = 0 + self.oscan1cycle = 1 + elif(self.oscan1cycle == 1): #TMS + tms_real = tms + self.oscan1cycle = 2 + elif(self.oscan1cycle == 2): #TDO + tdo_real = tms + self.handle_rising_tck_edge(tdi_real, tdo_real, tck, tms_real, trst, srst, rtck) + self.oscan1cycle = 0 + else: + self.handle_rising_tck_edge(tdi, tdo, tck, tms, trst, srst, rtck) + + while (tck == 1): + (tdi, tdo, tck, tms_n, trst, srst, rtck) = self.wait([{2: 'f'}, {3: 'e'}]) + if(tms_n != tms): + tms = tms_n + self.handle_tms_edge(tck, tms) + + + diff --git a/libsigrokdecode4DSL/decoders/common/__init__.py b/libsigrokdecode4DSL/decoders/common/__init__.py new file mode 100644 index 00000000..2a0beb50 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/common/__init__.py @@ -0,0 +1,19 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2016 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + diff --git a/libsigrokdecode4DSL/decoders/common/plugtrx/__init__.py b/libsigrokdecode4DSL/decoders/common/plugtrx/__init__.py new file mode 100644 index 00000000..8dd0822b --- /dev/null +++ b/libsigrokdecode4DSL/decoders/common/plugtrx/__init__.py @@ -0,0 +1,20 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2016 Bert Vermeulen +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 3 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +from .mod import * diff --git a/libsigrokdecode4DSL/decoders/common/plugtrx/mod.py b/libsigrokdecode4DSL/decoders/common/plugtrx/mod.py new file mode 100644 index 00000000..3d1b66dd --- /dev/null +++ b/libsigrokdecode4DSL/decoders/common/plugtrx/mod.py @@ -0,0 +1,192 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2016 Bert Vermeulen +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 3 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +# This module contains definitions for use by pluggable network adapters, +# such as SFP, XFP etc. + +MODULE_ID = { + 0x01: 'GBIC', + 0x02: 'Integrated module/connector', + 0x03: 'SFP', + 0x04: '300-pin XBI', + 0x05: 'XENPAK', + 0x06: 'XFP', + 0x07: 'XFF', + 0x08: 'XFP-E', + 0x09: 'XPAK', + 0x0a: 'X2', +} + +ALARM_THRESHOLDS = { + 0: 'Temp high alarm', + 2: 'Temp low alarm', + 4: 'Temp high warning', + 6: 'Temp low warning', + 16: 'Bias high alarm', + 18: 'Bias low alarm', + 20: 'Bias high warning', + 22: 'Bias low warning', + 24: 'TX power high alarm', + 26: 'TX power low alarm', + 28: 'TX power high warning', + 30: 'TX power low warning', + 32: 'RX power high alarm', + 34: 'RX power low alarm', + 36: 'RX power high warning', + 38: 'RX power low warning', + 40: 'AUX 1 high alarm', + 42: 'AUX 1 low alarm', + 44: 'AUX 1 high warning', + 46: 'AUX 1 low warning', + 48: 'AUX 2 high alarm', + 50: 'AUX 2 low alarm', + 52: 'AUX 2 high warning', + 54: 'AUX 2 low warning', +} + +AD_READOUTS = { + 0: 'Module temperature', + 4: 'TX bias current', + 6: 'Measured TX output power', + 8: 'Measured RX input power', + 10: 'AUX 1 measurement', + 12: 'AUX 2 measurement', +} + +GCS_BITS = [ + 'TX disable', + 'Soft TX disable', + 'MOD_NR', + 'P_Down', + 'Soft P_Down', + 'Interrupt', + 'RX_LOS', + 'Data_Not_Ready', + 'TX_NR', + 'TX_Fault', + 'TX_CDR not locked', + 'RX_NR', + 'RX_CDR not locked', +] + +CONNECTOR = { + 0x01: 'SC', + 0x02: 'Fibre Channel style 1 copper', + 0x03: 'Fibre Channel style 2 copper', + 0x04: 'BNC/TNC', + 0x05: 'Fibre Channel coax', + 0x06: 'FiberJack', + 0x07: 'LC', + 0x08: 'MT-RJ', + 0x09: 'MU', + 0x0a: 'SG', + 0x0b: 'Optical pigtail', + 0x20: 'HSSDC II', + 0x21: 'Copper pigtail', +} + +TRANSCEIVER = [ + # 10GB Ethernet + ['10GBASE-SR', '10GBASE-LR', '10GBASE-ER', '10GBASE-LRM', '10GBASE-SW', + '10GBASE-LW', '10GBASE-EW'], + # 10GB Fibre Channel + ['1200-MX-SN-I', '1200-SM-LL-L', 'Extended Reach 1550 nm', + 'Intermediate reach 1300 nm FP'], + # 10GB Copper + [], + # 10GB low speed + ['1000BASE-SX / 1xFC MMF', '1000BASE-LX / 1xFC SMF', '2xFC MMF', + '2xFC SMF', 'OC48-SR', 'OC48-IR', 'OC48-LR'], + # 10GB SONET/SDH interconnect + ['I-64.1r', 'I-64.1', 'I-64.2r', 'I-64.2', 'I-64.3', 'I-64.5'], + # 10GB SONET/SDH short haul + ['S-64.1', 'S-64.2a', 'S-64.2b', 'S-64.3a', 'S-64.3b', 'S-64.5a', 'S-64.5b'], + # 10GB SONET/SDH long haul + ['L-64.1', 'L-64.2a', 'L-64.2b', 'L-64.2c', 'L-64.3', 'G.959.1 P1L1-2D2'], + # 10GB SONET/SDH very long haul + ['V-64.2a', 'V-64.2b', 'V-64.3'], +] + +SERIAL_ENCODING = [ + '64B/66B', + '8B/10B', + 'SONET scrambled', + 'NRZ', + 'RZ', +] + +XMIT_TECH = [ + '850 nm VCSEL', + '1310 nm VCSEL', + '1550 nm VCSEL', + '1310 nm FP', + '1310 nm DFB', + '1550 nm DFB', + '1310 nm EML' + '1550 nm EML' + 'copper', +] + +CDR = [ + '9.95Gb/s', + '10.3Gb/s', + '10.5Gb/s', + '10.7Gb/s', + '11.1Gb/s', + '(unknown)', + 'lineside loopback mode', + 'XFI loopback mode', +] + +DEVICE_TECH = [ + ['no wavelength control', 'sctive wavelength control'], + ['uncooled transmitter device', 'cooled transmitter'], + ['PIN detector', 'APD detector'], + ['transmitter not tunable', 'transmitter tunable'], +] + +ENHANCED_OPTS = [ + 'VPS', + 'soft TX_DISABLE', + 'soft P_Down', + 'VPS LV regulator mode', + 'VPS bypassed regulator mode', + 'active FEC control', + 'wavelength tunability', + 'CMU', +] + +AUX_TYPES = [ + 'not implemented', + 'APD bias voltage', + '(unknown)', + 'TEC current', + 'laser temperature', + 'laser wavelength', + '5V supply voltage', + '3.3V supply voltage', + '1.8V supply voltage', + '-5.2V supply voltage', + '5V supply current', + '(unknown)', + '(unknown)', + '3.3V supply current', + '1.8V supply current', + '-5.2V supply current', +] diff --git a/libsigrokdecode4DSL/decoders/common/sdcard/__init__.py b/libsigrokdecode4DSL/decoders/common/sdcard/__init__.py new file mode 100644 index 00000000..fb323856 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/common/sdcard/__init__.py @@ -0,0 +1,20 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2012-2014 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +from .mod import * diff --git a/libsigrokdecode4DSL/decoders/common/sdcard/mod.py b/libsigrokdecode4DSL/decoders/common/sdcard/mod.py new file mode 100644 index 00000000..2ef33238 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/common/sdcard/mod.py @@ -0,0 +1,151 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2012-2014 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +# Normal commands (CMD) +# Unlisted items are 'Reserved' as per SD spec. The 'Unknown' items don't +# seem to be mentioned in the spec, but aren't marked as reserved either. +cmd_names = { + 0: 'GO_IDLE_STATE', + 1: 'SEND_OP_COND', # Reserved in SD mode + 2: 'ALL_SEND_CID', + 3: 'SEND_RELATIVE_ADDR', + 4: 'SET_DSR', + 5: 'IO_SEND_OP_COND', # SDIO-only + 6: 'SWITCH_FUNC', # New since spec 1.10 + 7: 'SELECT/DESELECT_CARD', + 8: 'SEND_IF_COND', + 9: 'SEND_CSD', + 10: 'SEND_CID', + 11: 'VOLTAGE_SWITCH', + 12: 'STOP_TRANSMISSION', + 13: 'SEND_STATUS', + # 14: Reserved + 15: 'GO_INACTIVE_STATE', + 16: 'SET_BLOCKLEN', + 17: 'READ_SINGLE_BLOCK', + 18: 'READ_MULTIPLE_BLOCK', + 19: 'SEND_TUNING_BLOCK', + 20: 'SPEED_CLASS_CONTROL', + # 21-22: Reserved + 23: 'SET_BLOCK_COUNT', + 24: 'WRITE_BLOCK', + 25: 'WRITE_MULTIPLE_BLOCK', + 26: 'Reserved for manufacturer', + 27: 'PROGRAM_CSD', + 28: 'SET_WRITE_PROT', + 29: 'CLR_WRITE_PROT', + 30: 'SEND_WRITE_PROT', + # 31: Reserved + 32: 'ERASE_WR_BLK_START', # SPI mode: ERASE_WR_BLK_START_ADDR + 33: 'ERASE_WR_BLK_END', # SPI mode: ERASE_WR_BLK_END_ADDR + 34: 'Reserved for CMD6', # New since spec 1.10 + 35: 'Reserved for CMD6', # New since spec 1.10 + 36: 'Reserved for CMD6', # New since spec 1.10 + 37: 'Reserved for CMD6', # New since spec 1.10 + 38: 'ERASE', + # 39: Reserved + 40: 'Reserved for security specification', + # 41: Reserved + 42: 'LOCK_UNLOCK', + # 43-49: Reserved + 50: 'Reserved for CMD6', # New since spec 1.10 + # 51: Reserved + 52: 'IO_RW_DIRECT', # SDIO-only + 53: 'IO_RW_EXTENDED', # SDIO-only + 54: 'Unknown', + 55: 'APP_CMD', + 56: 'GEN_CMD', + 57: 'Reserved for CMD6', # New since spec 1.10 + 58: 'READ_OCR', # Reserved in SD mode + 59: 'CRC_ON_OFF', # Reserved in SD mode + 60: 'Reserved for manufacturer', + 61: 'Reserved for manufacturer', + 62: 'Reserved for manufacturer', + 63: 'Reserved for manufacturer', +} + +# Application-specific commands (ACMD) +# Unlisted items are 'Reserved' as per SD spec. The 'Unknown' items don't +# seem to be mentioned in the spec, but aren't marked as reserved either. +acmd_names = { + # 1-5: Reserved + 6: 'SET_BUS_WIDTH', + # 7-12: Reserved + 13: 'SD_STATUS', + 14: 'Reserved for Security Application', + 15: 'Reserved for Security Application', + 16: 'Reserved for Security Application', + # 17: Reserved + 18: 'Reserved for SD security applications', + # 19-21: Reserved + 22: 'SEND_NUM_WR_BLOCKS', + 23: 'SET_WR_BLK_ERASE_COUNT', + # 24: Reserved + 25: 'Reserved for SD security applications', + 26: 'Reserved for SD security applications', + 27: 'Reserved for security specification', + 28: 'Reserved for security specification', + # 29: Reserved + 30: 'Reserved for security specification', + 31: 'Reserved for security specification', + 32: 'Reserved for security specification', + 33: 'Reserved for security specification', + 34: 'Reserved for security specification', + 35: 'Reserved for security specification', + # 36-37: Reserved + 38: 'Reserved for SD security applications', + # 39-40: Reserved + 41: 'SD_SEND_OP_COND', + 42: 'SET_CLR_CARD_DETECT', + 43: 'Reserved for SD security applications', + 44: 'Reserved for SD security applications', + 45: 'Reserved for SD security applications', + 46: 'Reserved for SD security applications', + 47: 'Reserved for SD security applications', + 48: 'Reserved for SD security applications', + 49: 'Reserved for SD security applications', + 50: 'Unknown', + 51: 'SEND_SCR', + 52: 'Reserved for security specification', + 53: 'Reserved for security specification', + 54: 'Reserved for security specification', + 55: 'Non-existant', # Doesn't exist (equivalent to CMD55) + 56: 'Reserved for security specification', + 57: 'Reserved for security specification', + 58: 'Reserved for security specification', + 59: 'Reserved for security specification', + 60: 'Unknown', + 61: 'Unknown', + 62: 'Unknown', + 63: 'Unknown', +} + +accepted_voltages = { + 0b0001: '2.7-3.6V', + 0b0010: 'reserved for low voltage range', + 0b0100: 'reserved', + 0b1000: 'reserved', + # All other values: "not defined". +} + +sd_status = { + # 311:0: Reserved for manufacturer + # 391:312: Reserved +} + diff --git a/libsigrokdecode4DSL/decoders/common/srdhelper/__init__.py b/libsigrokdecode4DSL/decoders/common/srdhelper/__init__.py new file mode 100644 index 00000000..fb323856 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/common/srdhelper/__init__.py @@ -0,0 +1,20 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2012-2014 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +from .mod import * diff --git a/libsigrokdecode4DSL/decoders/common/srdhelper/mod.py b/libsigrokdecode4DSL/decoders/common/srdhelper/mod.py new file mode 100644 index 00000000..6c45af98 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/common/srdhelper/mod.py @@ -0,0 +1,84 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2012-2020 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +from enum import Enum, IntEnum, unique +from itertools import chain +import re + +# Return the specified BCD number (max. 8 bits) as integer. +def bcd2int(b): + return (b & 0x0f) + ((b >> 4) * 10) + +def bin2int(s: str): + return int('0b' + s, 2) + +def bitpack(bits): + return sum([b << i for i, b in enumerate(bits)]) + +def bitunpack(num, minbits=0): + res = [] + while num or minbits > 0: + res.append(num & 1) + num >>= 1 + minbits -= 1 + return tuple(res) + +@unique +class SrdStrEnum(Enum): + @classmethod + def from_list(cls, name, l): + # Keys are limited/converted to [A-Z0-9_], values can be any string. + items = [(re.sub('[^A-Z0-9_]', '_', l[i]), l[i]) for i in range(len(l))] + return cls(name, items) + + @classmethod + def from_str(cls, name, s): + return cls.from_list(name, s.split()) + +@unique +class SrdIntEnum(IntEnum): + @classmethod + def _prefix(cls, p): + return tuple([a.value for a in cls if a.name.startswith(p)]) + + @classmethod + def prefixes(cls, prefix_list): + if isinstance(prefix_list, str): + prefix_list = prefix_list.split() + return tuple(chain(*[cls._prefix(p) for p in prefix_list])) + + @classmethod + def _suffix(cls, s): + return tuple([a.value for a in cls if a.name.endswith(s)]) + + @classmethod + def suffixes(cls, suffix_list): + if isinstance(suffix_list, str): + suffix_list = suffix_list.split() + return tuple(chain(*[cls._suffix(s) for s in suffix_list])) + + @classmethod + def from_list(cls, name, l): + # Manually construct (Python 3.4 is missing the 'start' argument). + # Python defaults to start=1, but we want start=0. + return cls(name, [(l[i], i) for i in range(len(l))]) + + @classmethod + def from_str(cls, name, s): + return cls.from_list(name, s.split()) diff --git a/libsigrokdecode4DSL/decoders/counter/__init__.py b/libsigrokdecode4DSL/decoders/counter/__init__.py new file mode 100644 index 00000000..505148dd --- /dev/null +++ b/libsigrokdecode4DSL/decoders/counter/__init__.py @@ -0,0 +1,28 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2018 Stefan Brüns +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This decoder is a simple edge counter. + +It can count rising and/or falling edges, provides an optional reset +signal. It can also divide the count to e.g. count the number of +fixed-length words (where a word corresponds to e.g. 9 clock edges). +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/counter/pd.py b/libsigrokdecode4DSL/decoders/counter/pd.py new file mode 100644 index 00000000..b0b1af71 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/counter/pd.py @@ -0,0 +1,145 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2018 Stefan Brüns +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd + +PIN_DATA, PIN_RESET = range(2) +ROW_EDGE, ROW_WORD, ROW_RESET = range(3) + +class Decoder(srd.Decoder): + api_version = 3 + id = 'counter' + name = 'Counter' + longname = 'Edge counter' + desc = 'Count the number of edges in a signal.' + license = 'gplv2+' + inputs = ['logic'] + outputs = [] + tags = ['Util'] + channels = ( + {'id': 'data', 'name': 'Data', 'desc': 'Data line'}, + ) + optional_channels = ( + {'id': 'reset', 'name': 'Reset', 'desc': 'Reset line'}, + ) + annotations = ( + ('edge_count', 'Edge count'), + ('word_count', 'Word count'), + ('word_reset', 'Word reset'), + ) + annotation_rows = ( + ('edge_counts', 'Edges', (ROW_EDGE,)), + ('word_counts', 'Words', (ROW_WORD,)), + ('word_resets', 'Word resets', (ROW_RESET,)), + ) + options = ( + {'id': 'data_edge', 'desc': 'Edges to count (data)', 'default': 'any', + 'values': ('any', 'rising', 'falling')}, + {'id': 'divider', 'desc': 'Count divider (word width)', 'default': 0}, + {'id': 'reset_edge', 'desc': 'Edge which clears counters (reset)', + 'default': 'falling', 'values': ('rising', 'falling')}, + {'id': 'edge_off', 'desc': 'Edge counter value after start/reset', 'default': 0}, + {'id': 'word_off', 'desc': 'Word counter value after start/reset', 'default': 0}, + {'id': 'dead_cycles', 'desc': 'Ignore this many edges after reset', 'default': 0}, + {'id': 'start_with_reset', 'desc': 'Assume decode starts with reset', + 'default': 'no', 'values': ('no', 'yes')}, + ) + + def __init__(self): + self.reset() + + def reset(self): + pass + + def metadata(self, key, value): + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def putc(self, cls, ss, annlist): + self.put(ss, self.samplenum, self.out_ann, [cls, annlist]) + + def decode(self): + opt_edge_map = {'rising': 'r', 'falling': 'f', 'any': 'e'} + + data_edge = self.options['data_edge'] + divider = self.options['divider'] + if divider < 0: + divider = 0 + reset_edge = self.options['reset_edge'] + + condition = [{PIN_DATA: opt_edge_map[data_edge]}] + have_reset = self.has_channel(PIN_RESET) + if have_reset: + cond_reset = len(condition) + condition.append({PIN_RESET: opt_edge_map[reset_edge]}) + + edge_count = int(self.options['edge_off']) + edge_start = None + word_count = int(self.options['word_off']) + word_start = None + + if self.options['start_with_reset'] == 'yes': + dead_count = int(self.options['dead_cycles']) + else: + dead_count = 0 + + while True: + self.wait(condition) + now = self.samplenum + + if have_reset and (self.matched & (0b1 < +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program. If not, see . +## + +''' +DALI is a biphase/manchester based lighting control protocol. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/dali/lists.py b/libsigrokdecode4DSL/decoders/dali/lists.py new file mode 100644 index 00000000..e9d3a4ba --- /dev/null +++ b/libsigrokdecode4DSL/decoders/dali/lists.py @@ -0,0 +1,98 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2015 Jeremy Swanson +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program. If not, see . +## + +# DALI extended commands +extended_commands = { + 0xA1: ['Terminate special processes', 'Terminate'], + 0xA3: ['DTR = DATA', 'DTR'], + 0xA5: ['INITIALISE', 'INIT'], + 0xA7: ['RANDOMISE', 'RAND'], + 0xA9: ['COMPARE', 'COMP'], + 0xAB: ['WITHDRAW', 'WDRAW'], + 0xB1: ['SET SEARCH H', 'SAH'], + 0xB3: ['SET SEARCH M', 'SAM'], + 0xB5: ['SET SEARCH L', 'SAL'], + 0xB7: ['Program Short Address', 'ProgSA'], + 0xB9: ['Verify Short Address', 'VfySA'], + 0xBB: ['Query Short Address', 'QryShort'], + 0xBD: ['Physical Selection', 'PysSel'], + 0xC1: ['Enable Device Type X', 'EnTyp'], + 0xC3: ['DTR1 = DATA', 'DTR1'], + 0xC5: ['DTR2 = DATA', 'DTR2'], + 0xC7: ['Write Memory Location', 'WRI'], +} + +# List of commands +dali_commands = { + 0x00: ['Immediate Off', 'IOFF'], + 0x01: ['Up 200ms', 'Up'], + 0x02: ['Down 200ms', 'Down'], + 0x03: ['Step Up', 'Step+'], + 0x04: ['Step Down', 'Step-'], + 0x05: ['Recall Maximum Level', 'Recall Max'], + 0x06: ['Recall Minimum Level', 'Recall Min'], + 0x07: ['Step down and off', 'Down Off'], + 0x08: ['Step ON and UP', 'On Up'], + 0x20: ['Reset', 'Rst'], + 0x21: ['Store Dim Level in DTR', 'Level -> DTR'], + 0x2A: ['Store DTR as Max Level', 'DTR->Max'], + 0x2B: ['Store DTR as Min Level', 'DTR->Min'], + 0x2C: ['Store DTR as Fail Level', 'DTR->Fail'], + 0x2D: ['Store DTR as Power On Level', 'DTR->Poweron'], + 0x2E: ['Store DTR as Fade Time', 'DTR->Fade'], + 0x2F: ['Store DTR as Fade Rate', 'DTR->Rate'], + 0x80: ['Store DTR as Short Address', 'DTR->Add'], + 0x81: ['Enable Memory Write', 'WEn'], + 0x90: ['Query Status', 'Status'], + 0x91: ['Query Ballast', 'Ballast'], + 0x92: ['Query Lamp Failure', 'LmpFail'], + 0x93: ['Query Power On', 'Power On'], + 0x94: ['Query Limit Error', 'Limit Err'], + 0x95: ['Query Reset', 'Reset State'], + 0x96: ['Query Missing Short Address', 'NoSrt'], + 0x97: ['Query Version', 'Ver'], + 0x98: ['Query DTR', 'GetDTR'], + 0x99: ['Query Device Type', 'Type'], + 0x9A: ['Query Physical Minimum', 'PhysMin'], + 0x9B: ['Query Power Fail', 'PowerFailed'], + 0x9C: ['Query DTR1', 'GetDTR1'], + 0x9D: ['Query DTR2', 'GetDTR2'], + 0xA0: ['Query Level', 'GetLevel'], + 0xA1: ['Query Max Level', 'GetMax'], + 0xA2: ['Query Min Level', 'GetMin'], + 0xA3: ['Query Power On', 'GetPwrOn'], + 0xA4: ['Query Fail Level', 'GetFail'], + 0xA5: ['Query Fade Rate', 'GetRate'], + 0xA6: ['Query Power Fail', 'PwrFail'], + 0xC0: ['Query Groups 0-7', 'GetGrpsL'], + 0xC1: ['Query Groups 7-15', 'GetGrpsH'], + 0xC2: ['Query BRNH', 'BRNH'], + 0xC3: ['Query BRNM', 'BRNM'], + 0xC4: ['Query BRNL', 'BRNL'], + 0xC5: ['Query Memory', 'GetMem'], +} + +# DALI device type 8 +dali_device_type8 = { + 0xE0: ['Set Temp X-Y Coordinate', 'Set X-Y'], + 0xE2: ['Activate Colour Set point', 'Activate SetPoint'], + 0xE7: ['Set Colour Temperature Tc', 'DTRs->ColTemp'], + 0xF9: ['Query Features', 'QryFeats'], + 0xFA: ['Query Current Setpoint Colour', 'GetSetPoint'], +} diff --git a/libsigrokdecode4DSL/decoders/dali/pd.py b/libsigrokdecode4DSL/decoders/dali/pd.py new file mode 100644 index 00000000..53147463 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/dali/pd.py @@ -0,0 +1,245 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2015 Jeremy Swanson +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program. If not, see . +## + +import sigrokdecode as srd +from .lists import * + +class SamplerateError(Exception): + pass + +class Decoder(srd.Decoder): + api_version = 3 + id = 'dali' + name = 'DALI' + longname = 'Digital Addressable Lighting Interface' + desc = 'Digital Addressable Lighting Interface (DALI) protocol.' + license = 'gplv2+' + inputs = ['logic'] + outputs = [] + tags = ['Embedded/industrial', 'Lighting'] + channels = ( + {'id': 'dali', 'name': 'DALI', 'desc': 'DALI data line'}, + ) + options = ( + {'id': 'polarity', 'desc': 'Polarity', 'default': 'active-low', + 'values': ('active-low', 'active-high')}, + ) + annotations = ( + ('bit', 'Bit'), + ('startbit', 'Startbit'), + ('sbit', 'Select bit'), + ('ybit', 'Individual or group'), + ('address', 'Address'), + ('command', 'Command'), + ('reply', 'Reply data'), + ('raw', 'Raw data'), + ) + annotation_rows = ( + ('bits', 'Bits', (0,)), + ('raw', 'Raw data', (7,)), + ('fields', 'Fields', (1, 2, 3, 4, 5, 6)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.samplerate = None + self.samplenum = None + self.edges, self.bits, self.ss_es_bits = [], [], [] + self.state = 'IDLE' + self.dev_type = None + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + self.old_dali = 1 if self.options['polarity'] == 'active-low' else 0 + + def metadata(self, key, value): + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + # One bit: 833.33us (one half low, one half high). + # This is how may samples are in 1TE. + self.halfbit = int((self.samplerate * 0.0008333) / 2.0) + + def putb(self, bit1, bit2, data): + ss, es = self.ss_es_bits[bit1][0], self.ss_es_bits[bit2][1] + self.put(ss, es, self.out_ann, data) + + def handle_bits(self, length): + a, c, f, g, b = 0, 0, 0, 0, self.bits + # Individual raw bits. + for i in range(length): + if i == 0: + ss = max(0, self.bits[0][0]) + else: + ss = self.ss_es_bits[i - 1][1] + es = self.bits[i][0] + (self.halfbit * 2) + self.ss_es_bits.append([ss, es]) + self.putb(i, i, [0, ['%d' % self.bits[i][1]]]) + # Bits[0:0]: Startbit + s = ['Startbit: %d' % b[0][1], 'ST: %d' % b[0][1], 'ST', 'S', 'S'] + self.putb(0, 0, [1, s]) + self.putb(0, 0, [7, s]) + # Bits[1:8] + for i in range(8): + f |= (b[1 + i][1] << (7 - i)) + if length == 9: # BACKWARD Frame + s = ['Reply: %02X' % f, 'Rply: %02X' % f, + 'Rep: %02X' % f, 'R: %02X' % f, 'R'] + self.putb(1, 8, [7, s]) + s = ['Reply: %d' % f, 'Rply: %d' % f, + 'Rep: %d' % f, 'R: %d' % f, 'R'] + self.putb(1, 8, [6, s]) + return + + # FORWARD FRAME + # Bits[9:16]: Command/data (MSB-first) + for i in range(8): + c |= (b[9 + i][1] << (7 - i)) + # Raw output + s = ['Raw data: %02X' % f, 'Raw: %02X' % f, + 'Raw: %02X' % f, 'R: %02X' % f, 'R'] + self.putb(1, 8, [7, s]) + s = ['Raw data: %02X' % c, 'Raw: %02X' % c, + 'Raw: %02X' % c, 'R: %02X' % c, 'R'] + self.putb(9, 16, [7, s]) + + # Bits[8:8]: Select bit + # s = ['Selectbit: %d' % b[8][1], 'SEL: %d' % b[8][1], 'SEL', 'SE', 'S'] + if b[8][1] == 1: + s = ['Command', 'Comd', 'COM', 'CO', 'C'] + else: + s = ['Arc Power Level', 'Arc Pwr', 'ARC', 'AC', 'A'] + self.putb(8, 8, [1, s]) + + # f &= 254 # Clear the select bit. + if f >= 254: # BROADCAST + s = ['BROADCAST', 'Brdcast', 'BC', 'B', 'B'] + self.putb(1, 7, [5, s]) + elif f >= 160: # Extended command 0b10100000 + if f == 0xC1: # DALI_ENABLE_DEVICE_TYPE_X + self.dev_type = -1 + x = extended_commands.get(f, ['Unknown', 'Unk']) + s = ['Extended Command: %02X (%s)' % (f, x[0]), + 'XC: %02X (%s)' % (f, x[1]), + 'XC: %02X' % f, 'X: %02X' % f, 'X'] + self.putb(1, 8, [5, s]) + elif f >= 128: # Group + # Bits[1:1]: Ybit + s = ['YBit: %d' % b[1][1], 'YB: %d' % b[1][1], 'YB', 'Y', 'Y'] + self.putb(1, 1, [3, s]) + g = (f & 127) >> 1 + s = ['Group address: %d' % g, 'Group: %d' % g, + 'GP: %d' % g, 'G: %d' % g, 'G'] + self.putb(2,7, [4, s]) + else: # Short address + # Bits[1:1]: Ybit + s = ['YBit: %d' % b[1][1], 'YB: %d' % b[1][1], 'YB', 'Y', 'Y'] + self.putb(1, 1, [3, s]) + a = f >> 1 + s = ['Short address: %d' % a, 'Addr: %d' % a, + 'Addr: %d' % a, 'A: %d' % a, 'A'] + self.putb(2, 7, [4, s]) + + # Bits[9:16]: Command/data (MSB-first) + if f >= 160 and f < 254: + if self.dev_type == -1: + self.dev_type = c + s = ['Type: %d' % c, 'Typ: %d' % c, + 'Typ: %d' % c, 'T: %d' % c, 'D'] + else: + self.dev_type = None + s = ['Data: %d' % c, 'Dat: %d' % c, + 'Dat: %d' % c, 'D: %d' % c, 'D'] + elif b[8][1] == 1: + un = c & 0xF0 + ln = c & 0x0F + if un == 0x10: # Set scene command + x = ['Recall Scene %d' % ln, 'SC %d' % ln] + elif un == 0x40: + x = ['Store DTR as Scene %d' % ln, 'SC %d = DTR' % ln] + elif un == 0x50: + x = ['Delete Scene %d' % ln, 'DEL SC %d' % ln] + elif un == 0x60: + x = ['Add to Group %d' % ln, 'Grp %d Add' % ln] + elif un == 0x70: + x = ['Remove from Group %d' % ln, 'Grp %d Del' % ln] + elif un == 0xB0: + x = ['Query Scene %d Level' % ln, 'Sc %d Level' % ln] + elif c >= 224: # Application specific commands + if self.dev_type == 8: + x = dali_device_type8.get(c, ['Unknown App', 'Unk']) + else: + x = ['Application Specific Command %d' % c, 'App Cmd %d' % c] + else: + x = dali_commands.get(c, ['Unknown', 'Unk']) + s = ['Command: %d (%s)' % (c, x[0]), 'Com: %d (%s)' % (c, x[1]), + 'Com: %d' % c, 'C: %d' % c, 'C'] + else: + s = ['Arc Power Level: %d' % c, 'Level: %d' % c, + 'Lev: %d' % c, 'L: %d' % c, 'L'] + self.putb(9, 16, [5, s]) + + def reset_decoder_state(self): + self.edges, self.bits, self.ss_es_bits = [], [], [] + self.state = 'IDLE' + + def decode(self): + if not self.samplerate: + raise SamplerateError('Cannot decode without samplerate.') + bit = 0 + while True: + # TODO: Come up with more appropriate self.wait() conditions. + (dali,) = self.wait() + if self.options['polarity'] == 'active-high': + dali ^= 1 # Invert. + + # State machine. + if self.state == 'IDLE': + # Wait for any edge (rising or falling). + if self.old_dali == dali: + continue + self.edges.append(self.samplenum) + self.state = 'PHASE0' + self.old_dali = dali + continue + + if self.old_dali != dali: + self.edges.append(self.samplenum) + elif self.samplenum == (self.edges[-1] + int(self.halfbit * 1.5)): + self.edges.append(self.samplenum - int(self.halfbit * 0.5)) + else: + continue + + bit = self.old_dali + if self.state == 'PHASE0': + self.phase0 = bit + self.state = 'PHASE1' + elif self.state == 'PHASE1': + if (bit == 1) and (self.phase0 == 1): # Stop bit. + if len(self.bits) == 17 or len(self.bits) == 9: + # Forward or Backward. + self.handle_bits(len(self.bits)) + self.reset_decoder_state() # Reset upon errors. + continue + else: + self.bits.append([self.edges[-3], bit]) + self.state = 'PHASE0' + + self.old_dali = dali diff --git a/libsigrokdecode4DSL/decoders/dcf77/__init__.py b/libsigrokdecode4DSL/decoders/dcf77/__init__.py new file mode 100644 index 00000000..caadcff8 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/dcf77/__init__.py @@ -0,0 +1,28 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2012 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This PD decodes the DCF77 protocol (a European long-wave time signal that +uses a 77.5kHz carrier frequency). + +Details: +http://en.wikipedia.org/wiki/DCF77 +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/dcf77/pd.py b/libsigrokdecode4DSL/decoders/dcf77/pd.py new file mode 100644 index 00000000..7365134e --- /dev/null +++ b/libsigrokdecode4DSL/decoders/dcf77/pd.py @@ -0,0 +1,311 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2012-2016 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd +import calendar +from common.srdhelper import bcd2int + +class SamplerateError(Exception): + pass + +class Decoder(srd.Decoder): + api_version = 3 + id = 'dcf77' + name = 'DCF77' + longname = 'DCF77 time protocol' + desc = 'European longwave time signal (77.5kHz carrier signal).' + license = 'gplv2+' + inputs = ['logic'] + outputs = [] + tags = ['Clock/timing'] + channels = ( + {'id': 'data', 'name': 'DATA', 'desc': 'DATA line'}, + ) + annotations = ( + ('start-of-minute', 'Start of minute'), + ('special-bits', 'Special bits (civil warnings, weather forecast)'), + ('call-bit', 'Call bit'), + ('summer-time', 'Summer time announcement'), + ('cest', 'CEST bit'), + ('cet', 'CET bit'), + ('leap-second', 'Leap second bit'), + ('start-of-time', 'Start of encoded time'), + ('minute', 'Minute'), + ('minute-parity', 'Minute parity bit'), + ('hour', 'Hour'), + ('hour-parity', 'Hour parity bit'), + ('day', 'Day of month'), + ('day-of-week', 'Day of week'), + ('month', 'Month'), + ('year', 'Year'), + ('date-parity', 'Date parity bit'), + ('raw-bits', 'Raw bits'), + ('unknown-bits', 'Unknown bits'), + ('warnings', 'Human-readable warnings'), + ) + annotation_rows = ( + ('bits', 'Bits', (17, 18)), + ('fields', 'Fields', tuple(range(0, 16 + 1))), + ('warnings', 'Warnings', (19,)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.samplerate = None + self.state = 'WAIT FOR RISING EDGE' + self.ss_bit = self.ss_bit_old = self.es_bit = self.ss_block = 0 + self.datebits = [] + self.bitcount = 0 # Counter for the DCF77 bits (0..58) + self.dcf77_bitnumber_is_known = 0 + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def metadata(self, key, value): + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + + def putx(self, data): + # Annotation for a single DCF77 bit. + self.put(self.ss_bit, self.es_bit, self.out_ann, data) + + def putb(self, data): + # Annotation for a multi-bit DCF77 field. + self.put(self.ss_block, self.samplenum, self.out_ann, data) + + # TODO: Which range to use? Only the 100ms/200ms or full second? + def handle_dcf77_bit(self, bit): + c = self.bitcount + + # Create one annotation for each DCF77 bit (containing the 0/1 value). + # Use 'Unknown DCF77 bit x: val' if we're not sure yet which of the + # 0..58 bits it is (because we haven't seen a 'new minute' marker yet). + # Otherwise, use 'DCF77 bit x: val'. + s = 'B' if self.dcf77_bitnumber_is_known else 'Unknown b' + ann = 17 if self.dcf77_bitnumber_is_known else 18 + self.putx([ann, ['%sit %d: %d' % (s, c, bit), '%d' % bit]]) + + # If we're not sure yet which of the 0..58 DCF77 bits we have, return. + # We don't want to decode bogus data. + if not self.dcf77_bitnumber_is_known: + return + + # Collect bits 36-58, we'll need them for a parity check later. + if c in range(36, 58 + 1): + self.datebits.append(bit) + + # Output specific "decoded" annotations for the respective DCF77 bits. + if c == 0: + # Start of minute: DCF bit 0. + if bit == 0: + self.putx([0, ['Start of minute (always 0)', + 'Start of minute', 'SoM']]) + else: + self.putx([19, ['Start of minute != 0', 'SoM != 0']]) + elif c in range(1, 14 + 1): + # Special bits (civil warnings, weather forecast): DCF77 bits 1-14. + if c == 1: + self.tmp = bit + self.ss_block = self.ss_bit + else: + self.tmp |= (bit << (c - 1)) + if c == 14: + s = '{:014b}'.format(self.tmp) + self.putb([1, ['Special bits: %s' % s, 'SB: %s' % s]]) + elif c == 15: + s = '' if (bit == 1) else 'not ' + self.putx([2, ['Call bit: %sset' % s, 'CB: %sset' % s]]) + # TODO: Previously this bit indicated use of the backup antenna. + elif c == 16: + s = '' if (bit == 1) else 'not ' + x = 'yes' if (bit == 1) else 'no' + self.putx([3, ['Summer time announcement: %sactive' % s, + 'Summer time: %sactive' % s, + 'Summer time: %s' % x, 'ST: %s' % x]]) + elif c == 17: + s = '' if (bit == 1) else 'not ' + x = 'yes' if (bit == 1) else 'no' + self.putx([4, ['CEST: %sin effect' % s, 'CEST: %s' % x]]) + elif c == 18: + s = '' if (bit == 1) else 'not ' + x = 'yes' if (bit == 1) else 'no' + self.putx([5, ['CET: %sin effect' % s, 'CET: %s' % x]]) + elif c == 19: + s = '' if (bit == 1) else 'not ' + x = 'yes' if (bit == 1) else 'no' + self.putx([6, ['Leap second announcement: %sactive' % s, + 'Leap second: %sactive' % s, + 'Leap second: %s' % x, 'LS: %s' % x]]) + elif c == 20: + # Start of encoded time: DCF bit 20. + if bit == 1: + self.putx([7, ['Start of encoded time (always 1)', + 'Start of encoded time', 'SoeT']]) + else: + self.putx([19, ['Start of encoded time != 1', 'SoeT != 1']]) + elif c in range(21, 27 + 1): + # Minutes (0-59): DCF77 bits 21-27 (BCD format). + if c == 21: + self.tmp = bit + self.ss_block = self.ss_bit + else: + self.tmp |= (bit << (c - 21)) + if c == 27: + m = bcd2int(self.tmp) + self.putb([8, ['Minutes: %d' % m, 'Min: %d' % m]]) + elif c == 28: + # Even parity over minute bits (21-28): DCF77 bit 28. + self.tmp |= (bit << (c - 21)) + parity = bin(self.tmp).count('1') + s = 'OK' if ((parity % 2) == 0) else 'INVALID!' + self.putx([9, ['Minute parity: %s' % s, 'Min parity: %s' % s]]) + elif c in range(29, 34 + 1): + # Hours (0-23): DCF77 bits 29-34 (BCD format). + if c == 29: + self.tmp = bit + self.ss_block = self.ss_bit + else: + self.tmp |= (bit << (c - 29)) + if c == 34: + self.putb([10, ['Hours: %d' % bcd2int(self.tmp)]]) + elif c == 35: + # Even parity over hour bits (29-35): DCF77 bit 35. + self.tmp |= (bit << (c - 29)) + parity = bin(self.tmp).count('1') + s = 'OK' if ((parity % 2) == 0) else 'INVALID!' + self.putx([11, ['Hour parity: %s' % s]]) + elif c in range(36, 41 + 1): + # Day of month (1-31): DCF77 bits 36-41 (BCD format). + if c == 36: + self.tmp = bit + self.ss_block = self.ss_bit + else: + self.tmp |= (bit << (c - 36)) + if c == 41: + self.putb([12, ['Day: %d' % bcd2int(self.tmp)]]) + elif c in range(42, 44 + 1): + # Day of week (1-7): DCF77 bits 42-44 (BCD format). + # A value of 1 means Monday, 7 means Sunday. + if c == 42: + self.tmp = bit + self.ss_block = self.ss_bit + else: + self.tmp |= (bit << (c - 42)) + if c == 44: + d = bcd2int(self.tmp) + try: + dn = calendar.day_name[d - 1] # day_name[0] == Monday + self.putb([13, ['Day of week: %d (%s)' % (d, dn), + 'DoW: %d (%s)' % (d, dn)]]) + except IndexError: + self.putb([19, ['Day of week: %d (%s)' % (d, 'invalid'), + 'DoW: %d (%s)' % (d, 'inv')]]) + elif c in range(45, 49 + 1): + # Month (1-12): DCF77 bits 45-49 (BCD format). + if c == 45: + self.tmp = bit + self.ss_block = self.ss_bit + else: + self.tmp |= (bit << (c - 45)) + if c == 49: + m = bcd2int(self.tmp) + try: + mn = calendar.month_name[m] # month_name[1] == January + self.putb([14, ['Month: %d (%s)' % (m, mn), + 'Mon: %d (%s)' % (m, mn)]]) + except IndexError: + self.putb([19, ['Month: %d (%s)' % (m, 'invalid'), + 'Mon: %d (%s)' % (m, 'inv')]]) + elif c in range(50, 57 + 1): + # Year (0-99): DCF77 bits 50-57 (BCD format). + if c == 50: + self.tmp = bit + self.ss_block = self.ss_bit + else: + self.tmp |= (bit << (c - 50)) + if c == 57: + self.putb([15, ['Year: %d' % bcd2int(self.tmp)]]) + elif c == 58: + # Even parity over date bits (36-58): DCF77 bit 58. + parity = self.datebits.count(1) + s = 'OK' if ((parity % 2) == 0) else 'INVALID!' + self.putx([16, ['Date parity: %s' % s, 'DP: %s' % s]]) + self.datebits = [] + else: + self.putx([19, ['Invalid DCF77 bit: %d' % c, + 'Invalid bit: %d' % c, 'Inv: %d' % c]]) + + def decode(self): + if not self.samplerate: + raise SamplerateError('Cannot decode without samplerate.') + while True: + if self.state == 'WAIT FOR RISING EDGE': + # Wait until the next rising edge occurs. + self.wait({0: 'r'}) + + # Save the sample number where the DCF77 bit begins. + self.ss_bit = self.samplenum + + # Calculate the length (in ms) between two rising edges. + len_edges = self.ss_bit - self.ss_bit_old + len_edges_ms = int((len_edges / self.samplerate) * 1000) + + # The time between two rising edges is usually around 1000ms. + # For DCF77 bit 59, there is no rising edge at all, i.e. the + # time between DCF77 bit 59 and DCF77 bit 0 (of the next + # minute) is around 2000ms. Thus, if we see an edge with a + # 2000ms distance to the last one, this edge marks the + # beginning of a new minute (and DCF77 bit 0 of that minute). + if len_edges_ms in range(1600, 2400 + 1): + self.bitcount = 0 + self.ss_bit_old = self.ss_bit + self.dcf77_bitnumber_is_known = 1 + + self.ss_bit_old = self.ss_bit + self.state = 'GET BIT' + + elif self.state == 'GET BIT': + # Wait until the next falling edge occurs. + self.wait({0: 'f'}) + + # Save the sample number where the DCF77 bit ends. + self.es_bit = self.samplenum + + # Calculate the length (in ms) of the current high period. + len_high = self.samplenum - self.ss_bit + len_high_ms = int((len_high / self.samplerate) * 1000) + + # If the high signal was 100ms long, that encodes a 0 bit. + # If it was 200ms long, that encodes a 1 bit. + if len_high_ms in range(40, 160 + 1): + bit = 0 + elif len_high_ms in range(161, 260 + 1): + bit = 1 + else: + bit = -1 + + if bit in (0, 1): + self.handle_dcf77_bit(bit) + self.bitcount += 1 + else: + self.putx([19, ['Invalid bit timing', 'Inv timing', 'Inv']]) + + self.state = 'WAIT FOR RISING EDGE' diff --git a/libsigrokdecode4DSL/decoders/dmx512/__init__.py b/libsigrokdecode4DSL/decoders/dmx512/__init__.py new file mode 100644 index 00000000..b5e57836 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/dmx512/__init__.py @@ -0,0 +1,25 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2016 Fabian J. Stumpf +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +DMX512 (Digital MultipleX 512) is a protocol based on RS485, used to control +professional lighting fixtures. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/dmx512/pd.py b/libsigrokdecode4DSL/decoders/dmx512/pd.py new file mode 100644 index 00000000..355095ee --- /dev/null +++ b/libsigrokdecode4DSL/decoders/dmx512/pd.py @@ -0,0 +1,179 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2016 Fabian J. Stumpf +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd + +class Decoder(srd.Decoder): + api_version = 3 + id = 'dmx512' + name = 'DMX512' + longname = 'Digital MultipleX 512' + desc = 'Digital MultipleX 512 (DMX512) lighting protocol.' + license = 'gplv2+' + inputs = ['logic'] + outputs = [] + tags = ['Embedded/industrial', 'Lighting'] + channels = ( + {'id': 'dmx', 'name': 'DMX data', 'desc': 'Any DMX data line'}, + ) + options = ( + {'id': 'invert', 'desc': 'Invert Signal?', 'default': 'no', + 'values': ('yes', 'no')}, + ) + annotations = ( + ('bit', 'Bit'), + ('break', 'Break'), + ('mab', 'Mark after break'), + ('startbit', 'Start bit'), + ('stopbits', 'Stop bit'), + ('startcode', 'Start code'), + ('channel', 'Channel'), + ('interframe', 'Interframe'), + ('interpacket', 'Interpacket'), + ('data', 'Data'), + ('error', 'Error'), + ) + annotation_rows = ( + ('name', 'Logical', (1, 2, 5, 6, 7, 8)), + ('data', 'Data', (9,)), + ('bits', 'Bits', (0, 3, 4)), + ('errors', 'Errors', (10,)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.samplerate = None + self.sample_usec = None + self.run_start = -1 + self.state = 'FIND BREAK' + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def metadata(self, key, value): + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + self.sample_usec = 1 / value * 1000000 + self.skip_per_bit = int(4 / self.sample_usec) + + def putr(self, data): + self.put(self.run_start, self.samplenum, self.out_ann, data) + + def decode(self): + if not self.samplerate: + raise SamplerateError('Cannot decode without samplerate.') + + inv = self.options['invert'] == 'yes' + + (dmx,) = self.wait({0: 'h' if inv else 'l'}) + self.run_start = self.samplenum + + while True: + # Seek for an interval with no state change with a length between + # 88 and 1000000 us (BREAK). + if self.state == 'FIND BREAK': + (dmx,) = self.wait({0: 'f' if inv else 'r'}) + runlen = (self.samplenum - self.run_start) * self.sample_usec + if runlen > 88 and runlen < 1000000: + self.putr([1, ['Break']]) + self.state = 'MARK MAB' + self.channel = 0 + elif runlen >= 1000000: + # Error condition. + self.putr([10, ['Invalid break length']]) + else: + (dmx,) = self.wait({0: 'h' if inv else 'l'}) + self.run_start = self.samplenum + # Directly following the BREAK is the MARK AFTER BREAK. + elif self.state == 'MARK MAB': + self.run_start = self.samplenum + (dmx,) = self.wait({0: 'r' if inv else 'f'}) + self.putr([2, ['MAB']]) + self.state = 'READ BYTE' + self.channel = 0 + self.bit = 0 + self.aggreg = dmx + self.run_start = self.samplenum + # Mark and read a single transmitted byte + # (start bit, 8 data bits, 2 stop bits). + elif self.state == 'READ BYTE': + bit_start = self.samplenum + bit_end = self.run_start + (self.bit + 1) * self.skip_per_bit + (dmx,) = self.wait({'skip': round(self.skip_per_bit/2)}) + bit_value = not dmx if inv else dmx + + if self.bit == 0: + self.byte = 0 + self.put(bit_start, bit_end, + self.out_ann, [3, ['Start bit']]) + if bit_value != 0: + # (Possibly) invalid start bit, mark but don't fail. + self.put(bit_start, bit_end, + self.out_ann, [10, ['Invalid start bit']]) + elif self.bit >= 9: + self.put(bit_start, bit_end, + self.out_ann, [4, ['Stop bit']]) + if bit_value != 1: + # Invalid stop bit, mark. + self.put(bit_start, bit_end, + self.out_ann, [10, ['Invalid stop bit']]) + if self.bit == 10: + # On invalid 2nd stop bit, search for new break. + self.state = 'FIND BREAK' + else: + # Label and process one bit. + self.put(bit_start, bit_end, + self.out_ann, [0, [str(bit_value)]]) + self.byte |= bit_value << (self.bit - 1) + + # Label a complete byte. + if self.state == 'READ BYTE' and self.bit == 10: + if self.channel == 0: + d = [5, ['Start code']] + else: + d = [6, ['Channel ' + str(self.channel)]] + self.put(self.run_start, bit_end, self.out_ann, d) + self.put(self.run_start + self.skip_per_bit, + bit_end - 2 * self.skip_per_bit, + self.out_ann, [9, [str(self.byte) + ' / ' + \ + str(hex(self.byte))]]) + # Continue by scanning the IFT. + self.channel += 1 + self.run_start = self.samplenum + self.state = 'MARK IFT' + + self.bit += 1 + (dmx,) = self.wait({'skip': round(bit_end - self.samplenum)}) + # Mark the INTERFRAME-TIME between bytes / INTERPACKET-TIME between packets. + elif self.state == 'MARK IFT': + self.run_start = self.samplenum + if self.channel > 512: + (dmx,) = self.wait({0: 'h' if inv else 'l'}) + self.putr([8, ['Interpacket']]) + self.state = 'FIND BREAK' + self.run_start = self.samplenum + else: + if (not dmx if inv else dmx): + (dmx,) = self.wait({0: 'h' if inv else 'l'}) + self.putr([7, ['Interframe']]) + self.state = 'READ BYTE' + self.bit = 0 + self.run_start = self.samplenum diff --git a/libsigrokdecode4DSL/decoders/ds1307/__init__.py b/libsigrokdecode4DSL/decoders/ds1307/__init__.py new file mode 100644 index 00000000..faf4ce68 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/ds1307/__init__.py @@ -0,0 +1,25 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2013 Matt Ranostay +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This decoder stacks on top of the 'i2c' PD and decodes the Dallas DS1307 +real-time clock (RTC) specific registers and commands. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/ds1307/pd.py b/libsigrokdecode4DSL/decoders/ds1307/pd.py new file mode 100644 index 00000000..f8ebe195 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/ds1307/pd.py @@ -0,0 +1,264 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2012-2014 Uwe Hermann +## Copyright (C) 2013 Matt Ranostay +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import re +import sigrokdecode as srd +from common.srdhelper import bcd2int + +days_of_week = ( + 'Sunday', 'Monday', 'Tuesday', 'Wednesday', + 'Thursday', 'Friday', 'Saturday', +) + +regs = ( + 'Seconds', 'Minutes', 'Hours', 'Day', 'Date', 'Month', 'Year', + 'Control', 'RAM', +) + +bits = ( + 'Clock halt', 'Seconds', 'Reserved', 'Minutes', '12/24 hours', 'AM/PM', + 'Hours', 'Day', 'Date', 'Month', 'Year', 'OUT', 'SQWE', 'RS', 'RAM', +) + +rates = { + 0b00: '1Hz', + 0b01: '4096Hz', + 0b10: '8192Hz', + 0b11: '32768Hz', +} + +DS1307_I2C_ADDRESS = 0x68 + +def regs_and_bits(): + l = [('reg-' + r.lower(), r + ' register') for r in regs] + l += [('bit-' + re.sub('\/| ', '-', b).lower(), b + ' bit') for b in bits] + return tuple(l) + +class Decoder(srd.Decoder): + api_version = 3 + id = 'ds1307' + name = 'DS1307' + longname = 'Dallas DS1307' + desc = 'Dallas DS1307 realtime clock module protocol.' + license = 'gplv2+' + inputs = ['i2c'] + outputs = [] + tags = ['Clock/timing', 'IC'] + annotations = regs_and_bits() + ( + ('read-datetime', 'Read date/time'), + ('write-datetime', 'Write date/time'), + ('reg-read', 'Register read'), + ('reg-write', 'Register write'), + ('warnings', 'Warnings'), + ) + annotation_rows = ( + ('bits', 'Bits', tuple(range(9, 24))), + ('regs', 'Registers', tuple(range(9))), + ('date-time', 'Date/time', (24, 25, 26, 27)), + ('warnings', 'Warnings', (28,)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.state = 'IDLE' + self.hours = -1 + self.minutes = -1 + self.seconds = -1 + self.days = -1 + self.date = -1 + self.months = -1 + self.years = -1 + self.bits = [] + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def putx(self, data): + self.put(self.ss, self.es, self.out_ann, data) + + def putd(self, bit1, bit2, data): + self.put(self.bits[bit1][1], self.bits[bit2][2], self.out_ann, data) + + def putr(self, bit): + self.put(self.bits[bit][1], self.bits[bit][2], self.out_ann, + [11, ['Reserved bit', 'Reserved', 'Rsvd', 'R']]) + + def handle_reg_0x00(self, b): # Seconds (0-59) / Clock halt bit + self.putd(7, 0, [0, ['Seconds', 'Sec', 'S']]) + ch = 1 if (b & (1 << 7)) else 0 + self.putd(7, 7, [9, ['Clock halt: %d' % ch, 'Clk hlt: %d' % ch, + 'CH: %d' % ch, 'CH']]) + s = self.seconds = bcd2int(b & 0x7f) + self.putd(6, 0, [10, ['Second: %d' % s, 'Sec: %d' % s, 'S: %d' % s, 'S']]) + + def handle_reg_0x01(self, b): # Minutes (0-59) + self.putd(7, 0, [1, ['Minutes', 'Min', 'M']]) + self.putr(7) + m = self.minutes = bcd2int(b & 0x7f) + self.putd(6, 0, [12, ['Minute: %d' % m, 'Min: %d' % m, 'M: %d' % m, 'M']]) + + def handle_reg_0x02(self, b): # Hours (1-12+AM/PM or 0-23) + self.putd(7, 0, [2, ['Hours', 'H']]) + self.putr(7) + ampm_mode = True if (b & (1 << 6)) else False + if ampm_mode: + self.putd(6, 6, [13, ['12-hour mode', '12h mode', '12h']]) + a = 'PM' if (b & (1 << 5)) else 'AM' + self.putd(5, 5, [14, [a, a[0]]]) + h = self.hours = bcd2int(b & 0x1f) + self.putd(4, 0, [15, ['Hour: %d' % h, 'H: %d' % h, 'H']]) + else: + self.putd(6, 6, [13, ['24-hour mode', '24h mode', '24h']]) + h = self.hours = bcd2int(b & 0x3f) + self.putd(5, 0, [15, ['Hour: %d' % h, 'H: %d' % h, 'H']]) + + def handle_reg_0x03(self, b): # Day / day of week (1-7) + self.putd(7, 0, [3, ['Day of week', 'Day', 'D']]) + for i in (7, 6, 5, 4, 3): + self.putr(i) + w = self.days = bcd2int(b & 0x07) + ws = days_of_week[self.days - 1] + self.putd(2, 0, [16, ['Weekday: %s' % ws, 'WD: %s' % ws, 'WD', 'W']]) + + def handle_reg_0x04(self, b): # Date (1-31) + self.putd(7, 0, [4, ['Date', 'D']]) + for i in (7, 6): + self.putr(i) + d = self.date = bcd2int(b & 0x3f) + self.putd(5, 0, [17, ['Date: %d' % d, 'D: %d' % d, 'D']]) + + def handle_reg_0x05(self, b): # Month (1-12) + self.putd(7, 0, [5, ['Month', 'Mon', 'M']]) + for i in (7, 6, 5): + self.putr(i) + m = self.months = bcd2int(b & 0x1f) + self.putd(4, 0, [18, ['Month: %d' % m, 'Mon: %d' % m, 'M: %d' % m, 'M']]) + + def handle_reg_0x06(self, b): # Year (0-99) + self.putd(7, 0, [6, ['Year', 'Y']]) + y = self.years = bcd2int(b & 0xff) + self.years += 2000 + self.putd(7, 0, [19, ['Year: %d' % y, 'Y: %d' % y, 'Y']]) + + def handle_reg_0x07(self, b): # Control Register + self.putd(7, 0, [7, ['Control', 'Ctrl', 'C']]) + for i in (6, 5, 3, 2): + self.putr(i) + o = 1 if (b & (1 << 7)) else 0 + s = 1 if (b & (1 << 4)) else 0 + s2 = 'en' if (b & (1 << 4)) else 'dis' + r = rates[b & 0x03] + self.putd(7, 7, [20, ['Output control: %d' % o, + 'OUT: %d' % o, 'O: %d' % o, 'O']]) + self.putd(4, 4, [21, ['Square wave output: %sabled' % s2, + 'SQWE: %sabled' % s2, 'SQWE: %d' % s, 'S: %d' % s, 'S']]) + self.putd(1, 0, [22, ['Square wave output rate: %s' % r, + 'Square wave rate: %s' % r, 'SQW rate: %s' % r, 'Rate: %s' % r, + 'RS: %s' % s, 'RS', 'R']]) + + def handle_reg_0x3f(self, b): # RAM (bytes 0x08-0x3f) + self.putd(7, 0, [8, ['RAM', 'R']]) + self.putd(7, 0, [23, ['SRAM: 0x%02X' % b, '0x%02X' % b]]) + + def output_datetime(self, cls, rw): + # TODO: Handle read/write of only parts of these items. + d = '%s, %02d.%02d.%4d %02d:%02d:%02d' % ( + days_of_week[self.days - 1], self.date, self.months, + self.years, self.hours, self.minutes, self.seconds) + self.put(self.ss_block, self.es, self.out_ann, + [cls, ['%s date/time: %s' % (rw, d)]]) + + def handle_reg(self, b): + r = self.reg if self.reg < 8 else 0x3f + fn = getattr(self, 'handle_reg_0x%02x' % r) + fn(b) + # Honor address auto-increment feature of the DS1307. When the + # address reaches 0x3f, it will wrap around to address 0. + self.reg += 1 + if self.reg > 0x3f: + self.reg = 0 + + def is_correct_chip(self, addr): + if addr == DS1307_I2C_ADDRESS: + return True + self.put(self.ss_block, self.es, self.out_ann, + [28, ['Ignoring non-DS1307 data (slave 0x%02X)' % addr]]) + return False + + def decode(self, ss, es, data): + cmd, databyte = data + + # Collect the 'BITS' packet, then return. The next packet is + # guaranteed to belong to these bits we just stored. + if cmd == 'BITS': + self.bits = databyte + return + + # Store the start/end samples of this I²C packet. + self.ss, self.es = ss, es + + # State machine. + if self.state == 'IDLE': + # Wait for an I²C START condition. + if cmd != 'START': + return + self.state = 'GET SLAVE ADDR' + self.ss_block = ss + elif self.state == 'GET SLAVE ADDR': + # Wait for an address write operation. + if cmd != 'ADDRESS WRITE': + return + if not self.is_correct_chip(databyte): + self.state = 'IDLE' + return + self.state = 'GET REG ADDR' + elif self.state == 'GET REG ADDR': + # Wait for a data write (master selects the slave register). + if cmd != 'DATA WRITE': + return + self.reg = databyte + self.state = 'WRITE RTC REGS' + elif self.state == 'WRITE RTC REGS': + # If we see a Repeated Start here, it's an RTC read. + if cmd == 'START REPEAT': + self.state = 'READ RTC REGS' + return + # Otherwise: Get data bytes until a STOP condition occurs. + if cmd == 'DATA WRITE': + self.handle_reg(databyte) + elif cmd == 'STOP': + self.output_datetime(25, 'Written') + self.state = 'IDLE' + elif self.state == 'READ RTC REGS': + # Wait for an address read operation. + if cmd != 'ADDRESS READ': + return + if not self.is_correct_chip(databyte): + self.state = 'IDLE' + return + self.state = 'READ RTC REGS2' + elif self.state == 'READ RTC REGS2': + if cmd == 'DATA READ': + self.handle_reg(databyte) + elif cmd == 'STOP': + self.output_datetime(24, 'Read') + self.state = 'IDLE' diff --git a/libsigrokdecode4DSL/decoders/ds2408/__init__.py b/libsigrokdecode4DSL/decoders/ds2408/__init__.py new file mode 100644 index 00000000..b196ce97 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/ds2408/__init__.py @@ -0,0 +1,25 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2012 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This decoder stacks on top of the 'onewire_network' PD and decodes the +Maxim DS2408 1-Wire 8-channel addressable switch protocol. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/ds2408/pd.py b/libsigrokdecode4DSL/decoders/ds2408/pd.py new file mode 100644 index 00000000..33f2873f --- /dev/null +++ b/libsigrokdecode4DSL/decoders/ds2408/pd.py @@ -0,0 +1,129 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2019 Mariusz Bialonczyk +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd + +# Dictionary of FUNCTION commands and their names. +command = { + 0xf0: 'Read PIO Registers', + 0xf5: 'Channel Access Read', + 0x5a: 'Channel Access Write', + 0xcc: 'Write Conditional Search Register', + 0xc3: 'Reset Activity Latches', + 0x3c: 'Disable Test Mode', +} + +class Decoder(srd.Decoder): + api_version = 3 + id = 'ds2408' + name = 'DS2408' + longname = 'Maxim DS2408' + desc = '1-Wire 8-channel addressable switch.' + license = 'gplv2+' + inputs = ['onewire_network'] + outputs = [] + tags = ['Embedded/industrial', 'IC'] + annotations = ( + ('text', 'Human-readable text'), + ) + + def __init__(self): + self.reset() + + def reset(self): + # Bytes for function command. + self.bytes = [] + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def putx(self, data): + self.put(self.ss, self.es, self.out_ann, data) + + def decode(self, ss, es, data): + code, val = data + + if code == 'RESET/PRESENCE': + self.ss, self.es = ss, es + self.putx([0, ['Reset/presence: %s' + % ('true' if val else 'false')]]) + self.bytes = [] + elif code == 'ROM': + self.ss, self.es = ss, es + family_code = val & 0xff + self.putx([0, ['ROM: 0x%016x (family code 0x%02x)' % (val, family_code)]]) + self.bytes = [] + elif code == 'DATA': + self.bytes.append(val) + if 1 == len(self.bytes): + self.ss, self.es = ss, es + if val not in command: + self.putx([0, ['Unrecognized command: 0x%02x' % val]]) + else: + self.putx([0, ['%s (0x%02x)' % (command[val], val)]]) + elif 0xf0 == self.bytes[0]: # Read PIO Registers + if 2 == len(self.bytes): + self.ss = ss + elif 3 == len(self.bytes): + self.es = es + self.putx([0, ['Target address: 0x%04x' + % ((self.bytes[2] << 8) + self.bytes[1])]]) + elif 3 < len(self.bytes): + self.ss, self.es = ss, es + self.putx([0, ['Data: 0x%02x' % self.bytes[-1]]]) + elif 0xf5 == self.bytes[0]: # Channel Access Read + if 2 == len(self.bytes): + self.ss = ss + elif 2 < len(self.bytes): + self.ss, self.es = ss, es + self.putx([0, ['PIO sample: 0x%02x' % self.bytes[-1]]]) + elif 0x5a == self.bytes[0]: # Channel Access Write + if 2 == len(self.bytes): + self.ss = ss + elif 3 == len(self.bytes): + self.es = es + if (self.bytes[-1] == (self.bytes[-2] ^ 0xff)): + self.putx([0, ['Data: 0x%02x (bit-inversion correct: 0x%02x)' % (self.bytes[-2], self.bytes[-1])]]) + else: + self.putx([0, ['Data error: second byte (0x%02x) is not bit-inverse of first (0x%02x)' % (self.bytes[-1], self.bytes[-2])]]) + elif 3 < len(self.bytes): + self.ss, self.es = ss, es + if 0xaa == self.bytes[-1]: + self.putx([0, ['Success']]) + elif 0xff == self.bytes[-1]: + self.putx([0, ['Fail New State']]) + elif 0xcc == self.bytes[0]: # Write Conditional Search Register + if 2 == len(self.bytes): + self.ss = ss + elif 3 == len(self.bytes): + self.es = es + self.putx([0, ['Target address: 0x%04x' + % ((self.bytes[2] << 8) + self.bytes[1])]]) + elif 3 < len(self.bytes): + self.ss, self.es = ss, es + self.putx([0, ['Data: 0x%02x' % self.bytes[-1]]]) + elif 0xc3 == self.bytes[0]: # Reset Activity Latches + if 2 == len(self.bytes): + self.ss = ss + elif 2 < len(self.bytes): + self.ss, self.es = ss, es + if 0xaa == self.bytes[-1]: + self.putx([0, ['Success']]) + else: + self.putx([0, ['Invalid byte']]) diff --git a/libsigrokdecode4DSL/decoders/ds243x/__init__.py b/libsigrokdecode4DSL/decoders/ds243x/__init__.py new file mode 100644 index 00000000..c460e045 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/ds243x/__init__.py @@ -0,0 +1,25 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2017 Kevin Redon +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This decoder stacks on top of the 'onewire_network' PD and decodes the +Maxim DS243x (1-Wire EEPROM) protocol. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/ds243x/pd.py b/libsigrokdecode4DSL/decoders/ds243x/pd.py new file mode 100644 index 00000000..7f9f6660 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/ds243x/pd.py @@ -0,0 +1,270 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2017 Kevin Redon +## Copyright (C) 2017 Soeren Apel +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd + +# Dictionary of FUNCTION commands and their names. +commands_2432 = { + 0x0f: 'Write scratchpad', + 0xaa: 'Read scratchpad', + 0x55: 'Copy scratchpad', + 0xf0: 'Read memory', + 0x5a: 'Load first secret', + 0x33: 'Compute next secret', + 0xa5: 'Read authenticated page', +} + +commands_2433 = { + 0x0f: 'Write scratchpad', + 0xaa: 'Read scratchpad', + 0x55: 'Copy scratchpad', + 0xf0: 'Read memory', +} + +# Maxim DS243x family code, present at the end of the ROM code. +family_codes = { + 0x33: ('DS2432', commands_2432), + 0x23: ('DS2433', commands_2433), +} + +# Calculate the CRC-16 checksum. +# Initial value: 0x0000, xor-in: 0x0000, polynom 0x8005, xor-out: 0xffff. +def crc16(byte_array): + reverse = 0xa001 # Use the reverse polynom to make algo simpler. + crc = 0x0000 # Initial value. + # Reverse CRC calculation. + for byte in byte_array: + for bit in range(8): + if (byte ^ crc) & 1: + crc = (crc >> 1) ^ reverse + else: + crc >>= 1 + byte >>= 1 + crc ^= 0xffff # Invert CRC. + return crc + +class Decoder(srd.Decoder): + api_version = 3 + id = 'ds243x' + name = 'DS243x' + longname = 'Maxim DS2432/3' + desc = 'Maxim DS243x series 1-Wire EEPROM protocol.' + license = 'gplv2+' + inputs = ['onewire_network'] + outputs = [] + tags = ['IC', 'Memory'] + annotations = ( + ('text', 'Human-readable text'), + ) + binary = ( + ('mem_read', 'Data read from memory'), + ) + + def __init__(self): + self.reset() + + def reset(self): + # Bytes for function command. + self.bytes = [] + self.family_code = None + self.family = '' + self.commands = commands_2432 # Use max command set until we know better. + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + self.out_binary = self.register(srd.OUTPUT_BINARY) + + def putx(self, data): + self.put(self.ss, self.es, self.out_ann, data) + + def decode(self, ss, es, data): + code, val = data + + if code == 'RESET/PRESENCE': + self.ss, self.es = ss, es + self.putx([0, ['Reset/presence: %s' + % ('true' if val else 'false')]]) + self.bytes = [] + elif code == 'ROM': + self.ss, self.es = ss, es + self.family_code = val & 0xff + + s = None + if self.family_code in family_codes: + self.family, self.commands = family_codes[val & 0xff] + s = 'is 0x%02x, %s detected' % (self.family_code, self.family) + else: + s = '0x%02x unknown' % (self.family_code) + + self.putx([0, ['ROM: 0x%016x (%s)' % (val, 'family code ' + s), + 'ROM: 0x%016x (%s)' % (val, self.family)]]) + self.bytes = [] + elif code == 'DATA': + self.bytes.append(val) + if 1 == len(self.bytes): + self.ss, self.es = ss, es + if val not in self.commands: + self.putx([0, ['Unrecognized command: 0x%02x' % val]]) + else: + self.putx([0, ['Function command: %s (0x%02x)' + % (self.commands[val], val)]]) + elif 0x0f == self.bytes[0]: # Write scratchpad + if 2 == len(self.bytes): + self.ss = ss + elif 3 == len(self.bytes): + self.es = es + self.putx([0, ['Target address: 0x%04x' + % ((self.bytes[2] << 8) + self.bytes[1])]]) + elif 4 == len(self.bytes): + self.ss = ss + elif 11 == len(self.bytes): + self.es = es + self.putx([0, ['Data: ' + (','.join(format(n, '#04x') + for n in self.bytes[3:11]))]]) + elif 12 == len(self.bytes): + self.ss = ss + elif 13 == len(self.bytes): + self.es = es + self.putx([0, ['CRC: ' + + ('ok' if crc16(self.bytes[0:11]) == (self.bytes[11] + + (self.bytes[12] << 8)) else 'error')]]) + elif 0xaa == self.bytes[0]: # Read scratchpad + if 2 == len(self.bytes): + self.ss = ss + elif 3 == len(self.bytes): + self.es = es + self.putx([0, ['Target address: 0x%04x' + % ((self.bytes[2] << 8) + self.bytes[1])]]) + elif 4 == len(self.bytes): + self.ss, self.es = ss, es + self.putx([0, ['Data status (E/S): 0x%02x' + % (self.bytes[3])]]) + elif 5 == len(self.bytes): + self.ss = ss + elif 12 == len(self.bytes): + self.es = es + self.putx([0, ['Data: ' + (','.join(format(n, '#04x') + for n in self.bytes[4:12]))]]) + elif 13 == len(self.bytes): + self.ss = ss + elif 14 == len(self.bytes): + self.es = es + self.putx([0, ['CRC: ' + + ('ok' if crc16(self.bytes[0:12]) == (self.bytes[12] + + (self.bytes[13] << 8)) else 'error')]]) + elif 0x5a == self.bytes[0]: # Load first secret + if 2 == len(self.bytes): + self.ss = ss + elif 4 == len(self.bytes): + self.es = es + self.putx([0, ['Authorization pattern (TA1, TA2, E/S): ' + + (','.join(format(n, '#04x') + for n in self.bytes[1:4]))]]) + elif 4 < len(self.bytes): + self.ss, self.es = ss, es + if (0xaa == self.bytes[-1] or 0x55 == self.bytes[-1]): + self.putx([0, ['End of operation']]) + elif 0x33 == self.bytes[0]: # Compute next secret + if 2 == len(self.bytes): + self.ss = ss + elif 3 == len(self.bytes): + self.es = es + self.putx([0, ['Target address: 0x%04x' + % ((self.bytes[2] << 8) + self.bytes[1])]]) + elif 3 < len(self.bytes): + self.ss, self.es = ss, es + if (0xaa == self.bytes[-1] or 0x55 == self.bytes[-1]): + self.putx([0, ['End of operation']]) + elif 0x55 == self.bytes[0]: # Copy scratchpad + if 2 == len(self.bytes): + self.ss = ss + elif 4 == len(self.bytes): + self.es = es + self.putx([0, ['Authorization pattern (TA1, TA2, E/S): ' + + (','.join(format(n, '#04x') + for n in self.bytes[1:4]))]]) + elif 5 == len(self.bytes): + self.ss = ss + elif 24 == len(self.bytes): + self.es = es + mac = ','.join(format(n, '#04x') for n in self.bytes[4:24]) + self.putx([0, ['Message authentication code: ' + mac, + 'MAC: ' + mac]]) + elif 24 < len(self.bytes): + self.ss, self.es = ss, es + if (0xaa == self.bytes[-1] or 0x55 == self.bytes[-1]): + self.putx([0, ['Operation succeeded']]) + elif (0 == self.bytes[-1]): + self.putx([0, ['Operation failed']]) + elif 0xa5 == self.bytes[0]: # Read authenticated page + if 2 == len(self.bytes): + self.ss = ss + elif 3 == len(self.bytes): + self.es = es + self.putx([0, ['Target address: 0x%04x' + % ((self.bytes[2] << 8) + self.bytes[1])]]) + elif 4 == len(self.bytes): + self.ss = ss + elif 35 == len(self.bytes): + self.es = es + self.putx([0, ['Data: ' + (','.join(format(n, '#04x') + for n in self.bytes[3:35]))]]) + elif 36 == len(self.bytes): + self.ss, self.es = ss, es + self.putx([0, ['Padding: ' + + ('ok' if 0xff == self.bytes[-1] else 'error')]]) + elif 37 == len(self.bytes): + self.ss = ss + elif 38 == len(self.bytes): + self.es = es + self.putx([0, ['CRC: ' + + ('ok' if crc16(self.bytes[0:36]) == (self.bytes[36] + + (self.bytes[37] << 8)) else 'error')]]) + elif 39 == len(self.bytes): + self.ss = ss + elif 58 == len(self.bytes): + self.es = es + mac = ','.join(format(n, '#04x') for n in self.bytes[38:58]) + self.putx([0, ['Message authentication code: ' + mac, + 'MAC: ' + mac]]) + elif 59 == len(self.bytes): + self.ss = ss + elif 60 == len(self.bytes): + self.es = es + self.putx([0, ['MAC CRC: ' + + ('ok' if crc16(self.bytes[38:58]) == (self.bytes[58] + + (self.bytes[59] << 8)) else 'error')]]) + elif 60 < len(self.bytes): + self.ss, self.es = ss, es + if (0xaa == self.bytes[-1] or 0x55 == self.bytes[-1]): + self.putx([0, ['Operation completed']]) + elif 0xf0 == self.bytes[0]: # Read memory + if 2 == len(self.bytes): + self.ss = ss + elif 3 == len(self.bytes): + self.es = es + self.putx([0, ['Target address: 0x%04x' + % ((self.bytes[2] << 8) + self.bytes[1])]]) + elif 3 < len(self.bytes): + self.ss, self.es = ss, es + self.putx([0, ['Data: 0x%02x' % (self.bytes[-1])]]) + + bdata = self.bytes[-1].to_bytes(1, byteorder='big') + self.put(ss, es, self.out_binary, [0, bdata]) diff --git a/libsigrokdecode4DSL/decoders/ds28ea00/__init__.py b/libsigrokdecode4DSL/decoders/ds28ea00/__init__.py new file mode 100644 index 00000000..31550707 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/ds28ea00/__init__.py @@ -0,0 +1,25 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2012 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This decoder stacks on top of the 'onewire_network' PD and decodes the +Maxim DS28EA00 1-Wire digital thermometer protocol. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/ds28ea00/pd.py b/libsigrokdecode4DSL/decoders/ds28ea00/pd.py new file mode 100644 index 00000000..9a578449 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/ds28ea00/pd.py @@ -0,0 +1,93 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2012 Iztok Jeras +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd + +# Dictionary of FUNCTION commands and their names. +command = { + # Scratchpad + 0x4e: 'Write scratchpad', + 0xbe: 'Read scratchpad', + 0x48: 'Copy scratchpad', + # Thermometer + 0x44: 'Convert temperature', + 0xb4: 'Read power mode', + 0xb8: 'Recall EEPROM', + 0xf5: 'PIO access read', + 0xA5: 'PIO access write', + 0x99: 'Chain', +} + +class Decoder(srd.Decoder): + api_version = 3 + id = 'ds28ea00' + name = 'DS28EA00' + longname = 'Maxim DS28EA00 1-Wire digital thermometer' + desc = '1-Wire digital thermometer with Sequence Detect and PIO.' + license = 'gplv2+' + inputs = ['onewire_network'] + outputs = [] + tags = ['IC', 'Sensor'] + annotations = ( + ('text', 'Human-readable text'), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.trn_beg = 0 + self.trn_end = 0 + self.state = 'ROM' + self.rom = 0x0000000000000000 + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def putx(self, data): + self.put(self.ss, self.es, self.out_ann, data) + + def decode(self, ss, es, data): + code, val = data + + self.ss, self.es = ss, es + + # State machine. + if code == 'RESET/PRESENCE': + self.putx([0, ['Reset/presence: %s' + % ('true' if val else 'false')]]) + self.state = 'ROM' + elif code == 'ROM': + self.rom = val + self.putx([0, ['ROM: 0x%016x' % (val)]]) + self.state = 'COMMAND' + elif code == 'DATA': + if self.state == 'COMMAND': + if val not in command: + self.putx([0, ['Unrecognized command: 0x%02x' % val]]) + return + self.putx([0, ['Function command: 0x%02x \'%s\'' + % (val, command[val])]]) + self.state = command[val].upper() + elif self.state == 'READ SCRATCHPAD': + self.putx([0, ['Scratchpad data: 0x%02x' % val]]) + elif self.state == 'CONVERT TEMPERATURE': + self.putx([0, ['Temperature conversion status: 0x%02x' % val]]) + elif self.state in [s.upper() for s in command.values()]: + self.putx([0, ['TODO \'%s\': 0x%02x' % (self.state, val)]]) diff --git a/libsigrokdecode4DSL/decoders/dsi/__init__.py b/libsigrokdecode4DSL/decoders/dsi/__init__.py new file mode 100644 index 00000000..bfba8672 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/dsi/__init__.py @@ -0,0 +1,24 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2015 Jeremy Swanson +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program. If not, see . +## + +''' +DSI is a biphase/manchester based lighting control protocol. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/dsi/pd.py b/libsigrokdecode4DSL/decoders/dsi/pd.py new file mode 100644 index 00000000..7ce95179 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/dsi/pd.py @@ -0,0 +1,157 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2015 Jeremy Swanson +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program. If not, see . +## + +import sigrokdecode as srd + +class SamplerateError(Exception): + pass + +class Decoder(srd.Decoder): + api_version = 3 + id = 'dsi' + name = 'DSI' + longname = 'Digital Serial Interface' + desc = 'Digital Serial Interface (DSI) lighting protocol.' + license = 'gplv2+' + inputs = ['logic'] + outputs = [] + tags = ['Embedded/industrial', 'Lighting'] + channels = ( + {'id': 'dsi', 'name': 'DSI', 'desc': 'DSI data line'}, + ) + options = ( + {'id': 'polarity', 'desc': 'Polarity', 'default': 'active-high', + 'values': ('active-low', 'active-high')}, + ) + annotations = ( + ('bit', 'Bit'), + ('startbit', 'Start bit'), + ('level', 'Dimmer level'), + ('raw', 'Raw data'), + ) + annotation_rows = ( + ('bits', 'Bits', (0,)), + ('raw', 'Raw data', (3,)), + ('fields', 'Fields', (1, 2)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.samplerate = None + self.samplenum = None + self.edges, self.bits, self.ss_es_bits = [], [], [] + self.state = 'IDLE' + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + self.old_dsi = 1 if self.options['polarity'] == 'active-low' else 0 + + def metadata(self, key, value): + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + # One bit: 1666.7us (one half low, one half high). + # This is how many samples are in 1TE. + self.halfbit = int((self.samplerate * 0.0016667) / 2.0) + + def putb(self, bit1, bit2, data): + ss, es = self.ss_es_bits[bit1][0], self.ss_es_bits[bit2][1] + self.put(ss, es, self.out_ann, data) + + def handle_bits(self, length): + a, c, f, g, b = 0, 0, 0, 0, self.bits + # Individual raw bits. + for i in range(length): + if i == 0: + ss = max(0, self.bits[0][0]) + else: + ss = self.ss_es_bits[i - 1][1] + es = self.bits[i][0] + (self.halfbit * 2) + self.ss_es_bits.append([ss, es]) + self.putb(i, i, [0, ['%d' % self.bits[i][1]]]) + # Bits[0:0]: Startbit + s = ['Startbit: %d' % b[0][1], 'ST: %d' % b[0][1], 'ST', 'S', 'S'] + self.putb(0, 0, [1, s]) + self.putb(0, 0, [3, s]) + # Bits[1:8] + for i in range(8): + f |= (b[1 + i][1] << (7 - i)) + g = f / 2.55 + if length == 9: # BACKWARD Frame + s = ['Data: %02X' % f, 'Dat: %02X' % f, + 'Dat: %02X' % f, 'D: %02X' % f, 'D'] + self.putb(1, 8, [3, s]) + s = ['Level: %d%%' % g, 'Lev: %d%%' % g, + 'Lev: %d%%' % g, 'L: %d' % g, 'D'] + self.putb(1, 8, [2, s]) + return + + def reset_decoder_state(self): + self.edges, self.bits, self.ss_es_bits = [], [], [] + self.state = 'IDLE' + + def decode(self): + if not self.samplerate: + raise SamplerateError('Cannot decode without samplerate.') + bit = 0 + while True: + (self.dsi,) = self.wait() + if self.options['polarity'] == 'active-high': + self.dsi ^= 1 # Invert. + + # State machine. + if self.state == 'IDLE': + # Wait for any edge (rising or falling). + if self.old_dsi == self.dsi: + continue + # Add in the first half of the start bit. + self.edges.append(self.samplenum - int(self.halfbit)) + self.edges.append(self.samplenum) + # Start bit is 0->1. + self.phase0 = self.dsi ^ 1 + self.state = 'PHASE1' + self.old_dsi = self.dsi + # Get the next sample point. + self.old_dsi = self.dsi + continue + + if self.old_dsi != self.dsi: + self.edges.append(self.samplenum) + elif self.samplenum == (self.edges[-1] + int(self.halfbit * 1.5)): + self.edges.append(self.samplenum - int(self.halfbit * 0.5)) + else: + continue + + bit = self.old_dsi + if self.state == 'PHASE0': + self.phase0 = bit + self.state = 'PHASE1' + elif self.state == 'PHASE1': + if (bit == 1) and (self.phase0 == 1): # Stop bit. + if len(self.bits) == 17 or len(self.bits) == 9: + # Forward or Backward. + self.handle_bits(len(self.bits)) + self.reset_decoder_state() # Reset upon errors. + continue + else: + self.bits.append([self.edges[-3], bit]) + self.state = 'PHASE0' + + self.old_dsi = self.dsi diff --git a/libsigrokdecode4DSL/decoders/edid/__init__.py b/libsigrokdecode4DSL/decoders/edid/__init__.py new file mode 100644 index 00000000..256d839d --- /dev/null +++ b/libsigrokdecode4DSL/decoders/edid/__init__.py @@ -0,0 +1,35 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2012 Bert Vermeulen +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +Extended Display Identification Data (EDID) 1.3 structure decoder. + +The three-character vendor ID as specified in the EDID standard refers to +a Plug and Play ID (PNPID). The list of PNPID assignments is done by Microsoft. + +The 'pnpids.txt' file included with this protocol decoder is derived from +the list of assignments downloadable from that page. It was retrieved in +January 2012. + +Details: +https://en.wikipedia.org/wiki/Extended_display_identification_data +http://msdn.microsoft.com/en-us/windows/hardware/gg463195 +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/edid/config b/libsigrokdecode4DSL/decoders/edid/config new file mode 100644 index 00000000..ba74a8f7 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/edid/config @@ -0,0 +1 @@ +extra-install pnpids.txt diff --git a/libsigrokdecode4DSL/decoders/edid/pd.py b/libsigrokdecode4DSL/decoders/edid/pd.py new file mode 100644 index 00000000..2d7460ce --- /dev/null +++ b/libsigrokdecode4DSL/decoders/edid/pd.py @@ -0,0 +1,668 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2012 Bert Vermeulen +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 3 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +# TODO: +# - EDID < 1.3 +# - add short annotations +# - Signal level standard field in basic display parameters block +# - Additional color point descriptors +# - Additional standard timing descriptors +# - Extensions + +import sigrokdecode as srd +import os + +EDID_HEADER = [0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00] +OFF_VENDOR = 8 +OFF_VERSION = 18 +OFF_BASIC = 20 +OFF_CHROM = 25 +OFF_EST_TIMING = 35 +OFF_STD_TIMING = 38 +OFF_DET_TIMING = 54 +OFF_NUM_EXT = 126 +OFF_CHECKSUM = 127 + +# Pre-EDID established timing modes +est_modes = [ + '720x400@70Hz', + '720x400@88Hz', + '640x480@60Hz', + '640x480@67Hz', + '640x480@72Hz', + '640x480@75Hz', + '800x600@56Hz', + '800x600@60Hz', + '800x600@72Hz', + '800x600@75Hz', + '832x624@75Hz', + '1024x768@87Hz(i)', + '1024x768@60Hz', + '1024x768@70Hz', + '1024x768@75Hz', + '1280x1024@75Hz', + '1152x870@75Hz', +] + +# X:Y display aspect ratios, as used in standard timing modes +xy_ratio = [ + (16, 10), + (4, 3), + (5, 4), + (16, 9), +] + +# Annotation classes +ANN_FIELDS = 0 +ANN_SECTIONS = 1 + +class Decoder(srd.Decoder): + api_version = 3 + id = 'edid' + name = 'EDID' + longname = 'Extended Display Identification Data' + desc = 'Data structure describing display device capabilities.' + license = 'gplv3+' + inputs = ['i2c'] + outputs = [] + tags = ['Display', 'Memory', 'PC'] + annotations = ( + ('fields', 'EDID structure fields'), + ('sections', 'EDID structure sections'), + ) + annotation_rows = ( + ('sections', 'Sections', (1,)), + ('fields', 'Fields', (0,)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.state = None + # Received data items, used as an index into samplenum/data + self.cnt = 0 + # Start/end sample numbers per data item + self.sn = [] + # Received data + self.cache = [] + # Random read offset + self.offset = 0 + # Extensions + self.extension = 0 + self.ext_sn = [[]] + self.ext_cache = [[]] + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def decode(self, ss, es, data): + cmd, data = data + + if cmd == 'ADDRESS WRITE' and data == 0x50: + self.state = 'offset' + self.ss = ss + return + + if cmd == 'ADDRESS READ' and data == 0x50: + if self.extension > 0: + self.state = 'extensions' + s = str(self.extension) + t = ["Extension: " + s, "X: " + s, s] + else: + self.state = 'header' + t = ["EDID"] + self.put(ss, es, self.out_ann, [ANN_SECTIONS, t]) + return + + if cmd == 'DATA WRITE' and self.state == 'offset': + self.offset = data + self.extension = self.offset // 128 + self.cnt = self.offset % 128 + if self.extension > 0: + ext = self.extension - 1 + l = len(self.ext_sn[ext]) + # Truncate or extend to self.cnt. + self.sn = self.ext_sn[ext][0:self.cnt] + [0] * max(0, self.cnt - l) + self.cache = self.ext_cache[ext][0:self.cnt] + [0] * max(0, self.cnt - l) + else: + l = len(self.sn) + self.sn = self.sn[0:self.cnt] + [0] * max(0, self.cnt - l) + self.cache = self.cache[0:self.cnt] + [0] * max(0, self.cnt - l) + ss = self.ss if self.ss else ss + s = str(data) + t = ["Offset: " + s, "O: " + s, s] + self.put(ss, es, self.out_ann, [ANN_SECTIONS, t]) + return + + # We only care about actual data bytes that are read (for now). + if cmd != 'DATA READ': + return + + self.cnt += 1 + if self.extension > 0: + self.ext_sn[self.extension - 1].append([ss, es]) + self.ext_cache[self.extension - 1].append(data) + else: + self.sn.append([ss, es]) + self.cache.append(data) + + if self.state is None or self.state == 'header': + # Wait for the EDID header + if self.cnt >= OFF_VENDOR: + if self.cache[-8:] == EDID_HEADER: + # Throw away any garbage before the header + self.sn = self.sn[-8:] + self.cache = self.cache[-8:] + self.cnt = 8 + self.state = 'edid' + self.put(self.sn[0][0], es, self.out_ann, + [ANN_SECTIONS, ['Header']]) + self.put(self.sn[0][0], es, self.out_ann, + [ANN_FIELDS, ['Header pattern']]) + elif self.state == 'edid': + if self.cnt == OFF_VERSION: + self.decode_vid(-10) + self.decode_pid(-8) + self.decode_serial(-6) + self.decode_mfrdate(-2) + self.put(self.sn[OFF_VENDOR][0], es, self.out_ann, + [ANN_SECTIONS, ['Vendor/product']]) + elif self.cnt == OFF_BASIC: + self.put(self.sn[OFF_VERSION][0], es, self.out_ann, + [ANN_SECTIONS, ['EDID Version']]) + self.put(self.sn[OFF_VERSION][0], self.sn[OFF_VERSION][1], + self.out_ann, [ANN_FIELDS, + ['Version %d' % self.cache[-2]]]) + self.put(self.sn[OFF_VERSION+1][0], self.sn[OFF_VERSION+1][1], + self.out_ann, [ANN_FIELDS, + ['Revision %d' % self.cache[-1]]]) + elif self.cnt == OFF_CHROM: + self.put(self.sn[OFF_BASIC][0], es, self.out_ann, + [ANN_SECTIONS, ['Basic display']]) + self.decode_basicdisplay(-5) + elif self.cnt == OFF_EST_TIMING: + self.put(self.sn[OFF_CHROM][0], es, self.out_ann, + [ANN_SECTIONS, ['Color characteristics']]) + self.decode_chromaticity(-10) + elif self.cnt == OFF_STD_TIMING: + self.put(self.sn[OFF_EST_TIMING][0], es, self.out_ann, + [ANN_SECTIONS, ['Established timings']]) + self.decode_est_timing(-3) + elif self.cnt == OFF_DET_TIMING: + self.put(self.sn[OFF_STD_TIMING][0], es, self.out_ann, + [ANN_SECTIONS, ['Standard timings']]) + self.decode_std_timing(self.cnt - 16) + elif self.cnt == OFF_NUM_EXT: + self.decode_descriptors(-72) + elif self.cnt == OFF_CHECKSUM: + self.put(ss, es, self.out_ann, + [0, ['Extensions present: %d' % self.cache[self.cnt-1]]]) + elif self.cnt == OFF_CHECKSUM+1: + checksum = 0 + for i in range(128): + checksum += self.cache[i] + if checksum % 256 == 0: + csstr = 'OK' + else: + csstr = 'WRONG!' + self.put(ss, es, self.out_ann, [0, ['Checksum: %d (%s)' % ( + self.cache[self.cnt-1], csstr)]]) + self.state = 'extensions' + + elif self.state == 'extensions': + cache = self.ext_cache[self.extension - 1] + sn = self.ext_sn[self.extension - 1] + v = cache[self.cnt - 1] + if self.cnt == 1: + if v == 2: + self.put(ss, es, self.out_ann, [1, ['Extensions Tag', 'Tag']]) + else: + self.put(ss, es, self.out_ann, [1, ['Bad Tag']]) + elif self.cnt == 2: + self.put(ss, es, self.out_ann, [1, ['Version']]) + self.put(ss, es, self.out_ann, [0, [str(v)]]) + elif self.cnt == 3: + self.put(ss, es, self.out_ann, [1, ['DTD offset']]) + self.put(ss, es, self.out_ann, [0, [str(v)]]) + elif self.cnt == 4: + self.put(ss, es, self.out_ann, [1, ['Format support | DTD count']]) + support = "Underscan: {0}, {1} Audio, YCbCr: {2}".format( + "yes" if v & 0x80 else "no", + "Basic" if v & 0x40 else "No", + ["None", "422", "444", "422+444"][(v & 0x30) >> 4]) + self.put(ss, es, self.out_ann, [0, ['{0}, DTDs: {1}'.format(support, v & 0xf)]]) + elif self.cnt <= cache[2]: + if self.cnt == cache[2]: + self.put(sn[4][0], es, self.out_ann, [1, ['Data block collection']]) + self.decode_data_block_collection(cache[4:], sn[4:]) + elif (self.cnt - cache[2]) % 18 == 0: + n = (self.cnt - cache[2]) / 18 + if n <= cache[3] & 0xf: + self.put(sn[self.cnt - 18][0], es, self.out_ann, [1, ['DTD']]) + self.decode_descriptors(-18) + + elif self.cnt == 127: + dtd_last = cache[2] + (cache[3] & 0xf) * 18 + self.put(sn[dtd_last][0], es, self.out_ann, [1, ['Padding']]) + elif self.cnt == 128: + checksum = sum(cache) % 256 + self.put(ss, es, self.out_ann, [0, ['Checksum: %d (%s)' % ( + cache[self.cnt-1], 'Wrong' if checksum else 'OK')]]) + + def ann_field(self, start, end, annotation): + annotation = annotation if isinstance(annotation, list) else [annotation] + sn = self.ext_sn[self.extension - 1] if self.extension else self.sn + self.put(sn[start][0], sn[end][1], + self.out_ann, [ANN_FIELDS, annotation]) + + def lookup_pnpid(self, pnpid): + pnpid_file = os.path.join(os.path.dirname(__file__), 'pnpids.txt') + if os.path.exists(pnpid_file): + for line in open(pnpid_file).readlines(): + if line.find(pnpid + ';') == 0: + return line[4:].strip() + return '' + + def decode_vid(self, offset): + pnpid = chr(64 + ((self.cache[offset] & 0x7c) >> 2)) + pnpid += chr(64 + (((self.cache[offset] & 0x03) << 3) + | ((self.cache[offset+1] & 0xe0) >> 5))) + pnpid += chr(64 + (self.cache[offset+1] & 0x1f)) + vendor = self.lookup_pnpid(pnpid) + if vendor: + pnpid += ' (%s)' % vendor + self.ann_field(offset, offset+1, pnpid) + + def decode_pid(self, offset): + pidstr = 'Product 0x%.2x%.2x' % (self.cache[offset+1], self.cache[offset]) + self.ann_field(offset, offset+1, pidstr) + + def decode_serial(self, offset): + serialnum = (self.cache[offset+3] << 24) \ + + (self.cache[offset+2] << 16) \ + + (self.cache[offset+1] << 8) \ + + self.cache[offset] + serialstr = '' + is_alnum = True + for i in range(4): + if not chr(self.cache[offset+3-i]).isalnum(): + is_alnum = False + break + serialstr += chr(self.cache[offset+3-i]) + serial = serialstr if is_alnum else str(serialnum) + self.ann_field(offset, offset+3, 'Serial ' + serial) + + def decode_mfrdate(self, offset): + datestr = '' + if self.cache[offset]: + datestr += 'week %d, ' % self.cache[offset] + datestr += str(1990 + self.cache[offset+1]) + if datestr: + self.ann_field(offset, offset+1, ['Manufactured ' + datestr, datestr]) + + def decode_basicdisplay(self, offset): + # Video input definition + vid = self.cache[offset] + if vid & 0x80: + # Digital + self.ann_field(offset, offset, 'Video input: VESA DFP 1.') + else: + # Analog + sls = (vid & 60) >> 5 + self.ann_field(offset, offset, 'Signal level standard: %.2x' % sls) + if vid & 0x10: + self.ann_field(offset, offset, 'Blank-to-black setup expected') + syncs = '' + if vid & 0x08: + syncs += 'separate syncs, ' + if vid & 0x04: + syncs += 'composite syncs, ' + if vid & 0x02: + syncs += 'sync on green, ' + if vid & 0x01: + syncs += 'Vsync serration required, ' + if syncs: + self.ann_field(offset, offset, 'Supported syncs: %s' % syncs[:-2]) + # Max horizontal/vertical image size + if self.cache[offset+1] != 0 and self.cache[offset+2] != 0: + # Projectors have this set to 0 + sizestr = '%dx%dcm' % (self.cache[offset+1], self.cache[offset+2]) + self.ann_field(offset+1, offset+2, 'Physical size: ' + sizestr) + # Display transfer characteristic (gamma) + if self.cache[offset+3] != 0xff: + gamma = (self.cache[offset+3] + 100) / 100 + self.ann_field(offset+3, offset+3, 'Gamma: %1.2f' % gamma) + # Feature support + fs = self.cache[offset+4] + dpms = '' + if fs & 0x80: + dpms += 'standby, ' + if fs & 0x40: + dpms += 'suspend, ' + if fs & 0x20: + dpms += 'active off, ' + if dpms: + self.ann_field(offset+4, offset+4, 'DPMS support: %s' % dpms[:-2]) + dt = (fs & 0x18) >> 3 + dtstr = '' + if dt == 0: + dtstr = 'Monochrome' + elif dt == 1: + dtstr = 'RGB color' + elif dt == 2: + dtstr = 'non-RGB multicolor' + if dtstr: + self.ann_field(offset+4, offset+4, 'Display type: %s' % dtstr) + if fs & 0x04: + self.ann_field(offset+4, offset+4, 'Color space: standard sRGB') + # Save this for when we decode the first detailed timing descriptor + self.have_preferred_timing = (fs & 0x02) == 0x02 + if fs & 0x01: + gft = '' + else: + gft = 'not ' + self.ann_field(offset+4, offset+4, + 'Generalized timing formula: %ssupported' % gft) + + def convert_color(self, value): + # Convert from 10-bit packet format to float + outval = 0.0 + for i in range(10): + if value & 0x01: + outval += 2 ** -(10-i) + value >>= 1 + return outval + + def decode_chromaticity(self, offset): + redx = (self.cache[offset+2] << 2) + ((self.cache[offset] & 0xc0) >> 6) + redy = (self.cache[offset+3] << 2) + ((self.cache[offset] & 0x30) >> 4) + self.ann_field(offset, offset+9, 'Chromacity red: X %1.3f, Y %1.3f' % ( + self.convert_color(redx), self.convert_color(redy))) + + greenx = (self.cache[offset+4] << 2) + ((self.cache[offset] & 0x0c) >> 6) + greeny = (self.cache[offset+5] << 2) + ((self.cache[offset] & 0x03) >> 4) + self.ann_field(offset, offset+9, 'Chromacity green: X %1.3f, Y %1.3f' % ( + self.convert_color(greenx), self.convert_color(greeny))) + + bluex = (self.cache[offset+6] << 2) + ((self.cache[offset+1] & 0xc0) >> 6) + bluey = (self.cache[offset+7] << 2) + ((self.cache[offset+1] & 0x30) >> 4) + self.ann_field(offset, offset+9, 'Chromacity blue: X %1.3f, Y %1.3f' % ( + self.convert_color(bluex), self.convert_color(bluey))) + + whitex = (self.cache[offset+8] << 2) + ((self.cache[offset+1] & 0x0c) >> 6) + whitey = (self.cache[offset+9] << 2) + ((self.cache[offset+1] & 0x03) >> 4) + self.ann_field(offset, offset+9, 'Chromacity white: X %1.3f, Y %1.3f' % ( + self.convert_color(whitex), self.convert_color(whitey))) + + def decode_est_timing(self, offset): + # Pre-EDID modes + bitmap = (self.cache[offset] << 9) \ + + (self.cache[offset+1] << 1) \ + + ((self.cache[offset+2] & 0x80) >> 7) + modestr = '' + for i in range(17): + if bitmap & (1 << (16-i)): + modestr += est_modes[i] + ', ' + if modestr: + self.ann_field(offset, offset+2, + 'Supported established modes: %s' % modestr[:-2]) + + def decode_std_timing(self, offset): + modestr = '' + for i in range(0, 16, 2): + if self.cache[offset+i] == 0x01 and self.cache[offset+i+1] == 0x01: + # Unused field + continue + x = (self.cache[offset+i] + 31) * 8 + ratio = (self.cache[offset+i+1] & 0xc0) >> 6 + ratio_x, ratio_y = xy_ratio[ratio] + y = x / ratio_x * ratio_y + refresh = (self.cache[offset+i+1] & 0x3f) + 60 + modestr += '%dx%d@%dHz, ' % (x, y, refresh) + if modestr: + self.ann_field(offset, offset + 15, + 'Supported standard modes: %s' % modestr[:-2]) + + def decode_detailed_timing(self, cache, sn, offset, is_first): + if is_first and self.have_preferred_timing: + # Only on first detailed timing descriptor + section = 'Preferred' + else: + section = 'Detailed' + section += ' timing descriptor' + + self.put(sn[0][0], sn[17][1], + self.out_ann, [ANN_SECTIONS, [section]]) + + pixclock = float((cache[1] << 8) + cache[0]) / 100 + self.ann_field(offset, offset+1, 'Pixel clock: %.2f MHz' % pixclock) + + horiz_active = ((cache[4] & 0xf0) << 4) + cache[2] + horiz_blank = ((cache[4] & 0x0f) << 8) + cache[3] + self.ann_field(offset+2, offset+4, 'Horizontal active: %d, blanking: %d' % (horiz_active, horiz_blank)) + + vert_active = ((cache[7] & 0xf0) << 4) + cache[5] + vert_blank = ((cache[7] & 0x0f) << 8) + cache[6] + self.ann_field(offset+5, offset+7, 'Vertical active: %d, blanking: %d' % (vert_active, vert_blank)) + + horiz_sync_off = ((cache[11] & 0xc0) << 2) + cache[8] + horiz_sync_pw = ((cache[11] & 0x30) << 4) + cache[9] + vert_sync_off = ((cache[11] & 0x0c) << 2) + ((cache[10] & 0xf0) >> 4) + vert_sync_pw = ((cache[11] & 0x03) << 4) + (cache[10] & 0x0f) + + syncs = (horiz_sync_off, horiz_sync_pw, vert_sync_off, vert_sync_pw) + self.ann_field(offset+8, offset+11, [ + 'Horizontal sync offset: %d, pulse width: %d, Vertical sync offset: %d, pulse width: %d' % syncs, + 'HSync off: %d, pw: %d, VSync off: %d, pw: %d' % syncs]) + + horiz_size = ((cache[14] & 0xf0) << 4) + cache[12] + vert_size = ((cache[14] & 0x0f) << 8) + cache[13] + self.ann_field(offset+12, offset+14, 'Physical size: %dx%dmm' % (horiz_size, vert_size)) + + horiz_border = cache[15] + self.ann_field(offset+15, offset+15, 'Horizontal border: %d pixels' % horiz_border) + vert_border = cache[16] + self.ann_field(offset+16, offset+16, 'Vertical border: %d lines' % vert_border) + + features = 'Flags: ' + if cache[17] & 0x80: + features += 'interlaced, ' + stereo = (cache[17] & 0x60) >> 5 + if stereo: + if cache[17] & 0x01: + features += '2-way interleaved stereo (' + features += ['right image on even lines', + 'left image on even lines', + 'side-by-side'][stereo-1] + features += '), ' + else: + features += 'field sequential stereo (' + features += ['right image on sync=1', 'left image on sync=1', + '4-way interleaved'][stereo-1] + features += '), ' + sync = (cache[17] & 0x18) >> 3 + sync2 = (cache[17] & 0x06) >> 1 + posneg = ['negative', 'positive'] + features += 'sync type ' + if sync == 0x00: + features += 'analog composite (serrate on RGB)' + elif sync == 0x01: + features += 'bipolar analog composite (serrate on RGB)' + elif sync == 0x02: + features += 'digital composite (serrate on composite polarity ' \ + + (posneg[sync2 & 0x01]) + ')' + elif sync == 0x03: + features += 'digital separate (' + features += 'Vsync polarity ' + (posneg[(sync2 & 0x02) >> 1]) + features += ', Hsync polarity ' + (posneg[sync2 & 0x01]) + features += ')' + features += ', ' + self.ann_field(offset+17, offset+17, features[:-2]) + + def decode_descriptor(self, cache, offset): + tag = cache[3] + self.ann_field(offset, offset+1, "Flag") + self.ann_field(offset+2, offset+2, "Flag (reserved)") + self.ann_field(offset+3, offset+3, "Tag: {0:X}".format(tag)) + self.ann_field(offset+4, offset+4, "Flag") + + sn = self.ext_sn[self.extension - 1] if self.extension else self.sn + + if tag == 0xff: + # Monitor serial number + self.put(sn[offset][0], sn[offset+17][1], self.out_ann, + [ANN_SECTIONS, ['Serial number']]) + text = bytes(cache[5:][:13]).decode(encoding='cp437', errors='replace') + self.ann_field(offset+5, offset+17, text.strip()) + elif tag == 0xfe: + # Text + self.put(sn[offset][0], sn[offset+17][1], self.out_ann, + [ANN_SECTIONS, ['Text']]) + text = bytes(cache[5:][:13]).decode(encoding='cp437', errors='replace') + self.ann_field(offset+5, offset+17, text.strip()) + elif tag == 0xfc: + # Monitor name + self.put(sn[offset][0], sn[offset+17][1], self.out_ann, + [ANN_SECTIONS, ['Monitor name']]) + text = bytes(cache[5:][:13]).decode(encoding='cp437', errors='replace') + self.ann_field(offset+5, offset+17, text.strip()) + elif tag == 0xfd: + # Monitor range limits + self.put(sn[offset][0], sn[offset+17][1], self.out_ann, + [ANN_SECTIONS, ['Monitor range limits']]) + self.ann_field(offset+5, offset+5, [ + 'Minimum vertical rate: {0}Hz'.format(cache[5]), + 'VSync >= {0}Hz'.format(cache[5])]) + self.ann_field(offset+6, offset+6, [ + 'Maximum vertical rate: {0}Hz'.format(cache[6]), + 'VSync <= {0}Hz'.format(cache[6])]) + self.ann_field(offset+7, offset+7, [ + 'Minimum horizontal rate: {0}kHz'.format(cache[7]), + 'HSync >= {0}kHz'.format(cache[7])]) + self.ann_field(offset+8, offset+8, [ + 'Maximum horizontal rate: {0}kHz'.format(cache[8]), + 'HSync <= {0}kHz'.format(cache[8])]) + self.ann_field(offset+9, offset+9, [ + 'Maximum pixel clock: {0}MHz'.format(cache[9] * 10), + 'PixClk <= {0}MHz'.format(cache[9] * 10)]) + if cache[10] == 0x02: + self.ann_field(offset+10, offset+10, ['Secondary timing formula supported', '2nd GTF: yes']) + self.ann_field(offset+11, offset+17, ['GTF']) + else: + self.ann_field(offset+10, offset+10, ['Secondary timing formula unsupported', '2nd GTF: no']) + self.ann_field(offset+11, offset+17, ['Padding']) + elif tag == 0xfb: + # Additional color point data + self.put(sn[offset][0], sn[offset+17][1], self.out_ann, + [ANN_SECTIONS, ['Additional color point data']]) + elif tag == 0xfa: + # Additional standard timing definitions + self.put(sn[offset][0], sn[offset+17][1], self.out_ann, + [ANN_SECTIONS, ['Additional standard timing definitions']]) + else: + self.put(sn[offset][0], sn[offset+17][1], self.out_ann, + [ANN_SECTIONS, ['Unknown descriptor']]) + + def decode_descriptors(self, offset): + # 4 consecutive 18-byte descriptor blocks + cache = self.ext_cache[self.extension - 1] if self.extension else self.cache + sn = self.ext_sn[self.extension - 1] if self.extension else self.sn + + for i in range(offset, 0, 18): + if cache[i] != 0 or cache[i+1] != 0: + self.decode_detailed_timing(cache[i:], sn[i:], i, i == offset) + else: + if cache[i+2] == 0 or cache[i+4] == 0: + self.decode_descriptor(cache[i:], i) + + def decode_data_block(self, tag, cache, sn): + codes = { 0: ['0: Reserved'], + 1: ['1: Audio Data Block', 'Audio'], + 2: ['2: Video Data Block', 'Video'], + 3: ['3: Vendor Specific Data Block', 'VSDB'], + 4: ['4: Speacker Allocation Data Block', 'SADB'], + 5: ['5: VESA DTC Data Block', 'DTC'], + 6: ['6: Reserved'], + 7: ['7: Extended', 'Ext'] } + ext_codes = { 0: [ '0: Video Capability Data Block', 'VCDB'], + 1: [ '1: Vendor Specific Video Data Block', 'VSVDB'], + 17: ['17: Vendor Specific Audio Data Block', 'VSADB'], } + if tag < 7: + code = codes[tag] + ext_len = 0 + if tag == 1: + aformats = { 1: '1 (LPCM)' } + rates = [ '192', '176', '96', '88', '48', '44', '32' ] + + aformat = cache[1] >> 3 + sup_rates = [ i for i in range(0, 8) if (1 << i) & cache[2] ] + + data = "Format: {0} Channels: {1}".format( + aformats.get(aformat, aformat), (cache[1] & 0x7) + 1) + data += " Rates: " + " ".join(rates[6 - i] for i in sup_rates) + data += " Extra: [{0:02X}]".format(cache[3]) + + elif tag ==2: + data = "VIC: " + data += ", ".join("{0}{1}".format(v & 0x7f, + ['', ' (Native)'][v >> 7]) + for v in cache[1:]) + + elif tag ==3: + ouis = { b'\x00\x0c\x03': 'HDMI Licensing, LLC' } + oui = bytes(cache[3:0:-1]) + ouis = ouis.get(oui, None) + data = "OUI: " + " ".join('{0:02X}'.format(x) for x in oui) + data += " ({0})".format(ouis) if ouis else "" + data += ", PhyAddr: {0}.{1}.{2}.{3}".format( + cache[4] >> 4, cache[4] & 0xf, cache[5] >> 4, cache[5] & 0xf) + data += ", [" + " ".join('{0:02X}'.format(x) for x in cache[6:]) + "]" + + elif tag ==4: + speakers = [ 'FL/FR', 'LFE', 'FC', 'RL/RR', + 'RC', 'FLC/FRC', 'RLC/RRC', 'FLW/FRW', + 'FLH/FRH', 'TC', 'FCH' ] + sup_speakers = cache[1] + (cache[2] << 8) + sup_speakers = [ i for i in range(0, 8) if (1 << i) & sup_speakers ] + data = "Speakers: " + " ".join(speakers[i] for i in sup_speakers) + + else: + data = " ".join('{0:02X}'.format(x) for x in cache[1:]) + + else: + # Extended tags + ext_len = 1 + ext_code = ext_codes.get(cache[1], ['Unknown', '?']) + code = zip(codes[7], [", ", ": "], ext_code) + code = [ "".join(x) for x in code ] + data = " ".join('{0:02X}'.format(x) for x in cache[2:]) + + self.put(sn[0][0], sn[0 + ext_len][1], self.out_ann, + [ANN_FIELDS, code]) + self.put(sn[1 + ext_len][0], sn[len(cache) - 1][1], self.out_ann, + [ANN_FIELDS, [data]]) + + def decode_data_block_collection(self, cache, sn): + offset = 0 + while offset < len(cache): + length = 1 + cache[offset] & 0x1f + tag = cache[offset] >> 5 + self.decode_data_block(tag, cache[offset:offset + length], sn[offset:]) + offset += length diff --git a/libsigrokdecode4DSL/decoders/edid/pnpids.txt b/libsigrokdecode4DSL/decoders/edid/pnpids.txt new file mode 100644 index 00000000..1ced20c5 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/edid/pnpids.txt @@ -0,0 +1,2135 @@ +AAE;Anatek Electronics Inc. +AAT;Ann Arbor Technologies +ABA;ABBAHOME INC. +ABC;AboCom System Inc +ABD;Allen Bradley Company +ABE;Alcatel Bell +ABO;Alcatel Bell +ABT;Anchor Bay Technologies, Inc. +ABV;Advanced Research Technology +ACA;Ariel Corporation +ACB;Aculab Ltd +ACC;Accton Technology Corporation +ACD;AWETA BV +ACE;Actek Engineering Pty Ltd +ACG;A&R Cambridge Ltd +ACH;Archtek Telecom Corporation +ACI;Ancor Communications Inc +ACK;Acksys +ACL;Apricot Computers +ACM;Acroloop Motion Control Systems Inc +ACO;Allion Computer Inc. +ACP;Aspen Tech Inc +ACR;Acer Technologies +ACS;Altos Computer Systems +ACT;Applied Creative Technology +ACU;Acculogic +ACV;ActivCard S.A +ADA;Addi-Data GmbH +ADB;Aldebbaron +ADC;Acnhor Datacomm +ADD;Advanced Peripheral Devices Inc +ADE;Arithmos, Inc. +ADH;Aerodata Holdings Ltd +ADI;ADI Systems Inc +ADK;Adtek System Science Company Ltd +ADL;ASTRA Security Products Ltd +ADM;Ad Lib MultiMedia Inc +ADN;Analog & Digital Devices Tel. Inc +ADP;Adaptec Inc +ADR;Nasa Ames Research Center +ADS;Analog Devices Inc +ADT;Adtek +ADT;Aved Display Technologies +ADV;Advanced Micro Devices Inc +ADX;Adax Inc +AEC;Antex Electronics Corporation +AED;Advanced Electronic Designs, Inc. +AEI;Actiontec Electric Inc +AEJ;Alpha Electronics Company +AEM;ASEM S.p.A. +AEP;Aetas Peripheral International +AET;Aethra Telecomunicazioni S.r.l. +AFA;Alfa Inc +AGC;Beijing Aerospace Golden Card Electronic Engineering Co.,Ltd. +AGI;Artish Graphics Inc +AGL;Argolis +AGM;Advan Int'l Corporation +AGT;Agilent Technologies +AHC;Advantech Co., Ltd. +AIC;Arnos Insturments & Computer Systems +AIE;Altmann Industrieelektronik +AII;Amptron International Inc. +AIL;Altos India Ltd +AIM;AIMS Lab Inc +AIR;Advanced Integ. Research Inc +AIS;Alien Internet Services +AIW;Aiwa Company Ltd +AIX;ALTINEX, INC. +AJA;AJA Video Systems, Inc. +AKB;Akebia Ltd +AKE;AKAMI Electric Co.,Ltd +AKI;AKIA Corporation +AKL;AMiT Ltd +AKM;Asahi Kasei Microsystems Company Ltd +AKP;Atom Komplex Prylad +AKY;Askey Computer Corporation +ALA;Alacron Inc +ALC;Altec Corporation +ALD;In4S Inc +ALG;Realtek Semiconductor Corp. +ALH;AL Systems +ALI;Acer Labs +ALJ;Altec Lansing +ALK;Acrolink Inc +ALL;Alliance Semiconductor Corporation +ALM;Acutec Ltd. +ALN;Alana Technologies +ALO;Algolith Inc. +ALP;Alps Electric Company Ltd +ALR;Advanced Logic +ALS;Avance Logic Inc +ALS;Texas Advanced optoelectronics Solutions, Inc +ALT;Altra +ALV;AlphaView LCD +ALX;ALEXON Co.,Ltd. +AMA;Asia Microelectronic Development Inc +AMB;Ambient Technologies, Inc. +AMC;Attachmate Corporation +AMD;Amdek Corporation +AMI;American Megatrends Inc +AML;Anderson Multimedia Communications (HK) Limited +AMN;Amimon LTD. +AMP;AMP Inc +AMT;AMT International Industry +AMX;AMX LLC +ANA;Anakron +ANC;Ancot +AND;Adtran Inc +ANI;Anigma Inc +ANK;Anko Electronic Company Ltd +ANL;Analogix Semiconductor, Inc +ANO;Anorad Corporation +ANP;Andrew Network Production +ANR;ANR Ltd +ANS;Ansel Communication Company +ANT;Ace CAD Enterprise Company Ltd +ANX;Acer Netxus Inc +AOA;AOpen Inc. +AOE;Advanced Optics Electronics, Inc. +AOL;America OnLine +AOT;Alcatel +APC;American Power Conversion +APD;AppliAdata +APG;Horner Electric Inc +API;A Plus Info Corporation +APL;Aplicom Oy +APM;Applied Memory Tech +APN;Appian Tech Inc +APP;Apple Computer Inc +APR;Aprilia s.p.a. +APS;Autologic Inc +APT;Audio Processing Technology Ltd +APV;A+V Link +APX;AP Designs Ltd +ARC;Alta Research Corporation +ARE;ICET S.p.A. +ARG;Argus Electronics Co., LTD +ARI;Argosy Research Inc +ARK;Ark Logic Inc +ARL;Arlotto Comnet Inc +ARM;Arima +ARO;Poso International B.V. +ARS;Arescom Inc +ART;Corion Industrial Corporation +ASC;Ascom Strategic Technology Unit +ASD;USC Information Sciences Institute +ASE;AseV Display Labs +ASI;Ahead Systems +ASK;Ask A/S +ASL;AccuScene Corporation Ltd +ASM;ASEM S.p.A. +ASN;Asante Tech Inc +ASP;ASP Microelectronics Ltd +AST;AST Research Inc +ASU;Asuscom Network Inc +ASX;AudioScience +ASY;Rockwell Collins / Airshow Systems +ATA;Allied Telesyn International (Asia) Pte Ltd +ATC;Ably-Tech Corporation +ATD;Alpha Telecom Inc +ATE;Innovate Ltd +ATH;Athena Informatica S.R.L. +ATI;Allied Telesis KK +ATK;Allied Telesyn Int'l +ATL;Arcus Technology Ltd +ATM;ATM Ltd +ATN;Athena Smartcard Solutions Ltd. +ATO;ASTRO DESIGN, INC. +ATP;Alpha-Top Corporation +ATT;AT&T +ATV;Office Depot, Inc. +ATX;Athenix Corporation +AUI;Alps Electric Inc +AUO;DO NOT USE - AUO +AUR;Aureal Semiconductor +AUT;Autotime Corporation +AVA;Avaya Communication +AVC;Auravision Corporation +AVD;Avid Electronics Corporation +AVE;Add Value Enterpises (Asia) Pte Ltd +AVI;Nippon Avionics Co.,Ltd +AVM;AVM GmbH +AVN ;Advance Computer Corporation +AVO;Avocent Corporation +AVR;AVerMedia Information, Inc. +AVT;Avtek (Electronics) Pty Ltd +AVV;SBS Technologies (Canada), Inc. (was Avvida Systems, Inc.) +AWC;Access Works Comm Inc +AWL;Aironet Wireless Communications, Inc +AWS;Wave Systems +AXB;Adrienne Electronics Corporation +AXC;AXIOMTEK CO., LTD. +AXE;D-Link Systems Inc (used as 2nd pnpid) +AXI;American Magnetics +AXL;Axel +AXP;American Express +AXT;Axtend Technologies Inc +AXX;Axxon Computer Corporation +AXY;AXYZ Automation Services, Inc +AYD;Aydin Displays +AYR;Airlib, Inc +AZM;AZ Middelheim - Radiotherapy +AZT;Aztech Systems Ltd +BAC;Biometric Access Corporation +BAN;Banyan +BBB;an-najah university +BBH;B&Bh +BBL;Brain Boxes Limited +BCC;Beaver Computer Corporaton +BCD;Dr. Seufert GmbH +BCM;Broadcom +BCQ;Deutsche Telekom Berkom GmbH +BCS;Booria CAD/CAM systems +BDO;Brahler ICS +BDR;Blonder Tongue Labs, Inc. +BDS;Barco Display Systems +BEC;Elektro Beckhoff GmbH +BEI;Beckworth Enterprises Inc +BEK;Beko Elektronik A.S. +BEL;Beltronic Industrieelektronik GmbH +BEO;Baug & Olufsen +BFE;B.F. Engineering Corporation +BGB;Barco Graphics N.V +BGT;Budzetron Inc +BHZ;BitHeadz, Inc. +BIC;Big Island Communications +BII;Boeckeler Instruments Inc +BIL;Billion Electric Company Ltd +BIO;BioLink Technologies International, Inc. +BIT;Bit 3 Computer +BLI;Busicom +BLN;BioLink Technologies +BLP;Bloomberg L.P. +BMI;Benson Medical Instruments Company +BML;BIOMED Lab +BMS;BIOMEDISYS +BNE;Bull AB +BNK;Banksia Tech Pty Ltd +BNO;Bang & Olufsen +BNS;Boulder Nonlinear Systems +BOB;Rainy Orchard +BOE;BOE +BOI;NINGBO BOIGLE DIGITAL TECHNOLOGY CO.,LTD +BOS;BOS +BPD;Micro Solutions, Inc. +BPU;Best Power +BRA;Braemac Pty Ltd +BRC;BARC +BRG;Bridge Information Co., Ltd +BRI;Boca Research Inc +BRM;Braemar Inc +BRO;BROTHER INDUSTRIES,LTD. +BSE;Bose Corporation +BSL;Biomedical Systems Laboratory +BST;BodySound Technologies, Inc. +BTC;Bit 3 Computer +BTE;Brilliant Technology +BTF;Bitfield Oy +BTI;BusTech Inc +BUF;Yasuhiko Shirai Melco Inc +BUJ;ATI Tech Inc +BUL;Bull +BUR;Bernecker & Rainer Ind-Eletronik GmbH +BUS;BusTek +BUT;21ST CENTURY ENTERTAINMENT +BWK;Bitworks Inc. +BXE;Buxco Electronics +BYD;byd:sign corporation +CAA;Castles Automation Co., Ltd +CAC;CA & F Elettronica +CAG;CalComp +CAI;Canon Inc. +CAL;Acon +CAM;Cambridge Audio +CAN;Canopus Company Ltd +CAN;Carrera Computer Inc +CAN;CORNEA +CAR;Cardinal Company Ltd +CAS;CASIO COMPUTER CO.,LTD +CAT;Consultancy in Advanced Technology +CBI;ComputerBoards Inc +CBR;Cebra Tech A/S +CBT;Cabletime Ltd +CBX;Cybex Computer Products Corporation +CCC;C-Cube Microsystems +CCI;Cache +CCJ;CONTEC CO.,LTD. +CCL;CCL/ITRI +CCP;Capetronic USA Inc +CDC;Core Dynamics Corporation +CDD;Convergent Data Devices +CDE;Colin.de +CDG;Christie Digital Systems Inc +CDI;Concept Development Inc +CDK;Cray Communications +CDN;Codenoll Technical Corporation +CDP;CalComp +CDS;Computer Diagnostic Systems +CDT;IBM Corporation +CDV;Convergent Design Inc. +CEA;Consumer Electronics Association +CEC;Chicony Electronics Company Ltd +CED;Cambridge Electronic Design Ltd +CEF;Cefar Digital Vision +CEI;Crestron Electronics, Inc. +CEM;MEC Electronics GmbH +CEN;Centurion Technologies P/L +CEP;C-DAC +CER;Ceronix +CET;TEC CORPORATION +CFG;Atlantis +CGA;Chunghwa Picture Tubes, LTD +CGS;Chyron Corp +CHA;Chase Research PLC +CHC;Chic Technology Corp. +CHD;ChangHong Electric Co.,Ltd +CHE;Acer Inc +CHG;Sichuan Changhong Electric CO, LTD. +CHI;Chrontel Inc +CHL;Chloride-R&D +CHM;CHIC TECHNOLOGY CORP. +CHO;Sichuang Changhong Corporation +CHP;CH Products +CHS;Agentur Chairos +CHT;Chunghwa Picture Tubes,LTD. +CHY;Cherry GmbH +CIC;Comm. Intelligence Corporation +CII;Cromack Industries Inc +CIL;Citicom Infotech Private Limited +CIN;Citron GmbH +CIP;Ciprico Inc +CIR;Cirrus Logic Inc +CIS;Cisco Systems Inc +CIT;Citifax Limited +CKC;The Concept Keyboard Company Ltd +CKJ;Carina System Co., Ltd. +CLA;Clarion Company Ltd +CLD;COMMAT L.t.d. +CLE;Classe Audio +CLG;CoreLogic +CLI;Cirrus Logic Inc +CLM;CrystaLake Multimedia +CLO;Clone Computers +CLT;automated computer control systems +CLV;Clevo Company +CLX;CardLogix +CMC;CMC Ltd +CMD;Colorado MicroDisplay, Inc. +CMG;Chenming Mold Ind. Corp. +CMI;C-Media Electronics +CMM;Comtime GmbH +CMN;Chimei Innolux Corporation +CMO;Chi Mei Optoelectronics corp. +CMR;Cambridge Research Systems Ltd +CMS;CompuMaster Srl +CMX;Comex Electronics AB +CNB;American Power Conversion +CNC;Alvedon Computers Ltd +CNE;Cine-tal +CNI;Connect Int'l A/S +CNN;Canon Inc +CNT;COINT Multimedia Systems +COB;COBY Electronics Co., Ltd +COD;CODAN Pty. Ltd. +COI;Codec Inc. +COL;Rockwell Collins, Inc. +COM;Comtrol Corporation +CON;Contec Company Ltd +COO;coolux GmbH +COR;Corollary Inc +COS;CoStar Corporation +COT;Core Technology Inc +COW;Polycow Productions +CPC;Ciprico Inc +CPD;CompuAdd +CPI;Computer Peripherals Inc +CPL;Compal Electronics Inc +CPQ;Compaq Computer Company +CPT;cPATH +CPX;Powermatic Data Systems +CRC;CONRAC GmbH +CRD;Cardinal Technical Inc +CRE;Creative Labs Inc +CRI;Crio Inc. +CRL;Creative Logic   +CRN;Cornerstone Imaging +CRO;Extraordinary Technologies PTY Limited +CRQ;Cirque Corporation +CRS;Crescendo Communication Inc +CRV;Cerevo Inc. +CRX;Cyrix Corporation +CSB;Transtex SA +CSC;Crystal Semiconductor +CSD;Cresta Systems Inc +CSE;Concept Solutions & Engineering +CSI;Cabletron System Inc +CSO;California Institute of Technology +CSS;CSS Laboratories +CST;CSTI Inc +CTA;CoSystems Inc +CTC;CTC Communication Development Company Ltd +CTE;Chunghwa Telecom Co., Ltd. +CTL;Creative Technology Ltd +CTM;Computerm Corporation +CTN;Computone Products +CTP;Computer Technology Corporation +CTS;Comtec Systems Co., Ltd. +CTX;Creatix Polymedia GmbH +CUB;Cubix Corporation +CUK;Calibre UK Ltd +CVA;Covia Inc. +CVS;Clarity Visual Systems +CWR;Connectware Inc +CXT;Conexant Systems +CYB;CyberVision +CYC;Cylink Corporation +CYD;Cyclades Corporation +CYL;Cyberlabs +CYT;Cytechinfo Inc +CYV;Cyviz AS +CYW;Cyberware +CYX;Cyrix Corporation +CZE;Carl Zeiss AG +DAC;Digital Acoustics Corporation +DAE;Digatron Industrie Elektronik GmbH +DAI;DAIS SET Ltd. +DAK;Daktronics +DAL;Digital Audio Labs Inc +DAN;Danelec Marine A/S +DAS;DAVIS AS +DAT;Datel Inc +DAU;Daou Tech Inc +DAV;Davicom Semiconductor Inc +DAW;DA2 Technologies Inc +DAX;Data Apex Ltd +DBD;Diebold Inc. +DBI;DigiBoard Inc +DBK;Databook Inc +DBL;Doble Engineering Company +DBN;DB Networks Inc +DCA;Digital Communications Association +DCC;Dale Computer Corporation +DCD;Datacast LLC +DCE;dSPACE GmbH +DCI;Concepts Inc +DCL;Dynamic Controls Ltd +DCM;DCM Data Products +DCO;Dialogue Technology Corporation +DCR;Decros Ltd +DCS;Diamond Computer Systems Inc +DCT;Dancall Telecom A/S +DCV;Datatronics Technology Inc +DDA;DA2 Technologies Corporation +DDD;Danka Data Devices +DDI;Data Display AG +DDS;Barco, n.v. +DDT;Datadesk Technologies Inc +DEC;Digital Equipment Corporation +DEI;Deico Electronics +DEL;Dell Inc. +DEN;Densitron Computers Ltd +DEX;idex displays +DFI;DFI +DFK;SharkTec A/S +DGA;Digiital Arts Inc +DGC;Data General Corporation +DGI;DIGI International +DGK;DugoTech Co., LTD +DGP;Digicorp European sales S.A. +DGS;Diagsoft Inc +DGT;The Dearborn Group +DGT;Dearborn Group Technology +DHP;DH Print +DHQ;Quadram +DHT;Projectavision Inc +DIA;Diadem +DIG;Digicom S.p.A. +DII;Dataq Instruments Inc +DIM;dPict Imaging, Inc. +DIN;Daintelecom Co., Ltd +DIS;Diseda S.A. +DIT;Dragon Information Technology +DJE;Capstone Visua lProduct Development +DJP;Maygay Machines, Ltd +DKY;Datakey Inc +DLB;Dolby Laboratories Inc. +DLC;Diamond Lane Comm. Corporation +DLG;Digital-Logic GmbH +DLK;D-Link Systems Inc +DLL;Dell Inc +DLT;Digitelec Informatique Park Cadera +DMB;Digicom Systems Inc +DMC;Dune Microsystems Corporation +DMM;Dimond Multimedia Systems Inc +DMP;D&M Holdings Inc, Professional Business Company +DMS;DOME imaging systems +DMT;Distributed Management Task Force, Inc. (DMTF) +DMV;NDS Ltd +DNA;DNA Enterprises, Inc. +DNG;Apache Micro Peripherals Inc +DNI;Deterministic Networks Inc. +DNT;Dr. Neuhous Telekommunikation GmbH +DNV;DiCon +DOL;Dolman Technologies Group Inc +DOM;Dome Imaging Systems +DON;DENON, Ltd. +DOT;Dotronic Mikroelektronik GmbH +DPA;DigiTalk Pro AV +DPC;Delta Electronics Inc +DPI;DocuPoint +DPL;Digital Projection Limited +DPM;ADPM Synthesis sas +DPS;Digital Processing Systems +DPT;DPT +DPX;DpiX, Inc. +DQB;Datacube Inc +DRB;Dr. Bott KG +DRC;Data Ray Corp. +DRD;DIGITAL REFLECTION INC. +DRI;Data Race Inc +DSD;DS Multimedia Pte Ltd +DSI;Digitan Systems Inc +DSM;DSM Digital Services GmbH +DSP;Domain Technology Inc +DTA;DELTATEC +DTC;DTC Tech Corporation +DTE;Dimension Technologies, Inc. +DTI;Diversified Technology, Inc. +DTK;Dynax Electronics (HK) Ltd +DTL;e-Net Inc +DTN;Datang Telephone Co +DTO;Deutsche Thomson OHG +DTT;Design & Test Technology, Inc. +DTX;Data Translation +DUA;Dosch & Amand GmbH & Company KG +DUN;NCR Corporation +DVD;Dictaphone Corporation +DVL;Devolo AG +DVS;Digital Video System +DVT;Data Video +DWE;Daewoo Electronics Company Ltd +DXC;Digipronix Control Systems +DXL;Dextera Labs Inc +DXP;Data Expert Corporation +DXS;Signet +DYC;Dycam Inc +DYM;Dymo-CoStar Corporation +DYN;Askey Computer Corporation +DYX;Dynax Electronics (HK) Ltd +EAS;Evans and Sutherland Computer +EBH;Data Price Informatica +EBT;HUALONG TECHNOLOGY CO., LTD +ECA;Electro Cam Corp. +ECC;ESSential Comm. Corporation +ECI;Enciris Technologies +ECK;Eugene Chukhlomin Sole Proprietorship, d.b.a. +ECL;Excel Company Ltd +ECM;E-Cmos Tech Corporation +ECO;Echo Speech Corporation +ECP;Elecom Company Ltd +ECS;Elitegroup Computer Systems Company Ltd +ECT;Enciris Technologies +EDC;e.Digital Corporation +EDG;Electronic-Design GmbH +EDI;Edimax Tech. Company Ltd +EDM;EDMI +EDT;Emerging Display Technologies Corp +EEE;ET&T Technology Company Ltd +EEH;EEH Datalink GmbH +EEP;E.E.P.D. GmbH +EES;EE Solutions, Inc. +EGD;EIZO GmbH Display Technologies +EGL;Eagle Technology +EGN;Egenera, Inc. +EGO;Ergo Electronics +EHJ;Epson Research +EHN;Enhansoft +EIC;Eicon Technology Corporation +EKA;MagTek Inc. +EKC;Eastman Kodak Company +EKS;EKSEN YAZILIM +ELA;ELAD srl +ELC;Electro Scientific Ind +ELE;Elecom Company Ltd +ELG;Elmeg GmbH Kommunikationstechnik +ELI;Edsun Laboratories +ELL;Electrosonic Ltd +ELM;Elmic Systems Inc +ELO;Elo TouchSystems Inc +ELO;Tyco Electronics +ELS;ELSA GmbH +ELT;Element Labs, Inc. +ELX;Elonex PLC +EMB;Embedded computing inc ltd +EMC;eMicro Corporation +EME;EMiNE TECHNOLOGY COMPANY, LTD. +EMG;EMG Consultants Inc +EMI;Ex Machina Inc +EMU;Emulex Corporation +ENC;Eizo Nanao Corporation +END;ENIDAN Technologies Ltd +ENE;ENE Technology Inc. +ENI;Efficient Networks +ENS;Ensoniq Corporation +ENT;Enterprise Comm. & Computing Inc +EPC;Empac +EPI;Envision Peripherals, Inc +EPN;EPiCON Inc. +EPS;KEPS +EQP;Equipe Electronics Ltd. +EQX;Equinox Systems Inc +ERG;Ergo System +ERI;Ericsson Mobile Communications AB +ERN;Ericsson, Inc. +ERP;Euraplan GmbH +ERT;Escort Insturments Corporation +ESA;Elbit Systems of America +ESC;Eden Sistemas de Computacao S/A +ESD;Ensemble Designs, Inc +ESG;ELCON Systemtechnik GmbH +ESI;Extended Systems, Inc. +ESK;ES&S +ESS;ESS Technology Inc +EST;Embedded Solution Technology +ESY;E-Systems Inc +ETC;Everton Technology Company Ltd +ETD;ELAN MICROELECTRONICS CORPORATION +ETH;Etherboot Project +ETI;Eclipse Tech Inc +ETK;eTEK Labs Inc. +ETL;Evertz Microsystems Ltd. +ETS;Electronic Trade Solutions Ltd +ETT;E-Tech Inc +EUT;Ericsson Mobile Networks B.V. +EVI;eviateg GmbH +EVX;Everex +EXA;Exabyte +EXC;Excession Audio +EXI;Exide Electronics +EXN;RGB Systems, Inc. dba Extron Electronics +EXP;Data Export Corporation +EXT;Exatech Computadores & Servicos Ltda +EXX;Exxact GmbH +EXY;Exterity Ltd +EZE;EzE Technologies +EZP;Storm Technology +FAR;Farallon Computing +FBI;Interface Corporation +FCB;Furukawa Electric Company Ltd +FCG;First International Computer Ltd +FCS;Focus Enhancements, Inc. +FDC;Future Domain +FDT;Fujitsu Display Technologies Corp. +FEC;FURUNO ELECTRIC CO., LTD. +FEL;Fellowes & Questec +FEN;Fen Systems Ltd. +FER;Ferranti Int'L +FFI;Fairfield Industries +FGD;Lisa Draexlmaier GmbH +FGL;Fujitsu General Limited. +FHL;FHLP +FIC;Formosa Industrial Computing Inc +FIL;Forefront Int'l Ltd +FIN;Finecom Co., Ltd. +FIR;Chaplet Systems Inc +FIS;FLY-IT Simulators +FIT;Feature Integration Technology Inc. +FJC;Fujitsu Takamisawa Component Limited +FJS;Fujitsu Spain +FJT;F.J. Tieman BV +FLE;ADTI Media, Inc +FLI;Faroudja Laboratories +FLY;Butterfly Communications +FMA;Fast Multimedia AG +FMC;Ford Microelectronics Inc +FMI;Fujitsu Microelect Inc +FMI;Fellowes, Inc. +FML;Fujitsu Microelect Ltd +FMZ;Formoza-Altair +FNC;Fanuc LTD +FNI;Funai Electric Co., Ltd. +FOA;FOR-A Company Limited +FOS;Foss Tecator +FOX;HON HAI PRECISON IND.CO.,LTD. +FPE;Fujitsu Peripherals Ltd +FPS;Deltec Corporation +FPX;Cirel Systemes +FRC;Force Computers +FRD;Freedom Scientific BLV +FRE;Forvus Research Inc +FRI;Fibernet Research Inc +FRS;South Mountain Technologies, LTD +FSC;Future Systems Consulting KK +FSI;Fore Systems Inc +FST;Modesto PC Inc +FTC;Futuretouch Corporation +FTE;Frontline Test Equipment Inc. +FTG;FTG Data Systems +FTI;FastPoint Technologies, Inc. +FTN;Fountain Technologies Inc +FTR;Mediasonic +FUJ;Fujitsu Ltd +FUN;sisel muhendislik +FUS;Fujitsu Siemens Computers GmbH +FVC;First Virtual Corporation +FVX;C-C-C Group Plc +FWA;Attero Tech, LLC +FWR;Flat Connections Inc +FXX;Fuji Xerox +FZC;Founder Group Shenzhen Co. +FZI;FZI Forschungszentrum Informatik +GAG;Gage Applied Sciences Inc +GAL;Galil Motion Control +GAU;Gaudi Co., Ltd. +GCC;GCC Technologies Inc +GCI;Gateway Comm. Inc +GCS;Grey Cell Systems Ltd +GDC;General Datacom +GDI;G. Diehl ISDN GmbH +GDS;GDS +GDT;Vortex Computersysteme GmbH +GEF;GE Fanuc Embedded Systems +GEH;GE Intelligent Platforms - Huntsville +GEM;Gem Plus +GEN;Genesys ATE Inc +GEO;GEO Sense +GES;GES Singapore Pte Ltd +GET;Getac Technology Corporation +GFM;GFMesstechnik GmbH +GFN;Gefen Inc. +GGL;Google Inc. +GIC;General Inst. Corporation +GIM;Guillemont International +GIS;AT&T Global Info Solutions +GJN;Grand Junction Networks +GLE;AD electronics +GLM;Genesys Logic +GLS;Gadget Labs LLC +GMK;GMK Electronic Design GmbH +GML;General Information Systems +GMM;GMM Research Inc +GMN;GEMINI 2000 Ltd +GMX;GMX Inc +GND;Gennum Corporation +GNN;GN Nettest Inc +GNZ;Gunze Ltd +GRA;Graphica Computer +GRE;GOLD RAIN ENTERPRISES CORP. +GRH;Granch Ltd +GRV;Advanced Gravis +GRY;Robert Gray Company +GSB;NIPPONDENCHI CO,.LTD +GSC;General Standards Corporation +GSM;Goldstar Company Ltd +GST;Graphic SystemTechnology +GSY;Grossenbacher Systeme AG +GTC;Graphtec Corporation +GTI;Goldtouch +GTK;G-Tech Corporation +GTM;Garnet System Company Ltd +GTS;Geotest Marvin Test Systems Inc +GTT;General Touch Technology Co., Ltd. +GUD;Guntermann & Drunck GmbH +GUZ;Guzik Technical Enterprises +GVC;GVC Corporation +GVL;Global Village Communication +GWI;GW Instruments +GWY;Gateway 2000 +GZE;GUNZE Limited +HAE;Haider electronics +HAI;Haivision Systems Inc. +HAL;Halberthal +HAN;Hanchang System Corporation +HAY;Hayes Microcomputer Products Inc +HCA;DAT +HCE;Hitachi Consumer Electronics Co., Ltd +HCL;HCL America Inc +HCM;HCL Peripherals +HCP;Hitachi Computer Products Inc +HCW;Hauppauge Computer Works Inc +HDC;HardCom Elektronik & Datateknik +HDI;HD-INFO d.o.o. +HDV;Holografika kft. +HEC;Hitachi Engineering Company Ltd +HEC;Hisense Electric Co., Ltd. +HEL;Hitachi Micro Systems Europe Ltd +HER;Ascom Business Systems +HET;HETEC Datensysteme GmbH +HHC;HIRAKAWA HEWTECH CORP. +HIB;Hibino Corporation +HIC;Hitachi Information Technology Co., Ltd. +HIK;Hikom Co., Ltd. +HIL;Hilevel Technology +HIT;Hitachi America Ltd +HJI;Harris & Jeffries Inc +HKA;HONKO MFG. CO., LTD. +HKG;Josef Heim KG +HMC;Hualon Microelectric Corporation +HMK;hmk Daten-System-Technik BmbH +HMX;HUMAX Co., Ltd. +HNS;Hughes Network Systems +HOB;HOB Electronic GmbH +HOE;Hosiden Corporation +HOL;Holoeye Photonics AG +HPA;Zytor Communications +HPC;Hewlett Packard Co. +HPD;Hewlett Packard +HPI;Headplay, Inc. +HPK;HAMAMATSU PHOTONICS K.K. +HPQ;HP +HPR;H.P.R. Electronics GmbH +HRC;Hercules +HRE;Qingdao Haier Electronics Co., Ltd. +HRL;Herolab GmbH +HRS;Harris Semiconductor +HRT;HERCULES +HSC;Hagiwara Sys-Com Company Ltd +HSD;HannStar Display Corp +HSM;AT&T Microelectronics +HSP;HannStar Display Corp +HTC;Hitachi Ltd +HTI;Hampshire Company, Inc. +HTK;Holtek Microelectronics Inc +HTX;Hitex Systementwicklung GmbH +HUB;GAI-Tronics, A Hubbell Company +HUM;IMP Electronics Ltd. +HWA;Harris Canada Inc +HWC;DBA Hans Wedemeyer +HWD;Highwater Designs Ltd +HWP;Hewlett Packard +HXM;Hexium Ltd. +HYC;Hypercope Gmbh Aachen +HYD;Hydis Technologies.Co.,LTD +HYO;HYC CO., LTD. +HYP;Hyphen Ltd +HYR;Hypertec Pty Ltd +HYT;Heng Yu Technology (HK) Limited +HYV;Hynix Semiconductor +IAF;Institut f r angewandte Funksystemtechnik GmbH +IAI;Integration Associates, Inc. +IAT;IAT Germany GmbH +IBC;Integrated Business Systems +IBI;INBINE.CO.LTD +IBM;IBM Brasil +IBM;IBM France +IBP;IBP Instruments GmbH +IBR;IBR GmbH +ICA;ICA Inc +ICC;BICC Data Networks Ltd +ICD;ICD Inc +ICE;IC Ensemble +ICI;Infotek Communication Inc +ICM;Intracom SA +ICN;Sanyo Icon +ICO;Intel Corp +ICS;Integrated Circuit Systems +ICV;Inside Contactless +ICX;ICCC A/S +IDC;International Datacasting Corporation +IDE;IDE Associates +IDK;IDK Corporation +IDO;IDEO Product Development +IDP;Integrated Device Technology, Inc. +IDS;Interdigital Sistemas de Informacao +IDT;International Display Technology +IDX;IDEXX Labs +IEC;Interlace Engineering Corporation +IEE;IEE +IEI;Interlink Electronics +IFS;In Focus Systems Inc +IFT;Informtech +IFX;Infineon Technologies AG +IGC;Intergate Pty Ltd +IGM;IGM Communi +IHE;InHand Electronics +IIC;ISIC Innoscan Industrial Computers A/S +III;Intelligent Instrumentation +IIN;IINFRA Co., Ltd +IKS;Ikos Systems Inc +ILC;Image Logic Corporation +ILS;Innotech Corporation +IMA;Imagraph +IMC;IMC Networks +IMD;ImasDe Canarias S.A. +IME;Imagraph +IMG;IMAGENICS Co., Ltd. +IMI;International Microsystems Inc +IMM;Immersion Corporation +IMN;Impossible Production +IMP;Impression Products Incorporated +IMT;Inmax Technology Corporation +INC;Home Row Inc +IND;ILC +INE;Inventec Electronics (M) Sdn. Bhd. +INF;Inframetrics Inc +ING;Integraph Corporation +INI;Initio Corporation +INK;Indtek Co., Ltd. +INL;InnoLux Display Corporation +INM;InnoMedia Inc +INN;Innovent Systems, Inc. +INO;Innolab Pte Ltd +INP;Interphase Corporation +INS;Ines GmbH +INT;Interphase Corporation +inu;Inovatec S.p.A. +INV;Inviso, Inc. +INZ;Best Buy +IOA;CRE Technology Corporation +IOD;I-O Data Device Inc +IOM;Iomega +ION;Inside Out Networks +IOS;i-O Display System +IOT;I/OTech Inc +IPC;IPC Corporation +IPD;Industrial Products Design, Inc. +IPI;Intelligent Platform Management Interface (IPMI) forum (Intel, HP, NEC, Dell) +IPM;IPM Industria Politecnica Meridionale SpA +IPN;Performance Technologies +IPR;Ithaca Peripherals +IPS;IPS, Inc. (Intellectual Property Solutions, Inc.) +IPT;International Power Technologies +IPW;IPWireless, Inc +IQT;IMAGEQUEST Co., Ltd +IRD;IRdata +ISA;Symbol Technologies +ISC;Id3 Semiconductors +ISG;Insignia Solutions Inc +ISI;Interface Solutions +ISL;Isolation Systems +ISM;Image Stream Medical +ISP;IntreSource Systems Pte Ltd +ISR;INSIS Co., LTD. +ISS;ISS Inc +IST;Intersolve Technologies +ISY;International Integrated Systems,Inc.(IISI) +ITA;Itausa Export North America +ITC;Intercom Inc +ITD;Internet Technology Corporation +ITE;Integrated Tech Express Inc +ITK;ITK Telekommunikation AG +ITL;Inter-Tel +ITM;ITM inc. +ITN;The NTI Group +ITP;IT-PRO Consulting und Systemhaus GmbH +ITR;Infotronic America, Inc. +ITS;IDTECH +ITT;I&T Telecom. +ITX;integrated Technology Express Inc +IUC;ICSL +IVI;Intervoice Inc +IVM;Liyama North America +IWR;Icuiti Corporation +IWX;Intelliworxx, Inc. +IXD;Intertex Data AB +JAC;Astec Inc +JAE;Japan Aviation Electronics Industry, Limited +JAS;Janz Automationssysteme AG +JAT;Jaton Corporation +JAZ;Carrera Computer Inc (used as second pnpid) +JCE;Jace Tech Inc +JDL;Japan Digital Laboratory Co.,Ltd. +JEN;N-Vision +JET;JET POWER TECHNOLOGY CO., LTD. +JFX;Jones Futurex Inc +JGD;University College +JIC;Jaeik Information & Communication Co., Ltd. +JMT;Micro Technical Company Ltd +JPC;JPC Technology Limited +JPW;Wallis Hamilton Industries +JQE;CNet Technical Inc +JSD;JS DigiTech, Inc +JSI;Jupiter Systems, Inc. +JSK;SANKEN ELECTRIC CO., LTD +JTS;JS Motorsports +JTY;jetway security micro,inc +JUK;Janich & Klass Computertechnik GmbH +JUP;Jupiter Systems +JVC;JVC +JWD;Video International Inc. +JWL;Jewell Instruments, LLC +JWS;JWSpencer & Co. +JWY;Jetway Information Co., Ltd +KAR;Karna +KBI;Kidboard Inc +KBL;Kobil Systems GmbH +KCL;Keycorp Ltd +KDE;KDE +KDK;Kodiak Tech +KDM;Korea Data Systems Co., Ltd. +KDS;KDS USA +KEC;Kyushu Electronics Systems Inc +KEM;Kontron Embedded Modules GmbH +KES;Kesa Corporation +KEY;Key Tech Inc +KFC;SCD Tech +KFE ;Komatsu Forest +KFX;Kofax Image Products +KIS;KiSS Technology A/S +KMC;Mitsumi Company Ltd +KML;Kensington Microware Ltd +KNC;Konica corporation +KNX;Nutech Marketing PTL +KOB;Kobil Systems GmbH +KOD;Eastman Kodak Company +KOE;KOLTER ELECTRONIC +KOL;Kollmorgen Motion Technologies Group +KOW;KOWA Company,LTD. +KPC;King Phoenix Company +KRL;Krell Industries Inc. +KRM;Kroma Telecom +KRY;Kroy LLC +KSC;Kinetic Systems Corporation +KSL;Karn Solutions Ltd. +KSX;King Tester Corporation +KTC;Kingston Tech Corporation +KTD;Takahata Electronics Co.,Ltd. +KTE;K-Tech +KTG;Kayser-Threde GmbH +KTI;Konica Technical Inc +KTK;Key Tronic Corporation +KTN;Katron Tech Inc +KUR;Kurta Corporation +KVA;Kvaser AB +KWD;Kenwood Corporation +KYC;Kyocera Corporation +KYE;KYE Syst Corporation +KYK;Samsung Electronics America Inc +KZI;K-Zone International co. Ltd. +KZN;K-Zone International +LAB;ACT Labs Ltd +LAC;LaCie +LAF;Microline +LAG;Laguna Systems +LAN;Sodeman Lancom Inc +LAS;LASAT Comm. A/S +LAV;Lava Computer MFG Inc +LBO;Lubosoft +LCC;LCI +LCD;Toshiba Matsushita Display Technology Co., Ltd +LCE;La Commande Electronique +LCI;Lite-On Communication Inc +LCM;Latitude Comm. +LCN;LEXICON +LCS;Longshine Electronics Company +LCT;Labcal Technologies +LDT;LogiDataTech Electronic GmbH +LEC;Lectron Company Ltd +LED;Long Engineering Design Inc +LEG;Legerity, Inc +LEN;Lenovo Group Limited +LEO;First International Computer Inc +LEX;Lexical Ltd +LGC;Logic Ltd +LGI;Logitech Inc +LGS;LG Semicom Company Ltd +LGX;Lasergraphics, Inc. +LHA;Lars Haagh ApS +LHE;Lung Hwa Electronics Company Ltd +LHT;Lighthouse Technologies Limited +LIP;Linked IP GmbH +LIT;Lithics Silicon Technology +LJX;Datalogic Corporation +LKM;Likom Technology Sdn. Bhd. +LLL;L-3 Communications +LMG;Lucent Technologies +LMI;Lexmark Int'l Inc +LMP;Leda Media Products +LMT;Laser Master +LND;Land Computer Company Ltd +LNK;Link Tech Inc +LNR;Linear Systems Ltd. +LNT;LANETCO International +LNV;Lenovo +LOC;Locamation B.V. +LOE;Loewe Opta GmbH +LOG;Logicode Technology Inc +LPE;El-PUSK Co., Ltd. +LPI;Design Technology +LPL;DO NOT USE - LPL +LSC;LifeSize Communications +LSI;Loughborough Sound Images +LSJ;LSI Japan Company Ltd +LSL;Logical Solutions +LSY;LSI Systems Inc +LTC;Labtec Inc +LTI;Jongshine Tech Inc +LTK;Lucidity Technology Company Ltd +LTN;Litronic Inc +LTS;LTS Scale LLC +LTV;Leitch Technology International Inc. +LTW;Lightware, Inc +LUC;Lucent Technologies +LUM;Lumagen, Inc. +LUX;Luxxell Research Inc +LWC;Labway Corporation +LWR;Lightware Visual Engineering +LWW;Lanier Worldwide +LXN;Luxeon +LXS;ELEA CardWare +LZX;Lightwell Company Ltd +MAC;MAC System Company Ltd +MAD;Xedia Corporation +MAE;Maestro Pty Ltd +MAG;MAG InnoVision +MAI;Mutoh America Inc +MAL;Meridian Audio Ltd +MAN;LGIC +MAS;Mass Inc. +MAT;Matsushita Electric Ind. Company Ltd +MAX;Rogen Tech Distribution Inc +MAY;Maynard Electronics +MAZ;MAZeT GmbH +MBC;MBC +MBD;Microbus PLC +MBM;Marshall Electronics +MBV;Moreton Bay +MCA;American Nuclear Systems Inc +MCC;Micro Industries +MCD;McDATA Corporation +MCE;Metz-Werke GmbH & Co KG +MCG;Motorola Computer Group +MCI;Micronics Computers +MCL;Motorola Communications Israel +MCM;Metricom Inc +MCN;Micron Electronics Inc +MCO;Motion Computing Inc. +MCP;Magni Systems Inc +MCQ;Mat's Computers +MCR;Marina Communicaitons +MCS;Micro Computer Systems +MCT;Microtec +MDA;Media4 Inc +MDC;Midori Electronics +MDD;MODIS +MDG;Madge Networks +MDI;Micro Design Inc +MDK;Mediatek Corporation +MDO;Panasonic +MDR;Medar Inc +MDS;Micro Display Systems Inc +MDT;Magus Data Tech +MDV;MET Development Inc +MDX;MicroDatec GmbH +MDY;Microdyne Inc +MEC;Mega System Technologies Inc +MED;Messeltronik Dresden GmbH +MEE;Mitsubishi Electric Engineering Co., Ltd. +MEG;Abeam Tech Ltd +MEI;Panasonic Industry Company +MEL;Mitsubishi Electric Corporation +MEN;MEN Mikroelectronik Nueruberg GmbH +MEQ;Matelect Ltd. +MET;Metheus Corporation +MFG;MicroField Graphics Inc +MFI;Micro Firmware +MFR;MediaFire Corp. +MGA;Mega System Technologies, Inc. +MGC;Mentor Graphics Corporation +MGE;Schneider Electric S.A. +MGL;M-G Technology Ltd +MGT;Megatech R & D Company +MIC;Micom Communications Inc +MID;miro Displays +MII;Mitec Inc +MIL;Marconi Instruments Ltd +MIN;Minicom Digital Signage +MIP;micronpc.com +MIR;Miro Computer Prod. +MIS;Modular Industrial Solutions Inc +MIT;MCM Industrial Technology GmbH +MJI;MARANTZ JAPAN, INC. +MJS;MJS Designs +MKC;Media Tek Inc. +MKT;MICROTEK Inc. +MKV;Trtheim Technology +MLD;Deep Video Imaging Ltd +MLG;Micrologica AG +MLI;McIntosh Laboratory Inc. +MLM;Millennium Engineering Inc +MLN;Mark Levinson +MLS;Milestone EPE +MLX;Mylex Corporation +MMA;Micromedia AG +MMD;Micromed Biotecnologia Ltd +MMF;Minnesota Mining and Manufacturing +MMI;Multimax +MMM;Electronic Measurements +MMN;MiniMan Inc +MMS;MMS Electronics +MNC;Mini Micro Methods Ltd +MNL;Monorail Inc +MNP;Microcom +MOD;Modular Technology +MOM;Momentum Data Systems +MOS;Moses Corporation +MOT;Motorola UDS +MPC;M-Pact Inc +MPI;Mediatrix Peripherals Inc +MPJ;Microlab +MPL;Maple Research Inst. Company Ltd +MPN;Mainpine Limited +MPS;mps Software GmbH +MPX;Micropix Technologies, Ltd. +MQP;MultiQ Products AB +MRA;Miranda Technologies Inc +MRC;Marconi Simulation & Ty-Coch Way Training +MRD;MicroDisplay Corporation +MRK;Maruko & Company Ltd +MRL;Miratel +MRO;Medikro Oy +MRT;Merging Technologies +MSA;Micro Systemation AB +MSC;Mouse Systems Corporation +MSD;Datenerfassungs- und Informationssysteme +MSF;M-Systems Flash Disk Pioneers +MSG;MSI GmbH +MSH;Microsoft +MSI;Microstep +MSK;Megasoft Inc +MSL;MicroSlate Inc. +MSM;Advanced Digital Systems +MSP;Mistral Solutions [P] Ltd. +MST;MS Telematica +MSU;motorola +MSV;Mosgi Corporation +MSX;Micomsoft Co., Ltd. +MSY;MicroTouch Systems Inc +MTB;Media Technologies Ltd. +MTC;Mars-Tech Corporation +MTD;MindTech Display Co. Ltd +MTE;MediaTec GmbH +MTH;Micro-Tech Hearing Instruments +MTI;MaxCom Technical Inc +MTI;Motorola Inc. +MTK;Microtek International Inc. +MTL;Mitel Corporation +MTN;Mtron Storage Technology Co., Ltd. +MTR;Mitron computer Inc +MTS;Multi-Tech Systems +MTU;Mark of the Unicorn Inc +MTX;Matrox +MUD;Multi-Dimension Institute +MUK;mainpine limited +MVD;Microvitec PLC +MVI;Media Vision Inc +MVM;SOBO VISION +MVS;Microvision +MVX;COM 1 +MWI;Multiwave Innovation Pte Ltd +MWR;mware +MWY;Microway Inc +MXD;MaxData Computer GmbH & Co.KG +MXI;Macronix Inc +MXL;Hitachi Maxell, Ltd. +MXP;Maxpeed Corporation +MXT;Maxtech Corporation +MXV;MaxVision Corporation +MYA;Monydata +MYR;Myriad Solutions Ltd +MYX;Micronyx Inc +NAC;Ncast Corporation +NAD;NAD Electronics +NAK;Nakano Engineering Co.,Ltd. +NAL;Network Alchemy +NAT;NaturalPoint Inc. +NAV;Navigation Corporation +NAX;Naxos Tecnologia +NBL;N*Able Technologies Inc +NBS;National Key Lab. on ISN +NBT;NingBo Bestwinning Technology CO., Ltd +NCA;Nixdorf Company +NCC;NCR Corporation +NCE;Norcent Technology, Inc. +NCI;NewCom Inc +NCL;NetComm Ltd +NCR;NCR Electronics +NCS;Northgate Computer Systems +NCT;NEC CustomTechnica, Ltd. +NDC;National DataComm Corporaiton +NDI;National Display Systems +NDK;Naitoh Densei CO., LTD. +NDL;Network Designers +NDS;Nokia Data +NEC;NEC Corporation +NEO;NEO TELECOM CO.,LTD. +NET;Mettler Toledo +NEU;NEUROTEC - EMPRESA DE PESQUISA E DESENVOLVIMENTO EM BIOMEDICINA +NEX;Nexgen Mediatech Inc., +NFC;BTC Korea Co., Ltd +NFS;Number Five Software +NGC;Network General +NGS;A D S Exports +NHT;Vinci Labs +NIC;National Instruments Corporation +NIS;Nissei Electric Company +NIT;Network Info Technology +NIX;Seanix Technology Inc +NLC;Next Level Communications +NMP;Nokia Mobile Phones +NMS;Natural Micro System +NMV;NEC-Mitsubishi Electric Visual Systems Corporation +NMX;Neomagic +NNC;NNC +NOE;NordicEye AB +NOI;North Invent A/S +NOK;Nokia Display Products +NOR;Norand Corporation +NOT;Not Limited Inc +NPI;Network Peripherals Inc +NRL;U.S. Naval Research Lab +NRT;Beijing Northern Radiantelecom Co. +NRV;Taugagreining hf +NSC;National Semiconductor Corporation +NSI;NISSEI ELECTRIC CO.,LTD +NSP;Nspire System Inc. +NSS;Newport Systems Solutions +NST;Network Security Technology Co +NTC;NeoTech S.R.L +NTI;New Tech Int'l Company +NTL;National Transcomm. Ltd +NTN;Nuvoton Technology Corporation +NTR;N-trig Innovative Technologies, Inc. +NTS;Nits Technology Inc. +NTT;NTT Advanced Technology Corporation +NTW;Networth Inc +NTX;Netaccess Inc +NUG;NU Technology, Inc. +NUI;NU Inc. +NVC;NetVision Corporation +NVD;Nvidia +NVI;NuVision US, Inc. +NVL;Novell Inc +NVT;Navatek Engineering Corporation +NWC;NW Computer Engineering +NWP;NovaWeb Technologies Inc +NWS;Newisys, Inc. +NXC;NextCom K.K. +NXG;Nexgen +NXP;NXP Semiconductors bv. +NXQ;Nexiq Technologies, Inc. +NXS;Technology Nexus Secure Open Systems AB +NYC;nakayo telecommunications,inc. +OAK;Oak Tech Inc +OAS;Oasys Technology Company +OBS;Optibase Technologies +OCD;Macraigor Systems Inc +OCN;Olfan +OCS;Open Connect Solutions +ODM;ODME Inc. +ODR;Odrac +OEC;ORION ELECTRIC CO.,LTD +OEI;Optum Engineering Inc. +OIC;Option Industrial Computers +OIM;Option International +OIN;Option International +OKI;OKI Electric Industrial Company Ltd +OLC;Olicom A/S +OLD;Olidata S.p.A. +OLI;Olivetti +OLT;Olitec S.A. +OLV;Olitec S.A. +OLY;OLYMPUS CORPORATION +OMC;OBJIX Multimedia Corporation +OMN;Omnitel +OMR;Omron Corporation +ONE;Oneac Corporation +ONK;ONKYO Corporation +ONL;OnLive, Inc +ONS;On Systems Inc +ONW;OPEN Networks Ltd +ONX;SOMELEC Z.I. Du Vert Galanta +OOS;OSRAM +OPC;Opcode Inc +OPI;D.N.S. Corporation +OPT;OPTi Inc +OPV;Optivision Inc +OQI;Oksori Company Ltd +ORG;ORGA Kartensysteme GmbH +ORI;OSR Open Systems Resources, Inc. +ORN;ORION ELECTRIC CO., LTD. +OSA;OSAKA Micro Computer, Inc. +OSP;OPTI-UPS Corporation +OSR;Oksori Company Ltd +OTB;outsidetheboxstuff.com +OTI;Orchid Technology +OTM;Optoma Corporation           +OTT;OPTO22, Inc. +OUK;OUK Company Ltd +OWL;Mediacom Technologies Pte Ltd +OXU;Oxus Research S.A. +OYO;Shadow Systems +OZO;Tribe Computer Works Inc +PAC;Pacific Avionics Corporation +PAD;Promotion and Display Technology Ltd. +PAK;Many CNC System Co., Ltd. +PAM;Peter Antesberger Messtechnik +PAN;The Panda Project +PAR;Parallan Comp Inc +PBI;Pitney Bowes +PBL;Packard Bell Electronics +PBN;Packard Bell NEC +PBV;Pitney Bowes +PCA;Philips BU Add On Card +PCB;OCTAL S.A. +PCC;PowerCom Technology Company Ltd +PCG;First Industrial Computer Inc +PCI;Pioneer Computer Inc +PCK;PCBANK21 +PCL;pentel.co.,ltd +PCM;PCM Systems Corporation +PCO;Performance Concepts Inc., +PCP;Procomp USA Inc +PCS;TOSHIBA PERSONAL COMPUTER SYSTEM CORPRATION +PCT;PC-Tel Inc +PCW;Pacific CommWare Inc +PCX;PC Xperten +PDM;Psion Dacom Plc. +PDN;AT&T Paradyne +PDR;Pure Data Inc +PDS;PD Systems International Ltd +PDT;PDTS - Prozessdatentechnik und Systeme +PDV;Prodrive B.V. +PEC;POTRANS Electrical Corp. +PEI;PEI Electronics Inc +PEL;Primax Electric Ltd +PEN;Interactive Computer Products Inc +PEP;Peppercon AG +PER;Perceptive Signal Technologies +PET;Practical Electronic Tools +PFT;Telia ProSoft AB +PGM;Paradigm Advanced Research Centre +PGP;propagamma kommunikation +PGS;Princeton Graphic Systems +PHC;Pijnenburg Beheer N.V. +PHE;Philips Medical Systems Boeblingen GmbH +PHI;DO NOT USE - PHI +PHL;Philips Consumer Electronics Company +PHO;Photonics Systems Inc. +PHS;Philips Communication Systems +PHY;Phylon Communications +PIE;Pacific Image Electronics Company Ltd +PIM;Prism, LLC +PIO;Pioneer Electronic Corporation +PIX;Pixie Tech Inc +PJA;Projecta +PJD;Projectiondesign AS +PJT;Pan Jit International Inc. +PKA;Acco UK ltd. +PLC;Pro-Log Corporation +PLF;Panasonic Avionics Corporation +PLM;PROLINK Microsystems Corp. +PLT;PT Hartono Istana Teknologi +PLV;PLUS Vision Corp. +PLX;Parallax Graphics +PLY;Polycom Inc. +PMC;PMC Consumer Electronics Ltd +PMD;TDK USA Corporation +PMM;Point Multimedia System +PMT;Promate Electronic Co., Ltd. +PMX;Photomatrix +PNG;Microsoft +PNG;P.I. Engineering Inc +PNL;Panelview, Inc. +PNP;Microsoft +PNR;Planar Systems, Inc. +PNS;PanaScope +PNX;Phoenix Technologies, Ltd. +POL;PolyComp (PTY) Ltd. +PON;Perpetual Technologies, LLC +POR;Portalis LC +PPC;Phoenixtec Power Company Ltd +PPD;MEPhI +PPI;Practical Peripherals +PPM;Clinton Electronics Corp. +PPP;Purup Prepress AS +PPR;PicPro +PPX;Perceptive Pixel Inc. +PQI;Pixel Qi +PRA;PRO/AUTOMATION +PRC;PerComm +PRD;Praim S.R.L. +PRF;Digital Electronics Corporation +PRG;The Phoenix Research Group Inc +PRI;Priva Hortimation BV +PRM;Prometheus +PRO;Proteon +PRS;Leutron Vision +PRX;Proxima Corporation +PSA;Advanced Signal Processing Technologies +PSC;Philips Semiconductors +PSD;Peus-Systems GmbH +PSE;Practical Solutions Pte., Ltd. +PSI;PSI-Perceptive Solutions Inc +PSL;Perle Systems Limited +PSM;Prosum +PST;Global Data SA +PTC;PS Technology Corporation +PTG;Cipher Systems Inc +PTH;Pathlight Technology Inc +PTI;Promise Technology Inc +PTL;Pantel Inc +PTS;Plain Tree Systems Inc +PTW;DO NOT USE +PVC;DO NOT USE +PVG;Proview Global Co., Ltd +PVI;Prime view international Co., Ltd +PVM;Penta Studiotechnik GmbH +PVN;Pixel Vision +PVP;Klos Technologies, Inc. +PXC;Phoenix Contact +PXE;PIXELA CORPORATION +PXL;The Moving Pixel Company +PXM;Proxim Inc +QCC;QuakeCom Company Ltd +QCH;Metronics Inc +QCI;Quanta Computer Inc +QCK;Quick Corporation +QCL;Quadrant Components Inc +QCP;Qualcomm Inc +QDI;Quantum Data Incorporated +QDM;Quadram +QDS;Quanta Display Inc. +QFF;Padix Co., Inc. +QFI;Quickflex, Inc +QLC;Q-Logic +QQQ;Chuomusen Co., Ltd. +QSI;Quantum Solutions, Inc. +QTD;Quantum 3D Inc +QTH;Questech Ltd +QTI;Quicknet Technologies Inc +QTM;Quantum +QTR;Qtronix Corporation +QUA;Quatographic AG +QUE;Questra Consulting +QVU;Quartics +RAC;Racore Computer Products Inc +RAD;Radisys Corporation +RAI;Rockwell Automation/Intecolor +RAN;Rancho Tech Inc +RAR;Raritan, Inc. +RAS;RAScom Inc +RAT;Rent-A-Tech +RAY;Raylar Design, Inc. +RCE;Parc d'Activite des Bellevues +RCH;Reach Technology Inc +RCI;RC International +RCN;Radio Consult SRL +RCO;Rockwell Collins +RDI;Rainbow Displays, Inc. +RDM;Tremon Enterprises Company Ltd +RDS;Radius Inc +REA;Real D +REC;ReCom +RED;Research Electronics Development Inc +REF;Reflectivity, Inc. +REL;Reliance Electric Ind Corporation +REM;SCI Systems Inc. +REN;Renesas Technology Corp. +RES;ResMed Pty Ltd +RGL;Robertson Geologging Ltd +RHM;Rohm Company Ltd +RIC;RICOH COMPANY, LTD. +RII;Racal Interlan Inc +RIO;Rios Systems Company Ltd +RIT;Ritech Inc +RIV;Rivulet Communications +RJA;Roland Corporation +RJS;Advanced Engineering +RKC;Reakin Technolohy Corporation +RLD;MEPCO +RLN;RadioLAN Inc +RMC;Raritan Computer, Inc +RMP;Research Machines +RMT;Roper Mobile +RNB;Rainbow Technologies +ROB;Robust Electronics GmbH +ROH;Rohm Co., Ltd. +ROK;Rockwell International +ROP;Roper International Ltd +RPI;RoomPro Technologies +RPT;R.P.T.Intergroups +RRI;Radicom Research Inc +RSC;PhotoTelesis +RSH;ADC-Centre +RSI;Rampage Systems Inc +RSN;Radiospire Networks, Inc. +RSQ;R Squared +RSS;Rockwell Semiconductor Systems +RSX;Rapid Tech Corporation +RTC;Relia Technologies +RTI;Rancho Tech Inc +RTK;DO NOT USE +RTL;Realtek Semiconductor Company Ltd +RTS;Raintree Systems +RUN;RUNCO International +RUP;Ups Manufactoring s.r.l. +RVC;RSI Systems Inc +RVI;Realvision Inc +RVL;Reveal Computer Prod +RWC;Red Wing Corporation +RXT;Tectona SoftSolutions (P) Ltd., +SAA;Sanritz Automation Co.,Ltd. +SAE;Saab Aerotech +SAG;Sedlbauer +SAI;Sage Inc +SAK;Saitek Ltd +SAM;Samsung Electric Company +SAN;Sanyo Electric Co.,Ltd. +SAS;Stores Automated Systems Inc +SAT;Shuttle Tech +SBC;Shanghai Bell Telephone Equip Mfg Co +SBD;Softbed - Consulting & Development Ltd +SBI;SMART Technologies Inc. +SBS;SBS-or Industrial Computers GmbH +SBT;Senseboard Technologies AB +SCC;SORD Computer Corporation +SCD;Sanyo Electric Company Ltd +SCE;Sun Corporation +SCH;Schlumberger Cards +SCI;System Craft +SCL;Sigmacom Co., Ltd. +SCM;SCM Microsystems Inc +SCN;Scanport, Inc. +SCO;SORCUS Computer GmbH +SCP;Scriptel Corporation +SCR;Systran Corporation +SCS;Nanomach Anstalt +SCT;Smart Card Technology +SDA;SAT (Societe Anonyme) +SDD;Intrada-SDD Ltd +SDE;Sherwood Digital Electronics Corporation +SDF;SODIFF E&T CO., Ltd. +SDH;Communications Specialies, Inc. +SDI;Samtron Displays Inc +SDK;SAIT-Devlonics +SDR;SDR Systems +SDS;SunRiver Data System +SDT;Siemens AG +SDX;SDX Business Systems Ltd +SEA;Seanix Technology Inc. +SEB;system elektronik GmbH +SEC;Seiko Epson Corporation +SEE;SeeColor Corporation +SEG;DO NOT USE +SEI;Seitz & Associates Inc +SEL;Way2Call Communications +SEM;Samsung Electronics Company Ltd +SEN;Sencore +SEO;SEOS Ltd +SEP;SEP Eletronica Ltda. +SER;Sony Ericsson Mobile Communications Inc. +SES;Session Control LLC +SET;SendTek Corporation +SFM;TORNADO Company +SFT;Mikroforum Ring 3 +SGC;Spectragraphics Corporation +SGD;Sigma Designs, Inc. +SGE;Kansai Electric Company Ltd +SGI;Scan Group Ltd +SGL;Super Gate Technology Company Ltd +SGM;SAGEM +SGO;Logos Design A/S +SGT;Stargate Technology +SGX;Silicon Graphics Inc +SGZ;Systec Computer GmbH +SHC;ShibaSoku Co., Ltd. +SHG;Soft & Hardware development Goldammer GmbH +SHI;Jiangsu Shinco Electronic Group Co., Ltd +SHP;Sharp Corporation +SHR;Digital Discovery +SHT;Shin Ho Tech +SIA;SIEMENS AG +SIB;Sanyo Electric Company Ltd +SIC;Sysmate Corporation +SID;Seiko Instruments Information Devices Inc +SIE;Siemens +SIG;Sigma Designs Inc +SII;Silicon Image, Inc. +SIL;Silicon Laboratories, Inc +SIM;S3 Inc +SIN;Singular Technology Co., Ltd. +SIR;Sirius Technologies Pty Ltd +SIS;Silicon Integrated Systems Corporation +SIT;Sitintel +SIU;Seiko Instruments USA Inc +SIX;Zuniq Data Corporation +SJE;Sejin Electron Inc +SKD;Schneider & Koch +SKT;Samsung Electro-Mechanics Company Ltd +SKY;SKYDATA S.P.A. +SLA;Systeme Lauer GmbH&Co KG +SLB;Shlumberger Ltd +SLC;Syslogic Datentechnik AG +SLF;StarLeaf +SLH;Silicon Library Inc. +SLI;Symbios Logic Inc +SLK;Silitek Corporation +SLM;Solomon Technology Corporation +SLR;Schlumberger Technology Corporate +SLS;Schnick-Schnack-Systems GmbH +SLT;Salt Internatioinal Corp. +SLX;Specialix +SMA;SMART Modular Technologies +SMB;Schlumberger +SMC;Standard Microsystems Corporation +SME;Sysmate Company +SMI;SpaceLabs Medical Inc +SMK;SMK CORPORATION +SML;Sumitomo Metal Industries, Ltd. +SMM;Shark Multimedia Inc +SMO;STMicroelectronics +SMP;Simple Computing +SMR;B.& V. s.r.l. +SMS;Silicom Multimedia Systems Inc +SMT;Silcom Manufacturing Tech Inc +SNC;Sentronic International Corp. +SNI;Siemens Microdesign GmbH +SNK;S&K Electronics +SNO;SINOSUN TECHNOLOGY CO., LTD +SNP;Siemens Nixdorf Info Systems +SNS;Cirtech (UK) Ltd +SNT;SuperNet Inc +SNW;Snell & Wilcox +SNX;Sonix Comm. Ltd +SNY;Sony +SOI;Silicon Optix Corporation +SOL;Solitron Technologies Inc +SON;Sony +SOR;Sorcus Computer GmbH +SOT;Sotec Company Ltd +SOY;SOYO Group, Inc +SPC;SpinCore Technologies, Inc +SPE;SPEA Software AG +SPH;G&W Instruments GmbH +SPI;SPACE-I Co., Ltd. +SPK;SpeakerCraft +SPL;Smart Silicon Systems Pty Ltd +SPN;Sapience Corporation +SPR;pmns GmbH +SPS;Synopsys Inc +SPT;Sceptre Tech Inc +SPU;SIM2 Multimedia S.P.A. +SPX;Simplex Time Recorder Co. +SQT;Sequent Computer Systems Inc +SRC;Integrated Tech Express Inc +SRD;Setred +SRF;Surf Communication Solutions Ltd +SRG;Intuitive Surgical, Inc. +SRT;SeeReal Technologies GmbH +SSC;Sierra Semiconductor Inc +SSD;FlightSafety International +SSE;Samsung Electronic Co. +SSI;S-S Technology Inc +SSJ;Sankyo Seiki Mfg.co., Ltd +SSP;Spectrum Signal Proecessing Inc +SSS;S3 Inc +SST;SystemSoft Corporation +STA;ST Electronics Systems Assembly Pte Ltd +STB;STB Systems Inc +STC;STAC Electronics +STD;STD Computer Inc +STE;SII Ido-Tsushin Inc +STF;Starflight Electronics +STG;StereoGraphics Corp. +STH;Semtech Corporation +STI;Smart Tech Inc +STK;SANTAK CORP. +STL;SigmaTel Inc +STM;SGS Thomson Microelectronics +STN;Samsung Electronics America +STO;Stollmann E+V GmbH +STP;StreamPlay Ltd +STR;Starlight Networks Inc +STS;SITECSYSTEM CO., LTD. +STT;Star Paging Telecom Tech (Shenzhen) Co. Ltd. +STW;Starwin Inc. +STY;SDS Technologies +SUB;Subspace Comm. Inc +SUM;Summagraphics Corporation +SUN;Sun Electronics Corporation +SUP;Supra Corporation +SUR;Surenam Computer Corporation +SVA;SGEG +SVC;Intellix Corp. +SVD;SVD Computer +SVI;Sun Microsystems +SVS;SVSI +SVT;SEVIT Co., Ltd. +SWC;Software Café +SWI;Sierra Wireless Inc. +SWL;Sharedware Ltd +SWS;Static +SWT;Software Technologies Group,Inc. +SXB;Syntax-Brillian +SXD;Silex technology, Inc. +SXL;SolutionInside +SXT;SHARP TAKAYA ELECTRONIC INDUSTRY CO.,LTD. +SYC;Sysmic +SYE;SY Electronics Ltd +SYK;Stryker Communications +SYL;Sylvania Computer Products +SYM;Symicron Computer Communications Ltd. +SYN;Synaptics Inc +SYP;SYPRO Co Ltd +SYS;Sysgration Ltd +SYT;Seyeon Tech Company Ltd +SYV;SYVAX Inc +SYX;Prime Systems, Inc. +TAA;Tandberg +TAB;Todos Data System AB +TAG;Teles AG +TAI;Toshiba America Info Systems Inc +TAM;Tamura Seisakusyo Ltd +TAS;Taskit Rechnertechnik GmbH +TAT;Teleliaison Inc +TAX;Taxan (Europe) Ltd +TBB;Triple S Engineering Inc +TBC;Turbo Communication, Inc +TBS;Turtle Beach System +TCC;Tandon Corporation +TCD;Taicom Data Systems Co., Ltd. +TCE;Century Corporation +TCH;Interaction Systems, Inc +TCI;Tulip Computers Int'l B.V. +TCJ;TEAC America Inc +TCL;Technical Concepts Ltd +TCM;3Com Corporation +TCN;Tecnetics (PTY) Ltd +TCO;Thomas-Conrad Corporation +TCR;Thomson Consumer Electronics +TCS;Tatung Company of America Inc +TCT;Telecom Technology Centre Co. Ltd. +TCX;FREEMARS Heavy Industries +TDC;Teradici +TDD;Tandberg Data Display AS +TDK;TDK USA Corporation +TDM;Tandem Computer Europe Inc +TDP;3D Perception +TDS;Tri-Data Systems Inc +TDT;TDT +TDV;TDVision Systems, Inc. +TDY;Tandy Electronics +TEA;TEAC System Corporation +TEC;Tecmar Inc +TEK;Tektronix Inc +TEL;Promotion and Display Technology Ltd. +TER;TerraTec Electronic GmbH +TGI;TriGem Computer Inc +TGM;TriGem Computer,Inc. +TGS;Torus Systems Ltd +TGV;Grass Valley Germany GmbH +THN;Thundercom Holdings Sdn. Bhd. +TIC;Trigem KinfoComm +TIP;TIPTEL AG +TIV;OOO Technoinvest +TIX;Tixi.Com GmbH +TKC;Taiko Electric Works.LTD +TKN;Teknor Microsystem Inc +TKO;TouchKo, Inc. +TKS;TimeKeeping Systems, Inc. +TLA;Ferrari Electronic GmbH +TLD;Telindus +TLI;TOSHIBA TELI CORPORATION +TLK;Telelink AG +TLS;Teleste Educational OY +TLT;Dai Telecom S.p.A. +TLV;S3 Inc +TLX;Telxon Corporation +TMC;Techmedia Computer Systems Corporation +TME;AT&T Microelectronics +TMI;Texas Microsystem +TMM;Time Management, Inc. +TMR;Taicom International Inc +TMS;Trident Microsystems Ltd +TMT;T-Metrics Inc. +TMX;Thermotrex Corporation +TNC;TNC Industrial Company Ltd +TNJ;DO NOT USE +TNM;TECNIMAGEN SA +TNY;Tennyson Tech Pty Ltd +TOE;TOEI Electronics Co., Ltd. +TOG;The OPEN Group +TOP;Orion Communications Co., Ltd. +TOS;Toshiba Corporation +TOU;Touchstone Technology +TPC;Touch Panel Systems Corporation +TPE;Technology Power Enterprises Inc +TPJ;(none) +TPK;TOPRE CORPORATION +TPR;Topro Technology Inc +TPS;Teleprocessing Systeme GmbH +TPT;Thruput Ltd +TPV;Top Victory Electronics ( Fujian ) Company Ltd +TPZ;Ypoaz Systems Inc +TRA;TriTech Microelectronics International +TRC;Trioc AB +TRD;Trident Microsystem Inc +TRE;Tremetrics +TRI;Tricord Systems +TRL;Royal Information +TRM;Tekram Technology Company Ltd +TRN;Datacommunicatie Tron B.V. +TRS;Torus Systems Ltd +TRU;Aashima Technology B.V. +TRX;Trex Enterprises +TSB;Toshiba America Info Systems Inc +TSC;Sanyo Electric Company Ltd +TSD;TechniSat Digital GmbH +TSE;Tottori Sanyo Electric +TSF;Racal-Airtech Software Forge Ltd +TSG;The Software Group Ltd +TSI;TeleVideo Systems +TSL;Tottori SANYO Electric Co., Ltd. +TSP;U.S. Navy +TST;Transtream Inc +TSV;TRANSVIDEO +TSY;TouchSystems +TTA;Topson Technology Co., Ltd. +TTB;National Semiconductor Japan Ltd +TTC;Telecommunications Techniques Corporation +TTE;TTE, Inc. +TTI;Trenton Terminals Inc +TTK;Totoku Electric Company Ltd +TTL;2-Tel B.V. +TTS;TechnoTrend Systemtechnik GmbH +TTY;TRIDELITY Display Solutions GmbH +TUT;Tut Systems +TVD;Tecnovision +TVI;Truevision +TVM;Taiwan Video & Monitor Corporation +TVO;TV One Ltd +TVR;TV Interactive Corporation +TVS;TVS Electronics Limited +TWA;Tidewater Association +TWE;Kontron Electronik +TWH;Twinhead International Corporation +TWI;Easytel oy +TWK;TOWITOKO electronics GmbH +TWX;TEKWorx Limited +TXL;Trixel Ltd +TXN;Texas Insturments +TXT;Textron Defense System +TYN;Tyan Computer Corporation +UAS;Ultima Associates Pte Ltd +UBI;Ungermann-Bass Inc +UBL;Ubinetics Ltd. +UDN;Uniden Corporation +UEC;Ultima Electronics Corporation +UEG;Elitegroup Computer Systems Company Ltd +UEI;Universal Electronics Inc +UET;Universal Empowering Technologies +UFG;UNIGRAF-USA +UFO;UFO Systems Inc +UHB;XOCECO +UIC;Uniform Industrial Corporation +UJR;Ueda Japan Radio Co., Ltd. +ULT;Ultra Network Tech +UMC;United Microelectr Corporation +UMG;Umezawa Giken Co.,Ltd +UMM;Universal Multimedia +UNA;Unisys DSD +UNB;Unisys Corporation +UNC;Unisys Corporation +UND;DO NOT USE - UND +UNE;DO NOT USE - UNE +UNF;DO NOT USE - UNF +UNI;Unisys Corporation +UNI;Uniform Industry Corp. +UNM;Unisys Corporation +UNO;Unisys Corporation +UNP;Unitop +UNS;Unisys Corporation +UNT;Unisys Corporation +UNY;Unicate +UPP;UPPI +UPS;Systems Enhancement +URD;Video Computer S.p.A. +USA;Utimaco Safeware AG +USD;U.S. Digital Corporation +USI;Universal Scientific Industrial Co., Ltd. +USR;U.S. Robotics Inc +UTD;Up to Date Tech +UWC;Uniwill Computer Corp. +VAL;Valence Computing Corporation +VAR;Varian Australia Pty Ltd +VBT;Valley Board Ltda +VCC;Virtual Computer Corporation +VCI;VistaCom Inc +VCJ;Victor Company of Japan, Limited +VCM;Vector Magnetics, LLC +VCX;VCONEX +VDA;Victor Data Systems +VDC;VDC Display Systems +VDM;Vadem +VDO;Video & Display Oriented Corporation +VDS;Vidisys GmbH & Company +VDT;Viditec, Inc. +VEC;Vector Informatik GmbH +VEK;Vektrex +VES;Vestel Elektronik Sanayi ve Ticaret A. S. +VFI;VeriFone Inc +VHI;Macrocad Development Inc. +VIA;VIA Tech Inc +VIB;Tatung UK Ltd +VIC;Victron B.V. +VID;Ingram Macrotron Germany +VIK;Viking Connectors +VIN;Vine Micros Ltd +VIR;Visual Interface, Inc +VIS;Visioneer +VIT;Visitech AS +VLB;ValleyBoard Ltda. +VLT;VideoLan Technologies +VMI;Vermont Microsystems +VML;Vine Micros Limited +VNC;Vinca Corporation +VOB;MaxData Computer AG +VPI;Video Products Inc +VPR;Best Buy +VQ@;Vision Quest +VRC;Virtual Resources Corporation +VSC;ViewSonic Corporation +VSD;3M +VSI;VideoServer +VSN;Ingram Macrotron +VSP;Vision Systems GmbH +VSR;V-Star Electronics Inc. +VTC;VTel Corporation +VTG;Voice Technologies Group Inc +VTI;VLSI Tech Inc +VTK;Viewteck Co., Ltd. +VTL;Vivid Technology Pte Ltd +VTM;Miltope Corporation +VTN;VIDEOTRON CORP. +VTS;VTech Computers Ltd +VTV;VATIV Technologies +VUT;Vutrix (UK) Ltd +VWB;Vweb Corp. +WAC;Wacom Tech +WAL;Wave Access +WAN;DO NOT USE +WAV;Wavephore +WBN;MicroSoftWare +WBS;WB Systemtechnik GmbH +WCI;Wisecom Inc +WCS;Woodwind Communications Systems Inc +WDC;Western Digital +WDE;Westinghouse Digital Electronics +WEB;WebGear Inc +WEC;Winbond Electronics Corporation +WEL ; W-DEV +WEY;WEY Design AG +WHI;Whistle Communications +WII;Innoware Inc +WIL;WIPRO Information Technology Ltd +WIN;Wintop Technology Inc +WIP;Wipro Infotech +WKH;Uni-Take Int'l Inc. +WLD;Wildfire Communications Inc +WML;Wolfson Microelectronics Ltd +WMO;Westermo Teleindustri AB +WMT;Winmate Communication Inc +WNI;WillNet Inc. +WNV;Winnov L.P. +WNX;Wincor Nixdorf International GmbH +WPA;Matsushita Communication Industrial Co., Ltd. +WPI;Wearnes Peripherals International (Pte) Ltd +WRC;WiNRADiO Communications +WSC;CIS Technology Inc +WSP;Wireless And Smart Products Inc. +WST;Wistron Corporation +WTC;ACC Microelectronics +WTI;WorkStation Tech +WTK;Wearnes Thakral Pte +WTS;Restek Electric Company Ltd +WVM;Wave Systems Corporation +WWV;World Wide Video, Inc. +WXT;Woxter Technology Co. Ltd +WYS;Myse Technology +WYT;Wooyoung Image & Information Co.,Ltd. +XAC;XAC Automation Corp +XAD;Alpha Data +XDM;XDM Ltd. +XER;DO NOT USE +XFG;Jan Strapko - FOTO +XFO;EXFO Electro Optical Engineering +XIN;Xinex Networks Inc +XIO;Xiotech Corporation +XIR;Xirocm Inc +XIT;Xitel Pty ltd +XLX;Xilinx, Inc. +XMM;C3PO S.L. +XNT;XN Technologies, Inc. +XOC;DO NOT USE +XQU;SHANGHAI SVA-DAV ELECTRONICS CO., LTD +XRC;Xircom Inc +XRO;XORO ELECTRONICS (CHENGDU) LIMITED +XSN;Xscreen AS +XST;XS Technologies Inc +XSY;XSYS +XTD;Icuiti Corporation +XTE;X2E GmbH +XTL;Crystal Computer +XTN;X-10 (USA) Inc +XYC;Xycotec Computer GmbH +YED;Y-E Data Inc +YHQ;Yokogawa Electric Corporation +YHW;Exacom SA +YMH;Yamaha Corporation +YOW;American Biometric Company +ZAN;Zandar Technologies plc +ZAX;Zefiro Acoustics +ZAZ;Zazzle Technologies +ZBR;Zebra Technologies International, LLC +ZCT;ZeitControl cardsystems GmbH +ZDS;Zenith Data Systems +ZGT;Zenith Data Systems +ZIC;ZTEIC DESIGN CO., LTD. +ZMT;Zalman Tech Co., Ltd. +ZMZ;Z Microsystems +ZNI;Zetinet Inc +ZNX;Znyx Adv. Systems +ZOW;Zowie Intertainment, Inc +ZRN;Zoran Corporation +ZSE;Zenith Data Systems +ZTC;ZyDAS Technology Corporation +ZTE;ZTE Corporation +ZTI;Zoom Telephonics Inc +ZTM;ZT Group Int'l Inc. +ZYD;Zydacron Inc +ZYP;Zypcom Inc +ZYT;Zytex Computers +ZYX;Zyxel +ZZZ;Boca Research Inc diff --git a/libsigrokdecode4DSL/decoders/eeprom24xx/__init__.py b/libsigrokdecode4DSL/decoders/eeprom24xx/__init__.py new file mode 100644 index 00000000..7d496fce --- /dev/null +++ b/libsigrokdecode4DSL/decoders/eeprom24xx/__init__.py @@ -0,0 +1,25 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This decoder stacks on top of the 'i2c' PD and decodes the +industry standard 24xx series serial EEPROM protocol. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/eeprom24xx/lists.py b/libsigrokdecode4DSL/decoders/eeprom24xx/lists.py new file mode 100644 index 00000000..c6ee63d5 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/eeprom24xx/lists.py @@ -0,0 +1,204 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +# +# Chip specific properties: +# +# - vendor: chip manufacturer +# - model: chip model +# - size: total EEPROM size (in number of bytes) +# - page_size: page size (in number of bytes) +# - page_wraparound: Whether writes wrap-around at page boundaries +# - addr_bytes: number of EEPROM address bytes used +# - addr_pins: number of address pins (A0/A1/A2) on this chip +# - max_speed: max. supported I²C speed (in kHz) +# +chips = { + # Generic chip (128 bytes, 8 bytes page size) + 'generic': { + 'vendor': '', + 'model': 'Generic', + 'size': 128, + 'page_size': 8, + 'page_wraparound': True, + 'addr_bytes': 1, + 'addr_pins': 3, + 'max_speed': 400, + }, + + # Microchip + 'microchip_24aa65': { + 'vendor': 'Microchip', + 'model': '24AA65', + 'size': 8 * 1024, + 'page_size': 64, # Actually 8, but there are 8 pages of "input cache" + 'page_wraparound': True, + 'addr_bytes': 2, + 'addr_pins': 3, + 'max_speed': 400, + }, + 'microchip_24lc65': { + 'vendor': 'Microchip', + 'model': '24LC65', + 'size': 8 * 1024, + 'page_size': 64, # Actually 8, but there are 8 pages of "input cache" + 'page_wraparound': True, + 'addr_bytes': 2, + 'addr_pins': 3, + 'max_speed': 400, + }, + 'microchip_24c65': { + 'vendor': 'Microchip', + 'model': '24C65', + 'size': 8 * 1024, + 'page_size': 64, # Actually 8, but there are 8 pages of "input cache" + 'page_wraparound': True, + 'addr_bytes': 2, + 'addr_pins': 3, + 'max_speed': 400, + }, + 'microchip_24aa64': { + 'vendor': 'Microchip', + 'model': '24AA64', + 'size': 8 * 1024, + 'page_size': 32, + 'page_wraparound': True, + 'addr_bytes': 2, + 'addr_pins': 3, + 'max_speed': 400, # 100 for VCC < 2.5V + }, + 'microchip_24lc64': { + 'vendor': 'Microchip', + 'model': '24LC64', + 'size': 8 * 1024, + 'page_size': 32, + 'page_wraparound': True, + 'addr_bytes': 2, + 'addr_pins': 3, + 'max_speed': 400, + }, + 'microchip_24aa02uid': { + 'vendor': 'Microchip', + 'model': '24AA02UID', + 'size': 256, + 'page_size': 8, + 'page_wraparound': True, + 'addr_bytes': 1, + 'addr_pins': 0, # Pins A0, A1, A2 not used + 'max_speed': 400, + }, + 'microchip_24aa025uid': { + 'vendor': 'Microchip', + 'model': '24AA025UID', + 'size': 256, + 'page_size': 16, + 'page_wraparound': True, + 'addr_bytes': 1, + 'addr_pins': 3, + 'max_speed': 400, + }, + 'microchip_24aa025uid_sot23': { + 'vendor': 'Microchip', + 'model': '24AA025UID (SOT-23)', + 'size': 256, + 'page_size': 16, + 'page_wraparound': True, + 'addr_bytes': 1, + 'addr_pins': 2, # SOT-23 package: A2 not available + 'max_speed': 400, + }, + + # ON Semiconductor + 'onsemi_cat24c256': { + 'vendor': 'ON Semiconductor', + 'model': 'CAT24C256', + 'size': 32 * 1024, + 'page_size': 64, + 'page_wraparound': True, + 'addr_bytes': 2, + 'addr_pins': 3, + 'max_speed': 1000, + }, + 'onsemi_cat24m01': { + 'vendor': 'ON Semiconductor', + 'model': 'CAT24M01', + 'size': 128 * 1024, + 'page_size': 256, + 'page_wraparound': True, + 'addr_bytes': 2, + 'addr_pins': 2, # Pin A0 not connected + 'max_speed': 1000, + }, + + # Siemens + 'siemens_slx_24c01': { + 'vendor': 'Siemens', + 'model': 'SLx 24C01', + 'size': 128, + 'page_size': 8, + 'page_wraparound': True, + 'addr_bytes': 1, + 'addr_pins': 0, # Pins A0, A1, A2 are not connected (NC) + 'max_speed': 400, + }, + 'siemens_slx_24c02': { + 'vendor': 'Siemens', + 'model': 'SLx 24C02', + 'size': 256, + 'page_size': 8, + 'page_wraparound': True, + 'addr_bytes': 1, + 'addr_pins': 0, # Pins A0, A1, A2 are not connected (NC) + 'max_speed': 400, + }, + + # ST + 'st_m24c01': { + 'vendor': 'ST', + 'model': 'M24C01', + 'size': 128, + 'page_size': 16, + 'page_wraparound': True, + 'addr_bytes': 1, + 'addr_pins': 3, # Called E0, E1, E2 on this chip. + 'max_speed': 400, + }, + 'st_m24c02': { + 'vendor': 'ST', + 'model': 'M24C02', + 'size': 256, + 'page_size': 16, + 'page_wraparound': True, + 'addr_bytes': 1, + 'addr_pins': 3, # Called E0, E1, E2 on this chip. + 'max_speed': 400, + }, + + # Xicor + 'xicor_x24c02': { + 'vendor': 'Xicor', + 'model': 'X24C02', + 'size': 256, + 'page_size': 4, + 'page_wraparound': True, + 'addr_bytes': 1, + 'addr_pins': 3, + 'max_speed': 100, + }, +} diff --git a/libsigrokdecode4DSL/decoders/eeprom24xx/pd.py b/libsigrokdecode4DSL/decoders/eeprom24xx/pd.py new file mode 100644 index 00000000..033a44b2 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/eeprom24xx/pd.py @@ -0,0 +1,433 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd +from .lists import * + +class Decoder(srd.Decoder): + api_version = 3 + id = 'eeprom24xx' + name = '24xx EEPROM' + longname = '24xx I²C EEPROM' + desc = '24xx series I²C EEPROM protocol.' + license = 'gplv2+' + inputs = ['i2c'] + outputs = [] + tags = ['IC', 'Memory'] + options = ( + {'id': 'chip', 'desc': 'Chip', 'default': 'generic', + 'values': tuple(chips.keys())}, + {'id': 'addr_counter', 'desc': 'Initial address counter value', + 'default': 0}, + ) + annotations = ( + # Warnings + ('warnings', 'Warnings'), + # Bits/bytes + ('control-code', 'Control code'), + ('address-pin', 'Address pin (A0/A1/A2)'), + ('rw-bit', 'Read/write bit'), + ('word-addr-byte', 'Word address byte'), + ('data-byte', 'Data byte'), + # Fields + ('control-word', 'Control word'), + ('word-addr', 'Word address'), + ('data', 'Data'), + # Operations + ('byte-write', 'Byte write'), + ('page-write', 'Page write'), + ('cur-addr-read', 'Current address read'), + ('random-read', 'Random read'), + ('seq-random-read', 'Sequential random read'), + ('seq-cur-addr-read', 'Sequential current address read'), + ('ack-polling', 'Acknowledge polling'), + ('set-bank-addr', 'Set bank address'), # SBA. Only 34AA04. + ('read-bank-addr', 'Read bank address'), # RBA. Only 34AA04. + ('set-wp', 'Set write protection'), # SWP + ('clear-all-wp', 'Clear all write protection'), # CWP + ('read-wp', 'Read write protection status'), # RPS + ) + annotation_rows = ( + ('bits-bytes', 'Bits/bytes', (1, 2, 3, 4, 5)), + ('fields', 'Fields', (6, 7, 8)), + ('ops', 'Operations', tuple(range(9, 21))), + ('warnings', 'Warnings', (0,)), + ) + binary = ( + ('binary', 'Binary'), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.reset_variables() + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + self.out_binary = self.register(srd.OUTPUT_BINARY) + self.chip = chips[self.options['chip']] + self.addr_counter = self.options['addr_counter'] + + def putb(self, data): + self.put(self.ss_block, self.es_block, self.out_ann, data) + + def putbin(self, data): + self.put(self.ss_block, self.es_block, self.out_binary, data) + + def putbits(self, bit1, bit2, bits, data): + self.put(bits[bit1][1], bits[bit2][2], self.out_ann, data) + + def reset_variables(self): + self.state = 'WAIT FOR START' + self.packets = [] + self.bytebuf = [] + self.is_cur_addr_read = False + self.is_random_access_read = False + self.is_seq_random_read = False + self.is_byte_write = False + self.is_page_write = False + + def packet_append(self): + self.packets.append([self.ss, self.es, self.cmd, self.databyte, self.bits]) + if self.cmd in ('DATA READ', 'DATA WRITE'): + self.bytebuf.append(self.databyte) + + def hexbytes(self, idx): + return ' '.join(['%02X' % b for b in self.bytebuf[idx:]]) + + def put_control_word(self, bits): + s = ''.join(['%d' % b[0] for b in reversed(bits[4:])]) + self.putbits(7, 4, bits, [1, ['Control code bits: ' + s, + 'Control code: ' + s, 'Ctrl code: ' + s, 'Ctrl code', 'Ctrl', 'C']]) + for i in reversed(range(self.chip['addr_pins'])): + self.putbits(i + 1, i + 1, bits, + [2, ['Address bit %d: %d' % (i, bits[i + 1][0]), + 'Addr bit %d' % i, 'A%d' % i, 'A']]) + s1 = 'read' if bits[0][0] == 1 else 'write' + s2 = 'R' if bits[0][0] == 1 else 'W' + self.putbits(0, 0, bits, [3, ['R/W bit: ' + s1, 'R/W', 'RW', s2]]) + self.putbits(7, 0, bits, [6, ['Control word', 'Control', 'CW', 'C']]) + + def put_word_addr(self, p): + if self.chip['addr_bytes'] == 1: + a = p[1][3] + self.put(p[1][0], p[1][1], self.out_ann, + [4, ['Word address byte: %02X' % a, 'Word addr byte: %02X' % a, + 'Addr: %02X' % a, 'A: %02X' % a, '%02X' % a]]) + self.put(p[1][0], p[1][1], self.out_ann, [7, ['Word address', + 'Word addr', 'Addr', 'A']]) + self.addr_counter = a + else: + a = p[1][3] + self.put(p[1][0], p[1][1], self.out_ann, + [4, ['Word address high byte: %02X' % a, + 'Word addr high byte: %02X' % a, + 'Addr high: %02X' % a, 'AH: %02X' % a, '%02X' % a]]) + a = p[2][3] + self.put(p[2][0], p[2][1], self.out_ann, + [4, ['Word address low byte: %02X' % a, + 'Word addr low byte: %02X' % a, + 'Addr low: %02X' % a, 'AL: %02X' % a, '%02X' % a]]) + self.put(p[1][0], p[2][1], self.out_ann, [7, ['Word address', + 'Word addr', 'Addr', 'A']]) + self.addr_counter = (p[1][3] << 8) | p[2][3] + + def put_data_byte(self, p): + if self.chip['addr_bytes'] == 1: + s = '%02X' % self.addr_counter + else: + s = '%04X' % self.addr_counter + self.put(p[0], p[1], self.out_ann, [5, ['Data byte %s: %02X' % \ + (s, p[3]), 'Data byte: %02X' % p[3], \ + 'Byte: %02X' % p[3], 'DB: %02X' % p[3], '%02X' % p[3]]]) + + def put_data_bytes(self, idx, cls, s): + for p in self.packets[idx:]: + self.put_data_byte(p) + self.addr_counter += 1 + self.put(self.packets[idx][0], self.packets[-1][1], self.out_ann, + [8, ['Data', 'D']]) + a = ''.join(['%s' % c[0] for c in s.split()]).upper() + self.putb([cls, ['%s (%s): %s' % (s, self.addr_and_len(), \ + self.hexbytes(self.chip['addr_bytes'])), + '%s (%s)' % (s, self.addr_and_len()), s, a, s[0]]]) + self.putbin([0, bytes(self.bytebuf[self.chip['addr_bytes']:])]) + + def addr_and_len(self): + if self.chip['addr_bytes'] == 1: + a = '%02X' % self.bytebuf[0] + else: + a = '%02X%02X' % tuple(self.bytebuf[:2]) + num_data_bytes = len(self.bytebuf) - self.chip['addr_bytes'] + d = '%d bytes' % num_data_bytes + if num_data_bytes <= 1: + d = d[:-1] + return 'addr=%s, %s' % (a, d) + + def decide_on_seq_or_rnd_read(self): + if len(self.bytebuf) < 2: + self.reset_variables() + return + if len(self.bytebuf) == 2: + self.is_random_access_read = True + else: + self.is_seq_random_read = True + + def put_operation(self): + idx = 1 + self.chip['addr_bytes'] + if self.is_byte_write: + # Byte write: word address, one data byte. + self.put_word_addr(self.packets) + self.put_data_bytes(idx, 9, 'Byte write') + elif self.is_page_write: + # Page write: word address, two or more data bytes. + self.put_word_addr(self.packets) + intitial_addr = self.addr_counter + self.put_data_bytes(idx, 10, 'Page write') + num_bytes_to_write = len(self.packets[idx:]) + if num_bytes_to_write > self.chip['page_size']: + self.putb([0, ['Warning: Wrote %d bytes but page size is ' + 'only %d bytes!' % (num_bytes_to_write, + self.chip['page_size'])]]) + page1 = int(intitial_addr / self.chip['page_size']) + page2 = int((self.addr_counter - 1) / self.chip['page_size']) + if page1 != page2: + self.putb([0, ['Warning: Page write crossed page boundary ' + 'from page %d to %d!' % (page1, page2)]]) + elif self.is_cur_addr_read: + # Current address read: no word address, one data byte. + self.put_data_byte(self.packets[1]) + self.put(self.packets[1][0], self.packets[-1][1], self.out_ann, + [8, ['Data', 'D']]) + self.putb([11, ['Current address read: %02X' % self.bytebuf[0], + 'Current address read', 'Cur addr read', 'CAR', 'C']]) + self.putbin([0, bytes([self.bytebuf[0]])]) + self.addr_counter += 1 + elif self.is_random_access_read: + # Random access read: word address, one data byte. + self.put_control_word(self.packets[idx][4]) + self.put_word_addr(self.packets) + self.put_data_bytes(idx + 1, 12, 'Random access read') + elif self.is_seq_random_read: + # Sequential random read: word address, two or more data bytes. + self.put_control_word(self.packets[idx][4]) + self.put_word_addr(self.packets) + self.put_data_bytes(idx + 1, 13, 'Sequential random read') + + def handle_wait_for_start(self): + # Wait for an I²C START condition. + if self.cmd not in ('START', 'START REPEAT'): + return + self.ss_block = self.ss + self.state = 'GET CONTROL WORD' + + def handle_get_control_word(self): + # The packet after START must be an ADDRESS READ or ADDRESS WRITE. + if self.cmd not in ('ADDRESS READ', 'ADDRESS WRITE'): + self.reset_variables() + return + self.packet_append() + self.put_control_word(self.bits) + self.state = '%s GET ACK NACK AFTER CONTROL WORD' % self.cmd[8] + + def handle_r_get_ack_nack_after_control_word(self): + if self.cmd == 'ACK': + self.state = 'R GET WORD ADDR OR BYTE' + elif self.cmd == 'NACK': + self.es_block = self.es + self.putb([0, ['Warning: No reply from slave!']]) + self.reset_variables() + else: + self.reset_variables() + + def handle_r_get_word_addr_or_byte(self): + if self.cmd == 'STOP': + self.es_block = self.es + self.putb([0, ['Warning: Slave replied, but master aborted!']]) + self.reset_variables() + return + elif self.cmd != 'DATA READ': + self.reset_variables() + return + self.packet_append() + self.state = 'R GET ACK NACK AFTER WORD ADDR OR BYTE' + + def handle_r_get_ack_nack_after_word_addr_or_byte(self): + if self.cmd == 'ACK': + self.state = 'R GET RESTART' + elif self.cmd == 'NACK': + self.is_cur_addr_read = True + self.state = 'GET STOP AFTER LAST BYTE' + else: + self.reset_variables() + + def handle_r_get_restart(self): + if self.cmd == 'RESTART': + self.state = 'R READ BYTE' + else: + self.reset_variables() + + def handle_r_read_byte(self): + if self.cmd == 'DATA READ': + self.packet_append() + self.state = 'R GET ACK NACK AFTER BYTE WAS READ' + else: + self.reset_variables() + + def handle_r_get_ack_nack_after_byte_was_read(self): + if self.cmd == 'ACK': + self.state = 'R READ BYTE' + elif self.cmd == 'NACK': + # It's either a RANDOM READ or a SEQUENTIAL READ. + self.state = 'GET STOP AFTER LAST BYTE' + else: + self.reset_variables() + + def handle_w_get_ack_nack_after_control_word(self): + if self.cmd == 'ACK': + self.state = 'W GET WORD ADDR' + elif self.cmd == 'NACK': + self.es_block = self.es + self.putb([0, ['Warning: No reply from slave!']]) + self.reset_variables() + else: + self.reset_variables() + + def handle_w_get_word_addr(self): + if self.cmd == 'STOP': + self.es_block = self.es + self.putb([0, ['Warning: Slave replied, but master aborted!']]) + self.reset_variables() + return + elif self.cmd != 'DATA WRITE': + self.reset_variables() + return + self.packet_append() + self.state = 'W GET ACK AFTER WORD ADDR' + + def handle_w_get_ack_after_word_addr(self): + if self.cmd == 'ACK': + self.state = 'W DETERMINE EEPROM READ OR WRITE' + else: + self.reset_variables() + + def handle_w_determine_eeprom_read_or_write(self): + if self.cmd == 'START REPEAT': + # It's either a RANDOM ACCESS READ or SEQUENTIAL RANDOM READ. + self.state = 'R2 GET CONTROL WORD' + elif self.cmd == 'DATA WRITE': + self.packet_append() + self.state = 'W GET ACK NACK AFTER BYTE WAS WRITTEN' + else: + self.reset_variables() + + def handle_w_write_byte(self): + if self.cmd == 'DATA WRITE': + self.packet_append() + self.state = 'W GET ACK NACK AFTER BYTE WAS WRITTEN' + elif self.cmd == 'STOP': + if len(self.bytebuf) < 2: + self.reset_variables() + return + self.es_block = self.es + if len(self.bytebuf) == 2: + self.is_byte_write = True + else: + self.is_page_write = True + self.put_operation() + self.reset_variables() + elif self.cmd == 'START REPEAT': + # It's either a RANDOM ACCESS READ or SEQUENTIAL RANDOM READ. + self.state = 'R2 GET CONTROL WORD' + else: + self.reset_variables() + + def handle_w_get_ack_nack_after_byte_was_written(self): + if self.cmd == 'ACK': + self.state = 'W WRITE BYTE' + else: + self.reset_variables() + + def handle_r2_get_control_word(self): + if self.cmd == 'ADDRESS READ': + self.packet_append() + self.state = 'R2 GET ACK AFTER ADDR READ' + else: + self.reset_variables() + + def handle_r2_get_ack_after_addr_read(self): + if self.cmd == 'ACK': + self.state = 'R2 READ BYTE' + else: + self.reset_variables() + + def handle_r2_read_byte(self): + if self.cmd == 'DATA READ': + self.packet_append() + self.state = 'R2 GET ACK NACK AFTER BYTE WAS READ' + elif self.cmd == 'STOP': + self.decide_on_seq_or_rnd_read() + self.es_block = self.es + self.putb([0, ['Warning: STOP expected after a NACK (not ACK)']]) + self.put_operation() + self.reset_variables() + else: + self.reset_variables() + + def handle_r2_get_ack_nack_after_byte_was_read(self): + if self.cmd == 'ACK': + self.state = 'R2 READ BYTE' + elif self.cmd == 'NACK': + self.decide_on_seq_or_rnd_read() + self.state = 'GET STOP AFTER LAST BYTE' + else: + self.reset_variables() + + def handle_get_stop_after_last_byte(self): + if self.cmd == 'STOP': + self.es_block = self.es + self.put_operation() + self.reset_variables() + elif self.cmd == 'START REPEAT': + self.es_block = self.es + self.putb([0, ['Warning: STOP expected (not RESTART)']]) + self.put_operation() + self.reset_variables() + self.ss_block = self.ss + self.state = 'GET CONTROL WORD' + else: + self.reset_variables() + + def decode(self, ss, es, data): + self.cmd, self.databyte = data + + # Collect the 'BITS' packet, then return. The next packet is + # guaranteed to belong to these bits we just stored. + if self.cmd == 'BITS': + self.bits = self.databyte + return + + # Store the start/end samples of this I²C packet. + self.ss, self.es = ss, es + + # State machine. + s = 'handle_%s' % self.state.lower().replace(' ', '_') + handle_state = getattr(self, s) + handle_state() diff --git a/libsigrokdecode4DSL/decoders/eeprom93xx/__init__.py b/libsigrokdecode4DSL/decoders/eeprom93xx/__init__.py new file mode 100644 index 00000000..c8eaf7a0 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/eeprom93xx/__init__.py @@ -0,0 +1,32 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2017 Kevin Redon +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This decoder stacks on top of the 'microwire' PD and decodes the 93xx EEPROM +specific instructions. + +The implemented instructions come from the STMicroelectronics M93Cx6 EEPROM +datasheet. They are compatible with the Atmel AT93Cxx EEPROM with slightly +different names. + +Warning: Other EEPROMs using Microwire might have different operation codes +and instructions. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/eeprom93xx/pd.py b/libsigrokdecode4DSL/decoders/eeprom93xx/pd.py new file mode 100644 index 00000000..7b64e59a --- /dev/null +++ b/libsigrokdecode4DSL/decoders/eeprom93xx/pd.py @@ -0,0 +1,141 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2017 Kevin Redon +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd + +class Decoder(srd.Decoder): + api_version = 3 + id = 'eeprom93xx' + name = '93xx EEPROM' + longname = '93xx Microwire EEPROM' + desc = '93xx series Microwire EEPROM protocol.' + license = 'gplv2+' + inputs = ['microwire'] + outputs = [] + tags = ['IC', 'Memory'] + options = ( + {'id': 'addresssize', 'desc': 'Address size', 'default': 8}, + {'id': 'wordsize', 'desc': 'Word size', 'default': 16}, + ) + annotations = ( + ('si-data', 'SI data'), + ('so-data', 'SO data'), + ('warning', 'Warning'), + ) + annotation_rows = ( + ('data', 'Data', (0, 1)), + ('warnings', 'Warnings', (2,)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.frame = [] + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + self.addresssize = self.options['addresssize'] + self.wordsize = self.options['wordsize'] + + def put_address(self, data): + # Get address (MSb first). + a = 0 + for b in range(len(data)): + a += (data[b].si << (len(data) - b - 1)) + self.put(data[0].ss, data[-1].es, self.out_ann, + [0, ['Address: 0x%x' % a, 'Addr: 0x%x' % a, '0x%x' % a]]) + + def put_word(self, si, data): + # Decode word (MSb first). + word = 0 + for b in range(len(data)): + d = data[b].si if si else data[b].so + word += (d << (len(data) - b - 1)) + idx = 0 if si else 1 + self.put(data[0].ss, data[-1].es, + self.out_ann, [idx, ['Data: 0x%x' % word, '0x%x' % word]]) + + def decode(self, ss, es, data): + if len(data) < (2 + self.addresssize): + self.put(ss, es, self.out_ann, [2, ['Not enough packet bits']]) + return + + opcode = (data[0].si << 1) + (data[1].si << 0) + + if opcode == 2: + # READ instruction. + self.put(data[0].ss, data[1].es, + self.out_ann, [0, ['Read word', 'READ']]) + self.put_address(data[2:2 + self.addresssize]) + + # Get all words. + word_start = 2 + self.addresssize + while len(data) - word_start > 0: + # Check if there are enough bits for a word. + if len(data) - word_start < self.wordsize: + self.put(data[word_start].ss, data[len(data) - 1].es, + self.out_ann, [2, ['Not enough word bits']]) + break + self.put_word(False, data[word_start:word_start + self.wordsize]) + # Go to next word. + word_start += self.wordsize + elif opcode == 1: + # WRITE instruction. + self.put(data[0].ss, data[1].es, + self.out_ann, [0, ['Write word', 'WRITE']]) + self.put_address(data[2:2 + self.addresssize]) + # Get word. + if len(data) < 2 + self.addresssize + self.wordsize: + self.put(data[2 + self.addresssize].ss, + data[len(data) - 1].ss, + self.out_ann, [2, ['Not enough word bits']]) + else: + self.put_word(True, data[2 + self.addresssize:2 + self.addresssize + self.wordsize]) + elif opcode == 3: + # ERASE instruction. + self.put(data[0].ss, data[1].es, + self.out_ann, [0, ['Erase word', 'ERASE']]) + self.put_address(data[2:2 + self.addresssize]) + elif opcode == 0: + if data[2].si == 1 and data[3].si == 1: + # WEN instruction. + self.put(data[0].ss, data[2 + self.addresssize - 1].es, + self.out_ann, [0, ['Write enable', 'WEN']]) + elif data[2].si == 0 and data[3].si == 0: + # WDS instruction. + self.put(data[0].ss, data[2 + self.addresssize - 1].es, + self.out_ann, [0, ['Write disable', 'WDS']]) + elif data[2].si == 1 and data[3].si == 0: + # ERAL instruction. + self.put(data[0].ss, data[2 + self.addresssize - 1].es, + self.out_ann, [0, ['Erase all memory', + 'Erase all', 'ERAL']]) + elif data[2].si == 0 and data[3].si == 1: + # WRAL instruction. + self.put(data[0].ss, data[2 + self.addresssize - 1].es, + self.out_ann, [0, ['Write all memory', + 'Write all', 'WRAL']]) + # Get word. + if len(data) < 2 + self.addresssize + self.wordsize: + self.put(data[2 + self.addresssize].ss, + data[len(data) - 1].ss, + self.out_ann, [2, ['Not enough word bits']]) + else: + self.put_word(True, data[2 + self.addresssize:2 + self.addresssize + self.wordsize]) diff --git a/libsigrokdecode4DSL/decoders/em4100/__init__.py b/libsigrokdecode4DSL/decoders/em4100/__init__.py new file mode 100644 index 00000000..c3c95e28 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/em4100/__init__.py @@ -0,0 +1,24 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2015 Benjamin Larsson +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +EM4100 is a biphase/manchester/PSK based 100-150kHz RFID protocol. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/em4100/pd.py b/libsigrokdecode4DSL/decoders/em4100/pd.py new file mode 100644 index 00000000..7f42ad70 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/em4100/pd.py @@ -0,0 +1,238 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2015 Benjamin Larsson +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd + +class SamplerateError(Exception): + pass + +class Decoder(srd.Decoder): + api_version = 3 + id = 'em4100' + name = 'EM4100' + longname = 'RFID EM4100' + desc = 'EM4100 100-150kHz RFID protocol.' + license = 'gplv2+' + inputs = ['logic'] + outputs = [] + tags = ['IC', 'RFID'] + channels = ( + {'id': 'data', 'name': 'Data', 'desc': 'Data line'}, + ) + options = ( + {'id': 'polarity', 'desc': 'Polarity', 'default': 'active-high', + 'values': ('active-low', 'active-high')}, + {'id': 'datarate' , 'desc': 'Data rate', 'default': 64, + 'values': (64, 32, 16)}, +# {'id': 'coding', 'desc': 'Bit coding', 'default': 'biphase', +# 'values': ('biphase', 'manchester', 'psk')}, + {'id': 'coilfreq', 'desc': 'Coil frequency', 'default': 125000}, + ) + annotations = ( + ('bit', 'Bit'), + ('header', 'Header'), + ('version-customer', 'Version/customer'), + ('data', 'Data'), + ('rowparity-ok', 'Row parity OK'), + ('rowparity-err', 'Row parity error'), + ('colparity-ok', 'Column parity OK'), + ('colparity-err', 'Column parity error'), + ('stopbit', 'Stop bit'), + ('tag', 'Tag'), + ) + annotation_rows = ( + ('bits', 'Bits', (0,)), + ('fields', 'Fields', (1, 2, 3, 4, 5, 6, 7, 8)), + ('tags', 'Tags', (9,)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.samplerate = None + self.oldpin = None + self.last_samplenum = None + self.lastlast_samplenum = None + self.last_edge = 0 + self.bit_width = 0 + self.halfbit_limit = 0 + self.oldpp = 0 + self.oldpl = 0 + self.oldsamplenum = 0 + self.last_bit_pos = 0 + self.ss_first = 0 + self.first_one = 0 + self.state = 'HEADER' + self.data = 0 + self.data_bits = 0 + self.ss_data = 0 + self.data_parity = 0 + self.payload_cnt = 0 + self.data_col_parity = [0, 0, 0, 0, 0, 0] + self.col_parity = [0, 0, 0, 0, 0, 0] + self.tag = 0 + self.all_row_parity_ok = True + self.col_parity_pos = [] + + def metadata(self, key, value): + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + self.bit_width = (self.samplerate / self.options['coilfreq']) * self.options['datarate'] + self.halfbit_limit = self.bit_width/2 + self.bit_width/4 + self.polarity = 0 if self.options['polarity'] == 'active-low' else 1 + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def putbit(self, bit, ss, es): + self.put(ss, es, self.out_ann, [0, [str(bit)]]) + if self.state == 'HEADER': + if bit == 1: + if self.first_one > 0: + self.first_one += 1 + if self.first_one == 9: + self.put(self.ss_first, es, self.out_ann, + [1, ['Header', 'Head', 'He', 'H']]) + self.first_one = 0 + self.state = 'PAYLOAD' + return + if self.first_one == 0: + self.first_one = 1 + self.ss_first = ss + + if bit == 0: + self.first_one = 0 + return + + if self.state == 'PAYLOAD': + self.payload_cnt += 1 + if self.data_bits == 0: + self.ss_data = ss + self.data = 0 + self.data_parity = 0 + self.data_bits += 1 + if self.data_bits == 5: + s = 'Version/customer' if self.payload_cnt <= 10 else 'Data' + c = 2 if self.payload_cnt <= 10 else 3 + self.put(self.ss_data, ss, self.out_ann, + [c, [s + ': %X' % self.data, '%X' % self.data]]) + s = 'OK' if self.data_parity == bit else 'ERROR' + c = 4 if s == 'OK' else 5 + if s == 'ERROR': + self.all_row_parity_ok = False + self.put(ss, es, self.out_ann, + [c, ['Row parity: ' + s, 'RP: ' + s, 'RP', 'R']]) + self.tag = (self.tag << 4) | self.data + self.data_bits = 0 + if self.payload_cnt == 50: + self.state = 'TRAILER' + self.payload_cnt = 0 + + self.data_parity ^= bit + self.data_col_parity[self.data_bits] ^= bit + self.data = (self.data << 1) | bit + return + + if self.state == 'TRAILER': + self.payload_cnt += 1 + if self.data_bits == 0: + self.ss_data = ss + self.data = 0 + self.data_parity = 0 + self.data_bits += 1 + self.col_parity[self.data_bits] = bit + self.col_parity_pos.append([ss, es]) + + if self.data_bits == 5: + self.put(ss, es, self.out_ann, [8, ['Stop bit', 'SB', 'S']]) + + for i in range(1, 5): + s = 'OK' if self.data_col_parity[i] == \ + self.col_parity[i] else 'ERROR' + c = 6 if s == 'OK' else 7 + self.put(self.col_parity_pos[i - 1][0], + self.col_parity_pos[i - 1][1], self.out_ann, + [c, ['Column parity %d: %s' % (i, s), + 'CP%d: %s' % (i, s), 'CP%d' % i, 'C']]) + + # Emit an annotation for valid-looking tags. + all_col_parity_ok = (self.data_col_parity[1:5] == self.col_parity[1:5]) + if all_col_parity_ok and self.all_row_parity_ok: + self.put(self.ss_first, es, self.out_ann, + [9, ['Tag: %010X' % self.tag, 'Tag', 'T']]) + + self.tag = 0 + self.data_bits = 0 + + if self.payload_cnt == 5: + self.state = 'HEADER' + self.payload_cnt = 0 + self.data_col_parity = [0, 0, 0, 0, 0, 0] + self.col_parity = [0, 0, 0, 0, 0, 0] + self.col_parity_pos = [] + self.all_row_parity_ok = True + + def manchester_decode(self, pl, pp, pin): + bit = self.oldpin ^ self.polarity + if pl > self.halfbit_limit: + es = int(self.samplenum - pl/2) + if self.oldpl > self.halfbit_limit: + ss = int(self.oldsamplenum - self.oldpl/2) + else: + ss = int(self.oldsamplenum - self.oldpl) + self.putbit(bit, ss, es) + self.last_bit_pos = int(self.samplenum - pl/2) + else: + es = int(self.samplenum) + if self.oldpl > self.halfbit_limit: + ss = int(self.oldsamplenum - self.oldpl/2) + self.putbit(bit, ss, es) + self.last_bit_pos = int(self.samplenum) + else: + if self.last_bit_pos <= self.oldsamplenum - self.oldpl: + ss = int(self.oldsamplenum - self.oldpl) + self.putbit(bit, ss, es) + self.last_bit_pos = int(self.samplenum) + + def decode(self): + if not self.samplerate: + raise SamplerateError('Cannot decode without samplerate.') + + # Initialize internal state from the very first sample. + (pin,) = self.wait() + self.oldpin = pin + self.last_samplenum = self.samplenum + self.lastlast_samplenum = self.samplenum + self.last_edge = self.samplenum + self.oldpl = 0 + self.oldpp = 0 + self.oldsamplenum = 0 + self.last_bit_pos = 0 + + while True: + # Ignore identical samples, only process edges. + (pin,) = self.wait({0: 'e'}) + pl = self.samplenum - self.oldsamplenum + pp = pin + self.manchester_decode(pl, pp, pin) + self.oldpl = pl + self.oldpp = pp + self.oldsamplenum = self.samplenum + self.oldpin = pin diff --git a/libsigrokdecode4DSL/decoders/em4305/__init__.py b/libsigrokdecode4DSL/decoders/em4305/__init__.py new file mode 100644 index 00000000..df437787 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/em4305/__init__.py @@ -0,0 +1,24 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2015 Benjamin Larsson +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +EM4305 is a 100-150kHz RFID protocol. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/em4305/pd.py b/libsigrokdecode4DSL/decoders/em4305/pd.py new file mode 100644 index 00000000..6297643c --- /dev/null +++ b/libsigrokdecode4DSL/decoders/em4305/pd.py @@ -0,0 +1,394 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2016 Benjamin Larsson +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd + +class SamplerateError(Exception): + pass + +class Decoder(srd.Decoder): + api_version = 3 + id = 'em4305' + name = 'EM4305' + longname = 'RFID EM4205/EM4305' + desc = 'EM4205/EM4305 100-150kHz RFID protocol.' + license = 'gplv2+' + inputs = ['logic'] + outputs = [] + tags = ['IC', 'RFID'] + channels = ( + {'id': 'data', 'name': 'Data', 'desc': 'Data line'}, + ) + options = ( + {'id': 'coilfreq', 'desc': 'Coil frequency', 'default': 125000}, + {'id': 'first_field_stop', 'desc': 'First field stop min', 'default': 40}, + {'id': 'w_gap', 'desc': 'Write gap min', 'default': 12}, + {'id': 'w_one_max', 'desc': 'Write one max', 'default': 32}, + {'id': 'w_zero_on_min', 'desc': 'Write zero on min', 'default': 15}, + {'id': 'w_zero_off_max', 'desc': 'Write zero off max', 'default': 27}, + {'id': 'em4100_decode', 'desc': 'EM4100 decode', 'default': 'on', + 'values': ('on', 'off')}, + ) + annotations = ( + ('bit_value', 'Bit value'), + ('first_field_stop', 'First field stop'), + ('write_gap', 'Write gap'), + ('write_mode_exit', 'Write mode exit'), + ('bit', 'Bit'), + ('opcode', 'Opcode'), + ('lock', 'Lock'), + ('data', 'Data'), + ('password', 'Password'), + ('address', 'Address'), + ('bitrate', 'Bitrate'), + ) + annotation_rows = ( + ('bits', 'Bits', (0,)), + ('structure', 'Structure', (1, 2, 3, 4)), + ('fields', 'Fields', (5, 6, 7, 8, 9)), + ('decode', 'Decode', (10,)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.samplerate = None + self.last_samplenum = None + self.state = 'FFS_SEARCH' + self.bits_pos = [[0 for col in range(3)] for row in range(70)] + self.br_string = ['RF/8', 'RF/16', 'Unused', 'RF/32', 'RF/40', + 'Unused', 'Unused', 'RF/64',] + self.encoder = ['not used', 'Manchester', 'Bi-phase', 'not used'] + self.delayed_on = ['No delay', 'Delayed on - BP/8', 'Delayed on - BP/4', 'No delay'] + self.em4100_decode1_partial = 0 + self.cmds = ['Invalid', 'Login', 'Write word', 'Invalid', 'Read word', 'Disable', 'Protect', 'Invalid'] + + def metadata(self, key, value): + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + self.field_clock = self.samplerate / self.options['coilfreq'] + self.wzmax = self.options['w_zero_off_max'] * self.field_clock + self.wzmin = self.options['w_zero_on_min'] * self.field_clock + self.womax = self.options['w_one_max'] * self.field_clock + self.ffs = self.options['first_field_stop'] * self.field_clock + self.writegap = self.options['w_gap'] * self.field_clock + self.nogap = 300 * self.field_clock + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def decode_config(self, idx): + bitrate = self.get_3_bits(idx+2) + self.put(self.bits_pos[idx][1], self.bits_pos[idx+5][2], + self.out_ann, [10, ['Data rate: ' + \ + self.br_string[bitrate], self.br_string[bitrate]]]) + encoding = self.bits_pos[idx+6][0]<<0 | self.bits_pos[idx+7][0]<<1 + self.put(self.bits_pos[idx+6][1], self.bits_pos[idx+10][2], + self.out_ann, [10, ['Encoder: ' + \ + self.encoder[encoding], self.encoder[encoding]]]) + self.put(self.bits_pos[idx+11][1], self.bits_pos[idx+12][2], self.out_ann, + [10, ['Zero bits', 'ZB']]) + delay_on = self.bits_pos[idx+13][0]<<0 | self.bits_pos[idx+14][0]<<1 + self.put(self.bits_pos[idx+13][1], self.bits_pos[idx+14][2], + self.out_ann, [10, ['Delayed on: ' + \ + self.delayed_on[delay_on], self.delayed_on[delay_on]]]) + lwr = self.bits_pos[idx+15][0]<<3 | self.bits_pos[idx+16][0]<<2 | \ + self.bits_pos[idx+18][0]<<1 | self.bits_pos[idx+19][0]<<0 + self.put(self.bits_pos[idx+15][1], self.bits_pos[idx+19][2], + self.out_ann, [10, ['Last default read word: %d' % lwr, 'LWR: %d' % lwr, '%d' % lwr]]) + self.put(self.bits_pos[idx+20][1], self.bits_pos[idx+20][2], + self.out_ann, [10, ['Read login: %d' % self.bits_pos[idx+20][0], '%d' % self.bits_pos[idx+20][0]]]) + self.put(self.bits_pos[idx+21][1], self.bits_pos[idx+21][2], self.out_ann, + [10, ['Zero bits', 'ZB']]) + self.put(self.bits_pos[idx+22][1], self.bits_pos[idx+22][2], + self.out_ann, [10, ['Write login: %d' % self.bits_pos[idx+22][0], '%d' % self.bits_pos[idx+22][0]]]) + self.put(self.bits_pos[idx+23][1], self.bits_pos[idx+24][2], self.out_ann, + [10, ['Zero bits', 'ZB']]) + self.put(self.bits_pos[idx+25][1], self.bits_pos[idx+25][2], + self.out_ann, [10, ['Disable: %d' % self.bits_pos[idx+25][0], '%d' % self.bits_pos[idx+25][0]]]) + self.put(self.bits_pos[idx+27][1], self.bits_pos[idx+27][2], + self.out_ann, [10, ['Reader talk first: %d' % self.bits_pos[idx+27][0], 'RTF: %d' % self.bits_pos[idx+27][0]]]) + self.put(self.bits_pos[idx+28][1], self.bits_pos[idx+28][2], self.out_ann, + [10, ['Zero bits', 'ZB']]) + self.put(self.bits_pos[idx+29][1], self.bits_pos[idx+29][2], + self.out_ann, [10, ['Pigeon mode: %d' % self.bits_pos[idx+29][0], '%d' % self.bits_pos[idx+29][0]]]) + self.put(self.bits_pos[idx+30][1], self.bits_pos[idx+34][2], + self.out_ann, [10, ['Reserved', 'Res', 'R']]) + + def put4bits(self, idx): + bits = self.bits_pos[idx][0]<<3 | self.bits_pos[idx+1][0]<<2 | \ + self.bits_pos[idx+2][0]<<1 | self.bits_pos[idx+3][0] + self.put(self.bits_pos[idx][1], self.bits_pos[idx+3][2], self.out_ann, + [10, ['%X' % bits]]) + + def em4100_decode1(self, idx): + self.put(self.bits_pos[idx][1], self.bits_pos[idx+9][2], self.out_ann, + [10, ['EM4100 header', 'EM header', 'Header', 'H']]) + self.put4bits(idx+10) + bits = self.bits_pos[idx+15][0]<<3 | self.bits_pos[idx+16][0]<<2 | \ + self.bits_pos[idx+18][0]<<1 | self.bits_pos[idx+19][0]<<0 + self.put(self.bits_pos[idx+15][1], self.bits_pos[idx+19][2], self.out_ann, + [10, ['%X' % bits]]) + self.put4bits(idx+21) + self.put4bits(idx+27) + self.em4100_decode1_partial = self.bits_pos[idx+32][0]<<3 | \ + self.bits_pos[idx+33][0]<<2 | self.bits_pos[idx+34][0]<<1 + self.put(self.bits_pos[idx+32][1], self.bits_pos[idx+34][2], + self.out_ann, [10, ['Partial nibble']]) + + def em4100_decode2(self, idx): + if self.em4100_decode1_partial != 0: + bits = self.em4100_decode1_partial + self.bits_pos[idx][0] + self.put(self.bits_pos[idx][1], self.bits_pos[idx][2], + self.out_ann, [10, ['%X' % bits]]) + self.em4100_decode1_partial = 0 + else: + self.put(self.bits_pos[idx][1], self.bits_pos[idx][2], + self.out_ann, [10, ['Partial nibble']]) + + self.put4bits(idx+2) + bits = self.bits_pos[idx+7][0]<<3 | self.bits_pos[idx+9][0]<<2 | \ + self.bits_pos[idx+10][0]<<1 | self.bits_pos[idx+11][0]<<0 + self.put(self.bits_pos[idx+7][1], self.bits_pos[idx+11][2], self.out_ann, + [10, ['%X' % bits]]) + self.put4bits(idx+13) + self.put4bits(idx+19) + bits = self.bits_pos[idx+24][0]<<3 | self.bits_pos[idx+25][0]<<2 | \ + self.bits_pos[idx+27][0]<<1 | self.bits_pos[idx+28][0]<<0 + self.put(self.bits_pos[idx+24][1], self.bits_pos[idx+28][2], self.out_ann, + [10, ['%X' % bits]]) + self.put(self.bits_pos[idx+30][1], self.bits_pos[idx+34][2], + self.out_ann, [10, ['EM4100 trailer']]) + + def get_32_bits(self, idx): + return self.get_8_bits(idx+27)<<24 | self.get_8_bits(idx+18)<<16 | \ + self.get_8_bits(idx+9)<<8 | self.get_8_bits(idx) + + def get_8_bits(self, idx): + retval = 0 + for i in range(0, 8): + retval <<= 1 + retval |= self.bits_pos[i+idx][0] + return retval + + def get_3_bits(self, idx): + return self.bits_pos[idx][0]<<2 | self.bits_pos[idx+1][0]<<1 | \ + self.bits_pos[idx+2][0] + + def get_4_bits(self, idx): + return self.bits_pos[idx][0]<<0 | self.bits_pos[idx+1][0]<<1 | \ + self.bits_pos[idx+2][0]<<2 | self.bits_pos[idx+3][0]<<3 + + def print_row_parity(self, idx, length): + parity = 0 + for i in range(0, length): + parity += self.bits_pos[i+idx][0] + parity = parity & 0x1 + if parity == self.bits_pos[idx+length][0]: + self.put(self.bits_pos[idx+length][1], self.bits_pos[idx+length][2], self.out_ann, + [5, ['Row parity OK', 'Parity OK', 'OK']]) + else: + self.put(self.bits_pos[idx+length][1], self.bits_pos[idx+length][2], self.out_ann, + [5, ['Row parity failed', 'Parity failed', 'Fail']]) + + def print_col_parity(self, idx): + data_1 = self.get_8_bits(idx) + data_2 = self.get_8_bits(idx+9) + data_3 = self.get_8_bits(idx+9+9) + data_4 = self.get_8_bits(idx+9+9+9) + col_par = self.get_8_bits(idx+9+9+9+9) + col_par_calc = data_1^data_2^data_3^data_4 + + if col_par == col_par_calc: + self.put(self.bits_pos[idx+9+9+9+9][1], self.bits_pos[idx+9+9+9+9+7][2], self.out_ann, + [5, ['Column parity OK', 'Parity OK', 'OK']]) + else: + self.put(self.bits_pos[idx+9+9+9+9][1], self.bits_pos[idx+9+9+9+9+7][2], self.out_ann, + [5, ['Column parity failed', 'Parity failed', 'Fail']]) + + def print_8bit_data(self, idx): + data = self.get_8_bits(idx) + self.put(self.bits_pos[idx][1], self.bits_pos[idx+7][2], self.out_ann, + [9, ['Data' + ': %X' % data, '%X' % data]]) + + def put_fields(self): + if self.bit_nr == 50: + self.put(self.bits_pos[0][1], self.bits_pos[0][2], self.out_ann, + [4, ['Logic zero']]) + self.put(self.bits_pos[1][1], self.bits_pos[4][2], self.out_ann, + [4, ['Command', 'Cmd', 'C']]) + self.put(self.bits_pos[5][1], self.bits_pos[49][2], self.out_ann, + [4, ['Password', 'Passwd', 'Pass', 'P']]) + # Get command. + cmd = self.get_3_bits(1) + self.put(self.bits_pos[1][1], self.bits_pos[3][2], self.out_ann, + [5, [self.cmds[cmd]]]) + self.print_row_parity(1, 3) + + # Print data. + self.print_8bit_data(5) + self.print_row_parity(5, 8) + self.print_8bit_data(14) + self.print_row_parity(14, 8) + self.print_8bit_data(23) + self.print_row_parity(23, 8) + self.print_8bit_data(32) + self.print_row_parity(32, 8) + self.print_col_parity(5) + if self.bits_pos[49][0] == 0: + self.put(self.bits_pos[49][1], self.bits_pos[49][2], self.out_ann, + [5, ['Stop bit', 'Stop', 'SB']]) + else: + self.put(self.bits_pos[49][1], self.bits_pos[49][2], self.out_ann, + [5, ['Stop bit error', 'Error']]) + + if cmd == 1: + password = self.get_32_bits(5) + self.put(self.bits_pos[12][1], self.bits_pos[46][2], self.out_ann, + [10, ['Login password: %X' % password]]) + + if self.bit_nr == 57: + self.put(self.bits_pos[0][1], self.bits_pos[0][2], self.out_ann, + [4, ['Logic zero', 'LZ']]) + self.put(self.bits_pos[1][1], self.bits_pos[4][2], self.out_ann, + [4, ['Command', 'Cmd', 'C']]) + self.put(self.bits_pos[5][1], self.bits_pos[11][2], self.out_ann, + [4, ['Address', 'Addr', 'A']]) + self.put(self.bits_pos[12][1], self.bits_pos[56][2], self.out_ann, + [4, ['Data', 'Da', 'D']]) + + # Get command. + cmd = self.get_3_bits(1) + self.put(self.bits_pos[1][1], self.bits_pos[3][2], self.out_ann, + [5, [self.cmds[cmd]]]) + self.print_row_parity(1, 3) + + # Get address. + addr = self.get_4_bits(5) + self.put(self.bits_pos[5][1], self.bits_pos[8][2], self.out_ann, + [9, ['Addr' + ': %d' % addr, '%d' % addr]]) + self.put(self.bits_pos[9][1], self.bits_pos[10][2], self.out_ann, + [5, ['Zero bits', 'ZB']]) + self.print_row_parity(5, 6) + # Print data. + self.print_8bit_data(12) + self.print_row_parity(12, 8) + self.print_8bit_data(21) + self.print_row_parity(21, 8) + self.print_8bit_data(30) + self.print_row_parity(30, 8) + self.print_8bit_data(39) + self.print_row_parity(39, 8) + self.print_col_parity(12) + if self.bits_pos[56][0] == 0: + self.put(self.bits_pos[56][1], self.bits_pos[56][2], self.out_ann, + [5, ['Stop bit', 'Stop', 'SB']]) + else: + self.put(self.bits_pos[56][1], self.bits_pos[56][2], self.out_ann, + [5, ['Stop bit error', 'Error']]) + + if addr == 4: + self.decode_config(12) + + if addr == 2: + password = self.get_32_bits(12) + self.put(self.bits_pos[12][1], self.bits_pos[46][2], self.out_ann, + [10, ['Write password: %X' % password]]) + + # If we are programming EM4100 data we can decode it halfway. + if addr == 5 and self.options['em4100_decode'] == 'on': + self.em4100_decode1(12) + if addr == 6 and self.options['em4100_decode'] == 'on': + self.em4100_decode2(12) + + self.bit_nr = 0 + + def add_bits_pos(self, bit, ss_bit, es_bit): + if self.bit_nr < 70: + self.bits_pos[self.bit_nr][0] = bit + self.bits_pos[self.bit_nr][1] = ss_bit + self.bits_pos[self.bit_nr][2] = es_bit + self.bit_nr += 1 + + def decode(self): + if not self.samplerate: + raise SamplerateError('Cannot decode without samplerate.') + + # Initialize internal state. + self.last_samplenum = self.samplenum + self.oldsamplenum = 0 + self.old_gap_end = 0 + self.gap_detected = 0 + self.bit_nr = 0 + + while True: + # Ignore identical samples, only process edges. + (pin,) = self.wait({0: 'e'}) + + pl = self.samplenum - self.oldsamplenum + pp = pin + samples = self.samplenum - self.last_samplenum + + if self.state == 'FFS_DETECTED': + if pl > self.writegap: + self.gap_detected = 1 + if (self.last_samplenum - self.old_gap_end) > self.nogap: + self.gap_detected = 0 + self.state = 'FFS_SEARCH' + self.put(self.old_gap_end, self.last_samplenum, + self.out_ann, [3, ['Write mode exit']]) + self.put_fields() + + if self.state == 'FFS_SEARCH': + if pl > self.ffs: + self.gap_detected = 1 + self.put(self.last_samplenum, self.samplenum, + self.out_ann, [1, ['First field stop', 'Field stop', 'FFS']]) + self.state = 'FFS_DETECTED' + + if self.gap_detected == 1: + self.gap_detected = 0 + if (self.last_samplenum - self.old_gap_end) > self.wzmin \ + and (self.last_samplenum - self.old_gap_end) < self.wzmax: + self.put(self.old_gap_end, self.samplenum, + self.out_ann, [0, ['0']]) + self.add_bits_pos(0, self.old_gap_end, self.samplenum) + if (self.last_samplenum - self.old_gap_end) > self.womax \ + and (self.last_samplenum-self.old_gap_end) < self.nogap: + # One or more 1 bits + one_bits = (int)((self.last_samplenum - self.old_gap_end) / self.womax) + for ox in range(0, one_bits): + bs = (int)(self.old_gap_end+ox*self.womax) + be = (int)(self.old_gap_end+ox*self.womax + self.womax) + self.put(bs, be, self.out_ann, [0, ['1']]) + self.add_bits_pos(1, bs, be) + if (self.samplenum - self.last_samplenum) > self.wzmin \ + and (self.samplenum - self.last_samplenum) < self.wzmax: + bs = (int)(self.old_gap_end+one_bits*self.womax) + self.put(bs, self.samplenum, self.out_ann, [0, ['0']]) + self.add_bits_pos(0, bs, self.samplenum) + + self.old_gap_end = self.samplenum + + if self.state == 'SKIP': + self.state = 'FFS_SEARCH' + + self.oldsamplenum = self.samplenum + self.last_samplenum = self.samplenum diff --git a/libsigrokdecode4DSL/decoders/enc28j60/__init__.py b/libsigrokdecode4DSL/decoders/enc28j60/__init__.py new file mode 100644 index 00000000..42f4377e --- /dev/null +++ b/libsigrokdecode4DSL/decoders/enc28j60/__init__.py @@ -0,0 +1,32 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2019 Jiahao Li +## +## Permission is hereby granted, free of charge, to any person obtaining a copy +## of this software and associated documentation files (the "Software"), to deal +## in the Software without restriction, including without limitation the rights +## to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +## copies of the Software, and to permit persons to whom the Software is +## furnished to do so, subject to the following conditions: +## +## The above copyright notice and this permission notice shall be included in all +## copies or substantial portions of the Software. +## +## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +## IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +## FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +## AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +## LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +## OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +## SOFTWARE. + +''' +This decoder stacks on top of the 'spi' PD and decodes the protocol spoken +by the Microchip ENC28J60 Ethernet chip. + +Details: +http://ww1.microchip.com/downloads/en/DeviceDoc/39662e.pdf +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/enc28j60/lists.py b/libsigrokdecode4DSL/decoders/enc28j60/lists.py new file mode 100644 index 00000000..59fbc1f2 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/enc28j60/lists.py @@ -0,0 +1,161 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2019 Jiahao Li +## +## Permission is hereby granted, free of charge, to any person obtaining a copy +## of this software and associated documentation files (the "Software"), to deal +## in the Software without restriction, including without limitation the rights +## to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +## copies of the Software, and to permit persons to whom the Software is +## furnished to do so, subject to the following conditions: +## +## The above copyright notice and this permission notice shall be included in all +## copies or substantial portions of the Software. +## +## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +## IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +## FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +## AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +## LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +## OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +## SOFTWARE. + +REGS = [ + [ + 'ERDPTL', + 'ERDPTH', + 'EWRPTL', + 'EWRPTH', + 'ETXSTL', + 'ETXSTH', + 'ETXNDL', + 'ETXNDH', + 'ERXSTL', + 'ERXSTH', + 'ERXNDL', + 'ERXNDH', + 'ERXRDPTL', + 'ERXRDPTH', + 'ERXWRPTL', + 'ERXWRPTH', + 'EDMASTL', + 'EDMASTH', + 'EDMANDL', + 'EDMANDH', + 'EDMADSTL', + 'EDMADSTH', + 'EDMACSL', + 'EDMACSH', + '—', + '—', + 'Reserved', + 'EIE', + 'EIR', + 'ESTAT', + 'ECON2', + 'ECON1', + ], + [ + 'EHT0', + 'EHT1', + 'EHT2', + 'EHT3', + 'EHT4', + 'EHT5', + 'EHT6', + 'EHT7', + 'EPMM0', + 'EPMM1', + 'EPMM2', + 'EPMM3', + 'EPMM4', + 'EPMM5', + 'EPMM6', + 'EPMM7', + 'EPMCSL', + 'EPMCSH', + '—', + '—', + 'EPMOL', + 'EPMOH', + 'Reserved', + 'Reserved', + 'ERXFCON', + 'EPKTCNT', + 'Reserved', + 'EIE', + 'EIR', + 'ESTAT', + 'ECON2', + 'ECON1', + ], + [ + 'MACON1', + 'Reserved', + 'MACON3', + 'MACON4', + 'MABBIPG', + '—', + 'MAIPGL', + 'MAIPGH', + 'MACLCON1', + 'MACLCON2', + 'MAMXFLL', + 'MAMXFLH', + 'Reserved', + 'Reserved', + 'Reserved', + '—', + 'Reserved', + 'Reserved', + 'MICMD', + '—', + 'MIREGADR', + 'Reserved', + 'MIWRL', + 'MIWRH', + 'MIRDL', + 'MIRDH', + 'Reserved', + 'EIE', + 'EIR', + 'ESTAT', + 'ECON2', + 'ECON1', + ], + [ + 'MAADR5', + 'MAADR6', + 'MAADR3', + 'MAADR4', + 'MAADR1', + 'MAADR2', + 'EBSTSD', + 'EBSTCON', + 'EBSTCSL', + 'EBSTCSH', + 'MISTAT', + '—', + '—', + '—', + '—', + '—', + '—', + '—', + 'EREVID', + '—', + '—', + 'ECOCON', + 'Reserved', + 'EFLOCON', + 'EPAUSL', + 'EPAUSH', + 'Reserved', + 'EIE', + 'EIR', + 'ESTAT', + 'ECON2', + 'ECON1', + ], +] diff --git a/libsigrokdecode4DSL/decoders/enc28j60/pd.py b/libsigrokdecode4DSL/decoders/enc28j60/pd.py new file mode 100644 index 00000000..f7a6625a --- /dev/null +++ b/libsigrokdecode4DSL/decoders/enc28j60/pd.py @@ -0,0 +1,294 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2019 Jiahao Li +## +## Permission is hereby granted, free of charge, to any person obtaining a copy +## of this software and associated documentation files (the "Software"), to deal +## in the Software without restriction, including without limitation the rights +## to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +## copies of the Software, and to permit persons to whom the Software is +## furnished to do so, subject to the following conditions: +## +## The above copyright notice and this permission notice shall be included in all +## copies or substantial portions of the Software. +## +## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +## IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +## FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +## AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +## LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +## OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +## SOFTWARE. + +import sigrokdecode as srd +from .lists import * + +OPCODE_MASK = 0b11100000 +REG_ADDR_MASK = 0b00011111 + +OPCODE_HANDLERS = { + 0b00000000: '_process_rcr', + 0b00100000: '_process_rbm', + 0b01000000: '_process_wcr', + 0b01100000: '_process_wbm', + 0b10000000: '_process_bfs', + 0b10100000: '_process_bfc', + 0b11100000: '_process_src', +} + +(ANN_RCR, ANN_RBM, ANN_WCR, ANN_WBM, ANN_BFS, ANN_BFC, ANN_SRC, ANN_DATA, +ANN_REG_ADDR, ANN_WARNING) = range(10) + +REG_ADDR_ECON1 = 0x1F +BIT_ECON1_BSEL0 = 0b00000001 +BIT_ECON1_BSEL1 = 0b00000010 + +class Decoder(srd.Decoder): + api_version = 3 + id = 'enc28j60' + name = 'ENC28J60' + longname = 'Microchip ENC28J60' + desc = 'Microchip ENC28J60 10Base-T Ethernet controller protocol.' + license = 'mit' + inputs = ['spi'] + outputs = [] + tags = ['Embedded/industrial', 'Networking'] + annotations = ( + ('rcr', 'Read Control Register'), + ('rbm', 'Read Buffer Memory'), + ('wcr', 'Write Control Register'), + ('wbm', 'Write Buffer Memory'), + ('bfs', 'Bit Field Set'), + ('bfc', 'Bit Field Clear'), + ('src', 'System Reset Command'), + ('data', 'Data'), + ('reg-addr', 'Register Address'), + ('warning', 'Warning'), + ) + annotation_rows = ( + ('commands', 'Commands', + (ANN_RCR, ANN_RBM, ANN_WCR, ANN_WBM, ANN_BFS, ANN_BFC, ANN_SRC)), + ('fields', 'Fields', (ANN_DATA, ANN_REG_ADDR)), + ('warnings', 'Warnings', (ANN_WARNING,)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.mosi = [] + self.miso = [] + self.ranges = [] + self.cmd_ss = None + self.cmd_es = None + self.range_ss = None + self.range_es = None + self.active = False + self.bsel0 = None + self.bsel1 = None + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def putc(self, data): + self.put(self.cmd_ss, self.cmd_es, self.out_ann, data) + + def putr(self, data): + self.put(self.range_ss, self.range_es, self.out_ann, data) + + def _process_command(self): + if len(self.mosi) == 0: + self.active = False + return + + header = self.mosi[0] + opcode = header & OPCODE_MASK + + if opcode not in OPCODE_HANDLERS: + self._put_command_warning("Unknown opcode.") + self.active = False + return + + getattr(self, OPCODE_HANDLERS[opcode])() + + self.active = False + + def _get_register_name(self, reg_addr): + if (self.bsel0 is None) or (self.bsel1 is None): + # We don't know the bank we're in yet. + return None + else: + bank = (self.bsel1 << 1) + self.bsel0 + return REGS[bank][reg_addr] + + def _put_register_header(self): + reg_addr = self.mosi[0] & REG_ADDR_MASK + reg_name = self._get_register_name(reg_addr) + + self.range_ss, self.range_es = self.cmd_ss, self.ranges[1][0] + + if reg_name is None: + # We don't know the bank we're in yet. + self.putr([ANN_REG_ADDR, [ + 'Reg Bank ? Addr 0x{0:02X}'.format(reg_addr), + '?:{0:02X}'.format(reg_addr)]]) + self.putr([ANN_WARNING, ['Warning: Register bank not known yet.', + 'Warning']]) + else: + self.putr([ANN_REG_ADDR, ['Reg {0}'.format(reg_name), + '{0}'.format(reg_name)]]) + + if (reg_name == '-') or (reg_name == 'Reserved'): + self.putr([ANN_WARNING, ['Warning: Invalid register accessed.', + 'Warning']]) + + def _put_data_byte(self, data, byte_index, binary=False): + self.range_ss = self.ranges[byte_index][0] + if byte_index == len(self.mosi) - 1: + self.range_es = self.cmd_es + else: + self.range_es = self.ranges[byte_index + 1][0] + + if binary: + self.putr([ANN_DATA, ['Data 0b{0:08b}'.format(data), + '{0:08b}'.format(data)]]) + else: + self.putr([ANN_DATA, ['Data 0x{0:02X}'.format(data), + '{0:02X}'.format(data)]]) + + def _put_command_warning(self, reason): + self.putc([ANN_WARNING, ['Warning: {0}'.format(reason), 'Warning']]) + + def _process_rcr(self): + self.putc([ANN_RCR, ['Read Control Register', 'RCR']]) + + if (len(self.mosi) != 2) and (len(self.mosi) != 3): + self._put_command_warning('Invalid command length.') + return + + self._put_register_header() + + reg_name = self._get_register_name(self.mosi[0] & REG_ADDR_MASK) + if reg_name is None: + # We can't tell if we're accessing MAC/MII registers or not + # Let's trust the user in this case. + pass + else: + if (reg_name[0] == 'M') and (len(self.mosi) != 3): + self._put_command_warning('Attempting to read a MAC/MII ' + + 'register without using the dummy byte.') + return + + if (reg_name[0] != 'M') and (len(self.mosi) != 2): + self._put_command_warning('Attempting to read a non-MAC/MII ' + + 'register using the dummy byte.') + return + + if len(self.mosi) == 2: + self._put_data_byte(self.miso[1], 1) + else: + self.range_ss, self.range_es = self.ranges[1][0], self.ranges[2][0] + self.putr([ANN_DATA, ['Dummy Byte', 'Dummy']]) + self._put_data_byte(self.miso[2], 2) + + def _process_rbm(self): + if self.mosi[0] != 0b00111010: + self._put_command_warning('Invalid header byte.') + return + + self.putc([ANN_RBM, ['Read Buffer Memory: Length {0}'.format( + len(self.mosi) - 1), 'RBM']]) + + for i in range(1, len(self.miso)): + self._put_data_byte(self.miso[i], i) + + def _process_wcr(self): + self.putc([ANN_WCR, ['Write Control Register', 'WCR']]) + + if len(self.mosi) != 2: + self._put_command_warning('Invalid command length.') + return + + self._put_register_header() + self._put_data_byte(self.mosi[1], 1) + + if self.mosi[0] & REG_ADDR_MASK == REG_ADDR_ECON1: + self.bsel0 = (self.mosi[1] & BIT_ECON1_BSEL0) >> 0 + self.bsel1 = (self.mosi[1] & BIT_ECON1_BSEL1) >> 1 + + def _process_wbm(self): + if self.mosi[0] != 0b01111010: + self._put_command_warning('Invalid header byte.') + return + + self.putc([ANN_WBM, ['Write Buffer Memory: Length {0}'.format( + len(self.mosi) - 1), 'WBM']]) + + for i in range(1, len(self.mosi)): + self._put_data_byte(self.mosi[i], i) + + def _process_bfc(self): + self.putc([ANN_BFC, ['Bit Field Clear', 'BFC']]) + + if len(self.mosi) != 2: + self._put_command_warning('Invalid command length.') + return + + self._put_register_header() + self._put_data_byte(self.mosi[1], 1, True) + + if self.mosi[0] & REG_ADDR_MASK == REG_ADDR_ECON1: + if self.mosi[1] & BIT_ECON1_BSEL0: + self.bsel0 = 0 + if self.mosi[1] & BIT_ECON1_BSEL1: + self.bsel1 = 0 + + def _process_bfs(self): + self.putc([ANN_BFS, ['Bit Field Set', 'BFS']]) + + if len(self.mosi) != 2: + self._put_command_warning('Invalid command length.') + return + + self._put_register_header() + self._put_data_byte(self.mosi[1], 1, True) + + if self.mosi[0] & REG_ADDR_MASK == REG_ADDR_ECON1: + if self.mosi[1] & BIT_ECON1_BSEL0: + self.bsel0 = 1 + if self.mosi[1] & BIT_ECON1_BSEL1: + self.bsel1 = 1 + + def _process_src(self): + self.putc([ANN_SRC, ['System Reset Command', 'SRC']]) + + if len(self.mosi) != 1: + self._put_command_warning('Invalid command length.') + return + + self.bsel0 = 0 + self.bsel1 = 0 + + def decode(self, ss, es, data): + ptype, data1, data2 = data + + if ptype == 'CS-CHANGE': + new_cs = data2 + + if new_cs == 0: + self.active = True + self.cmd_ss = ss + self.mosi = [] + self.miso = [] + self.ranges = [] + elif new_cs == 1: + if self.active: + self.cmd_es = es + self._process_command() + elif ptype == 'DATA': + mosi, miso = data1, data2 + + self.mosi.append(mosi) + self.miso.append(miso) + self.ranges.append((ss, es)) diff --git a/libsigrokdecode4DSL/decoders/flexray/__init__.py b/libsigrokdecode4DSL/decoders/flexray/__init__.py new file mode 100644 index 00000000..73dc7fa1 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/flexray/__init__.py @@ -0,0 +1,32 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2019 Stephan Thiele +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +FlexRay is a fast, deterministic and fault-tolerant fieldbus system +which is used in cars in high security related areas like X-by-Wire. + +It is the result of the FlexRay consortium which consisted of BMW, +Daimler, Motorola (today Freescale) and Philips, with the goal of +working out a common standard automotive bus system. + +This decoder assumes that at least one channel of a logic level RX line +of a transceiver is sampled (e.g. NXP TJA1080). +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/flexray/pd.py b/libsigrokdecode4DSL/decoders/flexray/pd.py new file mode 100644 index 00000000..8ec30439 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/flexray/pd.py @@ -0,0 +1,413 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2019 Stephan Thiele +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd + +# Selection of constants as defined in FlexRay specification 3.0.1 Chapter A.1: +class Const: + cChannelIdleDelimiter = 11 + cCrcInitA = 0xFEDCBA + cCrcInitB = 0xABCDEF + cCrcPolynomial = 0x5D6DCB + cCrcSize = 24 + cCycleCountMax = 63 + cdBSS = 2 + cdCAS = 30 + cdFES = 2 + cdFSS = 1 + cHCrcInit = 0x01A + cHCrcPolynomial = 0x385 + cHCrcSize = 11 + cSamplesPerBit = 8 + cSlotIDMax = 2047 + cStaticSlotIDMax = 1023 + cVotingSamples = 5 + +class SamplerateError(Exception): + pass + +class Decoder(srd.Decoder): + api_version = 3 + id = 'flexray' + name = 'FlexRay' + longname = 'FlexRay' + desc = 'Automotive network communications protocol.' + license = 'gplv2+' + inputs = ['logic'] + outputs = [] + tags = ['Automotive'] + channels = ( + {'id': 'channel', 'name': 'Channel', 'desc': 'FlexRay bus channel'}, + ) + options = ( + {'id': 'channel_type', 'desc': 'Channel type', 'default': 'A', + 'values': ('A', 'B')}, + {'id': 'bitrate', 'desc': 'Bitrate (bit/s)', 'default': 10000000, + 'values': (10000000, 5000000, 2500000)}, + ) + annotations = ( + ('data', 'FlexRay payload data'), + ('tss', 'Transmission start sequence'), + ('fss', 'Frame start sequence'), + ('reserved-bit', 'Reserved bit'), + ('ppi', 'Payload preamble indicator'), + ('null-frame', 'Nullframe indicator'), + ('sync-frame', 'Full identifier'), + ('startup-frame', 'Startup frame indicator'), + ('id', 'Frame ID'), + ('length', 'Data length'), + ('header-crc', 'Header CRC'), + ('cycle', 'Cycle code'), + ('data-byte', 'Data byte'), + ('frame-crc', 'Frame CRC'), + ('fes', 'Frame end sequence'), + ('bss', 'Byte start sequence'), + ('warning', 'Warning'), + ('bit', 'Bit'), + ('cid', 'Channel idle delimiter'), + ('dts', 'Dynamic trailing sequence'), + ('cas', 'Collision avoidance symbol'), + ) + annotation_rows = ( + ('bits', 'Bits', (15, 17)), + ('fields', 'Fields', tuple(range(15)) + (18, 19, 20)), + ('warnings', 'Warnings', (16,)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.samplerate = None + self.reset_variables() + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def metadata(self, key, value): + if key == srd.SRD_CONF_SAMPLERATE: + bitrate = float(self.options['bitrate']) + self.samplerate = value + self.bit_width = float(self.samplerate) / bitrate + self.sample_point = (self.bit_width / 100.0) * self.sample_point_percent + + # Generic helper for FlexRay bit annotations. + def putg(self, ss, es, data): + left, right = int(self.sample_point), int(self.bit_width - self.sample_point) + self.put(ss - left, es + right, self.out_ann, data) + + # Single-FlexRay-bit annotation using the current samplenum. + def putx(self, data): + self.putg(self.samplenum, self.samplenum, data) + + # Multi-FlexRay-bit annotation from self.ss_block to current samplenum. + def putb(self, data): + self.putg(self.ss_block, self.samplenum, data) + + # Generic CRC algorithm for any bit size and any data length. Used for + # 11-bit header and 24-bit trailer. Not very efficient but at least it + # works for now. + # + # TODO: + # - use precalculated tables to increase performance. + # - Add support for reverse CRC calculations. + + @staticmethod + def crc(data, data_len_bits, polynom, crc_len_bits, iv=0, xor=0): + reg = iv ^ xor + + for i in range(data_len_bits - 1, -1, -1): + bit = ((reg >> (crc_len_bits - 1)) & 0x1) ^ ((data >> i) & 0x1) + reg <<= 1 + if bit: + reg ^= polynom + + mask = (1 << crc_len_bits) - 1 + crc = reg & mask + + return crc ^ xor + + def reset_variables(self): + self.sample_point_percent = 50 # TODO: use vote based sampling + self.state = 'IDLE' + self.tss_start = self.tss_end = self.frame_type = self.dlc = None + self.rawbits = [] # All bits, including byte start sequence bits + self.bits = [] # Only actual FlexRay frame bits (no byte start sequence bits) + self.curbit = 0 # Current bit of FlexRay frame (bit 0 == FSS) + self.last_databit = 999 # Positive value that bitnum+x will never match + self.last_xmit_bit = 999 # Positive value that bitnum+x will never match + self.ss_block = None + self.ss_databytebits = [] + self.end_of_frame = False + self.dynamic_frame = False + self.ss_bit0 = None + self.ss_bit1 = None + self.ss_bit2 = None + + # Poor man's clock synchronization. Use signal edges which change to + # dominant state in rather simple ways. This naive approach is neither + # aware of the SYNC phase's width nor the specific location of the edge, + # but improves the decoder's reliability when the input signal's bitrate + # does not exactly match the nominal rate. + def dom_edge_seen(self, force=False): + self.dom_edge_snum = self.samplenum + self.dom_edge_bcount = self.curbit + + # Determine the position of the next desired bit's sample point. + def get_sample_point(self, bitnum): + samplenum = self.dom_edge_snum + samplenum += self.bit_width * (bitnum - self.dom_edge_bcount) + samplenum += self.sample_point + return int(samplenum) + + def is_bss_sequence(self): + # FlexRay uses NRZ encoding and adds a binary 10 sequence before each + # byte. After each 8 data bits, a BSS sequence is added but not after + # frame CRC. + + if self.end_of_frame: + return False + + if (len(self.rawbits) - 2) % 10 == 0: + return True + elif (len(self.rawbits) - 3) % 10 == 0: + return True + + return False + + def handle_bit(self, fr_rx): + self.rawbits.append(fr_rx) + self.bits.append(fr_rx) + + # Get the index of the current FlexRay frame bit. + bitnum = len(self.bits) - 1 + + # If this is a byte start sequence remove it from self.bits and ignore it. + if self.is_bss_sequence(): + self.bits.pop() + + if bitnum > 1: + self.putx([15, [str(fr_rx)]]) + else: + if len(self.rawbits) == 2: + self.ss_bit1 = self.samplenum + elif len(self.rawbits) == 3: + self.ss_bit2 = self.samplenum + + self.curbit += 1 # Increase self.curbit (bitnum is not affected). + return + else: + if bitnum > 1: + self.putx([17, [str(fr_rx)]]) + + # Bit 0: Frame start sequence (FSS) bit + if bitnum == 0: + self.ss_bit0 = self.samplenum + + # Bit 1: Start of header + elif bitnum == 1: + if self.rawbits[:3] == [1, 1, 0]: + self.put(self.tss_start, self.tss_end, self.out_ann, + [1, ['Transmission start sequence', 'TSS']]) + + self.putg(self.ss_bit0, self.ss_bit0, [17, [str(self.rawbits[:3][0])]]) + self.putg(self.ss_bit0, self.ss_bit0, [2, ['FSS', 'Frame start sequence']]) + self.putg(self.ss_bit1, self.ss_bit1, [15, [str(self.rawbits[:3][1])]]) + self.putg(self.ss_bit2, self.ss_bit2, [15, [str(self.rawbits[:3][2])]]) + self.putx([17, [str(fr_rx)]]) + self.putx([3, ['Reserved bit: %d' % fr_rx, 'RB: %d' % fr_rx, 'RB']]) + else: + self.put(self.tss_start, self.tss_end, self.out_ann, + [20, ['Collision avoidance symbol', 'CAS']]) + self.reset_variables() + + # TODO: warning, if sequence is neither [1, 1, 0] nor [1, 1, 1] + + # Bit 2: Payload preamble indicator. Must be 0 if null frame indicator is 0. + elif bitnum == 2: + self.putx([4, ['Payload preamble indicator: %d' % fr_rx, + 'PPI: %d' % fr_rx]]) + + # Bit 3: Null frame indicator (inversed) + elif bitnum == 3: + data_type = 'data frame' if fr_rx else 'null frame' + self.putx([5, ['Null frame indicator: %s' % data_type, + 'NF: %d' % fr_rx, 'NF']]) + + # Bit 4: Sync frame indicator + # Must be 1 if startup frame indicator is 1. + elif bitnum == 4: + self.putx([6, ['Sync frame indicator: %d' % fr_rx, + 'Sync: %d' % fr_rx, 'Sync']]) + + # Bit 5: Startup frame indicator + elif bitnum == 5: + self.putx([7, ['Startup frame indicator: %d' % fr_rx, + 'Startup: %d' % fr_rx, 'Startup']]) + + # Remember start of ID (see below). + elif bitnum == 6: + self.ss_block = self.samplenum + + # Bits 6-16: Frame identifier (ID[10..0]) + # ID must NOT be 0. + elif bitnum == 16: + self.id = int(''.join(str(d) for d in self.bits[6:]), 2) + self.putb([8, ['Frame ID: %d' % self.id, 'ID: %d' % self.id, + '%d' % self.id]]) + + # Remember start of payload length (see below). + elif bitnum == 17: + self.ss_block = self.samplenum + + # Bits 17-23: Payload length (Length[7..0]) + # Payload length in header is the half of the real payload size. + elif bitnum == 23: + self.payload_length = int(''.join(str(d) for d in self.bits[17:]), 2) + self.putb([9, ['Payload length: %d' % self.payload_length, + 'Length: %d' % self.payload_length, + '%d' % self.payload_length]]) + + # Remember start of header CRC (see below). + elif bitnum == 24: + self.ss_block = self.samplenum + + # Bits 24-34: Header CRC (11-bit) (HCRC[11..0]) + # Calculation of header CRC is equal on both channels. + elif bitnum == 34: + bits = ''.join([str(b) for b in self.bits[4:24]]) + header_to_check = int(bits, 2) + expected_crc = self.crc(header_to_check, len(bits), + Const.cHCrcPolynomial, Const.cHCrcSize, Const.cHCrcInit) + self.header_crc = int(''.join(str(d) for d in self.bits[24:]), 2) + + crc_ok = self.header_crc == expected_crc + crc_ann = "OK" if crc_ok else "bad" + + self.putb([10, ['Header CRC: 0x%X (%s)' % (self.header_crc, crc_ann), + '0x%X (%s)' % (self.header_crc, crc_ann), + '0x%X' % self.header_crc]]) + + # Remember start of cycle code (see below). + elif bitnum == 35: + self.ss_block = self.samplenum + + # Bits 35-40: Cycle code (Cyc[6..0]) + # Cycle code. Must be between 0 and 63. + elif bitnum == 40: + self.cycle = int(''.join(str(d) for d in self.bits[35:]), 2) + self.putb([11, ['Cycle: %d' % self.cycle, 'Cyc: %d' % self.cycle, + '%d' % self.cycle]]) + self.last_databit = 41 + 2 * self.payload_length * 8 + + # Remember all databyte bits, except the very last one. + elif bitnum in range(41, self.last_databit): + self.ss_databytebits.append(self.samplenum) + + # Bits 41-X: Data field (0-254 bytes, depending on length) + # The bits within a data byte are transferred MSB-first. + elif bitnum == self.last_databit: + self.ss_databytebits.append(self.samplenum) # Last databyte bit. + for i in range(2 * self.payload_length): + x = 40 + (8 * i) + 1 + b = int(''.join(str(d) for d in self.bits[x:x + 8]), 2) + ss = self.ss_databytebits[i * 8] + es = self.ss_databytebits[((i + 1) * 8) - 1] + self.putg(ss, es, [12, ['Data byte %d: 0x%02x' % (i, b), + 'DB%d: 0x%02x' % (i, b), '%02X' % b]]) + self.ss_databytebits = [] + self.ss_block = self.samplenum # Remember start of trailer CRC. + + # Trailer CRC (24-bit) (CRC[11..0]) + # Initialization vector of channel A and B are different, so CRCs are + # different for same data. + elif bitnum == self.last_databit + 23: + bits = ''.join([str(b) for b in self.bits[1:-24]]) + frame_to_check = int(bits, 2) + iv = Const.cCrcInitA if self.options['channel_type'] == 'A' else Const.cCrcInitB + expected_crc = self.crc(frame_to_check, len(bits), + Const.cCrcPolynomial, Const.cCrcSize, iv=iv) + self.frame_crc = int(''.join(str(d) for d in self.bits[self.last_databit:]), 2) + + crc_ok = self.frame_crc == expected_crc + crc_ann = "OK" if crc_ok else "bad" + + self.putb([13, ['Frame CRC: 0x%X (%s)' % (self.frame_crc, crc_ann), + '0x%X (%s)' % (self.frame_crc, crc_ann), + '0x%X' % self.frame_crc]]) + self.end_of_frame = True + + # Remember start of frame end sequence (see below). + elif bitnum == self.last_databit + 24: + self.ss_block = self.samplenum + + # Frame end sequence, must be 1 followed by 0. + elif bitnum == self.last_databit + 25: + self.putb([14, ['Frame end sequence', 'FES']]) + + # Check for DTS + elif bitnum == self.last_databit + 26: + if not fr_rx: + self.dynamic_frame = True + else: + self.last_xmit_bit = bitnum + self.ss_block = self.samplenum + + # Remember start of channel idle delimiter (see below). + elif bitnum == self.last_xmit_bit: + self.ss_block = self.samplenum + + # Channel idle limiter (CID[11..0]) + elif bitnum == self.last_xmit_bit + Const.cChannelIdleDelimiter - 1: + self.putb([18, ['Channel idle delimiter', 'CID']]) + self.reset_variables() + + # DTS if dynamic frame + elif bitnum > self.last_databit + 27: + if self.dynamic_frame: + if fr_rx: + if self.last_xmit_bit == 999: + self.putb([19, ['Dynamic trailing sequence', 'DTS']]) + self.last_xmit_bit = bitnum + 1 + self.ss_block = self.samplenum + + self.curbit += 1 + + def decode(self): + if not self.samplerate: + raise SamplerateError('Cannot decode without samplerate.') + + while True: + # State machine. + if self.state == 'IDLE': + # Wait for a dominant state (logic 0) on the bus. + (fr_rx,) = self.wait({0: 'l'}) + self.tss_start = self.samplenum + (fr_rx,) = self.wait({0: 'h'}) + self.tss_end = self.samplenum + self.dom_edge_seen(force = True) + self.state = 'GET BITS' + elif self.state == 'GET BITS': + # Wait until we're in the correct bit/sampling position. + pos = self.get_sample_point(self.curbit) + (fr_rx,) = self.wait([{'skip': pos - self.samplenum}, {0: 'f'}]) + if self.matched[1]: + self.dom_edge_seen() + if self.matched[0]: + self.handle_bit(fr_rx) diff --git a/libsigrokdecode4DSL/decoders/fsi/__init__.py b/libsigrokdecode4DSL/decoders/fsi/__init__.py new file mode 100644 index 00000000..8d589923 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/fsi/__init__.py @@ -0,0 +1,25 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2020 Raptor Engineering, LLC +## +## This program is free software: you can redistribute it and/or modify +## it under the terms of the GNU Affero General Public License as +## published by the Free Software Foundation, either version 3 of the +## License, or (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU Affero General Public License for more details. +## +## You should have received a copy of the GNU Affero General Public License +## along with this program. If not, see . +## + +''' +FSI is a low level serial protocol used by various devices on OpenPOWER +systems such as the Raptor Talos II and Blackbird. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/fsi/pd.py b/libsigrokdecode4DSL/decoders/fsi/pd.py new file mode 100644 index 00000000..4b6b69f6 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/fsi/pd.py @@ -0,0 +1,574 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2020 Raptor Engineering, LLC +## +## This program is free software: you can redistribute it and/or modify +## it under the terms of the GNU Affero General Public License as +## published by the Free Software Foundation, either version 3 of the +## License, or (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU Affero General Public License for more details. +## +## You should have received a copy of the GNU Affero General Public License +## along with this program. If not, see . +## + +import sigrokdecode as srd + +# ... +fields = { + # START field (indicates the start of a transaction) + 'START': { + 0b1: 'Start of FSI cycle', + }, +} + +class Decoder(srd.Decoder): + api_version = 3 + id = 'fsi' + name = 'FSI' + longname = 'Flexible Service Interface' + desc = 'Protocol for FSI devices on Raptor OpenPOWER systems.' + license = 'agplv3' + inputs = ['logic'] + outputs = [] + tags = ['PC'] + channels = ( + {'id': 'data', 'name': 'DATA', 'desc': 'Frame'}, + {'id': 'clock', 'name': 'CLOCK', 'desc': 'Clock'}, + ) + annotations = ( + ('warnings', 'Warnings'), + ('start', 'Start'), + ('cycle-type', 'Cycle type'), + ('direction', 'Direction'), + ('addr', 'Address'), + ('data', 'Data'), + ('commands', 'Commands'), + ('crc', 'CRC'), + ('turn-around', 'TAR'), + ) + annotation_rows = ( + ('data', 'Data', (1, 2, 3, 4, 5, 6, 7, 8,)), + ('warnings', 'Warnings', (0,)), + ) + + def __init__(self): + self.tar_cycles = 3 + self.reset() + + def reset(self): + self.state = 'IDLE' + self.break_start_sample_number = 0 + self.break_counter = 0 + self.samplenum = 0 + self.samplenum_prev = 0 + self.fsi_data_prev = 0 + self.fsi_data_break_prev = 0 + self.crc_internal = 0 + self.response_received = 0 + self.crc_calculating = 0 + self.busy_seq_count = 0 + self.valid_response = 0 + self.prev_address = {} + self.prev_address_valid = {0: False, 1: False, 2: False, 3: False} + self.ss_block = None + self.es_block = None + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def putb(self, data): + self.put(self.ss_block, self.es_block, self.out_ann, data) + + def decode(self): + while True: + # FSI is clocked out on the falling edge and latched in on the rising edge of the clock...according to the specification. + # That said, the specification is not clear on what this actually means (and it doesn't follow industry convention). + # Real IBM POWER9 hardware is verified to work in the following mode: + # FSI data being sent to the master is latched by the master logic at the falling edge of the FSI clock + # FSI data being from to the master is strobed by the master logic at the falling edge of the FSI clock + # FSI data being sent to the slave is latched by the slave logic at the rising edge of the FSI clock + # FSI data being from to the slave is strobed by the slave logic at the rising edge of the FSI clock + # + # The above means that we have to be able to make a reasonable guess as to who is transmitting (the master or the slave) + # in order to know which edge to sample on + + # Wait for either clock edge + (data, clk) = self.wait({1: 'e'}) + + # FSI data is electrically inverted + fsi_data = not data + fsi_clk = clk + current_sample_number = self.samplenum + + # Detect BREAK commands + # Note these can only be sent by the master, so sample on the rising clock edge only + if (fsi_clk): + if (self.fsi_data_break_prev == 1): + self.break_counter = self.break_counter + 1 + if (self.break_counter == 256): + self.ss_block = self.break_start_sample_number + self.es_block = current_sample_number + self.putb([6, ['BREAK']]) + self.state = 'BREAK_TAR_QUEUED' + self.busy_seq_count = 0 + self.valid_response = 0 + self.prev_address = {} + self.prev_address_valid = {0: False, 1: False, 2: False, 3: False} + self.ss_block = current_sample_number + else: + if (self.break_counter > 256): + self.es_block = current_sample_number + self.putb([0, ['BREAK asserted in excess of specification cycles']]) + self.break_start_sample_number = current_sample_number + self.break_counter = 0 + self.fsi_data_break_prev = fsi_data + + if ((self.state == 'TAR') or (self.state == 'RX_SLAVE_ID') or (self.state == 'RESPONSE') or (self.state == 'RX_DATA') + or (self.state == 'RX_IPOLL_INTERRUPT_FIELD') or (self.state == 'RX_IPOLL_DMA_CONTROL_FIELD') or (self.state == 'RX_IPOLL_DMA_CONTROL_FIELD') + or ((self.state == 'CRC') and (self.valid_response))): + # Slave is / should be transmitting, sample on falling clock edge only + if (fsi_clk): + continue + else: + # Master is / should be transmitting, sample on rising clock edge only + if (not fsi_clk): + continue + + # Transfer state machine + if (self.state == 'IDLE'): + self.crc_internal = 0 + self.response_received = 0 + if (self.fsi_data_prev == 1): + self.tx_slave_id = 0 + self.data_count = 2 + self.ss_block = self.samplenum_prev + self.es_block = current_sample_number + self.putb([1, ['START']]) + self.ss_block = current_sample_number + self.crc_calculating = 1 + self.state = 'TX_SLAVE_ID' + + elif (self.state == 'TX_SLAVE_ID'): + self.crc_calculating = 1 + if (self.data_count > 0): + self.tx_slave_id = (self.tx_slave_id >> 1) | (self.fsi_data_prev << 1) + self.data_count = self.data_count - 1 + if (self.data_count == 0): + self.es_block = current_sample_number + self.putb([5, ['Slave ID: 0x%01x' % self.tx_slave_id]]) + self.ss_block = current_sample_number + self.command_count = 0 + self.command_code = 0 + self.command = None + self.valid_command = False + self.state = 'COMMAND' + + elif (self.state == 'COMMAND'): + self.crc_calculating = 1 + self.command_code = (self.command_code << 1) | self.fsi_data_prev + self.command_count = self.command_count + 1 + if ((self.command_count == 3) and (self.command_code == 0b100)): + self.command = 'ABS_ADR' + self.valid_command = True + elif ((self.command_count == 3) and (self.command_code == 0b101)): + self.command = 'REL_ADR' + self.valid_command = True + elif ((self.command_count == 2) and (self.command_code == 0b11)): + self.command = 'SAME_ADR' + self.valid_command = True + elif ((self.command_count == 3) and (self.command_code == 0b010)): + self.command = 'D_POLL' + self.valid_command = True + elif ((self.command_count == 3) and (self.command_code == 0b011)): + self.command = 'E_POLL' + self.valid_command = True + elif ((self.command_count == 3) and (self.command_code == 0b001)): + self.command = 'I_POLL' + self.valid_command = True + if ((self.command_count > 7) or (self.valid_command == True)): + if (self.command_count == 8): + self.es_block = current_sample_number + self.putb([6, ['Invalid command code: 0x%02x/%d' % (self.command_code, self.command_count)]]) + self.putb([0, ['%s' % 'Invalid command code']]) + self.ss_block = current_sample_number + self.state = 'IDLE' + else: + self.es_block = current_sample_number + self.putb([6, ['Command: %s (0x%02x/%d)' % (self.command, self.command_code, self.command_count)]]) + self.ss_block = current_sample_number + if (self.command == 'ABS_ADR'): + self.address_length = 21 + self.address_count = 0 + self.address = 0 + self.state = 'DIRECTION' + elif (self.command == 'REL_ADR'): + self.address_length = 8 + self.address_count = 0 + self.address = 0 + self.state = 'DIRECTION' + elif (self.command == 'SAME_ADR'): + self.address_length = 2 + self.address_count = 0 + self.address = 0 + self.state = 'DIRECTION' + elif (self.command == 'D_POLL'): + self.crc = 0 + self.crc_count = 0 + self.state = 'CRC' + elif (self.command == 'E_POLL'): + self.crc = 0 + self.crc_count = 0 + self.state = 'CRC' + elif (self.command == 'I_POLL'): + self.crc = 0 + self.crc_count = 0 + self.state = 'CRC' + else: + self.state = 'IDLE' + + elif (self.state == 'DIRECTION'): + self.crc_calculating = 1 + self.direction = self.fsi_data_prev + self.es_block = current_sample_number + if (self.direction == 1): + self.putb([3, ['Direction: %s' % 'Read']]) + else: + self.putb([3, ['Direction: %s' % 'Write']]) + self.ss_block = current_sample_number + if (self.command == 'REL_ADR'): + self.state = 'REL_ADDRESS_SIGN' + else: + self.state = 'ADDRESS' + + elif (self.state == 'REL_ADDRESS_SIGN'): + self.crc_calculating = 1 + self.relative_address_negative = self.fsi_data_prev + self.es_block = current_sample_number + if (self.relative_address_negative == 1): + self.putb([5, ['Relative address sign: %s' % '(-)']]) + else: + self.putb([5, ['Relative address sign: %s' % '(+)']]) + self.ss_block = current_sample_number + self.state = 'ADDRESS' + + elif (self.state == 'ADDRESS'): + self.crc_calculating = 1 + self.address = (self.address << 1) | self.fsi_data_prev + self.address_count = self.address_count + 1 + if (self.address_count >= self.address_length): + self.address_raw = self.address + if (self.prev_address_valid[self.tx_slave_id]): + if (self.command == 'SAME_ADR'): + self.address = (self.prev_address[self.tx_slave_id] & ~0b11) | (self.address_raw & 0b11) + elif (self.command == 'REL_ADR'): + if (self.relative_address_negative): + self.address = self.prev_address[self.tx_slave_id] - (0x100 - self.address_raw) + else: + self.address = self.prev_address[self.tx_slave_id] + self.address_raw + self.es_block = current_sample_number + if (((self.command == 'SAME_ADR') or (self.command == 'REL_ADR'))): + self.putb([5, ['Address: 0x%06x (0x%03x)' % (self.address, self.address_raw)]]) + if (not self.prev_address_valid[self.tx_slave_id]): + self.putb([0, ['%s' % 'Base address for relative address not captured']]) + else: + self.putb([5, ['Address: 0x%06x' % self.address]]) + self.ss_block = current_sample_number + self.state = 'DATA_SIZE' + + elif (self.state == 'DATA_SIZE'): + self.crc_calculating = 1 + if (self.direction and ((self.address_raw & 3) == 3) and self.fsi_data_prev): + # OpenFSI suffers from an unfortunate conflict between the SAME_ADR command + # and the TERM command. Both start with 2'b11 and since both are variable + # length it is impossible to determine if a TERM command was sent until this + # point in the receiver process! + # + # Set correct command code for further processing + self.command_code = 0b111111 + self.command_count = 6 + self.command = 'TERM' + self.es_block = current_sample_number + self.putb([6, ['Command: %s (0x%02x/%d)' % (self.command, self.command_code, self.command_count)]]) + self.ss_block = current_sample_number + self.direction = 0 + self.busy_seq_count = 0 + self.crc = 0 + self.crc_count = 0 + self.state = 'CRC' + else: + if (self.fsi_data_prev == 0): + self.data_size = 'BYTE' + else: + if ((self.address_raw & 3) == 1): + self.data_size = 'WORD' + # Force lowest address bits to specification-mandated values if required + self.address = (self.address & ~3) | 1 + elif ((self.address_raw & 1) == 0): + self.data_size = 'HALF_WORD' + # Force lowest address bits to specification-mandated values if required + self.address = self.address & ~1 + else: + self.data_size = 'UNKNOWN' + self.es_block = current_sample_number + if (self.data_size == 'UNKNOWN'): + self.putb([0, ['Data Size: %s' % 'UNKNOWN']]) + self.state = 'IDLE' + else: + self.putb([3, ['Data Size: %s' % self.data_size]]) + if (self.direction == 1): + self.crc = 0 + self.crc_count = 0 + self.state = 'CRC' + else: + self.data = 0 + self.data_count = 0 + if (self.data_size == 'BYTE'): + self.data_length = 8 + elif (self.data_size == 'HALF_WORD'): + self.data_length = 16 + elif (self.data_size == 'WORD'): + self.data_length = 32 + else: + self.data_size = None + self.state = 'TX_DATA' + self.ss_block = current_sample_number + + elif (self.state == 'TX_DATA'): + self.crc_calculating = 1 + self.data = (self.data << 1) | self.fsi_data_prev + self.data_count = self.data_count + 1 + if (self.data_count >= self.data_length): + self.es_block = current_sample_number + if (self.data_size == 'BYTE'): + self.putb([5, ['Data: 0x%02x' % self.data]]) + elif (self.data_size == 'HALF_WORD'): + self.putb([5, ['Data: 0x%04x' % self.data]]) + else: + self.putb([5, ['Data: 0x%08x' % self.data]]) + self.ss_block = current_sample_number + self.crc = 0 + self.crc_count = 0 + self.state = 'CRC' + + elif (self.state == 'CRC'): + if (self.crc_count == 0): + self.computed_crc_tx_end = self.crc_internal + self.crc_calculating = 1 + self.crc = (self.crc << 1) | self.fsi_data_prev + self.crc_count = self.crc_count + 1 + if (self.crc_count >= 4): + self.es_block = current_sample_number + if (self.crc == self.computed_crc_tx_end): + self.putb([7, ['CRC: 0x%01x (GOOD)' % self.crc]]) + if (self.response_received): + if (((self.command == 'ABS_ADR') or (self.command == 'REL_ADR') or (self.command == 'SAME_ADR')) + and ((self.response == 'ACK_D') or (self.response == 'ACK'))): + self.prev_address[self.tx_slave_id] = self.address + self.prev_address_valid[self.tx_slave_id] = True + else: + self.putb([7, ['CRC: 0x%01x (BAD)' % self.crc]]) + self.putb([0, ['%s' % 'Bad CRC']]) + self.ss_block = current_sample_number + self.tar_timer = 0 + self.state = 'TAR' + self.timeout_counter = 0 + + elif (self.state == 'BREAK_TAR_QUEUED'): + # Special case, since break operates outside of the main state machine + # This state is a safe entry point into the main state machine + self.tar_timer = 0 + self.state = 'BREAK_TAR' + + elif (self.state == 'BREAK_TAR'): + self.tar_timer = self.tar_timer + 1 + if (self.tar_timer > self.tar_cycles): + self.crc_calculating = 0 + self.crc_internal = 0 + self.es_block = current_sample_number + self.putb([8, ['%s' % 'TAR']]) + self.ss_block = current_sample_number + self.state = 'IDLE' + + elif (self.state == 'TAR'): + self.crc_calculating = 0 + self.crc_internal = 0 + self.tar_timer = self.tar_timer + 1 + if (self.tar_timer > self.tar_cycles): + if (self.response_received == 1): + self.response_received = 0 + if (self.rx_slave_id == self.tx_slave_id): + if (self.response == 'BUSY'): + self.busy_seq_count = self.busy_seq_count + 1 + else: + self.busy_seq_count = 0 + # Sequence complete + self.state = 'IDLE' + if (self.timeout_counter == 0): + self.es_block = self.samplenum_prev + self.putb([8, ['%s' % 'TAR']]) + self.ss_block = current_sample_number + if (self.fsi_data_prev == 1): + self.crc_calculating = 1 + self.rx_slave_id = 0 + self.data_count = 2 + if (self.state == 'IDLE'): + # Already processed response message, was going to IDLE state + self.state = 'TX_SLAVE_ID' + else: + self.state = 'RX_SLAVE_ID' + self.ss_block = self.samplenum_prev + self.es_block = current_sample_number + self.putb([1, ['START']]) + self.ss_block = current_sample_number + else: + self.timeout_counter = self.timeout_counter + 1 + if (self.timeout_counter >= 256): + self.es_block = current_sample_number + self.putb([8, ['%s' % 'Response timeout']]) + self.putb([0, ['%s' % 'Response timeout']]) + self.state = 'IDLE' + + elif (self.state == 'RX_SLAVE_ID'): + self.crc_calculating = 1 + self.response_received = 1 + if (self.data_count > 0): + self.rx_slave_id = (self.rx_slave_id >> 1) | (self.fsi_data_prev << 1) + self.data_count = self.data_count - 1 + if (self.data_count == 0): + self.es_block = current_sample_number + self.putb([5, ['Slave ID: 0x%01x' % self.rx_slave_id]]) + if (self.rx_slave_id != self.tx_slave_id): + self.putb([0, ['%s' % 'Slave ID does not match active transaction']]) + self.ss_block = current_sample_number + self.response_count = 0 + self.response_code = 0 + self.response = None + self.valid_response = False + self.state = 'RESPONSE' + + elif (self.state == 'RESPONSE'): + self.crc_calculating = 1 + self.response_code = (self.response_code << 1) | self.fsi_data_prev + self.response_count = self.response_count + 1 + if ((self.command == 'I_POLL') and (self.rx_slave_id == self.tx_slave_id) and (self.response_count == 1) and (self.response_code == 0b0)): + self.response = 'I_POLL_RSP' + self.valid_response = True + elif ((self.response_count == 2) and (self.response_code == 0b00)): + if (self.direction == 1): + self.response = 'ACK_D' + else: + self.response = 'ACK' + self.valid_response = True + elif ((self.response_count == 2) and (self.response_code == 0b01)): + self.response = 'BUSY' + self.valid_response = True + elif ((self.response_count == 2) and (self.response_code == 0b10)): + self.response = 'ERR_A' + self.valid_response = True + elif ((self.response_count == 2) and (self.response_code == 0b11)): + self.response = 'ERR_C' + self.valid_response = True + if ((self.response_count > 2) or (self.valid_response == True)): + if (self.response_count == 8): + self.es_block = current_sample_number + self.putb([6, ['Invalid response code: 0x%02x/%d' % (self.response_code, self.response_count)]]) + self.putb([0, ['%s' % 'Invalid response code']]) + self.ss_block = current_sample_number + self.state = 'IDLE' + else: + self.es_block = current_sample_number + self.putb([6, ['Response: %s (0x%02x/%d)' % (self.response, self.response_code, self.response_count)]]) + self.ss_block = current_sample_number + if (self.response == 'ACK_D'): + self.data = 0 + self.data_count = 0 + if (self.data_size == 'BYTE'): + self.data_length = 8 + elif (self.data_size == 'HALF_WORD'): + self.data_length = 16 + elif (self.data_size == 'WORD'): + self.data_length = 32 + else: + self.data_size = None + #self.state = 'DIRECTION' + self.state = 'RX_DATA' + elif (self.response == 'ACK'): + self.crc = 0 + self.crc_count = 0 + self.state = 'CRC' + elif (self.response == 'BUSY'): + self.crc = 0 + self.crc_count = 0 + self.state = 'CRC' + elif (self.response == 'ERR_A'): + self.crc = 0 + self.crc_count = 0 + self.state = 'CRC' + elif (self.response == 'ERR_C'): + self.crc = 0 + self.crc_count = 0 + self.state = 'CRC' + elif (self.response == 'I_POLL_RSP'): + self.data = 0 + self.data_count = 0 + self.data_length = 2 + self.state = 'RX_IPOLL_INTERRUPT_FIELD' + else: + self.state = 'IDLE' + + elif (self.state == 'RX_DATA'): + self.crc_calculating = 1 + self.data = (self.data << 1) | self.fsi_data_prev + self.data_count = self.data_count + 1 + if (self.data_count >= self.data_length): + self.es_block = current_sample_number + self.putb([5, ['Data: 0x%08x' % self.data]]) + self.ss_block = current_sample_number + self.crc = 0 + self.crc_count = 0 + self.state = 'CRC' + + elif (self.state == 'RX_IPOLL_INTERRUPT_FIELD'): + self.crc_calculating = 1 + self.data = (self.data << 1) | self.fsi_data_prev + self.data_count = self.data_count + 1 + if (self.data_count >= self.data_length): + self.es_block = current_sample_number + self.putb([5, ['Interrupt Field: 0x%01x' % self.data]]) + self.ss_block = current_sample_number + self.data = 0 + self.data_count = 0 + self.data_length = 3 + self.state = 'RX_IPOLL_DMA_CONTROL_FIELD' + + elif (self.state == 'RX_IPOLL_DMA_CONTROL_FIELD'): + self.crc_calculating = 1 + self.data = (self.data << 1) | self.fsi_data_prev + self.data_count = self.data_count + 1 + if (self.data_count >= self.data_length): + self.es_block = current_sample_number + self.putb([5, ['DMA Control Field: 0x%01x' % self.data]]) + self.ss_block = current_sample_number + self.crc = 0 + self.crc_count = 0 + self.state = 'CRC' + + # CRC calculation + # Implement Galios-type LFSR for polynomial 0x7 (MSB first) + crc_prev = self.crc_internal + if (self.crc_calculating): + crc_feedback = (((crc_prev >> 3) & 1) ^ self.fsi_data_prev) & 1 + if (self.crc_calculating): + self.crc_internal = (self.crc_internal & ~(1 << 0)) | ((crc_feedback & 1) << 0) + self.crc_internal = (self.crc_internal & ~(1 << 1)) | ((((crc_prev & 1) ^ crc_feedback) & 1) << 1) + self.crc_internal = (self.crc_internal & ~(1 << 2)) | (((((crc_prev >> 1) & 1) ^ crc_feedback) & 1) << 2) + self.crc_internal = (self.crc_internal & ~(1 << 3)) | ((((crc_prev >> 2) & 1) & 1) << 3) + + self.fsi_data_prev = fsi_data + self.samplenum_prev = current_sample_number diff --git a/libsigrokdecode4DSL/decoders/gpib/__init__.py b/libsigrokdecode4DSL/decoders/gpib/__init__.py new file mode 100644 index 00000000..3b546ef1 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/gpib/__init__.py @@ -0,0 +1,24 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2016 Rudolf Reuter +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This protocol decoder can decode the GPIB (IEEE-488) protocol. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/gpib/pd.py b/libsigrokdecode4DSL/decoders/gpib/pd.py new file mode 100644 index 00000000..f0c963c2 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/gpib/pd.py @@ -0,0 +1,182 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2016 Rudolf Reuter +## Copyright (C) 2019 DreamSourceLab +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd + +class Decoder(srd.Decoder): + api_version = 3 + id = 'gpib' + name = 'GPIB' + longname = 'General Purpose Interface Bus' + desc = 'IEEE-488 General Purpose Interface Bus (GPIB / HPIB).' + license = 'gplv2+' + inputs = ['logic'] + outputs = [] + tags = ['PC'] + channels = ( + {'id': 'dio1' , 'name': 'DIO1', 'desc': 'Data I/O bit 1'}, + {'id': 'dio2' , 'name': 'DIO2', 'desc': 'Data I/O bit 2'}, + {'id': 'dio3' , 'name': 'DIO3', 'desc': 'Data I/O bit 3'}, + {'id': 'dio4' , 'name': 'DIO4', 'desc': 'Data I/O bit 4'}, + {'id': 'dio5' , 'name': 'DIO5', 'desc': 'Data I/O bit 5'}, + {'id': 'dio6' , 'name': 'DIO6', 'desc': 'Data I/O bit 6'}, + {'id': 'dio7' , 'name': 'DIO7', 'desc': 'Data I/O bit 7'}, + {'id': 'dio8' , 'name': 'DIO8', 'desc': 'Data I/O bit 8'}, + {'id': 'eoi', 'name': 'EOI', 'desc': 'End or identify'}, + {'id': 'dav', 'name': 'DAV', 'desc': 'Data valid'}, + {'id': 'nrfd', 'name': 'NRFD', 'desc': 'Not ready for data'}, + {'id': 'ndac', 'name': 'NDAC', 'desc': 'Not data accepted'}, + {'id': 'ifc', 'name': 'IFC', 'desc': 'Interface clear'}, + {'id': 'srq', 'name': 'SRQ', 'desc': 'Service request'}, + {'id': 'atn', 'name': 'ATN', 'desc': 'Attention'}, + {'id': 'ren', 'name': 'REN', 'desc': 'Remote enable'}, + ) + options = ( + {'id': 'sample_total', 'desc': 'Total number of samples', 'default': 0}, + ) + annotations = ( + ('items', 'Items'), + ('gpib', 'DAT/CMD'), + ('eoi', 'EOI'), + ) + annotation_rows = ( + ('bytes', 'Bytes', (0,)), + ('gpib', 'DAT/CMD', (1,)), + ('eoi', 'EOI', (2,)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.items = [] + self.itemcount = 0 + self.saved_item = None + self.saved_ATN = False + self.saved_EOI = False + self.samplenum = 0 + self.ss_item = self.es_item = None + self.first = True + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def putb(self, data): + self.put(self.ss_item, self.es_item, self.out_ann, data) + + def handle_bits(self, datapins): + dbyte = 0x20 + dATN = False + item2 = False + dEOI = False + item3 = False + # If this is the first item in a word, save its sample number. + if self.itemcount == 0: + self.ss_word = self.samplenum + + # Get the bits for this item. + item = 0 + for i in range(8): + item |= datapins[i] << i + + item = item ^ 0xff # Invert data byte. + self.items.append(item) + self.itemcount += 1 + + if datapins[14] == 0: + item2 = True + if datapins[8] == 0: + item3 = True + + if self.first: + # Save the start sample and item for later (no output yet). + self.ss_item = self.samplenum + self.first = False + self.saved_item = item + self.saved_ATN = item2 + self.saved_EOI = item3 + else: + # Output the saved item. + dbyte = self.saved_item + dATN = self.saved_ATN + dEOI = self.saved_EOI + self.es_item = self.samplenum + self.putb([0, ['%02X' % self.saved_item]]) + + # Encode item byte to GPIB convention. + self.strgpib = ' ' + if dATN: # ATN, decode commands. + if dbyte == 0x01: self.strgpib = 'GTL' + if dbyte == 0x04: self.strgpib = 'SDC' + if dbyte == 0x05: self.strgpib = 'PPC' + if dbyte == 0x08: self.strgpib = 'GET' + if dbyte == 0x09: self.strgpib = 'TCT' + if dbyte == 0x11: self.strgpib = 'LLO' + if dbyte == 0x14: self.strgpib = 'DCL' + if dbyte == 0x15: self.strgpib = 'PPU' + if dbyte == 0x18: self.strgpib = 'SPE' + if dbyte == 0x19: self.strgpib = 'SPD' + if dbyte == 0x3f: self.strgpib = 'UNL' + if dbyte == 0x5f: self.strgpib = 'UNT' + if dbyte > 0x1f and dbyte < 0x3f: # Address Listener. + self.strgpib = 'L' + chr(dbyte + 0x10) + if dbyte > 0x3f and dbyte < 0x5f: # Address Talker + self.strgpib = 'T' + chr(dbyte - 0x10) + else: + if dbyte > 0x1f and dbyte < 0x7f: + self.strgpib = chr(dbyte) + if dbyte == 0x0a: + self.strgpib = 'LF' + if dbyte == 0x0d: + self.strgpib = 'CR' + + self.putb([1, [self.strgpib]]) + self.strEOI = ' ' + if dEOI: + self.strEOI = 'EOI' + self.putb([2, [self.strEOI]]) + + self.ss_item = self.samplenum + self.saved_item = item + self.saved_ATN = item2 + self.saved_EOI = item3 + + if self.itemcount < 16: + return + + self.itemcount, self.items = 0, [] + + def decode(self): + + # Inspect samples at falling edge of DAV. But make sure to also + # start inspection when the capture happens to start with low + # DAV level. Optionally enforce processing when a user specified + # sample number was reached. + waitcond = [{9: 'l'}] + lsn = self.options['sample_total'] + if lsn: + waitcond.append({'skip': lsn}) + while True: + if lsn: + waitcond[1]['skip'] = lsn - self.samplenum - 1 + (d1, d2, d3, d4, d5, d6, d7, d8, eoi, dav, nrfd, ndac, ifc, srq, atn, ren) = self.wait(waitcond) + pins = (d1, d2, d3, d4, d5, d6, d7, d8, eoi, dav, nrfd, ndac, ifc, srq, atn, ren) + self.handle_bits(pins) + waitcond[0][9] = 'f' diff --git a/libsigrokdecode4DSL/decoders/graycode/__init__.py b/libsigrokdecode4DSL/decoders/graycode/__init__.py new file mode 100644 index 00000000..90ef824c --- /dev/null +++ b/libsigrokdecode4DSL/decoders/graycode/__init__.py @@ -0,0 +1,24 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2017 Christoph Rackwitz +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +Gray code and rotary encoder protocol. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/graycode/pd.py b/libsigrokdecode4DSL/decoders/graycode/pd.py new file mode 100644 index 00000000..9303c33a --- /dev/null +++ b/libsigrokdecode4DSL/decoders/graycode/pd.py @@ -0,0 +1,200 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2017 Christoph Rackwitz +## Copyright (C) 2019 DreamSourceLab +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import math +import sigrokdecode as srd +from collections import deque +from common.srdhelper import bitpack, bitunpack + +def gray_encode(plain): + return plain & (plain >> 1) + +def gray_decode(gray): + temp = gray + temp ^= (temp >> 8) + temp ^= (temp >> 4) + temp ^= (temp >> 2) + temp ^= (temp >> 1) + return temp + +def prefix_fmt(value, emin=None): + sgn = (value > 0) - (value < 0) + value = abs(value) + p = math.log10(value) if value else 0 + value = sgn * math.floor(value * 10**int(3 - p)) * 10**-int(3 - p) + e = p // 3 * 3 + if emin is not None and e < emin: + e = emin + value *= 10**-e + p -= e + decimals = 2 - int(p) + prefixes = {-9: 'n', -6: 'µ', -3: 'm', 0: '', 3: 'k', 6: 'M', 9: 'G'} + return '{0:.{1}f} {2}'.format(value, decimals, prefixes[e]) + +class ChannelMapError(Exception): + pass + +class Value: + def __init__(self, onchange): + self.onchange = onchange + self.timestamp = None + self.value = None + + def get(self): + return self.value + + def set(self, timestamp, newval): + if newval != self.value: + if self.value is not None: + self.onchange(self.timestamp, self.value, timestamp, newval) + + self.value = newval + self.timestamp = timestamp + elif False: + if self.value is not None: + self.onchange(self.timestamp, self.value, timestamp, newval) + +MAX_CHANNELS = 8 # 10 channels causes some weird problems... + +class Decoder(srd.Decoder): + api_version = 3 + id = 'graycode' + name = 'Gray code' + longname = 'Gray code and rotary encoder' + desc = 'Accumulate rotary encoder increments, provide statistics.' + license = 'gplv2+' + inputs = ['logic'] + outputs = [] + tags = ['Encoding'] + optional_channels = tuple( + {'id': 'd{}'.format(i), 'name': 'D{}'.format(i), 'desc': 'Data line {}'.format(i)} + for i in range(MAX_CHANNELS) + ) + options = ( + {'id': 'edges', 'desc': 'Edges per rotation', 'default': 0}, + {'id': 'avg_period', 'desc': 'Averaging period', 'default': 10}, + ) + annotations = ( + ('phase', 'Phase'), + ('increment', 'Increment'), + ('count', 'Count'), + ('turns', 'Turns'), + ('interval', 'Interval'), + ('average', 'Average'), + ('rpm', 'Rate'), + ) + annotation_rows = tuple((u, v, (i,)) for i, (u, v) in enumerate(annotations)) + + def __init__(self): + self.reset() + + def reset(self): + self.num_channels = 0 + self.samplerate = None + self.last_n = deque() + + self.phase = Value(self.on_phase) + self.increment = Value(self.on_increment) + self.count = Value(self.on_count) + self.turns = Value(self.on_turns) + + def on_phase(self, told, vold, tnew, vnew): + self.put(told, tnew, self.out_ann, [0, ['{}'.format(vold)]]) + + def on_increment(self, told, vold, tnew, vnew): + if vold == 0: + message = '0' + elif abs(vold) == self.ENCODER_STEPS // 2: + message = '±π' + else: + message = '{:+d}'.format(vold) + self.put(told, tnew, self.out_ann, [1, [message]]) + + def on_count(self, told, vold, tnew, vnew): + self.put(told, tnew, self.out_ann, [2, ['{}'.format(vold)]]) + + def on_turns(self, told, vold, tnew, vnew): + self.put(told, tnew, self.out_ann, [3, ['{:+d}'.format(vold)]]) + + def metadata(self, key, value): + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def decode(self): + chmask = [self.has_channel(i) for i in range(MAX_CHANNELS)] + self.num_channels = sum(chmask) + if chmask != [i < self.num_channels for i in range(MAX_CHANNELS)]: + raise ChannelMapError('Assigned channels need to be contiguous') + + self.ENCODER_STEPS = 1 << self.num_channels + + (d0, d1, d2, d3, d4, d5, d6, d7) = self.wait() + startbits = (d0, d1, d2, d3, d4, d5, d6, d7) + curtime = self.samplenum + + self.turns.set(self.samplenum, 0) + self.count.set(self.samplenum, 0) + self.phase.set(self.samplenum, gray_decode(bitpack(startbits[:self.num_channels]))) + + while True: + prevtime = curtime + (d0, d1, d2, d3, d4, d5, d6, d7) = self.wait([{i: 'e'} for i in range(self.num_channels)]) + bits = (d0, d1, d2, d3, d4, d5, d6, d7) + curtime = self.samplenum + + oldcount = self.count.get() + oldphase = self.phase.get() + + newphase = gray_decode(bitpack(bits[:self.num_channels])) + self.phase.set(self.samplenum, newphase) + + phasedelta_raw = (newphase - oldphase + (self.ENCODER_STEPS // 2 - 1)) % self.ENCODER_STEPS - (self.ENCODER_STEPS // 2 - 1) + phasedelta = phasedelta_raw + self.increment.set(self.samplenum, phasedelta) + if abs(phasedelta) == self.ENCODER_STEPS // 2: + phasedelta = 0 + + self.count.set(self.samplenum, self.count.get() + phasedelta) + + if self.options['edges']: + self.turns.set(self.samplenum, self.count.get() // self.options['edges']) + + if self.samplerate: + period = (curtime - prevtime) / self.samplerate + freq = abs(phasedelta_raw) / period + + self.put(prevtime, curtime, self.out_ann, [4, [ + '{}s, {}Hz'.format(prefix_fmt(period), prefix_fmt(freq))]]) + + if self.options['avg_period']: + self.last_n.append((abs(phasedelta_raw), period)) + if len(self.last_n) > self.options['avg_period']: + self.last_n.popleft() + + avg_period = sum(v for u, v in self.last_n) / (sum(u for u, v in self.last_n) or 1) + self.put(prevtime, curtime, self.out_ann, [5, [ + '{}s, {}Hz'.format(prefix_fmt(avg_period), + prefix_fmt(1 / avg_period))]]) + + if self.options['edges']: + self.put(prevtime, curtime, self.out_ann, [6, ['{}rpm'.format(prefix_fmt(60 * freq / self.options['edges'], emin=0))]]) diff --git a/libsigrokdecode4DSL/decoders/guess_bitrate/__init__.py b/libsigrokdecode4DSL/decoders/guess_bitrate/__init__.py new file mode 100644 index 00000000..a02bf183 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/guess_bitrate/__init__.py @@ -0,0 +1,40 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2013 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This protocol decoder tries to guess the bitrate / baudrate of the +communication on the specified channel. + +Typically this will be used to guess / detect the baudrate used in a UART +communication snippet, but it could also be used to guess bitrates of certain +other protocols or buses. + +It should be noted that this is nothing more than a simple guess / heuristic, +and that there are various cases in practice where the detection of the +bitrate or baudrate will not necessarily have the expected result. + +The precision of the estimated bitrate / baudrate will also depend on the +samplerate used to sample the respective channel. For good results it is +recommended to use a logic analyzer samplerate that is much higher than +the expected bitrate/baudrate that might be used on the channel. + +The last annotation emitted by the decoder will be the best bitrate guess. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/guess_bitrate/pd.py b/libsigrokdecode4DSL/decoders/guess_bitrate/pd.py new file mode 100644 index 00000000..462fa8aa --- /dev/null +++ b/libsigrokdecode4DSL/decoders/guess_bitrate/pd.py @@ -0,0 +1,79 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2013-2016 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd + +class SamplerateError(Exception): + pass + +class Decoder(srd.Decoder): + api_version = 3 + id = 'guess_bitrate' + name = 'Guess bitrate' + longname = 'Guess bitrate/baudrate' + desc = 'Guess the bitrate/baudrate of a UART (or other) protocol.' + license = 'gplv2+' + inputs = ['logic'] + outputs = [] + tags = ['Clock/timing', 'Util'] + channels = ( + {'id': 'data', 'name': 'Data', 'desc': 'Data line'}, + ) + annotations = ( + ('bitrate', 'Bitrate / baudrate'), + ) + + def putx(self, data): + self.put(self.ss_edge, self.samplenum, self.out_ann, data) + + def __init__(self): + self.reset() + + def reset(self): + self.ss_edge = None + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def metadata(self, key, value): + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + + def decode(self): + if not self.samplerate: + raise SamplerateError('Cannot decode without samplerate.') + + # Get the first edge on the data line. + self.wait({0: 'e'}) + self.ss_edge = self.samplenum + + # Get any subsequent edge on the data line. Get the smallest + # distance between any two transitions, assuming it corresponds + # to one bit time of the respective bitrate of the input stream. + # This heuristics keeps getting better for longer captures. + bitwidth = None + while True: + self.wait({0: 'e'}) + + b = self.samplenum - self.ss_edge + if bitwidth is None or b < bitwidth: + bitwidth = b + bitrate = int(float(self.samplerate) / float(b)) + self.putx([0, ['%d' % bitrate]]) + self.ss_edge = self.samplenum diff --git a/libsigrokdecode4DSL/decoders/hdcp/__init__.py b/libsigrokdecode4DSL/decoders/hdcp/__init__.py new file mode 100644 index 00000000..f2e10b6a --- /dev/null +++ b/libsigrokdecode4DSL/decoders/hdcp/__init__.py @@ -0,0 +1,27 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2018 Dave Craig +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This decoder stacks on top of the 'i2c' PD and decodes HDCP messages. + +Details: +https://www.digital-cp.com/sites/default/files/specifications/HDCP%20on%20HDMI%20Specification%20Rev2_2_Final1.pdf +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/hdcp/pd.py b/libsigrokdecode4DSL/decoders/hdcp/pd.py new file mode 100644 index 00000000..157b23a3 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/hdcp/pd.py @@ -0,0 +1,191 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2018 Dave Craig +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd + +msg_ids = { + 2: 'AKE_Init', + 3: 'AKE_Send_Cert', + 4: 'AKE_No_stored_km', + 5: 'AKE_Stored_km', + + 7: 'AKE_Send_H_prime', + 8: 'AKE_Send_Pairing_Info', + + 9: 'LC_Init', + 10: 'LC_Send_L_prime', + + 11: 'SKE_Send_Eks', + 12: 'RepeaterAuth_Send_ReceiverID_List', + + 15: 'RepeaterAuth_Send_Ack', + 16: 'RepeaterAuth_Stream_Manage', + 17: 'RepeaterAuth_Stream_Ready', +} + +write_items = { + 0x00: '1.4 Bksv - Receiver KSV', + 0x08: '1.4 Ri\' - Link Verification', + 0x0a: '1.4 Pj\' - Enhanced Link Verification', + 0x10: '1.4 Aksv - Transmitter KSV', + 0x15: '1.4 Ainfo - Transmitter KSV', + 0x18: '1.4 An - Session random number', + 0x20: '1.4 V\'H0', + 0x24: '1.4 V\'H1', + 0x28: '1.4 V\'H2', + 0x2c: '1.4 V\'H3', + 0x30: '1.4 V\'H4', + 0x40: '1.4 Bcaps', + 0x41: '1.4 Bstatus', + 0x43: '1.4 KSV FIFO', + 0x50: 'HDCP2Version', + 0x60: 'Write_Message', + 0x70: 'RxStatus', + 0x80: 'Read_Message', +} + +class Decoder(srd.Decoder): + api_version = 3 + id = 'hdcp' + name = 'HDCP' + longname = 'HDCP over HDMI' + desc = 'HDCP protocol over HDMI.' + license = 'gplv2+' + inputs = ['i2c'] + outputs = ['hdcp'] + tags = ['PC', 'Security/crypto'] + annotations = \ + tuple(('message-0x%02X' % i, 'Message 0x%02X' % i) for i in range(18)) + ( + ('summary', 'Summary'), + ('warning', 'Warning'), + ) + annotation_rows = ( + ('messages', 'Messages', tuple(range(18))), + ('summaries', 'Summaries', (18,)), + ('warnings', 'Warnings', (19,)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.state = 'IDLE' + self.stack = [] + self.msg = -1 + self.ss = self.es = self.ss_block = self.es_block = 0 + self.init_seq = [] + self.valid = 0 + self.type = '' + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def putb(self, data): + self.put(self.ss_block, self.es_block, self.out_ann, data) + + def decode(self, ss, es, data): + cmd, databyte = data + + # Collect the 'BITS' packet, then return. The next packet is + # guaranteed to belong to these bits we just stored. + if cmd == 'BITS': + self.bits = databyte + return + + self.ss, self.es = ss, es + + # State machine. + if self.state == 'IDLE': + # Wait for an I2C START condition. + if cmd == 'START': + self.reset() + self.ss_block = ss + elif cmd != 'START REPEAT': + return + self.state = 'GET SLAVE ADDR' + elif self.state == 'GET SLAVE ADDR': + if cmd == 'ADDRESS READ': + self.state = 'BUFFER DATA' + if databyte != 0x3a: + self.state = 'IDLE' + elif cmd == 'ADDRESS WRITE': + self.state = 'WRITE OFFSET' + if databyte != 0x3a: + self.state = 'IDLE' + elif self.state == 'WRITE OFFSET': + if cmd == 'DATA WRITE': + if databyte in write_items: + self.type = write_items[databyte] + if databyte in (0x10, 0x15, 0x18, 0x60): + self.state = 'BUFFER DATA' + # If we are reading, then jump back to IDLE for a start repeat. + # If we are writing, then just continue onwards. + if self.state == 'BUFFER DATA': + pass + elif self.type != '': + self.state = 'IDLE' + elif self.state == 'BUFFER DATA': + if cmd in ('STOP', 'NACK'): + self.es_block = es + self.state = 'IDLE' + if self.type == '': + return + if not self.stack: + self.putb([18, ['%s' % (self.type)]]) + return + if self.type == 'RxStatus': + rxstatus = (self.stack.pop() << 8) | self.stack.pop() + reauth_req = (rxstatus & 0x800) != 0 + ready = (rxstatus & 0x400) != 0 + length = rxstatus & 0x3ff + text = '%s, reauth %s, ready %s, length %s' % \ + (self.type, reauth_req, ready, length) + self.putb([18, [text]]) + elif self.type == '1.4 Bstatus': + bstatus = (self.stack.pop() << 8) | self.stack.pop() + device_count = bstatus & 0x7f + max_devs_exceeded = (bstatus & 0x80) != 0 + depth = ((bstatus & 0x700) >> 8) + max_cascase_exceeded = bstatus & 0x800 + hdmi_mode = (bstatus & 0x1000) != 0 + text = '%s, %s devices, depth %s, hdmi mode %s' % \ + (self.type, device_count, depth, hdmi_mode) + self.putb([18, [text]]) + elif self.type == 'Read_Message': + msg = self.stack.pop(0) + self.putb([msg, ['%s, %s' % (self.type, + msg_ids.get(msg, 'Invalid'))]]) + elif self.type == 'Write_Message': + msg = self.stack.pop(0) + self.putb([msg, ['%s, %s' % (self.type, + msg_ids.get(msg, 'Invalid'))]]) + elif self.type == 'HDCP2Version': + version = self.stack.pop(0) + if (version & 0x4): + self.putb([18, ['HDCP2']]) + else: + self.putb([18, ['NOT HDCP2']]) + else: + self.putb([18, ['%s' % (self.type)]]) + elif cmd == 'DATA READ': + # Stack up our data bytes. + self.stack.append(databyte) + elif cmd == 'DATA WRITE': + # Stack up our data bytes. + self.stack.append(databyte) diff --git a/libsigrokdecode4DSL/decoders/i2cdemux/__init__.py b/libsigrokdecode4DSL/decoders/i2cdemux/__init__.py new file mode 100644 index 00000000..e3e9a913 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/i2cdemux/__init__.py @@ -0,0 +1,27 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2012 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This is a generic I²C demultiplexing protocol decoder. + +It takes an I²C stream as input and outputs multiple I²C streams, each +stream containing only I²C packets for one specific I²C slave. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/i2cdemux/pd.py b/libsigrokdecode4DSL/decoders/i2cdemux/pd.py new file mode 100644 index 00000000..d6841d32 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/i2cdemux/pd.py @@ -0,0 +1,80 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2012 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd + +class Decoder(srd.Decoder): + api_version = 3 + id = 'i2cdemux' + name = 'I²C demux' + longname = 'I²C demultiplexer' + desc = 'Demux I²C packets into per-slave-address streams.' + license = 'gplv2+' + inputs = ['i2c'] + outputs = [] # TODO: Only known at run-time. + tags = ['Util'] + + def __init__(self): + self.reset() + + def reset(self): + self.packets = [] # Local cache of I²C packets + self.slaves = [] # List of known slave addresses + self.stream = -1 # Current output stream + self.streamcount = 0 # Number of created output streams + + def start(self): + self.out_python = [] + + # Grab I²C packets into a local cache, until an I²C STOP condition + # packet comes along. At some point before that STOP condition, there + # will have been an ADDRESS READ or ADDRESS WRITE which contains the + # I²C address of the slave that the master wants to talk to. + # We use this slave address to figure out which output stream should + # get the whole chunk of packets (from START to STOP). + def decode(self, ss, es, data): + + cmd, databyte = data + + # Add the I²C packet to our local cache. + self.packets.append([ss, es, data]) + + if cmd in ('ADDRESS READ', 'ADDRESS WRITE'): + if databyte in self.slaves: + self.stream = self.slaves.index(databyte) + return + + # We're never seen this slave, add a new stream. + self.slaves.append(databyte) + self.out_python.append(self.register(srd.OUTPUT_PYTHON, + proto_id='i2c-%s' % hex(databyte))) + self.stream = self.streamcount + self.streamcount += 1 + elif cmd == 'STOP': + if self.stream == -1: + raise Exception('Invalid stream!') # FIXME? + + # Send the whole chunk of I²C packets to the correct stream. + for p in self.packets: + self.put(p[0], p[1], self.out_python[self.stream], p[2]) + + self.packets = [] + self.stream = -1 + else: + pass # Do nothing, only add the I²C packet to our cache. diff --git a/libsigrokdecode4DSL/decoders/i2cfilter/__init__.py b/libsigrokdecode4DSL/decoders/i2cfilter/__init__.py new file mode 100644 index 00000000..be97bf0a --- /dev/null +++ b/libsigrokdecode4DSL/decoders/i2cfilter/__init__.py @@ -0,0 +1,37 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2012 Bert Vermeulen +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 3 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This is a generic I²C filtering protocol decoder. + +It takes input from the I²C protocol decoder and removes all traffic +except that from/to the specified slave address and/or direction. + +It then outputs the filtered data again as OUTPUT_PROTO of type/format 'i2c' +(up the protocol decoder stack). No annotations are output. + +The I²C slave address to filter out should be passed in as an option +'address', as an integer. A specific read or write operation can be selected +with the 'direction' option, which should be 'read', 'write', or 'both'. + +Both of these are optional; if no options are specified the entire payload +of the I²C session will be output. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/i2cfilter/pd.py b/libsigrokdecode4DSL/decoders/i2cfilter/pd.py new file mode 100644 index 00000000..7798e17a --- /dev/null +++ b/libsigrokdecode4DSL/decoders/i2cfilter/pd.py @@ -0,0 +1,93 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2012 Bert Vermeulen +## Copyright (C) 2012 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 3 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +# TODO: Support for filtering out multiple slave/direction pairs? + +import sigrokdecode as srd + +class Decoder(srd.Decoder): + api_version = 3 + id = 'i2cfilter' + name = 'I²C filter' + longname = 'I²C filter' + desc = 'Filter out addresses/directions in an I²C stream.' + license = 'gplv3+' + inputs = ['i2c'] + outputs = ['i2c'] + tags = ['Util'] + options = ( + {'id': 'address', 'desc': 'Address to filter out of the I²C stream', + 'default': 0}, + {'id': 'direction', 'desc': 'Direction to filter', 'default': 'both', + 'values': ('read', 'write', 'both')} + ) + + def __init__(self): + self.reset() + + def reset(self): + self.curslave = -1 + self.curdirection = None + self.packets = [] # Local cache of I²C packets + + def start(self): + self.out_python = self.register(srd.OUTPUT_PYTHON, proto_id='i2c') + if self.options['address'] not in range(0, 127 + 1): + raise Exception('Invalid slave (must be 0..127).') + + # Grab I²C packets into a local cache, until an I²C STOP condition + # packet comes along. At some point before that STOP condition, there + # will have been an ADDRESS READ or ADDRESS WRITE which contains the + # I²C address of the slave that the master wants to talk to. + # If that slave shall be filtered, output the cache (all packets from + # START to STOP) as proto 'i2c', otherwise drop it. + def decode(self, ss, es, data): + + cmd, databyte = data + + # Add the I²C packet to our local cache. + self.packets.append([ss, es, data]) + + if cmd in ('ADDRESS READ', 'ADDRESS WRITE'): + self.curslave = databyte + self.curdirection = cmd[8:].lower() + elif cmd in ('STOP', 'START REPEAT'): + # If this chunk was not for the correct slave, drop it. + if self.options['address'] == 0: + pass + elif self.curslave != self.options['address']: + self.packets = [] + return + + # If this chunk was not in the right direction, drop it. + if self.options['direction'] == 'both': + pass + elif self.options['direction'] != self.curdirection: + self.packets = [] + return + + # TODO: START->STOP chunks with both read and write (Repeat START) + # Otherwise, send out the whole chunk of I²C packets. + for p in self.packets: + self.put(p[0], p[1], self.out_python, p[2]) + + self.packets = [] + else: + pass # Do nothing, only add the I²C packet to our cache. diff --git a/libsigrokdecode4DSL/decoders/i2s/__init__.py b/libsigrokdecode4DSL/decoders/i2s/__init__.py new file mode 100644 index 00000000..a0b7097f --- /dev/null +++ b/libsigrokdecode4DSL/decoders/i2s/__init__.py @@ -0,0 +1,29 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2012 Joel Holdsworth +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +I²S (Integrated Interchip Sound) is a serial bus for connecting digital +audio devices (usually on the same device/board). + +Details: +http://www.nxp.com/acrobat_download/various/I2SBUS.pdf +http://en.wikipedia.org/wiki/I2s +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/i2s/pd.py b/libsigrokdecode4DSL/decoders/i2s/pd.py new file mode 100644 index 00000000..7e4959ec --- /dev/null +++ b/libsigrokdecode4DSL/decoders/i2s/pd.py @@ -0,0 +1,214 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2012 Joel Holdsworth +## Copyright (C) 2019 DreamSourceLab +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd +import struct + +''' +OUTPUT_PYTHON format: + +Packet: +[, ] + +, : + - 'DATA', [, ] + +: 'L' or 'R' +: integer +''' + +class Decoder(srd.Decoder): + api_version = 3 + id = 'i2s' + name = 'I²S' + longname = 'Integrated Interchip Sound' + desc = 'Serial bus for connecting digital audio devices.' + license = 'gplv2+' + inputs = ['logic'] + outputs = ['i2s'] + tags = ['Audio', 'PC'] + channels = ( + {'id': 'sck', 'name': 'SCK', 'desc': 'Bit clock line'}, + {'id': 'ws', 'name': 'WS', 'desc': 'Word select line'}, + {'id': 'sd', 'name': 'SD', 'desc': 'Serial data line'}, + ) + options = ( + {'id': 'ws_polarity', 'desc': 'WS polarity', 'default': 'left-high', + 'values': ('left-low', 'left-high')}, + {'id': 'clk_edge', 'desc': 'SCK active edge', 'default': 'rising-edge', + 'values': ('rising-edge', 'falling-edge')}, + {'id': 'bit_shift', 'desc': 'Bit shift', 'default': 'none', + 'values': ('right-shifted by one', 'none')}, + {'id': 'bit_align', 'desc': 'Bit align', 'default': 'left-aligned', + 'values': ('left-aligned', 'right-aligned')}, + {'id': 'bitorder', 'desc': 'Bit order', + 'default': 'msb-first', 'values': ('msb-first', 'lsb-first')}, + {'id': 'wordsize', 'desc': 'Word size', 'default': 16, + 'values': tuple(range(4,129,1))}, + ) + annotations = ( + ('left', 'Left channel'), + ('right', 'Right channel'), + ('warnings', 'Warnings'), + ) + binary = ( + ('wav', 'WAV file'), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.samplerate = None + self.oldws = 1 + self.bitcount = 0 + self.data = 0 + self.samplesreceived = 0 + self.first_sample = None + self.ss_block = None + self.wordlength = -1 + self.wrote_wav_header = False + + def start(self): + self.out_python = self.register(srd.OUTPUT_PYTHON) + self.out_binary = self.register(srd.OUTPUT_BINARY) + self.out_ann = self.register(srd.OUTPUT_ANN) + + def metadata(self, key, value): + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + + def putpb(self, data): + self.put(self.ss_block, self.samplenum, self.out_python, data) + + def putbin(self, data): + self.put(self.ss_block, self.samplenum, self.out_binary, data) + + def putb(self, data): + self.put(self.ss_block, self.samplenum, self.out_ann, data) + + def report(self): + # Calculate the sample rate. + samplerate = '?' + if self.ss_block is not None and \ + self.first_sample is not None and \ + self.ss_block > self.first_sample and \ + self.samplerate: + samplerate = '%d' % (self.samplesreceived * + self.samplerate / (self.ss_block - + self.first_sample)) + + return 'I²S: %d %d-bit samples received at %sHz' % \ + (self.samplesreceived, self.wordlength, samplerate) + + def wav_header(self): + # Chunk descriptor + h = b'RIFF' + h += b'\x24\x80\x00\x00' # Chunk size (2084) + h += b'WAVE' + # Fmt subchunk + h += b'fmt ' + h += b'\x10\x00\x00\x00' # Subchunk size (16 bytes) + h += b'\x01\x00' # Audio format (0x0001 == PCM) + h += b'\x02\x00' # Number of channels (2) + h += b'\x80\x3e\x00\x00' # Samplerate (16000) + h += b'\x00\xfa\x00\x00' # Byterate (64000) + h += b'\x04\x00' # Blockalign (4) + h += b'\x20\x00' # Bits per sample (32) + # Data subchunk + h += b'data' + h += b'\xff\xff\xff\xff' # Subchunk size (4G bytes) TODO + return h + + def wav_sample(self, sample): + return struct.pack(' self.bitcount: + self.putb([2, ['Received %d-bit word, expected %d-bit ' + 'word' % (self.bitcount, self.wordlength)]]) + else: + if (left_algined and msb) or (not left_algined and not msb): + self.data >>= self.bitcount - self.wordlength + else: + self.data &= int("1"*self.wordlength, 2) + self.oldws = self.oldws if left_high else not self.oldws + idx = 0 if self.oldws else 1 + c1 = 'Left channel' if self.oldws else 'Right channel' + c2 = 'Left' if self.oldws else 'Right' + c3 = 'L' if self.oldws else 'R' + v = '%08x' % self.data + self.putpb(['DATA', [c3, self.data]]) + self.putb([idx, ['%s: %s' % (c1, v), '%s: %s' % (c2, v), + '%s: %s' % (c3, v), c3]]) + self.putbin([0, self.wav_sample(self.data)]) + + + # Reset decoder state. + self.data = 0 if right_shifted else self.last + self.bitcount = 0 if right_shifted else 1 + self.ss_block = self.samplenum + + # Save the first sample position. + if self.first_sample is None: + self.first_sample = self.samplenum + + self.oldws = ws diff --git a/libsigrokdecode4DSL/decoders/iec/__init__.py b/libsigrokdecode4DSL/decoders/iec/__init__.py new file mode 100644 index 00000000..fa0d96f3 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/iec/__init__.py @@ -0,0 +1,24 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2017 Marcus Comstedt +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This protocol decoder can decode the Commodore serial IEEE-488 (IEC) protocol. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/iec/pd.py b/libsigrokdecode4DSL/decoders/iec/pd.py new file mode 100644 index 00000000..8acd1d1f --- /dev/null +++ b/libsigrokdecode4DSL/decoders/iec/pd.py @@ -0,0 +1,168 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2017 Marcus Comstedt +## Copyright (C) 2019 DreamSourceLab +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd + +step_wait_conds = ( + [{2: 'f'}, {0: 'l', 1: 'h'}], + [{2: 'f'}, {0: 'h', 1: 'h'}, {1: 'l'}], + [{2: 'f'}, {0: 'f'}, {1: 'l'}], + [{2: 'f'}, {1: 'e'}], +) + +class Decoder(srd.Decoder): + api_version = 3 + id = 'iec' + name = 'IEC' + longname = 'Commodore IEC bus' + desc = 'Commodore serial IEEE-488 (IEC) bus protocol.' + license = 'gplv2+' + inputs = ['logic'] + outputs = [] + tags = ['PC', 'Retro computing'] + channels = ( + {'id': 'data', 'name': 'DATA', 'desc': 'Data I/O'}, + {'id': 'clk', 'name': 'CLK', 'desc': 'Clock'}, + {'id': 'atn', 'name': 'ATN', 'desc': 'Attention'}, + ) + optional_channels = ( + {'id': 'srq', 'name': 'SRQ', 'desc': 'Service request'}, + ) + annotations = ( + ('items', 'Items'), + ('gpib', 'DAT/CMD'), + ('eoi', 'EOI'), + ) + annotation_rows = ( + ('bytes', 'Bytes', (0,)), + ('gpib', 'DAT/CMD', (1,)), + ('eoi', 'EOI', (2,)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.saved_ATN = False + self.saved_EOI = False + self.ss_item = self.es_item = None + self.step = 0 + self.bits = 0 + self.numbits = 0 + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def putb(self, data): + self.put(self.ss_item, self.es_item, self.out_ann, data) + + def handle_bits(self): + # Output the saved item. + dbyte = self.bits + dATN = self.saved_ATN + dEOI = self.saved_EOI + self.es_item = self.samplenum + self.putb([0, ['%02X' % dbyte]]) + + # Encode item byte to GPIB convention. + self.strgpib = ' ' + if dATN: # ATN, decode commands. + # Note: Commands < 0x20 are not used on IEC bus. + if dbyte == 0x01: self.strgpib = 'GTL' + if dbyte == 0x04: self.strgpib = 'SDC' + if dbyte == 0x05: self.strgpib = 'PPC' + if dbyte == 0x08: self.strgpib = 'GET' + if dbyte == 0x09: self.strgpib = 'TCT' + if dbyte == 0x11: self.strgpib = 'LLO' + if dbyte == 0x14: self.strgpib = 'DCL' + if dbyte == 0x15: self.strgpib = 'PPU' + if dbyte == 0x18: self.strgpib = 'SPE' + if dbyte == 0x19: self.strgpib = 'SPD' + + if dbyte == 0x3f: self.strgpib = 'UNL' + if dbyte == 0x5f: self.strgpib = 'UNT' + if dbyte > 0x1f and dbyte < 0x3f: # Address listener. + self.strgpib = 'L' + chr(dbyte + 0x10) + if dbyte > 0x3f and dbyte < 0x5f: # Address talker. + self.strgpib = 'T' + chr(dbyte - 0x10) + if dbyte > 0x5f and dbyte < 0x70: # Channel reopen. + self.strgpib = 'R' + chr(dbyte - 0x30) + if dbyte > 0xdf and dbyte < 0xf0: # Channel close. + self.strgpib = 'C' + chr(dbyte - 0xb0) + if dbyte > 0xef: # Channel open. + self.strgpib = 'O' + chr(dbyte - 0xc0) + else: + if dbyte > 0x1f and dbyte < 0x7f: + self.strgpib = chr(dbyte) + if dbyte == 0x0a: + self.strgpib = 'LF' + if dbyte == 0x0d: + self.strgpib = 'CR' + + self.putb([1, [self.strgpib]]) + self.strEOI = ' ' + if dEOI: + self.strEOI = 'EOI' + self.putb([2, [self.strEOI]]) + + def decode(self): + while True: + + (data, clk, atn, srq) = self.wait(step_wait_conds[self.step]) + + if (self.matched & (0b1 << 0)): + # Falling edge on ATN, reset step. + self.step = 0 + + if self.step == 0: + # Don't use self.matched[1] here since we might come from + # a step with different conds due to the code above. + if data == 0 and clk == 1: + # Rising edge on CLK while DATA is low: Ready to send. + self.step = 1 + elif self.step == 1: + if data == 1 and clk == 1: + # Rising edge on DATA while CLK is high: Ready for data. + self.ss_item = self.samplenum + self.saved_ATN = not atn + self.saved_EOI = False + self.bits = 0 + self.numbits = 0 + self.step = 2 + elif clk == 0: + # CLK low again, transfer aborted. + self.step = 0 + elif self.step == 2: + if data == 0 and clk == 1: + # DATA goes low while CLK is still high, EOI confirmed. + self.saved_EOI = True + elif clk == 0: + self.step = 3 + elif self.step == 3: + if (self.matched & (0b1 << 1)): + if clk == 1: + # Rising edge on CLK; latch DATA. + self.bits |= data << self.numbits + elif clk == 0: + # Falling edge on CLK; end of bit. + self.numbits += 1 + if self.numbits == 8: + self.handle_bits() + self.step = 0 diff --git a/libsigrokdecode4DSL/decoders/ieee488/__init__.py b/libsigrokdecode4DSL/decoders/ieee488/__init__.py new file mode 100644 index 00000000..4240114a --- /dev/null +++ b/libsigrokdecode4DSL/decoders/ieee488/__init__.py @@ -0,0 +1,26 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2019 Gerhard Sittig +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This protocol decoder can decode the GPIB (IEEE-488) protocol. Both variants +of (up to) 16 parallel lines as well as serial communication (IEC bus) are +supported in this implementation. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/ieee488/pd.py b/libsigrokdecode4DSL/decoders/ieee488/pd.py new file mode 100644 index 00000000..4531cb30 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/ieee488/pd.py @@ -0,0 +1,748 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2016 Rudolf Reuter +## Copyright (C) 2017 Marcus Comstedt +## Copyright (C) 2019 Gerhard Sittig +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +# This file was created from earlier implementations of the 'gpib' and +# the 'iec' protocol decoders. It combines the parallel and the serial +# transmission variants in a single instance with optional inputs for +# maximum code re-use. + +# TODO +# - Extend annotations for improved usability. +# - Keep talkers' data streams on separate annotation rows? Is this useful +# here at the GPIB level, or shall stacked decoders dispatch these? May +# depend on how often captures get inspected which involve multiple peers. +# - Make serial bit annotations optional? Could slow down interactive +# exploration for long captures (see USB). +# - Move the inlined Commodore IEC peripherals support to a stacked decoder +# when more peripherals get added. +# - SCPI over GPIB may "represent somewhat naturally" here already when +# text lines are a single run of data at the GPIB layer (each line between +# the address spec and either EOI or ATN). So a stacked SCPI decoder may +# only become necessary when the text lines' content shall get inspected. + +import sigrokdecode as srd +from common.srdhelper import bitpack + +''' +OUTPUT_PYTHON format for stacked decoders: + +General packet format: +[, , ] + +This is the list of s and their respective values: + +Raw bits and bytes at the physical transport level: + - 'IEC_BIT': is not applicable, is the transport's bit value. + - 'GPIB_RAW': is not applicable, is the transport's + byte value. Data bytes are in the 0x00-0xff range, command/address + bytes are in the 0x100-0x1ff range. + +GPIB level byte fields (commands, addresses, pieces of data): + - 'COMMAND': is not applicable, is the command's byte value. + - 'LISTEN': is the listener address (0-30), is the raw + byte value (including the 0x20 offset). + - 'TALK': is the talker address (0-30), is the raw byte + value (including the 0x40 offset). + - 'SECONDARY': is the secondary address (0-31), is the + raw byte value (including the 0x60 offset). + - 'MSB_SET': as well as are the raw byte value (including + the 0x80 offset). This usually does not happen for GPIB bytes with ATN + active, but was observed with the IEC bus and Commodore floppy drives, + when addressing channels within the device. + - 'DATA_BYTE': is the talker address (when available), + is the raw data byte (transport layer, ATN inactive). + +Extracted payload information (peers and their communicated data): + - 'TALK_LISTEN': is the current talker, is the list of + current listeners. These updates for the current "connected peers" + are sent when the set of peers changes, i.e. after talkers/listeners + got selected or deselected. Of course the data only covers what could + be gathered from the input data. Some controllers may not explicitly + address themselves, or captures may not include an early setup phase. + - 'TALKER_BYTES': is the talker address (when available), + is the accumulated byte sequence between addressing a talker and EOI, + or the next command/address. + - 'TALKER_TEXT': is the talker address (when available), + is the accumulated text sequence between addressing a talker and EOI, + or the next command/address. +''' + +class ChannelError(Exception): + pass + +def _format_ann_texts(fmts, **args): + if not fmts: + return None + return [fmt.format(**args) for fmt in fmts] + +_cmd_table = { + # Command codes in the 0x00-0x1f range. + 0x01: ['Go To Local', 'GTL'], + 0x04: ['Selected Device Clear', 'SDC'], + 0x05: ['Parallel Poll Configure', 'PPC'], + 0x08: ['Global Execute Trigger', 'GET'], + 0x09: ['Take Control', 'TCT'], + 0x11: ['Local Lock Out', 'LLO'], + 0x14: ['Device Clear', 'DCL'], + 0x15: ['Parallel Poll Unconfigure', 'PPU'], + 0x18: ['Serial Poll Enable', 'SPE'], + 0x19: ['Serial Poll Disable', 'SPD'], + # Unknown type of command. + None: ['Unknown command 0x{cmd:02x}', 'command 0x{cmd:02x}', 'cmd {cmd:02x}', 'C{cmd_ord:c}'], + # Special listener/talker "addresses" (deselecting previous peers). + 0x3f: ['Unlisten', 'UNL'], + 0x5f: ['Untalk', 'UNT'], +} + +def _is_command(b): + # Returns a tuple of booleans (or None when not applicable) whether + # the raw GPIB byte is: a command, an un-listen, an un-talk command. + if b in range(0x00, 0x20): + return True, None, None + if b in range(0x20, 0x40) and (b & 0x1f) == 31: + return True, True, False + if b in range(0x40, 0x60) and (b & 0x1f) == 31: + return True, False, True + return False, None, None + +def _is_listen_addr(b): + if b in range(0x20, 0x40): + return b & 0x1f + return None + +def _is_talk_addr(b): + if b in range(0x40, 0x60): + return b & 0x1f + return None + +def _is_secondary_addr(b): + if b in range(0x60, 0x80): + return b & 0x1f + return None + +def _is_msb_set(b): + if b & 0x80: + return b + return None + +def _get_raw_byte(b, atn): + # "Decorate" raw byte values for stacked decoders. + return b | 0x100 if atn else b + +def _get_raw_text(b, atn): + return ['{leader}{data:02x}'.format(leader = '/' if atn else '', data = b)] + +def _get_command_texts(b): + fmts = _cmd_table.get(b, None) + known = fmts is not None + if not fmts: + fmts = _cmd_table.get(None, None) + if not fmts: + return known, None + return known, _format_ann_texts(fmts, cmd = b, cmd_ord = ord('0') + b) + +def _get_address_texts(b): + laddr = _is_listen_addr(b) + taddr = _is_talk_addr(b) + saddr = _is_secondary_addr(b) + msb = _is_msb_set(b) + fmts = None + if laddr is not None: + fmts = ['Listen {addr:d}', 'L {addr:d}', 'L{addr_ord:c}'] + addr = laddr + elif taddr is not None: + fmts = ['Talk {addr:d}', 'T {addr:d}', 'T{addr_ord:c}'] + addr = taddr + elif saddr is not None: + fmts = ['Secondary {addr:d}', 'S {addr:d}', 'S{addr_ord:c}'] + addr = saddr + elif msb is not None: # For IEC bus compat. + fmts = ['Secondary {addr:d}', 'S {addr:d}', 'S{addr_ord:c}'] + addr = msb + return _format_ann_texts(fmts, addr = addr, addr_ord = ord('0') + addr) + +def _get_data_text(b): + # TODO Move the table of ASCII control characters to a common location? + # TODO Move the "printable with escapes" logic to a common helper? + _control_codes = { + 0x00: 'NUL', + 0x01: 'SOH', + 0x02: 'STX', + 0x03: 'ETX', + 0x04: 'EOT', + 0x05: 'ENQ', + 0x06: 'ACK', + 0x07: 'BEL', + 0x08: 'BS', + 0x09: 'TAB', + 0x0a: 'LF', + 0x0b: 'VT', + 0x0c: 'FF', + 0x0d: 'CR', + 0x0e: 'SO', + 0x0f: 'SI', + 0x10: 'DLE', + 0x11: 'DC1', + 0x12: 'DC2', + 0x13: 'DC3', + 0x14: 'DC4', + 0x15: 'NAK', + 0x16: 'SYN', + 0x17: 'ETB', + 0x18: 'CAN', + 0x19: 'EM', + 0x1a: 'SUB', + 0x1b: 'ESC', + 0x1c: 'FS', + 0x1d: 'GS', + 0x1e: 'RS', + 0x1f: 'US', + } + # Yes, exclude 0x7f (DEL) here. It's considered non-printable. + if b in range(0x20, 0x7f) and b not in ('[', ']'): + return '{:s}'.format(chr(b)) + elif b in _control_codes: + return '[{:s}]'.format(_control_codes[b]) + # Use a compact yet readable and unambigous presentation for bytes + # which contain non-printables. The format that is used here is + # compatible with 93xx EEPROM and UART decoders. + return '[{:02x}]'.format(b) + +( + PIN_DIO1, PIN_DIO2, PIN_DIO3, PIN_DIO4, + PIN_DIO5, PIN_DIO6, PIN_DIO7, PIN_DIO8, + PIN_EOI, PIN_DAV, PIN_NRFD, PIN_NDAC, + PIN_IFC, PIN_SRQ, PIN_ATN, PIN_REN, + PIN_CLK, +) = range(17) +PIN_DATA = PIN_DIO1 + +( + ANN_RAW_BIT, ANN_RAW_BYTE, + ANN_CMD, ANN_LADDR, ANN_TADDR, ANN_SADDR, ANN_DATA, + ANN_EOI, + ANN_TEXT, + # TODO Want to provide one annotation class per talker address (0-30)? + ANN_IEC_PERIPH, + ANN_WARN, +) = range(11) + +( + BIN_RAW, + BIN_DATA, + # TODO Want to provide one binary annotation class per talker address (0-30)? +) = range(2) + +class Decoder(srd.Decoder): + api_version = 3 + id = 'ieee488' + name = 'IEEE-488' + longname = 'IEEE-488 GPIB/HPIB/IEC' + desc = 'IEEE-488 General Purpose Interface Bus (GPIB/HPIB or IEC).' + license = 'gplv2+' + inputs = ['logic'] + outputs = ['ieee488'] + tags = ['PC', 'Retro computing'] + channels = ( + {'id': 'dio1' , 'name': 'DIO1/DATA', + 'desc': 'Data I/O bit 1, or serial data'}, + ) + optional_channels = ( + {'id': 'dio2' , 'name': 'DIO2', 'desc': 'Data I/O bit 2'}, + {'id': 'dio3' , 'name': 'DIO3', 'desc': 'Data I/O bit 3'}, + {'id': 'dio4' , 'name': 'DIO4', 'desc': 'Data I/O bit 4'}, + {'id': 'dio5' , 'name': 'DIO5', 'desc': 'Data I/O bit 5'}, + {'id': 'dio6' , 'name': 'DIO6', 'desc': 'Data I/O bit 6'}, + {'id': 'dio7' , 'name': 'DIO7', 'desc': 'Data I/O bit 7'}, + {'id': 'dio8' , 'name': 'DIO8', 'desc': 'Data I/O bit 8'}, + {'id': 'eoi', 'name': 'EOI', 'desc': 'End or identify'}, + {'id': 'dav', 'name': 'DAV', 'desc': 'Data valid'}, + {'id': 'nrfd', 'name': 'NRFD', 'desc': 'Not ready for data'}, + {'id': 'ndac', 'name': 'NDAC', 'desc': 'Not data accepted'}, + {'id': 'ifc', 'name': 'IFC', 'desc': 'Interface clear'}, + {'id': 'srq', 'name': 'SRQ', 'desc': 'Service request'}, + {'id': 'atn', 'name': 'ATN', 'desc': 'Attention'}, + {'id': 'ren', 'name': 'REN', 'desc': 'Remote enable'}, + {'id': 'clk', 'name': 'CLK', 'desc': 'Serial clock'}, + ) + options = ( + {'id': 'iec_periph', 'desc': 'Decode Commodore IEC bus peripherals details', + 'default': 'no', 'values': ('no', 'yes')}, + {'id': 'delim', 'desc': 'Payload data delimiter', + 'default': 'eol', 'values': ('none', 'eol')}, + ) + annotations = ( + ('bit', 'IEC bit'), + ('raw', 'Raw byte'), + ('cmd', 'Command'), + ('laddr', 'Listener address'), + ('taddr', 'Talker address'), + ('saddr', 'Secondary address'), + ('data', 'Data byte'), + ('eoi', 'EOI'), + ('text', 'Talker text'), + ('periph', 'IEC bus peripherals'), + ('warning', 'Warning'), + ) + annotation_rows = ( + ('bits', 'IEC bits', (ANN_RAW_BIT,)), + ('raws', 'Raw bytes', (ANN_RAW_BYTE,)), + ('gpib', 'Commands/data', (ANN_CMD, ANN_LADDR, ANN_TADDR, ANN_SADDR, ANN_DATA,)), + ('eois', 'EOI', (ANN_EOI,)), + ('texts', 'Talker texts', (ANN_TEXT,)), + ('periphs', 'IEC peripherals', (ANN_IEC_PERIPH,)), + ('warnings', 'Warnings', (ANN_WARN,)), + ) + binary = ( + ('raw', 'Raw bytes'), + ('data', 'Talker bytes'), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.curr_raw = None + self.curr_atn = None + self.curr_eoi = None + self.latch_atn = None + self.latch_eoi = None + self.accu_bytes = [] + self.accu_text = [] + self.ss_raw = None + self.es_raw = None + self.ss_eoi = None + self.es_eoi = None + self.ss_text = None + self.es_text = None + self.last_talker = None + self.last_listener = [] + self.last_iec_addr = None + self.last_iec_sec = None + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + self.out_bin = self.register(srd.OUTPUT_BINARY) + self.out_python = self.register(srd.OUTPUT_PYTHON) + + def putg(self, ss, es, data): + self.put(ss, es, self.out_ann, data) + + def putbin(self, ss, es, data): + self.put(ss, es, self.out_bin, data) + + def putpy(self, ss, es, ptype, addr, pdata): + self.put(ss, es, self.out_python, [ptype, addr, pdata]) + + def emit_eoi_ann(self, ss, es): + self.putg(ss, es, [ANN_EOI, ['EOI']]) + + def emit_bin_ann(self, ss, es, ann_cls, data): + self.putbin(ss, es, [ann_cls, bytes(data)]) + + def emit_data_ann(self, ss, es, ann_cls, data): + self.putg(ss, es, [ann_cls, data]) + + def emit_warn_ann(self, ss, es, data): + self.putg(ss, es, [ANN_WARN, data]) + + def flush_bytes_text_accu(self): + if self.accu_bytes and self.ss_text is not None and self.es_text is not None: + self.emit_bin_ann(self.ss_text, self.es_text, BIN_DATA, bytearray(self.accu_bytes)) + self.putpy(self.ss_text, self.es_text, 'TALKER_BYTES', self.last_talker, bytearray(self.accu_bytes)) + self.accu_bytes = [] + if self.accu_text and self.ss_text is not None and self.es_text is not None: + text = ''.join(self.accu_text) + self.emit_data_ann(self.ss_text, self.es_text, ANN_TEXT, [text]) + self.putpy(self.ss_text, self.es_text, 'TALKER_TEXT', self.last_talker, text) + self.accu_text = [] + self.ss_text = self.es_text = None + + def check_extra_flush(self, b): + # Optionally flush previously accumulated runs of payload data + # according to user specified conditions. + if self.options['delim'] == 'none': + return + if not self.accu_bytes: + return + + # This implementation exlusively handles "text lines", but adding + # support for more variants here is straight forward. + # + # Search for the first data byte _after_ a user specified text + # line termination sequence was seen. The termination sequence's + # alphabet may be variable, and the sequence may span multiple + # data bytes. We accept either CR or LF, and combine the CR+LF + # sequence to strive for maximum length annotations for improved + # readability at different zoom levels. It's acceptable that this + # implementation would also combine multiple line terminations + # like LF+LF. + term_chars = (10, 13) + is_eol = b in term_chars + had_eol = self.accu_bytes[-1] in term_chars + if had_eol and not is_eol: + self.flush_bytes_text_accu() + + def handle_ifc_change(self, ifc): + # Track IFC line for parallel input. + # Assertion of IFC de-selects all talkers and listeners. + if ifc: + self.last_talker = None + self.last_listener = [] + self.flush_bytes_text_accu() + + def handle_eoi_change(self, eoi): + # Track EOI line for parallel and serial input. + if eoi: + self.ss_eoi = self.samplenum + self.curr_eoi = eoi + else: + self.es_eoi = self.samplenum + if self.ss_eoi and self.latch_eoi: + self.emit_eoi_ann(self.ss_eoi, self.es_eoi) + self.es_text = self.es_eoi + self.flush_bytes_text_accu() + self.ss_eoi = self.es_eoi = None + self.curr_eoi = None + + def handle_atn_change(self, atn): + # Track ATN line for parallel and serial input. + self.curr_atn = atn + if atn: + self.flush_bytes_text_accu() + + def handle_iec_periph(self, ss, es, addr, sec, data): + # The annotation is optional. + if self.options['iec_periph'] != 'yes': + return + # Void internal state. + if addr is None and sec is None and data is None: + self.last_iec_addr = None + self.last_iec_sec = None + return + # Grab and evaluate new input. + _iec_addr_names = { + # TODO Add more items here. See the "Device numbering" section + # of the https://en.wikipedia.org/wiki/Commodore_bus page. + 8: 'Disk 0', + 9: 'Disk 1', + } + _iec_disk_range = range(8, 16) + if addr is not None: + self.last_iec_addr = addr + name = _iec_addr_names.get(addr, None) + if name: + self.emit_data_ann(ss, es, ANN_IEC_PERIPH, [name]) + addr = self.last_iec_addr # Simplify subsequent logic. + if sec is not None: + # BEWARE! The secondary address is a full byte and includes + # the 0x60 offset, to also work when the MSB was set. + self.last_iec_sec = sec + subcmd, channel = sec & 0xf0, sec & 0x0f + channel_ord = ord('0') + channel + if addr is not None and addr in _iec_disk_range: + subcmd_fmts = { + 0x60: ['Reopen {ch:d}', 'Re {ch:d}', 'R{ch_ord:c}'], + 0xe0: ['Close {ch:d}', 'Cl {ch:d}', 'C{ch_ord:c}'], + 0xf0: ['Open {ch:d}', 'Op {ch:d}', 'O{ch_ord:c}'], + }.get(subcmd, None) + if subcmd_fmts: + texts = _format_ann_texts(subcmd_fmts, ch = channel, ch_ord = channel_ord) + self.emit_data_ann(ss, es, ANN_IEC_PERIPH, texts) + sec = self.last_iec_sec # Simplify subsequent logic. + if data is not None: + if addr is None or sec is None: + return + # TODO Process data depending on peripheral type and channel? + + def handle_data_byte(self): + if not self.curr_atn: + self.check_extra_flush(self.curr_raw) + b = self.curr_raw + texts = _get_raw_text(b, self.curr_atn) + self.emit_data_ann(self.ss_raw, self.es_raw, ANN_RAW_BYTE, texts) + self.emit_bin_ann(self.ss_raw, self.es_raw, BIN_RAW, b.to_bytes(1, byteorder='big')) + self.putpy(self.ss_raw, self.es_raw, 'GPIB_RAW', None, _get_raw_byte(b, self.curr_atn)) + if self.curr_atn: + ann_cls = None + upd_iec = False, + py_type = None + py_peers = False + is_cmd, is_unl, is_unt = _is_command(b) + laddr = _is_listen_addr(b) + taddr = _is_talk_addr(b) + saddr = _is_secondary_addr(b) + msb = _is_msb_set(b) + if is_cmd: + known, texts = _get_command_texts(b) + if not known: + warn_texts = ['Unknown GPIB command', 'unknown', 'UNK'] + self.emit_warn_ann(self.ss_raw, self.es_raw, warn_texts) + ann_cls = ANN_CMD + py_type, py_addr = 'COMMAND', None + if is_unl: + self.last_listener = [] + py_peers = True + if is_unt: + self.last_talker = None + py_peers = True + if is_unl or is_unt: + upd_iec = True, None, None, None + elif laddr is not None: + addr = laddr + texts = _get_address_texts(b) + ann_cls = ANN_LADDR + py_type, py_addr = 'LISTEN', addr + if addr == self.last_talker: + self.last_talker = None + self.last_listener.append(addr) + upd_iec = True, addr, None, None + py_peers = True + elif taddr is not None: + addr = taddr + texts = _get_address_texts(b) + ann_cls = ANN_TADDR + py_type, py_addr = 'TALK', addr + if addr in self.last_listener: + self.last_listener.remove(addr) + self.last_talker = addr + upd_iec = True, addr, None, None + py_peers = True + elif saddr is not None: + addr = saddr + texts = _get_address_texts(b) + ann_cls = ANN_SADDR + upd_iec = True, None, b, None + py_type, py_addr = 'SECONDARY', addr + elif msb is not None: + # These are not really "secondary addresses", but they + # are used by the Commodore IEC bus (floppy channels). + texts = _get_address_texts(b) + ann_cls = ANN_SADDR + upd_iec = True, None, b, None + py_type, py_addr = 'MSB_SET', b + if ann_cls is not None and texts is not None: + self.emit_data_ann(self.ss_raw, self.es_raw, ann_cls, texts) + if upd_iec[0]: + self.handle_iec_periph(self.ss_raw, self.es_raw, upd_iec[1], upd_iec[2], upd_iec[3]) + if py_type: + self.putpy(self.ss_raw, self.es_raw, py_type, py_addr, b) + if py_peers: + self.last_listener.sort() + self.putpy(self.ss_raw, self.es_raw, 'TALK_LISTEN', self.last_talker, self.last_listener) + else: + self.accu_bytes.append(b) + text = _get_data_text(b) + if not self.accu_text: + self.ss_text = self.ss_raw + self.accu_text.append(text) + self.es_text = self.es_raw + self.emit_data_ann(self.ss_raw, self.es_raw, ANN_DATA, [text]) + self.handle_iec_periph(self.ss_raw, self.es_raw, None, None, b) + self.putpy(self.ss_raw, self.es_raw, 'DATA_BYTE', self.last_talker, b) + + def handle_dav_change(self, dav, data): + if dav: + # Data availability starts when the flag goes active. + self.ss_raw = self.samplenum + self.curr_raw = bitpack(data) + self.latch_atn = self.curr_atn + self.latch_eoi = self.curr_eoi + return + # Data availability ends when the flag goes inactive. Handle the + # previously captured data byte according to associated flags. + self.es_raw = self.samplenum + self.handle_data_byte() + self.ss_raw = self.es_raw = None + self.curr_raw = None + + def inject_dav_phase(self, ss, es, data): + # Inspection of serial input has resulted in one raw byte which + # spans a given period of time. Pretend we had seen a DAV active + # phase, to re-use code for the parallel transmission. + self.ss_raw = ss + self.curr_raw = bitpack(data) + self.latch_atn = self.curr_atn + self.latch_eoi = self.curr_eoi + self.es_raw = es + self.handle_data_byte() + self.ss_raw = self.es_raw = None + self.curr_raw = None + + def invert_pins(self, pins): + # All lines (including data bits!) are low active and thus need + # to get inverted to receive their logical state (high active, + # regular data bit values). Cope with inputs being optional. + return [1 - p if p in (0, 1) else p for p in pins] + + def decode_serial(self, has_clk, has_data_1, has_atn, has_srq): + if not has_clk or not has_data_1 or not has_atn: + raise ChannelError('IEC bus needs at least ATN and serial CLK and DATA.') + + # This is a rephrased version of decoders/iec/pd.py:decode(). + # SRQ was not used there either. Magic numbers were eliminated. + ( + STEP_WAIT_READY_TO_SEND, + STEP_WAIT_READY_FOR_DATA, + STEP_PREP_DATA_TEST_EOI, + STEP_CLOCK_DATA_BITS, + ) = range(4) + step_wait_conds = ( + [{PIN_ATN: 'f'}, {PIN_DATA: 'l', PIN_CLK: 'h'}], + [{PIN_ATN: 'f'}, {PIN_DATA: 'h', PIN_CLK: 'h'}, {PIN_CLK: 'l'}], + [{PIN_ATN: 'f'}, {PIN_DATA: 'f'}, {PIN_CLK: 'l'}], + [{PIN_ATN: 'f'}, {PIN_CLK: 'e'}], + ) + step = STEP_WAIT_READY_TO_SEND + bits = [] + + while True: + + # Sample input pin values. Keep DATA/CLK in verbatim form to + # re-use 'iec' decoder logic. Turn ATN to positive logic for + # easier processing. The data bits get handled during byte + # accumulation. + pins = self.wait(step_wait_conds[step]) + data, clk = pins[PIN_DATA], pins[PIN_CLK] + atn, = self.invert_pins([pins[PIN_ATN]]) + + if self.matched[0]: + # Falling edge on ATN, reset step. + step = STEP_WAIT_READY_TO_SEND + + if step == STEP_WAIT_READY_TO_SEND: + # Don't use self.matched[1] here since we might come from + # a step with different conds due to the code above. + if data == 0 and clk == 1: + # Rising edge on CLK while DATA is low: Ready to send. + step = STEP_WAIT_READY_FOR_DATA + elif step == STEP_WAIT_READY_FOR_DATA: + if data == 1 and clk == 1: + # Rising edge on DATA while CLK is high: Ready for data. + ss_byte = self.samplenum + self.handle_atn_change(atn) + if self.curr_eoi: + self.handle_eoi_change(False) + bits = [] + step = STEP_PREP_DATA_TEST_EOI + elif clk == 0: + # CLK low again, transfer aborted. + step = STEP_WAIT_READY_TO_SEND + elif step == STEP_PREP_DATA_TEST_EOI: + if data == 0 and clk == 1: + # DATA goes low while CLK is still high, EOI confirmed. + self.handle_eoi_change(True) + elif clk == 0: + step = STEP_CLOCK_DATA_BITS + ss_bit = self.samplenum + elif step == STEP_CLOCK_DATA_BITS: + if self.matched[1]: + if clk == 1: + # Rising edge on CLK; latch DATA. + bits.append(data) + elif clk == 0: + # Falling edge on CLK; end of bit. + es_bit = self.samplenum + self.emit_data_ann(ss_bit, es_bit, ANN_RAW_BIT, ['{:d}'.format(bits[-1])]) + self.putpy(ss_bit, es_bit, 'IEC_BIT', None, bits[-1]) + ss_bit = self.samplenum + if len(bits) == 8: + es_byte = self.samplenum + self.inject_dav_phase(ss_byte, es_byte, bits) + if self.curr_eoi: + self.handle_eoi_change(False) + step = STEP_WAIT_READY_TO_SEND + + def decode_parallel(self, has_data_n, has_dav, has_atn, has_eoi, has_srq): + + if False in has_data_n or not has_dav or not has_atn: + raise ChannelError('IEEE-488 needs at least ATN and DAV and eight DIO lines.') + has_ifc = self.has_channel(PIN_IFC) + + # Capture data lines at the falling edge of DAV, process their + # values at rising DAV edge (when data validity ends). Also make + # sure to start inspection when the capture happens to start with + # low signal levels, i.e. won't include the initial falling edge. + # Scan for ATN/EOI edges as well (including the trick which works + # around initial pin state). + # Map low-active physical transport lines to positive logic here, + # to simplify logical inspection/decoding of communicated data, + # and to avoid redundancy and inconsistency in later code paths. + waitcond = [] + idx_dav = len(waitcond) + waitcond.append({PIN_DAV: 'l'}) + idx_atn = len(waitcond) + waitcond.append({PIN_ATN: 'l'}) + idx_eoi = None + if has_eoi: + idx_eoi = len(waitcond) + waitcond.append({PIN_EOI: 'l'}) + idx_ifc = None + if has_ifc: + idx_ifc = len(waitcond) + waitcond.append({PIN_IFC: 'l'}) + while True: + pins = self.wait(waitcond) + pins = self.invert_pins(pins) + + # BEWARE! Order of evaluation does matter. For low samplerate + # captures, many edges fall onto the same sample number. So + # we process active edges of flags early (before processing + # data bits), and inactive edges late (after data got processed). + if idx_ifc is not None and self.matched[idx_ifc] and pins[PIN_IFC] == 1: + self.handle_ifc_change(pins[PIN_IFC]) + if idx_eoi is not None and self.matched[idx_eoi] and pins[PIN_EOI] == 1: + self.handle_eoi_change(pins[PIN_EOI]) + if self.matched[idx_atn] and pins[PIN_ATN] == 1: + self.handle_atn_change(pins[PIN_ATN]) + if self.matched[idx_dav]: + self.handle_dav_change(pins[PIN_DAV], pins[PIN_DIO1:PIN_DIO8 + 1]) + if self.matched[idx_atn] and pins[PIN_ATN] == 0: + self.handle_atn_change(pins[PIN_ATN]) + if idx_eoi is not None and self.matched[idx_eoi] and pins[PIN_EOI] == 0: + self.handle_eoi_change(pins[PIN_EOI]) + if idx_ifc is not None and self.matched[idx_ifc] and pins[PIN_IFC] == 0: + self.handle_ifc_change(pins[PIN_IFC]) + + waitcond[idx_dav][PIN_DAV] = 'e' + waitcond[idx_atn][PIN_ATN] = 'e' + if has_eoi: + waitcond[idx_eoi][PIN_EOI] = 'e' + if has_ifc: + waitcond[idx_ifc][PIN_IFC] = 'e' + + def decode(self): + # The decoder's boilerplate declares some of the input signals as + # optional, but only to support both serial and parallel variants. + # The CLK signal discriminates the two. For either variant some + # of the "optional" signals are not really optional for proper + # operation of the decoder. Check these conditions here. + has_clk = self.has_channel(PIN_CLK) + has_data_1 = self.has_channel(PIN_DIO1) + has_data_n = [bool(self.has_channel(pin) for pin in range(PIN_DIO1, PIN_DIO8 + 1))] + has_dav = self.has_channel(PIN_DAV) + has_atn = self.has_channel(PIN_ATN) + has_eoi = self.has_channel(PIN_EOI) + has_srq = self.has_channel(PIN_SRQ) + if has_clk: + self.decode_serial(has_clk, has_data_1, has_atn, has_srq) + else: + self.decode_parallel(has_data_n, has_dav, has_atn, has_eoi, has_srq) diff --git a/libsigrokdecode4DSL/decoders/ir_irmp/__init__.py b/libsigrokdecode4DSL/decoders/ir_irmp/__init__.py new file mode 100644 index 00000000..b6bbff60 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/ir_irmp/__init__.py @@ -0,0 +1,25 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2019 Rene Staffen +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +IRMP is a multi protocol infrared remote protocol decoder. See +https://www.mikrocontroller.net/articles/IRMP for details. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/ir_irmp/irmp_library.py b/libsigrokdecode4DSL/decoders/ir_irmp/irmp_library.py new file mode 100644 index 00000000..5ec65222 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/ir_irmp/irmp_library.py @@ -0,0 +1,137 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2019 Rene Staffen +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +Python binding for the IRMP library. +''' + +import ctypes +import platform + +class IrmpLibrary: + ''' + Library instance for an infrared protocol detector. + ''' + + __usable_instance = None + + class ResultData(ctypes.Structure): + _fields_ = [ + ( 'protocol', ctypes.c_uint32, ), + ( 'protocol_name', ctypes.c_char_p, ), + ( 'address', ctypes.c_uint32, ), + ( 'command', ctypes.c_uint32, ), + ( 'flags', ctypes.c_uint32, ), + ( 'start_sample', ctypes.c_uint32, ), + ( 'end_sample', ctypes.c_uint32, ), + ] + + FLAG_REPETITION = 1 << 0 + FLAG_RELEASE = 1 << 1 + + def _library_filename(self): + ''' + Determine the library filename depending on the platform. + ''' + + if platform.uname()[0] == 'Linux': + return 'libirmp.so' + if platform.uname()[0] == 'Darwin': + return 'libirmp.dylib' + return 'irmp.dll' + + def _library_setup_api(self): + ''' + Lookup the C library's API routines. Declare their prototypes. + ''' + + if not self._lib: + return False + + self._lib.irmp_get_sample_rate.restype = ctypes.c_uint32 + self._lib.irmp_get_sample_rate.argtypes = [] + + self._lib.irmp_reset_state.restype = None + self._lib.irmp_reset_state.argtypes = [] + + self._lib.irmp_add_one_sample.restype = ctypes.c_int + self._lib.irmp_add_one_sample.argtypes = [ ctypes.c_int, ] + + if False: + self._lib.irmp_detect_buffer.restype = self.ResultData + self._lib.irmp_detect_buffer.argtypes = [ ctypes.POINTER(ctypes.c_uint8), ctypes.c_size_t, ] + + self._lib.irmp_get_result_data.restype = ctypes.c_int + self._lib.irmp_get_result_data.argtypes = [ ctypes.POINTER(self.ResultData), ] + + self._lib.irmp_get_protocol_name.restype = ctypes.c_char_p + self._lib.irmp_get_protocol_name.argtypes = [ ctypes.c_uint32, ] + + # Create a result buffer that's local to the library instance. + self._data = self.ResultData() + + return True + + def __init__(self): + ''' + Create a library instance. + ''' + + # Only create a working instance for the first invocation. + # Degrade all other instances, make them fail "late" during + # execution, so that users will see the errors. + self._lib = None + self._data = None + if IrmpLibrary.__usable_instance is None: + filename = self._library_filename() + self._lib = ctypes.cdll.LoadLibrary(filename) + self._library_setup_api() + IrmpLibrary.__usable_instance = self + + def get_sample_rate(self): + if not self._lib: + return None + return self._lib.irmp_get_sample_rate() + + def reset_state(self): + if not self._lib: + return None + self._lib.irmp_reset_state() + + def add_one_sample(self, level): + if not self._lib: + raise Exception("IRMP library limited to a single instance.") + if not self._lib.irmp_add_one_sample(int(level)): + return False + self._lib.irmp_get_result_data(ctypes.byref(self._data)) + return True + + def get_result_data(self): + if not self._data: + return None + return { + 'proto_nr': self._data.protocol, + 'proto_name': self._data.protocol_name.decode('UTF-8', 'ignore'), + 'address': self._data.address, + 'command': self._data.command, + 'repeat': bool(self._data.flags & self.FLAG_REPETITION), + 'release': bool(self._data.flags & self.FLAG_RELEASE), + 'start': self._data.start_sample, + 'end': self._data.end_sample, + } diff --git a/libsigrokdecode4DSL/decoders/ir_irmp/pd.py b/libsigrokdecode4DSL/decoders/ir_irmp/pd.py new file mode 100644 index 00000000..979c1e01 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/ir_irmp/pd.py @@ -0,0 +1,137 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 Gump Yang +## Copyright (C) 2019 Rene Staffen +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +from . import irmp_library +import sigrokdecode as srd + +class SamplerateError(Exception): + pass + +class LibraryError(Exception): + pass + +class Decoder(srd.Decoder): + api_version = 3 + id = 'ir_irmp' + name = 'IR IRMP' + longname = 'IR IRMP' + desc = 'IRMP infrared remote control multi protocol.' + license = 'gplv2+' + inputs = ['logic'] + outputs = [] + tags = ['IR'] + channels = ( + {'id': 'ir', 'name': 'IR', 'desc': 'Data line'}, + ) + options = ( + {'id': 'polarity', 'desc': 'Polarity', 'default': 'active-low', + 'values': ('active-low', 'active-high')}, + ) + annotations = ( + ('packet', 'Packet'), + ) + annotation_rows = ( + ('packets', 'IR Packets', (0,)), + ) + + def putframe(self, data): + '''Emit annotation for an IR frame.''' + + # Cache result data fields in local variables. Get the ss/es + # timestamps, scaled to sample numbers. + nr = data['proto_nr'] + name = data['proto_name'] + addr = data['address'] + cmd = data['command'] + repeat = data['repeat'] + release = data['release'] + ss = data['start'] * self.rate_factor + es = data['end'] * self.rate_factor + + # Prepare display texts for several zoom levels. + # Implementor's note: Keep list lengths for flags aligned during + # maintenance. Make sure there are as many flags text variants + # as are referenced by annotation text variants. Differing list + # lengths or dynamic refs will severely complicate the logic. + rep_txts = ['repeat', 'rep', 'r'] + rel_txts = ['release', 'rel', 'R'] + flag_txts = [None,] * len(rep_txts) + for zoom in range(len(flag_txts)): + flag_txts[zoom] = [] + if repeat: + flag_txts[zoom].append(rep_txts[zoom]) + if release: + flag_txts[zoom].append(rel_txts[zoom]) + flag_txts = [' '.join(t) or '-' for t in flag_txts] + flg = flag_txts # Short name for .format() references. + txts = [ + 'Protocol: {name} ({nr}), Address 0x{addr:04x}, Command: 0x{cmd:04x}, Flags: {flg[0]}'.format(**locals()), + 'P: {name} ({nr}), Addr: 0x{addr:x}, Cmd: 0x{cmd:x}, Flg: {flg[1]}'.format(**locals()), + 'P: {nr} A: 0x{addr:x} C: 0x{cmd:x} F: {flg[1]}'.format(**locals()), + 'C:{cmd:x} A:{addr:x} {flg[2]}'.format(**locals()), + 'C:{cmd:x}'.format(**locals()), + ] + + # Emit the annotation from details which were constructed above. + self.put(ss, es, self.out_ann, [0, txts]) + + def __init__(self): + self.irmp = None + self.reset() + + def reset(self): + self.want_reset = True + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def metadata(self, key, value): + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + + def decode(self): + if not self.irmp: + try: + self.irmp = irmp_library.IrmpLibrary() + except Exception as e: + txt = e.args[0] + raise LibraryError(txt) + if self.irmp: + self.lib_rate = self.irmp.get_sample_rate() + if not self.irmp or not self.lib_rate: + raise LibraryError('Cannot access IRMP library. One instance limit exceeded?') + if not self.samplerate: + raise SamplerateError('Cannot decode without samplerate.') + if self.samplerate % self.lib_rate: + raise SamplerateError('Capture samplerate must be multiple of library samplerate ({})'.format(self.lib_rate)) + self.rate_factor = int(self.samplerate / self.lib_rate) + if self.want_reset: + self.irmp.reset_state() + self.want_reset = False + + self.active = 0 if self.options['polarity'] == 'active-low' else 1 + ir, = self.wait() + while True: + if self.active == 1: + ir = 1 - ir + if self.irmp.add_one_sample(ir): + data = self.irmp.get_result_data() + self.putframe(data) + ir, = self.wait([{'skip': self.rate_factor}]) diff --git a/libsigrokdecode4DSL/decoders/ir_nec/__init__.py b/libsigrokdecode4DSL/decoders/ir_nec/__init__.py new file mode 100644 index 00000000..c361c3dc --- /dev/null +++ b/libsigrokdecode4DSL/decoders/ir_nec/__init__.py @@ -0,0 +1,24 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 Gump Yang +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +NEC is a pulse-distance based infrared remote control protocol. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/ir_nec/lists.py b/libsigrokdecode4DSL/decoders/ir_nec/lists.py new file mode 100644 index 00000000..7d47a46d --- /dev/null +++ b/libsigrokdecode4DSL/decoders/ir_nec/lists.py @@ -0,0 +1,50 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +# Addresses/devices. Items that are not listed are reserved/unknown. +address = { + 0x40: 'Matsui TV', +} + +digits = { + 0: ['0', '0'], + 1: ['1', '1'], + 2: ['2', '2'], + 3: ['3', '3'], + 4: ['4', '4'], + 5: ['5', '5'], + 6: ['6', '6'], + 7: ['7', '7'], + 8: ['8', '8'], + 9: ['9', '9'], +} + +# Commands. Items that are not listed are reserved/unknown. +command = { + 0x40: dict(list(digits.items()) + list({ + 11: ['-/--', '-/--'], + 16: ['Mute', 'M'], + 18: ['Standby', 'StBy'], + 26: ['Volume up', 'Vol+'], + 27: ['Program up', 'P+'], + 30: ['Volume down', 'Vol-'], + 31: ['Program down', 'P-'], + 68: ['AV', 'AV'], + }.items())), +} diff --git a/libsigrokdecode4DSL/decoders/ir_nec/pd.py b/libsigrokdecode4DSL/decoders/ir_nec/pd.py new file mode 100644 index 00000000..830892e8 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/ir_nec/pd.py @@ -0,0 +1,237 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 Gump Yang +## Copyright (C) 2019 DreamSourceLab +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd +from .lists import * + +class SamplerateError(Exception): + pass + +class Decoder(srd.Decoder): + api_version = 3 + id = 'ir_nec' + name = 'IR NEC' + longname = 'IR NEC' + desc = 'NEC infrared remote control protocol.' + license = 'gplv2+' + inputs = ['logic'] + outputs = [] + tags = ['IR'] + channels = ( + {'id': 'ir', 'name': 'IR', 'desc': 'Data line'}, + ) + options = ( + {'id': 'polarity', 'desc': 'Polarity', 'default': 'active-low', + 'values': ('active-low', 'active-high')}, + {'id': 'cd_freq', 'desc': 'Carrier Frequency', 'default': 0}, + ) + annotations = ( + ('bit', 'Bit'), + ('agc-pulse', 'AGC pulse'), + ('longpause', 'Long pause'), + ('shortpause', 'Short pause'), + ('stop-bit', 'Stop bit'), + ('leader-code', 'Leader code'), + ('addr', 'Address'), + ('addr-inv', 'Address#'), + ('cmd', 'Command'), + ('cmd-inv', 'Command#'), + ('repeat-code', 'Repeat code'), + ('remote', 'Remote'), + ('warnings', 'Warnings'), + ) + annotation_rows = ( + ('bits', 'Bits', (0, 1, 2, 3, 4)), + ('fields', 'Fields', (5, 6, 7, 8, 9, 10)), + ('remote', 'Remote', (11,)), + ('warnings', 'Warnings', (12,)), + ) + + def putx(self, data): + self.put(self.ss_start, self.samplenum, self.out_ann, data) + + def putb(self, data): + self.put(self.ss_bit, self.samplenum, self.out_ann, data) + + def putd(self, data): + name = self.state.title() + d = {'ADDRESS': 6, 'ADDRESS#': 7, 'COMMAND': 8, 'COMMAND#': 9} + s = {'ADDRESS': ['ADDR', 'A'], 'ADDRESS#': ['ADDR#', 'A#'], + 'COMMAND': ['CMD', 'C'], 'COMMAND#': ['CMD#', 'C#']} + self.putx([d[self.state], ['%s: 0x%02X' % (name, data), + '%s: 0x%02X' % (s[self.state][0], data), + '%s: 0x%02X' % (s[self.state][1], data), s[self.state][1]]]) + + def putstop(self, ss): + self.put(ss, ss + self.stop, self.out_ann, + [4, ['Stop bit', 'Stop', 'St', 'S']]) + + def putpause(self, p): + self.put(self.ss_start, self.ss_other_edge, self.out_ann, + [1, ['AGC pulse', 'AGC', 'A']]) + idx = 2 if p == 'Long' else 3 + self.put(self.ss_other_edge, self.samplenum, self.out_ann, + [idx, [p + ' pause', '%s-pause' % p[0], '%sP' % p[0], 'P']]) + + def putremote(self): + dev = address.get(self.addr, 'Unknown device') + buttons = command.get(self.addr, None) + if buttons is None: + btn = ['Unknown', 'Unk'] + else: + btn = buttons.get(self.cmd, ['Unknown', 'Unk']) + self.put(self.ss_remote, self.ss_bit + self.stop, self.out_ann, + [11, ['%s: %s' % (dev, btn[0]), '%s: %s' % (dev, btn[1]), + '%s' % btn[1]]]) + + def __init__(self): + self.reset() + + def reset(self): + self.state = 'IDLE' + self.ss_bit = self.ss_start = self.ss_other_edge = self.ss_remote = 0 + self.data = self.count = self.active = None + self.addr = self.cmd = None + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + self.active = 0 if self.options['polarity'] == 'active-low' else 1 + + def metadata(self, key, value): + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + self.tolerance = 0.05 # +/-5% + self.lc = int(self.samplerate * 0.0135) - 1 # 13.5ms + self.rc = int(self.samplerate * 0.01125) - 1 # 11.25ms + self.dazero = int(self.samplerate * 0.001125) - 1 # 1.125ms + self.daone = int(self.samplerate * 0.00225) - 1 # 2.25ms + self.stop = int(self.samplerate * 0.000652) - 1 # 0.652ms + + def compare_with_tolerance(self, measured, base): + return (measured >= base * (1 - self.tolerance) + and measured <= base * (1 + self.tolerance)) + + def handle_bit(self, tick): + ret = None + if self.compare_with_tolerance(tick, self.dazero): + ret = 0 + elif self.compare_with_tolerance(tick, self.daone): + ret = 1 + if ret in (0, 1): + self.putb([0, ['%d' % ret]]) + self.data |= (ret << self.count) # LSB-first + self.count = self.count + 1 + self.ss_bit = self.samplenum + + def data_ok(self): + ret, name = (self.data >> 8) & (self.data & 0xff), self.state.title() + if self.count == 8: + if self.state == 'ADDRESS': + self.addr = self.data + if self.state == 'COMMAND': + self.cmd = self.data + self.putd(self.data) + self.ss_start = self.samplenum + return True + if ret == 0: + self.putd(self.data >> 8) + else: + self.putx([12, ['%s error: 0x%04X' % (name, self.data)]]) + self.data = self.count = 0 + self.ss_bit = self.ss_start = self.samplenum + return ret == 0 + + def decode(self): + if not self.samplerate: + raise SamplerateError('Cannot decode without samplerate.') + + cd_count = None + if self.options['cd_freq']: + cd_count = int(self.samplerate / self.options['cd_freq']) + 1 + prev_ir = None + + while True: + # Detect changes in the presence of an active input signal. + # The decoder can either be fed an already filtered RX signal + # or optionally can detect the presence of a carrier. Periods + # of inactivity (signal changes slower than the carrier freq, + # if specified) pass on the most recently sampled level. This + # approach works for filtered and unfiltered input alike, and + # only slightly extends the active phase of input signals with + # carriers included by one period of the carrier frequency. + # IR based communication protocols can cope with this slight + # inaccuracy just fine by design. Enabling carrier detection + # on already filtered signals will keep the length of their + # active period, but will shift their signal changes by one + # carrier period before they get passed to decoding logic. + if cd_count: + (cur_ir,) = self.wait([{0: 'e'}, {'skip': cd_count}]) + if (self.matched & (0b1 << 0)): + cur_ir = self.active + if cur_ir == prev_ir: + continue + prev_ir = cur_ir + self.ir = cur_ir + else: + (self.ir,) = self.wait({0: 'e'}) + + if self.ir != self.active: + # Save the non-active edge, then wait for the next edge. + self.ss_other_edge = self.samplenum + continue + + b = self.samplenum - self.ss_bit + + # State machine. + if self.state == 'IDLE': + if self.compare_with_tolerance(b, self.lc): + self.putpause('Long') + self.putx([5, ['Leader code', 'Leader', 'LC', 'L']]) + self.ss_remote = self.ss_start + self.data = self.count = 0 + self.state = 'ADDRESS' + elif self.compare_with_tolerance(b, self.rc): + self.putpause('Short') + self.putstop(self.samplenum) + self.samplenum += self.stop + self.putx([10, ['Repeat code', 'Repeat', 'RC', 'R']]) + self.data = self.count = 0 + self.ss_bit = self.ss_start = self.samplenum + elif self.state == 'ADDRESS': + self.handle_bit(b) + if self.count == 8: + self.state = 'ADDRESS#' if self.data_ok() else 'IDLE' + elif self.state == 'ADDRESS#': + self.handle_bit(b) + if self.count == 16: + self.state = 'COMMAND' if self.data_ok() else 'IDLE' + elif self.state == 'COMMAND': + self.handle_bit(b) + if self.count == 8: + self.state = 'COMMAND#' if self.data_ok() else 'IDLE' + elif self.state == 'COMMAND#': + self.handle_bit(b) + if self.count == 16: + self.state = 'STOP' if self.data_ok() else 'IDLE' + elif self.state == 'STOP': + self.putstop(self.ss_bit) + self.putremote() + self.ss_bit = self.ss_start = self.samplenum + self.state = 'IDLE' diff --git a/libsigrokdecode4DSL/decoders/ir_rc5/__init__.py b/libsigrokdecode4DSL/decoders/ir_rc5/__init__.py new file mode 100644 index 00000000..7a209b19 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/ir_rc5/__init__.py @@ -0,0 +1,24 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +RC-5 is a biphase/manchester based infrared remote control protocol. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/ir_rc5/lists.py b/libsigrokdecode4DSL/decoders/ir_rc5/lists.py new file mode 100644 index 00000000..4a8c958d --- /dev/null +++ b/libsigrokdecode4DSL/decoders/ir_rc5/lists.py @@ -0,0 +1,93 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +# Systems/addresses (0..31). Items that are not listed are reserved/unknown. +system = { + 0: ['TV receiver 1', 'TV1'], + 1: ['TV receiver 2', 'TV2'], + 2: ['Teletext', 'Txt'], + 3: ['Extension to TV1 and TV2', 'Ext TV1/TV2'], + 4: ['LaserVision player', 'LV'], + 5: ['Video cassette recorder 1', 'VCR1'], + 6: ['Video cassette recorder 2', 'VCR2'], + 7: ['Experimental', 'Exp'], + 8: ['Satellite TV receiver 1', 'Sat1'], + 9: ['Extension to VCR1 and VCR2', 'Ext VCR1/VCR2'], + 10: ['Satellite TV receiver 2', 'Sat2'], + 12: ['Compact disc video player', 'CD-Video'], + 13: ['Camcorder', 'Cam'], + 14: ['Photo on compact disc player', 'CD-Photo'], + 16: ['Audio preamplifier 1', 'Preamp1'], + 17: ['Radio tuner', 'Tuner'], + 18: ['Analog cassette recoder 1', 'Rec1'], + 19: ['Audio preamplifier 2', 'Preamp2'], + 20: ['Compact disc player', 'CD'], + 21: ['Audio stack or record player', 'Combi'], + 22: ['Audio satellite', 'Sat'], + 23: ['Analog cassette recoder 2', 'Rec2'], + 26: ['Compact disc recorder', 'CD-R'], + 29: ['Lighting 1', 'Light1'], + 30: ['Lighting 2', 'Light2'], + 31: ['Telephone', 'Phone'], +} + +digits = { + 0: ['0', '0'], + 1: ['1', '1'], + 2: ['2', '2'], + 3: ['3', '3'], + 4: ['4', '4'], + 5: ['5', '5'], + 6: ['6', '6'], + 7: ['7', '7'], + 8: ['8', '8'], + 9: ['9', '9'], +} + +# Commands (0..63 for RC-5, and 0..127 for Extended RC-5). +# Items that are not listed are reserved/unknown. +command = { + 'TV': dict(list(digits.items()) + list({ + 10: ['-/--', '-/--'], + 11: ['Channel/program', 'Ch/P'], + 12: ['Standby', 'StBy'], + 13: ['Mute', 'M'], + 14: ['Personal preferences', 'PP'], + 15: ['Display', 'Disp'], + 16: ['Volume up', 'Vol+'], + 17: ['Volume down', 'Vol-'], + 18: ['Brightness up', 'Br+'], + 19: ['Brightness down', 'Br-'], + 20: ['Saturation up', 'S+'], + 21: ['Saturation down', 'S-'], + 32: ['Program up', 'P+'], + 33: ['Program down', 'P-'], + }.items())), + 'VCR': dict(list(digits.items()) + list({ + 10: ['-/--', '-/--'], + 12: ['Standby', 'StBy'], + 32: ['Program up', 'P+'], + 33: ['Program down', 'P-'], + 50: ['Fast rewind', 'FRW'], + 52: ['Fast forward', 'FFW'], + 53: ['Play', 'Pl'], + 54: ['Stop', 'St'], + 55: ['Recording', 'Rec'], + }.items())), +} diff --git a/libsigrokdecode4DSL/decoders/ir_rc5/pd.py b/libsigrokdecode4DSL/decoders/ir_rc5/pd.py new file mode 100644 index 00000000..e18a90bf --- /dev/null +++ b/libsigrokdecode4DSL/decoders/ir_rc5/pd.py @@ -0,0 +1,187 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd +from .lists import * + +class SamplerateError(Exception): + pass + +class Decoder(srd.Decoder): + api_version = 3 + id = 'ir_rc5' + name = 'IR RC-5' + longname = 'IR RC-5' + desc = 'RC-5 infrared remote control protocol.' + license = 'gplv2+' + inputs = ['logic'] + outputs = [] + tags = ['IR'] + channels = ( + {'id': 'ir', 'name': 'IR', 'desc': 'IR data line'}, + ) + options = ( + {'id': 'polarity', 'desc': 'Polarity', 'default': 'active-low', + 'values': ('active-low', 'active-high')}, + {'id': 'protocol', 'desc': 'Protocol type', 'default': 'standard', + 'values': ('standard', 'extended')}, + ) + annotations = ( + ('bit', 'Bit'), + ('startbit1', 'Startbit 1'), + ('startbit2', 'Startbit 2'), + ('togglebit-0', 'Toggle bit 0'), + ('togglebit-1', 'Toggle bit 1'), + ('address', 'Address'), + ('command', 'Command'), + ) + annotation_rows = ( + ('bits', 'Bits', (0,)), + ('fields', 'Fields', (1, 2, 3, 4, 5, 6)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.samplerate = None + self.samplenum = None + self.edges, self.bits, self.ss_es_bits = [], [], [] + self.state = 'IDLE' + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + self.old_ir = 1 if self.options['polarity'] == 'active-low' else 0 + + def metadata(self, key, value): + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + # One bit: 1.78ms (one half low, one half high). + self.halfbit = int((self.samplerate * 0.00178) / 2.0) + + def putb(self, bit1, bit2, data): + ss, es = self.ss_es_bits[bit1][0], self.ss_es_bits[bit2][1] + self.put(ss, es, self.out_ann, data) + + def handle_bits(self): + a, c, b = 0, 0, self.bits + # Individual raw bits. + for i in range(14): + if i == 0: + ss = max(0, self.bits[0][0] - self.halfbit) + else: + ss = self.ss_es_bits[i - 1][1] + es = self.bits[i][0] + self.halfbit + self.ss_es_bits.append([ss, es]) + self.putb(i, i, [0, ['%d' % self.bits[i][1]]]) + # Bits[0:0]: Startbit 1 + s = ['Startbit1: %d' % b[0][1], 'SB1: %d' % b[0][1], 'SB1', 'S1', 'S'] + self.putb(0, 0, [1, s]) + # Bits[1:1]: Startbit 2 + ann_idx = 2 + s = ['Startbit2: %d' % b[1][1], 'SB2: %d' % b[1][1], 'SB2', 'S2', 'S'] + if self.options['protocol'] == 'extended': + s = ['CMD[6]#: %d' % b[1][1], 'C6#: %d' % b[1][1], 'C6#', 'C#', 'C'] + ann_idx = 6 + self.putb(1, 1, [ann_idx, s]) + # Bits[2:2]: Toggle bit + s = ['Togglebit: %d' % b[2][1], 'Toggle: %d' % b[2][1], + 'TB: %d' % b[2][1], 'TB', 'T'] + self.putb(2, 2, [3 if b[2][1] == 0 else 4, s]) + # Bits[3:7]: Address (MSB-first) + for i in range(5): + a |= (b[3 + i][1] << (4 - i)) + x = system.get(a, ['Unknown', 'Unk']) + s = ['Address: %d (%s)' % (a, x[0]), 'Addr: %d (%s)' % (a, x[1]), + 'Addr: %d' % a, 'A: %d' % a, 'A'] + self.putb(3, 7, [5, s]) + # Bits[8:13]: Command (MSB-first) + for i in range(6): + c |= (b[8 + i][1] << (5 - i)) + if self.options['protocol'] == 'extended': + inverted_bit6 = 1 if b[1][1] == 0 else 0 + c |= (inverted_bit6 << 6) + cmd_type = 'VCR' if x[1] in ('VCR1', 'VCR2') else 'TV' + x = command[cmd_type].get(c, ['Unknown', 'Unk']) + s = ['Command: %d (%s)' % (c, x[0]), 'Cmd: %d (%s)' % (c, x[1]), + 'Cmd: %d' % c, 'C: %d' % c, 'C'] + self.putb(8, 13, [6, s]) + + def edge_type(self): + # Categorize according to distance from last edge (short/long). + distance = self.samplenum - self.edges[-1] + s, l, margin = self.halfbit, self.halfbit * 2, int(self.halfbit / 2) + if distance in range(l - margin, l + margin + 1): + return 'l' + elif distance in range(s - margin, s + margin + 1): + return 's' + else: + return 'e' # Error, invalid edge distance. + + def reset_decoder_state(self): + self.edges, self.bits, self.ss_es_bits = [], [], [] + self.state = 'IDLE' + + def decode(self): + if not self.samplerate: + raise SamplerateError('Cannot decode without samplerate.') + while True: + + (self.ir,) = self.wait() + + # Wait for any edge (rising or falling). + if self.old_ir == self.ir: + continue + + # State machine. + if self.state == 'IDLE': + bit = 1 + self.edges.append(self.samplenum) + self.bits.append([self.samplenum, bit]) + self.state = 'MID1' + self.old_ir = self.ir + continue + edge = self.edge_type() + if edge == 'e': + self.reset_decoder_state() # Reset state machine upon errors. + continue + if self.state == 'MID1': + self.state = 'START1' if edge == 's' else 'MID0' + bit = None if edge == 's' else 0 + elif self.state == 'MID0': + self.state = 'START0' if edge == 's' else 'MID1' + bit = None if edge == 's' else 1 + elif self.state == 'START1': + if edge == 's': + self.state = 'MID1' + bit = 1 if edge == 's' else None + elif self.state == 'START0': + if edge == 's': + self.state = 'MID0' + bit = 0 if edge == 's' else None + + self.edges.append(self.samplenum) + if bit is not None: + self.bits.append([self.samplenum, bit]) + + if len(self.bits) == 14: + self.handle_bits() + self.reset_decoder_state() + + self.old_ir = self.ir diff --git a/libsigrokdecode4DSL/decoders/ir_rc6/__init__.py b/libsigrokdecode4DSL/decoders/ir_rc6/__init__.py new file mode 100644 index 00000000..b2cb9f9a --- /dev/null +++ b/libsigrokdecode4DSL/decoders/ir_rc6/__init__.py @@ -0,0 +1,24 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2019 Benedikt Otto +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +RC-6 is a biphase/manchester based infrared remote control protocol. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/ir_rc6/pd.py b/libsigrokdecode4DSL/decoders/ir_rc6/pd.py new file mode 100644 index 00000000..e195dbd1 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/ir_rc6/pd.py @@ -0,0 +1,205 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2019 Benedikt Otto +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd + +class SamplerateError(Exception): + pass + +class Decoder(srd.Decoder): + api_version = 3 + id = 'ir_rc6' + name = 'IR RC-6' + longname = 'IR RC-6' + desc = 'RC-6 infrared remote control protocol.' + license = 'gplv2+' + inputs = ['logic'] + outputs = [] + tags = ['IR'] + channels = ( + {'id': 'ir', 'name': 'IR', 'desc': 'IR data line'}, + ) + options = ( + {'id': 'polarity', 'desc': 'Polarity', 'default': 'auto', + 'values': ('auto', 'active-low', 'active-high')}, + ) + annotations = ( + ('bit', 'Bit'), + ('sync', 'Sync'), + ('startbit', 'Startbit'), + ('field', 'Field'), + ('togglebit', 'Togglebit'), + ('address', 'Address'), + ('command', 'Command'), + ) + annotation_rows = ( + ('bits', 'Bits', (0,)), + ('fields', 'Fields', (1, 2, 3, 4, 5, 6)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.samplerate = None + self.edges, self.deltas, self.bits = [], [], [] + self.state = 'IDLE' + self.mode = 0 + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def metadata(self, key, value): + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + # One bit: 0.889ms (one half low, one half high). + self.halfbit = int((self.samplerate * 0.000889) / 2.0) + + def putb(self, bit, data): + self.put(bit[0], bit[1], self.out_ann, data) + + def putbits(self, bit1, bit2, data): + self.put(bit1[0], bit2[1], self.out_ann, data) + + def putx(self, ss, es, data): + self.put(ss, es, self.out_ann, data) + + def handle_bit(self): + if len(self.bits) != 6: + return + if self.bits[0][2] == 8 and self.bits[0][3] == 1: + self.putb(self.bits[0], [1, ['Synchronisation', 'Sync']]) + else: + return + if self.bits[1][3] == 1: + self.putb(self.bits[1], [2, ['Startbit', 'Start']]) + else: + return + self.mode = sum([self.bits[2 + i][3] << (2 - i) for i in range(3)]) + self.putbits(self.bits[2], self.bits[4], [3, ['Field: %d' % self.mode]]) + self.putb(self.bits[5], [4, ['Toggle: %d' % self.bits[5][3]]]) + + def handle_package(self): + # Sync and start bits have to be 1. + if self.bits[0][3] == 0 or self.bits[1][3] == 0: + return + if len(self.bits) <= 6: + return + + if self.mode == 0 and len(self.bits) == 22: # Mode 0 standard + value = sum([self.bits[6 + i][3] << (7 - i) for i in range(8)]) + self.putbits(self.bits[6], self.bits[13], [5, ['Address: %0.2X' % value]]) + + value = sum([self.bits[14 + i][3] << (7 - i) for i in range(8)]) + self.putbits(self.bits[14], self.bits[21], [6, ['Data: %0.2X' % value]]) + + self.bits = [] + + if self.mode == 6 and len(self.bits) >= 15: # Mode 6 + if self.bits[6][3] == 0: # Short addr, Mode 6A + value = sum([self.bits[6 + i][3] << (7 - i) for i in range(8)]) + self.putbits(self.bits[6], self.bits[13], [5, ['Address: %0.2X' % value]]) + + num_data_bits = len(self.bits) - 14 + value = sum([self.bits[14 + i][3] << (num_data_bits - 1 - i) for i in range(num_data_bits)]) + self.putbits(self.bits[14], self.bits[-1], [6, ['Data: %X' % value]]) + + self.bits = [] + + elif len(self.bits) >= 23: # Long addr, Mode 6B + value = sum([self.bits[6 + i][3] << (15 - i) for i in range(16)]) + self.putbits(self.bits[6], self.bits[21], [5, ['Address: %0.2X' % value]]) + + num_data_bits = len(self.bits) - 22 + value = sum([self.bits[22 + i][3] << (num_data_bits - 1 - i) for i in range(num_data_bits)]) + self.putbits(self.bits[22], self.bits[-1], [6, ['Data: %X' % value]]) + + self.bits = [] + + def decode(self): + if not self.samplerate: + raise SamplerateError('Cannot decode without samplerate.') + value = 0 + num_edges = -1 + self.invert = False + + while True: + conditions = [{0: 'e'}] + if self.state == 'DATA': + conditions.append({'skip': self.halfbit * 6}) + (self.ir,) = self.wait(conditions) + + if len(conditions) == 2: + if self.matched[1]: + self.state = 'IDLE' + + self.edges.append(self.samplenum) + if len(self.edges) < 2: + continue + + delta = (self.edges[-1] - self.edges[-2]) / self.halfbit + delta = int(delta + 0.5) + self.deltas.append(delta) + + if len(self.deltas) < 2: + continue + + if self.deltas[-2:] == [6, 2]: + self.state = 'SYNC' + num_edges = 0 + self.bits = [] + + if self.options['polarity'] == 'auto': + value = 1 + else: + value = self.ir if self.options['polarity'] == 'active-high' else 1 - self.ir + + self.bits.append((self.edges[-3], self.edges[-1], 8, value)) + self.invert = self.ir == 0 + self.putb(self.bits[-1], [0, ['%d' % value]]) # Add bit. + + if (num_edges % 2) == 0: # Only count every second edge. + if self.deltas[-2] in [1, 2, 3] and self.deltas[-1] in [1, 2, 3, 6]: + self.state = 'DATA' + if self.deltas[-2] != self.deltas[-1]: + # Insert border between 2 bits. + self.edges.insert(-1, self.edges[-2] + self.deltas[-2] * self.halfbit) + total = self.deltas[-1] + self.deltas[-1] = self.deltas[-2] + self.deltas.append(total - self.deltas[-1]) + + self.bits.append((self.edges[-4], self.edges[-2], self.deltas[-2] * 2, value)) + + num_edges += 1 + else: + self.bits.append((self.edges[-3], self.edges[-1], self.deltas[-1] * 2, value)) + + self.putb(self.bits[-1], [0, ['%d' % value]]) # Add bit. + + if len(self.bits) > 0: + self.handle_bit() + if self.state == 'IDLE': + self.handle_package() + + if self.options['polarity'] == 'auto': + value = self.ir if self.invert else 1 - self.ir + else: + value = self.ir if self.options['polarity'] == 'active-low' else 1 - self.ir + + num_edges += 1 diff --git a/libsigrokdecode4DSL/decoders/ir_sirc/__init__.py b/libsigrokdecode4DSL/decoders/ir_sirc/__init__.py new file mode 100644 index 00000000..4061ed73 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/ir_sirc/__init__.py @@ -0,0 +1,26 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2020 Tom Flanagan +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +Decoder for the Sony IR remote control protocol (SIRC). + +https://www.sbprojects.net/knowledge/ir/sirc.php +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/ir_sirc/lists.py b/libsigrokdecode4DSL/decoders/ir_sirc/lists.py new file mode 100644 index 00000000..5c6d8fa6 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/ir_sirc/lists.py @@ -0,0 +1,201 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2020 Tom Flanagan +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +NUMBERS = { + 0x00: '1', + 0x01: '2', + 0x02: '3', + 0x03: '4', + 0x04: '5', + 0x05: '6', + 0x06: '7', + 0x07: '8', + 0x08: '9', + 0x09: '0/10', +} + +ADDRESSES = { + # TV + (0x01, None): (['TV: ', 'TV:'], { + 0x15: 'Power', + 0x25: 'Input', + + 0x33: 'Right', + 0x34: 'Left', + 0x3A: 'Display', + + 0x60: 'Home', + 0x65: 'Enter', + + 0x74: 'Up', + 0x75: 'Down', + + }), + + # Video + (0x0B, None): (['Video: ', 'V:'], { + 0x18: 'Stop', + 0x19: 'Pause', + 0x1A: 'Play', + 0x1B: 'Rewind', + 0x1C: 'Fast Forward', + + 0x42: 'Up', + 0x43: 'Down', + 0x4D: 'Home', + + 0x51: 'Enter', + 0x5A: 'Display', + + 0x61: 'Right', + 0x62: 'Left', + }), + + # BR Input select + (0x10, 0x28): (['BlueRay: ', 'BR:'], { + 0x16: 'BlueRay', + }), + + # Amp, Game, Sat, Tuner, USB + (0x10, 0x08): (['Playback: ', 'PB:'], { + 0x2A: 'Shuffle', + 0x2C: 'Repeat', + 0x2E: 'Folder Down', + 0x2F: 'Folder Up', + + 0x30: 'Previous', + 0x31: 'Next', + 0x32: 'Play', + 0x33: 'Rewind', + 0x34: 'Fast Forward', + 0x38: 'Stop', + 0x39: 'Pause', + + 0x73: 'Options', + 0x7D: 'Return', + }), + + # CD + (0x11, None): (['CD: ', 'CD:'], { + 0x28: 'Display', + + 0x30: 'Previous', + 0x31: 'Next', + 0x32: 'Play', + 0x33: 'Rewind', + 0x34: 'Fast Forward', + 0x38: 'Stop', + 0x39: 'Pause', + }), + + # BD + (0x1A, 0xE2): (['BlueRay: ', 'BD:'], { + 0x18: 'Stop', + 0x19: 'Pause', + 0x1A: 'Play', + 0x1B: 'Rewind', + 0x1C: 'Fast Forward', + + 0x29: 'Menu', + 0x2C: 'Top Menu', + + 0x39: 'Up', + 0x3A: 'Down', + 0x3B: 'Left', + 0x3C: 'Right', + 0x3D: 'Enter', + 0x3F: 'Options', + + 0x41: 'Display', + 0x42: 'Home', + 0x43: 'Return', + + 0x56: 'Next', + 0x57: 'Previous', + }), + + # DVD + (0x1A, 0x49): (['DVD: ', 'DVD:'], { + 0x0B: 'Enter', + 0x0E: 'Return', + 0x17: 'Options', + + 0x1A: 'Top Menu', + 0x1B: 'Menu', + + 0x30: 'Previous', + 0x31: 'Next', + 0x32: 'Play', + 0x33: 'Rewind', + 0x34: 'Fast Forward', + 0x38: 'Stop', + 0x39: 'Pause', + + 0x54: 'Display', + + 0x7B: 'Left', + 0x7C: 'Right', + 0x79: 'Up', + 0x7A: 'Down', + }), + + # Amp, Game, Sat, Tuner, USB modes + (0x30, None): (['Keypad: ', 'KP:'], { + 0x0C: 'Enter', + + 0x12: 'Volume Up', + 0x13: 'Volume Down', + 0x14: 'Mute', + 0x15: 'Power', + + 0x21: 'Tuner', + 0x22: 'Video', + 0x25: 'CD', + + 0x4D: 'Home', + 0x4B: 'Display', + + 0x60: 'Sleep', + 0x6A: 'TV', + + 0x53: 'Home', + + 0x7C: 'Game', + 0x7D: 'DVD', + }), + + # Amp, Game, Sat, Tuner, USB modes + (0xB0, None): (['Arrows: ', 'Ar:'], { + 0x7A: 'Left', + 0x7B: 'Right', + 0x78: 'Up', + 0x79: 'Down', + 0x77: 'Amp Menu', + }), + + # TV mode + (0x97, None): (['TV Extra', 'TV:'], { + 0x23: 'Return', + 0x36: 'Options', + + }), +} + +for (address, extended), (name, commands) in ADDRESSES.items(): + commands.update(NUMBERS) diff --git a/libsigrokdecode4DSL/decoders/ir_sirc/pd.py b/libsigrokdecode4DSL/decoders/ir_sirc/pd.py new file mode 100644 index 00000000..14ba63f0 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/ir_sirc/pd.py @@ -0,0 +1,215 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2020 Tom Flanagan +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +from common.srdhelper import bitpack_lsb +from .lists import ADDRESSES +import sigrokdecode as srd + +class SamplerateError(Exception): + pass + +class SIRCError(Exception): + pass + +class SIRCErrorSilent(SIRCError): + pass + +class Ann: + BIT, AGC, PAUSE, START, CMD, ADDR, EXT, REMOTE, WARN = range(9) + +AGC_USEC = 2400 +ONE_USEC = 1200 +ZERO_USEC = 600 +PAUSE_USEC = 600 + +class Decoder(srd.Decoder): + api_version = 3 + id = 'ir_sirc' + name = 'IR SIRC' + longname = 'Sony IR (SIRC)' + desc = 'Sony infrared remote control protocol (SIRC).' + license = 'gplv2+' + tags = ['IR'] + inputs = ['logic'] + outputs = [] + channels = ( + {'id': 'ir', 'name': 'IR', 'desc': 'IR data line'}, + ) + options = ( + {'id': 'polarity', 'desc': 'Polarity', 'default': 'active-low', + 'values': ('active-low', 'active-high')}, + ) + annotations = ( + ('bit', 'Bit'), + ('agc', 'AGC'), + ('pause', 'Pause'), + ('start', 'Start'), + ('command', 'Command'), + ('address', 'Address'), + ('extended', 'Extended'), + ('remote', 'Remote'), + ('warning', 'Warning'), + ) + annotation_rows = ( + ('bits', 'Bits', (Ann.BIT, Ann.AGC, Ann.PAUSE)), + ('fields', 'Fields', (Ann.START, Ann.CMD, Ann.ADDR, Ann.EXT)), + ('remotes', 'Remotes', (Ann.REMOTE,)), + ('warnings', 'Warnings', (Ann.WARN,)), + ) + + def __init__(self): + self.reset() + + def reset(self): + pass + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + self.active = self.options['polarity'] == 'active-high' + + def metadata(self, key, value): + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + self.snum_per_us = self.samplerate / 1e6 + + def putg(self, ss, es, cls, texts): + self.put(ss, es, self.out_ann, [cls, texts]) + + def tolerance(self, ss, es, expected): + microseconds = (es - ss) / self.snum_per_us + tolerance = expected * 0.30 + return (expected - tolerance) < microseconds < (expected + tolerance) + + def wait_wrap(self, conds, timeout): + if timeout is not None: + to = int(timeout * self.snum_per_us) + conds.append({'skip': to}) + ss = self.samplenum + pins = self.wait(conds) + es = self.samplenum + return pins, ss, es, self.matched + + def read_pulse(self, high, time): + e = 'f' if high else 'r' + max_time = int(time * 1.30) + (ir,), ss, es, (edge, timeout) = self.wait_wrap([{0: e}], max_time) + if timeout or not self.tolerance(ss, es, time): + raise SIRCError('Timeout') + return ir, ss, es, (edge, timeout) + + def read_bit(self): + e = 'f' if self.active else 'r' + _, high_ss, high_es, (edge, timeout) = self.wait_wrap([{0: e}], 2000) + if timeout: + raise SIRCError('Bit High Timeout') + if self.tolerance(high_ss, high_es, ONE_USEC): + bit = 1 + elif self.tolerance(high_ss, high_es, ZERO_USEC): + bit = 0 + else: + raise SIRCError('Bit Low Timeout') + try: + _, low_ss, low_es, _ = self.read_pulse(not self.active, PAUSE_USEC) + good = True + except SIRCError: + low_es = high_es + int(PAUSE_USEC * self.snum_per_us) + good = False + self.putg(high_ss, low_es, Ann.BIT, ['{}'.format(bit)]) + return bit, high_ss, low_es, good + + def read_signal(self): + # Start code + try: + _, agc_ss, agc_es, _ = self.read_pulse(self.active, AGC_USEC) + _, pause_ss, pause_es, _ = self.read_pulse(not self.active, PAUSE_USEC) + except SIRCError: + raise SIRCErrorSilent('not an SIRC message') + self.putg(agc_ss, agc_es, Ann.AGC, ['AGC', 'A']) + self.putg(pause_ss, pause_es, Ann.PAUSE, ['Pause', 'P']) + self.putg(agc_ss, pause_es, Ann.START, ['Start', 'S']) + + # Read bits + bits = [] + while True: + bit, ss, es, good = self.read_bit() + bits.append((bit, ss, es)) + if len(bits) > 20: + raise SIRCError('too many bits') + if not good: + if len(bits) == 12: + command = bits[0:7] + address = bits[7:12] + extended = [] + elif len(bits) == 15: + command = bits[0:7] + address = bits[7:15] + extended = [] + elif len(bits) == 20: + command = bits[0:7] + address = bits[7:12] + extended = bits[12:20] + else: + raise SIRCError('incorrect bits count {}'.format(len(bits))) + break + + command_num = bitpack_lsb(command, 0) + address_num = bitpack_lsb(address, 0) + command_str = '0x{:02X}'.format(command_num) + address_str = '0x{:02X}'.format(address_num) + self.putg(command[0][1], command[-1][2], Ann.CMD, [ + 'Command: {}'.format(command_str), + 'C:{}'.format(command_str), + ]) + self.putg(address[0][1], address[-1][2], Ann.ADDR, [ + 'Address: {}'.format(address_str), + 'A:{}'.format(address_str), + ]) + extended_num = None + if extended: + extended_num = bitpack_lsb(extended, 0) + extended_str = '0x{:02X}'.format(extended_num) + self.putg(extended[0][1], extended[-1][2], Ann.EXT, [ + 'Extended: {}'.format(extended_str), + 'E:{}'.format(extended_str), + ]) + return address_num, command_num, extended_num, bits[0][1], bits[-1][2] + + def decode(self): + if not self.samplerate: + raise SamplerateError('Cannot decode without samplerate.') + + unknown = (['Unknown Device: ', 'UNK: '], {}) + while True: + e = 'h' if self.active else 'l' + _, _, frame_ss, _ = self.wait_wrap([{0: e}], None) + try: + addr, cmd, ext, payload_ss, payload_es = self.read_signal() + names, cmds = ADDRESSES.get((addr, ext), unknown) + text = cmds.get(cmd, 'Unknown') + self.putg(frame_ss, payload_es, Ann.REMOTE, [ + n + text for n in names + ]) + except SIRCErrorSilent as e: + pass + except SIRCError as e: + self.putg(frame_ss, self.samplenum, Ann.WARN, [ + 'Error: {}'.format(e), + 'Error', + 'E', + ]) diff --git a/libsigrokdecode4DSL/decoders/jitter/__init__.py b/libsigrokdecode4DSL/decoders/jitter/__init__.py new file mode 100644 index 00000000..3394ad75 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/jitter/__init__.py @@ -0,0 +1,29 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 Sebastien Bourdelin +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This protocol decoder retrieves the timing jitter between two digital signals. + +It allows to define a clock source channel and a resulting signal channel. + +Each time a significant edge is detected in the clock source, we calculate the +elapsed time before the resulting signal answers and report the timing jitter. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/jitter/pd.py b/libsigrokdecode4DSL/decoders/jitter/pd.py new file mode 100644 index 00000000..8ea1aa67 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/jitter/pd.py @@ -0,0 +1,198 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 Sebastien Bourdelin +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd + +# Helper dictionary for edge detection. +edge_detector = { + 'rising': lambda x, y: bool(not x and y), + 'falling': lambda x, y: bool(x and not y), + 'both': lambda x, y: bool(x ^ y), +} + +class SamplerateError(Exception): + pass + +class Decoder(srd.Decoder): + api_version = 3 + id = 'jitter' + name = 'Jitter' + longname = 'Timing jitter calculation' + desc = 'Retrieves the timing jitter between two digital signals.' + license = 'gplv2+' + inputs = ['logic'] + outputs = [] + tags = ['Clock/timing', 'Util'] + channels = ( + {'id': 'clk', 'name': 'Clock', 'desc': 'Clock reference channel'}, + {'id': 'sig', 'name': 'Resulting signal', 'desc': 'Resulting signal controlled by the clock'}, + ) + options = ( + {'id': 'clk_polarity', 'desc': 'Clock edge polarity', + 'default': 'rising', 'values': ('rising', 'falling', 'both')}, + {'id': 'sig_polarity', 'desc': 'Resulting signal edge polarity', + 'default': 'rising', 'values': ('rising', 'falling', 'both')}, + ) + annotations = ( + ('jitter', 'Jitter value'), + ('clk_missed', 'Clock missed'), + ('sig_missed', 'Signal missed'), + ) + annotation_rows = ( + ('jitter', 'Jitter values', (0,)), + ('clk_missed', 'Clock missed', (1,)), + ('sig_missed', 'Signal missed', (2,)), + ) + binary = ( + ('ascii-float', 'Jitter values as newline-separated ASCII floats'), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.state = 'CLK' + self.samplerate = None + self.oldclk, self.oldsig = 0, 0 + self.clk_start = None + self.sig_start = None + self.clk_missed = 0 + self.sig_missed = 0 + + def start(self): + self.clk_edge = edge_detector[self.options['clk_polarity']] + self.sig_edge = edge_detector[self.options['sig_polarity']] + self.out_ann = self.register(srd.OUTPUT_ANN) + self.out_binary = self.register(srd.OUTPUT_BINARY) + self.out_clk_missed = self.register(srd.OUTPUT_META, + meta=(int, 'Clock missed', 'Clock transition missed')) + self.out_sig_missed = self.register(srd.OUTPUT_META, + meta=(int, 'Signal missed', 'Resulting signal transition missed')) + + def metadata(self, key, value): + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + + # Helper function for jitter time annotations. + def putx(self, delta): + # Adjust granularity. + if delta == 0 or delta >= 1: + delta_s = '%.1fs' % (delta) + elif delta <= 1e-12: + delta_s = '%.1ffs' % (delta * 1e15) + elif delta <= 1e-9: + delta_s = '%.1fps' % (delta * 1e12) + elif delta <= 1e-6: + delta_s = '%.1fns' % (delta * 1e9) + elif delta <= 1e-3: + delta_s = '%.1fμs' % (delta * 1e6) + else: + delta_s = '%.1fms' % (delta * 1e3) + + self.put(self.clk_start, self.sig_start, self.out_ann, [0, [delta_s]]) + + # Helper function for ASCII float jitter values (one value per line). + def putb(self, delta): + if delta is None: + return + # Format the delta to an ASCII float value terminated by a newline. + x = str(delta) + '\n' + self.put(self.clk_start, self.sig_start, self.out_binary, + [0, x.encode('UTF-8')]) + + # Helper function for missed clock and signal annotations. + def putm(self, data): + self.put(self.samplenum, self.samplenum, self.out_ann, data) + + def handle_clk(self, clk, sig): + if self.clk_start == self.samplenum: + # Clock transition already treated. + # We have done everything we can with this sample. + return True + + if self.clk_edge(self.oldclk, clk): + # Clock edge found. + # We note the sample and move to the next state. + self.clk_start = self.samplenum + self.state = 'SIG' + return False + else: + if self.sig_start is not None \ + and self.sig_start != self.samplenum \ + and self.sig_edge(self.oldsig, sig): + # If any transition in the resulting signal + # occurs while we are waiting for a clock, + # we increase the missed signal counter. + self.sig_missed += 1 + self.put(self.samplenum, self.samplenum, self.out_sig_missed, self.sig_missed) + self.putm([2, ['Missed signal', 'MS']]) + # No clock edge found, we have done everything we + # can with this sample. + return True + + def handle_sig(self, clk, sig): + if self.sig_start == self.samplenum: + # Signal transition already treated. + # We have done everything we can with this sample. + return True + + if self.sig_edge(self.oldsig, sig): + # Signal edge found. + # We note the sample, calculate the jitter + # and move to the next state. + self.sig_start = self.samplenum + self.state = 'CLK' + # Calculate and report the timing jitter. + delta = (self.sig_start - self.clk_start) / self.samplerate + self.putx(delta) + self.putb(delta) + return False + else: + if self.clk_start != self.samplenum \ + and self.clk_edge(self.oldclk, clk): + # If any transition in the clock signal + # occurs while we are waiting for a resulting + # signal, we increase the missed clock counter. + self.clk_missed += 1 + self.put(self.samplenum, self.samplenum, self.out_clk_missed, self.clk_missed) + self.putm([1, ['Missed clock', 'MC']]) + # No resulting signal edge found, we have done + # everything we can with this sample. + return True + + def decode(self): + if not self.samplerate: + raise SamplerateError('Cannot decode without samplerate.') + while True: + # Wait for a transition on CLK and/or SIG. + (clk, sig) = self.wait([{0: 'e'}, {1: 'e'}]) + + # State machine: + # For each sample we can move 2 steps forward in the state machine. + while True: + # Clock state has the lead. + if self.state == 'CLK': + if self.handle_clk(clk, sig): + break + if self.state == 'SIG': + if self.handle_sig(clk, sig): + break + + # Save current CLK/SIG values for the next round. + self.oldclk, self.oldsig = clk, sig diff --git a/libsigrokdecode4DSL/decoders/jtag/__init__.py b/libsigrokdecode4DSL/decoders/jtag/__init__.py new file mode 100644 index 00000000..51bb6299 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/jtag/__init__.py @@ -0,0 +1,30 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2012 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +JTAG (Joint Test Action Group), a.k.a. "IEEE 1149.1: Standard Test Access Port +and Boundary-Scan Architecture", is a protocol used for testing, debugging, +and flashing various digital ICs. + +Details: +https://en.wikipedia.org/wiki/Joint_Test_Action_Group +http://focus.ti.com/lit/an/ssya002c/ssya002c.pdf +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/jtag/pd.py b/libsigrokdecode4DSL/decoders/jtag/pd.py new file mode 100644 index 00000000..e9c629b6 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/jtag/pd.py @@ -0,0 +1,289 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2012-2015 Uwe Hermann +## Copyright (C) 2019 DreamSourceLab +## +## Version: +## Modified by Shiqiu Nie(369614718@qq.com) +## Date: 2017-01-11 +## Descript: +## 1. 2017-01-10 Fixed TDI/TDO data decode, when JTAG TAP run into +## SHIFT-IR/SHIFT-DR status,the first bit is not a valid bit. +## 2. 2017-01-11 Fixed decode when shift only one bit. +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd + +''' +OUTPUT_PYTHON format: + +Packet: +[, ] + +: + - 'NEW STATE': is the new state of the JTAG state machine. + Valid values: 'TEST-LOGIC-RESET', 'RUN-TEST/IDLE', 'SELECT-DR-SCAN', + 'CAPTURE-DR', 'SHIFT-DR', 'EXIT1-DR', 'PAUSE-DR', 'EXIT2-DR', 'UPDATE-DR', + 'SELECT-IR-SCAN', 'CAPTURE-IR', 'SHIFT-IR', 'EXIT1-IR', 'PAUSE-IR', + 'EXIT2-IR', 'UPDATE-IR'. + - 'IR TDI': Bitstring that was clocked into the IR register. + - 'IR TDO': Bitstring that was clocked out of the IR register. + - 'DR TDI': Bitstring that was clocked into the DR register. + - 'DR TDO': Bitstring that was clocked out of the DR register. + +All bitstrings are a list consisting of two items. The first is a sequence +of '1' and '0' characters (the right-most character is the LSB. Example: +'01110001', where 1 is the LSB). The second item is a list of ss/es values +for each bit that is in the bitstring. +''' + +jtag_states = [ + # Intro "tree" + 'TEST-LOGIC-RESET', 'RUN-TEST/IDLE', + # DR "tree" + 'SELECT-DR-SCAN', 'CAPTURE-DR', 'UPDATE-DR', 'PAUSE-DR', + 'SHIFT-DR', 'EXIT1-DR', 'EXIT2-DR', + # IR "tree" + 'SELECT-IR-SCAN', 'CAPTURE-IR', 'UPDATE-IR', 'PAUSE-IR', + 'SHIFT-IR', 'EXIT1-IR', 'EXIT2-IR', +] + +class Decoder(srd.Decoder): + api_version = 3 + id = 'jtag' + name = 'JTAG' + longname = 'Joint Test Action Group (IEEE 1149.1)' + desc = 'Protocol for testing, debugging, and flashing ICs.' + license = 'gplv2+' + inputs = ['logic'] + outputs = ['jtag'] + tags = ['Debug/trace'] + channels = ( + {'id': 'tdi', 'name': 'TDI', 'desc': 'Test data input'}, + {'id': 'tdo', 'name': 'TDO', 'desc': 'Test data output'}, + {'id': 'tck', 'name': 'TCK', 'desc': 'Test clock'}, + {'id': 'tms', 'name': 'TMS', 'desc': 'Test mode select'}, + ) + optional_channels = ( + {'id': 'trst', 'name': 'TRST#', 'desc': 'Test reset'}, + {'id': 'srst', 'name': 'SRST#', 'desc': 'System reset'}, + {'id': 'rtck', 'name': 'RTCK', 'desc': 'Return clock signal'}, + ) + annotations = tuple([tuple([s.lower(), s]) for s in jtag_states]) + ( \ + ('bit-tdi', 'Bit (TDI)'), + ('bit-tdo', 'Bit (TDO)'), + ('bitstring-tdi', 'Bitstring (TDI)'), + ('bitstring-tdo', 'Bitstring (TDO)'), + ) + annotation_rows = ( + ('bits-tdi', 'Bits (TDI)', (16,)), + ('bits-tdo', 'Bits (TDO)', (17,)), + ('bitstrings-tdi', 'Bitstring (TDI)', (18,)), + ('bitstrings-tdo', 'Bitstring (TDO)', (19,)), + ('states', 'States', tuple(range(15 + 1))), + ) + + def __init__(self): + self.reset() + + def reset(self): + # self.state = 'TEST-LOGIC-RESET' + self.state = 'RUN-TEST/IDLE' + self.oldstate = None + self.bits_tdi = [] + self.bits_tdo = [] + self.bits_samplenums_tdi = [] + self.bits_samplenums_tdo = [] + self.ss_item = self.es_item = None + self.ss_bitstring = self.es_bitstring = None + self.saved_item = None + self.first = True + self.first_bit = True + self.bits_cnt = 0 + self.data_ready = False + + def start(self): + self.out_python = self.register(srd.OUTPUT_PYTHON) + self.out_ann = self.register(srd.OUTPUT_ANN) + + def putx(self, data): + self.put(self.ss_item, self.es_item, self.out_ann, data) + + def putp(self, data): + self.put(self.ss_item, self.es_item, self.out_python, data) + + def putx_bs(self, data): + self.put(self.ss_bitstring, self.es_bitstring, self.out_ann, data) + + def putp_bs(self, data): + self.put(self.ss_bitstring, self.es_bitstring, self.out_python, data) + + def advance_state_machine(self, tms): + self.oldstate = self.state + + # Intro "tree" + if self.state == 'TEST-LOGIC-RESET': + self.state = 'TEST-LOGIC-RESET' if (tms) else 'RUN-TEST/IDLE' + elif self.state == 'RUN-TEST/IDLE': + self.state = 'SELECT-DR-SCAN' if (tms) else 'RUN-TEST/IDLE' + + # DR "tree" + elif self.state == 'SELECT-DR-SCAN': + self.state = 'SELECT-IR-SCAN' if (tms) else 'CAPTURE-DR' + elif self.state == 'CAPTURE-DR': + self.state = 'EXIT1-DR' if (tms) else 'SHIFT-DR' + elif self.state == 'SHIFT-DR': + self.state = 'EXIT1-DR' if (tms) else 'SHIFT-DR' + elif self.state == 'EXIT1-DR': + self.state = 'UPDATE-DR' if (tms) else 'PAUSE-DR' + elif self.state == 'PAUSE-DR': + self.state = 'EXIT2-DR' if (tms) else 'PAUSE-DR' + elif self.state == 'EXIT2-DR': + self.state = 'UPDATE-DR' if (tms) else 'SHIFT-DR' + elif self.state == 'UPDATE-DR': + self.state = 'SELECT-DR-SCAN' if (tms) else 'RUN-TEST/IDLE' + + # IR "tree" + elif self.state == 'SELECT-IR-SCAN': + self.state = 'TEST-LOGIC-RESET' if (tms) else 'CAPTURE-IR' + elif self.state == 'CAPTURE-IR': + self.state = 'EXIT1-IR' if (tms) else 'SHIFT-IR' + elif self.state == 'SHIFT-IR': + self.state = 'EXIT1-IR' if (tms) else 'SHIFT-IR' + elif self.state == 'EXIT1-IR': + self.state = 'UPDATE-IR' if (tms) else 'PAUSE-IR' + elif self.state == 'PAUSE-IR': + self.state = 'EXIT2-IR' if (tms) else 'PAUSE-IR' + elif self.state == 'EXIT2-IR': + self.state = 'UPDATE-IR' if (tms) else 'SHIFT-IR' + elif self.state == 'UPDATE-IR': + self.state = 'SELECT-DR-SCAN' if (tms) else 'RUN-TEST/IDLE' + + def handle_rising_tck_edge(self, tdi, tdo, tck, tms, trst, srst, rtck): + # Rising TCK edges always advance the state machine. + self.advance_state_machine(tms) + + if self.first: + # Save the start sample and item for later (no output yet). + self.ss_item = self.samplenum + self.first = False + else: + # Output the saved item (from the last CLK edge to the current). + self.es_item = self.samplenum + # Output the old state (from last rising TCK edge to current one). + self.putx([jtag_states.index(self.oldstate), [self.oldstate]]) + self.putp(['NEW STATE', self.state]) + + # Upon SHIFT-IR/SHIFT-DR collect the current TDI/TDO values. + if self.state.startswith('SHIFT-'): + #if self.first_bit: + #self.ss_bitstring = self.samplenum + # self.first_bit = False + + #else: + if self.bits_cnt > 0: + if self.bits_cnt == 1: + self.ss_bitstring = self.samplenum + + if self.bits_cnt > 1: + self.putx([16, [str(self.bits_tdi[0])]]) + self.putx([17, [str(self.bits_tdo[0])]]) + # Use self.samplenum as ES of the previous bit. + self.bits_samplenums_tdi[0][1] = self.samplenum + self.bits_samplenums_tdo[0][1] = self.samplenum + + self.bits_tdi.insert(0, tdi) + self.bits_tdo.insert(0, tdo) + + # Use self.samplenum as SS of the current bit. + self.bits_samplenums_tdi.insert(0, [self.samplenum, -1]) + self.bits_samplenums_tdo.insert(0, [self.samplenum, -1]) + + self.bits_cnt = self.bits_cnt + 1 + + # Output all TDI/TDO bits if we just switched from SHIFT-* to EXIT1-*. + if self.oldstate.startswith('SHIFT-') and \ + self.state.startswith('EXIT1-'): + + #self.es_bitstring = self.samplenum + if self.bits_cnt > 0: + if self.bits_cnt == 1: # Only shift one bit + self.ss_bitstring = self.samplenum + self.bits_tdi.insert(0, tdi) + self.bits_tdo.insert(0, tdo) + ## Use self.samplenum as SS of the current bit. + self.bits_samplenums_tdi.insert(0, [self.samplenum, -1]) + self.bits_samplenums_tdo.insert(0, [self.samplenum, -1]) + else: + ### ---------------------------------------------------------------- + self.putx([16, [str(self.bits_tdi[0])]]) + self.putx([17, [str(self.bits_tdo[0])]]) + ### Use self.samplenum as ES of the previous bit. + self.bits_samplenums_tdi[0][1] = self.samplenum + self.bits_samplenums_tdo[0][1] = self.samplenum + + self.bits_tdi.insert(0, tdi) + self.bits_tdo.insert(0, tdo) + + ## Use self.samplenum as SS of the current bit. + self.bits_samplenums_tdi.insert(0, [self.samplenum, -1]) + self.bits_samplenums_tdo.insert(0, [self.samplenum, -1]) + ## ---------------------------------------------------------------- + + self.data_ready = True + + self.first_bit = True + self.bits_cnt = 0 + if self.oldstate.startswith('EXIT'):# and \ + #self.state.startswith('PAUSE-'): + if self.data_ready: + self.data_ready = False + self.es_bitstring = self.samplenum + t = self.state[-2:] + ' TDI' + b = ''.join(map(str, self.bits_tdi)) + h = ' (0x%X' % int('0b' + b, 2) + ')' + s = t + ': ' + h + ', ' + str(len(self.bits_tdi)) + ' bits' #b + + self.putx_bs([18, [s]]) + self.bits_samplenums_tdi[0][1] = self.samplenum # ES of last bit. + self.putp_bs([t, [b, self.bits_samplenums_tdi]]) + self.putx([16, [str(self.bits_tdi[0])]]) # Last bit. + self.bits_tdi = [] + self.bits_samplenums_tdi = [] + + t = self.state[-2:] + ' TDO' + b = ''.join(map(str, self.bits_tdo)) + h = ' (0x%X' % int('0b' + b, 2) + ')' + s = t + ': ' + h + ', ' + str(len(self.bits_tdo)) + ' bits' #+ b + self.putx_bs([19, [s]]) + self.bits_samplenums_tdo[0][1] = self.samplenum # ES of last bit. + self.putp_bs([t, [b, self.bits_samplenums_tdo]]) + self.putx([17, [str(self.bits_tdo[0])]]) # Last bit. + self.bits_tdo = [] + self.bits_samplenums_tdo = [] + + #self.first_bit = True + #self.bits_cnt = 0 + + #self.ss_bitstring = self.samplenum + + self.ss_item = self.samplenum + + def decode(self): + while True: + # Wait for a rising edge on TCK. + (tdi, tdo, tck, tms, trst, srst, rtck) = self.wait({2: 'r'}) + self.handle_rising_tck_edge(tdi, tdo, tck, tms, trst, srst, rtck) diff --git a/libsigrokdecode4DSL/decoders/jtag_ejtag/__init__.py b/libsigrokdecode4DSL/decoders/jtag_ejtag/__init__.py new file mode 100644 index 00000000..1c66dcd5 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/jtag_ejtag/__init__.py @@ -0,0 +1,25 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2018 Vladislav Ivanov +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This decoder stacks on top of the 'jtag' PD and decodes JTAG data specific +to the MIPS EJTAG protocol. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/jtag_ejtag/pd.py b/libsigrokdecode4DSL/decoders/jtag_ejtag/pd.py new file mode 100644 index 00000000..f16f0b4e --- /dev/null +++ b/libsigrokdecode4DSL/decoders/jtag_ejtag/pd.py @@ -0,0 +1,408 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2018 Vladislav Ivanov +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd +from common.srdhelper import bin2int + +class Instruction(object): + IDCODE = 0x01 + IMPCODE = 0x03 + ADDRESS = 0x08 + DATA = 0x09 + CONTROL = 0x0A + ALL = 0x0B + EJTAGBOOT = 0x0C + NORMALBOOT = 0x0D + FASTDATA = 0x0E + TCBCONTROLA = 0x10 + TCBCONTROLB = 0x11 + TCBDATA = 0x12 + TCBCONTROLC = 0x13 + PCSAMPLE = 0x14 + TCBCONTROLD = 0x15 + TCBCONTROLE = 0x16 + +class State(object): + RESET = 0 + DEVICE_ID = 1 + IMPLEMENTATION = 2 + DATA = 3 + ADDRESS = 4 + CONTROL = 5 + FASTDATA = 6 + PC_SAMPLE = 7 + BYPASS = 8 + +class ControlReg(object): + PRACC = (1 << 18) + PRNW = (1 << 19) + +class Ann(object): + INSTRUCTION = 0 + REGISTER = 1 + CONTROL_FIELD_IN = 10 + CONTROL_FIELD_OUT = 11 + PRACC = 12 + +ejtag_insn = { + 0x00: ['Free', 'Boundary scan'], + 0x01: ['IDCODE', 'Select Device Identification (ID) register'], + 0x02: ['Free', 'Boundary scan'], + 0x03: ['IMPCODE', 'Select Implementation register'], + 0x08: ['ADDRESS', 'Select Address register'], + 0x09: ['DATA', 'Select Data register'], + 0x0A: ['CONTROL', 'Select EJTAG Control register'], + 0x0B: ['ALL', 'Select the Address, Data and EJTAG Control registers'], + 0x0C: ['EJTAGBOOT', 'Fetch code from the debug exception vector after reset'], + 0x0D: ['NORMALBOOT', 'Execute the reset handler after reset'], + 0x0E: ['FASTDATA', 'Select the Data and Fastdata registers'], + 0x0F: ['Reserved', 'Reserved'], + 0x10: ['TCBCONTROLA', 'Select the control register TCBTraceControl'], + 0x11: ['TCBCONTROLB', 'Selects trace control block register B'], + 0x12: ['TCBDATA', 'Access the registers specified by TCBCONTROLB'], + 0x13: ['TCBCONTROLC', 'Select trace control block register C'], + 0x14: ['PCSAMPLE', 'Select the PCsample register'], + 0x15: ['TCBCONTROLD', 'Select trace control block register D'], + 0x16: ['TCBCONTROLE', 'Select trace control block register E'], + 0x17: ['FDC', 'Select Fast Debug Channel'], + 0x1C: ['Free', 'Boundary scan'], +} + +ejtag_reg = { + 0x00: 'RESET', + 0x01: 'DEVICE_ID', + 0x02: 'IMPLEMENTATION', + 0x03: 'DATA', + 0x04: 'ADDRESS', + 0x05: 'CONTROL', + 0x06: 'FASTDATA', + 0x07: 'PC_SAMPLE', + 0x08: 'BYPASS', +} + +ejtag_control_reg = [ + [31, 31, 'Rocc', [ + # Read + ['No reset ocurred', 'Reset ocurred'], + # Write + ['Acknowledge reset', 'No effect'], + ]], + [30, 29, 'Psz', [ + ['Access: byte', 'Access: halfword', 'Access: word', 'Access: triple'], + ]], + [23, 23, 'VPED', [ + ['VPE disabled', 'VPE enabled'], + ]], + [22, 22, 'Doze', [ + ['Processor is not in low-power mode', 'Processor is in low-power mode'], + ]], + [21, 21, 'Halt', [ + ['Internal system bus clock is running', 'Internal system bus clock is stopped'], + ]], + [20, 20, 'Per Rst', [ + ['No peripheral reset applied', 'Peripheral reset applied'], + ['Deassert peripheral reset', 'Assert peripheral reset'], + ]], + [19, 19, 'PRn W', [ + ['Read processor access', 'Write processor access'], + ]], + [18, 18, 'Pr Acc', [ + ['No pending processor access', 'Pending processor access'], + ['Finish processor access', 'Don\'t finish processor access'], + ]], + [16, 16, 'Pr Rst', [ + ['No processor reset applied', 'Processor reset applied'], + ['Deassert processor reset', 'Assert system reset'], + ]], + [15, 15, 'Prob En', [ + ['Probe will not serve processor accesses', 'Probe will service processor accesses'], + ]], + [14, 14, 'Prob Trap', [ + ['Default location', 'DMSEG fetch'], + ['Set to default location', 'Set to DMSEG fetch'], + ]], + [13, 13, 'ISA On Debug', [ + ['MIPS32/MIPS64 ISA', 'microMIPS ISA'], + ['Set to MIPS32/MIPS64 ISA', 'Set to microMIPS ISA'], + ]], + [12, 12, 'EJTAG Brk', [ + ['No pending debug interrupt', 'Pending debug interrupt'], + ['No effect', 'Request debug interrupt'], + ]], + [3, 3, 'DM', [ + ['Not in debug mode', 'In debug mode'], + ]], +] + +ejtag_state_map = { + Instruction.IDCODE: State.DEVICE_ID, + Instruction.IMPCODE: State.IMPLEMENTATION, + Instruction.DATA: State.DATA, + Instruction.ADDRESS: State.ADDRESS, + Instruction.CONTROL: State.CONTROL, + Instruction.FASTDATA: State.FASTDATA, +} + +class RegData(object): + def __init__(self): + self.ss = None + self.es = None + self.data = None + +class LastData(object): + def __init__(self): + self.data_in = RegData() + self.data_out = RegData() + +class PraccState(object): + def reset(self): + self.address_in = None + self.address_out = None + self.data_in = None + self.data_out = None + self.write = False + self.ss = 0 + self.es = 0 + + def __init__(self): + self.reset() + +regs_items = { + 'ann': tuple([tuple([s.lower(), s]) for s in list(ejtag_reg.values())]), + 'rows_range': tuple(range(1, 1 + 9)), +} + +class Decoder(srd.Decoder): + api_version = 3 + id = 'jtag_ejtag' + name = 'JTAG / EJTAG' + longname = 'Joint Test Action Group / EJTAG (MIPS)' + desc = 'MIPS EJTAG protocol.' + license = 'gplv2+' + inputs = ['jtag'] + outputs = [] + tags = ['Debug/trace'] + annotations = ( + ('instruction', 'Instruction'), + ) + regs_items['ann'] + ( + ('control_field_in', 'Control field in'), + ('control_field_out', 'Control field out'), + ('pracc', 'PrAcc'), + ) + annotation_rows = ( + ('instructions', 'Instructions', (0,)), + ('regs', 'Registers', regs_items['rows_range']), + ('control_fields_in', 'Control fields in', (10,)), + ('control_fields_out', 'Control fields out', (11,)), + ('pracc', 'PrAcc', (12,)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.state = State.RESET + self.pracc_state = PraccState() + + def put_current(self, data): + self.put(self.ss, self.es, self.out_ann, data) + + def put_at(self, ss: int, es: int, data): + self.put(ss, es, self.out_ann, data) + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def select_reg(self, ir_value: int): + self.state = ejtag_state_map.get(ir_value, State.RESET) + + def parse_pracc(self): + control_in = bin2int(self.last_data['in']['data'][0]) + control_out = bin2int(self.last_data['out']['data'][0]) + + # Check if JTAG master acknowledges a pending PrAcc. + if not ((not (control_in & ControlReg.PRACC)) and \ + (control_out & ControlReg.PRACC)): + return + + ss, es = self.pracc_state.ss, self.pracc_state.es + pracc_write = (control_out & ControlReg.PRNW) != 0 + + s = 'PrAcc: ' + s += 'Store' if pracc_write else 'Load/Fetch' + + if pracc_write: + if self.pracc_state.address_out is not None: + s += ', A:' + ' 0x{:08X}'.format(self.pracc_state.address_out) + if self.pracc_state.data_out is not None: + s += ', D:' + ' 0x{:08X}'.format(self.pracc_state.data_out) + else: + if self.pracc_state.address_out is not None: + s += ', A:' + ' 0x{:08X}'.format(self.pracc_state.address_out) + if self.pracc_state.data_in is not None: + s += ', D:' + ' 0x{:08X}'.format(self.pracc_state.data_in) + + self.pracc_state.reset() + + self.put_at(ss, es, [Ann.PRACC, [s]]) + + def parse_control_reg(self, ann): + reg_write = ann == Ann.CONTROL_FIELD_IN + control_bit_positions = [] + data_select = 'in' if (reg_write) else 'out' + + control_bit_positions = self.last_data[data_select]['data'][1] + control_data = self.last_data[data_select]['data'][0] + + # Annotate control register fields. + for field in ejtag_control_reg: + start_bit = 31 - field[1] + end_bit = 31 - field[0] + comment = field[2] + value_descriptions = [] + + if reg_write: + if len(field[3]) < 2: + continue + value_descriptions = field[3][1] + else: + value_descriptions = field[3][0] + + ss = control_bit_positions[start_bit][0] + es = control_bit_positions[end_bit][1] + + value_str = control_data[end_bit : start_bit + 1] + value_index = bin2int(value_str) + + short_desc = comment + ': ' + value_str + long_desc = value_descriptions[value_index] if len(value_descriptions) > value_index else '?' + + self.put_at(ss, es, [ann, [long_desc, short_desc]]) + + def check_last_data(self): + if not hasattr(self, 'last_data'): + self.last_data = {'in': {}, 'out': {}} + + def handle_fastdata(self, val, ann): + spracc_write_desc = { + 0: ['0', 'SPrAcc: 0', 'Request completion of Fastdata access'], + 1: ['1', 'SPrAcc: 1', 'No effect'], + } + spracc_read_desc = { + 0: ['0', 'SPrAcc: 0', 'Fastdata access failure'], + 1: ['1', 'SPrAcc: 1', 'Successful completion of Fastdata access'], + } + + bitstring = val[0] + bit_sample_pos = val[1] + fastdata_state = bitstring[32] + data = bin2int(bitstring[0:32]) + + fastdata_bit_pos = bit_sample_pos[32] + data_pos = [bit_sample_pos[31][0], bit_sample_pos[0][1]] + + ss_fastdata, es_fastdata = fastdata_bit_pos + ss_data, es_data = data_pos + + display_data = [ann, ['0x{:08X}'.format(data)]] + spracc_display_data = [] + + if ann == Ann.CONTROL_FIELD_IN: + spracc_display_data = [ann, spracc_write_desc[int(fastdata_state)]] + elif ann == Ann.CONTROL_FIELD_OUT: + spracc_display_data = [ann, spracc_read_desc[int(fastdata_state)]] + + self.put_at(ss_fastdata, es_fastdata, spracc_display_data) + self.put_at(ss_data, es_data, display_data) + + def handle_dr_tdi(self, val): + value = bin2int(val[0]) + self.check_last_data() + self.last_data['in'] = {'ss': self.ss, 'es': self.es, 'data': val} + + self.pracc_state.ss, self.pracc_state.es = self.ss, self.es + + if self.state == State.ADDRESS: + self.pracc_state.address_in = value + elif self.state == State.DATA: + self.pracc_state.data_in = value + elif self.state == State.FASTDATA: + self.handle_fastdata(val, Ann.CONTROL_FIELD_IN) + + def handle_dr_tdo(self, val): + value = bin2int(val[0]) + self.check_last_data() + self.last_data['out'] = {'ss': self.ss, 'es': self.es, 'data': val} + if self.state == State.ADDRESS: + self.pracc_state.address_out = value + elif self.state == State.DATA: + self.pracc_state.data_out = value + elif self.state == State.FASTDATA: + self.handle_fastdata(val, Ann.CONTROL_FIELD_OUT) + + def handle_ir_tdi(self, val): + code = bin2int(val[0]) + hexval = '0x{:02X}'.format(code) + if code in ejtag_insn: + # Format instruction name. + insn = ejtag_insn[code] + s_short = insn[0] + s_long = insn[0] + ': ' + insn[1] + ' (' + hexval + ')' + # Display it and select data register. + self.put_current([Ann.INSTRUCTION, [s_long, s_short]]) + else: + self.put_current([Ann.INSTRUCTION, [hexval, 'IR TDI ({})'.format(hexval)]]) + self.select_reg(code) + + def handle_new_state(self, new_state): + if new_state != 'UPDATE-DR' or not hasattr(self, 'last_data'): + return + + if self.state == State.RESET: + return + + reg_name = ejtag_reg[self.state] + ann_index = Ann.REGISTER + self.state + display_data = [ann_index, [reg_name]] + self.put_at(self.last_data['in']['ss'], self.last_data['in']['es'], display_data) + + if self.state == State.CONTROL: + control_bit_positions = self.last_data['in']['data'][1] + bit_count = len(control_bit_positions) + # Check if control register data length is correct. + if bit_count != 32: + error_display = [Ann.REGISTER, ['Error: length != 32']] + self.put_at(self.last_data['in']['ss'], self.last_data['in']['es'], error_display) + return + self.parse_control_reg(Ann.CONTROL_FIELD_IN) + self.parse_control_reg(Ann.CONTROL_FIELD_OUT) + self.parse_pracc() + + def decode(self, ss: int, es: int, data): + cmd, val = data + self.ss, self.es = ss, es + + if cmd == 'IR TDI': + self.handle_ir_tdi(val) + elif cmd == 'DR TDI': + self.handle_dr_tdi(val) + elif cmd == 'DR TDO': + self.handle_dr_tdo(val) + elif cmd == 'NEW STATE': + self.handle_new_state(val) diff --git a/libsigrokdecode4DSL/decoders/jtag_stm32/__init__.py b/libsigrokdecode4DSL/decoders/jtag_stm32/__init__.py new file mode 100644 index 00000000..bf69e8e0 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/jtag_stm32/__init__.py @@ -0,0 +1,29 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2012 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This decoder stacks on top of the 'jtag' PD and decodes JTAG data specific to +the STM32 microcontroller series. + +Details: +https://en.wikipedia.org/wiki/STM32 +http://www.st.com/internet/com/TECHNICAL_RESOURCES/TECHNICAL_LITERATURE/REFERENCE_MANUAL/CD00171190.pdf (e.g. chapter 31.7: "JTAG debug port") +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/jtag_stm32/pd.py b/libsigrokdecode4DSL/decoders/jtag_stm32/pd.py new file mode 100644 index 00000000..82558b82 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/jtag_stm32/pd.py @@ -0,0 +1,269 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2012-2015 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd + +# JTAG debug port data registers (in IR[3:0]) and their sizes (in bits) +# Note: The ARM DAP-DP is not IEEE 1149.1 (JTAG) compliant (as per ARM docs), +# as it does not implement the EXTEST, SAMPLE, and PRELOAD instructions. +# Instead, BYPASS is decoded for any of these instructions. +ir = { + '1111': ['BYPASS', 1], # Bypass register + '1110': ['IDCODE', 32], # ID code register + '1010': ['DPACC', 35], # Debug port access register + '1011': ['APACC', 35], # Access port access register + '1000': ['ABORT', 35], # Abort register # TODO: 32 bits? Datasheet typo? +} + +# Boundary scan data registers (in IR[8:4]) and their sizes (in bits) +bs_ir = { + '11111': ['BYPASS', 1], # Bypass register +} + +# ARM Cortex-M3 r1p1-01rel0 ID code +cm3_idcode = 0x3ba00477 + +# http://infocenter.arm.com/help/topic/com.arm.doc.ddi0413c/Chdjibcg.html +cm3_idcode_ver = { + 0x3: 'JTAG-DP', + 0x2: 'SW-DP', +} +cm3_idcode_part = { + 0xba00: 'JTAG-DP', + 0xba10: 'SW-DP', +} + +# http://infocenter.arm.com/help/topic/com.arm.doc.faqs/ka14408.html +jedec_id = { + 5: { + 0x3b: 'ARM Ltd.', + }, +} + +# JTAG ID code in the STM32F10xxx BSC (boundary scan) TAP +jtag_idcode = { + 0x06412041: 'Low-density device, rev. A', + 0x06410041: 'Medium-density device, rev. A', + 0x16410041: 'Medium-density device, rev. B/Z/Y', + 0x06414041: 'High-density device, rev. A/Z/Y', + 0x06430041: 'XL-density device, rev. A', + 0x06418041: 'Connectivity-line device, rev. A/Z', +} + +# ACK[2:0] in the DPACC/APACC registers (unlisted values are reserved) +ack_val = { + '001': 'WAIT', + '010': 'OK/FAULT', +} + +# 32bit debug port registers (addressed via A[3:2]) +dp_reg = { + '00': 'Reserved', # Must be kept at reset value + '01': 'DP CTRL/STAT', + '10': 'DP SELECT', + '11': 'DP RDBUFF', +} + +# APB-AP registers (each of them 32 bits wide) +apb_ap_reg = { + 0x00: ['CSW', 'Control/status word'], + 0x04: ['TAR', 'Transfer address'], + # 0x08: Reserved SBZ + 0x0c: ['DRW', 'Data read/write'], + 0x10: ['BD0', 'Banked data 0'], + 0x14: ['BD1', 'Banked data 1'], + 0x18: ['BD2', 'Banked data 2'], + 0x1c: ['BD3', 'Banked data 3'], + # 0x20-0xf4: Reserved SBZ + 0x800000000: ['ROM', 'Debug ROM address'], + 0xfc: ['IDR', 'Identification register'], +} + +# TODO: Split off generic ARM/Cortex-M3 parts into another protocol decoder? + +# Bits[31:28]: Version (here: 0x3) +# JTAG-DP: 0x3, SW-DP: 0x2 +# Bits[27:12]: Part number (here: 0xba00) +# JTAG-DP: 0xba00, SW-DP: 0xba10 +# Bits[11:1]: JEDEC (JEP-106) manufacturer ID (here: 0x23b) +# Bits[11:8]: Continuation code ('ARM Ltd.': 0x04) +# Bits[7:1]: Identity code ('ARM Ltd.': 0x3b) +# Bits[0:0]: Reserved (here: 0x1) +def decode_device_id_code(bits): + id_hex = '0x%x' % int('0b' + bits, 2) + ver = cm3_idcode_ver.get(int('0b' + bits[-32:-28], 2), 'UNKNOWN') + part = cm3_idcode_part.get(int('0b' + bits[-28:-12], 2), 'UNKNOWN') + ids = jedec_id.get(int('0b' + bits[-12:-8], 2) + 1, {}) + manuf = ids.get(int('0b' + bits[-7:-1], 2), 'UNKNOWN') + return (id_hex, manuf, ver, part) + +# DPACC is used to access debug port registers (CTRL/STAT, SELECT, RDBUFF). +# APACC is used to access all Access Port (AHB-AP) registers. + +# APACC/DPACC, when transferring data IN: +# Bits[34:3] = DATA[31:0]: 32bit data to transfer (write request) +# Bits[2:1] = A[3:2]: 2-bit address (debug/access port register) +# Bits[0:0] = RnW: Read request (1) or write request (0) +def data_in(instruction, bits): + data, a, rnw = bits[:-3], bits[-3:-1], bits[-1] + data_hex = '0x%x' % int('0b' + data, 2) + r = 'Read request' if (rnw == '1') else 'Write request' + # reg = dp_reg[a] if (instruction == 'DPACC') else apb_ap_reg[a] + reg = dp_reg[a] if (instruction == 'DPACC') else a # TODO + return 'New transaction: DATA: %s, A: %s, RnW: %s' % (data_hex, reg, r) + +# APACC/DPACC, when transferring data OUT: +# Bits[34:3] = DATA[31:0]: 32bit data which is read (read request) +# Bits[2:0] = ACK[2:0]: 3-bit acknowledge +def data_out(bits): + data, ack = bits[:-3], bits[-3:] + data_hex = '0x%x' % int('0b' + data, 2) + ack_meaning = ack_val.get(ack, 'Reserved') + return 'Previous transaction result: DATA: %s, ACK: %s' \ + % (data_hex, ack_meaning) + +class Decoder(srd.Decoder): + api_version = 3 + id = 'jtag_stm32' + name = 'JTAG / STM32' + longname = 'Joint Test Action Group / ST STM32' + desc = 'ST STM32-specific JTAG protocol.' + license = 'gplv2+' + inputs = ['jtag'] + outputs = [] + tags = ['Debug/trace'] + annotations = ( + ('item', 'Item'), + ('field', 'Field'), + ('command', 'Command'), + ('warning', 'Warning'), + ) + annotation_rows = ( + ('items', 'Items', (0,)), + ('fields', 'Fields', (1,)), + ('commands', 'Commands', (2,)), + ('warnings', 'Warnings', (3,)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.state = 'IDLE' + self.samplenums = None + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def putx(self, data): + self.put(self.ss, self.es, self.out_ann, data) + + def putf(self, s, e, data): + self.put(self.samplenums[s][0], self.samplenums[e][1], self.out_ann, data) + + def handle_reg_bypass(self, cmd, bits): + self.putx([0, ['BYPASS: ' + bits]]) + + def handle_reg_idcode(self, cmd, bits): + bits = bits[1:] + + id_hex, manuf, ver, part = decode_device_id_code(bits) + cc = '0x%x' % int('0b' + bits[-12:-8], 2) + ic = '0x%x' % int('0b' + bits[-7:-1], 2) + + self.putf(0, 0, [1, ['Reserved', 'Res', 'R']]) + self.putf(8, 11, [0, ['Continuation code: %s' % cc, 'CC', 'C']]) + self.putf(1, 7, [0, ['Identity code: %s' % ic, 'IC', 'I']]) + self.putf(1, 11, [1, ['Manufacturer: %s' % manuf, 'Manuf', 'M']]) + self.putf(12, 27, [1, ['Part: %s' % part, 'Part', 'P']]) + self.putf(28, 31, [1, ['Version: %s' % ver, 'Version', 'V']]) + self.putf(32, 32, [1, ['BYPASS (BS TAP)', 'BS', 'B']]) + + self.putx([2, ['IDCODE: %s (%s: %s/%s)' % \ + decode_device_id_code(bits)]]) + + def handle_reg_dpacc(self, cmd, bits): + bits = bits[1:] + s = data_in('DPACC', bits) if (cmd == 'DR TDI') else data_out(bits) + self.putx([2, [s]]) + + def handle_reg_apacc(self, cmd, bits): + bits = bits[1:] + s = data_in('APACC', bits) if (cmd == 'DR TDI') else data_out(bits) + self.putx([2, [s]]) + + def handle_reg_abort(self, cmd, bits): + bits = bits[1:] + # Bits[31:1]: reserved. Bit[0]: DAPABORT. + a = '' if (bits[0] == '1') else 'No ' + s = 'DAPABORT = %s: %sDAP abort generated' % (bits[0], a) + self.putx([2, [s]]) + + # Warn if DAPABORT[31:1] contains non-zero bits. + if (bits[:-1] != ('0' * 31)): + self.putx([3, ['WARNING: DAPABORT[31:1] reserved!']]) + + def handle_reg_unknown(self, cmd, bits): + bits = bits[1:] + self.putx([2, ['Unknown instruction: %s' % bits]]) + + def decode(self, ss, es, data): + cmd, val = data + + self.ss, self.es = ss, es + + if cmd != 'NEW STATE': + # The right-most char in the 'val' bitstring is the LSB. + val, self.samplenums = val + self.samplenums.reverse() + + if cmd == 'IR TDI': + # Switch to the state named after the instruction, or 'UNKNOWN'. + # The STM32F10xxx has two serially connected JTAG TAPs, the + # boundary scan tap (5 bits) and the Cortex-M3 TAP (4 bits). + # See UM 31.5 "STM32F10xxx JTAG TAP connection" for details. + self.state = ir.get(val[5:9], ['UNKNOWN', 0])[0] + bstap_ir = bs_ir.get(val[:5], ['UNKNOWN', 0])[0] + self.putf(4, 8, [1, ['IR (BS TAP): ' + bstap_ir]]) + self.putf(0, 3, [1, ['IR (M3 TAP): ' + self.state]]) + self.putx([2, ['IR: %s' % self.state]]) + + # State machine + if self.state == 'BYPASS': + # Here we're interested in incoming bits (TDI). + if cmd != 'DR TDI': + return + handle_reg = getattr(self, 'handle_reg_%s' % self.state.lower()) + handle_reg(cmd, val) + self.state = 'IDLE' + elif self.state in ('IDCODE', 'ABORT', 'UNKNOWN'): + # Here we're interested in outgoing bits (TDO). + if cmd != 'DR TDO': + return + handle_reg = getattr(self, 'handle_reg_%s' % self.state.lower()) + handle_reg(cmd, val) + self.state = 'IDLE' + elif self.state in ('DPACC', 'APACC'): + # Here we're interested in incoming and outgoing bits (TDI/TDO). + if cmd not in ('DR TDI', 'DR TDO'): + return + handle_reg = getattr(self, 'handle_reg_%s' % self.state.lower()) + handle_reg(cmd, val) + if cmd == 'DR TDO': # Assumes 'DR TDI' comes before 'DR TDO'. + self.state = 'IDLE' diff --git a/libsigrokdecode4DSL/decoders/lfast/__init__.py b/libsigrokdecode4DSL/decoders/lfast/__init__.py new file mode 100644 index 00000000..681f4f9e --- /dev/null +++ b/libsigrokdecode4DSL/decoders/lfast/__init__.py @@ -0,0 +1,35 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2020 Soeren Apel +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +LFAST is a physical communication interface used mainly by the NXP Zipwire +interface. It's a framed asynchronous serial interface using differential +TX/RX pairs, capable of data rates of up to 320 MBit/s. + +This interface is also provided by Infineon as HSCT. + +As with most differential signals, it's sufficient to measure TXP or RXP, no +need for a differential probe. The REFCLK used by the hardware isn't needed by +this protocol decoder either. + +For details see https://www.nxp.com/docs/en/application-note/AN5134.pdf and +https://hitex.co.uk/fileadmin/uk-files/downloads/ShieldBuddy/tc27xD_um_v2.2.pdf +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/lfast/pd.py b/libsigrokdecode4DSL/decoders/lfast/pd.py new file mode 100644 index 00000000..7476e59a --- /dev/null +++ b/libsigrokdecode4DSL/decoders/lfast/pd.py @@ -0,0 +1,335 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2020 Soeren Apel +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd +from common.srdhelper import bitpack +import decimal + +''' +OUTPUT_PYTHON format: + +[ss, es, data] where data is a data byte of the LFAST payload. All bytes of +the payload are sent at once, each with its start and end sample. +''' + +# See tc27xD_um_v2.2.pdf, Table 20-10 +payload_sizes = { + 0b000: '8 bit', + 0b001: '32 bit / 4 byte', + 0b010: '64 bit / 8 byte', + 0b011: '96 bit / 12 byte', + 0b100: '128 bit / 16 byte', + 0b101: '256 bit / 32 byte', + 0b110: '512 bit / 64 byte', + 0b111: '288 bit / 36 byte' +} + +# See tc27xD_um_v2.2.pdf, Table 20-10 +payload_byte_sizes = { + 0b000: 1, + 0b001: 4, + 0b010: 8, + 0b011: 12, + 0b100: 16, + 0b101: 32, + 0b110: 64, + 0b111: 36 +} + +# See tc27xD_um_v2.2.pdf, Table 20-11 +channel_types = { + 0b0000: 'Interface Control / PING', + 0b0001: 'Unsolicited Status (32 bit)', + 0b0010: 'Slave Interface Control / Read', + 0b0011: 'CTS Transfer', + 0b0100: 'Data Channel A', + 0b0101: 'Data Channel B', + 0b0110: 'Data Channel C', + 0b0111: 'Data Channel D', + 0b1000: 'Data Channel E', + 0b1001: 'Data Channel F', + 0b1010: 'Data Channel G', + 0b1011: 'Data Channel H', + 0b1100: 'Reserved', + 0b1101: 'Reserved', + 0b1110: 'Reserved', + 0b1111: 'Reserved', +} + +# See tc27xD_um_v2.2.pdf, Table 20-12 +control_payloads = { + 0x00: 'PING', + 0x01: 'Reserved', + 0x02: 'Slave interface clock multiplier start', + 0x04: 'Slave interface clock multiplier stop', + 0x08: 'Use 5 MBaud for M->S', + 0x10: 'Use 320 MBaud for M->S', + 0x20: 'Use 5 MBaud for S->M', + 0x40: 'Use 20 MBaud for S->M (needs 20 MHz SysClk)', + 0x80: 'Use 320 MBaud for S->M', + 0x31: 'Enable slave interface transmitter', + 0x32: 'Disable slave interface transmitter', + 0x34: 'Enable clock test mode', + 0x38: 'Disable clock test mode and payload loopback', + 0xFF: 'Enable payload loopback', +} + + +ann_bit, ann_sync, ann_header_pl_size, ann_header_ch_type, ann_header_cts, \ + ann_payload, ann_control_data, ann_sleepbit, ann_warning = range(9) +state_sync, state_header, state_payload, state_sleepbit = range(4) + +class Decoder(srd.Decoder): + api_version = 3 + id = 'lfast' + name = 'LFAST' + longname = 'NXP LFAST interface' + desc = 'Differential high-speed P2P interface' + license = 'gplv2+' + inputs = ['logic'] + outputs = ['lfast'] + tags = ['Embedded/industrial'] + channels = ( + {'id': 'data', 'name': 'Data', 'desc': 'TXP or RXP'}, + ) + annotations = ( + ('bit', 'Bits'), + ('sync', 'Sync Pattern'), + ('header_pl_size', 'Payload Size'), + ('header_ch_type', 'Logical Channel Type'), + ('header_cts', 'Clear To Send'), + ('payload', 'Payload'), + ('ctrl_data', 'Control Data'), + ('sleep', 'Sleep Bit'), + ('warning', 'Warning'), + ) + annotation_rows = ( + ('bits', 'Bits', (ann_bit,)), + ('fields', 'Fields', (ann_sync, ann_header_pl_size, ann_header_ch_type, + ann_header_cts, ann_payload, ann_control_data, ann_sleepbit,)), + ('warnings', 'Warnings', (ann_warning,)), + ) + + def __init__(self): + decimal.getcontext().rounding = decimal.ROUND_HALF_UP + self.bit_len = 0xFFFFFFFF + self.reset() + + def reset(self): + self.prev_bit_len = self.bit_len + self.ss = self.es = 0 + self.ss_payload = self.es_payload = 0 + self.ss_byte = 0 + self.bits = [] + self.payload = [] + self.payload_size = 0 # Expected number of bytes, as read from header + self.bit_len = 0 # Length of one bit time, in samples + self.timeout = 0 # Desired timeout for next edge, in samples + self.ch_type_id = 0 # ID of channel type + self.state = state_sync + + def metadata(self, key, value): + pass + + def start(self): + self.out_python = self.register(srd.OUTPUT_PYTHON) + self.out_ann = self.register(srd.OUTPUT_ANN) + + def put_ann(self, ss, es, ann_class, value): + self.put(ss, es, self.out_ann, [ann_class, value]) + + def put_payload(self): + self.put(self.ss_payload, self.es_payload, self.out_python, self.payload) + + def handle_sync(self): + if len(self.bits) == 1: + self.ss_sync = self.ss_bit + + if len(self.bits) == 16: + value = bitpack(self.bits) + if value == 0xA84B: + self.put_ann(self.ss_sync, self.es_bit, ann_sync, ['Sync OK']) + else: + self.put_ann(self.ss_sync, self.es_bit, ann_warning, ['Wrong Sync Value: {:02X}'.format(value)]) + self.reset() + + # Only continue if we didn't just reset + if self.ss > 0: + self.bits = [] + self.state = state_header + self.timeout = int(9.4 * self.bit_len) + + def handle_header(self): + if len(self.bits) == 1: + self.ss_header = self.ss_bit + + if len(self.bits) == 8: + # See tc27xD_um_v2.2.pdf, Figure 20-47, for the header structure + bit_len = (self.es_bit - self.ss_header) / 8 + value = bitpack(self.bits) + + ss = self.ss_header + es = ss + 3 * bit_len + size_id = (value & 0xE0) >> 5 + size = payload_sizes.get(size_id) + self.payload_size = payload_byte_sizes.get(size_id) + self.put_ann(int(ss), int(es), ann_header_pl_size, [size]) + + ss = es + es = ss + 4 * bit_len + self.ch_type_id = (value & 0x1E) >> 1 + ch_type = channel_types.get(self.ch_type_id) + self.put_ann(int(ss), int(es), ann_header_ch_type, [ch_type]) + + ss = es + es = ss + bit_len + cts = value & 0x01 + self.put_ann(int(ss), int(es), ann_header_cts, ['{}'.format(cts)]) + + self.bits = [] + self.state = state_payload + self.timeout = int(9.4 * self.bit_len) + + def handle_payload(self): + self.timeout = int((self.payload_size - len(self.payload)) * 8 * self.bit_len) + + if len(self.bits) == 1: + self.ss_byte = self.ss_bit + if self.ss_payload == 0: + self.ss_payload = self.ss_bit + + if len(self.bits) == 8: + value = bitpack(self.bits) + value_hex = '{:02X}'.format(value) + + # Control transfers have no SIPI payload, show them as control transfers + # Check the channel_types list for the meaning of the magic values + if (self.ch_type_id >= 0b0100) and (self.ch_type_id <= 0b1011): + self.put_ann(self.ss_byte, self.es_bit, ann_payload, [value_hex]) + else: + # Control transfers are 8-bit transfers, so only evaluate the first byte + if len(self.payload) == 0: + ctrl_data = control_payloads.get(value, value_hex) + self.put_ann(self.ss_byte, self.es_bit, ann_control_data, [ctrl_data]) + else: + self.put_ann(self.ss_byte, self.es_bit, ann_control_data, [value_hex]) + + self.bits = [] + self.es_payload = self.es_bit + self.payload.append((self.ss_byte, self.es_payload, value)) + + if (len(self.payload) == self.payload_size): + self.timeout = int(1.4 * self.bit_len) + self.state = state_sleepbit + + def handle_sleepbit(self): + if len(self.bits) == 0: + self.put_ann(self.ss_bit, self.es_bit, ann_sleepbit, ['No LVDS sleep mode request', 'No sleep', 'N']) + elif len(self.bits) > 1: + self.put_ann(self.ss_bit, self.es_bit, ann_warning, ['Expected only the sleep bit, got {} bits instead'.format(len(self.bits))]) + else: + if self.bits[0] == 1: + self.put_ann(self.ss_bit, self.es_bit, ann_sleepbit, ['LVDS sleep mode request', 'Sleep', 'Y']) + else: + self.put_ann(self.ss_bit, self.es_bit, ann_sleepbit, ['No LVDS sleep mode request', 'No sleep', 'N']) + + # We only send the payload out if this is an actual data transfer; + # check the channel_types list for the meaning of the magic values + if (self.ch_type_id >= 0b0100) and (self.ch_type_id <= 0b1011): + if len(self.payload) > 0: + self.put_payload() + + def decode(self): + while True: + if self.timeout == 0: + rising_edge, = self.wait({0: 'e'}) + else: + rising_edge, = self.wait([{0: 'e'}, {'skip': self.timeout}]) + + # If this is the first edge, we only update ss + if self.ss == 0: + self.ss = self.samplenum + # Let's set the timeout for the sync pattern as well + self.timeout = int(16.2 * self.prev_bit_len) + continue + + self.es = self.samplenum + + # Check for the sleep bit if this is a timeout condition + if (len(self.matched) == 2) and self.matched[1]: + rising_edge = ~rising_edge + if self.state == state_sync: + self.reset() + continue + elif self.state == state_sleepbit: + self.ss_bit += self.bit_len + self.es_bit = self.ss_bit + self.bit_len + self.handle_sleepbit() + self.reset() + continue + + # Shouldn't happen but we check just in case + if int(self.es - self.ss) == 0: + continue + + # We use the first bit to deduce the bit length + if self.bit_len == 0: + self.bit_len = self.es - self.ss + + # Determine number of bits covered by this edge + bit_count = (self.es - self.ss) / self.bit_len + bit_count = int(decimal.Decimal(bit_count).to_integral_value()) + + if bit_count == 0: + self.put_ann(self.ss, self.es, ann_warning, ['Bit time too short']) + self.reset() + continue + + bit_value = '0' if rising_edge else '1' + + divided_len = (self.es - self.ss) / bit_count + for i in range(bit_count): + self.ss_bit = int(self.ss + i * divided_len) + self.es_bit = int(self.ss_bit + divided_len) + self.put_ann(self.ss_bit, self.es_bit, ann_bit, [bit_value]) + + # Place the new bit at the front of the bit list + self.bits.insert(0, (0 if rising_edge else 1)) + + if self.state == state_sync: + self.handle_sync() + elif self.state == state_header: + self.handle_header() + elif self.state == state_payload: + self.handle_payload() + elif self.state == state_sleepbit: + self.handle_sleepbit() + self.reset() + + if self.ss == 0: + break # Because reset() was called, invalidating everything + + # Only update ss if we didn't just perform a reset + if self.ss > 0: + self.ss = self.samplenum + + # If we got here when a timeout occurred, we have processed all null + # bits that we could and should reset now to find the next packet + if (len(self.matched) == 2) and self.matched[1]: + self.reset() diff --git a/libsigrokdecode4DSL/decoders/lin/__init__.py b/libsigrokdecode4DSL/decoders/lin/__init__.py new file mode 100644 index 00000000..f5b2835f --- /dev/null +++ b/libsigrokdecode4DSL/decoders/lin/__init__.py @@ -0,0 +1,28 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2018 Stephan Thiele +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This decoder stacks on top of the 'uart' PD and decodes the LIN +(Local Interconnect Network) protocol. + +LIN is layered on top of the UART (async serial) protocol, with 8n1 settings. +Bytes are sent LSB-first. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/lin/pd.py b/libsigrokdecode4DSL/decoders/lin/pd.py new file mode 100644 index 00000000..c6db6787 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/lin/pd.py @@ -0,0 +1,235 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2018 Stephan Thiele +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd + +class LinFsm: + class State: + WaitForBreak = 'WAIT_FOR_BREAK' + Sync = 'SYNC' + Pid = 'PID' + Data = 'DATA' + Checksum = 'CHECKSUM' + Error = 'ERROR' + + def transit(self, target_state): + if not self._transition_allowed(target_state): + return False + self.state = target_state + return True + + def _transition_allowed(self, target_state): + if target_state == LinFsm.State.Error: + return True + return target_state in self.allowed_state[self.state] + + def reset(self): + self.state = LinFsm.State.WaitForBreak + + def __init__(self): + a = dict() + a[LinFsm.State.WaitForBreak] = (LinFsm.State.Sync,) + a[LinFsm.State.Sync] = (LinFsm.State.Pid,) + a[LinFsm.State.Pid] = (LinFsm.State.Data,) + a[LinFsm.State.Data] = (LinFsm.State.Data, LinFsm.State.Checksum) + a[LinFsm.State.Checksum] = (LinFsm.State.WaitForBreak,) + a[LinFsm.State.Error] = (LinFsm.State.Sync,) + self.allowed_state = a + + self.state = None + self.reset() + +class Decoder(srd.Decoder): + api_version = 3 + id = 'lin' + name = 'LIN' + longname = 'Local Interconnect Network' + desc = 'Local Interconnect Network (LIN) protocol.' + license = 'gplv2+' + inputs = ['uart'] + outputs = [] + tags = ['Automotive'] + options = ( + {'id': 'version', 'desc': 'Protocol version', 'default': 2, 'values': (1, 2)}, + ) + annotations = ( + ('data', 'LIN data'), + ('control', 'Protocol info'), + ('error', 'Error descriptions'), + ('inline_error', 'Protocol violations and errors'), + ) + annotation_rows = ( + ('data', 'Data', (0, 1, 3)), + ('error', 'Error', (2,)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.fsm = LinFsm() + self.lin_header = [] + self.lin_rsp = [] + self.lin_version = None + self.out_ann = None + self.ss_block = None + self.es_block = None + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + self.lin_version = self.options['version'] + + def putx(self, data): + self.put(self.ss_block, self.es_block, self.out_ann, data) + + def wipe_break_null_byte(self, value): + # Upon a break condition a null byte is received which must be ignored. + if self.fsm.state not in (LinFsm.State.WaitForBreak, LinFsm.State.Error): + if len(self.lin_rsp): + value = self.lin_rsp.pop()[2] + else: + self.lin_header.pop() + + if value != 0: + self.fsm.transit(LinFsm.State.Error) + self.handle_error(None) + return False + + return True + + def handle_wait_for_break(self, value): + self.wipe_break_null_byte(value) + + def handle_break(self, value): + if self.fsm.state not in (LinFsm.State.WaitForBreak, LinFsm.State.Error): + if self.wipe_break_null_byte(value): + self.fsm.transit(LinFsm.State.Checksum) + self.handle_checksum() + + self.fsm.reset() + self.fsm.transit(LinFsm.State.Sync) + + self.putx([1, ['Break condition', 'Break', 'Brk', 'B']]) + + def handle_sync(self, value): + self.fsm.transit(LinFsm.State.Pid) + self.lin_header.append((self.ss_block, self.es_block, value)) + + def handle_pid(self, value): + self.fsm.transit(LinFsm.State.Data) + self.lin_header.append((self.ss_block, self.es_block, value)) + + def handle_data(self, value): + self.lin_rsp.append((self.ss_block, self.es_block, value)) + + def handle_checksum(self): + sync = self.lin_header.pop(0) if len(self.lin_header) else None + + self.put(sync[0], sync[1], self.out_ann, [0, ['Sync', 'S']]) + + if sync[2] != 0x55: + self.put(sync[0], sync[1], self.out_ann, + [2, ['Sync is not 0x55', 'Not 0x55', '!= 0x55']]) + + pid = self.lin_header.pop(0) if len(self.lin_header) else None + checksum = self.lin_rsp.pop() if len(self.lin_rsp) else None + + if pid: + id_ = pid[2] & 0x3F + parity = pid[2] >> 6 + + expected_parity = self.calc_parity(pid[2]) + parity_valid = parity == expected_parity + + if not parity_valid: + self.put(pid[0], pid[1], self.out_ann, [2, ['P != %d' % expected_parity]]) + + ann_class = 0 if parity_valid else 3 + self.put(pid[0], pid[1], self.out_ann, [ann_class, [ + 'ID: %02X Parity: %d (%s)' % (id_, parity, 'ok' if parity_valid else 'bad'), + 'ID: 0x%02X' % id_, 'I: %d' % id_ + ]]) + + if len(self.lin_rsp): + checksum_valid = self.checksum_is_valid(pid[2], self.lin_rsp, checksum[2]) + + for b in self.lin_rsp: + self.put(b[0], b[1], self.out_ann, [0, ['Data: 0x%02X' % b[2], 'D: 0x%02X' % b[2]]]) + + ann_class = 0 if checksum_valid else 3 + self.put(checksum[0], checksum[1], self.out_ann, + [ann_class, ['Checksum: 0x%02X' % checksum[2], 'Checksum', 'Chk', 'C']]) + + if not checksum_valid: + self.put(checksum[0], checksum[1], self.out_ann, [2, ['Checksum invalid']]) + else: + pass # No response. + + self.lin_header.clear() + self.lin_rsp.clear() + + def handle_error(self, dummy): + self.putx([3, ['Error', 'Err', 'E']]) + + def checksum_is_valid(self, pid, data, checksum): + if self.lin_version == 2: + id_ = pid & 0x3F + + if id_ != 60 and id_ != 61: + checksum += pid + + for d in data: + checksum += d[2] + + carry_bits = int(checksum / 256) + checksum += carry_bits + + return checksum & 0xFF == 0xFF + + @staticmethod + def calc_parity(pid): + id_ = [((pid & 0x3F) >> i) & 1 for i in range(8)] + + p0 = id_[0] ^ id_[1] ^ id_[2] ^ id_[4] + p1 = not (id_[1] ^ id_[3] ^ id_[4] ^ id_[5]) + + return (p0 << 0) | (p1 << 1) + + def decode(self, ss, es, data): + ptype, rxtx, pdata = data + + self.ss_block, self.es_block = ss, es + + # Ignore all UART packets except the actual data packets or BREAK. + if ptype == 'BREAK': + self.handle_break(pdata) + if ptype != 'DATA': + return + + # We're only interested in the byte value (not individual bits). + pdata = pdata[0] + + # Short LIN overview: + # - Message begins with a BREAK (0x00) for at least 13 bittimes. + # - Break is always followed by a SYNC byte (0x55). + # - Sync byte is followed by a PID byte (Protected Identifier). + # - PID byte is followed by 1 - 8 data bytes and a final checksum byte. + + handler = getattr(self, 'handle_%s' % self.fsm.state.lower()) + handler(pdata) diff --git a/libsigrokdecode4DSL/decoders/lm75/__init__.py b/libsigrokdecode4DSL/decoders/lm75/__init__.py new file mode 100644 index 00000000..83ce811b --- /dev/null +++ b/libsigrokdecode4DSL/decoders/lm75/__init__.py @@ -0,0 +1,25 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2012 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This decoder stacks on top of the 'i2c' PD and decodes the National LM75 +(and compatibles) temperature sensor protocol. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/lm75/pd.py b/libsigrokdecode4DSL/decoders/lm75/pd.py new file mode 100644 index 00000000..14df1b52 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/lm75/pd.py @@ -0,0 +1,186 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2012 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +# TODO: Better support for various LM75 compatible devices. + +import sigrokdecode as srd + +# LM75 only supports 9 bit resolution, compatible devices usually 9-12 bits. +resolution = { + # CONFIG[6:5]: + 0x00: 9, + 0x01: 10, + 0x02: 11, + 0x03: 12, +} + +ft = { + # CONFIG[4:3]: + 0x00: 1, + 0x01: 2, + 0x02: 4, + 0x03: 6, +} + +class Decoder(srd.Decoder): + api_version = 3 + id = 'lm75' + name = 'LM75' + longname = 'National LM75' + desc = 'National LM75 (and compatibles) temperature sensor.' + license = 'gplv2+' + inputs = ['i2c'] + outputs = [] + tags = ['Sensor'] + options = ( + {'id': 'sensor', 'desc': 'Sensor type', 'default': 'lm75', + 'values': ('lm75',)}, + {'id': 'resolution', 'desc': 'Resolution (bits)', 'default': 9, + 'values': (9, 10, 11, 12)}, + ) + annotations = ( + ('celsius', 'Temperature in degrees Celsius'), + ('kelvin', 'Temperature in Kelvin'), + ('text-verbose', 'Human-readable text (verbose)'), + ('text', 'Human-readable text'), + ('warnings', 'Human-readable warnings'), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.state = 'IDLE' + self.reg = 0x00 # Currently selected register + self.databytes = [] + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def putx(self, data): + # Helper for annotations which span exactly one I²C packet. + self.put(self.ss, self.es, self.out_ann, data) + + def putb(self, data): + # Helper for annotations which span a block of I²C packets. + self.put(self.ss_block, self.es_block, self.out_ann, data) + + def warn_upon_invalid_slave(self, addr): + # LM75 and compatible devices have a 7-bit I²C slave address where + # the 4 MSBs are fixed to 1001, and the 3 LSBs are configurable. + # Thus, valid slave addresses (1001xxx) range from 0x48 to 0x4f. + if addr not in range(0x48, 0x4f + 1): + s = 'Warning: I²C slave 0x%02x not an LM75 compatible sensor.' + self.putx([4, [s % addr]]) + + def output_temperature(self, s, rw): + # TODO: Support for resolutions other than 9 bit. + before, after = self.databytes[0], (self.databytes[1] >> 7) * 5 + celsius = float('%d.%d' % (before, after)) + kelvin = celsius + 273.15 + self.putb([0, ['%s: %.1f °C' % (s, celsius)]]) + self.putb([1, ['%s: %.1f K' % (s, kelvin)]]) + + # Warn about the temperature register (0x00) being read-only. + if s == 'Temperature' and rw == 'WRITE': + s = 'Warning: The temperature register is read-only!' + self.putb([4, [s]]) + + def handle_temperature_reg(self, b, s, rw): + # Common helper for the temperature/T_HYST/T_OS registers. + if len(self.databytes) == 0: + self.ss_block = self.ss + self.databytes.append(b) + return + self.databytes.append(b) + self.es_block = self.es + self.output_temperature(s, rw) + self.databytes = [] + + def handle_reg_0x00(self, b, rw): + # Temperature register (16bits, read-only). + self.handle_temperature_reg(b, 'Temperature', rw) + + def handle_reg_0x01(self, b, rw): + # Configuration register (8 bits, read/write). + # TODO: Bit-exact annotation ranges. + + sd = b & (1 << 0) + tmp = 'normal operation' if (sd == 0) else 'shutdown mode' + s = 'SD = %d: %s\n' % (sd, tmp) + s2 = 'SD = %s, ' % tmp + + cmp_int = b & (1 << 1) + tmp = 'comparator' if (cmp_int == 0) else 'interrupt' + s += 'CMP/INT = %d: %s mode\n' % (cmp_int, tmp) + s2 += 'CMP/INT = %s, ' % tmp + + pol = b & (1 << 2) + tmp = 'low' if (pol == 0) else 'high' + s += 'POL = %d: OS polarity is active-%s\n' % (pol, tmp) + s2 += 'POL = active-%s, ' % tmp + + bits = (b & ((1 << 4) | (1 << 3))) >> 3 + s += 'Fault tolerance setting: %d bit(s)\n' % ft[bits] + s2 += 'FT = %d' % ft[bits] + + # Not supported by LM75, but by various compatible devices. + if self.options['sensor'] != 'lm75': # TODO + bits = (b & ((1 << 6) | (1 << 5))) >> 5 + s += 'Resolution: %d bits\n' % resolution[bits] + s2 += ', resolution = %d' % resolution[bits] + + self.putx([2, [s]]) + self.putx([3, [s2]]) + + def handle_reg_0x02(self, b, rw): + # T_HYST register (16 bits, read/write). + self.handle_temperature_reg(b, 'T_HYST trip temperature', rw) + + def handle_reg_0x03(self, b, rw): + # T_OS register (16 bits, read/write). + self.handle_temperature_reg(b, 'T_OS trip temperature', rw) + + def decode(self, ss, es, data): + cmd, databyte = data + + # Store the start/end samples of this I²C packet. + self.ss, self.es = ss, es + + # State machine. + if self.state == 'IDLE': + # Wait for an I²C START condition. + if cmd != 'START': + return + self.state = 'GET SLAVE ADDR' + elif self.state == 'GET SLAVE ADDR': + # Wait for an address read/write operation. + if cmd in ('ADDRESS READ', 'ADDRESS WRITE'): + self.warn_upon_invalid_slave(databyte) + self.state = cmd[8:] + ' REGS' # READ REGS / WRITE REGS + elif self.state in ('READ REGS', 'WRITE REGS'): + if cmd in ('DATA READ', 'DATA WRITE'): + handle_reg = getattr(self, 'handle_reg_0x%02x' % self.reg) + handle_reg(databyte, cmd[5:]) # READ / WRITE + elif cmd == 'STOP': + # TODO: Any output? + self.state = 'IDLE' + else: + # self.putx([0, ['Ignoring: %s (data=%s)' % (cmd, databyte)]]) + pass diff --git a/libsigrokdecode4DSL/decoders/lpc/__init__.py b/libsigrokdecode4DSL/decoders/lpc/__init__.py new file mode 100644 index 00000000..52277587 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/lpc/__init__.py @@ -0,0 +1,25 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2012 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +LPC (Low Pin Count) is a protocol for low-bandwidth devices used on +some PC mainboards, such as the "BIOS chip" or the so-called "Super I/O". +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/lpc/pd.py b/libsigrokdecode4DSL/decoders/lpc/pd.py new file mode 100644 index 00000000..6ae13f4a --- /dev/null +++ b/libsigrokdecode4DSL/decoders/lpc/pd.py @@ -0,0 +1,547 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2012-2013 Uwe Hermann +## Copyright (C) 2019 DreamSourceLab +## Copyright (C) 2020 Raptor Engineering, LLC +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd + +# ... +fields = { + # START field (indicates start or stop of a transaction) + 'START': { + 0b0000: 'Start of cycle for a target', + 0b0001: 'Reserved', + 0b0010: 'Grant for bus master 0', + 0b0011: 'Grant for bus master 1', + 0b0100: 'Reserved', + 0b0101: 'TPM', + 0b0110: 'Reserved', + 0b0111: 'Reserved', + 0b1000: 'Reserved', + 0b1001: 'Reserved', + 0b1010: 'Reserved', + 0b1011: 'Reserved', + 0b1100: 'Reserved', + 0b1101: 'Start of cycle for a Firmware Memory Read cycle', + 0b1110: 'Start of cycle for a Firmware Memory Write cycle', + 0b1111: 'Stop/abort (end of a cycle for a target)', + }, + # Cycle type / direction field + # Bit 0 (LAD[0]) is unused, should always be 0. + # Neither host nor peripheral are allowed to drive 0b11x0. + 'CT_DR': { + 0b0000: 'I/O read', + 0b0001: 'I/O read', + 0b0010: 'I/O write', + 0b0011: 'I/O write', + 0b0100: 'Memory read', + 0b0101: 'Memory read', + 0b0110: 'Memory write', + 0b0111: 'Memory write', + 0b1000: 'DMA read', + 0b1001: 'DMA read', + 0b1010: 'DMA write', + 0b1011: 'DMA write', + 0b1100: 'Reserved / not allowed', + 0b1101: 'Reserved / not allowed', + 0b1110: 'Reserved / not allowed', + 0b1111: 'Reserved / not allowed', + }, + # Cycle type / direction field + # False for read cycle, True for write cycle + 'CT_DR_WR': { + 0b0000: False, + 0b0001: False, + 0b0010: True, + 0b0011: True, + 0b0100: False, + 0b0101: False, + 0b0110: True, + 0b0111: True, + 0b1000: False, + 0b1001: False, + 0b1010: True, + 0b1011: True, + 0b1100: False, + 0b1101: False, + 0b1110: False, + 0b1111: False, + }, + # SIZE field (determines how many bytes are to be transferred) + # Bits[3:2] are reserved, must be driven to 0b00. + # Neither host nor peripheral are allowed to drive 0b0010. + 'SIZE': { + 0b0000: '8 bits (1 byte)', + 0b0001: '16 bits (2 bytes)', + 0b0010: 'Reserved / not allowed', + 0b0011: '32 bits (4 bytes)', + }, + # CHANNEL field (bits[2:0] contain the DMA channel number) + 'CHANNEL': { + 0b0000: '0', + 0b0001: '1', + 0b0010: '2', + 0b0011: '3', + 0b0100: '4', + 0b0101: '5', + 0b0110: '6', + 0b0111: '7', + }, + # SYNC field (used to add wait states) + 'SYNC': { + 0b0000: 'Ready', + 0b0001: 'Reserved', + 0b0010: 'Reserved', + 0b0011: 'Reserved', + 0b0100: 'Reserved', + 0b0101: 'Short wait', + 0b0110: 'Long wait', + 0b0111: 'Reserved', + 0b1000: 'Reserved', + 0b1001: 'Ready more (DMA only)', + 0b1010: 'Error', + 0b1011: 'Reserved', + 0b1100: 'Reserved', + 0b1101: 'Reserved', + 0b1110: 'Reserved', + 0b1111: 'Reserved', + }, +} + +class Decoder(srd.Decoder): + api_version = 3 + id = 'lpc' + name = 'LPC' + longname = 'Low Pin Count' + desc = 'Protocol for low-bandwidth devices on PC mainboards.' + license = 'gplv2+' + inputs = ['logic'] + outputs = [] + tags = ['PC'] + channels = ( + {'id': 'lframe', 'name': 'LFRAME#', 'desc': 'Frame'}, + {'id': 'lclk', 'name': 'LCLK', 'desc': 'Clock'}, + {'id': 'lad0', 'name': 'LAD[0]', 'desc': 'Addr/control/data 0'}, + {'id': 'lad1', 'name': 'LAD[1]', 'desc': 'Addr/control/data 1'}, + {'id': 'lad2', 'name': 'LAD[2]', 'desc': 'Addr/control/data 2'}, + {'id': 'lad3', 'name': 'LAD[3]', 'desc': 'Addr/control/data 3'}, + ) + optional_channels = ( + {'id': 'lreset', 'name': 'LRESET#', 'desc': 'Reset'}, + {'id': 'ldrq', 'name': 'LDRQ#', 'desc': 'Encoded DMA / bus master request'}, + {'id': 'serirq', 'name': 'SERIRQ', 'desc': 'Serialized IRQ'}, + {'id': 'clkrun', 'name': 'CLKRUN#', 'desc': 'Clock run'}, + {'id': 'lpme', 'name': 'LPME#', 'desc': 'LPC power management event'}, + {'id': 'lpcpd', 'name': 'LPCPD#', 'desc': 'Power down'}, + {'id': 'lsmi', 'name': 'LSMI#', 'desc': 'System Management Interrupt'}, + ) + annotations = ( + ('warnings', 'Warnings'), + ('start', 'Start'), + ('cycle-type', 'Cycle-type/direction'), + ('addr', 'Address'), + ('tar1', 'Turn-around cycle 1'), + ('sync', 'Sync'), + ('timeout', 'Time Out'), + ('data', 'Data'), + ('tar2', 'Turn-around cycle 2'), + ) + annotation_rows = ( + ('data', 'Data', (1, 2, 3, 4, 5, 6, 7, 8)), + ('warnings', 'Warnings', (0,)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.state = 'IDLE' + self.oldlclk = -1 + self.samplenum = 0 + self.lad = -1 + self.addr = 0 + self.direction = 0 + self.cur_nibble = 0 + self.cycle_type = -1 + self.databyte = 0 + self.tarcount = 0 + self.synccount = 0 + self.timeoutcount = 0 + self.oldpins = None + self.ss_block = self.es_block = None + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def putb(self, data): + self.put(self.ss_block, self.es_block, self.out_ann, data) + + def handle_get_start(self, lframe): + # LAD[3:0]: START field (1 clock cycle). + + # The last value of LAD[3:0] before LFRAME# gets de-asserted is what + # the peripherals must use. However, the host can keep LFRAME# asserted + # multiple clocks, and we output all START fields that occur, even + # though the peripherals are supposed to ignore all but the last one. + self.es_block = self.samplenum + self.putb([1, [fields['START'][self.oldlad], 'START', 'St', 'S']]) + self.ss_block = self.samplenum + + + # LFRAME# is asserted (low). Wait until it gets de-asserted again + # (the host is allowed to keep it asserted multiple clocks). + if lframe != 1: + return + + if (self.oldlad == 0b0000 or self.oldlad == 0b0101): + self.start_field = self.oldlad + self.state = 'GET CT/DR' + elif (self.oldlad == 0b1101 or self.oldlad == 0b1110): + self.start_field = self.oldlad + if (self.oldlad == 0b1110): + self.direction = True + else: + self.direction = False + self.state = 'GET FW IDSEL' + else: + self.state = 'IDLE' + + def handle_get_ct_dr(self): + # LAD[3:0]: Cycle type / direction field (1 clock cycle). + + self.cycle_type = fields['CT_DR'][self.oldlad] + self.direction = fields['CT_DR_WR'][self.oldlad] + + # TODO: Warning/error on invalid cycle types. + if self.cycle_type == 'Reserved': + self.putb([0, ['Invalid cycle type (%s)' % self.oldlad_bits]]) + + self.es_block = self.samplenum + self.putb([2, ['Cycle type: %s' % self.cycle_type]]) + self.ss_block = self.samplenum + + self.state = 'GET ADDR' + self.addr = 0 + self.cur_nibble = 0 + + def handle_get_fw_idsel(self): + # LAD[3:0]: IDSEL field (1 clock cycle). + self.es_block = self.samplenum + s = 'IDSEL: 0x%%0%dx' % self.oldlad + self.putb([3, [s % self.oldlad]]) + self.ss_block = self.samplenum + + self.state = 'GET FW ADDR' + self.addr = 0 + self.cur_nibble = 0 + + def handle_get_fw_addr(self): + # LAD[3:0]: ADDR field (7 clock cycles). + addr_nibbles = 7 # Address is 28bits. + + # Addresses are driven MSN-first. + offset = ((addr_nibbles - 1) - self.cur_nibble) * 4 + if (offset < 0): + self.putb([0, ['Warning: Invalid address shift: %d' % offset]]) + self.state = 'IDLE' + return + self.addr |= (self.oldlad << offset) + + # Continue if we haven't seen all ADDR cycles, yet. + if (self.cur_nibble < addr_nibbles - 1): + self.cur_nibble += 1 + return + + self.es_block = self.samplenum + s = 'Address: 0x%%0%dx' % addr_nibbles + self.putb([3, [s % self.addr]]) + self.ss_block = self.samplenum + + self.state = 'GET FW MSIZE' + + def handle_get_fw_msize(self): + # LAD[3:0]: MSIZE field (1 clock cycle). + self.es_block = self.samplenum + s = 'MSIZE: 0x%%0%dx' % self.oldlad + self.putb([3, [s % self.oldlad]]) + self.ss_block = self.samplenum + self.msize = self.oldlad + + if self.direction == 1: + self.state = 'GET FW DATA' + self.cycle_count = 0 + self.dataword = 0 + self.cur_nibble = 0 + else: + self.state = 'GET TAR' + self.tar_count = 0 + + def handle_get_addr(self): + # LAD[3:0]: ADDR field (4/8/0 clock cycles). + + # I/O cycles: 4 ADDR clocks. Memory cycles: 8 ADDR clocks. + # DMA cycles: no ADDR clocks at all. + if self.cycle_type in ('I/O read', 'I/O write'): + addr_nibbles = 4 # Address is 16bits. + elif self.cycle_type in ('Memory read', 'Memory write'): + addr_nibbles = 8 # Address is 32bits. + else: + addr_nibbles = 0 # TODO: How to handle later on? + + # Addresses are driven MSN-first. + offset = ((addr_nibbles - 1) - self.cur_nibble) * 4 + if (offset < 0): + self.putb([0, ['Warning: Invalid address shift: %d' % offset]]) + self.state = 'IDLE' + return + self.addr |= (self.oldlad << offset) + + # Continue if we haven't seen all ADDR cycles, yet. + if (self.cur_nibble < addr_nibbles - 1): + self.cur_nibble += 1 + return + + self.es_block = self.samplenum + s = 'Address: 0x%%0%dx' % addr_nibbles + self.putb([3, [s % self.addr]]) + self.ss_block = self.samplenum + + if self.direction == 1: + self.state = 'GET DATA' + self.cycle_count = 0 + else: + self.state = 'GET TAR' + self.tar_count = 0 + + def handle_get_tar(self): + # LAD[3:0]: First TAR (turn-around) field (2 clock cycles). + + self.es_block = self.samplenum + self.putb([4, ['TAR, cycle %d: %s' % (self.tarcount, self.oldlad_bits)]]) + self.ss_block = self.samplenum + + # On the first TAR clock cycle LAD[3:0] is driven to 1111 by + # either the host or peripheral. On the second clock cycle, + # the host or peripheral tri-states LAD[3:0], but its value + # should still be 1111, due to pull-ups on the LAD lines. + if self.oldlad_bits != '1111': + self.putb([0, ['TAR, cycle %d: %s (expected 1111)' % \ + (self.tarcount, self.oldlad_bits)]]) + + if (self.tarcount != 1): + self.tarcount += 1 + return + + self.tarcount = 0 + self.state = 'GET SYNC' + + def handle_get_sync(self, lframe): + # LAD[3:0]: SYNC field (1-n clock cycles). + + self.sync_val = self.oldlad_bits + self.cycle_type = fields['SYNC'][self.oldlad] + + self.es_block = self.samplenum + # TODO: Warnings if reserved value are seen? + if self.cycle_type == 'Reserved': + self.putb([0, ['SYNC, cycle %d: %s (reserved value)' % \ + (self.synccount, self.sync_val)]]) + + self.es_block = self.samplenum + self.putb([5, ['SYNC, cycle %d: %s' % (self.synccount, self.sync_val)]]) + self.ss_block = self.samplenum + + # TODO + if (self.cycle_type != 'Short wait' and self.cycle_type != 'Long wait'): + self.cycle_count = 0 + if (lframe == 0): + self.state = 'GET TIMEOUT' + elif (self.start_field == 0b1101 or self.start_field == 0b1110): + self.state = 'GET FW DATA' + self.cycle_count = 0 + self.dataword = 0 + self.cur_nibble = 0 + else: + self.state = 'GET DATA' + + def handle_get_timeout(self): + # LFRAME#: tie low (4 clock cycles). + + if (self.oldlframe != 0): + self.putb([0, ['TIMEOUT cycle, LFRAME# must be low for 4 LCLk cycles']]) + self.timeoutcount = 0 + self.state = 'IDLE' + return + + self.es_block = self.samplenum + self.putb([6, ['Timeout %d' % self.timeoutcount]]) + self.ss_block = self.samplenum + + if (self.timeoutcount != 3): + self.timeoutcount += 1 + return + + self.timeoutcount = 0 + self.state = 'IDLE' + + def handle_get_fw_data(self): + # LAD[3:0]: DATA field + if (self.msize == 0b0000): + data_nibbles = 2 # Data is 8bits. + elif (self.msize == 0b0001): + data_nibbles = 4 # Data is 16bits. + elif (self.msize == 0b0010): + data_nibbles = 8 # Data is 32bits. + elif (self.msize == 0b0100): + data_nibbles = 32 # Data is 128bits. + elif (self.msize == 0b0111): + data_nibbles = 256 # Data is 1024bits. + else: + self.putb([0, ['Warning: Invalid MSIZE: %d' % self.msize]]) + self.state = 'IDLE' + return + + # Data is driven LSN-first. + nibble_swap = self.cur_nibble % 2 + offset = ((data_nibbles - 1) - self.cur_nibble) * 4 + if (nibble_swap): + offset += 4 + else: + offset -= 4 + if (offset < 0): + self.putb([0, ['Warning: Invalid data shift: %d' % offset]]) + self.state = 'IDLE' + return + self.dataword |= (self.oldlad << offset) + + # Continue if we haven't seen all DATA cycles, yet. + if (self.cur_nibble < data_nibbles - 1): + self.cur_nibble += 1 + return + + self.es_block = self.samplenum + s = 'DATA: 0x%%0%dx' % data_nibbles + self.putb([3, [s % self.dataword]]) + self.ss_block = self.samplenum + + self.cycle_count = 0 + self.state = 'GET TAR2' + + def handle_get_data(self): + # LAD[3:0]: DATA field (2 clock cycles). + + # Data is driven LSN-first. + if (self.cycle_count == 0): + self.databyte = self.oldlad + elif (self.cycle_count == 1): + self.databyte |= (self.oldlad << 4) + else: + self.putb([0, ['Warning: Invalid cycle_count: %d' % self.cycle_count]]) + self.state = 'IDLE' + return + + if (self.cycle_count != 1): + self.cycle_count += 1 + return + + self.es_block = self.samplenum + self.putb([7, ['DATA: 0x%02x' % self.databyte]]) + self.ss_block = self.samplenum + + self.cycle_count = 0 + self.state = 'GET TAR2' + + def handle_get_tar2(self): + # LAD[3:0]: Second TAR field (2 clock cycles). + + self.es_block = self.samplenum + self.putb([8, ['TAR, cycle %d: %s' % (self.tarcount, self.oldlad_bits)]]) + self.ss_block = self.samplenum + + # On the first TAR clock cycle LAD[3:0] is driven to 1111 by + # either the host or peripheral. On the second clock cycle, + # the host or peripheral tri-states LAD[3:0], but its value + # should still be 1111, due to pull-ups on the LAD lines. + if self.oldlad_bits != '1111': + self.putb([0, ['Warning: TAR, cycle %d: %s (expected 1111)' + % (self.tarcount, self.oldlad_bits)]]) + + if (self.tarcount != 1): + self.tarcount += 1 + return + + self.tarcount = 0 + self.state = 'IDLE' + + def decode(self): + while True: + + # Only look at the signals upon rising LCLK edges. The LPC clock + # is the same as the PCI clock (which is sampled at rising edges). + (lframe, lclk, lad0, lad1, lad2, lad3, lreset, ldrq, serirq, clkrun, lpme, lpcpd, lsmi) = self.wait({1: 'r'}) + + # Store LAD[3:0] bit values (one nibble) in local variables. + # Most (but not all) states need this. + lad = (lad3 << 3) | (lad2 << 2) | (lad1 << 1) | lad0 + lad_bits = '{:04b}'.format(lad) + # self.putb([0, ['LAD: %s' % lad_bits]]) + + # TODO: Only memory read/write is currently supported/tested. + + # Detect host cycle abort requests + if (lframe == 0) and (self.oldlframe == 0): + self.state = 'GET TIMEOUT' + + # State machine + if self.state == 'IDLE': + # A valid LPC cycle starts with LFRAME# being asserted (low). + if lframe == 0: + self.ss_block = self.samplenum + self.state = 'GET START' + self.lad = -1 + else: + self.wait({0: 'f'}) + elif self.state == 'GET START': + self.handle_get_start(lframe) + elif self.state == 'GET CT/DR': + self.handle_get_ct_dr() + elif self.state == 'GET ADDR': + self.handle_get_addr() + elif self.state == 'GET FW IDSEL': + self.handle_get_fw_idsel() + elif self.state == 'GET FW ADDR': + self.handle_get_fw_addr() + elif self.state == 'GET FW MSIZE': + self.handle_get_fw_msize() + elif self.state == 'GET TAR': + self.handle_get_tar() + elif self.state == 'GET SYNC': + self.handle_get_sync(lframe) + elif self.state == 'GET TIMEOUT': + self.handle_get_timeout() + elif self.state == 'GET FW DATA': + self.handle_get_fw_data() + elif self.state == 'GET DATA': + self.handle_get_data() + elif self.state == 'GET TAR2': + self.handle_get_tar2() + + self.oldlframe = lframe + self.oldlad = lad + self.oldlad_bits = lad_bits diff --git a/libsigrokdecode4DSL/decoders/ltc242x/__init__.py b/libsigrokdecode4DSL/decoders/ltc242x/__init__.py new file mode 100644 index 00000000..43d9d346 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/ltc242x/__init__.py @@ -0,0 +1,25 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2020 Analog Devices Inc. +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 3 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This decoder stacks on top of the 'spi' PD and decodes the +Linear Technology LTC2421/LTC2422 protocol. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/ltc242x/pd.py b/libsigrokdecode4DSL/decoders/ltc242x/pd.py new file mode 100644 index 00000000..27ae5b99 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/ltc242x/pd.py @@ -0,0 +1,86 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2020 Analog Devices Inc. +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 3 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd + +input_voltage_format = ['%.6fV', '%.2fV'] + +class Decoder(srd.Decoder): + api_version = 3 + id = 'ltc242x' + name = 'LTC242x' + longname = 'Linear Technology LTC242x' + desc = 'Linear Technology LTC2421/LTC2422 1-/2-channel 20-bit ADC.' + license = 'gplv2+' + inputs = ['spi'] + outputs = [] + tags = ['IC', 'Analog/digital'] + annotations = ( + ('ch0_voltage', 'CH0 voltage'), + ('ch1_voltage', 'CH1 voltage'), + ) + annotation_rows = ( + ('ch0_voltages', 'CH0 voltages', (0,)), + ('ch1_voltages', 'CH1 voltages', (1,)), + ) + options = ( + {'id': 'vref', 'desc': 'Reference voltage (V)', 'default': 1.5}, + ) + + def __init__(self): + self.reset() + + def reset(self): + self.data = 0 + self.ss, self.es = 0, 0 + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def handle_input_voltage(self, data): + input_voltage = data & 0x3FFFFF + input_voltage = -(2**21 - input_voltage) + input_voltage = (input_voltage / 0xfffff) * self.options['vref'] + ann = [] + for format in input_voltage_format: + ann.append(format % input_voltage) + + channel = (data & (1 << 22)) >> 22 + self.put(self.ss, self.es, self.out_ann, [channel, ann]) + + def decode(self, ss, es, data): + ptype = data[0] + + if ptype == 'CS-CHANGE': + cs_old, cs_new = data[1:] + if cs_old is not None and cs_old == 0 and cs_new == 1: + self.es = es + self.data >>= 1 + self.handle_input_voltage(self.data) + + self.data = 0 + elif cs_old is not None and cs_old == 1 and cs_new == 0: + self.ss = ss + + elif ptype == 'BITS': + miso = data[2] + for bit in reversed(miso): + self.data = self.data | bit[0] + + self.data <<= 1 diff --git a/libsigrokdecode4DSL/decoders/ltc26x7/__init__.py b/libsigrokdecode4DSL/decoders/ltc26x7/__init__.py new file mode 100644 index 00000000..efedd4d3 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/ltc26x7/__init__.py @@ -0,0 +1,25 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2020 Analog Devices Inc. +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 3 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This decoder stacks on top of the 'i2c' PD and decodes the +Linear Technology LTC2607/LTC2617/LTC2627 protocol. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/ltc26x7/pd.py b/libsigrokdecode4DSL/decoders/ltc26x7/pd.py new file mode 100644 index 00000000..3255d5fd --- /dev/null +++ b/libsigrokdecode4DSL/decoders/ltc26x7/pd.py @@ -0,0 +1,187 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2020 Analog Devices Inc. +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 3 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd + +slave_address = { + 0x00: ['GND', 'GND', 'GND', 'G'], + 0x01: ['FLOAT', 'FLOAT', 'FLOAT', 'F'], + 0x02: ['VCC', 'VCC', 'VCC', 'V'], +} + +commands = { + 0x00: ['Write Input Register', 'Write In Reg', 'Wr In Reg', 'WIR'], + 0x01: ['Update DAC', 'Update', 'U'], + 0x03: ['Write and Power Up DAC', 'Write & Power Up', 'W&PU'], + 0x04: ['Power Down DAC', 'Power Down', 'PD'], + 0x0F: ['No Operation', 'No Op', 'NO'], +} + +addresses = { + 0x00: ['DAC A', 'A'], + 0x01: ['DAC B', 'B'], + 0x0F: ['All DACs', 'All'], +} + +input_voltage_format = ['%.6fV', '%.2fV'] + +class Decoder(srd.Decoder): + api_version = 3 + id = 'ltc26x7' + name = 'LTC26x7' + longname = 'Linear Technology LTC26x7' + desc = 'Linear Technology LTC26x7 16-/14-/12-bit rail-to-rail DACs.' + license = 'gplv2+' + inputs = ['i2c'] + outputs = [] + tags = ['IC', 'Analog/digital'] + options = ( + {'id': 'chip', 'desc': 'Chip', 'default': 'ltc2607', + 'values': ('ltc2607', 'ltc2617', 'ltc2627')}, + {'id': 'vref', 'desc': 'Reference voltage (V)', 'default': 1.5}, + ) + annotations = ( + ('slave_addr', 'Slave address'), + ('command', 'Command'), + ('address', 'Address'), + ('dac_a_voltage', 'DAC A voltage'), + ('dac_b_voltage', 'DAC B voltage'), + ) + annotation_rows = ( + ('addr_cmd', 'Address/command', (0, 1, 2)), + ('dac_a_voltages', 'DAC A voltages', (3,)), + ('dac_b_voltages', 'DAC B voltages', (4,)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.state = 'IDLE' + self.ss = -1 + self.data = 0x00 + self.dac_val = 0 + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def convert_ternary_str(self, n): + if n == 0: + return [0, 0, 0] + nums = [] + while n: + n, r = divmod(n, 3) + nums.append(r) + while len(nums) < 3: + nums.append(0) + return list(reversed(nums)) + + def handle_slave_addr(self, data): + if data == 0x73: + ann = ['Global address', 'Global addr', 'Glob addr', 'GA'] + self.put(self.ss, self.es, self.out_ann, [0, ann]) + return + ann = ['CA2=%s CA1=%s CA0=%s', '2=%s 1=%s 0=%s', '%s %s %s', '%s %s %s'] + addr = 0 + for i in range(7): + if i in [2, 3]: + continue + offset = i + if i > 3: + offset -= 2 + mask = 1 << i + if data & mask: + mask = 1 << offset + addr |= mask + + addr -= 0x04 + ternary_values = self.convert_ternary_str(addr) + for i in range(len(ann)): + ann[i] = ann[i] % (slave_address[ternary_values[0]][i], + slave_address[ternary_values[1]][i], + slave_address[ternary_values[2]][i]) + self.put(self.ss, self.es, self.out_ann, [0, ann]) + + def handle_cmd_addr(self, data): + cmd_val = (data >> 4) & 0x0F + self.dac_val = (data & 0x0F) + sm = (self.ss + self.es) // 2 + + self.put(self.ss, sm, self.out_ann, [1, commands[cmd_val]]) + self.put(sm, self.es, self.out_ann, [2, addresses[self.dac_val]]) + + def handle_data(self, data): + self.data = (self.data << 8) & 0xFF00 + self.data += data + if self.options['chip'] == 'ltc2617': + self.data = (self.data >> 2) + self.data = (self.options['vref'] * self.data) / 0x3FFF + elif self.options['chip'] == 'ltc2627': + self.data = (self.data >> 4) + self.data = (self.options['vref'] * self.data) / 0x0FFF + else: + self.data = (self.options['vref'] * self.data) / 0xFFFF + ann = [] + for format in input_voltage_format: + ann.append(format % self.data) + self.data = 0 + + if self.dac_val == 0x0F: # All DACs (A and B). + self.put(self.ss, self.es, self.out_ann, [3 + 0, ann]) + self.put(self.ss, self.es, self.out_ann, [3 + 1, ann]) + else: + self.put(self.ss, self.es, self.out_ann, [3 + self.dac_val, ann]) + + def decode(self, ss, es, data): + cmd, databyte = data + self.es = es + + # State machine. + if self.state == 'IDLE': + # Wait for an I²C START condition. + if cmd != 'START': + return + self.state = 'GET SLAVE ADDR' + elif self.state == 'GET SLAVE ADDR': + # Wait for an address write operation. + if cmd != 'ADDRESS WRITE': + return + self.ss = ss + self.handle_slave_addr(databyte) + self.ss = -1 + self.state = 'GET CMD ADDR' + elif self.state == 'GET CMD ADDR': + if cmd != 'DATA WRITE': + return + self.ss = ss + self.handle_cmd_addr(databyte) + self.ss = -1 + self.state = 'WRITE DATA' + elif self.state == 'WRITE DATA': + if cmd == 'DATA WRITE': + if self.ss == -1: + self.ss = ss + self.data = databyte + return + self.handle_data(databyte) + self.ss = -1 + elif cmd == 'STOP': + self.state = 'IDLE' + else: + return diff --git a/libsigrokdecode4DSL/decoders/maple_bus/__init__.py b/libsigrokdecode4DSL/decoders/maple_bus/__init__.py new file mode 100644 index 00000000..33b90a5b --- /dev/null +++ b/libsigrokdecode4DSL/decoders/maple_bus/__init__.py @@ -0,0 +1,25 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2017 Marcus Comstedt +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +Maple bus is serial communication protocol used by peripherals for the +SEGA Dreamcast game console. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/maple_bus/pd.py b/libsigrokdecode4DSL/decoders/maple_bus/pd.py new file mode 100644 index 00000000..0e4e6043 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/maple_bus/pd.py @@ -0,0 +1,219 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2017 Marcus Comstedt +## Copyright (C) 2019 DreamSourceLab +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd + +ann = [ + ['Size', 'L'], + ['SrcAP', 'S'], + ['DstAP', 'D'], + ['Cmd', 'C'], + ['Data'], + ['Cksum', 'K'], +] + +class Decoder(srd.Decoder): + api_version = 3 + id = 'maple_bus' + name = 'Maple bus' + longname = 'SEGA Maple bus' + desc = 'Maple bus peripheral protocol for SEGA Dreamcast.' + license = 'gplv2+' + inputs = ['logic'] + outputs = [] + tags = ['Retro computing'] + channels = ( + {'id': 'sdcka', 'name': 'SDCKA', 'desc': 'Data/clock line A'}, + {'id': 'sdckb', 'name': 'SDCKB', 'desc': 'Data/clock line B'}, + ) + annotations = ( + ('start', 'Start pattern'), + ('end', 'End pattern'), + ('start-with-crc', 'Start pattern with CRC'), + ('occupancy', 'SDCKB occupancy pattern'), + ('reset', 'RESET pattern'), + ('bit', 'Bit'), + ('size', 'Data size'), + ('source', 'Source AP'), + ('dest', 'Destination AP'), + ('command', 'Command'), + ('data', 'Data'), + ('checksum', 'Checksum'), + ('frame-error', 'Frame error'), + ('checksum-error', 'Checksum error'), + ('size-error', 'Size error'), + ) + annotation_rows = ( + ('bits', 'Bits', (0, 1, 2, 3, 4, 5)), + ('fields', 'Fields', (6, 7, 8, 9, 10, 11)), + ('warnings', 'Warnings', (12, 13, 14)), + ) + binary = ( + ('size', 'Data size'), + ('source', 'Source AP'), + ('dest', 'Destination AP'), + ('command', 'Command code'), + ('data', 'Data'), + ('checksum', 'Checksum'), + ) + + def __init__(self): + self.reset() + + def reset(self): + pass + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + self.out_binary = self.register(srd.OUTPUT_BINARY) + self.pending_bit_pos = None + + def putx(self, data): + self.put(self.ss, self.es, self.out_ann, data) + + def putb(self, data): + self.put(self.ss, self.es, self.out_binary, data) + + def byte_annotation(self, bintype, d): + return [bintype + 6, + ['%s: %02X' % (name, d) for name in ann[bintype]] + ['%02X' % d]] + + def got_start(self): + self.putx([0, ['Start pattern', 'Start', 'S']]) + + def got_end(self): + self.putx([1, ['End pattern', 'End', 'E']]) + if self.length != self.expected_length + 1: + self.putx([14, ['Size error', 'L error', 'LE']]) + + def got_start_with_crc(self): + self.putx([2, ['Start pattern with CRC', 'Start CRC', 'SC']]) + + def got_occupancy(self): + self.putx([3, ['SDCKB occupancy pattern', 'Occupancy', 'O']]) + + def got_reset(self): + self.putx([4, ['RESET pattern', 'RESET', 'R']]) + + def output_pending_bit(self): + if self.pending_bit_pos: + self.put(self.pending_bit_pos, self.pending_bit_pos, self.out_ann, [5, ['Bit: %d' % self.pending_bit, '%d' % self.pending_bit]]) + + def got_bit(self, n): + self.output_pending_bit() + self.data = self.data * 2 + n + self.pending_bit = n + self.pending_bit_pos = self.samplenum + + def got_byte(self): + self.output_pending_bit() + bintype = 4 + if self.length < 4: + if self.length == 0: + self.expected_length = 4 * (self.data + 1) + bintype = self.length + elif self.length == self.expected_length: + bintype = 5 + if self.data != self.checksum: + self.putx([13, ['Cksum error', 'K error', 'KE']]) + self.length = self.length + 1 + self.checksum = self.checksum ^ self.data + self.putx(self.byte_annotation(bintype, self.data)) + self.putb([bintype, bytes([self.data])]) + self.pending_bit_pos = None + + def frame_error(self): + self.putx([7, ['Frame error', 'F error', 'FE']]) + + def handle_start(self): + self.wait({0: 'l', 1: 'h'}) + self.ss = self.samplenum + count = 0 + while True: + (sdcka, sdckb) = self.wait([{1: 'f'}, {0: 'r'}]) + if (self.matched & (0b1 << 0)): + count = count + 1 + if (self.matched & (0b1 << 1)): + self.es = self.samplenum + if sdckb == 1: + if count == 4: + self.got_start() + return True + elif count == 6: + self.got_start_with_crc() + return True + elif count == 8: + self.got_occupancy() + return False + elif count >= 14: + self.got_reset() + return False + self.frame_error() + return False + + def handle_byte_or_stop(self): + self.ss = self.samplenum + self.pending_bit_pos = None + initial = True + counta = 0 + countb = 0 + self.data = 0 + while countb < 4: + (sdcka, sdckb) = self.wait([{0: 'f'}, {1: 'f'}]) + self.es = self.samplenum + if (self.matched & (0b1 << 0)): + if counta == countb: + self.got_bit(sdckb) + counta = counta + 1 + elif counta == 1 and countb == 0 and self.data == 0 and sdckb == 0: + self.wait([{0: 'h', 1: 'h'}, {0: 'f'}, {1: 'f'}]) + self.es = self.samplenum + if (self.matched & (0b1 << 0)): + self.got_end() + else: + self.frame_error() + return False + else: + self.frame_error() + return False + elif (self.matched & (0b1 << 1)): + if counta == countb + 1: + self.got_bit(sdcka) + countb = countb + 1 + elif counta == 0 and countb == 0 and sdcka == 1 and initial: + self.ss = self.samplenum + initial = False + else: + self.frame_error() + return False + self.wait({0: 'h'}) + self.es = self.samplenum + self.got_byte() + return True + + def decode(self): + while True: + while not self.handle_start(): + pass + self.length = 0 + self.expected_length = 4 + self.checksum = 0 + while self.handle_byte_or_stop(): + pass diff --git a/libsigrokdecode4DSL/decoders/max7219/__init__.py b/libsigrokdecode4DSL/decoders/max7219/__init__.py new file mode 100644 index 00000000..673d3040 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/max7219/__init__.py @@ -0,0 +1,25 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2015 Paul Evans +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This decoder stacks on top of the 'spi' PD and decodes the Maxim MAX7219 and +MAX7221 LED matrix driver protocol. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/max7219/pd.py b/libsigrokdecode4DSL/decoders/max7219/pd.py new file mode 100644 index 00000000..53067a67 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/max7219/pd.py @@ -0,0 +1,115 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2015 Paul Evans +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import re +import sigrokdecode as srd + +def _decode_intensity(val): + intensity = val & 0x0f + if intensity == 0: + return 'min' + elif intensity == 15: + return 'max' + else: + return intensity + +registers = { + 0x00: ['No-op', lambda _: ''], + 0x09: ['Decode', lambda v: '0b{:08b}'.format(v)], + 0x0A: ['Intensity', _decode_intensity], + 0x0B: ['Scan limit', lambda v: 1 + v], + 0x0C: ['Shutdown', lambda v: 'off' if v else 'on'], + 0x0F: ['Display test', lambda v: 'on' if v else 'off'] +} + +ann_reg, ann_digit, ann_warning = range(3) + +class Decoder(srd.Decoder): + api_version = 3 + id = 'max7219' + name = 'MAX7219' + longname = 'Maxim MAX7219/MAX7221' + desc = 'Maxim MAX72xx series 8-digit LED display driver.' + license = 'gplv2+' + inputs = ['spi'] + outputs = [] + tags = ['Display'] + annotations = ( + ('register', 'Registers written to the device'), + ('digit', 'Digits displayed on the device'), + ('warnings', 'Human-readable warnings'), + ) + annotation_rows = ( + ('commands', 'Commands', (ann_reg, ann_digit)), + ('warnings', 'Warnings', (ann_warning,)), + ) + + def __init__(self): + self.reset() + + def reset(self): + pass + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + self.pos = 0 + self.cs_start = 0 + + def putreg(self, ss, es, reg, value): + self.put(ss, es, self.out_ann, [ann_reg, ['%s: %s' % (reg, value)]]) + + def putdigit(self, ss, es, digit, value): + self.put(ss, es, self.out_ann, [ann_digit, ['Digit %d: %02X' % (digit, value)]]) + + def putwarn(self, ss, es, message): + self.put(ss, es, self.out_ann, [ann_warning, [message]]) + + def decode(self, ss, es, data): + ptype, mosi, _ = data + + if ptype == 'DATA': + if not self.cs_asserted: + return + + if self.pos == 0: + self.addr = mosi + self.addr_start = ss + elif self.pos == 1: + if self.addr >= 1 and self.addr <= 8: + self.putdigit(self.addr_start, es, self.addr, mosi) + elif self.addr in registers: + name, decoder = registers[self.addr] + self.putreg(self.addr_start, es, name, decoder(mosi)) + else: + self.putwarn(self.addr_start, es, + 'Unknown register %02X' % (self.addr)) + + self.pos += 1 + elif ptype == 'CS-CHANGE': + self.cs_asserted = mosi + if self.cs_asserted: + self.pos = 0 + self.cs_start = ss + else: + if self.pos == 1: + # Don't warn if pos=0 so that CS# glitches don't appear + # as spurious warnings. + self.putwarn(self.cs_start, es, 'Short write') + elif self.pos > 2: + self.putwarn(self.cs_start, es, 'Overlong write') diff --git a/libsigrokdecode4DSL/decoders/mcs48/__init__.py b/libsigrokdecode4DSL/decoders/mcs48/__init__.py new file mode 100644 index 00000000..b989a2a8 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/mcs48/__init__.py @@ -0,0 +1,31 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2018 fenugrec +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This protocol decoder de-multiplexes Intel MCS-48 (8039, 8048, etc.) external +program memory accesses. + +This requires 14 channels: 8 for D0-D7 (data and lower 8 bits of address), +4 for A8-A11 (output on port P2), ALE and PSEN. + +An optional A12 is supported, which may be an arbitrary I/O pin driven by +software (use case is dumping ROM of an HP 3478A). +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/mcs48/pd.py b/libsigrokdecode4DSL/decoders/mcs48/pd.py new file mode 100644 index 00000000..50216a41 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/mcs48/pd.py @@ -0,0 +1,119 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2018 fenugrec +## Copyright (C) 2019 DreamSourceLab +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd + +class ChannelError(Exception): + pass + +class Decoder(srd.Decoder): + api_version = 3 + id = 'mcs48' + name = 'MCS-48' + longname = 'Intel MCS-48' + desc = 'Intel MCS-48 external memory access protocol.' + license = 'gplv2+' + inputs = ['logic'] + outputs = [] + tags = ['Retro computing'] + channels = ( + {'id': 'ale', 'name': 'ALE', 'desc': 'Address latch enable'}, + {'id': 'psen', 'name': '/PSEN', 'desc': 'Program store enable'}, + ) + tuple({ + 'id': 'd%d' % i, + 'name': 'D%d' % i, + 'desc': 'CPU data line %d' % i + } for i in range(0, 8) + ) + tuple({ + 'id': 'a%d' % i, + 'name': 'A%d' % i, + 'desc': 'CPU address line %d' % i + } for i in range(8, 12) + ) + optional_channels = tuple({ + 'id': 'a%d' % i, + 'name': 'A%d' % i, + 'desc': 'CPU address line %d' % i + } for i in range(12, 13) + ) + annotations = ( + ('romdata', 'Address:Data'), + ) + binary = ( + ('romdata', 'AAAA:DD'), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.addr = 0 + self.addr_s = 0 + self.data = 0 + self.data_s = 0 + + # Flag to make sure we get an ALE pulse first. + self.started = 0 + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + self.out_bin = self.register(srd.OUTPUT_BINARY) + + def newaddr(self, addr, data): + # Falling edge on ALE: reconstruct address. + self.started = 1 + addr = sum([bit << i for i, bit in enumerate(addr)]) + addr <<= len(data) + addr |= sum([bit << i for i, bit in enumerate(data)]) + self.addr = addr + self.addr_s = self.samplenum + + def newdata(self, data): + # Edge on PSEN: get data. + data = sum([bit << i for i, bit in enumerate(data)]) + self.data = data + self.data_s = self.samplenum + if self.started: + anntext = '{:04X}:{:02X}'.format(self.addr, self.data) + self.put(self.addr_s, self.data_s, self.out_ann, [0, [anntext]]) + bindata = self.addr.to_bytes(2, byteorder='big') + bindata += self.data.to_bytes(1, byteorder='big') + self.put(self.addr_s, self.data_s, self.out_bin, [0, bindata]) + + def decode(self): + # Address bits above A11 are optional, and are considered to be A12+. + # This logic needs more adjustment when more bank address pins are + # to get supported. For now, having just A12 is considered sufficient. + has_bank = self.has_channel(14) + bank_pin_count = 1 if has_bank else 0 + # Sample address on the falling ALE edge. + # Save data on falling edge of PSEN. + while True: + (ale, psen, d0, d1, d2, d3, d4, d5, d6, d7, a8, a9, a10, a11, a12) = self.wait([{0: 'f'}, {1: 'r'}]) + data = (d0, d1, d2, d3, d4, d5, d6, d7) + addr = (a8, a9, a10, a11) + bank = (a12, ) + if has_bank: + addr += bank[:bank_pin_count] + # Handle those conditions (one or more) that matched this time. + if (self.matched & (0b1 << 0)): + self.newaddr(addr, data) + if (self.matched & (0b1 << 1)): + self.newdata(data) diff --git a/libsigrokdecode4DSL/decoders/mdio/__init__.py b/libsigrokdecode4DSL/decoders/mdio/__init__.py new file mode 100644 index 00000000..98213b97 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/mdio/__init__.py @@ -0,0 +1,39 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2016 Elias Oenal +## All rights reserved. +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are met: +## +## 1. Redistributions of source code must retain the above copyright notice, +## this list of conditions and the following disclaimer. +## 2. Redistributions in binary form must reproduce the above copyright notice, +## this list of conditions and the following disclaimer in the documentation +## and/or other materials provided with the distribution. +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +## AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +## IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +## ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +## LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +## CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +## SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +## INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +## CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +## ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +## POSSIBILITY OF SUCH DAMAGE. +## + +''' +The MDIO (Management Data Input/Output) protocol decoder supports the +MII Management serial bus (a bidirectional bus between the PHY and the STA), +with a clock line (MDC) and a bi-directional data line (MDIO). + +MDIO is also known as SMI (Serial Management Interface). + +It's part of the Ethernet standard. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/mdio/pd.py b/libsigrokdecode4DSL/decoders/mdio/pd.py new file mode 100644 index 00000000..9ea76c9e --- /dev/null +++ b/libsigrokdecode4DSL/decoders/mdio/pd.py @@ -0,0 +1,327 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2016 Elias Oenal +## Copyright (C) 2019 DreamSourceLab +## All rights reserved. +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are met: +## +## 1. Redistributions of source code must retain the above copyright notice, +## this list of conditions and the following disclaimer. +## 2. Redistributions in binary form must reproduce the above copyright notice, +## this list of conditions and the following disclaimer in the documentation +## and/or other materials provided with the distribution. +## +## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +## AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +## IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +## ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +## LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +## CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +## SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +## INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +## CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +## ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +## POSSIBILITY OF SUCH DAMAGE. +## + +import sigrokdecode as srd + +class Decoder(srd.Decoder): + api_version = 3 + id = 'mdio' + name = 'MDIO' + longname = 'Management Data Input/Output' + desc = 'MII management bus between MAC and PHY.' + license = 'bsd' + inputs = ['logic'] + outputs = ['mdio'] + tags = ['Networking'] + channels = ( + {'id': 'mdc', 'name': 'MDC', 'desc': 'Clock'}, + {'id': 'mdio', 'name': 'MDIO', 'desc': 'Data'}, + ) + options = ( + {'id': 'show_debug_bits', 'desc': 'Show debug bits', + 'default': 'no', 'values': ('yes', 'no')}, + ) + annotations = ( + ('bit-val', 'Bit value'), + ('bit-num', 'Bit number'), + ('frame', 'Frame'), + ('frame-idle', 'Bus idle state'), + ('frame-error', 'Frame error'), + ('decode', 'Decode'), + ) + annotation_rows = ( + ('bit-val', 'Bit value', (0,)), + ('bit-num', 'Bit number', (1,)), + ('frame', 'Frame', (2, 3)), + ('frame-error', 'Frame error', (4,)), + ('decode', 'Decode', (5,)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.illegal_bus = 0 + self.samplenum = -1 + self.clause45_addr = -1 # Clause 45 is context sensitive. + self.reset_decoder_state() + + def start(self): + self.out_python = self.register(srd.OUTPUT_PYTHON) + self.out_ann = self.register(srd.OUTPUT_ANN) + + def putbit(self, mdio, ss, es): + self.put(ss, es, self.out_ann, [0, ['%d' % mdio]]) + if self.options['show_debug_bits'] == 'yes': + self.put(ss, es, self.out_ann, [1, ['%d' % (self.bitcount - 1), '%d' % ((self.bitcount - 1) % 10)]]) + + def putff(self, data): + self.put(self.ss_frame_field, self.samplenum, self.out_ann, data) + + def putdata(self): + self.put(self.ss_frame_field, self.mdiobits[0][2], self.out_ann, + [2, ['DATA: %04X' % self.data, 'DATA', 'D']]) + + if self.clause45 and self.opcode == 0: + self.clause45_addr = self.data + + # Decode data. + if self.opcode > 0 or not self.clause45: + decoded_min = '' + if self.clause45 and self.clause45_addr != -1: + decoded_min += str.format('ADDR: %04X ' % self.clause45_addr) + elif self.clause45: + decoded_min += str.format('ADDR: UKWN ') + + if self.clause45 and self.opcode > 1 \ + or (not self.clause45 and self.opcode): + decoded_min += str.format('READ: %04X' % self.data) + is_read = 1 + else: + decoded_min += str.format('WRITE: %04X' % self.data) + is_read = 0 + decoded_ext = str.format(' %s: %02d' % \ + ('PRTAD' if self.clause45 else 'PHYAD', self.portad)) + decoded_ext += str.format(' %s: %02d' % \ + ('DEVAD' if self.clause45 else 'REGAD', self.devad)) + if self.ta_invalid or self.op_invalid: + decoded_ext += ' ERROR' + self.put(self.ss_frame, self.mdiobits[0][2], self.out_ann, + [5, [decoded_min + decoded_ext, decoded_min]]) + + self.put(self.ss_frame, self.mdiobits[0][2], self.out_python, + [(bool(self.clause45), int(self.clause45_addr), \ + bool(is_read), int(self.portad), int(self.devad), \ + int(self.data))]) + + # Post read increment address. + if self.clause45 and self.opcode == 2 and self.clause45_addr != -1: + self.clause45_addr += 1 + + def reset_decoder_state(self): + self.mdiobits = [] + self.bitcount = -1 + self.opcode = -1 + self.clause45 = 0 + self.ss_frame = -1 + self.ss_frame_field = -1 + self.preamble_len = 0 + self.ta_invalid = -1 + self.op_invalid = '' + self.portad = -1 + self.portad_bits = 5 + self.devad = -1 + self.devad_bits = 5 + self.data = -1 + self.data_bits = 16 + self.state = 'PRE' + + def state_PRE(self, mdio): + if self.illegal_bus: + if mdio == 0: # Stay in illegal bus state. + return + else: # Leave and continue parsing. + self.illegal_bus = 0 + self.put(self.ss_illegal, self.samplenum, self.out_ann, + [4, ['ILLEGAL BUS STATE', 'ILL']]) + self.ss_frame = self.samplenum + + if self.ss_frame == -1: + self.ss_frame = self.samplenum + + if mdio == 1: + self.preamble_len += 1 + + # Valid MDIO can't clock more than 16 succeeding ones without being + # in either IDLE or PRE. + if self.preamble_len > 16: + if self.preamble_len >= 10000 + 32: + self.put(self.ss_frame, self.mdiobits[32][1], self.out_ann, + [3, ['IDLE #%d' % (self.preamble_len - 32), 'IDLE', 'I']]) + self.ss_frame = self.mdiobits[32][1] + self.preamble_len = 32 + # This is getting out of hand, free some memory. + del self.mdiobits[33:-1] + if mdio == 0: + if self.preamble_len < 32: + self.ss_frame = self.mdiobits[self.preamble_len][1] + self.put(self.ss_frame, self.samplenum, self.out_ann, + [4, ['SHORT PREAMBLE', 'SHRT PRE']]) + elif self.preamble_len > 32: + self.ss_frame = self.mdiobits[32][1] + self.put(self.mdiobits[self.preamble_len][1], + self.mdiobits[32][1], self.out_ann, + [3, ['IDLE #%d' % (self.preamble_len - 32), + 'IDLE', 'I']]) + self.preamble_len = 32 + else: + self.ss_frame = self.mdiobits[32][1] + self.put(self.ss_frame, self.samplenum, self.out_ann, + [2, ['PRE #%d' % self.preamble_len, 'PRE', 'P']]) + self.ss_frame_field = self.samplenum + self.state = 'ST' + elif mdio == 0: + self.ss_illegal = self.ss_frame + self.illegal_bus = 1 + + def state_ST(self, mdio): + if mdio == 0: + self.clause45 = 1 + self.state = 'OP' + + def state_OP(self, mdio): + if self.opcode == -1: + if self.clause45: + st = ['ST (Clause 45)', 'ST 45'] + else: + st = ['ST (Clause 22)', 'ST 22'] + self.putff([2, st + ['ST', 'S']]) + self.ss_frame_field = self.samplenum + + if mdio: + self.opcode = 2 + else: + self.opcode = 0 + else: + if self.clause45: + self.state = 'PRTAD' + self.opcode += mdio + else: + if mdio == self.opcode: + self.op_invalid = 'invalid for Clause 22' + self.state = 'PRTAD' + + def state_PRTAD(self, mdio): + if self.portad == -1: + self.portad = 0 + if self.clause45: + if self.opcode == 0: + op = ['OP: ADDR', 'OP: A'] + elif self.opcode == 1: + op = ['OP: WRITE', 'OP: W'] + elif self.opcode == 2: + op = ['OP: READINC', 'OP: RI'] + elif self.opcode == 3: + op = ['OP: READ', 'OP: R'] + else: + op = ['OP: READ', 'OP: R'] if self.opcode else ['OP: WRITE', 'OP: W'] + self.putff([2, op + ['OP', 'O']]) + if self.op_invalid: + self.putff([4, ['OP %s' % self.op_invalid, 'OP', 'O']]) + self.ss_frame_field = self.samplenum + self.portad_bits -= 1 + self.portad |= mdio << self.portad_bits + if not self.portad_bits: + self.state = 'DEVAD' + + def state_DEVAD(self, mdio): + if self.devad == -1: + self.devad = 0 + if self.clause45: + prtad = ['PRTAD: %02d' % self.portad, 'PRT', 'P'] + else: + prtad = ['PHYAD: %02d' % self.portad, 'PHY', 'P'] + self.putff([2, prtad]) + self.ss_frame_field = self.samplenum + self.devad_bits -= 1 + self.devad |= mdio << self.devad_bits + if not self.devad_bits: + self.state = 'TA' + + def state_TA(self, mdio): + if self.ta_invalid == -1: + self.ta_invalid = '' + if self.clause45: + regad = ['DEVAD: %02d' % self.devad, 'DEV', 'D'] + else: + regad = ['REGAD: %02d' % self.devad, 'REG', 'R'] + self.putff([2, regad]) + self.ss_frame_field = self.samplenum + if mdio != 1 and ((self.clause45 and self.opcode < 2) + or (not self.clause45 and self.opcode == 0)): + self.ta_invalid = ' invalid (bit1)' + else: + if mdio != 0: + if self.ta_invalid: + self.ta_invalid = ' invalid (bit1 and bit2)' + else: + self.ta_invalid = ' invalid (bit2)' + self.state = 'DATA' + + def state_DATA(self, mdio): + if self.data == -1: + self.data = 0 + self.putff([2, ['TA', 'T']]) + if self.ta_invalid: + self.putff([4, ['TA%s' % self.ta_invalid, 'TA', 'T']]) + self.ss_frame_field = self.samplenum + self.data_bits -= 1 + self.data |= mdio << self.data_bits + if not self.data_bits: + # Output final bit. + self.mdiobits[0][2] = self.mdiobits[0][1] + self.quartile_cycle_length() + self.bitcount += 1 + self.putbit(self.mdiobits[0][0], self.mdiobits[0][1], self.mdiobits[0][2]) + self.putdata() + self.reset_decoder_state() + + def process_state(self, argument, mdio): + method_name = 'state_' + str(argument) + method = getattr(self, method_name) + return method(mdio) + + # Returns the first quartile point of the frames cycle lengths. This is a + # conservative guess for the end of the last cycle. On average it will be + # more likely to fall short, than being too long, which makes for better + # readability in GUIs. + def quartile_cycle_length(self): + # 48 is the minimum number of samples we have to have at the end of a + # frame. The last sample only has a leading clock edge and is ignored. + bitlen = [] + for i in range(1, 49): + bitlen.append(self.mdiobits[i][2] - self.mdiobits[i][1]) + bitlen = sorted(bitlen) + return bitlen[12] + + def handle_bit(self, mdio): + self.bitcount += 1 + self.mdiobits.insert(0, [mdio, self.samplenum, -1]) + + if self.bitcount > 0: + self.mdiobits[1][2] = self.samplenum # Note end of last cycle. + # Output the last bit we processed. + self.putbit(self.mdiobits[1][0], self.mdiobits[1][1], self.mdiobits[1][2]) + + self.process_state(self.state, mdio) + + def decode(self): + while True: + # Process pin state upon rising MDC edge. + (mdc, mdio) = self.wait({0: 'r'}) + self.handle_bit(mdio) diff --git a/libsigrokdecode4DSL/decoders/microwire/__init__.py b/libsigrokdecode4DSL/decoders/microwire/__init__.py new file mode 100644 index 00000000..53f52d09 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/microwire/__init__.py @@ -0,0 +1,40 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2017 Kevin Redon +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +Microwire is a 3-wire half-duplex synchronous serial communication protocol. + +Originally from National Semiconductor, it is often used in EEPROM chips with +device specific commands on top of the bit stream. + +Channels: + + - CS: chip select, active high + - SK: clock line, for the synchronous communication (idle low) + - SI: slave data input, output by the master and parsed by the selected slave + on rising edge of clock line (idle low) + - SO: slave data output, output by the selected slave and changed on rising + edge of clock line, or goes from low to high when ready during status + check (idle high impedance) + +The channel names might vary from chip to chip but the underlying function is +the same. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/microwire/pd.py b/libsigrokdecode4DSL/decoders/microwire/pd.py new file mode 100644 index 00000000..47d87b85 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/microwire/pd.py @@ -0,0 +1,195 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2017 Kevin Redon +## Copyright (C) 2019 DreamSourceLab +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd +from collections import namedtuple + +''' +OUTPUT_PYTHON format: + +Packet: +[namedtuple('ss': bit start sample number, + 'es': bit end sample number, + 'si': SI bit, + 'so': SO bit, + ), ...] + +Since address and word size are variable, a list of all bits in each packet +need to be output. Since Microwire is a synchronous protocol with separate +input and output lines (SI and SO) they are provided together, but because +Microwire is half-duplex only the SI or SO bits will be considered at once. +To be able to annotate correctly the instructions formed by the bit, the start +and end sample number of each bit (pair of SI/SO bit) are provided. +''' + +PyPacket = namedtuple('PyPacket', 'ss es si so') +Packet = namedtuple('Packet', 'samplenum matched cs sk si so') + +class Decoder(srd.Decoder): + api_version = 3 + id = 'microwire' + name = 'Microwire' + longname = 'Microwire' + desc = '3-wire, half-duplex, synchronous serial bus.' + license = 'gplv2+' + inputs = ['logic'] + outputs = ['microwire'] + tags = ['Embedded/industrial'] + channels = ( + {'id': 'cs', 'name': 'CS', 'desc': 'Chip select'}, + {'id': 'sk', 'name': 'SK', 'desc': 'Clock'}, + {'id': 'si', 'name': 'SI', 'desc': 'Slave in'}, + {'id': 'so', 'name': 'SO', 'desc': 'Slave out'}, + ) + annotations = ( + ('start-bit', 'Start bit'), + ('si-bit', 'SI bit'), + ('so-bit', 'SO bit'), + ('status-check-ready', 'Status check ready'), + ('status-check-busy', 'Status check busy'), + ('warning', 'Warning'), + ) + annotation_rows = ( + ('si-bits', 'SI bits', (0, 1)), + ('so-bits', 'SO bits', (2,)), + ('status', 'Status', (3, 4)), + ('warnings', 'Warnings', (5,)), + ) + + def __init__(self): + self.reset() + + def reset(self): + pass + + def start(self): + self.out_python = self.register(srd.OUTPUT_PYTHON) + self.out_ann = self.register(srd.OUTPUT_ANN) + + def decode(self): + while True: + # Wait for slave to be selected on rising CS. + (cs, sk, si, so) = self.wait({0: 'r'}) + if sk: + self.put(self.samplenum, self.samplenum, self.out_ann, + [5, ['Clock should be low on start', + 'Clock high on start', 'Clock high', 'SK high']]) + sk = 0 # Enforce correct state for correct clock handling. + # Because we don't know if this is bit communication or a + # status check we have to collect the SI and SO values on SK + # edges while the chip is selected and figure out afterwards. + packet = [] + while cs: + # Save change. + packet.append(Packet(self.samplenum, self.matched, cs, sk, si, so)) + edge = 'r' if sk == 0 else 'f' + (cs, sk, si, so) = self.wait([{0: 'l'}, {1: edge}, {3: 'e'}]) + # Save last change. + packet.append(Packet(self.samplenum, self.matched, cs, sk, si, so)) + + # Figure out if this is a status check. + # Either there is no clock or no start bit (on first rising edge). + status_check = True + for change in packet: + # Get first clock rising edge. + if (change.matched & (0b1 << 1)) and change.sk: + if change.si: + status_check = False + break + + # The packet is for a status check. + # SO low = busy, SO high = ready. + # The SO signal might be noisy in the beginning because it starts + # in high impedance. + if status_check: + start_samplenum = packet[0].samplenum + bit_so = packet[0].so + # Check for SO edges. + for change in packet: + if (change.matched & (0b1 << 2)): + if bit_so == 0 and change.so: + # Rising edge Busy -> Ready. + self.put(start_samplenum, change.samplenum, + self.out_ann, [4, ['Busy', 'B']]) + start_samplenum = change.samplenum + bit_so = change.so + # Put last state. + if bit_so == 0: + self.put(start_samplenum, packet[-1].samplenum, + self.out_ann, [4, ['Busy', 'B']]) + else: + self.put(start_samplenum, packet[-1].samplenum, + self.out_ann, [3, ['Ready', 'R']]) + else: + # Bit communication. + # Since the slave samples SI on clock rising edge we do the + # same. Because the slave changes SO on clock rising edge we + # sample on the falling edge. + bit_start = 0 # Rising clock sample of bit start. + bit_si = 0 # SI value at rising clock edge. + bit_so = 0 # SO value at falling clock edge. + start_bit = True # Start bit incoming (first bit). + pydata = [] # Python output data. + for change in packet: + if (change.matched & (0b1 << 1)): + # Clock edge. + if change.sk: # Rising clock edge. + if bit_start > 0: # Bit completed. + if start_bit: + if bit_si == 0: # Start bit missing. + self.put(bit_start, change.samplenum, + self.out_ann, + [5, ['Start bit not high', + 'Start bit low']]) + else: + self.put(bit_start, change.samplenum, + self.out_ann, + [0, ['Start bit', 'S']]) + start_bit = False + else: + self.put(bit_start, change.samplenum, + self.out_ann, + [1, ['SI bit: %d' % bit_si, + 'SI: %d' % bit_si, + '%d' % bit_si]]) + self.put(bit_start, change.samplenum, + self.out_ann, + [2, ['SO bit: %d' % bit_so, + 'SO: %d' % bit_so, + '%d' % bit_so]]) + pydata.append(PyPacket(bit_start, + change.samplenum, bit_si, bit_so)) + bit_start = change.samplenum + bit_si = change.si + else: # Falling clock edge. + bit_so = change.so + elif (change.matched & (0b1 << 0)) and \ + change.cs == 0 and change.sk == 0: + # End of packet. + self.put(bit_start, change.samplenum, self.out_ann, + [1, ['SI bit: %d' % bit_si, + 'SI: %d' % bit_si, '%d' % bit_si]]) + self.put(bit_start, change.samplenum, self.out_ann, + [2, ['SO bit: %d' % bit_so, + 'SO: %d' % bit_so, '%d' % bit_so]]) + pydata.append(PyPacket(bit_start, change.samplenum, + bit_si, bit_so)) + self.put(packet[0].samplenum, packet[len(packet) - 1].samplenum, + self.out_python, pydata) diff --git a/libsigrokdecode4DSL/decoders/midi/__init__.py b/libsigrokdecode4DSL/decoders/midi/__init__.py new file mode 100644 index 00000000..378b0167 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/midi/__init__.py @@ -0,0 +1,28 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2013 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This decoder stacks on top of the 'uart' PD and decodes the MIDI +(Musical Instrument Digital Interface) protocol. + +MIDI is layered on top of the UART (async serial) protocol, with a fixed +baud rate of 31250 baud (+/- 1%) and 8n1 settings. Bytes are sent LSB-first. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/midi/lists.py b/libsigrokdecode4DSL/decoders/midi/lists.py new file mode 100644 index 00000000..1e628615 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/midi/lists.py @@ -0,0 +1,840 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2013-2016 Uwe Hermann +## Copyright (C) 2016 Chris Dreher +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +# Each status byte has 3 string names, each shorter than the previous +status_bytes = { + # Channel voice messages + 0x80: ['note off', 'note off', 'N off'], + 0x90: ['note on', 'note on', 'N on'], # However, velocity = 0 means "note off". + 0xa0: ['polyphonic key pressure / aftertouch', 'key pressure', 'KP' ], + 0xb0: ['control change', 'ctrl chg', 'CC'], + 0xc0: ['program change', 'prgm chg', 'PC'], + 0xd0: ['channel pressure / aftertouch', 'channel pressure', 'CP'], + 0xe0: ['pitch bend change', 'pitch bend', 'PB'], + + # Channel mode messages + # 0xb0: 'select channel mode', # Note: Same as 'control change'. + + # System exclusive messages + 0xf0: ['system exclusive', 'SysEx', 'SE'], + + # System common messages + 0xf1: ['MIDI time code quarter frame', 'MIDI time code', 'MIDI time'], + 0xf2: ['song position pointer', 'song position', 'song pos'], + 0xf3: ['song select', 'song select', 'song sel'], + 0xf4: ['undefined 0xf4', 'undef 0xf4', 'undef'], + 0xf5: ['undefined 0xf5', 'undef 0xf5', 'undef'], + 0xf6: ['tune request', 'tune request', 'tune req'], + 0xf7: ['end of system exclusive (EOX)', 'end of SysEx', 'EOX'], + + # System real time messages + 0xf8: ['timing clock', 'timing clock', 'clock'], + 0xf9: ['undefined 0xf9', 'undef 0xf9', 'undef'], + 0xfa: ['start', 'start', 's'], + 0xfb: ['continue', 'continue', 'cont'], + 0xfc: ['stop', 'stop', 'st'], + 0xfd: ['undefined 0xfd', 'undef 0xfd', 'undef'], + 0xfe: ['active sensing', 'active sensing', 'sensing'], + 0xff: ['system reset', 'reset', 'rst'], +} + +# Universal system exclusive (SysEx) messages, non-realtime (0x7e) +universal_sysex_nonrealtime = { + (0x00, None): 'unused', + (0x01, None): 'sample dump header', + (0x02, None): 'sample data packet', + (0x03, None): 'sample dump request', + + (0x04, None): 'MIDI time code', + (0x04, 0x00): 'special', + (0x04, 0x01): 'punch in points', + (0x04, 0x02): 'punch out points', + (0x04, 0x03): 'delete punch in point', + (0x04, 0x04): 'delete punch out point', + (0x04, 0x05): 'event start point', + (0x04, 0x06): 'event stop point', + (0x04, 0x07): 'event start points with additional info', + (0x04, 0x08): 'event stop points with additional info', + (0x04, 0x09): 'delete event start point', + (0x04, 0x0a): 'delete event stop point', + (0x04, 0x0b): 'cue points', + (0x04, 0x0c): 'cue points with additional info', + (0x04, 0x0d): 'delete cue point', + (0x04, 0x0e): 'event name in additional info', + + (0x05, None): 'sample dump extensions', + (0x05, 0x01): 'multiple loop points', + (0x05, 0x02): 'loop points request', + + (0x06, None): 'general information', + (0x06, 0x01): 'identity request', + (0x06, 0x02): 'identity reply', + + (0x07, None): 'file dump', + (0x07, 0x01): 'header', + (0x07, 0x02): 'data packet', + (0x07, 0x03): 'request', + + (0x08, None): 'MIDI tuning standard', + (0x08, 0x00): 'bulk dump request', + (0x08, 0x01): 'bulk dump reply', + + (0x09, None): 'general MIDI', + (0x09, 0x01): 'general MIDI system on', + (0x09, 0x02): 'general MIDI system off', + + (0x7b, None): 'end of file', + (0x7c, None): 'wait', + (0x7d, None): 'cancel', + (0x7e, None): 'nak', + (0x7f, None): 'ack', +} + +# Universal system exclusive (SysEx) messages, realtime (0x7f) +universal_sysex_realtime = { + (0x00, None): 'unused', + + (0x01, None): 'MIDI time code', + (0x01, 0x01): 'full message', + (0x01, 0x02): 'user bits', + + (0x02, None): 'MIDI show control', + (0x02, 0x00): 'MSC extensions', + # (0x02, TODO): 'TODO', # 0x01 - 0x7f: MSC commands. + + (0x03, None): 'notation information', + (0x03, 0x01): 'bar number', + (0x03, 0x02): 'time signature (immediate)', + (0x03, 0x42): 'time signature (delayed)', + + (0x04, None): 'device control', + (0x04, 0x01): 'master volume', + (0x04, 0x02): 'master balance', + + (0x05, None): 'real time MTC cueing', + (0x05, 0x00): 'special', + (0x05, 0x01): 'punch in points', + (0x05, 0x02): 'punch out points', + (0x05, 0x03): 'reserved', + (0x05, 0x04): 'reserved', + (0x05, 0x05): 'event start points', + (0x05, 0x06): 'event stop points', + (0x05, 0x07): 'event start points with additional info', + (0x05, 0x08): 'event stop points with additional info', + (0x05, 0x09): 'reserved', + (0x05, 0x0a): 'reserved', + (0x05, 0x0b): 'cue points', + (0x05, 0x0c): 'cue points with additional info', + (0x05, 0x0d): 'reserved', + (0x05, 0x0e): 'event name in additional info', + + (0x06, None): 'MIDI machine control commands', + # (0x06, TODO): 'TODO', # 0x00 - 0x7f: MMC commands. + + (0x07, None): 'MIDI machine control responses', + # (0x07, TODO): 'TODO', # 0x00 - 0x7f: MMC commands. + + (0x08, None): 'MIDI tuning standard', + (0x85, 0x02): 'note change', +} + +# Note: Not all IDs are used/listed, i.e. there are some "holes". +sysex_manufacturer_ids = { + # American group (range 01-1f, 000001-001f7f) + (0x01,): 'Sequential', + (0x02,): 'IDP', + (0x03,): 'Voyetra/Octave-Plateau', + (0x04,): 'Moog', + (0x05,): 'Passport Designs', + (0x06,): 'Lexicon', + (0x07,): 'Kurzweil', + (0x08,): 'Fender', + (0x09,): 'Gulbransen', + (0x0a,): 'AKG Acoustics', + (0x0b,): 'Voyce Music', + (0x0c,): 'Waveframe Corp', + (0x0d,): 'ADA Signal Processors', + (0x0e,): 'Garfield Electronics', + (0x0f,): 'Ensoniq', + (0x10,): 'Oberheim', + (0x11,): 'Apple Computer', + (0x12,): 'Grey Matter Response', + (0x13,): 'Digidesign', + (0x14,): 'Palm Tree Instruments', + (0x15,): 'JLCooper Electronics', + (0x16,): 'Lowrey', + (0x17,): 'Adams-Smith', + (0x18,): 'Emu Systems', + (0x19,): 'Harmony Systems', + (0x1a,): 'ART', + (0x1b,): 'Baldwin', + (0x1c,): 'Eventide', + (0x1d,): 'Inventronics', + (0x1f,): 'Clarity', + + (0x00, 0x00, 0x01): 'Time Warner Interactive', + (0x00, 0x00, 0x07): 'Digital Music Corp.', + (0x00, 0x00, 0x08): 'IOTA Systems', + (0x00, 0x00, 0x09): 'New England Digital', + (0x00, 0x00, 0x0a): 'Artisyn', + (0x00, 0x00, 0x0b): 'IVL Technologies', + (0x00, 0x00, 0x0c): 'Southern Music Systems', + (0x00, 0x00, 0x0d): 'Lake Butler Sound Company', + (0x00, 0x00, 0x0e): 'Alesis', + (0x00, 0x00, 0x10): 'DOD Electronics', + (0x00, 0x00, 0x11): 'Studer-Editech', + (0x00, 0x00, 0x14): 'Perfect Fretworks', + (0x00, 0x00, 0x15): 'KAT', + (0x00, 0x00, 0x16): 'Opcode', + (0x00, 0x00, 0x17): 'Rane Corp.', + (0x00, 0x00, 0x18): 'Anadi Inc.', + (0x00, 0x00, 0x19): 'KMX', + (0x00, 0x00, 0x1a): 'Allen & Heath Brenell', + (0x00, 0x00, 0x1b): 'Peavy Electronics', + (0x00, 0x00, 0x1c): '360 Systems', + (0x00, 0x00, 0x1d): 'Spectrum Design and Development', + (0x00, 0x00, 0x1e): 'Marquis Music', + (0x00, 0x00, 0x1f): 'Zeta Systems', + + (0x00, 0x00, 0x20): 'Axxes', + (0x00, 0x00, 0x21): 'Orban', + (0x00, 0x00, 0x24): 'KTI', + (0x00, 0x00, 0x25): 'Breakaway Technologies', + (0x00, 0x00, 0x26): 'CAE', + (0x00, 0x00, 0x29): 'Rocktron Corp.', + (0x00, 0x00, 0x2a): 'PianoDisc', + (0x00, 0x00, 0x2b): 'Cannon Research Group', + (0x00, 0x00, 0x2d): 'Rogers Instrument Corp.', + (0x00, 0x00, 0x2e): 'Blue Sky Logic', + (0x00, 0x00, 0x2f): 'Encore Electronics', + + (0x00, 0x00, 0x30): 'Uptown', + (0x00, 0x00, 0x31): 'Voce', + (0x00, 0x00, 0x32): 'CTI Audio, Inc. (Music. Intel Dev.)', + (0x00, 0x00, 0x33): 'S&S Research', + (0x00, 0x00, 0x34): 'Broderbund Software, Inc.', + (0x00, 0x00, 0x35): 'Allen Organ Co.', + (0x00, 0x00, 0x37): 'Music Quest', + (0x00, 0x00, 0x38): 'APHEX', + (0x00, 0x00, 0x39): 'Gallien Krueger', + (0x00, 0x00, 0x3a): 'IBM', + (0x00, 0x00, 0x3c): 'Hotz Instruments Technologies', + (0x00, 0x00, 0x3d): 'ETA Lighting', + (0x00, 0x00, 0x3e): 'NSI Corporation', + (0x00, 0x00, 0x3f): 'Ad Lib, Inc.', + + (0x00, 0x00, 0x40): 'Richmond Sound Design', + (0x00, 0x00, 0x41): 'Microsoft', + (0x00, 0x00, 0x42): 'The Software Toolworks', + (0x00, 0x00, 0x43): 'Niche/RJMG', + (0x00, 0x00, 0x44): 'Intone', + (0x00, 0x00, 0x47): 'GT Electronics / Groove Tubes', + (0x00, 0x00, 0x49): 'Timeline Vista', + (0x00, 0x00, 0x4a): 'Mesa Boogie', + (0x00, 0x00, 0x4c): 'Sequoia Development', + (0x00, 0x00, 0x4d): 'Studio Electronics', + (0x00, 0x00, 0x4e): 'Euphonix', + (0x00, 0x00, 0x4f): 'InterMIDI, Inc.', + + (0x00, 0x00, 0x50): 'MIDI Solutions', + (0x00, 0x00, 0x51): '3DO Company', + (0x00, 0x00, 0x52): 'Lightwave Research', + (0x00, 0x00, 0x53): 'Micro-W', + (0x00, 0x00, 0x54): 'Spectral Synthesis', + (0x00, 0x00, 0x55): 'Lone Wolf', + (0x00, 0x00, 0x56): 'Studio Technologies', + (0x00, 0x00, 0x57): 'Peterson EMP', + (0x00, 0x00, 0x58): 'Atari', + (0x00, 0x00, 0x59): 'Marion Systems', + (0x00, 0x00, 0x5a): 'Design Event', + (0x00, 0x00, 0x5b): 'Winjammer Software', + (0x00, 0x00, 0x5c): 'AT&T Bell Labs', + (0x00, 0x00, 0x5e): 'Symetrix', + (0x00, 0x00, 0x5f): 'MIDI the World', + + (0x00, 0x00, 0x60): 'Desper Products', + (0x00, 0x00, 0x61): 'Micros\'N MIDI', + (0x00, 0x00, 0x62): 'Accordians Intl', + (0x00, 0x00, 0x63): 'EuPhonics', + (0x00, 0x00, 0x64): 'Musonix', + (0x00, 0x00, 0x65): 'Turtle Beach Systems', + (0x00, 0x00, 0x66): 'Mackie Designs', + (0x00, 0x00, 0x67): 'Compuserve', + (0x00, 0x00, 0x68): 'BES Technologies', + (0x00, 0x00, 0x69): 'QRS Music Rolls', + (0x00, 0x00, 0x6a): 'P G Music', + (0x00, 0x00, 0x6b): 'Sierra Semiconductor', + (0x00, 0x00, 0x6c): 'EpiGraf Audio Visual', + (0x00, 0x00, 0x6d): 'Electronics Deiversified', + (0x00, 0x00, 0x6e): 'Tune 1000', + (0x00, 0x00, 0x6f): 'Advanced Micro Devices', + + (0x00, 0x00, 0x70): 'Mediamation', + (0x00, 0x00, 0x71): 'Sabine Music', + (0x00, 0x00, 0x72): 'Woog Labs', + (0x00, 0x00, 0x73): 'Micropolis', + (0x00, 0x00, 0x74): 'Ta Horng Musical Inst.', + (0x00, 0x00, 0x75): 'eTek (formerly Forte)', + (0x00, 0x00, 0x76): 'Electrovoice', + (0x00, 0x00, 0x77): 'Midisoft', + (0x00, 0x00, 0x78): 'Q-Sound Labs', + (0x00, 0x00, 0x79): 'Westrex', + (0x00, 0x00, 0x7a): 'NVidia', + (0x00, 0x00, 0x7b): 'ESS Technology', + (0x00, 0x00, 0x7c): 'MediaTrix Peripherals', + (0x00, 0x00, 0x7d): 'Brooktree', + (0x00, 0x00, 0x7e): 'Otari', + (0x00, 0x00, 0x7f): 'Key Electronics', + + (0x00, 0x01, 0x01): 'Crystalake Multimedia', + (0x00, 0x01, 0x02): 'Crystal Semiconductor', + (0x00, 0x01, 0x03): 'Rockwell Semiconductor', + + # European group (range 20-3f, 002000-003f7f) + (0x20,): 'Passac', + (0x21,): 'SIEL', + (0x22,): 'Synthaxe', + (0x24,): 'Hohner', + (0x25,): 'Twister', + (0x26,): 'Solton', + (0x27,): 'Jellinghaus MS', + (0x28,): 'Southworth Music Systems', + (0x29,): 'PPG', + (0x2a,): 'JEN', + (0x2b,): 'SSL Limited', + (0x2c,): 'Audio Veritrieb', + (0x2f,): 'Elka', + + (0x30,): 'Dynacord', + (0x31,): 'Viscount', + (0x33,): 'Clavia Digital Instruments', + (0x34,): 'Audio Architecture', + (0x35,): 'GeneralMusic Corp.', + (0x39,): 'Soundcraft Electronics', + (0x3b,): 'Wersi', + (0x3c,): 'Avab Elektronik Ab', + (0x3d,): 'Digigram', + (0x3e,): 'Waldorf Electronics', + (0x3f,): 'Quasimidi', + + (0x00, 0x20, 0x00): 'Dream', + (0x00, 0x20, 0x01): 'Strand Lighting', + (0x00, 0x20, 0x02): 'Amek Systems', + (0x00, 0x20, 0x04): 'Böhm Electronic', + (0x00, 0x20, 0x06): 'Trident Audio', + (0x00, 0x20, 0x07): 'Real World Studio', + (0x00, 0x20, 0x09): 'Yes Technology', + (0x00, 0x20, 0x0a): 'Audiomatica', + (0x00, 0x20, 0x0b): 'Bontempi/Farfisa', + (0x00, 0x20, 0x0c): 'F.B.T. Elettronica', + (0x00, 0x20, 0x0d): 'MidiTemp', + (0x00, 0x20, 0x0e): 'LA Audio (Larking Audio)', + (0x00, 0x20, 0x0f): 'Zero 88 Lighting Limited', + + (0x00, 0x20, 0x10): 'Micon Audio Electronics GmbH', + (0x00, 0x20, 0x11): 'Forefront Technology', + (0x00, 0x20, 0x13): 'Kenton Electronics', + (0x00, 0x20, 0x15): 'ADB', + (0x00, 0x20, 0x16): 'Marshall Products', + (0x00, 0x20, 0x17): 'DDA', + (0x00, 0x20, 0x18): 'BSS', + (0x00, 0x20, 0x19): 'MA Lighting Technology', + (0x00, 0x20, 0x1a): 'Fatar', + (0x00, 0x20, 0x1b): 'QSC Audio', + (0x00, 0x20, 0x1c): 'Artisan Classic Organ', + (0x00, 0x20, 0x1d): 'Orla Spa', + (0x00, 0x20, 0x1e): 'Pinnacle Audio', + (0x00, 0x20, 0x1f): 'TC Electronics', + + (0x00, 0x20, 0x20): 'Doepfer Musikelektronik', + (0x00, 0x20, 0x21): 'Creative Technology Pte', + (0x00, 0x20, 0x22): 'Minami/Seiyddo', + (0x00, 0x20, 0x23): 'Goldstar', + (0x00, 0x20, 0x24): 'Midisoft s.a.s di M. Cima', + (0x00, 0x20, 0x25): 'Samick', + (0x00, 0x20, 0x26): 'Penny and Giles', + (0x00, 0x20, 0x27): 'Acorn Computer', + (0x00, 0x20, 0x28): 'LSC Electronics', + (0x00, 0x20, 0x29): 'Novation EMS', + (0x00, 0x20, 0x2a): 'Samkyung Mechatronics', + (0x00, 0x20, 0x2b): 'Medeli Electronics', + (0x00, 0x20, 0x2c): 'Charlie Lab', + (0x00, 0x20, 0x2d): 'Blue Chip Music Tech', + (0x00, 0x20, 0x2e): 'BEE OH Corp', + + # Japanese group (range 40-5f, 004000-005f7f) + (0x40,): 'Kawai', + (0x41,): 'Roland', + (0x42,): 'Korg', + (0x43,): 'Yamaha', + (0x44,): 'Casio', + (0x46,): 'Kamiya Studio', + (0x47,): 'Akai', + (0x48,): 'Japan Victor', + (0x49,): 'Mesosha', + (0x4a,): 'Hoshino Gakki', + (0x4b,): 'Fujitsu Elect', + (0x4c,): 'Sony', + (0x4d,): 'Nisshin Onpa', + (0x4e,): 'TEAC', + (0x50,): 'Matsushita Electric', + (0x51,): 'Fostex', + (0x52,): 'Zoom', + (0x53,): 'Midori Electronics', + (0x54,): 'Matsushita Communication Industrial', + (0x55,): 'Suzuki Musical Inst. Mfg.', + + # Other (range 60-7c, 006000-007f7f) + + # Special (7d-7f) + (0x7d,): 'Non-Commercial', + (0x7e,): 'Universal Non-Realtime', + (0x7f,): 'Universal Realtime', +} + +control_functions = { + 0x00: ['bank select MSB', 'bank MSB', 'bank-M'], + 0x01: ['modulation wheel/lever MSB', 'modulation MSB', 'mod-M'], + 0x02: ['breath controller MSB', 'breath MSB', 'breath-M'], + # 0x03: undefined MSB + 0x04: ['foot controller MSB', 'foot MSB', 'foot-M'], + 0x05: ['portamento time MSB', 'portamento MSB', 'porta-M'], + 0x06: ['data entry MSB', 'data entry MSB', 'data-M'], + 0x07: ['channel volume MSB (formerly main volume)', 'channel volume MSB', 'ch vol-M'], + 0x08: ['balance MSB', 'bal MSB', 'bal-M'], + # 0x09: undefined MSB + 0x0a: ['pan MSB', 'pan MSB', 'pan-M'], + 0x0b: ['expression controller MSB', 'expression MSB', 'expr-M'], + 0x0c: ['effect control 1 MSB', 'effect 1 MSB', 'eff-1-M'], + 0x0d: ['effect control 2 MSB', 'effect 2 MSB', 'eff-2-M'], + # 0x0e-0x0f: undefined MSB + 0x10: ['general purpose controller 1 MSB', 'GP ctrl 1 MSB', 'GPC-1-M'], + 0x11: ['general purpose controller 2 MSB', 'GP ctrl 2 MSB', 'GPC-2-M'], + 0x12: ['general purpose controller 3 MSB', 'GP ctrl 3 MSB', 'GPC-3-M'], + 0x13: ['general purpose controller 4 MSB', 'GP ctrl 4 MSB', 'GPC-4-M'], + # 0x14-0x1f: undefined MSB + 0x20: ['bank select LSB', 'bank LSB', 'bank-L'], + 0x21: ['modulation wheel/lever LSB', 'modulation LSB', 'mod-L'], + 0x22: ['breath controller LSB', 'breath LSB', 'breath-L'], + # 0x23: undefined LSB + 0x24: ['foot controller LSB', 'foot LSB', 'foot-L'], + 0x25: ['portamento time LSB', 'portamento LSB', 'porta-L'], + 0x26: ['data entry LSB', 'data entry LSB', 'data-L'], + 0x27: ['channel volume LSB (formerly main volume)', 'channel volume LSB', 'ch vol-L'], + 0x28: ['balance LSB', 'bal LSB', 'bal-L'], + # 0x29: undefined LSB + 0x2a: ['pan LSB', 'pan LSB', 'pan-L'], + 0x2b: ['expression controller LSB', 'expression LSB', 'expr-L'], + 0x2c: ['effect control 1 LSB', 'effect 1 LSB', 'eff-1-L'], + 0x2d: ['effect control 2 LSB', 'effect 2 LSB', 'eff-2-L'], + # 0x2e-0x2f: undefined LSB + 0x30: ['general purpose controller 1 LSB', 'GP ctrl 1 LSB', 'GPC-1-L'], + 0x31: ['general purpose controller 2 LSB', 'GP ctrl 2 LSB', 'GPC-2-L'], + 0x32: ['general purpose controller 3 LSB', 'GP ctrl 3 LSB', 'GPC-3-L'], + 0x33: ['general purpose controller 4 LSB', 'GP ctrl 4 LSB', 'GPC-4-L'], + # 0x34-0x3f: undefined LSB + 0x40: ['damper pedal (sustain)', 'sustain', 'sust'], + 0x41: ['portamento on/off', 'porta on/off', 'porta on/off'], + 0x42: ['sostenuto', 'sostenuto', 'sostenuto'], + 0x43: ['soft pedal', 'soft pedal', 'soft pedal'], + 0x44: ['legato footswitch', 'legato switch', 'legato'], # vv: 00-3f = normal, 40-7f = legato + 0x45: ['hold 2', 'hold 2', 'hold 2'], + 0x46: ['sound controller 1 (default: sound variation)', 'sound ctrl 1', 'snd ctrl 1'], + 0x47: ['sound controller 2 (default: timbre / harmonic intensity)', 'sound ctrl 2', 'snd ctrl 2'], + 0x48: ['sound controller 3 (default: release time)', 'sound ctrl 3', 'snd ctrl 3'], + 0x49: ['sound controller 4 (default: attack time)', 'sound ctrl 4', 'snd ctrl 4'], + 0x4a: ['sound controller 5 (default: brightness)', 'sound ctrl 5', 'snd ctrl 5'], + 0x4b: ['sound controller 6 (GM2 default: decay time)', 'sound ctrl 6', 'snd ctrl 6'], + 0x4c: ['sound controller 7 (GM2 default: vibrato rate)', 'sound ctrl 7', 'snd ctrl 7'], + 0x4d: ['sound controller 8 (GM2 default: vibrato depth)', 'sound ctrl 8', 'snd ctrl 8'], + 0x4e: ['sound controller 9 (GM2 default: vibrato delay)', 'sound ctrl 9', 'snd ctrl 9'], + 0x4f: ['sound controller 10', 'sound ctrl 10', 'snd ctrl 10'], + 0x50: ['general purpose controller 5', 'GP controller 5', 'GPC-5'], + 0x51: ['general purpose controller 6', 'GP controller 6', 'GPC-6'], + 0x52: ['general purpose controller 7', 'GP controller 7', 'GPC-7'], + 0x53: ['general purpose controller 8', 'GP controller 8', 'GPC-8'], + 0x54: ['portamento control', 'portamento ctrl', 'porta ctrl'], + # 0x55-0x5a: undefined + 0x5b: ['effects 1 depth (formerly external effects depth)', 'effects 1 depth', 'eff 1 depth'], + 0x5c: ['effects 2 depth (formerly tremolo depth)', 'effects 2 depth', 'eff 2 depth'], + 0x5d: ['effects 3 depth (formerly chorus depth)', 'effects 3 depth', 'eff 3 depth'], + 0x5e: ['effects 4 depth (formerly celeste/detune depth)', 'effects 4 depth', 'eff 4 depth'], + 0x5f: ['effects 5 depth (formerly phaser depth)', 'effects 5 depth', 'eff 5 depth'], + 0x60: ['data increment', 'data inc', 'data++'], + 0x61: ['data decrement', 'data dec', 'data--'], + 0x62: ['Non-Registered Parameter Number LSB', 'NRPN LSB', 'NRPN-L'], + 0x63: ['Non-Registered Parameter Number MSB', 'NRPN MSB', 'NRPN-M'], + 0x64: ['Registered Parameter Number LSB', 'RPN LSB', 'RPN-L'], + 0x65: ['Registered Parameter Number MSB', 'RPN MSB', 'RPN-M'], + # 0x66-0x77: undefined + # 0x78-0x7f: reserved for channel mode messages + 0x78: ['all sound off', 'all snd off', 'snd off'], + 0x79: ['reset all controllers', 'reset all ctrls', 'reset ctrls'], + 0x7a: ['local control', 'local ctrl', 'local ctrl'], + 0x7b: ['all notes off', 'notes off', 'notes off'], + 0x7c: ['omni mode off', 'omni off', 'omni off'], # all notes off + 0x7d: ['omni mode on', 'omni on', 'omni on'], # all notes off + 0x7e: ['mono mode on', 'mono on', 'mono'], # mono mode on, all notes off + 0x7f: ['poly mode on', 'poly on', 'poly'], # mono mode off, all notes off +} + +gm_instruments = { + 1: 'Acoustic Grand Piano', + 2: 'Bright Acoustic Piano', + 3: 'Electric Grand Piano', + 4: 'Honky-tonk Piano', + 5: 'Electric Piano 1', + 6: 'Electric Piano 2', + 7: 'Harpsichord', + 8: 'Clavi', + 9: 'Celesta', + 10: 'Glockenspiel', + 11: 'Music Box', + 12: 'Vibraphone', + 13: 'Marimba', + 14: 'Xylophone', + 15: 'Tubular Bells', + 16: 'Dulcimer', + 17: 'Drawbar Organ', + 18: 'Percussive Organ', + 19: 'Rock Organ', + 20: 'Church Organ', + 21: 'Reed Organ', + 22: 'Accordion', + 23: 'Harmonica', + 24: 'Tango Accordion', + 25: 'Acoustic Guitar (nylon)', + 26: 'Acoustic Guitar (steel)', + 27: 'Electric Guitar (jazz)', + 28: 'Electric Guitar (clean)', + 29: 'Electric Guitar (muted)', + 30: 'Overdriven Guitar', + 31: 'Distortion Guitar', + 32: 'Guitar harmonics', + 33: 'Acoustic Bass', + 34: 'Electric Bass (finger)', + 35: 'Electric Bass (pick)', + 36: 'Fretless Bass', + 37: 'Slap Bass 1', + 38: 'Slap Bass 2', + 39: 'Synth Bass 1', + 40: 'Synth Bass 2', + 41: 'Violin', + 42: 'Viola', + 43: 'Cello', + 44: 'Contrabass', + 45: 'Tremolo Strings', + 46: 'Pizzicato Strings', + 47: 'Orchestral Harp', + 48: 'Timpani', + 49: 'String Ensemble 1', + 50: 'String Ensemble 2', + 51: 'SynthStrings 1', + 52: 'SynthStrings 2', + 53: 'Choir Aahs', + 54: 'Voice Oohs', + 55: 'Synth Voice', + 56: 'Orchestra Hit', + 57: 'Trumpet', + 58: 'Trombone', + 59: 'Tuba', + 60: 'Muted Trumpet', + 61: 'French Horn', + 62: 'Brass Section', + 63: 'SynthBrass 1', + 64: 'SynthBrass 2', + 65: 'Soprano Sax', + 66: 'Alto Sax', + 67: 'Tenor Sax', + 68: 'Baritone Sax', + 69: 'Oboe', + 70: 'English Horn', + 71: 'Bassoon', + 72: 'Clarinet', + 73: 'Piccolo', + 74: 'Flute', + 75: 'Recorder', + 76: 'Pan Flute', + 77: 'Blown Bottle', + 78: 'Shakuhachi', + 79: 'Whistle', + 80: 'Ocarina', + 81: 'Lead 1 (square)', + 82: 'Lead 2 (sawtooth)', + 83: 'Lead 3 (calliope)', + 84: 'Lead 4 (chiff)', + 85: 'Lead 5 (charang)', + 86: 'Lead 6 (voice)', + 87: 'Lead 7 (fifths)', + 88: 'Lead 8 (bass + lead)', + 89: 'Pad 1 (new age)', + 90: 'Pad 2 (warm)', + 91: 'Pad 3 (polysynth)', + 92: 'Pad 4 (choir)', + 93: 'Pad 5 (bowed)', + 94: 'Pad 6 (metallic)', + 95: 'Pad 7 (halo)', + 96: 'Pad 8 (sweep)', + 97: 'FX 1 (rain)', + 98: 'FX 2 (soundtrack)', + 99: 'FX 3 (crystal)', + 100: 'FX 4 (atmosphere)', + 101: 'FX 5 (brightness)', + 102: 'FX 6 (goblins)', + 103: 'FX 7 (echoes)', + 104: 'FX 8 (sci-fi)', + 105: 'Sitar', + 106: 'Banjo', + 107: 'Shamisen', + 108: 'Koto', + 109: 'Kalimba', + 110: 'Bag pipe', + 111: 'Fiddle', + 112: 'Shanai', + 113: 'Tinkle Bell', + 114: 'Agogo', + 115: 'Steel Drums', + 116: 'Woodblock', + 117: 'Taiko Drum', + 118: 'Melodic Tom', + 119: 'Synth Drum', + 120: 'Reverse Cymbal', + 121: 'Guitar Fret Noise', + 122: 'Breath Noise', + 123: 'Seashore', + 124: 'Bird Tweet', + 125: 'Telephone Ring', + 126: 'Helicopter', + 127: 'Applause', + 128: 'Gunshot', +} + +drum_kit = { + 1: 'GM Standard Kit', + 9: 'GS Room Kit', + 17: 'GS Power Kit', + 25: 'GS Power Kit', + 26: 'GS TR-808 Kit', + 33: 'GS Jazz Kit', + 41: 'GS Brush Kit', + 49: 'GS Orchestra Kit', + 57: 'GS Sound FX Kit', + 128: 'GS CM-64/CM-32 Kit', +} + +# Each quarter frame type has 2 string names, each shorter than the previous +quarter_frame_type = { + 0: ['frame count LS nibble', 'frame LSN'], + 1: ['frame count MS nibble', 'frame MSN'], + 2: ['seconds LS nibble', 'sec LSN'], + 3: ['seconds MS nibble', 'sec MSN'], + 4: ['minutes LS nibble', 'min LSN'], + 5: ['minutes MS nibble', 'min MSN'], + 6: ['hours LS nibble', 'hrs LSN'], + 7: ['hours MS nibble and SMPTE type', 'hrs MSN'], +} + +smpte_type = { + 0: '24 fps', + 1: '25 fps', + 2: '30 fps (drop-frame)', + 3: '30 fps (non-drop)', +} + +chromatic_notes = { + 0: 'C-1', + 1: 'C#-1', + 2: 'D-1', + 3: 'D#-1', + 4: 'E-1', + 5: 'F-1', + 6: 'F#-1', + 7: 'G-1', + 8: 'G#-1', + 9: 'A-1', + 10: 'A#-1', + 11: 'B-1', + 12: 'C0', + 13: 'C#0', + 14: 'D0', + 15: 'D#0', + 16: 'E0', + 17: 'F0', + 18: 'F#0', + 19: 'G0', + 20: 'G#0', + 21: 'A0', + 22: 'A#0', + 23: 'B0', + 24: 'C1', + 25: 'C#1', + 26: 'D1', + 27: 'D#1', + 28: 'E1', + 29: 'F1', + 30: 'F#1', + 31: 'G1', + 32: 'G#1', + 33: 'A1', + 34: 'A#1', + 35: 'B1', + 36: 'C2', + 37: 'C#2', + 38: 'D2', + 39: 'D#2', + 40: 'E2', + 41: 'F2', + 42: 'F#2', + 43: 'G2', + 44: 'G#2', + 45: 'A2', + 46: 'A#2', + 47: 'B2', + 48: 'C3', + 49: 'C#3', + 50: 'D3', + 51: 'D#3', + 52: 'E3', + 53: 'F3', + 54: 'F#3', + 55: 'G3', + 56: 'G#3', + 57: 'A3', + 58: 'A#3', + 59: 'B3', + 60: 'C4', + 61: 'C#4', + 62: 'D4', + 63: 'D#4', + 64: 'E4', + 65: 'F4', + 66: 'F#4', + 67: 'G4', + 68: 'G#4', + 69: 'A4', + 70: 'A#4', + 71: 'B4', + 72: 'C5', + 73: 'C#5', + 74: 'D5', + 75: 'D#5', + 76: 'E5', + 77: 'F5', + 78: 'F#5', + 79: 'G5', + 80: 'G#5', + 81: 'A5', + 82: 'A#5', + 83: 'B5', + 84: 'C6', + 85: 'C#6', + 86: 'D6', + 87: 'D#6', + 88: 'E6', + 89: 'F6', + 90: 'F#6', + 91: 'G6', + 92: 'G#6', + 93: 'A6', + 94: 'A#6', + 95: 'B6', + 96: 'C7', + 97: 'C#7', + 98: 'D7', + 99: 'D#7', + 100: 'E7', + 101: 'F7', + 102: 'F#7', + 103: 'G7', + 104: 'G#7', + 105: 'A7', + 106: 'A#7', + 107: 'B7', + 108: 'C8', + 109: 'C#8', + 110: 'D8', + 111: 'D#8', + 112: 'E8', + 113: 'F8', + 114: 'F#8', + 115: 'G8', + 116: 'G#8', + 117: 'A8', + 118: 'A#8', + 119: 'B8', + 120: 'C9', + 121: 'C#9', + 122: 'D9', + 123: 'D#9', + 124: 'E9', + 125: 'F9', + 126: 'F#9', + 127: 'G9', +} + +percussion_notes = { + 35: 'Acoustic Bass Drum', + 36: 'Bass Drum 1', + 37: 'Side Stick', + 38: 'Acoustic Snare', + 39: 'Hand Clap', + 40: 'Electric Snare', + 41: 'Low Floor Tom', + 42: 'Closed Hi Hat', + 43: 'High Floor Tom', + 44: 'Pedal Hi-Hat', + 45: 'Low Tom', + 46: 'Open Hi-Hat', + 47: 'Low-Mid Tom', + 48: 'Hi Mid Tom', + 49: 'Crash Cymbal 1', + 50: 'High Tom', + 51: 'Ride Cymbal 1', + 52: 'Chinese Cymbal', + 53: 'Ride Bell', + 54: 'Tambourine', + 55: 'Splash Cymbal', + 56: 'Cowbell', + 57: 'Crash Cymbal 2', + 58: 'Vibraslap', + 59: 'Ride Cymbal 2', + 60: 'Hi Bongo', + 61: 'Low Bongo', + 62: 'Mute Hi Conga', + 63: 'Open Hi Conga', + 64: 'Low Conga', + 65: 'High Timbale', + 66: 'Low Timbale', + 67: 'High Agogo', + 68: 'Low Agogo', + 69: 'Cabasa', + 70: 'Maracas', + 71: 'Short Whistle', + 72: 'Long Whistle', + 73: 'Short Guiro', + 74: 'Long Guiro', + 75: 'Claves', + 76: 'Hi Wood Block', + 77: 'Low Wood Block', + 78: 'Mute Cuica', + 79: 'Open Cuica', + 80: 'Mute Triangle', + 81: 'Open Triangle', +} diff --git a/libsigrokdecode4DSL/decoders/midi/pd.py b/libsigrokdecode4DSL/decoders/midi/pd.py new file mode 100644 index 00000000..ae35e123 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/midi/pd.py @@ -0,0 +1,627 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2013-2016 Uwe Hermann +## Copyright (C) 2016 Chris Dreher +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd +from .lists import * + +RX = 0 +TX = 1 + +class Decoder(srd.Decoder): + api_version = 3 + id = 'midi' + name = 'MIDI' + longname = 'Musical Instrument Digital Interface' + desc = 'Musical Instrument Digital Interface (MIDI) protocol.' + license = 'gplv2+' + inputs = ['uart'] + outputs = [] + tags = ['Audio', 'PC'] + annotations = ( + ('text-verbose', 'Human-readable text (verbose)'), + ('text-sysreal-verbose', 'Human-readable SysReal text (verbose)'), + ('text-error', 'Human-readable Error text'), + ) + annotation_rows = ( + ('normal', 'Normal', (0, 2)), + ('sys-real', 'SysReal', (1,)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.state = 'IDLE' + self.status_byte = 0 + self.explicit_status_byte = False + self.cmd = [] + self.ss = None + self.es = None + self.ss_block = None + self.es_block = None + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def putx(self, data): + self.put(self.ss_block, self.es_block, self.out_ann, data) + + def get_note_name(self, channel, note): + if channel != 10: + return chromatic_notes[note] + else: + return 'assuming ' + percussion_notes.get(note, 'undefined') + + def check_for_garbage_flush(self, is_flushed): + if is_flushed: + if self.explicit_status_byte: + self.cmd.insert(0, self.status_byte) + self.handle_garbage_msg(None) + + def soft_clear_status_byte(self): + self.explicit_status_byte = False + + def hard_clear_status_byte(self): + self.status_byte = 0 + self.explicit_status_byte = False + + def set_status_byte(self, newbyte): + self.status_byte = newbyte + self.explicit_status_byte = True + + def handle_channel_msg_0x80(self, is_flushed): + # Note off: 8n kk vv + # n = channel, kk = note, vv = velocity + c = self.cmd + if len(c) < 2: + self.check_for_garbage_flush(is_flushed) + return + self.es_block = self.es + msg, chan = self.status_byte & 0xf0, (self.status_byte & 0x0f) + 1 + note, velocity = c[0], c[1] + note_name = self.get_note_name(chan, note) + self.putx([0, ['Channel %d: %s (note = %d \'%s\', velocity = %d)' % \ + (chan, status_bytes[msg][0], note, note_name, velocity), + 'ch %d: %s %d, velocity = %d' % \ + (chan, status_bytes[msg][1], note, velocity), + '%d: %s %d, vel %d' % \ + (chan, status_bytes[msg][2], note, velocity)]]) + self.cmd, self.state = [], 'IDLE' + self.soft_clear_status_byte() + + def handle_channel_msg_0x90(self, is_flushed): + # Note on: 9n kk vv + # n = channel, kk = note, vv = velocity + # If velocity == 0 that actually means 'note off', though. + c = self.cmd + if len(c) < 2: + self.check_for_garbage_flush(is_flushed) + return + self.es_block = self.es + msg, chan = self.status_byte & 0xf0, (self.status_byte & 0x0f) + 1 + note, velocity = c[0], c[1] + s = status_bytes[0x80] if (velocity == 0) else status_bytes[msg] + note_name = self.get_note_name(chan, note) + self.putx([0, ['Channel %d: %s (note = %d \'%s\', velocity = %d)' % \ + (chan, s[0], note, note_name, velocity), + 'ch %d: %s %d, velocity = %d' % \ + (chan, s[1], note, velocity), + '%d: %s %d, vel %d' % \ + (chan, s[2], note, velocity)]]) + self.cmd, self.state = [], 'IDLE' + self.soft_clear_status_byte() + + def handle_channel_msg_0xa0(self, is_flushed): + # Polyphonic key pressure / aftertouch: An kk vv + # n = channel, kk = polyphonic key pressure, vv = pressure value + c = self.cmd + if len(c) < 2: + self.check_for_garbage_flush(is_flushed) + return + self.es_block = self.es + msg, chan = self.status_byte & 0xf0, (self.status_byte & 0x0f) + 1 + note, pressure = c[0], c[1] + note_name = self.get_note_name(chan, note) + self.putx([0, ['Channel %d: %s of %d for note = %d \'%s\'' % \ + (chan, status_bytes[msg][0], pressure, note, note_name), + 'ch %d: %s %d for note %d' % \ + (chan, status_bytes[msg][1], pressure, note), + '%d: %s %d, N %d' % \ + (chan, status_bytes[msg][2], pressure, note)]]) + self.cmd, self.state = [], 'IDLE' + self.soft_clear_status_byte() + + def handle_controller_0x44(self): + # Legato footswitch: Bn 44 vv + # n = channel, vv = value (<= 0x3f: normal, > 0x3f: legato) + c = self.cmd + msg, chan = self.status_byte & 0xf0, (self.status_byte & 0x0f) + 1 + vv = c[1] + t = ('normal', 'no') if vv <= 0x3f else ('legato', 'yes') + self.putx([0, ['Channel %d: %s \'%s\' = %s' % \ + (chan, status_bytes[msg][0], + control_functions[0x44][0], t[0]), + 'ch %d: %s \'%s\' = %s' % \ + (chan, status_bytes[msg][1], + control_functions[0x44][1], t[0]), + '%d: %s \'%s\' = %s' % \ + (chan, status_bytes[msg][2], + control_functions[0x44][2], t[1])]]) + + def handle_controller_0x54(self): + # Portamento control (PTC): Bn 54 kk + # n = channel, kk = source note for pitch reference + c = self.cmd + msg, chan = self.status_byte & 0xf0, (self.status_byte & 0x0f) + 1 + kk = c[1] + kk_name = self.get_note_name(chan, kk) + self.putx([0, ['Channel %d: %s \'%s\' (source note = %d / %s)' % \ + (chan, status_bytes[msg][0], + control_functions[0x54][0], kk, kk_name), + 'ch %d: %s \'%s\' (source note = %d)' % \ + (chan, status_bytes[msg][1], + control_functions[0x54][1], kk), + '%d: %s \'%s\' (src N %d)' % \ + (chan, status_bytes[msg][2], + control_functions[0x54][2], kk)]]) + + def handle_controller_generic(self): + c = self.cmd + msg, chan = self.status_byte & 0xf0, (self.status_byte & 0x0f) + 1 + fn, param = c[0], c[1] + default_name = 'undefined' + ctrl_fn = control_functions.get(fn, default_name) + if ctrl_fn == default_name: + ctrl_fn = ('undefined 0x%02x' % fn, 'undef 0x%02x' % fn, '0x%02x' % fn) + self.putx([0, ['Channel %d: %s \'%s\' (param = 0x%02x)' % \ + (chan, status_bytes[msg][0], ctrl_fn[0], param), + 'ch %d: %s \'%s\' (param = 0x%02x)' % \ + (chan, status_bytes[msg][1], ctrl_fn[1], param), + '%d: %s \'%s\' is 0x%02x' % \ + (chan, status_bytes[msg][2], ctrl_fn[2], param)]]) + + def handle_channel_mode(self): + # Channel Mode: Bn mm vv + # n = channel, mm = mode number (120 - 127), vv = value + c = self.cmd + msg, chan = self.status_byte & 0xf0, (self.status_byte & 0x0f) + 1 + mm, vv = c[0], c[1] + mode_fn = control_functions.get(mm, ('undefined', 'undef', 'undef')) + # Decode the value based on the mode number. + vv_string = ('', '') + if mm == 122: # mode = local control? + if vv == 0: + vv_string = ('off', 'off') + elif vv == 127: # mode = poly mode on? + vv_string = ('on', 'on') + else: + vv_string = ('(non-standard param value of 0x%02x)' % vv, + '0x%02x' % vv) + elif mm == 126: # mode = mono mode on? + if vv != 0: + vv_string = ('(%d channels)' % vv, '(%d ch)' % vv) + else: + vv_string = ('(channels \'basic\' through 16)', + '(ch \'basic\' thru 16)') + elif vv != 0: # All other channel mode messages expect vv == 0. + vv_string = ('(non-standard param value of 0x%02x)' % vv, + '0x%02x' % vv) + self.putx([0, ['Channel %d: %s \'%s\' %s' % \ + (chan, status_bytes[msg][0], mode_fn[0], vv_string[0]), + 'ch %d: %s \'%s\' %s' % \ + (chan, status_bytes[msg][1], mode_fn[1], vv_string[1]), + '%d: %s \'%s\' %s' % \ + (chan, status_bytes[msg][2], mode_fn[2], vv_string[1])]]) + self.cmd, self.state = [], 'IDLE' + self.soft_clear_status_byte() + + def handle_channel_msg_0xb0(self, is_flushed): + # Control change (or channel mode messages): Bn cc vv + # n = channel, cc = control number (0 - 119), vv = control value + c = self.cmd + if len(c) < 2: + self.check_for_garbage_flush(is_flushed) + return + self.es_block = self.es + if c[0] in range(0x78, 0x7f + 1): + self.handle_channel_mode() + return + handle_ctrl = getattr(self, 'handle_controller_0x%02x' % c[0], + self.handle_controller_generic) + handle_ctrl() + self.cmd, self.state = [], 'IDLE' + self.soft_clear_status_byte() + + def handle_channel_msg_0xc0(self, is_flushed): + # Program change: Cn pp + # n = channel, pp = program number (0 - 127) + c = self.cmd + if len(c) < 1: + self.check_for_garbage_flush(is_flushed) + return + self.es_block = self.es + msg, chan = self.status_byte & 0xf0, (self.status_byte & 0x0f) + 1 + pp = self.cmd[0] + 1 + change_type = 'instrument' + name = '' + if chan != 10: # channel != percussion + name = gm_instruments.get(pp, 'undefined') + else: + change_type = 'drum kit' + name = drum_kit.get(pp, 'undefined') + self.putx([0, ['Channel %d: %s to %s %d (assuming %s)' % \ + (chan, status_bytes[msg][0], change_type, pp, name), + 'ch %d: %s to %s %d' % \ + (chan, status_bytes[msg][1], change_type, pp), + '%d: %s %d' % \ + (chan, status_bytes[msg][2], pp)]]) + self.cmd, self.state = [], 'IDLE' + self.soft_clear_status_byte() + + def handle_channel_msg_0xd0(self, is_flushed): + # Channel pressure / aftertouch: Dn vv + # n = channel, vv = pressure value + c = self.cmd + if len(c) < 1: + self.check_for_garbage_flush(is_flushed) + return + self.es_block = self.es + msg, chan = self.status_byte & 0xf0, (self.status_byte & 0x0f) + 1 + vv = self.cmd[0] + self.putx([0, ['Channel %d: %s %d' % (chan, status_bytes[msg][0], vv), + 'ch %d: %s %d' % (chan, status_bytes[msg][1], vv), + '%d: %s %d' % (chan, status_bytes[msg][2], vv)]]) + self.cmd, self.state = [], 'IDLE' + self.soft_clear_status_byte() + + def handle_channel_msg_0xe0(self, is_flushed): + # Pitch bend change: En ll mm + # n = channel, ll = pitch bend change LSB, mm = pitch bend change MSB + c = self.cmd + if len(c) < 2: + self.check_for_garbage_flush(is_flushed) + return + self.es_block = self.es + msg, chan = self.status_byte & 0xf0, (self.status_byte & 0x0f) + 1 + ll, mm = self.cmd[0], self.cmd[1] + decimal = (mm << 7) + ll + self.putx([0, ['Channel %d: %s 0x%02x 0x%02x (%d)' % \ + (chan, status_bytes[msg][0], ll, mm, decimal), + 'ch %d: %s 0x%02x 0x%02x (%d)' % \ + (chan, status_bytes[msg][1], ll, mm, decimal), + '%d: %s (%d)' % \ + (chan, status_bytes[msg][2], decimal)]]) + self.cmd, self.state = [], 'IDLE' + self.soft_clear_status_byte() + + def handle_channel_msg_generic(self, is_flushed): + # TODO: It should not be possible to hit this code. + # It currently can not be unit tested. + msg_type = self.status_byte & 0xf0 + self.es_block = self.es + self.putx([2, ['Unknown channel message type: 0x%02x' % msg_type]]) + self.cmd, self.state = [], 'IDLE' + self.soft_clear_status_byte() + + def handle_channel_msg(self, newbyte): + if newbyte is not None: + if newbyte >= 0x80: + self.set_status_byte(newbyte) + else: + self.cmd.append(newbyte) + msg_type = self.status_byte & 0xf0 + handle_msg = getattr(self, 'handle_channel_msg_0x%02x' % msg_type, + self.handle_channel_msg_generic) + handle_msg(newbyte is None) + + def handle_sysex_msg(self, newbyte): + # SysEx message: 1 status byte, 1-3 manuf. bytes, x data bytes, EOX byte + # + # SysEx messages are variable length, can be terminated by EOX or + # by any non-SysReal status byte, and it clears self.status_byte. + # + # Note: All System message codes don't utilize self.status_byte. + self.hard_clear_status_byte() + if newbyte != 0xf7 and newbyte is not None: # EOX + self.cmd.append(newbyte) + return + self.es_block = self.es + # Note: Unlike other methods, this code pops bytes out of self.cmd + # to isolate the data. + msg = self.cmd.pop(0) + if len(self.cmd) < 1: + self.putx([2, ['%s: truncated manufacturer code (<1 bytes)' % \ + status_bytes[msg][0], + '%s: truncated manufacturer (<1 bytes)' % \ + status_bytes[msg][1], + '%s: trunc. manu.' % status_bytes[msg][2]]]) + self.cmd, self.state = [], 'IDLE' + return + # Extract the manufacturer name (or SysEx realtime or non-realtime). + m1 = self.cmd.pop(0) + manu = (m1,) + if m1 == 0x00: # If byte == 0, then 2 more manufacturer bytes follow. + if len(self.cmd) < 2: + self.putx([2, ['%s: truncated manufacturer code (<3 bytes)' % \ + status_bytes[msg][0], + '%s: truncated manufacturer (<3 bytes)' % \ + status_bytes[msg][1], + '%s: trunc. manu.' % status_bytes[msg][2]]]) + self.cmd, self.state = [], 'IDLE' + return + manu = (m1, self.cmd.pop(0), self.cmd.pop(0)) + default_name = 'undefined' + manu_name = sysex_manufacturer_ids.get(manu, default_name) + if manu_name == default_name: + if len(manu) == 3: + manu_name = ('%s (0x%02x 0x%02x 0x%02x)' % \ + (default_name, manu[0], manu[1], manu[2]), + default_name) + else: + manu_name = ('%s (0x%02x)' % (default_name, manu[0]), + default_name) + else: + manu_name = (manu_name, manu_name) + # Extract the payload, display in 1 of 2 formats + # TODO: Write methods to decode SysEx realtime & non-realtime payloads. + payload0 = '' + payload1 = '' + while len(self.cmd) > 0: + byte = self.cmd.pop(0) + payload0 += '0x%02x ' % (byte) + payload1 += '%02x ' % (byte) + if payload0 == '': + payload0 = '' + payload1 = '<>' + payload = (payload0, payload1) + self.putx([0, ['%s: for \'%s\' with payload %s' % \ + (status_bytes[msg][0], manu_name[0], payload[0]), + '%s: \'%s\', payload %s' % \ + (status_bytes[msg][1], manu_name[1], payload[1]), + '%s: \'%s\', payload %s' % \ + (status_bytes[msg][2], manu_name[1], payload[1])]]) + self.cmd, self.state = [], 'IDLE' + + def handle_syscommon_midi_time_code_quarter_frame_msg(self, newbyte): + # MIDI time code quarter frame: F1 nd + # n = message type + # d = values + # + # Note: All System message codes don't utilize self.status_byte, + # and System Exclusive and System Common clear it. + c = self.cmd + if len(c) < 2: + if newbyte is None: + self.handle_garbage_msg(None) + return + msg = c[0] + nn, dd = (c[1] & 0x70) >> 4, c[1] & 0x0f + group = ('System Common', 'SysCom', 'SC') + self.es_block = self.es + if nn != 7: # If message type does not contain SMPTE type. + self.putx([0, ['%s: %s of %s, value 0x%01x' % \ + (group[0], status_bytes[msg][0], + quarter_frame_type[nn][0], dd), + '%s: %s of %s, value 0x%01x' % \ + (group[1], status_bytes[msg][1], + quarter_frame_type[nn][1], dd), + '%s: %s of %s, value 0x%01x' % \ + (group[2], status_bytes[msg][2], + quarter_frame_type[nn][1], dd)]]) + self.cmd, self.state = [], 'IDLE' + return + tt = (dd & 0x6) >> 1 + self.putx([0, ['%s: %s of %s, value 0x%01x for %s' % \ + (group[0], status_bytes[msg][0], \ + quarter_frame_type[nn][0], dd, smpte_type[tt]), + '%s: %s of %s, value 0x%01x for %s' % \ + (group[1], status_bytes[msg][1], \ + quarter_frame_type[nn][1], dd, smpte_type[tt]), + '%s: %s of %s, value 0x%01x for %s' % \ + (group[2], status_bytes[msg][2], \ + quarter_frame_type[nn][1], dd, smpte_type[tt])]]) + self.cmd, self.state = [], 'IDLE' + + def handle_syscommon_msg(self, newbyte): + # System common messages + # + # There are 5 simple formats (which are directly handled here) and + # 1 complex one called MIDI time code quarter frame. + # + # Note: While the MIDI lists 0xf7 as a "system common" message, it + # is actually only used with SysEx messages so it is processed there. + # + # Note: All System message codes don't utilize self.status_byte. + self.hard_clear_status_byte() + if newbyte is not None: + self.cmd.append(newbyte) + c = self.cmd + msg = c[0] + group = ('System Common', 'SysCom', 'SC') + if msg == 0xf1: + # MIDI time code quarter frame + self.handle_syscommon_midi_time_code_quarter_frame_msg(newbyte) + return + elif msg == 0xf2: + # Song position pointer: F2 ll mm + # ll = LSB position, mm = MSB position + if len(c) < 3: + if newbyte is None: + self.handle_garbage_msg(None) + return + ll, mm = c[1], c[2] + decimal = (mm << 7) + ll + self.es_block = self.es + self.putx([0, ['%s: %s 0x%02x 0x%02x (%d)' % \ + (group[0], status_bytes[msg][0], ll, mm, decimal), + '%s: %s 0x%02x 0x%02x (%d)' % \ + (group[1], status_bytes[msg][1], ll, mm, decimal), + '%s: %s (%d)' % \ + (group[2], status_bytes[msg][2], decimal)]]) + elif msg == 0xf3: + # Song select: F3 ss + # ss = song selection number + if len(c) < 2: + if newbyte is None: + self.handle_garbage_msg(None) + return + ss = c[1] + self.es_block = self.es + self.putx([0, ['%s: %s number %d' % \ + (group[0], status_bytes[msg][0], ss), + '%s: %s number %d' % \ + (group[1], status_bytes[msg][1], ss), + '%s: %s # %d' % \ + (group[2], status_bytes[msg][2], ss)]]) + elif msg == 0xf4 or msg == 0xf5 or msg == 0xf6: + # Undefined 0xf4, Undefined 0xf5, and Tune Request (respectively). + # All are only 1 byte long with no data bytes. + self.es_block = self.es + self.putx([0, ['%s: %s' % (group[0], status_bytes[msg][0]), + '%s: %s' % (group[1], status_bytes[msg][1]), + '%s: %s' % (group[2], status_bytes[msg][2])]]) + self.cmd, self.state = [], 'IDLE' + + def handle_sysrealtime_msg(self, newbyte): + # System realtime message: 0b11111ttt (t = message type) + # + # Important: These messages are handled differently from all others + # because they are allowed to temporarily interrupt other messages. + # The interrupted messages resume after the realtime message is done. + # Thus, they mostly leave 'self' the way it was found. + # + # Note: All System message codes don't utilize self.status_byte. + old_ss_block, old_es_block = self.ss_block, self.es_block + self.ss_block, self.es_block = self.ss, self.es + group = ('System Realtime', 'SysReal', 'SR') + self.putx([1, ['%s: %s' % (group[0], status_bytes[newbyte][0]), + '%s: %s' % (group[1], status_bytes[newbyte][1]), + '%s: %s' % (group[2], status_bytes[newbyte][2])]]) + self.ss_block, self.es_block = old_ss_block, old_es_block + # Deliberately not resetting self.cmd or self.state. + + def handle_garbage_msg(self, newbyte): + # Handle messages that are either not handled or are corrupt. + self.es_block = self.es + if newbyte is not None: + self.cmd.append(newbyte) + return + payload = '' + max_bytes = 16 # Put a limit on the length on the hex dump. + for index in range(len(self.cmd)): + if index == max_bytes: + payload += ' ...' + break + if index == 0: + payload = '0x%02x' % self.cmd[index] + else: + payload += ' 0x%02x' % self.cmd[index] + self.putx([2, ['UNHANDLED DATA: %s' % payload, + 'UNHANDLED', '???', '?']]) + self.cmd, self.state = [], 'IDLE' + self.hard_clear_status_byte() + + def handle_state(self, state, newbyte): + # 'newbyte' can either be: + # 1. Value between 0x00-0xff, deal with the byte normally. + # 2. Value of 'None' which means "flush any buffered data". + if state == 'HANDLE CHANNEL MSG': + self.handle_channel_msg(newbyte) + elif state == 'HANDLE SYSEX MSG': + self.handle_sysex_msg(newbyte) + elif state == 'HANDLE SYSCOMMON MSG': + self.handle_syscommon_msg(newbyte) + elif state == 'HANDLE SYSREALTIME MSG': + self.handle_sysrealtime_msg(newbyte) + elif state == 'BUFFER GARBAGE MSG': + self.handle_garbage_msg(newbyte) + + def get_next_state(self, newbyte): + # 'newbyte' must be a valid byte between 0x00 and 0xff. + # + # Try to determine the state based off of the 'newbyte' parameter. + if newbyte in range(0x80, 0xef + 1): + return 'HANDLE CHANNEL MSG' + if newbyte == 0xf0: + return 'HANDLE SYSEX MSG' + if newbyte in range(0xf1, 0xf7): + return'HANDLE SYSCOMMON MSG' + if newbyte in range(0xf8, 0xff + 1): + return 'HANDLE SYSREALTIME MSG' + # Passing 0xf7 is an error; messages don't start with 0xf7. + if newbyte == 0xf7: + return 'BUFFER GARBAGE MSG' + # Next, base the state off of self.status_byte. + if self.status_byte < 0x80: + return 'BUFFER GARBAGE MSG' + return self.get_next_state(self.status_byte) + + def decode(self, ss, es, data): + ptype, rxtx, pdata = data + state = 'IDLE' + + # For now, ignore all UART packets except the actual data packets. + if ptype != 'DATA': + return + + # We're only interested in the byte value (not individual bits). + pdata = pdata[0] + + # Short MIDI overview: + # - Status bytes are 0x80-0xff, data bytes are 0x00-0x7f. + # - Most messages: 1 status byte, 1-2 data bytes. + # - Real-time system messages: always 1 byte. + # - SysEx messages: 1 status byte, n data bytes, EOX byte. + # + # Aspects of the MIDI protocol that complicate decoding: + # - MIDI System Realtime messages can briefly interrupt other + # messages already in progress. + # - "Running Status" allows for omitting the status byte in most + # scenarios if sequential messages have the same status byte. + # - System Exclusive (SysEx) messages can be terminated by ANY + # status byte (not limited to EOX byte). + + # State machine. + if pdata >= 0x80 and pdata != 0xf7: + state = self.get_next_state(pdata) + if state != 'HANDLE SYSREALTIME MSG' and self.state != 'IDLE': + # Flush the previous data since a new message is starting. + self.handle_state(self.state, None) + # Cache ss and es -after- flushing previous data. + self.ss, self.es = ss, es + # This is a status byte, remember the start sample. + if state != 'HANDLE SYSREALTIME MSG': + self.ss_block = ss + elif self.state == 'IDLE' or self.state == 'BUFFER GARBAGE MSG': + # Deal with "running status" or that we're buffering garbage. + self.ss, self.es = ss, es + if self.state == 'IDLE': + self.ss_block = ss + state = self.get_next_state(pdata) + else: + self.ss, self.es = ss, es + state = self.state + + # Yes, this is intentionally _not_ an 'elif' here. + if state != 'HANDLE SYSREALTIME MSG': + self.state = state + if state == 'BUFFER GARBAGE MSG': + self.status_byte = 0 + self.handle_state(state, pdata) diff --git a/libsigrokdecode4DSL/decoders/miller/__init__.py b/libsigrokdecode4DSL/decoders/miller/__init__.py new file mode 100644 index 00000000..ce0d4941 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/miller/__init__.py @@ -0,0 +1,26 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2017 Christoph Rackwitz +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +The Miller protocol decoder supports (modified) Miller encoded data. + +E.g. used in NFC communication at 106 kbaud. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/miller/pd.py b/libsigrokdecode4DSL/decoders/miller/pd.py new file mode 100644 index 00000000..f41711f1 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/miller/pd.py @@ -0,0 +1,190 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2017 Christoph Rackwitz +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +# http://www.gorferay.com/type-a-communications-interface/ +# https://resources.infosecinstitute.com/introduction-rfid-security/ +# https://www.radio-electronics.com/info/wireless/nfc/near-field-communications-modulation-rf-signal-interface.php +# https://www.researchgate.net/figure/Modified-Miller-Code_fig16_283498836 + +# Miller: either edge +# modified Miller: falling edge + +import sigrokdecode as srd + +def roundto(x, k=1.0): + return round(x / k) * k + +class Decoder(srd.Decoder): + api_version = 3 + id = 'miller' + name = 'Miller' + longname = 'Miller encoding' + desc = 'Miller encoding protocol.' + license = 'gplv2+' + inputs = ['logic'] + outputs = [] + tags = ['Encoding'] + channels = ( + {'id': 'data', 'name': 'Data', 'desc': 'Data signal'}, + ) + options = ( + {'id': 'baudrate', 'desc': 'Baud rate', 'default': 106000}, + {'id': 'edge', 'desc': 'Edge', 'default': 'falling', 'values': ('rising', 'falling', 'either')}, + ) + annotations = ( + ('bit', 'Bit'), + ('bitstring', 'Bitstring'), + ) + annotation_rows = tuple((u, v, (i,)) for i, (u, v) in enumerate(annotations)) + binary = ( + ('raw', 'Raw binary'), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.samplerate = None + + def metadata(self, key, value): + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + self.out_binary = self.register(srd.OUTPUT_BINARY) + + def decode_bits(self): + timeunit = self.samplerate / self.options['baudrate'] + edgetype = self.options['edge'][0] + + self.wait({0: edgetype}) # first symbol, beginning of unit + prevedge = self.samplenum + + # start of message: '0' + prevbit = 0 + yield (0, prevedge, prevedge + timeunit) + expectedstart = self.samplenum + timeunit + + # end of message: '0' followed by one idle symbol + + while True: + self.wait([{0: edgetype}, {'skip': int(3 * timeunit)}]) + got_timeout = (self.matched & (0b1 << 1)) + sampledelta = (self.samplenum - prevedge) + prevedge = self.samplenum + timedelta = roundto(sampledelta / timeunit, 0.5) + + # a mark stands for a 1 bit + # a mark has an edge in the middle + + # a space stands for a 0 bit + # a space either has an edge at the beginning or no edge at all + # after a mark, a space is edge-less + # after a space, a space has an edge + + # we get 1.0, 1.5, 2.0 times between edges + + # end of transmission is always a space, either edged or edge-less + + if prevbit == 0: # space -> ??? + if timedelta == 1.0: # 1.0 units -> space + yield (0, self.samplenum, self.samplenum + timeunit) + prevbit = 0 + expectedstart = self.samplenum + timeunit + elif timedelta == 1.5: # 1.5 units -> mark + yield (1, expectedstart, self.samplenum + 0.5*timeunit) + prevbit = 1 + expectedstart = self.samplenum + timeunit*0.5 + elif timedelta >= 2.0: + # idle symbol (end of message) + yield None + else: + # assert timedelta >= 2.0 + yield (False, self.samplenum - sampledelta, self.samplenum) + break + else: # mark -> ??? + if timedelta <= 0.5: + yield (False, self.samplenum - sampledelta, self.samplenum) + break + if timedelta == 1.0: # 1.0 units -> mark again (1.5 from start) + yield (1, expectedstart, self.samplenum + 0.5*timeunit) + prevbit = 1 + expectedstart = self.samplenum + 0.5*timeunit + elif timedelta == 1.5: # 1.5 units -> space (no pulse) and space (pulse) + yield (0, expectedstart, self.samplenum) + yield (0, self.samplenum, self.samplenum + timeunit) + prevbit = 0 + expectedstart = self.samplenum + timeunit + elif timedelta == 2.0: # 2.0 units -> space (no pulse) and mark (pulse) + yield (0, expectedstart, expectedstart + timeunit) + yield (1, self.samplenum - 0.5*timeunit, self.samplenum + 0.5*timeunit) + prevbit = 1 + expectedstart = self.samplenum + timeunit*0.5 + else: # longer -> space and end of message + yield (0, expectedstart, expectedstart + timeunit) + yield None + break + + def decode_run(self): + numbits = 0 + bitvalue = 0 + bitstring = '' + stringstart = None + stringend = None + + for bit in self.decode_bits(): + if bit is None: + break + + (value, ss, es) = bit + + if value is False: + self.put(int(ss), int(es), self.out_ann, [1, ['ERROR']]) + else: + self.put(int(ss), int(es), self.out_ann, [0, ['{}'.format(value)]]) + + if value is False: + numbits = 0 + break + + if stringstart is None: + stringstart = ss + + stringend = es + + bitvalue |= value << numbits + numbits += 1 + + bitstring += '{}'.format(value) + if numbits % 4 == 0: + bitstring += ' ' + + if not numbits: + return + + self.put(int(stringstart), int(stringend), self.out_ann, [1, ['{}'.format(bitstring)]]) + + numbytes = numbits // 8 + (numbits % 8 > 0) + bytestring = bitvalue.to_bytes(numbytes, 'little') + self.put(int(stringstart), int(stringend), self.out_binary, [0, bytestring]) + + def decode(self): + while True: + self.decode_run() diff --git a/libsigrokdecode4DSL/decoders/mipi_dsi/__init__.py b/libsigrokdecode4DSL/decoders/mipi_dsi/__init__.py new file mode 100644 index 00000000..5935127a --- /dev/null +++ b/libsigrokdecode4DSL/decoders/mipi_dsi/__init__.py @@ -0,0 +1,24 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2012 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' + +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/mipi_dsi/pd.py b/libsigrokdecode4DSL/decoders/mipi_dsi/pd.py new file mode 100644 index 00000000..a7e1f71d --- /dev/null +++ b/libsigrokdecode4DSL/decoders/mipi_dsi/pd.py @@ -0,0 +1,218 @@ +## +## This file is part of the libsigrokdecode project. +## Author: Shiqiu Nie (369614718@qq.com) +## Version: 0.2 +## Date: 2019-07-01 +## History: +## 1. 2019-07-01 Create decoder +## 2. 2017-01-17 V0.1,Decode EscMode,BTA,Data,Stop,Idle +## 3. 2019-07-01 V0.2,Support for V3 decoder +## +## Copyright (C) 2010-2016 Uwe Hermann +## Copyright (C) 2019 DreamSourceLab +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +# TODO: + +import sigrokdecode as srd + +''' +OUTPUT_PYTHON format: + +Packet: +[, ] + +: + + + is the data or address byte associated with the 'ADDRESS*' and 'DATA*' +command. +''' + +# CMD: [annotation-type-index, long annotation, short annotation] +proto = { + 'ESC Mode': [0, 'Escape mode entry', 'ESC'], + 'BTA': [1, 'Bi-directional Data Lane Turnaround','BTA'], + 'DATA': [2, 'Data', 'Data'], + 'STOP': [3, 'Stop', 'S'], + 'LPDT': [4, 'LPDT Command', 'LPDT'], + 'DI': [5, 'Data Identifier', 'DI'], + 'ECC': [6, 'Error Correction Code', 'ECC'], + 'WC': [7, 'Word count', 'WC'], + 'CRC': [8, 'CheckSUM', 'CRC'], + 'ULPS': [9, 'Ultra-Low Power State', 'ULPS'], + 'IDLE': [10,'Idle', 'Idle'], +} +# LP state +lp_state = ['LP-00','LP-01','LP-10','LP-11'] + +class Decoder(srd.Decoder): + api_version = 3 + id = 'MIPI_DSI' + name = 'MIPI_DSI' + longname = 'MIPI Display Serial Interface' + desc = 'MIPI Display Serial Interface low power communication' + license = 'gplv2+' + inputs = ['logic'] + outputs = ['mipi_dsi'] + tags = ['Embedded/industrial'] + channels = ( + {'id': 'D0N', 'type': 8, 'name': 'D0N', 'desc': 'LP data 0 neg'}, + {'id': 'D0P', 'type': 108, 'name': 'D0P', 'desc': 'LP data 0 pos'}, + ) + options = ( + + ) + annotations = ( + ('111', 'LP-00', 'LP-00'), + ('110', 'LP-01', 'LP-01'), + ('109', 'LP-10', 'LP-10'), + ('108', 'LP-11', 'LP-11'), + ('7', 'EscapeMode', 'Escape mode'), + ('6', 'BTA', 'Bi-directional Data Lane Turnaround'), + ('112', 'LPDT', 'LPDT'), + ('0', 'DI', 'Data identifier'), + ('12', 'ECC', 'ECC'), + ('11', 'WC', 'Word count'), + ('107', 'CRC', 'CheckSUM'), + ('5', 'Stop', 'Stop condition'), + ('1', 'Idle', 'Idle'), + ) + annotation_rows = ( + ('LPData', 'LPData', (0, 1, 2, 3,)), + ('LP', 'LP', (4, 5, 6, 7, 8, 9, 10)), + ) + binary = ( + ) + + def __init__(self): + self.reset() + + def reset(self): + self.samplerate = None + self.ss = self.es = self.ss_byte = -1 + self.bitcount = 0 + self.databyte = 0 + self.is_esc_bta = 0 # 0=ESC Mode 1=BTA Mode + self.state = 'FIND START' + self.pdu_start = None + self.pdu_bits = 0 + self.bits = [] + self.findmode_state = 'Find Mode state0' + + def metadata(self, key, value): + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + + def start(self): + self.out_python = self.register(srd.OUTPUT_PYTHON) + self.out_ann = self.register(srd.OUTPUT_ANN) + self.out_binary = self.register(srd.OUTPUT_BINARY) + self.out_bitrate = self.register(srd.OUTPUT_META, + meta=(int, 'Bitrate', 'Bitrate from Start bit to Stop bit')) + + def putx(self, data): + self.put(self.ss, self.es, self.out_ann, data) + + def putp(self, data): + self.put(self.ss, self.es, self.out_python, data) + + def putb(self, data): + self.put(self.ss, self.es, self.out_binary, data) + + def handle_start(self): + self.ss, self.es = self.samplenum, self.samplenum + self.pdu_start = self.samplenum + self.pdu_bits = 0 + self.state = 'FIND MODE' + self.bitcount = self.databyte = 0 + self.findmode_state = 'Find Mode state0' + self.bits = [] + + def handle_esc_bta(self, d0n, d0p): + self.es = self.samplenum + cmd = 'ESC Mode' if(d0n == 1) else 'BTA' + self.putp([cmd, None]) + self.putx([proto[cmd][0], proto[cmd][1:]]) + self.bitcount = self.databyte = 0 + self.bits = [] + self.state = 'FIND DATA' + self.ss = self.samplenum + + def handle_stop(self): + cmd = 'STOP' + self.es = self.samplenum + self.putp([cmd, None]) + self.putx([proto[cmd][0], proto[cmd][1:]]) + self.state = 'FIND START' + self.is_esc_bta = 0 + self.bits = [] + + def handle_data(self, d0n, d0p): + self.pdu_bits += 1 + + self.databyte >>= 1 + if d0p: + self.databyte |= 0x80 + + if self.bitcount == 0: + self.ss_byte = self.samplenum + + if self.bitcount < 7: + self.bitcount += 1 + return + + d = self.databyte + + self.es = self.samplenum + h = '0x%02X' % d + cmd = 'DATA' + self.putb([cmd, None]) + self.putx([proto[cmd][0], [h]]) + + self.bitcount = self.databyte = 0 + self.bits = [] + self.state = 'FIND DATA' + self.ss = self.samplenum + + def decode(self): + while True: + # State machine. + if self.state == 'FIND START': + # Wait for a START condition (S): D0P = high, D0N = falling. + self.wait({0: 'f', 1: 'h'}) + self.handle_start() + elif self.state == 'FIND MODE': + if self.findmode_state == 'Find Mode state0': + self.wait({0: 'l', 1: 'l'}) + self.findmode_state = 'Find Mode state1' + elif self.findmode_state == 'Find Mode state1': + (d0n, d0p) = self.wait([{0: 'h', 1: 'l'}, {0: 'l', 1: 'h'}]) + self.findmode_state = 'Find Mode state2' + elif self.findmode_state == 'Find Mode state2': + self.wait({0: 'l', 1: 'l'}) + self.handle_esc_bta(d0n, d0p) + #self.findmode_state = 'Find Mode state0' + elif self.state == 'FIND DATA': + (d0n, d0p) = self.wait([{0: 'h', 1: 'l'}, {0: 'l', 1: 'h'}]) + self.wait([{0: 'l', 1: 'l'}, {0: 'h', 1: 'h'}]) + + if (self.matched & (0b1 << 0)): + self.handle_data(d0n, d0p) + else : + self.handle_stop() + + diff --git a/libsigrokdecode4DSL/decoders/mipi_rffe/__init__.py b/libsigrokdecode4DSL/decoders/mipi_rffe/__init__.py new file mode 100644 index 00000000..c63ab855 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/mipi_rffe/__init__.py @@ -0,0 +1,25 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2012 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +RFFE(RF Front-End Control Interface)is a bidirectional, single-master +bus using two signals (SCLK = serial clock line, SDATA = serial data line). +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/mipi_rffe/pd.py b/libsigrokdecode4DSL/decoders/mipi_rffe/pd.py new file mode 100644 index 00000000..a8673d03 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/mipi_rffe/pd.py @@ -0,0 +1,510 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2010-2016 Uwe Hermann +## Copyright (C) 2020 DreamSourceLab +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +# TODO: Look into arbitration, collision detection, clock synchronisation, etc. +# TODO: Implement support for inverting SDATA/SCLK levels (0->1 and 1->0). +# TODO: Implement support for detecting various bus errors. + +import sigrokdecode as srd + +''' +OUTPUT_PYTHON format: + +Packet: +[SSC,, ] + +SSC:Sequence Start Condition + +: + - 'SA' (Slave Address) + - 'COMMAND' (command Key) + - 'BC' (Byte Count) + - 'P' (Parity) + - 'ADDRESS' (Register Address) + - 'DATA WRITE0' (Register 0 Write Data) +: + - 'ADDRESS' (Register Address) + - 'P' (Parity) + - 'BP' (Bus Park) + - 'DATA READ' (Data, read) + - 'DATA WRITE' (Data, write) + + : A Command Frame shall consist of a 4-bit Slave address field, an 8-bit command payload field, and a single parity bit. + + : A Data or Address Frame shall consist of eight data bits or eight address bits, respectively, and a single parity bit. +''' +# cmd: [annotation-type-index, long annotation, short annotation] +proto = { + 'SSC': [0, 'Sequence Start Condition', 'SSC'], + 'SA': [1, 'Slave Address', 'SA'], + 'ERW': [2, 'Extended Register Write', 'ERW'], + 'ERR': [3, 'Extended Register Read', 'ERR'], + 'ERWL': [4, 'Extended Register Write Long', 'ERWL'], + 'ERRL': [5, 'Extended Register Read Long', 'ERRL'], + 'RW': [6, 'Register Write', 'RW'], + 'RR': [7, 'Register Read', 'RR'], + 'R0W': [8, 'Register 0 Write', 'R0W'], + 'BC': [9, 'Byte', 'BC'], + 'P': [10, 'Parity', 'P'], + 'ADDRESS': [11, 'Address', 'A'], + 'BP': [12, 'Bus Pack', 'BP'], + 'DATA': [13, 'Data ', 'DATA'], + 'CMD_WARNINGS': [14, 'Command Warnings', 'CMD_WARN'], + 'BIC': [15, 'Bus Idle Condition ', 'BIC'], + 'BC_WARNINGS': [16, 'BC Warnings', 'BC_WARN'], + 'IJE': [17, 'Illegal Jump Edge', 'IJE_WAEN'], + 'PW': [18, 'Parity warnings', 'P_WAEN'], +} + +class Decoder(srd.Decoder): + api_version = 3 + id = 'mipi_rffe' + name = 'MIPI_RFFE' + longname = 'RF Front-End Control Interface' + desc = 'Two-wire, single-master, serial bus.' + license = 'gplv2+' + inputs = ['logic'] + outputs = ['mipi_rffe'] + tags = ['Embedded/industrial'] + channels = ( + {'id': 'sclk', 'type': 8, 'name': 'SCLK', 'desc': 'Serial clock line'}, + {'id': 'sdata', 'type': 108, 'name': 'SDATA', 'desc': 'Serial data line'}, + ) + options = ( + {'id': 'error_display', 'desc': 'Error display options', + 'default': 'display', 'values': ('display', 'not_display')}, + ) + annotations = ( + ('7', 'ssc', 'Sequence Start Condition'), + ('6', 'sa', 'Slave Address'), + ('1', 'erw', 'Extended register write'), + ('5', 'err', 'Extended register read'), + ('0', 'erwl', 'Extended register write long'), + ('112', 'errl', 'Extended register read long'), + ('111', 'rw', 'Register write'), + ('110', 'rr', 'Register read'), + ('109', 'r0w', 'Register 0 write'), + ('108', 'bc', 'Byte'), + ('7', 'p', 'Parity'), + ('75', 'address', 'Address'), + ('70', 'bp', 'Bus pack'), + ('65', 'data', 'DATA'), + ('1000', 'Command warnings', 'Command warnings'), + ('50', 'Bus Idle Condition ', 'Bus Idle Condition '), + ('1000', 'BC warnings', 'BC warnings'), + ('1000', 'Illegal Jump Edge', 'IJE_WAEN'), + ('1000', 'Parity warnings', 'Parity warnings'), + ) + annotation_rows = ( + ('command-data', 'Command/Data', (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 , 11, 12, 13 ,15)), + ('warnings', 'Warnings', (14,16,17,18,)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.samplerate = None + self.ss = self.es = -1 + self.bitcount = 0 + self.databyte = 0 + self.state = 'FIND SSC' + self.extended = -1 + self.cmdkey = 'NULL' + self.BC = 0 + self.bits = 0 + self.Pcount = 0 + self.BPcount = 0 + self.ADDcount = 0 + self.BPss = 0 + self.SSCs = 0 + self.Pes = 0 + self.sdata = -1 + self.Pdata = -1 + self.parity = False + + def metadata(self, key, value): + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + self._display = 1 if self.options['error_display'] == 'display' else 0 + + def putx(self, data): + self.put(self.ss, self.es, self.out_ann, data) + + def Parity(self): + self.parity = True + if self.Pcount == 1 : + if self.cmdkey == 'ERW' : + self.Pdata = self.Pdata + (0*(2**(self.Pkey+1))) + if self.cmdkey == 'ERR' : + self.Pdata = self.Pdata + (2*(2**(self.Pkey+1))) + if self.cmdkey == 'ERWL' : + self.Pdata = self.Pdata + (6*(2**(self.Pkey+1))) + if self.cmdkey == 'ERRL' : + self.Pdata = self.Pdata + (7*(2**(self.Pkey+1))) + if self.cmdkey == 'RW' : + self.Pdata = self.Pdata + (2*(2**(self.Pkey+1))) + if self.cmdkey == 'RR' : + self.Pdata = self.Pdata + (3*(2**(self.Pkey+1))) + if self.cmdkey == 'R0W' : + self.Pdata = self.Pdata + (1*(2**(self.Pkey+1))) + while self.Pdata : + self.parity = not self.parity + self.Pdata = self.Pdata & (self.Pdata - 1) + self.Pdata = self.Pkey = 0 + + + def handle_BP(self,cmd,state): + + self.ss = self.Pes + self.es = self.samplenum + self.putx([proto[cmd][0], proto[cmd][1:]]) + self.state = state + + def handle(self,cmd,state,key,key0): + key1 = key + if key > 7 : + key = 7 + if self.bitcount == 0: + self.DATAss = self.samplenum + + if self.bitcount < key: + while True : + if self._display : + (sclk, sdata) = self.wait([{0: 'f'},{0: 'l', 1: 'e'}]) + if (self.matched & (0b1 << 0)): + self.databyte <<= 1 + self.databyte |= sdata + break + if (self.matched & (0b1 << 1)): + self.ss = self.samplenum + (sclk, sdata) = self.wait([{0: 'f'},{0: 'l', 1: 'e'}]) + if (self.matched & (0b1 << 0)): + self.es = self.samplenum + self.putx([proto['IJE'][0], proto['IJE'][1:]]) + self.databyte <<= 1 + self.databyte |= sdata + break + if (self.matched & (0b1 << 1)): + self.es = self.samplenum + self.putx([proto['IJE'][0], proto['IJE'][1:]]) + else : + (sclk, sdata) = self.wait({0: 'f'}) + self.databyte <<= 1 + self.databyte |= sdata + break + + self.bitcount += 1 + return + + while True : + if self._display : + (sclk, sdata) = self.wait([{0: 'f'},{0: 'l', 1: 'e'}]) + if (self.matched & (0b1 << 0)): + self.databyte <<= 1 + self.databyte |= sdata + break + if (self.matched & (0b1 << 1)): + self.ss = self.samplenum + (sclk, sdata) = self.wait([{0: 'f'},{0: 'l', 1: 'e'}]) + if (self.matched & (0b1 << 0)): + self.es = self.samplenum + self.putx([proto['IJE'][0], proto['IJE'][1:]]) + self.databyte <<= 1 + self.databyte |= sdata + break + if (self.matched & (0b1 << 1)): + self.es = self.samplenum + self.putx([proto['IJE'][0], proto['IJE'][1:]]) + else : + (sclk, sdata) = self.wait({0: 'f'}) + self.databyte <<= 1 + self.databyte |= sdata + break + self.wait({0 : 'r'}) + d = self.databyte + self.ss = self.DATAss + self.es = self.samplenum + if cmd != 'P': + self.Pdata = d + self.Pkey = key + if cmd == 'BC': + self.BC = d + if self.cmdkey == 'ERW' or self.cmdkey == 'ERR' : + if self.BC < 4 or self.BC > 16 : + self.putx([proto['BC_WARNINGS'][0], proto['BC_WARNINGS'][1:]]) + self.init() + return + else : + if self.BC < 1 or self.BC > 8 : + self.putx([proto['BC_WARNINGS'][0], proto['BC_WARNINGS'][1:]]) + self.init() + return + if cmd == 'P': + self.Pes = self.samplenum + if self._display : + self.Parity() + if self.parity != d : + self.putx([proto['PW'][0], proto['PW'][1:]]) + self.putx([proto[cmd][0], ['%s: %d' % (proto[cmd][1],d), + '%s: %d' % (proto[cmd][2],d), '%d' % d]]) + self.bitcount = self.databyte = 0 + self.state = state + return + self.putx([proto[cmd][0], ['%s[%d:%d]: %02X' % (proto[cmd][1],key1,key0,d), + '%s[%d:%d]: %02X' % (proto[cmd][2],key1,key0,d), '%02X' % d]]) + self.bitcount = self.databyte = 0 + if cmd == 'DATA': + self.bits -= 8 + self.state = state + + + + + def handle_CMD(self): + if self.bitcount == 0: + self.DATAss = self.samplenum + (sclk, sdata) = self.wait({0: 'f'}) + if sdata : + self.wait({0 : 'r'}) + self.cmdset('R0W','FIND DATA') + return + + if self.bitcount == 1: + if self.sdata : + self.extended = 0 + else : + self.extended = 1 + + if self.bitcount == 2: + if not self.extended : + if self.sdata : + self.cmdset('RR','FIND ADDRESS') + return + else : + self.cmdset('RW','FIND ADDRESS') + return + + if self.bitcount == 3: + if not self.sdata : + if self.extended : + self.cmdset('ERR','FIND BTEY_COUNT') + return + else : + self.cmdset('ERW','FIND BTEY_COUNT') + return + elif self.extended : + self.ss = self.DATAss + self.es = self.samplenum + self.putx([proto['CMD_WARNINGS'][0], proto['CMD_WARNINGS'][1:]]) + self.init() + return + + if self.bitcount == 4: + if self.sdata : + self.cmdset('ERRL','FIND BTEY_COUNT') + return + else : + self.cmdset('ERWL','FIND BTEY_COUNT') + return + + if self.bitcount <4: + while True : + if self._display : + (sclk,self.sdata) = self.wait([{0: 'f'},{0: 'l', 1: 'e'}]) + if (self.matched & (0b1 << 0)): + break + if (self.matched & (0b1 << 1)): + self.ss = self.samplenum + (sclk,self.sdata) = self.wait([{0: 'f'},{0: 'l', 1: 'e'}]) + if (self.matched & (0b1 << 0)): + self.es = self.samplenum + self.putx([proto['IJE'][0], proto['IJE'][1:]]) + break + if (self.matched & (0b1 << 1)): + self.es = self.samplenum + self.putx([proto['IJE'][0], proto['IJE'][1:]]) + else : + (sclk,self.sdata) = self.wait({0: 'f'}) + break + self.wait({0 : 'r'}) + self.bitcount += 1 + + def cmdset(self,cmd,state): + self.ss = self.DATAss + self.es = self.samplenum + self.putx([proto[cmd][0], proto[cmd][1:]]) + self.state = state + self.bitcount = 0 + self.extended = -1 + self.cmdkey = cmd + + def initBP(self,sclk,sdata,state,key): + self.handle_BP('BP',state) + self.state = state + if key : + self.init() + + def init(self): + self.cmdkey = 'NULL' + self.ADDcount = 0 + self.Pcount = 0 + self.BPcount = 0 + self.BC = 0 + self.bitcount = 0 + self.Pes = 0 + self.state = 'FIND SSC' + + + + + def decode(self): + while True: + if self.state == 'FIND SSC': + self.wait({0: 'l', 1: 'r'}) + self.BPss = self.samplenum + self.wait([{0: 'h'},{0: 'l', 1: 'f'}]) + if (self.matched & (0b1 << 0)): + continue + if (self.matched & (0b1 << 1)): + self.wait([{0: 'l', 1: 'e'},{0: 'r'}]) + if (self.matched & (0b1 << 0)): + continue + if (self.matched & (0b1 << 1)): + self.ss,self.es = self.BPss,self.samplenum + self.putx([proto['SSC'][0], proto['SSC'][1:]]) + self.state = 'FIND SLAVE ADDRESS' + + elif self.state == 'FIND SLAVE ADDRESS': + self.handle('SA','FIND COMMAND',3,0) + + elif self.state == 'FIND COMMAND': + self.handle_CMD() + + elif self.state == 'FIND BTEY_COUNT': + if self.cmdkey == 'ERW' or self.cmdkey == 'ERR' : + self.handle('BC','FIND PARITY',3,0) + else : + self.handle('BC','FIND PARITY',2,0) + self.bits = self.BC*8 + + elif self.state == 'FIND ADDRESS': + if self.cmdkey == 'RW' or self.cmdkey == 'RR' : + self.handle('ADDRESS','FIND PARITY',4,0) + elif self.cmdkey == 'ERR' or self.cmdkey == 'ERW' : + self.handle('ADDRESS','FIND PARITY',7,0) + else : + if self.Pcount == 1 : + self.handle('ADDRESS','FIND PARITY',15,8) + else : + self.handle('ADDRESS','FIND PARITY',7,0) + + elif self.state == 'FIND DATA': + if self.cmdkey == 'R0W' : + self.handle('DATA','FIND PARITY',6,0) + elif self.cmdkey == 'RW' or self.cmdkey == 'RR' : + self.handle('DATA','FIND PARITY',7,0) + else : + self.handle('DATA','FIND PARITY',self.bits-1,self.bits-8) + + elif self.state == 'FIND PARITY': + self.Pcount += 1 + self.handle('P','NULL',0,0) + if self.cmdkey == 'R0W' : + self.state = 'FIND BUS_PARK' + + elif self.cmdkey == 'ERW' : + if self.Pcount == 1 : + self.ADDcount,self.state = 1,'FIND ADDRESS' + elif self.Pcount == 2 : + self.state = 'FIND DATA' + elif self.Pcount == self.BC + 2 : + self.state = 'FIND BUS_PARK' + continue + elif self.Pcount > 2 : + self.state = 'FIND DATA' + + elif self.cmdkey == 'ERR' : + if self.Pcount == 1 : + self.ADDcount,self.state = 1,'FIND ADDRESS' + elif self.Pcount == 2 : + self.BPcount,self.state = 1,'FIND BUS_PARK' + elif self.Pcount == self.BC + 2 : + self.BPcount =2 + self.state = 'FIND BUS_PARK' + continue + elif self.Pcount > 2 : + self.state = 'FIND DATA' + + elif self.cmdkey == 'ERWL' : + if self.Pcount == 1 : + self.ADDcount,self.state = 2,'FIND ADDRESS' + elif self.Pcount == 2 : + self.ADDcount,self.state = 1,'FIND ADDRESS' + elif self.Pcount == 3 : + self.state = 'FIND DATA' + elif self.Pcount == self.BC + 3 : + self.state = 'FIND BUS_PARK' + continue + elif self.Pcount > 3 : + self.state = 'FIND DATA' + + elif self.cmdkey == 'ERRL' : + if self.Pcount == 1 : + self.ADDcount,self.state = 2,'FIND ADDRESS' + elif self.Pcount == 2 : + self.ADDcount,self.state = 1,'FIND ADDRESS' + elif self.Pcount == 3 : + self.BPcount,self.state = 1,'FIND BUS_PARK' + elif self.Pcount == self.BC + 3 : + self.BPcount =2 + self.state = 'FIND BUS_PARK' + continue + elif self.Pcount > 3 : + self.state = 'FIND DATA' + + elif self.cmdkey == 'RW' : + if self.Pcount == 1 : + self.state = 'FIND DATA' + elif self.Pcount == 2 : + self.BPcount,self.state = 1,'FIND BUS_PARK' + + elif self.cmdkey == 'RR' : + self.state = 'FIND BUS_PARK' + self.BPcount = 1 if (self.Pcount == 1) else 2 + + elif self.state == 'FIND BUS_PARK': + (sclk, sdata) = self.wait({0: 'l', 1: 'l'}) + self.ss = self.samplenum + if self.cmdkey == 'ERR' or self.cmdkey == 'ERRL' or self.cmdkey == 'RR': + key = 0 if (self.BPcount == 1) else 1 + self.initBP(sclk,sdata,'FIND DATA',key) + else : + self.initBP(sclk,sdata,'FIND SSC',1) + + + + + + + \ No newline at end of file diff --git a/libsigrokdecode4DSL/decoders/mlx90614/__init__.py b/libsigrokdecode4DSL/decoders/mlx90614/__init__.py new file mode 100644 index 00000000..2e20c4d9 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/mlx90614/__init__.py @@ -0,0 +1,25 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2012 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This decoder stacks on top of the 'i2c' PD and decodes the Melexis MLX90614 +infrared thermometer protocol. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/mlx90614/pd.py b/libsigrokdecode4DSL/decoders/mlx90614/pd.py new file mode 100644 index 00000000..f0dbe22a --- /dev/null +++ b/libsigrokdecode4DSL/decoders/mlx90614/pd.py @@ -0,0 +1,78 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2012 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd + +class Decoder(srd.Decoder): + api_version = 3 + id = 'mlx90614' + name = 'MLX90614' + longname = 'Melexis MLX90614' + desc = 'Melexis MLX90614 infrared thermometer protocol.' + license = 'gplv2+' + inputs = ['i2c'] + outputs = [] + tags = ['IC', 'Sensor'] + annotations = ( + ('celsius', 'Temperature in degrees Celsius'), + ('kelvin', 'Temperature in Kelvin'), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.state = 'IGNORE START REPEAT' + self.data = [] + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def putx(self, data): + self.put(self.ss, self.es, self.out_ann, data) + + # Quick hack implementation! This needs to be improved a lot! + def decode(self, ss, es, data): + cmd, databyte = data + + # State machine. + if self.state == 'IGNORE START REPEAT': + if cmd != 'START REPEAT': + return + self.state = 'IGNORE ADDRESS WRITE' + elif self.state == 'IGNORE ADDRESS WRITE': + if cmd != 'ADDRESS WRITE': + return + self.state = 'GET TEMPERATURE' + elif self.state == 'GET TEMPERATURE': + if cmd != 'DATA WRITE': + return + if len(self.data) == 0: + self.data.append(databyte) + self.ss = ss + elif len(self.data) == 1: + self.data.append(databyte) + self.es = es + else: + kelvin = (self.data[0] | (self.data[1] << 8)) * 0.02 + celsius = kelvin - 273.15 + self.putx([0, ['Temperature: %3.2f °C' % celsius]]) + self.putx([1, ['Temperature: %3.2f K' % kelvin]]) + self.state = 'IGNORE START REPEAT' + self.data = [] diff --git a/libsigrokdecode4DSL/decoders/modbus/__init__.py b/libsigrokdecode4DSL/decoders/modbus/__init__.py new file mode 100644 index 00000000..b60eea15 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/modbus/__init__.py @@ -0,0 +1,28 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 Bart de Waal +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This decoder stacks on top of the 'uart' PD and decodes Modbus RTU, +a protocol with a single a client and one or more servers. + +The RX channel will be checked for both client->server and server->client +communication, the TX channel only for client->server. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/modbus/pd.py b/libsigrokdecode4DSL/decoders/modbus/pd.py new file mode 100644 index 00000000..487acf1f --- /dev/null +++ b/libsigrokdecode4DSL/decoders/modbus/pd.py @@ -0,0 +1,934 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2015 Bart de Waal +## Copyright (C) 2019 DreamSourceLab +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 3 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd +from math import ceil + +RX = 0 +TX = 1 + +class No_more_data(Exception): + '''This exception is a signal that we should stop parsing an ADU as there + is no more data to parse.''' + pass + +class Data: + '''The Data class is used to hold the bytes from the serial decode.''' + def __init__(self, start, end, data): + self.start = start + self.end = end + self.data = data + +class Modbus_ADU: + '''An Application Data Unit is what Modbus calls one message. + Protocol decoders are supposed to keep track of state and then provide + decoded data to the backend as it reads it. In Modbus' case, the state is + the ADU up to that point. This class represents the state and writes the + messages to the backend. + This class is for the common infrastructure between CS and SC. It should + not be used directly, only inhereted from.''' + + def __init__(self, parent, start, write_channel, annotation_prefix): + self.data = [] # List of all the data received up to now + self.parent = parent # Reference to the decoder object + self.start = start + self.last_read = start # The last moment parsed by this ADU object + self.write_channel = write_channel + self.last_byte_put = -1 + self.annotation_prefix = annotation_prefix + # Any Modbus message needs to be at least 4 bytes long. The Modbus + # function may make this longer. + self.minimum_length = 4 + + # This variable is used by an external function to determine when the + # next frame should be started. + self.startNewFrame = False + + # If there is an error in a frame, we'd like to highlight it. Keep + # track of errors. + self.hasError = False + + def add_data(self, start, end, data): + '''Let the frame handle another piece of data. + start: start of this data + end: end of this data + data: data as received from the UART decoder''' + ptype, rxtx, pdata = data + self.last_read = end + if ptype == 'DATA': + self.data.append(Data(start, end, pdata[0])) + self.parse() # parse() is defined in the specific type of ADU. + + def puti(self, byte_to_put, annotation, message): + '''This class keeps track of how much of the data has already been + annotated. This function tells the parent class to write message, but + only if it hasn't written about this bit before. + byte_to_put: Only write if it hasn't yet written byte_to_put. It will + write from the start of self.last_byte_put+1 to the end + of byte_to_put. + annotation: Annotation to write to, without annotation_prefix. + message: Message to write.''' + if byte_to_put > len(self.data) - 1: + # If the byte_to_put hasn't been read yet. + raise No_more_data + + if annotation == 'error': + self.hasError = True + + if byte_to_put > self.last_byte_put: + self.parent.puta( + self.data[self.last_byte_put + 1].start, + self.data[byte_to_put].end, + self.annotation_prefix + annotation, + message) + self.last_byte_put = byte_to_put + raise No_more_data + + def putl(self, annotation, message, maximum=None): + '''Puts the last byte on the stack with message. The contents of the + last byte will be applied to message using format.''' + last_byte_address = len(self.data) - 1 + if maximum is not None and last_byte_address > maximum: + return + self.puti(last_byte_address, annotation, + message.format(self.data[-1].data)) + + def close(self, message_overflow): + '''Function to be called when next message is started. As there is + always space between one message and the next, we can use that space + for errors at the end.''' + # TODO: Figure out how to make this happen for last message. + data = self.data + if len(data) < self.minimum_length: + if len(data) == 0: + # Sometimes happens with noise, safe to ignore. + return + self.parent.puta( + data[self.last_byte_put].end, message_overflow, + self.annotation_prefix + 'error', + 'Message too short or not finished') + self.hasError = True + if self.hasError and self.parent.options['channel'] == 'RX': + # If we are on RX mode (so client->server and server->client + # messages can be seperated) we like to mark blocks containing + # errors. We don't do this in TX mode, because then we interpret + # each frame as both a client->server and server->client frame, and + # one of those is bound to contain an error, making highlighting + # frames useless. + self.parent.puta(data[0].start, data[-1].end, + 'error-indication', 'Frame contains error') + if len(data) > 256: + try: + self.puti(len(data) - 1, self.annotation_prefix + 'error', + 'Modbus data frames are limited to 256 bytes') + except No_more_data: + pass + + def check_crc(self, byte_to_put): + '''Check the CRC code, data[byte_to_put] is the 2nd byte of the CRC.''' + crc_byte1, crc_byte2 = self.calc_crc(byte_to_put) + data = self.data + if data[-2].data == crc_byte1 and data[-1].data == crc_byte2: + self.puti(byte_to_put, 'crc', 'CRC correct') + else: + self.puti(byte_to_put, 'error', + 'CRC should be {} {}'.format(crc_byte1, crc_byte2)) + + def half_word(self, start): + '''Return the half word (16 bit) value starting at start bytes in. If + it goes out of range it raises the usual errors.''' + if (start + 1) > (len(self.data) - 1): + # If there isn't enough length to access data[start + 1]. + raise No_more_data + return self.data[start].data * 0x100 + self.data[start + 1].data + + def calc_crc(self, last_byte): + '''Calculate the CRC, as described in the spec. + The last byte of the CRC should be data[last_byte].''' + if last_byte < 3: + # Every Modbus ADU should be as least 4 long, so we should never + # have to calculate a CRC on something shorter. + raise Exception('Could not calculate CRC: message too short') + + result = 0xFFFF + magic_number = 0xA001 # As defined in the modbus specification. + for byte in self.data[:last_byte - 1]: + result = result ^ byte.data + for i in range(8): + LSB = result & 1 + result = result >> 1 + if (LSB): # If the LSB is true. + result = result ^ magic_number + byte1 = result & 0xFF + byte2 = (result & 0xFF00) >> 8 + return (byte1, byte2) + + def parse_write_single_coil(self): + '''Parse function 5, write single coil.''' + self.minimum_length = 8 + + self.puti(1, 'function', 'Function 5: Write Single Coil') + + address = self.half_word(2) + self.puti(3, 'address', + 'Address 0x{:X} / {:d}'.format(address, address + 10000)) + + raw_value = self.half_word(4) + value = 'Invalid Coil Value' + if raw_value == 0x0000: + value = 'Coil Value OFF' + elif raw_value == 0xFF00: + value = 'Coil Value ON' + self.puti(5, 'data', value) + + self.check_crc(7) + + def parse_write_single_register(self): + '''Parse function 6, write single register.''' + self.minimum_length = 8 + + self.puti(1, 'function', 'Function 6: Write Single Register') + + address = self.half_word(2) + self.puti(3, 'address', + 'Address 0x{:X} / {:d}'.format(address, address + 30000)) + + value = self.half_word(4) + value_formatted = 'Register Value 0x{0:X} / {0:d}'.format(value) + self.puti(5, 'data', value_formatted) + + self.check_crc(7) + + def parse_diagnostics(self): + '''Parse function 8, diagnostics. This function has many subfunctions, + but they are all more or less the same.''' + self.minimum_length = 8 + + self.puti(1, 'function', 'Function 8: Diagnostics') + + diag_subfunction = { + 0: 'Return Query data', + 1: 'Restart Communications Option', + 2: 'Return Diagnostics Register', + 3: 'Change ASCII Input Delimiter', + 4: 'Force Listen Only Mode', + 10: 'Clear Counters and Diagnostic Register', + 11: 'Return Bus Message Count', + 12: 'Return Bus Communication Error Count', + 13: 'Return Bus Exception Error Count', + 14: 'Return Slave Message Count', + 15: 'Return Slave No Response Count', + 16: 'Return Slave NAK Count', + 17: 'Return Slave Busy Count', + 18: 'Return Bus Character Overrun Count', + 20: 'Return Overrun Counter and Flag', + } + subfunction = self.half_word(2) + subfunction_name = diag_subfunction.get(subfunction, + 'Reserved subfunction') + self.puti(3, 'data', + 'Subfunction {}: {}'.format(subfunction, subfunction_name)) + + diagnostic_data = self.half_word(4) + self.puti(5, 'data', + 'Data Field: {0} / 0x{0:04X}'.format(diagnostic_data)) + + self.check_crc(7) + + def parse_mask_write_register(self): + '''Parse function 22, Mask Write Register.''' + self.minimum_length = 10 + data = self.data + + self.puti(1, 'function', 'Function 22: Mask Write Register') + + address = self.half_word(2) + self.puti(3, 'address', + 'Address 0x{:X} / {:d}'.format(address, address + 30001)) + + self.half_word(4) # To make sure we don't oveflow data. + and_mask_1 = data[4].data + and_mask_2 = data[5].data + self.puti(5, 'data', + 'AND mask: {:08b} {:08b}'.format(and_mask_1, and_mask_2)) + + self.half_word(6) # To make sure we don't oveflow data. + or_mask_1 = data[6].data + or_mask_2 = data[7].data + self.puti(7, 'data', + 'OR mask: {:08b} {:08b}'.format(or_mask_1, or_mask_2)) + + self.check_crc(9) + + def parse_not_implemented(self): + '''Explicitly mark certain functions as legal functions, but not + implemented in this parser. This is due to the author not being able to + find anything (hardware or software) that supports these functions.''' + # TODO: Implement these functions. + + # Mentioning what function it is is no problem. + function = self.data[1].data + functionname = { + 20: 'Read File Record', + 21: 'Write File Record', + 24: 'Read FIFO Queue', + 43: 'Read Device Identification/Encapsulated Interface Transport', + }[function] + self.puti(1, 'function', + 'Function {}: {} (not supported)'.format(function, functionname)) + + # From there on out we can keep marking it unsupported. + self.putl('data', 'This function is not currently supported') + +class Modbus_ADU_SC(Modbus_ADU): + '''SC stands for Server -> Client.''' + def parse(self): + '''Select which specific Modbus function we should parse.''' + data = self.data + + # This try-catch is being used as flow control. + try: + server_id = data[0].data + if 1 <= server_id <= 247: + message = 'Slave ID: {}'.format(server_id) + else: + message = 'Slave ID {} is invalid' + self.puti(0, 'server-id', message) + + function = data[1].data + if function == 1 or function == 2: + self.parse_read_bits() + elif function == 3 or function == 4 or function == 23: + self.parse_read_registers() + elif function == 5: + self.parse_write_single_coil() + elif function == 6: + self.parse_write_single_register() + elif function == 7: + self.parse_read_exception_status() + elif function == 8: + self.parse_diagnostics() + elif function == 11: + self.parse_get_comm_event_counter() + elif function == 12: + self.parse_get_comm_event_log() + elif function == 15 or function == 16: + self.parse_write_multiple() + elif function == 17: + self.parse_report_server_id() + elif function == 22: + self.parse_mask_write_register() + elif function in {21, 21, 24, 43}: + self.parse_not_implemented() + elif function > 0x80: + self.parse_error() + else: + self.puti(1, 'error', + 'Unknown function: {}'.format(data[1].data)) + self.putl('error', 'Unknown function') + + # If the message gets here without raising an exception, the + # message goes on longer than it should. + self.putl('error', 'Message too long') + + except No_more_data: + # Just a message saying we don't need to parse anymore this round. + pass + + def parse_read_bits(self): + self.mimumum_length = 5 + + data = self.data + function = data[1].data + + if function == 1: + self.puti(1, 'function', 'Function 1: Read Coils') + else: + self.puti(1, 'function', 'Function 2: Read Discrete Inputs') + + bytecount = self.data[2].data + self.minimum_length = 5 + bytecount # 3 before data, 2 CRC. + self.puti(2, 'length', 'Byte count: {}'.format(bytecount)) + + # From here on out, we expect registers on 3 and 4, 5 and 6 etc. + # So registers never start when the length is even. + self.putl('data', '{:08b}', bytecount + 2) + self.check_crc(bytecount + 4) + + def parse_read_registers(self): + self.mimumum_length = 5 + + data = self.data + + function = data[1].data + if function == 3: + self.puti(1, 'function', 'Function 3: Read Holding Registers') + elif function == 4: + self.puti(1, 'function', 'Function 4: Read Input Registers') + elif function == 23: + self.puti(1, 'function', 'Function 23: Read/Write Multiple Registers') + + bytecount = self.data[2].data + self.minimum_length = 5 + bytecount # 3 before data, 2 CRC. + if bytecount % 2 == 0: + self.puti(2, 'length', 'Byte count: {}'.format(bytecount)) + else: + self.puti(2, 'error', + 'Error: Odd byte count ({})'.format(bytecount)) + + # From here on out, we expect registers on 3 and 4, 5 and 6 etc. + # So registers never start when the length is even. + if len(data) % 2 == 1: + register_value = self.half_word(-2) + self.putl('data', '0x{0:04X} / {0}'.format(register_value), + bytecount + 2) + else: + raise No_more_data + + self.check_crc(bytecount + 4) + + def parse_read_exception_status(self): + self.mimumum_length = 5 + + self.puti(1, 'function', 'Function 7: Read Exception Status') + exception_status = self.data[2].data + self.puti(2, 'data', + 'Exception status: {:08b}'.format(exception_status)) + self.check_crc(4) + + def parse_get_comm_event_counter(self): + self.mimumum_length = 8 + + self.puti(1, 'function', 'Function 11: Get Comm Event Counter') + + status = self.half_word(2) + if status == 0x0000: + self.puti(3, 'data', 'Status: not busy') + elif status == 0xFFFF: + self.puti(3, 'data', 'Status: busy') + else: + self.puti(3, 'error', 'Bad status: 0x{:04X}'.format(status)) + + count = self.half_word(4) + self.puti(5, 'data', 'Event Count: {}'.format(count)) + self.check_crc(7) + + def parse_get_comm_event_log(self): + self.mimumum_length = 11 + self.puti(1, 'function', 'Function 12: Get Comm Event Log') + + data = self.data + + bytecount = data[2].data + self.puti(2, 'length', 'Bytecount: {}'.format(bytecount)) + # The bytecount is the length of everything except the slaveID, + # function code, bytecount and CRC. + self.mimumum_length = 5 + bytecount + + status = self.half_word(3) + if status == 0x0000: + self.puti(4, 'data', 'Status: not busy') + elif status == 0xFFFF: + self.puti(4, 'data', 'Status: busy') + else: + self.puti(4, 'error', 'Bad status: 0x{:04X}'.format(status)) + + event_count = self.half_word(5) + self.puti(6, 'data', 'Event Count: {}'.format(event_count)) + + message_count = self.half_word(7) + self.puti(8, 'data', 'Message Count: {}'.format(message_count)) + + self.putl('data', 'Event: 0x{:02X}'.format(data[-1].data), + bytecount + 2) + + self.check_crc(bytecount + 4) + + def parse_write_multiple(self): + '''Function 15 and 16 are almost the same, so we can parse them both + using one function.''' + self.mimumum_length = 8 + + function = self.data[1].data + if function == 15: + data_unit = 'Coils' + max_outputs = 0x07B0 + long_address_offset = 10001 + elif function == 16: + data_unit = 'Registers' + max_outputs = 0x007B + long_address_offset = 30001 + + self.puti(1, 'function', + 'Function {}: Write Multiple {}'.format(function, data_unit)) + + starting_address = self.half_word(2) + # Some instruction manuals use a long form name for addresses, this is + # listed here for convienience. + address_name = long_address_offset + starting_address + self.puti(3, 'address', + 'Start at address 0x{:X} / {:d}'.format(starting_address, + address_name)) + + quantity_of_outputs = self.half_word(4) + if quantity_of_outputs <= max_outputs: + self.puti(5, 'data', + 'Write {} {}'.format(quantity_of_outputs, data_unit)) + else: + self.puti(5, 'error', + 'Bad value: {} {}. Max is {}'.format(quantity_of_outputs, + data_unit, max_outputs)) + + self.check_crc(7) + + def parse_report_server_id(self): + # Buildup of this function: + # 1 byte serverID + # 1 byte function (17) + # 1 byte bytecount + # 1 byte serverID (counts for bytecount) + # 1 byte Run Indicator Status (counts for bytecount) + # bytecount - 2 bytes of device specific data (counts for bytecount) + # 2 bytes of CRC + self.mimumum_length = 7 + data = self.data + self.puti(1, 'function', 'Function 17: Report Server ID') + + bytecount = data[2].data + self.puti(2, 'length', 'Data is {} bytes long'.format(bytecount)) + + self.puti(3, 'data', 'serverID: {}'.format(data[3].data)) + + run_indicator_status = data[4].data + if run_indicator_status == 0x00: + self.puti(4, 'data', 'Run Indicator status: Off') + elif run_indicator_status == 0xFF: + self.puti(4, 'data', 'Run Indicator status: On') + else: + self.puti(4, 'error', + 'Bad Run Indicator status: 0x{:X}'.format(run_indicator_status)) + + self.putl('data', 'Device specific data: {}, "{}"'.format(data[-1].data, + chr(data[-1].data)), 2 + bytecount) + + self.check_crc(4 + bytecount) + + def parse_error(self): + '''Parse a Modbus error message.''' + self.mimumum_length = 5 + # The function code of an error is always 0x80 above the function call + # that caused it. + functioncode = self.data[1].data - 0x80 + + functions = { + 1: 'Read Coils', + 2: 'Read Discrete Inputs', + 3: 'Read Holding Registers', + 4: 'Read Input Registers', + 5: 'Write Single Coil', + 6: 'Write Single Register', + 7: 'Read Exception Status', + 8: 'Diagnostic', + 11: 'Get Com Event Counter', + 12: 'Get Com Event Log', + 15: 'Write Multiple Coils', + 16: 'Write Multiple Registers', + 17: 'Report Slave ID', + 20: 'Read File Record', + 21: 'Write File Record', + 22: 'Mask Write Register', + 23: 'Read/Write Multiple Registers', + 24: 'Read FIFO Queue', + 43: 'Read Device Identification/Encapsulated Interface Transport', + } + functionname = '{}: {}'.format(functioncode, + functions.get(functioncode, 'Unknown function')) + self.puti(1, 'function', + 'Error for function {}'.format(functionname)) + + error = self.data[2].data + errorcodes = { + 1: 'Illegal Function', + 2: 'Illegal Data Address', + 3: 'Illegal Data Value', + 4: 'Slave Device Failure', + 5: 'Acknowledge', + 6: 'Slave Device Busy', + 8: 'Memory Parity Error', + 10: 'Gateway Path Unavailable', + 11: 'Gateway Target Device failed to respond', + } + errorname = '{}: {}'.format(error, errorcodes.get(error, 'Unknown')) + self.puti(2, 'data', 'Error {}'.format(errorname)) + self.check_crc(4) + +class Modbus_ADU_CS(Modbus_ADU): + '''CS stands for Client -> Server.''' + def parse(self): + '''Select which specific Modbus function we should parse.''' + data = self.data + + # This try-catch is being used as flow control. + try: + server_id = data[0].data + message = '' + if server_id == 0: + message = 'Broadcast message' + elif 1 <= server_id <= 247: + message = 'Slave ID: {}'.format(server_id) + elif 248 <= server_id <= 255: + message = 'Slave ID: {} (reserved address)'.format(server_id) + self.puti(0, 'server-id', message) + + function = data[1].data + if function >= 1 and function <= 4: + self.parse_read_data_command() + if function == 5: + self.parse_write_single_coil() + if function == 6: + self.parse_write_single_register() + if function in {7, 11, 12, 17}: + self.parse_single_byte_request() + elif function == 8: + self.parse_diagnostics() + if function in {15, 16}: + self.parse_write_multiple() + elif function == 22: + self.parse_mask_write_register() + elif function == 23: + self.parse_read_write_registers() + elif function in {21, 21, 24, 43}: + self.parse_not_implemented() + else: + self.puti(1, 'error', + 'Unknown function: {}'.format(data[1].data)) + self.putl('error', 'Unknown function') + + # If the message gets here without raising an exception, the + # message goes on longer than it should. + self.putl('error', 'Message too long') + + except No_more_data: + # Just a message saying we don't need to parse anymore this round. + pass + + def parse_read_data_command(self): + '''Interpret a command to read x units of data starting at address, ie + functions 1, 2, 3 and 4, and write the result to the annotations.''' + data = self.data + self.minimum_length = 8 + + function = data[1].data + functionname = {1: 'Read Coils', + 2: 'Read Discrete Inputs', + 3: 'Read Holding Registers', + 4: 'Read Input Registers', + }[function] + + self.puti(1, 'function', + 'Function {}: {}'.format(function, functionname)) + + starting_address = self.half_word(2) + # Some instruction manuals use a long form name for addresses, this is + # listed here for convienience. + # Example: holding register 60 becomes 30061. + address_name = 10000 * function + 1 + starting_address + self.puti(3, 'address', + 'Start at address 0x{:X} / {:d}'.format(starting_address, + address_name)) + + self.puti(5, 'length', + 'Read {:d} units of data'.format(self.half_word(4))) + self.check_crc(7) + + def parse_single_byte_request(self): + '''Some Modbus functions have no arguments, this parses those.''' + function = self.data[1].data + function_name = {7: 'Read Exception Status', + 11: 'Get Comm Event Counter', + 12: 'Get Comm Event Log', + 17: 'Report Slave ID', + }[function] + self.puti(1, 'function', + 'Function {}: {}'.format(function, function_name)) + + self.check_crc(3) + + def parse_write_multiple(self): + '''Function 15 and 16 are almost the same, so we can parse them both + using one function.''' + self.mimumum_length = 9 + + function = self.data[1].data + if function == 15: + data_unit = 'Coils' + max_outputs = 0x07B0 + ratio_bytes_data = 1/8 + long_address_offset = 10001 + elif function == 16: + data_unit = 'Registers' + max_outputs = 0x007B + ratio_bytes_data = 2 + long_address_offset = 30001 + + self.puti(1, 'function', + 'Function {}: Write Multiple {}'.format(function, data_unit)) + + starting_address = self.half_word(2) + # Some instruction manuals use a long form name for addresses, this is + # listed here for convienience. + address_name = long_address_offset + starting_address + self.puti(3, 'address', + 'Start at address 0x{:X} / {:d}'.format(starting_address, + address_name)) + + quantity_of_outputs = self.half_word(4) + if quantity_of_outputs <= max_outputs: + self.puti(5, 'length', + 'Write {} {}'.format(quantity_of_outputs, data_unit)) + else: + self.puti(5, 'error', + 'Bad value: {} {}. Max is {}'.format(quantity_of_outputs, + data_unit, max_outputs)) + proper_bytecount = ceil(quantity_of_outputs * ratio_bytes_data) + + bytecount = self.data[6].data + if bytecount == proper_bytecount: + self.puti(6, 'length', 'Byte count: {}'.format(bytecount)) + else: + self.puti(6, 'error', + 'Bad byte count, is {}, should be {}'.format(bytecount, + proper_bytecount)) + self.mimumum_length = bytecount + 9 + + self.putl('data', 'Value 0x{:X}', 6 + bytecount) + + self.check_crc(bytecount + 8) + + def parse_read_file_record(self): + self.puti(1, 'function', 'Function 20: Read file records') + + data = self.data + + bytecount = data[2].data + + self.minimum_length = 5 + bytecount + # 1 for serverID, 1 for function, 1 for bytecount, 2 for CRC. + + if 0x07 <= bytecount <= 0xF5: + self.puti(2, 'length', 'Request is {} bytes long'.format(bytecount)) + else: + self.puti(2, 'error', + 'Request claims to be {} bytes long, legal values are between' + ' 7 and 247'.format(bytecount)) + + current_byte = len(data) - 1 + # Function 20 is a number of sub-requests, the first starting at 3, + # the total length of the sub-requests is bytecount. + if current_byte <= bytecount + 2: + step = (current_byte - 3) % 7 + if step == 0: + if data[current_byte].data == 6: + self.puti(current_byte, 'data', 'Start sub-request') + else: + self.puti(current_byte, 'error', + 'First byte of subrequest should be 0x06') + elif step == 1: + raise No_more_data + elif step == 2: + file_number = self.half_word(current_byte - 1) + self.puti(current_byte, 'data', + 'Read File number {}'.format(file_number)) + elif step == 3: + raise No_more_data + elif step == 4: + record_number = self.half_word(current_byte - 1) + self.puti(current_byte, 'address', + 'Read from record number {}'.format(record_number)) + # TODO: Check if within range. + elif step == 5: + raise No_more_data + elif step == 6: + records_to_read = self.half_word(current_byte - 1) + self.puti(current_byte, 'length', + 'Read {} records'.format(records_to_read)) + self.check_crc(4 + bytecount) + + def parse_read_write_registers(self): + '''Parse function 23: Read/Write multiple registers.''' + self.minimum_length = 13 + + self.puti(1, 'function', 'Function 23: Read/Write Multiple Registers') + + starting_address = self.half_word(2) + # Some instruction manuals use a long form name for addresses, this is + # listed here for convienience. + # Example: holding register 60 becomes 30061. + address_name = 30001 + starting_address + self.puti(3, 'address', + 'Read starting at address 0x{:X} / {:d}'.format(starting_address, + address_name)) + + self.puti(5, 'length', 'Read {:d} units of data'.format(self.half_word(4))) + + starting_address = self.half_word(6) + self.puti(7, 'address', + 'Write starting at address 0x{:X} / {:d}'.format(starting_address, + address_name)) + + quantity_of_outputs = self.half_word(8) + self.puti(9, 'length', + 'Write {} registers'.format(quantity_of_outputs)) + proper_bytecount = quantity_of_outputs * 2 + + bytecount = self.data[10].data + if bytecount == proper_bytecount: + self.puti(10, 'length', 'Byte count: {}'.format(bytecount)) + else: + self.puti(10, 'error', + 'Bad byte count, is {}, should be {}'.format(bytecount, + proper_bytecount)) + self.mimumum_length = bytecount + 13 + + self.putl('data', 'Data, value 0x{:02X}', 10 + bytecount) + + self.check_crc(bytecount + 12) + +class Decoder(srd.Decoder): + api_version = 3 + id = 'modbus' + name = 'Modbus' + longname = 'Modbus RTU over RS232/RS485' + desc = 'Modbus RTU protocol for industrial applications.' + license = 'gplv3+' + inputs = ['uart'] + outputs = ['modbus'] + tags = ['Embedded/industrial'] + annotations = ( + ('sc-server-id', ''), + ('sc-function', ''), + ('sc-crc', ''), + ('sc-address', ''), + ('sc-data', ''), + ('sc-length', ''), + ('sc-error', ''), + ('cs-server-id', ''), + ('cs-function', ''), + ('cs-crc', ''), + ('cs-address', ''), + ('cs-data', ''), + ('cs-length', ''), + ('cs-error', ''), + ('error-indication', ''), + ) + annotation_rows = ( + ('sc', 'Server->client', (7, 8, 9, 10, 11, 12, 13)), + ('cs', 'Client->server', (0, 1, 2, 3, 4, 5, 6)), + ('error-indicator', 'Errors in frame', (14,)), + ) + options = ( + {'id': 'channel', 'desc': 'Direction', 'default': 'TX', + 'values': ('TX', 'RX')}, + ) + + def __init__(self): + self.reset() + + def reset(self): + self.ADUSc = None # Start off with empty slave -> client ADU. + self.ADUCs = None # Start off with empty client -> slave ADU. + + # The reason we have both (despite not supporting full duplex comms) is + # because we want to be able to decode the message as both client -> + # server and server -> client, and let the user see which of the two + # the ADU was. + + self.bitlength = None # We will later test how long a bit is. + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def puta(self, start, end, ann_str, message): + '''Put an annotation from start to end, with ann as a + string. This means you don't have to know the ann's + number to write annotations to it.''' + ann = [s[0] for s in self.annotations].index(ann_str) + self.put(start, end, self.out_ann, [ann, [message]]) + + def decode_adu(self, ss, es, data, direction): + '''Decode the next byte or bit (depending on type) in the ADU. + ss: Start time of the data + es: End time of the data + data: Data as passed from the UART decoder + direction: Is this data for the Cs (client -> server) or Sc (server -> + client) being decoded right now?''' + ptype, rxtx, pdata = data + + # We don't have a nice way to get the baud rate from UART, so we have + # to figure out how long a bit lasts. We do this by looking at the + # length of (probably) the startbit. + if self.bitlength is None: + if ptype == 'STARTBIT' or ptype == 'STOPBIT': + self.bitlength = es - ss + else: + # If we don't know the bitlength yet, we can't start decoding. + return + + # Select the ADU, create the ADU if needed. + # We set ADU.startNewFrame = True when we know the old one is over. + if direction == 'Sc': + if (self.ADUSc is None) or self.ADUSc.startNewFrame: + self.ADUSc = Modbus_ADU_SC(self, ss, TX, 'sc-') + ADU = self.ADUSc + if direction == 'Cs': + if self.ADUCs is None or self.ADUCs.startNewFrame: + self.ADUCs = Modbus_ADU_CS(self, ss, TX, 'cs-') + ADU = self.ADUCs + + # We need to determine if the last ADU is over. + # According to the Modbus spec, there should be 3.5 characters worth of + # space between each message. But if within a message there is a length + # of more than 1.5 character, that's an error. For our purposes + # somewhere between seems fine. + # A character is 11 bits long, so (3.5 + 1.5)/2 * 11 ~= 28 + # TODO: Display error for too short or too long. + if (ss - ADU.last_read) <= self.bitlength * 28: + ADU.add_data(ss, es, data) + else: + # It's been too long since the last part of the ADU! + # If there is any data in the ADU we need to show it to the user + if len(ADU.data) > 0: + # Extend errors for 3 bits after last byte, we can guarantee + # space. + ADU.close(ADU.data[-1].end + self.bitlength * 3) + + ADU.startNewFrame = True + # Restart this function, it will make a new ADU for us. + self.decode_adu(ss, es, data, direction) + + def decode(self, ss, es, data): + ptype, rxtx, pdata = data + + # Decide what ADU(s) we need this packet to go to. + # Note that it's possible to go to both ADUs. + if self.options['channel'] == 'TX': + self.decode_adu(ss, es, data, 'Sc') + if self.options['channel'] == 'RX': + self.decode_adu(ss, es, data, 'Cs') diff --git a/libsigrokdecode4DSL/decoders/morse/__init__.py b/libsigrokdecode4DSL/decoders/morse/__init__.py new file mode 100644 index 00000000..5d916247 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/morse/__init__.py @@ -0,0 +1,28 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2017 Christoph Rackwitz +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +Morse code is a method of transmitting text information as a series of +on-off tones. + +Details: +https://en.wikipedia.org/wiki/Morse_code +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/morse/pd.py b/libsigrokdecode4DSL/decoders/morse/pd.py new file mode 100644 index 00000000..8b5cb829 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/morse/pd.py @@ -0,0 +1,250 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2017 Christoph Rackwitz +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd + +def decode_ditdah(s): + return tuple({'-': 3, '.': 1}[c] for c in s) + +def encode_ditdah(tpl): + return ''.join({1: '.', 3: '-'}[c] for c in tpl) + +# https://www.itu.int/dms_pubrec/itu-r/rec/m/R-REC-M.1677-1-200910-I!!PDF-E.pdf +# Recommendation ITU-R M.1677-1 +# (10/2009) +# International Morse code +alphabet = { + # 1.1.1 Letters + '.-': 'a', + '-...': 'b', + '-.-.': 'c', + '-..': 'd', + '.': 'e', + '..-..': 'é', # "accented" + '..-.': 'f', + '--.': 'g', + '....': 'h', + '..': 'i', + '.---': 'j', + '-.-': 'k', + '.-..': 'l', + '--': 'm', + '-.': 'n', + '---': 'o', + '.--.': 'p', + '--.-': 'q', + '.-.': 'r', + '...': 's', + '-': 't', + '..-': 'u', + '...-': 'v', + '.--': 'w', + '-..-': 'x', + '-.--': 'y', + '--..': 'z', + + # 1.1.2 Figures + '.----': '1', + '..---': '2', + '...--': '3', + '....-': '4', + '.....': '5', + '-....': '6', + '--...': '7', + '---..': '8', + '----.': '9', + '-----': '0', + + # 1.1.3 Punctuation marks and miscellaneous signs + '.-.-.-': '.', # Full stop (period) + '--..--': ',', # Comma + '---...': ':', # Colon or division sign + '..--..': '?', # Question mark (note of interrogation or request for repetition of a transmission not understood) + '.----.': '’', # Apostrophe + '-....-': '-', # Hyphen or dash or subtraction sign + '-..-.': '/', # Fraction bar or division sign + '-.--.': '(', # Left-hand bracket (parenthesis) + '-.--.-': ')', # Right-hand bracket (parenthesis) + '.-..-.': '“ ”', # Inverted commas (quotation marks) (before and after the words) + '-...-': '=', # Double hyphen + '...-.': 'UNDERSTOOD', # Understood + '........': 'ERROR', # Error (eight dots) + '.-.-.': '+', # Cross or addition sign + '.-...': 'WAIT', # Wait + '...-.-': 'EOW', # End of work + '-.-.-': 'START', # Starting signal (to precede every transmission) + '.--.-.': '@', # Commercial at + + #'-.-': 'ITT', # K: Invitation to transmit + + # 3.2.1 For the multiplication sign, the signal corresponding to the letter X shall be transmitted. + #'-..-': '×', # Multiplication sign +} + +alphabet = {decode_ditdah(k): v for k, v in alphabet.items()} + +# 2 Spacing and length of the signals (right side is just for printing). +symbols = { # level, time units + # 2.1 A dash is equal to three dots. + (1, 1): '*', + (1, 3): '===', + # 2.2 The space between the signals forming the same letter is equal to one dot. + (0, 1): '_', + # 2.3 The space between two letters is equal to three dots. + (0, 3): '__', + # 2.4 The space between two words is equal to seven dots. + (0, 7): '___', +} + +class Decoder(srd.Decoder): + api_version = 3 + id = 'morse' + name = 'Morse' + longname = 'Morse code' + desc = 'Demodulated morse code protocol.' + license = 'gplv2+' + inputs = ['logic'] + outputs = [] + tags = ['Encoding'] + channels = ( + {'id': 'data', 'name': 'Data', 'desc': 'Data line'}, + ) + options = ( + {'id': 'timeunit', 'desc': 'Time unit (guess)', 'default': 0.1}, + ) + annotations = ( + ('time', 'Time'), + ('units', 'Units'), + ('symbol', 'Symbol'), + ('letter', 'Letter'), + ('word', 'Word'), + ) + annotation_rows = tuple((u, v, (i,)) for i, (u, v) in enumerate(annotations)) + + def __init__(self): + self.reset() + + def reset(self): + self.samplerate = None + + def metadata(self, key, value): + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + self.out_binary = self.register(srd.OUTPUT_BINARY) + + def decode_symbols(self): + # Annotate symbols, emit symbols, handle timeout via token. + + timeunit = self.options['timeunit'] + + self.wait({0: 'r'}) + prevtime = self.samplenum # Time of an actual edge. + + while True: + (val,) = self.wait([{0: 'e'}, {'skip': int(5 * self.samplerate * timeunit)}]) + + pval = 1 - val + curtime = self.samplenum + dt = (curtime - prevtime) / self.samplerate + units = dt / timeunit + iunits = int(max(1, round(units))) + error = abs(units - iunits) + + symbol = (pval, iunits) + + if (self.matched & (0b1 << 1)): + yield None # Flush word. + continue + + self.put(prevtime, curtime, self.out_ann, [0, ['{:.3g}'.format(dt)]]) + + if symbol in symbols: + self.put(prevtime, curtime, self.out_ann, [1, ['{:.1f}*{:.3g}'.format(units, timeunit)]]) + yield (prevtime, curtime, symbol) + else: + self.put(prevtime, curtime, self.out_ann, [1, ['!! {:.1f}*{:.3g} !!'.format(units, timeunit)]]) + + prevtime = curtime + + thisunit = dt / iunits + timeunit += (thisunit - timeunit) * 0.2 * max(0, 1 - 2*error) # Adapt. + + def decode_morse(self): + # Group symbols into letters. + sequence = () + s0 = s1 = None + + for item in self.decode_symbols(): + do_yield = False + if item is not None: # Level + width. + (t0, t1, symbol) = item + (sval, sunits) = symbol + if sval == 1: + if s0 is None: + s0 = t0 + s1 = t1 + sequence += (sunits,) + else: + # Generate "flush" for end of letter, end of word. + if sunits >= 3: + do_yield = True + else: + do_yield = True + if do_yield: + if sequence: + yield (s0, s1, alphabet.get(sequence, encode_ditdah(sequence))) + sequence = () + s0 = s1 = None + if item is None: + yield None # Pass through flush of 5+ space. + + def decode(self): + + # Strictly speaking there is no point in running this decoder + # when the sample rate is unknown or zero. But the previous + # implementation already fell back to a rate of 1 in that case. + # We stick with this approach, to not introduce new constraints + # for existing use scenarios. + if not self.samplerate: + self.samplerate = 1.0 + + # Annotate letters, group into words. + s0 = s1 = None + word = '' + for item in self.decode_morse(): + do_yield = False + + if item is not None: # Append letter. + (t0, t1, letter) = item + self.put(t0, t1, self.out_ann, [3, [letter]]) + if s0 is None: + s0 = t0 + s1 = t1 + word += letter + else: + do_yield = True + + if do_yield: # Flush of word. + if word: + self.put(s0, s1, self.out_ann, [4, [word]]) + word = '' + s0 = s1 = None diff --git a/libsigrokdecode4DSL/decoders/mrf24j40/__init__.py b/libsigrokdecode4DSL/decoders/mrf24j40/__init__.py new file mode 100644 index 00000000..37b8b5c3 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/mrf24j40/__init__.py @@ -0,0 +1,25 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2015 Karl Palsson +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This decoder stacks on top of the 'spi' PD and decodes Microchip MRF24J40 +IEEE 802.15.4 2.4 GHz RF tranceiver commands and data. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/mrf24j40/lists.py b/libsigrokdecode4DSL/decoders/mrf24j40/lists.py new file mode 100644 index 00000000..f5931c24 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/mrf24j40/lists.py @@ -0,0 +1,165 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2015 Karl Palsson +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +sregs = { + 0: 'RXMCR', + 1: 'PANIDL', + 2: 'PANIDH', + 3: 'SADRL', + 4: 'SADRH', + 5: 'EADR0', + 6: 'EADR1', + 7: 'EADR2', + 8: 'EADR3', + 9: 'EADR4', + 0xa: 'EADR5', + 0xb: 'EADR6', + 0xc: 'EADR7', + 0xd: 'RXFLUSH', + 0xe: 'Reserved', + 0xf: 'Reserved', + 0x10: 'ORDER', + 0x11: 'TXMCR', + 0x12: 'ACKTMOUT', + 0x13: 'ESLOTG1', + 0x14: 'SYMTICKL', + 0x15: 'SYMTICKH', + 0x16: 'PACON0', + 0x17: 'PACON1', + 0x18: 'PACON2', + 0x19: 'Reserved', + 0x1a: 'TXBCON0', + 0x1b: 'TXNCON', + 0x1c: 'TXG1CON', + 0x1d: 'TXG2CON', + 0x1e: 'ESLOTG23', + 0x1f: 'ESLOTG45', + 0x20: 'ESLOTG67', + 0x21: 'TXPEND', + 0x22: 'WAKECON', + 0x23: 'FRMOFFSET', + 0x24: 'TXSTAT', + 0x25: 'TXBCON1', + 0x26: 'GATECLK', + 0x27: 'TXTIME', + 0x28: 'HSYMTIMRL', + 0x29: 'HSYMTIMRH', + 0x2a: 'SOFTRST', + 0x2b: 'Reserved', + 0x2c: 'SECCON0', + 0x2d: 'SECCON1', + 0x2e: 'TXSTBL', + 0x2f: 'Reserved', + 0x30: 'RXSR', + 0x31: 'INTSTAT', + 0x32: 'INTCON', + 0x33: 'GPIO', + 0x34: 'TRISGPIO', + 0x35: 'SLPACK', + 0x36: 'RFCTL', + 0x37: 'SECCR2', + 0x38: 'BBREG0', + 0x39: 'BBREG1', + 0x3a: 'BBREG2', + 0x3b: 'BBREG3', + 0x3c: 'BBREG4', + 0x3d: 'Reserved', + 0x3e: 'BBREG6', + 0x3f: 'CCAEDTH', +} + +lregs = { + 0x200: 'RFCON0', + 0x201: 'RFCON1', + 0x202: 'RFCON2', + 0x203: 'RFCON3', + 0x204: 'Reserved', + 0x205: 'RFCON5', + 0x206: 'RFCON6', + 0x207: 'RFCON7', + 0x208: 'RFCON8', + 0x209: 'SLPCAL0', + 0x20A: 'SLPCAL1', + 0x20B: 'SLPCAL2', + 0x20C: 'Reserved', + 0x20D: 'Reserved', + 0x20E: 'Reserved', + 0x20F: 'RFSTATE', + 0x210: 'RSSI', + 0x211: 'SLPCON0', + 0x212: 'Reserved', + 0x213: 'Reserved', + 0x214: 'Reserved', + 0x215: 'Reserved', + 0x216: 'Reserved', + 0x217: 'Reserved', + 0x218: 'Reserved', + 0x219: 'Reserved', + 0x21A: 'Reserved', + 0x21B: 'Reserved', + 0x21C: 'Reserved', + 0x21D: 'Reserved', + 0x21E: 'Reserved', + 0x21F: 'Reserved', + 0x220: 'SLPCON1', + 0x221: 'Reserved', + 0x222: 'WAKETIMEL', + 0x223: 'WAKETIMEH', + 0x224: 'REMCNTL', + 0x225: 'REMCNTH', + 0x226: 'MAINCNT0', + 0x227: 'MAINCNT1', + 0x228: 'MAINCNT2', + 0x229: 'MAINCNT3', + 0x22A: 'Reserved', + 0x22B: 'Reserved', + 0x22C: 'Reserved', + 0x22D: 'Reserved', + 0x22E: 'Reserved', + 0x22F: 'TESTMODE', + 0x230: 'ASSOEADR0', + 0x231: 'ASSOEADR1', + 0x232: 'ASSOEADR2', + 0x233: 'ASSOEADR3', + 0x234: 'ASSOEADR4', + 0x235: 'ASSOEADR5', + 0x236: 'ASSOEADR6', + 0x237: 'ASSOEADR7', + 0x238: 'ASSOSADR0', + 0x239: 'ASSOSADR1', + 0x23A: 'Reserved', + 0x23B: 'Reserved', + 0x23C: 'Unimplemented', + 0x23D: 'Unimplemented', + 0x23E: 'Unimplemented', + 0x23F: 'Unimplemented', + 0x240: 'UPNONCE0', + 0x241: 'UPNONCE1', + 0x242: 'UPNONCE2', + 0x243: 'UPNONCE3', + 0x244: 'UPNONCE4', + 0x245: 'UPNONCE5', + 0x246: 'UPNONCE6', + 0x247: 'UPNONCE7', + 0x248: 'UPNONCE8', + 0x249: 'UPNONCE9', + 0x24A: 'UPNONCE10', + 0x24B: 'UPNONCE11', + 0x24C: 'UPNONCE12' +} diff --git a/libsigrokdecode4DSL/decoders/mrf24j40/pd.py b/libsigrokdecode4DSL/decoders/mrf24j40/pd.py new file mode 100644 index 00000000..b242ee66 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/mrf24j40/pd.py @@ -0,0 +1,137 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2015 Karl Palsson +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd +from .lists import * + +class Decoder(srd.Decoder): + api_version = 3 + id = 'mrf24j40' + name = 'MRF24J40' + longname = 'Microchip MRF24J40' + desc = 'IEEE 802.15.4 2.4 GHz RF tranceiver chip.' + license = 'gplv2+' + inputs = ['spi'] + outputs = [] + tags = ['IC', 'Wireless/RF'] + annotations = ( + ('sread', 'Short register read commands'), + ('swrite', 'Short register write commands'), + ('lread', 'Long register read commands'), + ('lwrite', 'Long register write commands'), + ('warning', 'Warnings'), + ) + annotation_rows = ( + ('read', 'Read', (0, 2)), + ('write', 'Write', (1, 3)), + ('warnings', 'Warnings', (4,)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.ss_cmd, self.es_cmd = 0, 0 + self.mosi_bytes = [] + self.miso_bytes = [] + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def putx(self, data): + self.put(self.ss_cmd, self.es_cmd, self.out_ann, data) + + def putw(self, pos, msg): + self.put(pos[0], pos[1], self.out_ann, [4, [msg]]) + + def reset_data(self): + self.mosi_bytes = [] + self.miso_bytes = [] + + def handle_short(self): + write = self.mosi_bytes[0] & 0x1 + reg = (self.mosi_bytes[0] >> 1) & 0x3f + reg_desc = sregs.get(reg, 'illegal') + if write: + self.putx([1, ['%s: %#x' % (reg_desc, self.mosi_bytes[1])]]) + else: + self.putx([0, ['%s: %#x' % (reg_desc, self.miso_bytes[1])]]) + + def handle_long(self): + dword = self.mosi_bytes[0] << 8 | self.mosi_bytes[1] + write = dword & (0x1 << 4) + reg = dword >> 5 & 0x3ff + if reg >= 0x0: + reg_desc = 'TX:%#x' % reg + if reg >= 0x80: + reg_desc = 'TX beacon:%#x' % reg + if reg >= 0x100: + reg_desc = 'TX GTS1:%#x' % reg + if reg >= 0x180: + reg_desc = 'TX GTS2:%#x' % reg + if reg >= 0x200: + reg_desc = lregs.get(reg, 'illegal') + if reg >= 0x280: + reg_desc = 'Security keys:%#x' % reg + if reg >= 0x2c0: + reg_desc = 'Reserved:%#x' % reg + if reg >= 0x300: + reg_desc = 'RX:%#x' % reg + + if write: + self.putx([3, ['%s: %#x' % (reg_desc, self.mosi_bytes[2])]]) + else: + self.putx([2, ['%s: %#x' % (reg_desc, self.miso_bytes[2])]]) + + def decode(self, ss, es, data): + ptype = data[0] + if ptype == 'CS-CHANGE': + # If we transition high mid-stream, toss out our data and restart. + cs_old, cs_new = data[1:] + if cs_old is not None and cs_old == 0 and cs_new == 1: + if len(self.mosi_bytes) not in (0, 2, 3): + self.putw([self.ss_cmd, es], 'Misplaced CS!') + self.reset_data() + return + + # Don't care about anything else. + if ptype != 'DATA': + return + mosi, miso = data[1:] + + self.ss, self.es = ss, es + + if len(self.mosi_bytes) == 0: + self.ss_cmd = ss + self.mosi_bytes.append(mosi) + self.miso_bytes.append(miso) + + # Everything is either 2 bytes or 3 bytes. + if len(self.mosi_bytes) < 2: + return + + if self.mosi_bytes[0] & 0x80: + if len(self.mosi_bytes) == 3: + self.es_cmd = es + self.handle_long() + self.reset_data() + else: + self.es_cmd = es + self.handle_short() + self.reset_data() diff --git a/libsigrokdecode4DSL/decoders/mxc6225xu/__init__.py b/libsigrokdecode4DSL/decoders/mxc6225xu/__init__.py new file mode 100644 index 00000000..2aaa726f --- /dev/null +++ b/libsigrokdecode4DSL/decoders/mxc6225xu/__init__.py @@ -0,0 +1,28 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2012 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This decoder stacks on top of the 'i2c' PD and decodes the MEMSIC MXC6225XU +digital thermal orientation sensor (DTOS) protocol. + +The chip's I²C interface supports standard mode and fast mode (max. 400kHz). +Its I²C slave address is 0x2a. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/mxc6225xu/pd.py b/libsigrokdecode4DSL/decoders/mxc6225xu/pd.py new file mode 100644 index 00000000..e9617782 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/mxc6225xu/pd.py @@ -0,0 +1,217 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2012 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd + +# Definitions of various bits in MXC6225XU registers. +status = { + # SH[1:0] + 'sh': { + 0b00: 'none', + 0b01: 'shake left', + 0b10: 'shake right', + 0b11: 'undefined', + }, + # ORI[1:0] and OR[1:0] (same format) + 'ori': { + 0b00: 'vertical in upright orientation', + 0b01: 'rotated 90 degrees clockwise', + 0b10: 'vertical in inverted orientation', + 0b11: 'rotated 90 degrees counterclockwise', + }, + # SHTH[1:0] + 'shth': { + 0b00: '0.5g', + 0b01: '1.0g', + 0b10: '1.5g', + 0b11: '2.0g', + }, + # SHC[1:0] + 'shc': { + 0b00: '16', + 0b01: '32', + 0b10: '64', + 0b11: '128', + }, + # ORC[1:0] + 'orc': { + 0b00: '16', + 0b01: '32', + 0b10: '64', + 0b11: '128', + }, +} + +class Decoder(srd.Decoder): + api_version = 3 + id = 'mxc6225xu' + name = 'MXC6225XU' + longname = 'MEMSIC MXC6225XU' + desc = 'Digital Thermal Orientation Sensor (DTOS) protocol.' + license = 'gplv2+' + inputs = ['i2c'] + outputs = [] + tags = ['IC', 'Sensor'] + annotations = ( + ('text', 'Human-readable text'), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.state = 'IDLE' + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def putx(self, data): + self.put(self.ss, self.es, self.out_ann, data) + + def handle_reg_0x00(self, b): + # XOUT: 8-bit x-axis acceleration output. + # Data is in 2's complement, values range from -128 to 127. + self.putx([0, ['XOUT: %d' % b]]) + + def handle_reg_0x01(self, b): + # YOUT: 8-bit y-axis acceleration output. + # Data is in 2's complement, values range from -128 to 127. + self.putx([0, ['YOUT: %d' % b]]) + + def handle_reg_0x02(self, b): + # STATUS: Orientation and shake status. + + # Bits[7:7]: INT + int_val = (b >> 7) & 1 + s = 'unchanged and no' if (int_val == 0) else 'changed or' + ann = 'INT = %d: Orientation %s shake event occurred\n' % (int_val, s) + + # Bits[6:5]: SH[1:0] + sh = (((b >> 6) & 1) << 1) | ((b >> 5) & 1) + ann += 'SH[1:0] = %s: Shake event: %s\n' % \ + (bin(sh)[2:], status['sh'][sh]) + + # Bits[4:4]: TILT + tilt = (b >> 4) & 1 + s = '' if (tilt == 0) else 'not ' + ann += 'TILT = %d: Orientation measurement is %svalid\n' % (tilt, s) + + # Bits[3:2]: ORI[1:0] + ori = (((b >> 3) & 1) << 1) | ((b >> 2) & 1) + ann += 'ORI[1:0] = %s: %s\n' % (bin(ori)[2:], status['ori'][ori]) + + # Bits[1:0]: OR[1:0] + or_val = (((b >> 1) & 1) << 1) | ((b >> 0) & 1) + ann += 'OR[1:0] = %s: %s\n' % (bin(or_val)[2:], status['ori'][or_val]) + + # ann += 'b = %s\n' % (bin(b)) + + self.putx([0, [ann]]) + + def handle_reg_0x03(self, b): + # DETECTION: Powerdown, orientation and shake detection parameters. + # Note: This is a write-only register. + + # Bits[7:7]: PD + pd = (b >> 7) & 1 + s = 'Do not power down' if (pd == 0) else 'Power down' + ann = 'PD = %d: %s the device (into a low-power state)\n' % (pd, s) + + # Bits[6:6]: SHM + shm = (b >> 6) & 1 + ann = 'SHM = %d: Set shake mode to %d\n' % (shm, shm) + + # Bits[5:4]: SHTH[1:0] + shth = (((b >> 5) & 1) << 1) | ((b >> 4) & 1) + ann += 'SHTH[1:0] = %s: Set shake threshold to %s\n' \ + % (bin(shth)[2:], status['shth'][shth]) + + # Bits[3:2]: SHC[1:0] + shc = (((b >> 3) & 1) << 1) | ((b >> 2) & 1) + ann += 'SHC[1:0] = %s: Set shake count to %s readings\n' \ + % (bin(shc)[2:], status['shc'][shc]) + + # Bits[1:0]: ORC[1:0] + orc = (((b >> 1) & 1) << 1) | ((b >> 0) & 1) + ann += 'ORC[1:0] = %s: Set orientation count to %s readings\n' \ + % (bin(orc)[2:], status['orc'][orc]) + + self.putx([0, [ann]]) + + # TODO: Fixup, this is copy-pasted from another PD. + # TODO: Handle/check the ACKs/NACKs. + def decode(self, ss, es, data): + cmd, databyte = data + + # Store the start/end samples of this I²C packet. + self.ss, self.es = ss, es + + # State machine. + if self.state == 'IDLE': + # Wait for an I²C START condition. + if cmd != 'START': + return + self.state = 'GET SLAVE ADDR' + elif self.state == 'GET SLAVE ADDR': + # Wait for an address write operation. + # TODO: We should only handle packets to the slave(?) + if cmd != 'ADDRESS WRITE': + return + self.state = 'GET REG ADDR' + elif self.state == 'GET REG ADDR': + # Wait for a data write (master selects the slave register). + if cmd != 'DATA WRITE': + return + self.reg = databyte + self.state = 'WRITE REGS' + elif self.state == 'WRITE REGS': + # If we see a Repeated Start here, it's a multi-byte read. + if cmd == 'START REPEAT': + self.state = 'READ REGS' + return + # Otherwise: Get data bytes until a STOP condition occurs. + if cmd == 'DATA WRITE': + handle_reg = getattr(self, 'handle_reg_0x%02x' % self.reg) + handle_reg(databyte) + self.reg += 1 + # TODO: Check for NACK! + elif cmd == 'STOP': + # TODO + self.state = 'IDLE' + else: + pass # TODO + elif self.state == 'READ REGS': + # Wait for an address read operation. + # TODO: We should only handle packets to the slave(?) + if cmd == 'ADDRESS READ': + self.state = 'READ REGS2' + return + else: + pass # TODO + elif self.state == 'READ REGS2': + if cmd == 'DATA READ': + handle_reg = getattr(self, 'handle_reg_0x%02x' % self.reg) + handle_reg(databyte) + self.reg += 1 + # TODO: Check for NACK! + elif cmd == 'STOP': + # TODO + self.state = 'IDLE' + else: + pass # TODO? diff --git a/libsigrokdecode4DSL/decoders/nes_gamepad/__init__.py b/libsigrokdecode4DSL/decoders/nes_gamepad/__init__.py new file mode 100644 index 00000000..e7543748 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/nes_gamepad/__init__.py @@ -0,0 +1,54 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2019 Stephan Thiele +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This decoder stacks on top of the 'spi' PD and decodes the button states +of an NES gamepad. + +The SPI decoder needs to be configured as follows: + +Clock polarity = 1 +Clock phase = 0 +Bit order = msb-first +Word size = 8 + +Chip-select is not used and must not be assigned to any channel. + +Hardware setup is as follows: + ___ + GND |o \ + CUP |o o| VCC + OUT 0 |o o| D3 + D1 |o o| D4 + ----- +NES Gamepad Connector + +VCC - Power 5V +GND - Ground +CUP - Shift register clock (CLK) +OUT 0 - Shift register latch (optional) +D1 - Gamepad data (MOSI) +D3 - Data (unused) +D4 - Data (unused) + +Data pins D3 and D4 are not used by the standard gamepad but +by special controllers like the Nintento Zapper light gun. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/nes_gamepad/pd.py b/libsigrokdecode4DSL/decoders/nes_gamepad/pd.py new file mode 100644 index 00000000..b276e5db --- /dev/null +++ b/libsigrokdecode4DSL/decoders/nes_gamepad/pd.py @@ -0,0 +1,105 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2019 Stephan Thiele +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd + +class Decoder(srd.Decoder): + api_version = 3 + id = 'nes_gamepad' + name = 'NES gamepad' + longname = 'Nintendo Entertainment System gamepad' + desc = 'NES gamepad button states.' + license = 'gplv2+' + inputs = ['spi'] + outputs = [] + tags = ['Retro computing'] + options = ( + # Currently only the standard controller is supported. This might be + # extended by special controllers like the Nintendo Zapper light gun. + {'id': 'variant', 'desc': 'Gamepad variant', + 'default': 'Standard gamepad', 'values': ('Standard gamepad',)}, + ) + annotations = ( + ('button', 'Button state'), + ('no-press', 'No button press'), + ('not-connected', 'Gamepad unconnected') + ) + annotation_rows = ( + ('buttons', 'Button states', (0,)), + ('no-presses', 'No button presses', (1,)), + ('not-connected-vals', 'Gamepad unconnected', (2,)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.variant = None + self.ss_block = None + self.es_block = None + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + self.variant = self.options['variant'] + + def putx(self, data): + self.put(self.ss_block, self.es_block, self.out_ann, data) + + def handle_data(self, value): + if value == 0xFF: + self.putx([1, ['No button is pressed']]) + return + + if value == 0x00: + self.putx([2, ['Gamepad is not connected']]) + return + + buttons = [ + 'A', + 'B', + 'Select', + 'Start', + 'North', + 'South', + 'West', + 'East' + ] + + bits = format(value, '08b') + button_str = '' + + for b in enumerate(bits): + button_index = b[0] + button_is_pressed = b[1] == '0' + + if button_is_pressed: + if button_str != '': + button_str += ' + ' + button_str += buttons[button_index] + + self.putx([0, ['%s' % button_str]]) + + def decode(self, ss, es, data): + ptype, mosi, miso = data + self.ss_block, self.es_block = ss, es + + if ptype != 'DATA': + return + + self.handle_data(miso) diff --git a/libsigrokdecode4DSL/decoders/nrf24l01/__init__.py b/libsigrokdecode4DSL/decoders/nrf24l01/__init__.py new file mode 100644 index 00000000..60124780 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/nrf24l01/__init__.py @@ -0,0 +1,29 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 Jens Steinhauser +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This decoder stacks on top of the 'spi' PD and decodes the protocol spoken +by the Nordic Semiconductor nRF24L01 and nRF24L01+ 2.4GHz transceiver chips. + +Details: +http://www.nordicsemi.com/eng/Products/2.4GHz-RF/nRF24L01 +http://www.nordicsemi.com/eng/Products/2.4GHz-RF/nRF24L01P +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/nrf24l01/pd.py b/libsigrokdecode4DSL/decoders/nrf24l01/pd.py new file mode 100644 index 00000000..89a2d3b4 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/nrf24l01/pd.py @@ -0,0 +1,370 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 Jens Steinhauser +## Copyright (C) 2019 DreamSourceLab +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd + +class ChannelError(Exception): + pass + +regs = { +# addr: ('name', size) + 0x00: ('CONFIG', 1), + 0x01: ('EN_AA', 1), + 0x02: ('EN_RXADDR', 1), + 0x03: ('SETUP_AW', 1), + 0x04: ('SETUP_RETR', 1), + 0x05: ('RF_CH', 1), + 0x06: ('RF_SETUP', 1), + 0x07: ('STATUS', 1), + 0x08: ('OBSERVE_TX', 1), + 0x09: ('RPD', 1), + 0x0a: ('RX_ADDR_P0', 5), + 0x0b: ('RX_ADDR_P1', 5), + 0x0c: ('RX_ADDR_P2', 1), + 0x0d: ('RX_ADDR_P3', 1), + 0x0e: ('RX_ADDR_P4', 1), + 0x0f: ('RX_ADDR_P5', 1), + 0x10: ('TX_ADDR', 5), + 0x11: ('RX_PW_P0', 1), + 0x12: ('RX_PW_P1', 1), + 0x13: ('RX_PW_P2', 1), + 0x14: ('RX_PW_P3', 1), + 0x15: ('RX_PW_P4', 1), + 0x16: ('RX_PW_P5', 1), + 0x17: ('FIFO_STATUS', 1), + 0x1c: ('DYNPD', 1), + 0x1d: ('FEATURE', 1), +} + +xn297_regs = { + 0x19: ('DEMOD_CAL', 1), + 0x1a: ('RF_CAL2', 6), + 0x1b: ('DEM_CAL2', 3), + 0x1e: ('RF_CAL', 3), + 0x1f: ('BB_CAL', 5), +} + +class Decoder(srd.Decoder): + api_version = 3 + id = 'nrf24l01' + name = 'nRF24L01(+)' + longname = 'Nordic Semiconductor nRF24L01(+)' + desc = '2.4GHz RF transceiver chip.' + license = 'gplv2+' + inputs = ['spi'] + outputs = [] + tags = ['IC', 'Wireless/RF'] + options = ( + {'id': 'chip', 'desc': 'Chip type', + 'default': 'nrf24l01', 'values': ('nrf24l01', 'xn297')}, + {'id': 'hex_display', 'desc': 'Display payload in Hex', 'default': 'yes', + 'values': ('yes', 'no')}, + ) + annotations = ( + # Sent from the host to the chip. + ('cmd', 'Commands sent to the device'), + ('tx-data', 'Payload sent to the device'), + + # Returned by the chip. + ('register', 'Registers read from the device'), + ('rx-data', 'Payload read from the device'), + + ('warning', 'Warnings'), + ) + ann_cmd = 0 + ann_tx = 1 + ann_reg = 2 + ann_rx = 3 + ann_warn = 4 + annotation_rows = ( + ('commands', 'Commands', (ann_cmd, ann_tx)), + ('responses', 'Responses', (ann_reg, ann_rx)), + ('warnings', 'Warnings', (ann_warn,)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.next() + self.requirements_met = True + self.cs_was_released = False + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + if self.options['chip'] == 'xn297': + regs.update(xn297_regs) + + def warn(self, pos, msg): + '''Put a warning message 'msg' at 'pos'.''' + self.put(pos[0], pos[1], self.out_ann, [self.ann_warn, [msg]]) + + def putp(self, pos, ann, msg): + '''Put an annotation message 'msg' at 'pos'.''' + self.put(pos[0], pos[1], self.out_ann, [ann, [msg]]) + + def next(self): + '''Resets the decoder after a complete command was decoded.''' + # 'True' for the first byte after CS went low. + self.first = True + + # The current command, and the minimum and maximum number + # of data bytes to follow. + self.cmd = None + self.min = 0 + self.max = 0 + + # Used to collect the bytes after the command byte + # (and the start/end sample number). + self.mb = [] + self.mb_s = -1 + self.mb_e = -1 + + def mosi_bytes(self): + '''Returns the collected MOSI bytes of a multi byte command.''' + return [b[0] for b in self.mb] + + def miso_bytes(self): + '''Returns the collected MISO bytes of a multi byte command.''' + return [b[1] for b in self.mb] + + def decode_command(self, pos, b): + '''Decodes the command byte 'b' at position 'pos' and prepares + the decoding of the following data bytes.''' + c = self.parse_command(b) + if c is None: + self.warn(pos, 'unknown command') + return + + self.cmd, self.dat, self.min, self.max = c + + if self.cmd in ('W_REGISTER', 'ACTIVATE', 'RST_FSPI'): + # Don't output anything now, the command is merged with + # the data bytes following it. + self.mb_s = pos[0] + else: + self.putp(pos, self.ann_cmd, self.format_command()) + + def format_command(self): + '''Returns the label for the current command.''' + if self.cmd == 'R_REGISTER': + reg = regs[self.dat][0] if self.dat in regs else 'unknown register' + return 'Cmd R_REGISTER "{}"'.format(reg) + else: + return 'Cmd {}'.format(self.cmd) + + def parse_command(self, b): + '''Parses the command byte. + + Returns a tuple consisting of: + - the name of the command + - additional data needed to dissect the following bytes + - minimum number of following bytes + - maximum number of following bytes + ''' + buflen = 32 + if self.options['chip'] == 'xn297': + buglen = 64 + if (b & 0xe0) in (0b00000000, 0b00100000): + c = 'R_REGISTER' if not (b & 0xe0) else 'W_REGISTER' + d = b & 0x1f + m = regs[d][1] if d in regs else 1 + return (c, d, 1, m) + if b == 0b01010000: + # nRF24L01 only + return ('ACTIVATE', None, 1, 1) + if b == 0b01100001: + return ('R_RX_PAYLOAD', None, 1, buflen) + if b == 0b01100000: + return ('R_RX_PL_WID', None, 1, 1) + if b == 0b10100000: + return ('W_TX_PAYLOAD', None, 1, buflen) + if b == 0b10110000: + return ('W_TX_PAYLOAD_NOACK', None, 1, buflen) + if (b & 0xf8) == 0b10101000: + return ('W_ACK_PAYLOAD', b & 0x07, 1, buflen) + if b == 0b11100001: + return ('FLUSH_TX', None, 0, 0) + if b == 0b11100010: + return ('FLUSH_RX', None, 0, 0) + if b == 0b11100011: + return ('REUSE_TX_PL', None, 0, 0) + if b == 0b11111111: + return ('NOP', None, 0, 0) + + if self.options['chip'] == 'xn297': + if b == 0b11111101: + return ('CE_FSPI_ON', None, 1, 1) + if b == 0b11111100: + return ('CE_FSPI_OFF', None, 1, 1) + if b == 0b01010011: + return ('RST_FSPI', None, 1, 1) + + def decode_register(self, pos, ann, regid, data): + '''Decodes a register. + + pos -- start and end sample numbers of the register + ann -- is the annotation number that is used to output the register. + regid -- may be either an integer used as a key for the 'regs' + dictionary, or a string directly containing a register name.' + data -- is the register content. + ''' + + if type(regid) == int: + # Get the name of the register. + if regid not in regs: + self.warn(pos, 'unknown register') + return + name = regs[regid][0] + else: + name = regid + + # Multi byte register come LSByte first. + data = reversed(data) + + if self.cmd == 'W_REGISTER' and ann == self.ann_cmd: + # The 'W_REGISTER' command is merged with the following byte(s). + label = '{}: {}'.format(self.format_command(), name) + else: + label = 'Reg {}'.format(name) + + self.decode_mb_data(pos, ann, data, label, True) + + def decode_mb_data(self, pos, ann, data, label, always_hex): + '''Decodes the data bytes 'data' of a multibyte command at position + 'pos'. The decoded data is prefixed with 'label'. If 'always_hex' is + True, all bytes are decoded as hex codes, otherwise only non + printable characters are escaped.''' + + if always_hex: + def escape(b): + return '{:02X}'.format(b) + else: + def escape(b): + c = chr(b) + if not str.isprintable(c): + return '\\x{:02X}'.format(b) + return c + + data = ''.join([escape(b) for b in data]) + text = '{} = "{}"'.format(label, data.strip()) + self.putp(pos, ann, text) + + def finish_command(self, pos): + '''Decodes the remaining data bytes at position 'pos'.''' + + always_hex = self.options['hex_display'] == 'yes' + if self.cmd == 'R_REGISTER': + self.decode_register(pos, self.ann_reg, + self.dat, self.miso_bytes()) + elif self.cmd == 'W_REGISTER': + self.decode_register(pos, self.ann_cmd, + self.dat, self.mosi_bytes()) + elif self.cmd == 'R_RX_PAYLOAD': + self.decode_mb_data(pos, self.ann_rx, + self.miso_bytes(), 'RX payload', always_hex) + elif (self.cmd == 'W_TX_PAYLOAD' or + self.cmd == 'W_TX_PAYLOAD_NOACK'): + self.decode_mb_data(pos, self.ann_tx, + self.mosi_bytes(), 'TX payload', always_hex) + elif self.cmd == 'W_ACK_PAYLOAD': + lbl = 'ACK payload for pipe {}'.format(self.dat) + self.decode_mb_data(pos, self.ann_tx, + self.mosi_bytes(), lbl, always_hex) + elif self.cmd == 'R_RX_PL_WID': + msg = 'Payload width = {}'.format(self.mb[0][1]) + self.putp(pos, self.ann_reg, msg) + elif self.cmd == 'ACTIVATE': + if self.mosi_bytes()[0] == 0x8c: + self.cmd = 'DEACTIVATE' + elif self.mosi_bytes()[0] != 0x73: + self.warn(pos, 'wrong data for "ACTIVATE" command') + self.putp(pos, self.ann_cmd, self.format_command()) + elif self.cmd == 'RST_FSPI': + if self.mosi_bytes()[0] == 0x5a: + self.cmd = 'RST_FSPI_HOLD' + elif self.mosi_bytes()[0] == 0xa5: + self.cmd = 'RST_FSPI_RELS' + else: + self.warn(pos, 'wrong data for "RST_FSPI" command') + self.putp(pos, self.ann_cmd, self.format_command()) + + + def decode(self, ss, es, data): + if not self.requirements_met: + return + + ptype, data1, data2 = data + + if ptype == 'TRANSFER': + if self.cmd: + # Check if we got the minimum number of data bytes + # after the command byte. + if len(self.mb) < self.min: + self.warn((ss, ss), 'missing data bytes') + elif self.mb: + self.finish_command((self.mb_s, self.mb_e)) + + self.next() + self.cs_was_released = True + elif ptype == 'CS-CHANGE': + if data1 is None: + if data2 is None: + self.requirements_met = False + raise ChannelError('CS# pin required.') + elif data2 == 1: + self.cs_was_released = True + + if data1 == 0 and data2 == 1: + # Rising edge, the complete command is transmitted, process + # the bytes that were send after the command byte. + if self.cmd: + # Check if we got the minimum number of data bytes + # after the command byte. + if len(self.mb) < self.min: + self.warn((ss, ss), 'missing data bytes') + elif self.mb: + self.finish_command((self.mb_s, self.mb_e)) + + self.next() + self.cs_was_released = True + elif ptype == 'DATA' and self.cs_was_released: + mosi, miso = data1, data2 + pos = (ss, es) + + if miso is None or mosi is None: + self.requirements_met = False + raise ChannelError('Both MISO and MOSI pins required.') + + if self.first: + self.first = False + # First MOSI byte is always the command. + self.decode_command(pos, mosi) + # First MISO byte is always the status register. + self.decode_register(pos, self.ann_reg, 'STATUS', [miso]) + else: + if not self.cmd or len(self.mb) >= self.max: + self.warn(pos, 'excess byte') + else: + # Collect the bytes after the command byte. + if self.mb_s == -1: + self.mb_s = ss + self.mb_e = es + self.mb.append((mosi, miso)) diff --git a/libsigrokdecode4DSL/decoders/nrf905/__init__.py b/libsigrokdecode4DSL/decoders/nrf905/__init__.py new file mode 100644 index 00000000..c6d35abb --- /dev/null +++ b/libsigrokdecode4DSL/decoders/nrf905/__init__.py @@ -0,0 +1,26 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2020 Jorge Solla Rubiales +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + +''' +This decoder stacks on top of the 'spi' PD and decodes the Nordic Semiconductor +NRF905 (433/868/915MHz transceiver) command/responses. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/nrf905/pd.py b/libsigrokdecode4DSL/decoders/nrf905/pd.py new file mode 100644 index 00000000..12949fea --- /dev/null +++ b/libsigrokdecode4DSL/decoders/nrf905/pd.py @@ -0,0 +1,301 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2020 Jorge Solla Rubiales +## +## Permission is hereby granted, free of charge, to any person obtaining a copy +## of this software and associated documentation files (the "Software"), to deal +## in the Software without restriction, including without limitation the rights +## to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +## copies of the Software, and to permit persons to whom the Software is +## furnished to do so, subject to the following conditions: +## +## The above copyright notice and this permission notice shall be included in all +## copies or substantial portions of the Software. +## +## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +## IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +## FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +## AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +## LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +## OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +## SOFTWARE. + +import sigrokdecode as srd +from common.srdhelper import SrdIntEnum + +CFG_REGS = { + 0: [{'name': 'CH_NO', 'stbit': 7, 'nbits': 8}], + 1: [ + {'name': 'AUTO_RETRAN', 'stbit': 5, 'nbits': 1, + 'opts': {0: 'No retransmission', 1: 'Retransmission of data packet'}}, + {'name': 'RX_RED_PWR', 'stbit': 4, 'nbits': 1, + 'opts': {0: 'Normal operation', 1: 'Reduced power'}}, + {'name': 'PA_PWR', 'stbit': 3, 'nbits': 2, + 'opts': {0: '-10 dBm', 1: '-2 dBm', 2: '+6 dBm', 3: '+10 dBm'}}, + {'name': 'HFREQ_PLL', 'stbit': 1, 'nbits': 1, + 'opts': {0: '433 MHz', 1: '868 / 915 MHz'}}, + {'name': 'CH_NO_8', 'stbit': 0, 'nbits': 1}, + ], + 2: [ + {'name': 'TX_AFW (TX addr width)', 'stbit': 6, 'nbits': 3}, + {'name': 'RX_AFW (RX addr width)', 'stbit': 2, 'nbits': 3}, + ], + 3: [{'name': 'RW_PW (RX payload width)', 'stbit': 5, 'nbits': 6}], + 4: [{'name': 'TX_PW (TX payload width)', 'stbit': 5, 'nbits': 6}], + 5: [{'name': 'RX_ADDR_0', 'stbit': 7, 'nbits': 8}], + 6: [{'name': 'RX_ADDR_1', 'stbit': 7, 'nbits': 8}], + 7: [{'name': 'RX_ADDR_2', 'stbit': 7, 'nbits': 8}], + 8: [{'name': 'RX_ADDR_3', 'stbit': 7, 'nbits': 8}], + 9: [ + {'name': 'CRC_MODE', 'stbit': 7, 'nbits': 1, + 'opts': {0: '8 CRC check bit', 1: '16 CRC check bit'}}, + {'name': 'CRC_EN', 'stbit': 6, 'nbits': 1, + 'opts': {0: 'Disabled', 1: 'Enabled'}}, + {'name': 'XOR', 'stbit': 5, 'nbits': 3, + 'opts': {0: '4 MHz', 1: '8 MHz', 2: '12 MHz', + 3: '16 MHz', 4: '20 MHz'}}, + {'name': 'UP_CLK_EN', 'stbit': 2, 'nbits': 1, + 'opts': {0: 'No external clock signal avail.', + 1: 'External clock signal enabled'}}, + {'name': 'UP_CLK_FREQ', 'stbit': 1, 'nbits': 2, + 'opts': {0: '4 MHz', 1: '2 MHz', 2: '1 MHz', 3: '500 kHz'}}, + ], +} + +CHN_CFG = [ + {'name': 'PA_PWR', 'stbit': 3, 'nbits': 2, + 'opts': {0: '-10 dBm', 1: '-2 dBm', 2: '+6 dBm', 3: '+10 dBm'}}, + {'name': 'HFREQ_PLL', 'stbit': 1, 'nbits': 1, + 'opts': {0: '433 MHz', 1: '868 / 915 MHz'}}, +] + +STAT_REG = [ + {'name': 'AM', 'stbit': 7, 'nbits': 1}, + {'name': 'DR', 'stbit': 5, 'nbits': 1}, +] + +Ann = SrdIntEnum.from_str('Ann', 'CMD REG_WR REG_RD TX RX RESP WARN') + +class Decoder(srd.Decoder): + api_version = 3 + id = 'nrf905' + name = 'nRF905' + longname = 'Nordic Semiconductor nRF905' + desc = '433/868/933MHz transceiver chip.' + license = 'mit' + inputs = ['spi'] + outputs = [] + tags = ['IC', 'Wireless/RF'] + annotations = ( + ('cmd', 'Command sent to the device'), + ('reg-write', 'Config register written to the device'), + ('reg-read', 'Config register read from the device'), + ('tx-data', 'Payload sent to the device'), + ('rx-data', 'Payload read from the device'), + ('resp', 'Response to commands received from the device'), + ('warning', 'Warning'), + ) + annotation_rows = ( + ('commands', 'Commands', (Ann.CMD,)), + ('responses', 'Responses', (Ann.RESP,)), + ('registers', 'Registers', (Ann.REG_WR, Ann.REG_RD)), + ('tx', 'Transmitted data', (Ann.TX,)), + ('rx', 'Received data', (Ann.RX,)), + ('warnings', 'Warnings', (Ann.WARN,)), + ) + + def __init__(self): + self.ss_cmd, self.es_cmd = 0, 0 + self.cs_asserted = False + self.reset() + + def reset(self): + self.mosi_bytes, self.miso_bytes = [], [] + self.cmd_samples = {'ss': 0, 'es': 0} + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def extract_bits(self, byte, start_bit, num_bits): + begin = 7 - start_bit + end = begin + num_bits + if begin < 0 or end > 8: + return 0 + binary = format(byte, '08b')[begin:end] + return int(binary, 2) + + def extract_vars(self, reg_vars, reg_value): + # Iterate all vars on current register. + data = '' + for var in reg_vars: + var_value = self.extract_bits(reg_value, var['stbit'], var['nbits']) + data += var['name'] + ' = ' + str(var_value) + opt = '' + + # If var has options, just add the option meaning. + if 'opts' in var: + opt = var['opts'].get(var_value, 'unknown') + data += ' (' + opt + ')' + + # Add var separator. + if reg_vars.index(var) != len(reg_vars) - 1: + data += ' | ' + return data + + def parse_config_register(self, addr, value, is_write): + reg_value = value[0] + data = 'CFG_REG[' + hex(addr) + '] -> ' + + # Get register vars for this register. + if addr in CFG_REGS: + reg_vars = CFG_REGS[addr] + else: + # Invalid register address. + self.put(value[1], value[2], + self.out_ann, [Ann.WARN, ['Invalid reg. addr']]) + return + + data += self.extract_vars(reg_vars, reg_value) + + ann = Ann.REG_WR if is_write else Ann.REG_RD + self.put(value[1], value[2], self.out_ann, [ann, [data]]) + + def parse_config_registers(self, addr, registers, is_write): + i = 0 + while i < len(registers): + reg_addr = i + addr + if reg_addr <= 9: + self.parse_config_register(reg_addr, registers[i], is_write) + i += 1 + + def dump_cmd_bytes(self, prefix, cmd_bytes, ann): + ss, es = cmd_bytes[1][1], 0 + data = '' + for byte in cmd_bytes[1:]: + data += format(byte[0], '02X') + ' ' + es = byte[2] + self.put(ss, es, self.out_ann, [ann, [prefix + data]]) + + def handle_WC(self): + start_addr = self.mosi_bytes[0][0] & 0x0F + if start_addr > 9: + return + self.parse_config_registers(start_addr, self.mosi_bytes[1:], True) + + def handle_RC(self): + start_addr = self.mosi_bytes[0][0] & 0x0F + if start_addr > 9: + return + self.parse_config_registers(start_addr, self.miso_bytes[1:], False) + + def handle_WTP(self): + self.dump_cmd_bytes('Write TX payload.: ', self.mosi_bytes, Ann.TX) + + def handle_RTP(self): + self.dump_cmd_bytes('Read TX payload: ', self.miso_bytes, Ann.RESP) + + def handle_WTA(self): + self.dump_cmd_bytes('Write TX addr: ', self.mosi_bytes, Ann.REG_WR) + + def handle_RTA(self): + self.dump_cmd_bytes('Read TX addr: ', self.miso_bytes, Ann.RESP) + + def handle_RRP(self): + self.dump_cmd_bytes('Read RX payload: ', self.miso_bytes, Ann.RX) + + def handle_CC(self): + cmd, dta = self.mosi_bytes[0], self.mosi_bytes[1] + channel = ((cmd[0] & 0x01) << 8) + dta + data = self.extract_vars(CHN_CFG, cmd[0]) + data += '| CHN = ' + str(channel) + self.put(self.mosi_bytes[0][1], self.mosi_bytes[1][2], + self.out_ann, [Ann.REG_WR, [data]]) + + def handle_STAT(self): + status = 'STAT = ' + self.extract_vars(STAT_REG, self.miso_bytes[0][0]) + self.put(self.miso_bytes[0][1], self.miso_bytes[0][2], + self.out_ann, [Ann.REG_RD, [status]]) + + def process_cmd(self): + cmd, cmd_name, cmd_hnd = '', '', None + + for byte in self.mosi_bytes: + cmd += hex(byte[0]) + ' ' + + cmd = self.mosi_bytes[0][0] + + if (cmd & 0xF0) == 0x00: + cmd_name, cmd_hnd = 'CMD: W_CONFIG (WC)', self.handle_WC + elif (cmd & 0xF0) == 0x10: + cmd_name, cmd_hnd = 'CMD: R_CONFIG (RC)', self.handle_RC + elif cmd == 0x20: + cmd_name, cmd_hnd = 'CMD: W_TX_PAYLOAD (WTP)', self.handle_WTP + elif cmd == 0x21: + cmd_name, cmd_hnd = 'CMD: R_TX_PAYLOAD (RTP)', self.handle_RTP + elif cmd == 0x22: + cmd_name, cmd_hnd = 'CMD: W_TX_ADDRESS (WTA)', self.handle_WTA + elif cmd == 0x23: + cmd_name, cmd_hnd = 'CMD: R_TX_ADDRESS (RTA)', self.handle_RTA + elif cmd == 0x24: + cmd_name, cmd_hnd = 'CMD: R_RX_PAYLOAD (RRP)', self.handle_RRP + elif (cmd & 0xF0 == 0x80): + cmd_name, cmd_hnd = 'CMD: CHANNEL_CONFIG (CC)', self.handle_CC + + # Report command name. + self.put(self.cmd_samples['ss'], self.cmd_samples['es'], + self.out_ann, [Ann.CMD, [cmd_name]]) + + # Handle status byte. + self.handle_STAT() + + # Handle command. + if cmd_hnd is not None: + cmd_hnd() + + def set_cs_status(self, sample, asserted): + if self.cs_asserted == asserted: + return + + if asserted: + self.cmd_samples['ss'] = sample + self.cmd_samples['es'] = -1 + else: + self.cmd_samples['es'] = sample + + self.cs_asserted = asserted + + def decode(self, ss, es, data): + ptype, data1, data2 = data + + if ptype == 'CS-CHANGE': + if data1 is None and data2 is None: + self.requirements_met = False + raise ChannelError('CS# pin required.') + + if data1 is None and data2 == 0: + self.set_cs_status(ss, True) + + elif data1 is None and data2 == 1: + self.set_cs_status(ss, False) + + elif data1 == 1 and data2 == 0: + self.set_cs_status(ss, True) + + elif data1 == 0 and data2 == 1: + self.set_cs_status(ss, False) + if len(self.mosi_bytes): + self.process_cmd() + self.reset() + + elif ptype == 'DATA': + # Ignore traffic if CS is not asserted. + if self.cs_asserted is False: + return + + mosi, miso = data1, data2 + if miso is None or mosi is None: + raise ChannelError('Both MISO and MOSI pins required.') + + self.mosi_bytes.append((mosi, ss, es)) + self.miso_bytes.append((miso, ss, es)) diff --git a/libsigrokdecode4DSL/decoders/numbers_and_state/__init__.py b/libsigrokdecode4DSL/decoders/numbers_and_state/__init__.py new file mode 100644 index 00000000..4fe42a34 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/numbers_and_state/__init__.py @@ -0,0 +1,41 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2019 Comlab AG +## Copyright (C) 2020 Gerhard Sittig +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This protocol decoder takes a set of logic input signals, and interprets +their bit pattern according to user specifications as different kinds of +numbers, or an enumeration of e.g. machine states. + +Supported formats are: signed and unsigned integers, fixed point numbers, +IEEE754 floating point numbers, and number to text mapping controlled by +external data files. (Support for half precision floats depends on the +Python runtime, and may not universally be available.) + +User provided text mapping files can either use the JSON format: + {"one": 1, "two": 2, "four": 4} +or the Python programming language: + enumtext = { 1: "one", 2: "two", 3: "three", } + +In addition to all enum values on one row (sequential presentation of +the data), a limited number of enum values also are shown in tabular +presentation, which can help visualize state machines or task switches. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/numbers_and_state/pd.py b/libsigrokdecode4DSL/decoders/numbers_and_state/pd.py new file mode 100644 index 00000000..b8ac87e7 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/numbers_and_state/pd.py @@ -0,0 +1,377 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2019 Comlab AG +## Copyright (C) 2020 Gerhard Sittig +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +# This implementation started as a "vector slicer", then turned into the +# "numbers and states" decoder, because users always had the freedom to +# connect any logic signal to either of the decoder inputs. That's when +# slicing vectors took second seat, and just was not needed any longer +# in the strict sense. +# +# TODO +# - Find an appropriate number of input channels, and maximum enum slots. +# - Re-check correctness of signed integers. Signed fixed point is based +# on integers and transparently benefits from fixes and improvements. +# - Local formatting in individual decoders becomes obsolete when common +# support for user selected formatting gets introduced. +# - There is overlap with the 'parallel' decoder. Ideally the numbers +# decoder could stack on top of parallel, but parallel currently is +# severely limited in its number of input channels, and dramatically +# widening the parallel decoder may be undesirable. + +from common.srdhelper import bitpack +import json +import sigrokdecode as srd +import struct + +''' +OUTPUT_PYTHON format: + +Packet: +[, ] + +This is a list of s and their respective values: + - 'RAW': The data is a tuple of bit count and bit pattern (a number, + assuming unsigned integer presentation of the input data bit pattern). + - 'NUMBER': The data is the conversion result of the bit pattern. + - 'ENUM': The data is a tuple of the raw number and its mapped text. +''' + +# TODO Better raise the number of channels to 32. This allows access to +# IEEE754 single precision numbers, and shall cover most busses, _and_ +# remains within most logic analyzers' capabilities, and keeps the UI +# dialog somewhat managable. What's a good default for the number of +# enum slots (which translate to annotation rows)? Notice that 2 to the +# power of the channel count is way out of the question. :) +_max_channels = 16 +_max_enum_slots = 32 + +class ChannelError(Exception): + pass + +class Pin: + CLK, BIT_0 = range(2) + BIT_N = BIT_0 + _max_channels + +class Ann: + RAW, NUM = range(2) + ENUM_0 = NUM + 1 + ENUM_OVR = ENUM_0 + _max_enum_slots + ENUMS = range(ENUM_0, ENUM_OVR) + WARN = ENUM_OVR + 1 + + @staticmethod + def enum_indices(): + return [i for i in range(Ann.ENUMS)] + + @staticmethod + def get_enum_idx(code): + if code in range(_max_enum_slots): + return Ann.ENUM_0 + code + return Ann.ENUM_OVR + +def _channel_decl(count): + return tuple([ + {'id': 'bit{}'.format(i), 'name': 'Bit{}'.format(i), 'desc': 'Bit position {}'.format(i)} + for i in range(count) + ]) + +def _enum_cls_decl(count): + return tuple([ + ('enum{}'.format(i), 'Enumeration slot {}'.format(i)) + for i in range(count) + ] + [('enumovr', 'Enumeration overflow')]) + +def _enum_rows_decl(count): + return tuple([ + ('enums{}'.format(i), 'Enumeration slots {}'.format(i), (Ann.ENUM_0 + i,)) + for i in range(count) + ] + [('enumsovr', 'Enumeration overflows', (Ann.ENUM_OVR,))]) + +class Decoder(srd.Decoder): + api_version = 3 + id = 'numbers_and_state' + name = 'Numbers and State' + longname = 'Interpret bit patters as numbers or state enums' + desc = 'Interpret bit patterns as different kinds of numbers (integer, float, enum).' + license = 'gplv2+' + inputs = ['logic'] + outputs = ['numbers_and_state'] + tags = ['Encoding', 'Util'] + optional_channels = ( + {'id': 'clk', 'name': 'Clock', 'desc': 'Clock'}, + ) + _channel_decl(_max_channels) + options = ( + {'id': 'clkedge', 'desc': 'Clock edge', 'default': 'rising', + 'values': ('rising', 'falling', 'either')}, + {'id': 'count', 'desc': 'Total bits count', 'default': 0}, + {'id': 'interp', 'desc': 'Interpretation', 'default': 'unsigned', + 'values': ('unsigned', 'signed', 'fixpoint', 'fixsigned', 'ieee754', 'enum')}, + {'id': 'fracbits', 'desc': 'Fraction bits count', 'default': 0}, + {'id': 'mapping', 'desc': 'Enum to text map file', + 'default': 'enumtext.json'}, + {'id': 'format', 'desc': 'Number format', 'default': '-', + 'values': ('-', 'bin', 'oct', 'dec', 'hex')}, + ) + annotations = ( + ('raw', 'Raw pattern'), + ('number', 'Number'), + ) + _enum_cls_decl(_max_enum_slots) + ( + ('warning', 'Warning'), + ) + annotation_rows = ( + ('raws', 'Raw bits', (Ann.RAW,)), + ('numbers', 'Numbers', (Ann.NUM,)), + ) + _enum_rows_decl(_max_enum_slots) + ( + ('warnings', 'Warnings', (Ann.WARN,)), + ) + + def __init__(self): + self.reset() + + def reset(self): + pass + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + self.out_python = self.register(srd.OUTPUT_PYTHON) + + def putg(self, ss, es, cls, data): + self.put(ss, es, self.out_ann, [cls, data]) + + def putpy(self, ss, es, ptype, pdata): + self.put(ss, es, self.out_python, (ptype, pdata)) + + def grab_pattern(self, pins): + '''Get a bit pattern from potentially incomplete probes' values.''' + + # Pad and trim the input data, to achieve the user specified + # total number of bits. Map all unassigned signals to 0 (low). + # Return raw number (unsigned integer interpreation). + bits = pins + (None,) * self.bitcount + bits = bits[:self.bitcount] + bits = [b if b in (0, 1) else 0 for b in bits] + pattern = bitpack(bits) + return pattern + + def handle_pattern(self, ss, es, pattern): + fmt = '{{:0{}b}}'.format(self.bitcount) + txt = fmt.format(pattern) + self.putg(ss, es, Ann.RAW, [txt]) + self.putpy(ss, es, 'RAW', (self.bitcount, pattern)) + + try: + value = self.interpreter(ss, es, pattern) + except: + value = None + if value is None: + return + self.putpy(ss, es, 'NUMBER', value) + try: + formatted = self.formatter(ss, es, value) + except: + formatted = None + if formatted: + self.putg(ss, es, Ann.NUM, formatted) + if self.interpreter == self.interp_enum: + cls = Ann.get_enum_idx(pattern) + self.putg(ss, es, cls, formatted) + self.putpy(ss, es, 'ENUM', (value, formatted)) + + def interp_unsigned(self, ss, es, pattern): + value = pattern + return value + + def interp_signed(self, ss, es, pattern): + if not 'signmask' in self.interp_state: + self.interp_state.update({ + 'signmask': 1 << (self.bitcount - 1), + 'signfull': 1 << self.bitcount, + }) + is_neg = pattern & self.interp_state['signmask'] + if is_neg: + value = -(self.interp_state['signfull'] - pattern) + else: + value = pattern + return value + + def interp_fixpoint(self, ss, es, pattern): + if not 'fixdiv' in self.interp_state: + self.interp_state.update({ + 'fixsign': self.options['interp'] == 'fixsigned', + 'fixdiv': 2 ** self.options['fracbits'], + }) + if self.interp_state['fixsign']: + value = self.interp_signed(ss, es, pattern) + else: + value = self.interp_unsigned(ss, es, pattern) + value /= self.interp_state['fixdiv'] + return value + + def interp_ieee754(self, ss, es, pattern): + if not 'ieee_has_16bit' in self.interp_state: + self.interp_state.update({ + 'ieee_fmt_int_16': '=H', + 'ieee_fmt_flt_16': '=e', + 'ieee_fmt_int_32': '=L', + 'ieee_fmt_flt_32': '=f', + 'ieee_fmt_int_64': '=Q', + 'ieee_fmt_flt_64': '=d', + }) + try: + fmt = self.interp_state.update['ieee_fmt_flt_16'] + has_16bit_support = 8 * struct.calcsize(fmt) == 16 + except: + has_16bit_support = False + self.interp_state['ieee_has_16bit'] = has_16bit_support + if self.bitcount == 16: + if not self.interp_state['ieee_has_16bit']: + return None + buff = struct.pack(self.interp_state['ieee_fmt_int_16'], pattern) + value, = struct.unpack(self.interp_state['ieee_fmt_flt_16'], buff) + return value + if self.bitcount == 32: + buff = struct.pack(self.interp_state['ieee_fmt_int_32'], pattern) + value, = struct.unpack(self.interp_state['ieee_fmt_flt_32'], buff) + return value + if self.bitcount == 64: + buff = struct.pack(self.interp_state['ieee_fmt_int_64'], pattern) + value, = struct.unpack(self.interp_state['ieee_fmt_flt_64'], buff) + return value + return None + + def interp_enum(self, ss, es, pattern): + if not 'enum_map' in self.interp_state: + self.interp_state.update({ + 'enum_fn': self.options['mapping'], + 'enum_map': {}, + 'enum_have_map': False, + }) + try: + fn = self.interp_state['enum_fn'] + # TODO Optionally try in several locations? Next to the + # decoder implementation? Where else? Expect users to + # enter absolute paths? + with open(fn, 'r') as f: + maptext = f.read() + maptable = {} + if fn.endswith('.js') or fn.endswith('.json'): + # JSON requires string literals on the LHS, so the + # table is written "in reverse order". + js_table = json.loads(maptext) + for k, v in js_table.items(): + maptable[v] = k + elif fn.endswith('.py'): + # Expect a specific identifier at the Python module + # level, and assume that it's a dictionary. + py_table = {} + exec(maptext, py_table) + maptable.update(py_table['enumtext']) + self.interp_state['enum_map'].update(maptable) + self.interp_state['enum_have_map'] = True + except: + # Silently ignore failure. This happens while the user + # is typing the filename, and is non-fatal. If the file + # exists and is not readable or not valid or of unknown + # format, the worst thing that can happen is that the + # decoder implementation keeps using "anonymous" phrases + # until a mapping has become available. No harm is done. + # This decoder cannot tell intermediate from final file + # read attempts, so we cannot raise severity here. + pass + value = self.interp_state['enum_map'].get(pattern, None) + if value is None: + value = pattern + return value + + def format_native(self, ss, es, value): + return ['{}'.format(value),] + + def format_bin(self, ss, es, value): + if not self.format_string: + self.format_string = '{{:0{}b}}'.format(self.bitcount) + return [self.format_string.format(value)] + + def format_oct(self, ss, es, value): + if not self.format_string: + self.format_string = '{{:0{}o}}'.format((self.bitcount + 3 - 1) // 3) + return [self.format_string.format(value)] + + def format_dec(self, ss, es, value): + if not self.format_string: + self.format_string = '{:d}' + return [self.format_string.format(value)] + + def format_hex(self, ss, es, value): + if not self.format_string: + self.format_string = '{{:0{}x}}'.format((self.bitcount + 4 - 1) // 4) + return [self.format_string.format(value)] + + def decode(self): + channels = [ch for ch in range(_max_channels) if self.has_channel(ch)] + have_clk = Pin.CLK in channels + if have_clk: + channels.remove(Pin.CLK) + if not channels: + raise ChannelError("Need at least one bit channel.") + if have_clk: + clkedge = { + 'rising': 'r', + 'falling': 'f', + 'either': 'e', + }.get(self.options['clkedge']) + wait_cond = {Pin.CLK: clkedge} + else: + wait_cond = [{ch: 'e'} for ch in channels] + + bitcount = self.options['count'] + if not bitcount: + bitcount = channels[-1] - Pin.BIT_0 + 1 + self.bitcount = bitcount + + self.interpreter = { + 'unsigned': self.interp_unsigned, + 'signed': self.interp_signed, + 'fixpoint': self.interp_fixpoint, + 'fixsigned': self.interp_fixpoint, + 'ieee754': self.interp_ieee754, + 'enum': self.interp_enum, + }.get(self.options['interp']) + self.interp_state = {} + self.formatter = { + '-': self.format_native, + 'bin': self.format_bin, + 'oct': self.format_oct, + 'dec': self.format_dec, + 'hex': self.format_hex, + }.get(self.options['format']) + self.format_string = None + + pins = self.wait() + ss = self.samplenum + prev_pattern = self.grab_pattern(pins[Pin.BIT_0:]) + while True: + pins = self.wait(wait_cond) + es = self.samplenum + pattern = self.grab_pattern(pins[Pin.BIT_0:]) + if pattern == prev_pattern: + continue + self.handle_pattern(ss, es, prev_pattern) + ss = es + prev_pattern = pattern diff --git a/libsigrokdecode4DSL/decoders/nunchuk/__init__.py b/libsigrokdecode4DSL/decoders/nunchuk/__init__.py new file mode 100644 index 00000000..a6092c45 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/nunchuk/__init__.py @@ -0,0 +1,30 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2012 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This decoder stacks on top of the 'i2c' PD and decodes the Nintendo Wii +Nunchuk controller protocol. + +Details: +http://wiibrew.org/wiki/Wiimote/Extension_Controllers/Nunchuck +http://todbot.com/blog/2008/02/18/wiichuck-wii-nunchuck-adapter-available/ +https://www.sparkfun.com/products/9281 +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/nunchuk/pd.py b/libsigrokdecode4DSL/decoders/nunchuk/pd.py new file mode 100644 index 00000000..59b10289 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/nunchuk/pd.py @@ -0,0 +1,207 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2010-2014 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd + +class Decoder(srd.Decoder): + api_version = 3 + id = 'nunchuk' + name = 'Nunchuk' + longname = 'Nintendo Wii Nunchuk' + desc = 'Nintendo Wii Nunchuk controller protocol.' + license = 'gplv2+' + inputs = ['i2c'] + outputs = [] + tags = ['Sensor'] + annotations = \ + tuple(('reg-0x%02X' % i, 'Register 0x%02X' % i) for i in range(6)) + ( + ('bit-bz', 'BZ bit'), + ('bit-bc', 'BC bit'), + ('bit-ax', 'AX bits'), + ('bit-ay', 'AY bits'), + ('bit-az', 'AZ bits'), + ('nunchuk-write', 'Nunchuk write'), + ('cmd-init', 'Init command'), + ('summary', 'Summary'), + ('warnings', 'Warnings'), + ) + annotation_rows = ( + ('regs', 'Registers', tuple(range(13))), + ('summary', 'Summary', (13,)), + ('warnings', 'Warnings', (14,)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.state = 'IDLE' + self.sx = self.sy = self.ax = self.ay = self.az = self.bz = self.bc = -1 + self.databytecount = 0 + self.reg = 0x00 + self.ss = self.es = self.ss_block = self.es_block = 0 + self.init_seq = [] + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def putx(self, data): + self.put(self.ss, self.es, self.out_ann, data) + + def putb(self, data): + self.put(self.ss_block, self.es_block, self.out_ann, data) + + def putd(self, bit1, bit2, data): + self.put(self.bits[bit1][1], self.bits[bit2][2], self.out_ann, data) + + def handle_reg_0x00(self, databyte): + self.ss_block = self.ss + self.sx = databyte + self.putx([0, ['Analog stick X position: 0x%02X' % self.sx, + 'SX: 0x%02X' % self.sx]]) + + def handle_reg_0x01(self, databyte): + self.sy = databyte + self.putx([1, ['Analog stick Y position: 0x%02X' % self.sy, + 'SY: 0x%02X' % self.sy]]) + + def handle_reg_0x02(self, databyte): + self.ax = databyte << 2 + self.putx([2, ['Accelerometer X value bits[9:2]: 0x%03X' % self.ax, + 'AX[9:2]: 0x%03X' % self.ax]]) + + def handle_reg_0x03(self, databyte): + self.ay = databyte << 2 + self.putx([3, ['Accelerometer Y value bits[9:2]: 0x%03X' % self.ay, + 'AY[9:2]: 0x%03X' % self.ay]]) + + def handle_reg_0x04(self, databyte): + self.az = databyte << 2 + self.putx([4, ['Accelerometer Z value bits[9:2]: 0x%03X' % self.az, + 'AZ[9:2]: 0x%03X' % self.az]]) + + def handle_reg_0x05(self, databyte): + self.es_block = self.es + self.bz = (databyte & (1 << 0)) >> 0 # Bits[0:0] + self.bc = (databyte & (1 << 1)) >> 1 # Bits[1:1] + ax_rest = (databyte & (3 << 2)) >> 2 # Bits[3:2] + ay_rest = (databyte & (3 << 4)) >> 4 # Bits[5:4] + az_rest = (databyte & (3 << 6)) >> 6 # Bits[7:6] + self.ax |= ax_rest + self.ay |= ay_rest + self.az |= az_rest + + # self.putx([5, ['Register 5', 'Reg 5', 'R5']]) + + s = '' if (self.bz == 0) else 'not ' + self.putd(0, 0, [6, ['Z: %spressed' % s, 'BZ: %d' % self.bz]]) + + s = '' if (self.bc == 0) else 'not ' + self.putd(1, 1, [7, ['C: %spressed' % s, 'BC: %d' % self.bc]]) + + self.putd(3, 2, [8, ['Accelerometer X value bits[1:0]: 0x%X' % ax_rest, + 'AX[1:0]: 0x%X' % ax_rest]]) + + self.putd(5, 4, [9, ['Accelerometer Y value bits[1:0]: 0x%X' % ay_rest, + 'AY[1:0]: 0x%X' % ay_rest]]) + + self.putd(7, 6, [10, ['Accelerometer Z value bits[1:0]: 0x%X' % az_rest, + 'AZ[1:0]: 0x%X' % az_rest]]) + + self.reg = 0x00 + + def output_full_block_if_possible(self): + # For now, only output summary annotations if all values are available. + t = (self.sx, self.sy, self.ax, self.ay, self.az, self.bz, self.bc) + if -1 in t: + return + bz = 'pressed' if self.bz == 0 else 'not pressed' + bc = 'pressed' if self.bc == 0 else 'not pressed' + s = 'Analog stick: %d/%d, accelerometer: %d/%d/%d, Z: %s, C: %s' % \ + (self.sx, self.sy, self.ax, self.ay, self.az, bz, bc) + self.putb([13, [s]]) + + def handle_reg_write(self, databyte): + self.putx([11, ['Nunchuk write: 0x%02X' % databyte]]) + if len(self.init_seq) < 2: + self.init_seq.append(databyte) + + def output_init_seq(self): + if len(self.init_seq) != 2: + self.putb([14, ['Init sequence was %d bytes long (2 expected)' % \ + len(self.init_seq)]]) + return + + if self.init_seq != [0x40, 0x00]: + self.putb([14, ['Unknown init sequence (expected: 0x40 0x00)']]) + return + + # TODO: Detect Nunchuk clones (they have different init sequences). + + self.putb([12, ['Initialize Nunchuk', 'Init Nunchuk', 'Init', 'I']]) + + def decode(self, ss, es, data): + cmd, databyte = data + + # Collect the 'BITS' packet, then return. The next packet is + # guaranteed to belong to these bits we just stored. + if cmd == 'BITS': + self.bits = databyte + return + + self.ss, self.es = ss, es + + # State machine. + if self.state == 'IDLE': + # Wait for an I²C START condition. + if cmd != 'START': + return + self.state = 'GET SLAVE ADDR' + self.ss_block = ss + elif self.state == 'GET SLAVE ADDR': + # Wait for an address read/write operation. + if cmd == 'ADDRESS READ': + self.state = 'READ REGS' + elif cmd == 'ADDRESS WRITE': + self.state = 'WRITE REGS' + elif self.state == 'READ REGS': + if cmd == 'DATA READ': + handle_reg = getattr(self, 'handle_reg_0x%02x' % self.reg) + handle_reg(databyte) + self.reg += 1 + elif cmd == 'STOP': + self.es_block = es + self.output_full_block_if_possible() + self.sx = self.sy = self.ax = self.ay = self.az = -1 + self.bz = self.bc = -1 + self.state = 'IDLE' + else: + # self.putx([14, ['Ignoring: %s (data=%s)' % (cmd, databyte)]]) + pass + elif self.state == 'WRITE REGS': + if cmd == 'DATA WRITE': + self.handle_reg_write(databyte) + elif cmd == 'STOP': + self.es_block = es + self.output_init_seq() + self.init_seq = [] + self.state = 'IDLE' + else: + # self.putx([14, ['Ignoring: %s (data=%s)' % (cmd, databyte)]]) + pass diff --git a/libsigrokdecode4DSL/decoders/onewire_link/__init__.py b/libsigrokdecode4DSL/decoders/onewire_link/__init__.py new file mode 100644 index 00000000..abd55671 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/onewire_link/__init__.py @@ -0,0 +1,56 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2012 Uwe Hermann +## Copyright (C) 2017 Kevin Redon +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This protocol decoder handles the 1-Wire link layer. + +The 1-Wire protocol enables bidirectional communication over a single wire +(and ground) between a single master and one or multiple slaves. The protocol +is layered: + + - Link layer (reset, presence detection, reading/writing bits) + - Network layer (skip/search/match device ROM addresses) + - Transport layer (transport data between 1-Wire master and device) + +Sample rate: +A sufficiently high samplerate is required to properly detect all the elements +of the protocol. A lower samplerate can be used if the master does not use +overdrive communication speed. The following minimal values should be used: + + - overdrive available: 2MHz minimum, 5MHz suggested + - overdrive not available: 400kHz minimum, 1MHz suggested + +Channels: +1-Wire requires a single signal, but some master implementations might have a +separate signal used to deliver power to the bus during temperature conversion +as an example. + + - owr (1-Wire signal line) + +Options: +1-Wire is an asynchronous protocol with fixed timing values, so the decoder +must know the samplerate. +Two speeds are available: normal and overdrive. The decoder detects when +switching speed, but the user can set which to start decoding with: + + - overdrive (to decode starting with overdrive speed) +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/onewire_link/pd.py b/libsigrokdecode4DSL/decoders/onewire_link/pd.py new file mode 100644 index 00000000..6592279e --- /dev/null +++ b/libsigrokdecode4DSL/decoders/onewire_link/pd.py @@ -0,0 +1,347 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2017 Kevin Redon +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd + +class SamplerateError(Exception): + pass + +# Timing values in us for the signal at regular and overdrive speed. +timing = { + 'RSTL': { + 'min': { + False: 480.0, + True: 48.0, + }, + 'max': { + False: 960.0, + True: 80.0, + }, + }, + 'RSTH': { + 'min': { + False: 480.0, + True: 48.0, + }, + }, + 'PDH': { + 'min': { + False: 15.0, + True: 2.0, + }, + 'max': { + False: 60.0, + True: 6.0, + }, + }, + 'PDL': { + 'min': { + False: 60.0, + True: 8.0, + }, + 'max': { + False: 240.0, + True: 24.0, + }, + }, + 'SLOT': { + 'min': { + False: 60.0, + True: 6.0, + }, + 'max': { + False: 120.0, + True: 16.0, + }, + }, + 'REC': { + 'min': { + False: 1.0, + True: 1.0, + }, + }, + 'LOWR': { + 'min': { + False: 1.0, + True: 1.0, + }, + 'max': { + False: 15.0, + True: 2.0, + }, + }, +} + +class Decoder(srd.Decoder): + api_version = 3 + id = 'onewire_link' + name = 'OneWire link layer' + longname = '1-Wire serial communication bus (link layer)' + desc = 'Bidirectional, half-duplex, asynchronous serial bus.' + license = 'gplv2+' + inputs = ['logic'] + outputs = ['onewire_link'] + tags = ['Embedded/industrial'] + channels = ( + {'id': 'owr', 'name': 'OWR', 'desc': '1-Wire signal line'}, + ) + options = ( + {'id': 'overdrive', 'desc': 'Start in overdrive speed', + 'default': 'no', 'values': ('yes', 'no')}, + ) + annotations = ( + ('bit', 'Bit'), + ('warnings', 'Warnings'), + ('reset', 'Reset'), + ('presence', 'Presence'), + ('overdrive', 'Overdrive speed notifications'), + ) + annotation_rows = ( + ('bits', 'Bits', (0, 2, 3)), + ('info', 'Info', (4,)), + ('warnings', 'Warnings', (1,)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.samplerate = None + self.state = 'INITIAL' + self.present = 0 + self.bit = 0 + self.bit_count = -1 + self.command = 0 + self.overdrive = False + self.fall = 0 + self.rise = 0 + + def start(self): + self.out_python = self.register(srd.OUTPUT_PYTHON) + self.out_ann = self.register(srd.OUTPUT_ANN) + self.overdrive = (self.options['overdrive'] == 'yes') + self.fall = 0 + self.rise = 0 + self.bit_count = -1 + + def putm(self, data): + self.put(0, 0, self.out_ann, data) + + def putpfs(self, data): + self.put(self.fall, self.samplenum, self.out_python, data) + + def putfs(self, data): + self.put(self.fall, self.samplenum, self.out_ann, data) + + def putfr(self, data): + self.put(self.fall, self.rise, self.out_ann, data) + + def putprs(self, data): + self.put(self.rise, self.samplenum, self.out_python, data) + + def putrs(self, data): + self.put(self.rise, self.samplenum, self.out_ann, data) + + def checks(self): + # Check if samplerate is appropriate. + if self.options['overdrive'] == 'yes': + if self.samplerate < 2000000: + self.putm([1, ['Sampling rate is too low. Must be above ' + + '2MHz for proper overdrive mode decoding.']]) + elif self.samplerate < 5000000: + self.putm([1, ['Sampling rate is suggested to be above 5MHz ' + + 'for proper overdrive mode decoding.']]) + else: + if self.samplerate < 400000: + self.putm([1, ['Sampling rate is too low. Must be above ' + + '400kHz for proper normal mode decoding.']]) + elif self.samplerate < 1000000: + self.putm([1, ['Sampling rate is suggested to be above ' + + '1MHz for proper normal mode decoding.']]) + + def metadata(self, key, value): + if key != srd.SRD_CONF_SAMPLERATE: + return + self.samplerate = value + + def wait_falling_timeout(self, start, t): + # Wait until either a falling edge is seen, and/or the specified + # number of samples have been skipped (i.e. time has passed). + cnt = int((t[self.overdrive] / 1000000.0) * self.samplerate) + samples_to_skip = (start + cnt) - self.samplenum + samples_to_skip = samples_to_skip if (samples_to_skip > 0) else 0 + return self.wait([{0: 'f'}, {'skip': samples_to_skip}]) + + def decode(self): + if not self.samplerate: + raise SamplerateError('Cannot decode without samplerate.') + self.checks() + while True: + # State machine. + if self.state == 'INITIAL': # Unknown initial state. + # Wait until we reach the idle high state. + self.wait({0: 'h'}) + self.rise = self.samplenum + self.state = 'IDLE' + elif self.state == 'IDLE': # Idle high state. + # Wait for falling edge. + self.wait({0: 'f'}) + self.fall = self.samplenum + # Get time since last rising edge. + time = ((self.fall - self.rise) / self.samplerate) * 1000000.0 + if self.rise > 0 and \ + time < timing['REC']['min'][self.overdrive]: + self.putfr([1, ['Recovery time not long enough' + 'Recovery too short', + 'REC < ' + str(timing['REC']['min'][self.overdrive])]]) + # A reset pulse or slot can start on a falling edge. + self.state = 'LOW' + # TODO: Check minimum recovery time. + elif self.state == 'LOW': # Reset pulse or slot. + # Wait for rising edge. + self.wait({0: 'r'}) + self.rise = self.samplenum + # Detect reset or slot base on timing. + time = ((self.rise - self.fall) / self.samplerate) * 1000000.0 + if time >= timing['RSTL']['min'][False]: # Normal reset pulse. + if time > timing['RSTL']['max'][False]: + self.putfr([1, ['Too long reset pulse might mask interrupt ' + + 'signalling by other devices', + 'Reset pulse too long', + 'RST > ' + str(timing['RSTL']['max'][False])]]) + # Regular reset pulse clears overdrive speed. + if self.overdrive: + self.putfr([4, ['Exiting overdrive mode', 'Overdrive off']]) + self.overdrive = False + self.putfr([2, ['Reset', 'Rst', 'R']]) + self.state = 'PRESENCE DETECT HIGH' + elif self.overdrive == True and \ + time >= timing['RSTL']['min'][self.overdrive] and \ + time < timing['RSTL']['max'][self.overdrive]: + # Overdrive reset pulse. + self.putfr([2, ['Reset', 'Rst', 'R']]) + self.state = 'PRESENCE DETECT HIGH' + elif time < timing['SLOT']['max'][self.overdrive]: + # Read/write time slot. + if time < timing['LOWR']['min'][self.overdrive]: + self.putfr([1, ['Low signal not long enough', + 'Low too short', + 'LOW < ' + str(timing['LOWR']['min'][self.overdrive])]]) + if time < timing['LOWR']['max'][self.overdrive]: + self.bit = 1 # Short pulse is a 1 bit. + else: + self.bit = 0 # Long pulse is a 0 bit. + # Wait for end of slot. + self.state = 'SLOT' + else: + # Timing outside of known states. + self.putfr([1, ['Erroneous signal', 'Error', 'Err', 'E']]) + self.state = 'IDLE' + elif self.state == 'PRESENCE DETECT HIGH': # Wait for slave presence signal. + # Wait for a falling edge and/or presence detect signal. + self.wait_falling_timeout(self.rise, timing['PDH']['max']) + + # Calculate time since rising edge. + time = ((self.samplenum - self.rise) / self.samplerate) * 1000000.0 + + if (self.matched & (0b1 << 0)) and not (self.matched & (0b1 << 1)): + # Presence detected. + if time < timing['PDH']['min'][self.overdrive]: + self.putrs([1, ['Presence detect signal is too early', + 'Presence detect too early', + 'PDH < ' + str(timing['PDH']['min'][self.overdrive])]]) + self.fall = self.samplenum + self.state = 'PRESENCE DETECT LOW' + else: # No presence detected. + self.putrs([3, ['Presence: false', 'Presence', 'Pres', 'P']]) + self.putprs(['RESET/PRESENCE', False]) + self.state = 'IDLE' + elif self.state == 'PRESENCE DETECT LOW': # Slave presence signalled. + # Wait for end of presence signal (on rising edge). + self.wait({0: 'r'}) + # Calculate time since start of presence signal. + time = ((self.samplenum - self.fall) / self.samplerate) * 1000000.0 + if time < timing['PDL']['min'][self.overdrive]: + self.putfs([1, ['Presence detect signal is too short', + 'Presence detect too short', + 'PDL < ' + str(timing['PDL']['min'][self.overdrive])]]) + elif time > timing['PDL']['max'][self.overdrive]: + self.putfs([1, ['Presence detect signal is too long', + 'Presence detect too long', + 'PDL > ' + str(timing['PDL']['max'][self.overdrive])]]) + if time > timing['RSTH']['min'][self.overdrive]: + self.rise = self.samplenum + # Wait for end of presence detect. + self.state = 'PRESENCE DETECT' + + # End states (for additional checks). + if self.state == 'SLOT': # Wait for end of time slot. + # Wait for a falling edge and/or end of timeslot. + self.wait_falling_timeout(self.fall, timing['SLOT']['min']) + + if (self.matched & (0b1 << 0)) and not (self.matched & (0b1 << 1)): + # Low detected before end of slot. + self.putfs([1, ['Time slot not long enough', + 'Slot too short', + 'SLOT < ' + str(timing['SLOT']['min'][self.overdrive])]]) + # Don't output invalid bit. + self.fall = self.samplenum + self.state = 'LOW' + else: # End of time slot. + # Output bit. + self.putfs([0, ['Bit: %d' % self.bit, '%d' % self.bit]]) + self.putpfs(['BIT', self.bit]) + # Save command bits. + if self.bit_count >= 0: + self.command += (self.bit << self.bit_count) + self.bit_count += 1 + # Check for overdrive ROM command. + if self.bit_count >= 8: + if self.command == 0x3c or self.command == 0x69: + self.overdrive = True + self.put(self.samplenum, self.samplenum, + self.out_ann, + [4, ['Entering overdrive mode', 'Overdrive on']]) + self.bit_count = -1 + self.state = 'IDLE' + + if self.state == 'PRESENCE DETECT': + # Wait for a falling edge and/or end of presence detect. + self.wait_falling_timeout(self.rise, timing['RSTH']['min']) + + if (self.matched & (0b1 << 0)) and not (self.matched & (0b1 << 1)): + # Low detected before end of presence detect. + self.putfs([1, ['Presence detect not long enough', + 'Presence detect too short', + 'RTSH < ' + str(timing['RSTH']['min'][self.overdrive])]]) + # Inform about presence detected. + self.putrs([3, ['Slave presence detected', 'Slave present', + 'Present', 'P']]) + self.putprs(['RESET/PRESENCE', True]) + self.fall = self.samplenum + self.state = 'LOW' + else: # End of time slot. + # Inform about presence detected. + self.putrs([3, ['Presence: true', 'Presence', 'Pres', 'P']]) + self.putprs(['RESET/PRESENCE', True]) + self.rise = self.samplenum + # Start counting the first 8 bits to get the ROM command. + self.bit_count = 0 + self.command = 0 + self.state = 'IDLE' diff --git a/libsigrokdecode4DSL/decoders/onewire_network/__init__.py b/libsigrokdecode4DSL/decoders/onewire_network/__init__.py new file mode 100644 index 00000000..60907efa --- /dev/null +++ b/libsigrokdecode4DSL/decoders/onewire_network/__init__.py @@ -0,0 +1,56 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2012 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This decoder stacks on top of the 'onewire_link' PD and decodes the +1-Wire protocol (network layer). + +The 1-Wire protocol enables bidirectional communication over a single wire +(and ground) between a single master and one or multiple slaves. The protocol +is layered: + + - Link layer (reset, presence detection, reading/writing bits) + - Network layer (skip/search/match device ROM addresses) + - Transport layer (transport data between 1-Wire master and device) + +Network layer: + +The following link layer annotations are shown: + + - RESET/PRESENCE True/False + The event is marked from the signal negative edge to the end of the reset + high period. It is also reported if there are any devices attached to the + bus. + +The following network layer annotations are shown: + + - ROM command + The requested ROM command is displayed as an 8bit hex value and by name. + - ROM + The 64bit value of the addressed device is displayed: + Family code (1 byte) + serial number (6 bytes) + CRC (1 byte) + - Data + Data intended for the transport layer is displayed as an 8bit hex value. + +TODO: + - Add CRC checks, to see if there were communication errors on the wire. + - Add reporting original/complement address values from the search algorithm. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/onewire_network/pd.py b/libsigrokdecode4DSL/decoders/onewire_network/pd.py new file mode 100644 index 00000000..ef302aea --- /dev/null +++ b/libsigrokdecode4DSL/decoders/onewire_network/pd.py @@ -0,0 +1,185 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2012 Iztok Jeras +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd + +# Dictionary of ROM commands and their names, next state. +command = { + 0x33: ['Read ROM' , 'GET ROM' ], + 0x0f: ['Conditional read ROM' , 'GET ROM' ], + 0xcc: ['Skip ROM' , 'TRANSPORT' ], + 0x55: ['Match ROM' , 'GET ROM' ], + 0xf0: ['Search ROM' , 'SEARCH ROM'], + 0xec: ['Conditional search ROM' , 'SEARCH ROM'], + 0x3c: ['Overdrive skip ROM' , 'TRANSPORT' ], + 0x69: ['Overdrive match ROM' , 'GET ROM' ], + 0xa5: ['Resume' , 'TRANSPORT' ], + 0x96: ['DS2408: Disable Test Mode' , 'GET ROM' ], +} + +class Decoder(srd.Decoder): + api_version = 3 + id = 'onewire_network' + name = '1-Wire network layer' + longname = '1-Wire serial communication bus (network layer)' + desc = 'Bidirectional, half-duplex, asynchronous serial bus.' + license = 'gplv2+' + inputs = ['onewire_link'] + outputs = ['onewire_network'] + tags = ['Embedded/industrial'] + annotations = ( + ('text', 'Human-readable text'), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.ss_block = 0 + self.es_block = 0 + self.state = 'COMMAND' + self.bit_cnt = 0 + self.search = 'P' + self.data_p = 0x0 + self.data_n = 0x0 + self.data = 0x0 + self.rom = 0x0000000000000000 + + def start(self): + self.out_python = self.register(srd.OUTPUT_PYTHON) + self.out_ann = self.register(srd.OUTPUT_ANN) + + def putx(self, data): + # Helper function for most annotations. + self.put(self.ss_block, self.es_block, self.out_ann, data) + + def puty(self, data): + # Helper function for most protocol packets. + self.put(self.ss_block, self.es_block, self.out_python, data) + + def decode(self, ss, es, data): + code, val = data + + # State machine. + if code == 'RESET/PRESENCE': + self.search = 'P' + self.bit_cnt = 0 + self.put(ss, es, self.out_ann, + [0, ['Reset/presence: %s' % ('true' if val else 'false')]]) + self.put(ss, es, self.out_python, ['RESET/PRESENCE', val]) + self.state = 'COMMAND' + return + + # For now we're only interested in 'RESET/PRESENCE' and 'BIT' packets. + if code != 'BIT': + return + + if self.state == 'COMMAND': + # Receiving and decoding a ROM command. + if self.onewire_collect(8, val, ss, es) == 0: + return + if self.data in command: + self.putx([0, ['ROM command: 0x%02x \'%s\'' + % (self.data, command[self.data][0])]]) + self.state = command[self.data][1] + else: + self.putx([0, ['ROM command: 0x%02x \'%s\'' + % (self.data, 'unrecognized')]]) + self.state = 'COMMAND ERROR' + elif self.state == 'GET ROM': + # A 64 bit device address is selected. + # Family code (1 byte) + serial number (6 bytes) + CRC (1 byte) + if self.onewire_collect(64, val, ss, es) == 0: + return + self.rom = self.data & 0xffffffffffffffff + self.putx([0, ['ROM: 0x%016x' % self.rom]]) + self.puty(['ROM', self.rom]) + self.state = 'TRANSPORT' + elif self.state == 'SEARCH ROM': + # A 64 bit device address is searched for. + # Family code (1 byte) + serial number (6 bytes) + CRC (1 byte) + if self.onewire_search(64, val, ss, es) == 0: + return + self.rom = self.data & 0xffffffffffffffff + self.putx([0, ['ROM: 0x%016x' % self.rom]]) + self.puty(['ROM', self.rom]) + self.state = 'TRANSPORT' + elif self.state == 'TRANSPORT': + # The transport layer is handled in byte sized units. + if self.onewire_collect(8, val, ss, es) == 0: + return + self.putx([0, ['Data: 0x%02x' % self.data]]) + self.puty(['DATA', self.data]) + elif self.state == 'COMMAND ERROR': + # Since the command is not recognized, print raw data. + if self.onewire_collect(8, val, ss, es) == 0: + return + self.putx([0, ['ROM error data: 0x%02x' % self.data]]) + + # Data collector. + def onewire_collect(self, length, val, ss, es): + # Storing the sample this sequence begins with. + if self.bit_cnt == 0: + self.ss_block = ss + self.data = self.data & ~(1 << self.bit_cnt) | (val << self.bit_cnt) + self.bit_cnt += 1 + # Storing the sample this sequence ends with. + # In case the full length of the sequence is received, return 1. + if self.bit_cnt == length: + self.es_block = es + self.data = self.data & ((1 << length) - 1) + self.bit_cnt = 0 + return 1 + else: + return 0 + + # Search collector. + def onewire_search(self, length, val, ss, es): + # Storing the sample this sequence begins with. + if (self.bit_cnt == 0) and (self.search == 'P'): + self.ss_block = ss + + if self.search == 'P': + # Master receives an original address bit. + self.data_p = self.data_p & ~(1 << self.bit_cnt) | \ + (val << self.bit_cnt) + self.search = 'N' + elif self.search == 'N': + # Master receives a complemented address bit. + self.data_n = self.data_n & ~(1 << self.bit_cnt) | \ + (val << self.bit_cnt) + self.search = 'D' + elif self.search == 'D': + # Master transmits an address bit. + self.data = self.data & ~(1 << self.bit_cnt) | (val << self.bit_cnt) + self.search = 'P' + self.bit_cnt += 1 + + # Storing the sample this sequence ends with. + # In case the full length of the sequence is received, return 1. + if self.bit_cnt == length: + self.es_block = es + self.data_p = self.data_p & ((1 << length) - 1) + self.data_n = self.data_n & ((1 << length) - 1) + self.data = self.data & ((1 << length) - 1) + self.search = 'P' + self.bit_cnt = 0 + return 1 + else: + return 0 diff --git a/libsigrokdecode4DSL/decoders/ook/__init__.py b/libsigrokdecode4DSL/decoders/ook/__init__.py new file mode 100644 index 00000000..24e493bb --- /dev/null +++ b/libsigrokdecode4DSL/decoders/ook/__init__.py @@ -0,0 +1,36 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2018 Steve R +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +OOK decodes On-off keying based remote control protocols. + +It is aimed at 433MHz but should also work with other common RC frequencies. +The input can be captured directly from a transmitter (before the modulation +stage) or demodulated by an RF receiver. + +Over the air captured traces will be a lot noisier and will probably need the +area of interest to be zoomed onto, then selected with the "Cursors" and the +"Save Selected Range As" feature to be used to extract it from the noise. + +There is a limited amount of pre-filtering and garbage removal built into the +decoder which can sometimes extract signals directly from a larger over the air +trace. It depends heavily on your environment. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/ook/pd.py b/libsigrokdecode4DSL/decoders/ook/pd.py new file mode 100644 index 00000000..559291c4 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/ook/pd.py @@ -0,0 +1,484 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2018 Steve R +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd + +''' +OUTPUT_PYTHON format: +Samples: The Samples array is sent when a DECODE_TIMEOUT occurs. +[, , ] + is the sample number of the start of the decoded bit. This may not line +up with the pulses that were converted into the decoded bit particularly for +Manchester encoding. + is the sample number of the end of the decoded bit. + is a single character string which is the state of the decoded bit. +This can be +'0' zero or low +'1' one or high +'E' Error or invalid. This can be caused by missing transitions or the wrong +pulse lengths according to the rules for the particular encoding. In some cases +this is intentional (Oregon 1 preamble) and is part of the sync pattern. In +other cases the signal could simply be broken. + +If there are more than self.max_errors (default 5) in decoding then the +OUTPUT_PYTHON is not sent as the data is assumed to be worthless. +There also needs to be a low for five times the preamble period at the end of +each set of pulses to trigger a DECODE_TIMEOUT and get the OUTPUT_PYTHON sent. +''' + +class SamplerateError(Exception): + pass + +class Decoder(srd.Decoder): + api_version = 3 + id = 'ook' + name = 'OOK' + longname = 'On-off keying' + desc = 'On-off keying protocol.' + license = 'gplv2+' + inputs = ['logic'] + outputs = ['ook'] + tags = ['Encoding'] + channels = ( + {'id': 'data', 'name': 'Data', 'desc': 'Data line'}, + ) + annotations = ( + ('frame', 'Frame'), + ('info', 'Info'), + ('1111', '1111'), + ('1010', '1010'), + ('diffman', 'Diff Man'), + ('nrz', 'NRZ'), + ) + annotation_rows = ( + ('frame', 'Framing',(0,)), + ('info', 'Info', (1,)), + ('man1111', 'Man 1111', (2,)), + ('man1010', 'Man 1010', (3,)), + ('diffman', 'Diff Man', (4,)), + ('nrz', 'NRZ', (5,)), + ) + binary = ( + ('pulse-lengths', 'Pulse lengths'), + ) + options = ( + {'id': 'invert', 'desc': 'Invert data', 'default': 'no', + 'values': ('no', 'yes')}, + {'id': 'decodeas', 'desc': 'Decode type', 'default': 'Manchester', + 'values': ('NRZ', 'Manchester', 'Diff Manchester')}, + {'id': 'preamble', 'desc': 'Preamble', 'default': 'auto', + 'values': ('auto', '1010', '1111')}, + {'id': 'preamlen', 'desc': 'Filter length', 'default': '7', + 'values': ('0', '3', '4', '5', '6', '7', '8', '9', '10')}, + {'id': 'diffmanvar', 'desc': 'Transition at start', 'default': '1', + 'values': ('1', '0')}, + ) + + def __init__(self): + self.reset() + + def reset(self): + self.samplerate = None + self.ss = self.es = -1 + self.ss_1111 = self.ss_1010 = -1 + self.samplenumber_last = None + self.sample_first = None + self.sample_high = 0 + self.sample_low = 0 + self.edge_count = 0 + self.word_first = None + self.word_count = 0 + self.state = 'IDLE' + self.lstate = None + self.lstate_1010 = None + self.insync = 0 # Preamble in sync flag + self.man_errors = 0 + self.man_errors_1010 = 0 + self.preamble = [] # Preamble buffer + self.half_time = -1 # Half time for man 1111 + self.half_time_1010 = 0 # Half time for man 1010 + self.pulse_lengths = [] # Pulse lengths + self.decoded = [] # Decoded stream + self.decoded_1010 = [] # Decoded stream + self.diff_man_trans = '0' # Transition + self.diff_man_len = 1 # Length of pulse in half clock periods + self.max_errors = 5 # Max number of errors to output OOK + + def metadata(self, key, value): + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + self.out_python = self.register(srd.OUTPUT_PYTHON) + self.out_binary = self.register(srd.OUTPUT_BINARY) + self.invert = self.options['invert'] + self.decodeas = self.options['decodeas'] + self.preamble_val = self.options['preamble'] + self.preamble_len = self.options['preamlen'] + self.diffmanvar = self.options['diffmanvar'] + + def putx(self, data): + self.put(self.ss, self.es, self.out_ann, data) + + def putp(self, data): + self.put(self.ss, self.es, self.out_python, data) + + def dump_pulse_lengths(self): + if self.samplerate: + self.pulse_lengths[-1] = self.sample_first # Fix final pulse length. + s = 'Pulses(us)=' + s += ','.join(str(int(int(x) * 1000000 / self.samplerate)) + for x in self.pulse_lengths) + s += '\n' + self.put(self.samplenum - 10, self.samplenum, self.out_binary, + [0, bytes([ord(c) for c in s])]) + + def decode_nrz(self, start, samples, state): + self.pulse_lengths.append(samples) + # Use different high and low widths to compensate skewed waveforms. + dsamples = self.sample_high if state == '1' else self.sample_low + self.ss, self.es = start, start + samples + while samples > dsamples * 0.5: + if samples >= dsamples * 1.5: # More than one bit. + self.es = self.ss + dsamples + self.putx([5, [state]]) + self.decoded.append([self.ss, self.es, state]) + self.edge_count += 1 + elif samples >= dsamples * 0.5 and samples < dsamples * 1.5: # Last bit. + self.putx([5, [state]]) + self.decoded.append([self.ss, self.es, state]) + self.edge_count += 1 + else: + self.edge_count += 1 + samples -= dsamples + self.ss += dsamples + self.es += dsamples + + # Ensure 2nd row doesn't go past end of 1st row. + if self.es > self.samplenum: + self.es = self.samplenum + + if self.state == 'DECODE_TIMEOUT': # Five bits - reset. + self.ss = self.decoded[0][0] + self.es = self.decoded[len(self.decoded) - 1][1] + self.dump_pulse_lengths() + self.putp(self.decoded) + self.decode_timeout() + break + + def lock_onto_preamble(self, samples, state): # Filters and recovers clock. + self.edge_count += 1 + l2s = 5 # Max ratio of long to short pulses. + + # Filter incoming pulses to remove random noise. + if self.state == 'DECODE_TIMEOUT': + self.preamble = [] + self.edge_count = 0 + self.word_first = self.samplenum + self.sample_first = self.samplenum - self.samplenumber_last + self.state = 'WAITING_FOR_PREAMBLE' + self.man_errors = 0 + + pre_detect = int(self.preamble_len) # Number of valid pulses to detect. + pre_samples = self.samplenum - self.samplenumber_last + if len(self.preamble) > 0: + if (pre_samples * l2s < self.preamble[-1][1] or + self.preamble[-1][1] * l2s < pre_samples): # Garbage in. + self.put(self.samplenum, self.samplenum, + self.out_ann, [0, ['R']]) # Display resets. + self.preamble = [] # Clear buffer. + self.preamble.append([self.samplenumber_last, + pre_samples, state]) + self.edge_count = 0 + self.samplenumber_last = self.samplenum + self.word_first = self.samplenum + else: + self.preamble.append([self.samplenumber_last, + pre_samples, state]) + else: + self.preamble.append([self.samplenumber_last, + pre_samples, state]) + + pre = self.preamble + if len(self.preamble) == pre_detect: # Have a valid series of pulses. + if self.preamble[0][2] == '1': + self.sample_high = self.preamble[0][1] # Allows skewed pulses. + self.sample_low = self.preamble[1][1] + else: + self.sample_high = self.preamble[1][1] + self.sample_low = self.preamble[0][1] + + self.edge_count = 0 + + for i in range(len(self.preamble)): + if i > 1: + if (pre[i][1] > pre[i - 2][1] * 1.25 or + pre[i][1] * 1.25 < pre[i - 2][1]): # Adjust ref width. + if pre[i][2] == '1': + self.sample_high = pre[i][1] + else: + self.sample_low = pre[i][1] + + # Display start of preamble. + if self.decodeas == 'NRZ': + self.decode_nrz(pre[i][0], pre[i][1], pre[i][2]) + if self.decodeas == 'Manchester': + self.decode_manchester(pre[i][0], pre[i][1], pre[i][2]) + if self.decodeas == 'Diff Manchester': + self.es = pre[i][0] + pre[i][1] + self.decode_diff_manchester(pre[i][0], pre[i][1], pre[i][2]) + + # Used to timeout signal. + self.sample_first = int((self.sample_high + self.sample_low)/2) + self.insync = 1 + self.state = 'DECODING' + self.lstate = state + self.lstate_1010 = state + + def decode_diff_manchester(self, start, samples, state): + self.pulse_lengths.append(samples) + + # Use different high and low widths to compensate skewed waveforms. + dsamples = self.sample_high if state == '1' else self.sample_low + + self.es = start + samples + p_length = round(samples / dsamples) # Find relative pulse length. + + if self.edge_count == 0: + self.diff_man_trans = '1' # Very first pulse must be a transition. + self.diff_man_len = 1 # Must also be a half pulse. + self.ss = start + elif self.edge_count % 2 == 1: # Time to make a decision. + if self.diffmanvar == '0': # Transition at self.ss is a zero. + self.diff_man_trans = '0' if self.diff_man_trans == '1' else '1' + if self.diff_man_len == 1 and p_length == 1: + self.putx([4, [self.diff_man_trans]]) + self.decoded.append([self.ss, self.es, self.diff_man_trans]) + self.diff_man_trans = '1' + elif self.diff_man_len == 1 and p_length == 2: + self.es -= int(samples / 2) + self.putx([4, [self.diff_man_trans]]) + self.decoded.append([self.ss, self.es, self.diff_man_trans]) + self.diff_man_trans = '0' + self.edge_count += 1 # Add a virt edge to keep in sync with clk. + elif self.diff_man_len == 2 and p_length == 1: + self.putx([4, [self.diff_man_trans]]) + self.decoded.append([self.ss, self.es, self.diff_man_trans]) + self.diff_man_trans = '1' + elif self.diff_man_len == 2 and p_length == 2: # Double illegal E E. + self.es -= samples + self.putx([4, ['E']]) + self.decoded.append([self.ss, self.es, 'E']) + self.ss = self.es + self.es += samples + self.putx([4, ['E']]) + self.decoded.append([self.ss, self.es, 'E']) + self.diff_man_trans = '1' + elif self.diff_man_len == 1 and p_length > 4: + if self.state == 'DECODE_TIMEOUT': + self.es = self.ss + 2 * self.sample_first + self.putx([4, [self.diff_man_trans]]) # Write error. + self.decoded.append([self.ss, self.es, self.diff_man_trans]) + self.ss = self.decoded[0][0] + self.es = self.decoded[len(self.decoded) - 1][1] + self.dump_pulse_lengths() + if self.man_errors < self.max_errors: + self.putp(self.decoded) + else: + error_message = 'Probably not Diff Manchester encoded' + self.ss = self.word_first + self.putx([1, [error_message]]) + self.decode_timeout() + self.diff_man_trans = '1' + self.ss = self.es + self.diff_man_len = p_length # Save the previous length. + self.edge_count += 1 + + def decode_manchester_sim(self, start, samples, state, + dsamples, half_time, lstate, ss, pream): + ook_bit = [] + errors = 0 + if self.edge_count == 0: + half_time += 1 + if samples > 0.75 * dsamples and samples <= 1.5 * dsamples: # Long p. + half_time += 2 + if half_time % 2 == 0: # Transition. + es = start + else: + es = start + int(samples / 2) + if ss == start: + lstate = 'E' + es = start + samples + if not (self.edge_count == 0 and pream == '1010'): # Skip first p. + ook_bit = [ss, es, lstate] + lstate = state + ss = es + elif samples > 0.25 * dsamples and samples <= 0.75 * dsamples: # Short p. + half_time += 1 + if (half_time % 2 == 0): # Transition. + es = start + samples + ook_bit = [ss, es, lstate] + lstate = state + ss = es + else: # 1st half. + ss = start + lstate = state + else: # Too long or too short - error. + errors = 1 + if self.state != 'DECODE_TIMEOUT': # Error condition. + lstate = 'E' + es = ss + samples + else: # Assume final half bit buried in timeout pulse. + es = ss + self.sample_first + ook_bit = [ss, es, lstate] + ss = es + + return (half_time, lstate, ss, ook_bit, errors) + + def decode_manchester(self, start, samples, state): + self.pulse_lengths.append(samples) + + # Use different high and low widths to compensate skewed waveforms. + dsamples = self.sample_high if state == '1' else self.sample_low + + if self.preamble_val != '1010': # 1111 preamble is half clock T. + (self.half_time, self.lstate, self.ss_1111, ook_bit, errors) = ( + self.decode_manchester_sim(start, samples, state, dsamples * 2, + self.half_time, self.lstate, + self.ss_1111, '1111')) + self.man_errors += errors + if ook_bit != []: + self.decoded.append([ook_bit[0], ook_bit[1], ook_bit[2]]) + + if self.preamble_val != '1111': # 1010 preamble is clock T. + (self.half_time_1010, self.lstate_1010, self.ss_1010, + ook_bit, errors) = ( + self.decode_manchester_sim(start, samples, state, dsamples, + self.half_time_1010, self.lstate_1010, + self.ss_1010, '1010')) + self.man_errors_1010 += errors + if ook_bit != []: + self.decoded_1010.append([ook_bit[0], ook_bit[1], ook_bit[2]]) + + self.edge_count += 1 + + # Stream display and save ook_bit. + if ook_bit != []: + self.ss, self.es = ook_bit[0], ook_bit[1] + if self.preamble_val == '1111': + self.putx([2, [ook_bit[2]]]) + if self.preamble_val == '1010': + self.putx([3, [ook_bit[2]]]) + + if self.state == 'DECODE_TIMEOUT': # End of packet. + self.dump_pulse_lengths() + + decoded = [] + # If 1010 preamble has less errors use it. + if (self.preamble_val == '1010' or + (self.man_errors_1010 < self.max_errors and + self.man_errors_1010 < self.man_errors and + len(self.decoded_1010) > 0)): + decoded = self.decoded_1010 + man_errors = self.man_errors_1010 + d_row = 3 + else: + decoded = self.decoded + man_errors = self.man_errors + d_row = 2 + + if self.preamble_val == 'auto': # Display OOK packet. + for i in range(len(decoded)): + self.ss, self.es = decoded[i][0], decoded[i][1] + self.putx([d_row, [decoded[i][2]]]) + + if (man_errors < self.max_errors and len(decoded) > 0): + self.ss, self.es = decoded[0][0], decoded[len(decoded) - 1][1] + self.putp(decoded) + else: + error_message = 'Not Manchester encoded or wrong preamble' + self.ss = self.word_first + self.putx([1, [error_message]]) + + self.put(self.es, self.es, self.out_ann, [0, ['T']]) # Mark timeout. + self.decode_timeout() + + def decode_timeout(self): + self.word_count = 0 + self.samplenumber_last = None + self.edge_count = 0 + self.man_errors = 0 # Clear the bit error counters. + self.man_errors_1010 = 0 + self.state = 'IDLE' + self.wait({0: 'e'}) # Get rid of long pulse. + self.samplenumber_last = self.samplenum + self.word_first = self.samplenum + self.insync = 0 # Preamble in sync flag + self.preamble = [] # Preamble buffer + self.half_time = -1 # Half time for man 1111 + self.half_time_1010 = 0 # Half time for man 1010 + self.decoded = [] # Decoded bits + self.decoded_1010 = [] # Decoded bits for man 1010 + self.pulse_lengths = [] + + def decode(self): + while True: + if self.edge_count == 0: # Waiting for a signal. + (ook,) = self.wait({0: 'e'}) + self.state = 'DECODING' + else: + (ook,) = self.wait([{0: 'e'}, {'skip': 5 * self.sample_first}]) + if (self.matched & (0b1 << 1)) and not (self.matched & (0b1 << 0)): # No edges for 5 p's. + self.state = 'DECODE_TIMEOUT' + + if not self.samplenumber_last: # Set counters to start of signal. + self.samplenumber_last = self.samplenum + self.word_first = self.samplenum + continue + samples = self.samplenum - self.samplenumber_last + if not self.sample_first: # Get number of samples for first pulse. + self.sample_first = samples + + pinstate = ook + if self.state == 'DECODE_TIMEOUT': # No edge so flip the state. + pinstate = int(not pinstate) + if self.invert == 'yes': # Invert signal. + pinstate = int(not pinstate) + state = '0' if pinstate else '1' + + # No preamble filtering or checking and no skew correction. + if self.preamble_len == '0': + self.sample_high = self.sample_first + self.sample_low = self.sample_first + self.insync = 0 + + if self.insync == 0: + self.lock_onto_preamble(samples, state) + else: + if self.decodeas == 'NRZ': + self.decode_nrz(self.samplenumber_last, samples, state) + if self.decodeas == 'Manchester': + self.decode_manchester(self.samplenumber_last, + samples, state) + if self.decodeas == 'Diff Manchester': + self.decode_diff_manchester(self.samplenumber_last, + samples, state) + + self.samplenumber_last = self.samplenum diff --git a/libsigrokdecode4DSL/decoders/ook_oregon/__init__.py b/libsigrokdecode4DSL/decoders/ook_oregon/__init__.py new file mode 100644 index 00000000..f1a1fdf4 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/ook_oregon/__init__.py @@ -0,0 +1,25 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2018 Steve R +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This decoder stacks on top of the 'ook' PD and decodes the Oregon Scientific +433MHz remote control protocol for weather sensors. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/ook_oregon/lists.py b/libsigrokdecode4DSL/decoders/ook_oregon/lists.py new file mode 100644 index 00000000..c46c4cc7 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/ook_oregon/lists.py @@ -0,0 +1,75 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2018 Steve R +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +# Most of the info here comes from "434MHz RF Protocol Descriptions for +# Wireless Weather Sensors - October 2015" Known Sensor ID Codes - p25. + +# Format is 4 hex digit ID code followed by a LIST of models that use that +# ID and the type of sensor. +# SensorID is used as the hash in a Python hash table, so it must be upper case. +# The type of sensor is used to decode and display readings in the L2 decode, +# it's case-sensitive. +# Be very careful with the formatting ' [] and commas. + +sensor = { +# 'SensorID': [['model1', 'model2'], 'type'], + '1984': [['WGR800'], 'Wind'], # The newer anemometer with no temperature/RH sensor. + '1994': [['WGR800'], 'Wind'], # The original anemometer which included a temperature/RH sensor. + '1A2D': [['THGR228N'], 'Temp_Hum1'], + '1A3D': [['THGR918'], ''], + '1D20': [['THGN123N', 'THGR122NX', 'THGN123N', 'THGR228N'], 'Temp_Hum'], + '1D30': [['THGN500', 'THGN132N'], ''], + '2914': [['PCR800'], 'Rain'], + '2A19': [['PCR800'], 'Rain1'], + '2A1D': [['RGR918'], 'Rain'], + '2D10': [['RGR968', 'PGR968 '], 'Rain1'], + '3A0D': [['STR918', 'WGR918'], 'Wind'], + '5A5D': [['BTHR918'], ''], + '5A6D': [['BTHR918N'], 'Temp_Hum_Baro'], + '5D53': [['BTHGN129'], 'Baro'], + '5D60': [['BTHR968'], 'Temp_Hum_Baro'], + 'C844': [['THWR800'], 'Temp'], + 'CC13': [['RTGR328N'], 'Temp_Hum'], + 'CC23': [['THGR328N'], 'Temp_Hum'], + 'CD39': [['RTHR328N'], 'Temp'], + 'D874': [['UVN800'], 'UV1'], + 'EA4C': [['THWR288A'], 'Temp'], + 'EC40': [['THN132N', 'THR238NF'], 'Temp'], + 'EC70': [['UVR128'], 'UV'], + 'F824': [['THGN800', 'THGN801', 'THGR810'], 'Temp_Hum'], + 'F8B4': [['THGR810'], 'Temp_Hum'], +# '': ['PSR01'], '', ''], +# '': ['RTGR328NA'], '', ''], +# '': ['THC268'], '', ''], +# '': ['THWR288A-JD'], '', ''], +# '': ['THGR268'], '', ''], +# '': ['THR268'], '', ''], +} + +# The sensor checksum exceptions are used to calculate the right checksum for +# sensors that don't follow the v1, v2.1 and v3 methods. For instance a v2.1 +# sensor that has a v3 checksum. +sensor_checksum = { +# 'SensorID': ['checksum_method', 'comment'], + '1D20': ['v3', 'THGR228N'], + '5D60': ['v3', 'BTHR918N'], + 'EC40': ['v3', 'THN132N'], +} + +dir_table = ['N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW', 'N'] diff --git a/libsigrokdecode4DSL/decoders/ook_oregon/pd.py b/libsigrokdecode4DSL/decoders/ook_oregon/pd.py new file mode 100644 index 00000000..225f5983 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/ook_oregon/pd.py @@ -0,0 +1,389 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2018 Steve R +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd +import math +from .lists import * + +class Decoder(srd.Decoder): + api_version = 3 + id = 'ook_oregon' + name = 'Oregon' + longname = 'Oregon Scientific' + desc = 'Oregon Scientific weather sensor protocol.' + license = 'gplv2+' + inputs = ['ook'] + outputs = [] + tags = ['Sensor'] + annotations = ( + ('bit', 'Bit'), + ('field', 'Field'), + ('l2', 'Level 2'), + ('pre', 'Preamble'), + ('syn', 'Sync'), + ('id', 'SensorID'), + ('ch', 'Channel'), + ('roll', 'Rolling code'), + ('f1', 'Flags1'), + ) + annotation_rows = ( + ('bits', 'Bits', (0,)), + ('fields', 'Fields', (1, 3, 4)), + ('l2', 'Level 2', (2,)), + ) + binary = ( + ('data-hex', 'Hex data'), + ) + options = ( + {'id': 'unknown', 'desc': 'Unknown type is', 'default': 'Unknown', + 'values': ('Unknown', 'Temp', 'Temp_Hum', 'Temp_Hum1', 'Temp_Hum_Baro', + 'Temp_Hum_Baro1', 'UV', 'UV1', 'Wind', 'Rain', 'Rain1')}, + ) + + def __init__(self): + self.reset() + + def reset(self): + self.decoded = [] # Local cache of decoded OOK. + self.skip = None + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + self.out_binary = self.register(srd.OUTPUT_BINARY) + self.unknown = self.options['unknown'] + + def putx(self, data): + self.put(self.ss, self.es, self.out_ann, data) + + def dump_oregon_hex(self, start, finish): + nib = self.decoded_nibbles + hexstring = '' + for x in nib: + hexstring += str(x[3]) if x[3] != '' else ' ' + s = 'Oregon ' + self.ver + ' \"' + hexstring.upper() + '\"\n' + self.put(start, finish, self.out_binary, + [0, bytes([ord(c) for c in s])]) + + def oregon_put_pre_and_sync(self, len_pream, len_sync, ver): + ook = self.decoded + self.decode_pos = len_pream + self.ss, self.es = ook[0][0], ook[self.decode_pos][0] + self.putx([1, ['Oregon ' + ver + ' Preamble', ver + ' Preamble', + ver + ' Pre', ver]]) + self.decode_pos += len_sync + self.ss, self.es = ook[len_pream][0], ook[self.decode_pos][0] + self.putx([1, ['Sync', 'Syn', 'S']]) + + # Strip off preamble and sync bits. + self.decoded = self.decoded[self.decode_pos:] + self.ookstring = self.ookstring[self.decode_pos:] + self.ver = ver + + def oregon(self): + self.ookstring = '' + self.decode_pos = 0 + ook = self.decoded + for i in range(len(ook)): + self.ookstring += ook[i][2] + if '10011001' in self.ookstring[:40]: + (preamble, data) = self.ookstring.split('10011001', 1) + if len(data) > 0 and len(preamble) > 16: + self.oregon_put_pre_and_sync(len(preamble), 8, 'v2.1') + self.oregon_v2() + elif 'E1100' in self.ookstring[:17]: + (preamble, data) = self.ookstring.split('E1100', 1) + if len(data) > 0 and len(preamble) <= 12: + self.oregon_put_pre_and_sync(len(preamble), 5, 'v1') + self.oregon_v1() + elif '0101' in self.ookstring[:28]: + (preamble, data) = self.ookstring.split('0101', 1) + if len(data) > 0 and len(preamble) > 12: + self.oregon_put_pre_and_sync(len(preamble), 4, 'v3') + self.oregon_v3() + elif len(self.ookstring) > 16: # Ignore short packets. + error_message = 'Not Oregon or wrong preamble' + self.ss, self.es = ook[0][0], ook[len(ook) - 1][1] + self.putx([1,[error_message]]) + + def oregon_v1(self): + ook = self.decoded + self.decode_pos = 0 + self.decoded_nibbles = [] + if len(self.decoded) >= 32: # Check there are at least 8 nibbles. + self.oregon_put_nib('RollingCode', ook[self.decode_pos][0], + ook[self.decode_pos + 3][1], 4) + self.oregon_put_nib('Ch', ook[self.decode_pos][0], + ook[self.decode_pos + 3][1], 4) + self.oregon_put_nib('Temp', ook[self.decode_pos][0], + ook[self.decode_pos + 15][1], 16) + self.oregon_put_nib('Checksum', ook[self.decode_pos][0], + ook[self.decode_pos + 7][1], 8) + + self.dump_oregon_hex(ook[0][0], ook[len(ook) - 1][1]) + + # L2 decode. + self.oregon_temp(2) + self.oregon_channel(1) + self.oregon_battery(2) + self.oregon_checksum_v1() + + def oregon_v2(self): # Convert to v3 format - discard odd bits. + self.decode_pos = 0 + self.ookstring = self.ookstring[1::2] + for i in range(len(self.decoded)): + if i % 2 == 1: + self.decoded[i][0] = self.decoded[i - 1][0] # Re-align start pos. + self.decoded = self.decoded[1::2] # Discard left hand bits. + self.oregon_v3() # Decode with v3 decoder. + + def oregon_nibbles(self, ookstring): + num_nibbles = int(len(ookstring) / 4) + nibbles = [] + for i in range(num_nibbles): + nibble = ookstring[4 * i : 4 * i + 4] + nibble = nibble[::-1] # Reversed from right. + nibbles.append(nibble) + return nibbles + + def oregon_put_nib(self, label, start, finish, numbits): + param = self.ookstring[self.decode_pos:self.decode_pos + numbits] + param = self.oregon_nibbles(param) + if 'E' in ''.join(param): # Blank out fields with errors. + result = '' + else: + result = hex(int(''.join(param), 2))[2:] + if len(result) < numbits / 4: # Reinstate leading zeros. + result = '0' * (int(numbits / 4) - len(result)) + result + if label != '': + label += ': ' + self.put(start, finish, self.out_ann, [1, [label + result, result]]) + if label == '': # No label - use nibble position. + label = int(self.decode_pos / 4) + for i in range(len(param)): + ss = self.decoded[self.decode_pos + (4 * i)][0] + es = self.decoded[self.decode_pos + (4 * i) + 3][1] + # Blank out nibbles with errors. + result = '' if ('E' in param[i]) else hex(int(param[i], 2))[2:] + # Save nibbles for L2 decoder. + self.decoded_nibbles.append([ss, es, label, result]) + self.decode_pos += numbits + + def oregon_v3(self): + self.decode_pos = 0 + self.decoded_nibbles = [] + ook = self.decoded + + if len(self.decoded) >= 32: # Check there are at least 8 nibbles. + self.oregon_put_nib('SensorID', ook[self.decode_pos][0], + ook[self.decode_pos + 16][0], 16) + self.oregon_put_nib('Ch', ook[self.decode_pos][0], + ook[self.decode_pos + 3][1], 4) + self.oregon_put_nib('RollingCode', ook[self.decode_pos][0], + ook[self.decode_pos + 7][1], 8) + self.oregon_put_nib('Flags1', ook[self.decode_pos][0], + ook[self.decode_pos + 3][1], 4) + + rem_nibbles = len(self.ookstring[self.decode_pos:]) // 4 + for i in range(rem_nibbles): # Display and save rest of nibbles. + self.oregon_put_nib('', ook[self.decode_pos][0], + ook[self.decode_pos + 3][1], 4) + self.dump_oregon_hex(ook[0][0], ook[len(ook) - 1][1]) + self.oregon_level2() # Level 2 decode. + else: + error_message = 'Too short to decode' + self.put(ook[0][0], ook[-1][1], self.out_ann, [1, [error_message]]) + + def oregon_put_l2_param(self, offset, digits, dec_point, pre_label, label): + nib = self.decoded_nibbles + result = 0 + out_string = ''.join(str(x[3]) for x in nib[offset:offset + digits]) + if len(out_string) == digits: + for i in range(dec_point, 0, -1): + result += int(nib[offset + dec_point - i][3], 16) / pow(10, i) + for i in range(dec_point, digits): + result += int(nib[offset + i][3], 16) * pow(10, i - dec_point) + result = '%g' % (result) + else: + result = '' + es = nib[offset + digits - 1][1] + if label == '\u2103': + es = nib[offset + digits][1] # Align temp to include +/- nibble. + self.put(nib[offset][0], es, self.out_ann, + [2, [pre_label + result + label, result]]) + + def oregon_temp(self, offset): + nib = self.decoded_nibbles + if nib[offset + 3][3] != '': + temp_sign = str(int(nib[offset + 3][3], 16)) + temp_sign = '-' if temp_sign != '0' else '+' + else: + temp_sign = '?' + self.oregon_put_l2_param(offset, 3, 1, temp_sign, '\u2103') + + def oregon_baro(self, offset): + nib = self.decoded_nibbles + baro = '' + if not (nib[offset + 2][3] == '' or nib[offset + 1][3] == '' + or nib[offset][3] == ''): + baro = str(int(nib[offset + 1][3] + nib[offset][3], 16) + 856) + self.put(nib[offset][0], nib[offset + 3][1], + self.out_ann, [2, [baro + ' mb', baro]]) + + def oregon_wind_dir(self, offset): + nib = self.decoded_nibbles + if nib[offset][3] != '': + w_dir = int(int(nib[offset][3], 16) * 22.5) + w_compass = dir_table[math.floor((w_dir + 11.25) / 22.5)] + self.put(nib[offset][0], nib[offset][1], self.out_ann, + [2, [w_compass + ' (' + str(w_dir) + '\u00b0)', w_compass]]) + + def oregon_channel(self, offset): + nib = self.decoded_nibbles + channel = '' + if nib[offset][3] != '': + ch = int(nib[offset][3], 16) + if self.ver != 'v3': # May not be true for all v2.1 sensors. + if ch != 0: + bit_pos = 0 + while ((ch & 1) == 0): + bit_pos += 1 + ch = ch >> 1 + if self.ver == 'v2.1': + bit_pos += 1 + channel = str(bit_pos) + elif self.ver == 'v3': # Not sure if this applies to all v3's. + channel = str(ch) + if channel != '': + self.put(nib[offset][0], nib[offset][1], + self.out_ann, [2, ['Ch ' + channel, channel]]) + + def oregon_battery(self, offset): + nib = self.decoded_nibbles + batt = 'OK' + if nib[offset][3] != '': + if (int(nib[offset][3], 16) >> 2) & 0x1 == 1: + batt = 'Low' + self.put(nib[offset][0], nib[offset][1], + self.out_ann, [2, ['Batt ' + batt, batt]]) + + def oregon_level2(self): # v2 and v3 level 2 decoder. + nib = self.decoded_nibbles + self.sensor_id = (nib[0][3] + nib[1][3] + nib[2][3] + nib[3][3]).upper() + nl, sensor_type = sensor.get(self.sensor_id, [['Unknown'], 'Unknown']) + names = ','.join(nl) + # Allow user to try decoding an unknown sensor. + if sensor_type == 'Unknown' and self.unknown != 'Unknown': + sensor_type = self.unknown + self.put(nib[0][0], nib[3][1], self.out_ann, + [2, [names + ' - ' + sensor_type, names, nl[0]]]) + self.oregon_channel(4) + self.oregon_battery(7) + if sensor_type == 'Rain': + self.oregon_put_l2_param(8, 4, 2, '', ' in/hr') # Rain rate + self.oregon_put_l2_param(12, 6, 3, 'Total ', ' in') # Rain total + self.oregon_checksum(18) + if sensor_type == 'Rain1': + self.oregon_put_l2_param(8, 3, 1, '', ' mm/hr') # Rain rate + self.oregon_put_l2_param(11, 5, 1, 'Total ', ' mm') # Rain total + self.oregon_checksum(18) + if sensor_type == 'Temp': + self.oregon_temp(8) + self.oregon_checksum(12) + if sensor_type == 'Temp_Hum_Baro': + self.oregon_temp(8) + self.oregon_put_l2_param(12, 2, 0, 'Hum ', '%') # Hum + self.oregon_baro(15) # Baro + self.oregon_checksum(19) + if sensor_type == 'Temp_Hum_Baro1': + self.oregon_temp(8) + self.oregon_put_l2_param(12, 2, 0, 'Hum ', '%') # Hum + self.oregon_baro(14) # Baro + if sensor_type == 'Temp_Hum': + self.oregon_temp(8) + self.oregon_put_l2_param(12, 2, 0, 'Hum ', '%') # Hum + self.oregon_checksum(15) + if sensor_type == 'Temp_Hum1': + self.oregon_temp(8) + self.oregon_put_l2_param(12, 2, 0, 'Hum ', '%') # Hum + self.oregon_checksum(14) + if sensor_type == 'UV': + self.oregon_put_l2_param(8, 2, 0, '', '') # UV + if sensor_type == 'UV1': + self.oregon_put_l2_param(11, 2, 0,'' ,'') # UV + if sensor_type == 'Wind': + self.oregon_wind_dir(8) + self.oregon_put_l2_param(11, 3, 1, 'Gust ', ' m/s') # Wind gust + self.oregon_put_l2_param(14, 3, 1, 'Speed ', ' m/s') # Wind speed + self.oregon_checksum(17) + + def oregon_put_checksum(self, nibbles, checksum): + nib = self.decoded_nibbles + result = 'BAD' + if (nibbles + 1) < len(nib): + if (nib[nibbles + 1][3] != '' and nib[nibbles][3] != '' + and checksum != -1): + if self.ver != 'v1': + if checksum == (int(nib[nibbles + 1][3], 16) * 16 + + int(nib[nibbles][3], 16)): + result = 'OK' + else: + if checksum == (int(nib[nibbles][3], 16) * 16 + + int(nib[nibbles + 1][3], 16)): + result = 'OK' + rx_check = (nib[nibbles + 1][3] + nib[nibbles][3]).upper() + details = '%s Calc %s Rx %s ' % (result, hex(checksum)[2:].upper(), + rx_check) + self.put(nib[nibbles][0], nib[nibbles + 1][1], + self.out_ann, [2, ['Checksum ' + details, result]]) + + def oregon_checksum(self, nibbles): + checksum = 0 + for i in range(nibbles): # Add reversed nibbles. + nibble = self.ookstring[i * 4 : i * 4 + 4] + nibble = nibble[::-1] # Reversed from right. + if 'E' in nibble: # Abort checksum if there are errors. + checksum = -1 + break + checksum += int(nibble, 2) + if checksum > 255: + checksum -= 255 # Make it roll over at 255. + chk_ver, comment = sensor_checksum.get(self.sensor_id, + ['Unknown', 'Unknown']) + if chk_ver != 'Unknown': + self.ver = chk_ver + if self.ver == 'v2.1': + checksum -= 10 # Subtract 10 from v2 checksums. + self.oregon_put_checksum(nibbles, checksum) + + def oregon_checksum_v1(self): + nib = self.decoded_nibbles + checksum = 0 + for i in range(3): # Add the first three bytes. + if nib[2 * i][3] == '' or nib[2 * i + 1][3] == '': # Abort if blank. + checksum = -1 + break + checksum += ((int(nib[2 * i][3], 16) & 0xF) << 4 | + (int(nib[2 * i + 1][3], 16) & 0xF)) + if checksum > 255: + checksum -= 255 # Make it roll over at 255. + self.oregon_put_checksum(6, checksum) + + def decode(self, ss, es, data): + self.decoded = data + self.oregon() diff --git a/libsigrokdecode4DSL/decoders/ook_vis/__init__.py b/libsigrokdecode4DSL/decoders/ook_vis/__init__.py new file mode 100644 index 00000000..f50f9ef8 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/ook_vis/__init__.py @@ -0,0 +1,25 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2018 Steve R +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This decoder stacks on top of the 'ook' PD and visualizes protocol details +in various ways. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/ook_vis/pd.py b/libsigrokdecode4DSL/decoders/ook_vis/pd.py new file mode 100644 index 00000000..f985b96f --- /dev/null +++ b/libsigrokdecode4DSL/decoders/ook_vis/pd.py @@ -0,0 +1,194 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2018 Steve R +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd +from common.srdhelper import bcd2int + +class Decoder(srd.Decoder): + api_version = 3 + id = 'ook_vis' + name = 'OOK visualisation' + longname = 'On-off keying visualisation' + desc = 'OOK visualisation in various formats.' + license = 'gplv2+' + inputs = ['ook'] + outputs = ['ook'] + tags = ['Encoding'] + annotations = ( + ('bit', 'Bit'), + ('ref', 'Reference'), + ('field', 'Field'), + ('ref_field', 'Ref field'), + ('level2', 'L2'), + ('ref_level2', 'Ref L2'), + ) + annotation_rows = ( + ('bits', 'Bits', (0,)), + ('compare', 'Compare', (1,)), + ('fields', 'Fields', (2,)), + ('ref_fields', 'Ref fields', (3,)), + ('level2', 'L2', (4,)), + ('ref_level2', 'Ref L2', (5,)), + ) + options = ( + {'id': 'displayas', 'desc': 'Display as', 'default': 'Nibble - Hex', + 'values': ('Byte - Hex', 'Byte - Hex rev', 'Byte - BCD', + 'Byte - BCD rev', 'Nibble - Hex', 'Nibble - Hex rev', 'Nibble - BCD', + 'Nibble - BCD rev')}, + {'id': 'synclen', 'desc': 'Sync length', 'default': '4', + 'values': ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10')}, + {'id': 'syncoffset', 'desc': 'Sync offset', 'default': '0', + 'values': ('-4', '-3', '-2', '-1', '0', '1', '2', '3', '4')}, + {'id': 'refsample', 'desc': 'Compare', 'default': 'off', 'values': + ('off', 'show numbers', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', + '21', '22', '23', '24', '25', '26', '27', '28', '29', '30')}, + ) + + def __init__(self): + self.reset() + + def reset(self): + self.decoded = [] # Local cache of decoded OOK. + self.ookstring = '' + self.ookcache = [] + self.trace_num = 0 + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + self.out_python = self.register(srd.OUTPUT_PYTHON) + self.displayas = self.options['displayas'] + self.sync_length = self.options['synclen'] + self.sync_offset = self.options['syncoffset'] + self.ref = self.options['refsample'] + + def putx(self, data): + self.put(self.ss, self.es, self.out_ann, data) + + def putp(self, data): + self.put(self.ss, self.es, self.out_python, data) + + def display_level2(self, bits, line): + self.decode_pos = 0 + ook = self.decoded + # Find the end of the preamble which could be 1010 or 1111. + if len(ook) > 1: + preamble_end = len(ook) + 1 + char_first = ook[0][2] + char_second = ook[1][2] + if char_first == char_second: # 1111 + preamble = '1111' + char_last = char_first + else: + preamble = '1010' + char_last = char_second + for i in range(len(ook)): + if preamble == '1111': + if ook[i][2] != char_last: + preamble_end = i + break + else: + char_last = ook[i][2] + else: + if ook[i][2] != char_last: + char_last = ook[i][2] + else: + preamble_end = i + break + + if len(ook) >= preamble_end: + preamble_end += int(self.sync_offset) - 1 + self.ss, self.es = ook[0][0], ook[preamble_end][1] + self.putx([line, ['Preamble', 'Pre', 'P']]) + self.decode_pos += preamble_end + + if len(ook) > self.decode_pos + int(self.sync_length): + self.ss = self.es + self.es = ook[self.decode_pos + int(self.sync_length)][1] + self.putx([line, ['Sync', 'Syn', 'S']]) + self.decode_pos += int(self.sync_length) + 1 + + ookstring = self.ookstring[self.decode_pos:] + rem_nibbles = len(ookstring) // bits + for i in range(rem_nibbles): # Display the rest of nibbles. + self.ss = ook[self.decode_pos][0] + self.es = ook[self.decode_pos + bits - 1][1] + self.put_field(bits, line) + + def put_field(self, numbits, line): + param = self.ookstring[self.decode_pos:self.decode_pos + numbits] + if 'rev' in self.displayas: + param = param[::-1] # Reversed from right. + if not 'E' in param: # Format if no errors. + if 'Hex' in self.displayas: + param = hex(int(param, 2))[2:] + elif 'BCD' in self.displayas: + param = bcd2int(int(param, 2)) + self.putx([line, [str(param)]]) + self.decode_pos += numbits + + def display_all(self): + ookstring = '' + self.decode_pos = 0 + ook = self.decoded + for i in range(len(ook)): + self.ookstring += ook[i][2] + bits = 4 if 'Nibble' in self.displayas else 8 + rem_nibbles = len(self.ookstring) // bits + for i in range(rem_nibbles): # Display the rest of the nibbles. + self.ss = ook[self.decode_pos][0] + self.es = ook[self.decode_pos + bits - 1][1] + self.put_field(bits, 2) + + self.display_level2(bits, 4) # Display L2 decode. + + if (self.ref != 'off' and self.ref != 'show numbers' and + len(self.ookcache) >= int(self.ref)): # Compare traces. + ref = int(self.ref) - 1 + self.display_ref(self.trace_num, ref) + if len(self.ookcache) == int(self.ref): # Backfill. + for i in range(0, ref): + self.display_ref(i, ref) + elif self.ref == 'show numbers': # Display ref numbers. + self.ss = self.ookcache[self.trace_num][0][0] + end_sig = len(self.ookcache[self.trace_num]) - 1 + self.es = self.ookcache[self.trace_num][end_sig][1] + self.putx([1, [str(self.trace_num + 1)]]) + + def display_ref(self, t_num, ref): + display_len = len(self.ookcache[ref]) + if len(self.ookcache[t_num]) < len(self.ookcache[ref]): + display_len = len(self.ookcache[t_num]) + for i in range(display_len): + self.ss = self.ookcache[t_num][i][0] + self.es = self.ookcache[t_num][i][1] + self.putx([1, [self.ookcache[ref][i][2]]]) + + def add_to_cache(self): # Cache the OOK so it can be used as a reference. + self.ookcache.append(self.decoded) + + def decode(self, ss, es, data): + self.decoded = data + self.add_to_cache() + self.display_all() + self.ookstring = '' + self.trace_num += 1 + self.ss = ss + self.es = es + self.putp(data) # Send data up the stack. diff --git a/libsigrokdecode4DSL/decoders/pan1321/__init__.py b/libsigrokdecode4DSL/decoders/pan1321/__init__.py new file mode 100644 index 00000000..428fc91f --- /dev/null +++ b/libsigrokdecode4DSL/decoders/pan1321/__init__.py @@ -0,0 +1,25 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2012 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This decoder stacks on top of the 'uart' PD and decodes the Panasonic PAN1321 +Bluetooth module Serial Port Profile (SPP) protocol. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/pan1321/pd.py b/libsigrokdecode4DSL/decoders/pan1321/pd.py new file mode 100644 index 00000000..6c931147 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/pan1321/pd.py @@ -0,0 +1,164 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2012-2013 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd + +# ... +RX = 0 +TX = 1 + +class Decoder(srd.Decoder): + api_version = 3 + id = 'pan1321' + name = 'PAN1321' + longname = 'Panasonic PAN1321' + desc = 'Bluetooth RF module with Serial Port Profile (SPP).' + license = 'gplv2+' + inputs = ['uart'] + outputs = [] + tags = ['Wireless/RF'] + annotations = ( + ('text-verbose', 'Human-readable text (verbose)'), + ('text', 'Human-readable text'), + ('warnings', 'Human-readable warnings'), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.cmd = ['', ''] + self.ss_block = None + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def putx(self, data): + self.put(self.ss_block, self.es_block, self.out_ann, data) + + def handle_host_command(self, rxtx, s): + if s.startswith('AT+JAAC'): + # AT+JAAC= (0 or 1) + p = s[s.find('=') + 1:] + if p not in ('0', '1'): + self.putx([2, ['Warning: Invalid JAAC parameter "%s"' % p]]) + return + x = 'Auto' if (p == '1') else 'Don\'t auto' + self.putx([0, ['%s-accept new connections' % x]]) + self.putx([1, ['%s-accept connections' % x]]) + elif s.startswith('AT+JPRO'): + # AT+JPRO= (0 or 1) + p = s[s.find('=') + 1:] + if p not in ('0', '1'): + self.putx([2, ['Warning: Invalid JPRO parameter "%s"' % p]]) + return + onoff = 'off' if (p == '0') else 'on' + x = 'Leaving' if (p == '0') else 'Entering' + self.putx([0, ['%s production mode' % x]]) + self.putx([1, ['Production mode = %s' % onoff]]) + elif s.startswith('AT+JRES'): + # AT+JRES + if s != 'AT+JRES': # JRES has no params. + self.putx([2, ['Warning: Invalid JRES usage.']]) + return + self.putx([0, ['Triggering a software reset']]) + self.putx([1, ['Reset']]) + elif s.startswith('AT+JSDA'): + # AT+JSDA=, (l: length in bytes, d: data) + # l is (max?) 3 decimal digits and ranges from 1 to MTU size. + # Data can be ASCII or binary values (l bytes total). + l, d = s[s.find('=') + 1:].split(',') + if not l.isnumeric(): + self.putx([2, ['Warning: Invalid data length "%s".' % l]]) + if int(l) != len(d): + self.putx([2, ['Warning: Data length mismatch (%d != %d).' % \ + (int(l), len(d))]]) + # TODO: Warn if length > MTU size (which is firmware-dependent + # and is negotiated by both Bluetooth devices upon connection). + b = ''.join(['%02x ' % ord(c) for c in d])[:-1] + self.putx([0, ['Sending %d data bytes: %s' % (int(l), b)]]) + self.putx([1, ['Send %d = %s' % (int(l), b)]]) + elif s.startswith('AT+JSEC'): + # AT+JSEC=,,,, + # secmode: Security mode 1 or 3 (default). + # linkkey_info: Must be 1 or 2. Has no function according to docs. + # pintype: 1: variable pin (default), 2: fixed pin. + # pinlen: PIN length (2 decimal digits). Max. PIN length is 16. + # pin: The Bluetooth PIN ('pinlen' chars). Used if pintype=2. + # Note: AT+JSEC (if used) must be the first command after reset. + # TODO: Parse all the other parameters. + pin = s[-4:] + self.putx([0, ['Host set the Bluetooth PIN to "' + pin + '"']]) + self.putx([1, ['PIN = ' + pin]]) + elif s.startswith('AT+JSLN'): + # AT+JSLN=, + # namelen: Friendly name length (2 decimal digits). Max. len is 18. + # name: The Bluetooth "friendly name" ('namelen' ASCII characters). + name = s[s.find(',') + 1:] + self.putx([0, ['Host set the Bluetooth name to "' + name + '"']]) + self.putx([1, ['BT name = ' + name]]) + else: + self.putx([0, ['Host sent unsupported command: %s' % s]]) + self.putx([1, ['Unsupported command: %s' % s]]) + + def handle_device_reply(self, rxtx, s): + if s == 'ROK': + self.putx([0, ['Device initialized correctly']]) + self.putx([1, ['Init']]) + elif s == 'OK': + self.putx([0, ['Device acknowledged last command']]) + self.putx([1, ['ACK']]) + elif s.startswith('ERR'): + error = s[s.find('=') + 1:] + self.putx([0, ['Device sent error code ' + error]]) + self.putx([1, ['ERR = ' + error]]) + else: + self.putx([0, ['Device sent an unknown reply: %s' % s]]) + self.putx([1, ['Unknown reply: %s' % s]]) + + def decode(self, ss, es, data): + ptype, rxtx, pdata = data + + # For now, ignore all UART packets except the actual data packets. + if ptype != 'DATA': + return + + # We're only interested in the byte value (not individual bits). + pdata = pdata[0] + + # If this is the start of a command/reply, remember the start sample. + if self.cmd[rxtx] == '': + self.ss_block = ss + + # Append a new (ASCII) byte to the currently built/parsed command. + self.cmd[rxtx] += chr(pdata) + + # Get packets/bytes until an \r\n sequence is found (end of command). + if self.cmd[rxtx][-2:] != '\r\n': + return + + # Handle host commands and device replies. + # We remove trailing \r\n from the strings before handling them. + self.es_block = es + if rxtx == RX: + self.handle_device_reply(rxtx, self.cmd[rxtx][:-2]) + elif rxtx == TX: + self.handle_host_command(rxtx, self.cmd[rxtx][:-2]) + + self.cmd[rxtx] = '' diff --git a/libsigrokdecode4DSL/decoders/parallel/__init__.py b/libsigrokdecode4DSL/decoders/parallel/__init__.py new file mode 100644 index 00000000..100523ec --- /dev/null +++ b/libsigrokdecode4DSL/decoders/parallel/__init__.py @@ -0,0 +1,34 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2013 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This protocol decoder can decode synchronous parallel buses with various +number of data bits/channels and one (optional) clock line. + +If no clock line is supplied, the decoder works slightly differently in +that it interprets every transition on any of the supplied data channels +like there had been a clock transition. + +It is required to use the lowest data channels, and use consecutive ones. +For example, for a 4-bit sync parallel bus, channels D0/D1/D2/D3 (and CLK) +should be used. Using combinations like D7/D12/D3/D15 is not supported. +For an 8-bit bus you should use D0-D7, for a 16-bit bus use D0-D15 and so on. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/parallel/pd.py b/libsigrokdecode4DSL/decoders/parallel/pd.py new file mode 100644 index 00000000..d7544c16 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/parallel/pd.py @@ -0,0 +1,213 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2013-2016 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd +from common.srdhelper import bitpack + +''' +OUTPUT_PYTHON format: + +Packet: +[, ] + +, + - 'ITEM', [, ] + - 'WORD', [, , ] + +: + - A single item (a number). It can be of arbitrary size. The max. number + of bits in this item is specified in . + +: + - The size of an item (in bits). For a 4-bit parallel bus this is 4, + for a 16-bit parallel bus this is 16, and so on. + +: + - A single word (a number). It can be of arbitrary size. The max. number + of bits in this word is specified in . The (exact) number + of items in this word is specified in . + +: + - The size of a word (in bits). For a 2-item word with 8-bit items + is 16, for a 3-item word with 4-bit items + is 12, and so on. + +: + - The size of a word (in number of items). For a 4-item word (no matter + how many bits each item consists of) is 4, for a 7-item + word is 7, and so on. +''' + +def channel_list(num_channels): + l = [{'id': 'clk', 'name': 'CLK', 'desc': 'Clock line'}] + for i in range(num_channels): + d = {'id': 'd%d' % i, 'name': 'D%d' % i, 'desc': 'Data line %d' % i} + l.append(d) + return tuple(l) + +class ChannelError(Exception): + pass + +NUM_CHANNELS = 8 + +class Decoder(srd.Decoder): + api_version = 3 + id = 'parallel' + name = 'Parallel' + longname = 'Parallel sync bus' + desc = 'Generic parallel synchronous bus.' + license = 'gplv2+' + inputs = ['logic'] + outputs = ['parallel'] + tags = ['Util'] + optional_channels = channel_list(NUM_CHANNELS) + options = ( + {'id': 'clock_edge', 'desc': 'Clock edge to sample on', + 'default': 'rising', 'values': ('rising', 'falling')}, + {'id': 'wordsize', 'desc': 'Data wordsize (# bus cycles)', + 'default': 0}, + {'id': 'endianness', 'desc': 'Data endianness', + 'default': 'little', 'values': ('little', 'big')}, + ) + annotations = ( + ('items', 'Items'), + ('words', 'Words'), + ) + annotation_rows = ( + ('items', 'Items', (0,)), + ('words', 'Words', (1,)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.items = [] + self.saved_item = None + self.ss_item = self.es_item = None + self.saved_word = None + self.ss_word = self.es_word = None + self.first = True + + def start(self): + self.out_python = self.register(srd.OUTPUT_PYTHON) + self.out_ann = self.register(srd.OUTPUT_ANN) + + def putpb(self, data): + self.put(self.ss_item, self.es_item, self.out_python, data) + + def putb(self, data): + self.put(self.ss_item, self.es_item, self.out_ann, data) + + def putpw(self, data): + self.put(self.ss_word, self.es_word, self.out_python, data) + + def putw(self, data): + self.put(self.ss_word, self.es_word, self.out_ann, data) + + def handle_bits(self, item, used_pins): + + # If a word was previously accumulated, then emit its annotation + # now after its end samplenumber became available. + if self.saved_word is not None: + if self.options['wordsize'] > 0: + self.es_word = self.samplenum + self.putw([1, [self.fmt_word.format(self.saved_word)]]) + self.putpw(['WORD', self.saved_word]) + self.saved_word = None + + # Defer annotations for individual items until the next sample + # is taken, and the previous sample's end samplenumber has + # become available. + if self.first: + # Save the start sample and item for later (no output yet). + self.ss_item = self.samplenum + self.first = False + self.saved_item = item + else: + # Output the saved item (from the last CLK edge to the current). + self.es_item = self.samplenum + self.putpb(['ITEM', self.saved_item]) + self.putb([0, [self.fmt_item.format(self.saved_item)]]) + self.ss_item = self.samplenum + self.saved_item = item + + # Get as many items as the configured wordsize specifies. + if not self.items: + self.ss_word = self.samplenum + self.items.append(item) + ws = self.options['wordsize'] + if len(self.items) < ws: + return + + # Collect words and prepare annotation details, but defer emission + # until the end samplenumber becomes available. + endian = self.options['endianness'] + if endian == 'big': + self.items.reverse() + word = sum([self.items[i] << (i * used_pins) for i in range(ws)]) + self.saved_word = word + self.items = [] + + def decode(self): + # Determine which (optional) channels have input data. Insist in + # a non-empty input data set. Cope with sparse connection maps. + # Store enough state to later "compress" sampled input data. + max_possible = len(self.optional_channels) + idx_channels = [ + idx if self.has_channel(idx) else None + for idx in range(max_possible) + ] + has_channels = [idx for idx in idx_channels if idx is not None] + if not has_channels: + raise ChannelError('At least one channel has to be supplied.') + max_connected = max(has_channels) + + # Determine .wait() conditions, depending on the presence of a + # clock signal. Either inspect samples on the configured edge of + # the clock, or inspect samples upon ANY edge of ANY of the pins + # which provide input data. + if self.has_channel(0): + edge = self.options['clock_edge'][0] + conds = {0: edge} + else: + conds = [{idx: 'e'} for idx in has_channels] + + # Pre-determine which input data to strip off, the width of + # individual items and multiplexed words, as well as format + # strings here. This simplifies call sites which run in tight + # loops later. + idx_strip = max_connected + 1 + num_item_bits = idx_strip - 1 + num_word_items = self.options['wordsize'] + num_word_bits = num_item_bits * num_word_items + num_digits = (num_item_bits + 3) // 4 + self.fmt_item = "{{:0{}x}}".format(num_digits) + num_digits = (num_word_bits + 3) // 4 + self.fmt_word = "{{:0{}x}}".format(num_digits) + + # Keep processing the input stream. Assume "always zero" for + # not-connected input lines. Pass data bits (all inputs except + # clock) to the handle_bits() method. + while True: + (clk, d0, d1, d2, d3, d4, d5, d6, d7) = self.wait(conds) + pins = (clk, d0, d1, d2, d3, d4, d5, d6, d7) + bits = [0 if idx is None else pins[idx] for idx in idx_channels] + item = bitpack(bits[1:idx_strip]) + self.handle_bits(item, num_item_bits) diff --git a/libsigrokdecode4DSL/decoders/pca9571/__init__.py b/libsigrokdecode4DSL/decoders/pca9571/__init__.py new file mode 100644 index 00000000..28152d93 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/pca9571/__init__.py @@ -0,0 +1,25 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2019 Mickael Bosch +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This decoder stacks on top of the 'i2c' PD and decodes the NXP Semiconductors +PCA9571 8-bit I²C output expander protocol. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/pca9571/pd.py b/libsigrokdecode4DSL/decoders/pca9571/pd.py new file mode 100644 index 00000000..df309e91 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/pca9571/pd.py @@ -0,0 +1,102 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2019 Mickael Bosch +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd + +NUM_OUTPUT_CHANNELS = 8 + +# TODO: Other I²C functions: general call / reset address, device ID address. + +class Decoder(srd.Decoder): + api_version = 3 + id = 'pca9571' + name = 'PCA9571' + longname = 'NXP PCA9571' + desc = 'NXP PCA9571 8-bit I²C output expander.' + license = 'gplv2+' + inputs = ['i2c'] + outputs = [] + tags = ['Embedded/industrial', 'IC'] + annotations = ( + ('register', 'Register type'), + ('value', 'Register value'), + ('warning', 'Warning messages'), + ) + annotation_rows = ( + ('regs', 'Registers', (0, 1)), + ('warnings', 'Warnings', (2,)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.state = 'IDLE' + self.last_write = 0xFF # Chip port default state is high. + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def putx(self, data): + self.put(self.ss, self.es, self.out_ann, data) + + def handle_io(self, b): + if self.state == 'READ DATA': + operation = ['Outputs read', 'R'] + if b != self.last_write: + self.putx([2, ['Warning: read value and last write value ' + '(%02X) are different' % self.last_write]]) + else: + operation = ['Outputs set', 'W'] + self.last_write = b + self.putx([1, [operation[0] + ': %02X' % b, + operation[1] + ': %02X' % b]]) + + def check_correct_chip(self, addr): + if addr != 0x25: + self.putx([2, ['Warning: I²C slave 0x%02X not a PCA9571 ' + 'compatible chip.' % addr]]) + return False + return True + + def decode(self, ss, es, data): + cmd, databyte = data + self.ss, self.es = ss, es + + # State machine. + if cmd in ('ACK', 'BITS'): # Discard 'ACK' and 'BITS'. + pass + elif cmd in ('START', 'START REPEAT'): # Start a communication. + self.state = 'GET SLAVE ADDR' + elif cmd in ('NACK', 'STOP'): # Reset the state machine. + self.state = 'IDLE' + elif cmd in ('ADDRESS READ', 'ADDRESS WRITE'): + if ((self.state == 'GET SLAVE ADDR') and + self.check_correct_chip(databyte)): + if cmd == 'ADDRESS READ': + self.state = 'READ DATA' + else: + self.state = 'WRITE DATA' + else: + self.state = 'IDLE' + elif cmd in ('DATA READ', 'DATA WRITE'): + if self.state in ('READ DATA', 'WRITE DATA'): + self.handle_io(databyte) + else: + self.state = 'IDLE' diff --git a/libsigrokdecode4DSL/decoders/pjdl/__init__.py b/libsigrokdecode4DSL/decoders/pjdl/__init__.py new file mode 100644 index 00000000..c3cc8559 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/pjdl/__init__.py @@ -0,0 +1,27 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2020 Gerhard Sittig +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This protocol decoder interprets the PJDL data link of the PJON protocol. +Bytes and frames get extracted from single wire serial communication +(which often is referred to as "software bitbang" because that's what +the Arduino reference implementation happens to do). +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/pjdl/pd.py b/libsigrokdecode4DSL/decoders/pjdl/pd.py new file mode 100644 index 00000000..d5cc39f2 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/pjdl/pd.py @@ -0,0 +1,723 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2020 Gerhard Sittig +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +# See the https://www.pjon.org/ PJON project page and especially the +# https://www.pjon.org/PJDL-specification-v4.1.php PJDL v4.1 spec for +# the "Padded Jittering Data Link" single wire serial data link layer. + +# TODO +# - Improve (fix, and extend) carrier sense support. Detection of the +# idle/busy connection state is incomplete and fragile. Getting 'IDLE' +# operational in the PJDL decoder would greatly help the PJON decoder +# to flush ACK details before the start of new frames. +# - Check the correctness of timing assumptions. This implementation has +# support for tolerances, which the spec does not discuss. Though real +# world traffic was found to not decode at all with strict spec values +# and without tolerances, while communication peers were able to talk +# to each other. This needs more attention. +# - Check robustness when input data contains glitches. The spec does +# not discuss how to handle these. Data bit sampling happens to work +# because their value is taken at the center of the bit time. But +# pad bits suffer badly from glitches, which breaks frame inspection +# as well. +# - Cleanup the decoder implementation in general terms. Some details +# have become obsolete ("edges", "pads"), and/or are covered by other +# code paths. +# - Implement more data link decoders which can feed their output into +# the PJON protocol decoder. Candidates are: PJDLR, PJDLS, TSDL. +# - Determine whether or not the data link layer should interpret any +# frame content. From my perspective it should not, and needs not in +# the strict sense. Possible gains would be getting the (expected!) +# packet length, or whether a synchronous response is requested. But +# this would duplicate knowledge which should remain internal to the +# PJON decoder. And this link layer decoder neither shall assume that +# the input data would be correct, or complete. Instead the decoder +# shall remain usable on captures which demonstrate faults, and be +# helpful in pointing them out. The design goal is to extract the +# maximum of information possible, and pass it on as transparently +# as possible. + +import sigrokdecode as srd +from common.srdhelper import bitpack +from math import ceil, floor + +''' +OUTPUT_PYTHON format for stacked decoders: + +General packet format: +[, ] + +This is the list of s and their respective values: + +Carrier sense: +- 'IDLE': is the pin level (always 0). +- 'BUSY': is always True. + +Raw bit slots: +- 'PAD_BIT': is the pin level (always 1). +- 'DATA_BIT': is the pin level (0, or 1). +- 'SHORT_BIT': is the pin level (always 1). +- 'SYNC_LOSS': is an arbitrary text (internal use only). + +Date bytes and frames: +- 'SYNC_PAD': is True. Spans the high pad bit as well as the + low data bit. +- 'DATA_BYTE': is the byte value (0..255). +- 'FRAME_INIT': is True. Spans three sync pads. +- 'FRAME_DATA': is the sequence of bytes in the frame. Non-data + phases in the frame get represented by strings instead of numbers + ('INIT', 'SYNC', 'SHORT', 'WAIT'). Frames can be incomplete, depending + on the decoder's input data. +- 'SYNC_RESP_WAIT': is always True. + +Notice that this link layer decoder is not aware of frame content. Will +neither check packet length, nor variable width fields, nor verify the +presence of requested synchronous responses. Cannot tell the sequence of +frame bytes then ACK bytes (without wait phase) from just frame bytes. +An upper layer protocol decoder will interpret content, the link layer +decoder remains as transparent as possible, and will neither assume +correct nor complete input data. +''' + +# Carrier sense, and synchronization loss implementation is currently +# incomplete, and results in too many too short annotations, some of +# them spurious and confusing. TODO Improve the implementation. +_with_ann_carrier = False +_with_ann_sync_loss = False + +PIN_DATA, = range(1) +ANN_CARRIER_BUSY, ANN_CARRIER_IDLE, \ +ANN_PAD_BIT, ANN_LOW_BIT, ANN_DATA_BIT, ANN_SHORT_DATA, ANN_SYNC_LOSS, \ +ANN_DATA_BYTE, \ +ANN_FRAME_INIT, ANN_FRAME_BYTES, ANN_FRAME_WAIT, \ + = range(11) + +class SamplerateError(Exception): + pass + +class Decoder(srd.Decoder): + api_version = 3 + id = 'pjdl' + name = 'PJDL' + longname = 'Padded Jittering Data Link' + desc = 'PJDL, a single wire serial link layer for PJON.' + license = 'gplv2+' + inputs = ['logic'] + outputs = ['pjon_link'] + tags = ['Embedded/industrial'] + channels = ( + {'id': 'data' , 'name': 'DATA', 'desc': 'Single wire data'}, + ) + options = ( + {'id': 'mode', 'desc': 'Communication mode', + 'default': 1, 'values': (1, 2, 3, 4)}, + {'id': 'idle_add_us', 'desc': 'Added idle time (us)', 'default': 4}, + ) + annotations = ( + ('cs_busy', 'Carrier busy'), + ('cs_idle', 'Carrier idle'), + ('bit_pad', 'Pad bit'), + ('bit_low', 'Low bit'), + ('bit_data', 'Data bit'), + ('bit_short', 'Short data'), + ('sync_loss', 'Sync loss'), + ('byte', 'Data byte'), + ('frame_init', 'Frame init'), + ('frame_bytes', 'Frame bytes'), + ('frame_wait', 'Frame wait'), + ) + annotation_rows = ( + ('carriers', 'Carriers', (ANN_CARRIER_BUSY, ANN_CARRIER_IDLE,)), + ('bits', 'Bits', (ANN_PAD_BIT, ANN_LOW_BIT, ANN_DATA_BIT, ANN_SHORT_DATA,)), + ('bytes', 'Bytes', (ANN_FRAME_INIT, ANN_DATA_BYTE, ANN_FRAME_WAIT,)), + ('frames', 'Frames', (ANN_FRAME_BYTES,)), + ('warns', 'Warnings', (ANN_SYNC_LOSS,)), + ) + + # Communication modes' data bit and pad bit duration (in us), and + # tolerances in percent and absolute (us). + mode_times = { + 1: (44, 116), + 2: (40, 92), + 3: (28, 88), + 4: (26, 60), + } + time_tol_perc = 10 + time_tol_abs = 1.5 + + def __init__(self): + self.reset() + + def reset(self): + self.reset_state() + + def reset_state(self): + self.carrier_want_idle = True + self.carrier_is_busy = False + self.carrier_is_idle = False + self.carrier_idle_ss = None + self.carrier_busy_ss = None + self.syncpad_fall_ss = None + + self.edges = None + self.symbols = None + self.sync_pads = None + self.data_bits = None + self.frame_bytes = None + self.short_bits = None + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + self.out_python = self.register(srd.OUTPUT_PYTHON) + + def metadata(self, key, value): + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + self.span_prepare() + + def putg(self, ss, es, data): + cls = data[0] + if not _with_ann_carrier and cls in (ANN_CARRIER_BUSY, ANN_CARRIER_IDLE): + return + if not _with_ann_sync_loss and cls in (ANN_SYNC_LOSS,): + return + self.put(ss, es, self.out_ann, data) + + def putpy(self, ss, es, ptype, pdata): + self.put(ss, es, self.out_python, [ptype, pdata]) + + def symbols_clear(self): + syms = self.symbols or [] + self.symbols = [] + return syms + + def symbols_append(self, ss, es, symbol, data = None): + if self.symbols is None: + self.symbols = [] + item = (ss, es, symbol, data) + self.symbols.append(item) + + def symbols_get_last(self, count = None): + if not self.symbols: + return None + if count is None: + count = 1 + if len(self.symbols) < count: + return None + items = self.symbols[-count:] + if count == 1: + items = items[0] + return items + + def symbols_update_last(self, ss, es, symbol, data = None): + if not self.symbols: + return None + item = list(self.symbols[-1]) + if ss is not None: + item[0] = ss + if es is not None: + item[1] = es + if symbol is not None: + item[2] = symbol + if data is not None: + item[3] = data + self.symbols[-1] = tuple(item) + + def symbols_has_prev(self, want_items): + if not isinstance(want_items, (list, tuple,)): + want_items = [want_items] + if self.symbols is None: + return False + if len(self.symbols) < len(want_items): + return False + sym_off = len(self.symbols) - len(want_items) + for idx, want_item in enumerate(want_items): + if self.symbols[sym_off + idx][2] != want_item: + return False + return True + + def symbols_collapse(self, count, symbol, data = None, squeeze = None): + if self.symbols is None: + return None + if len(self.symbols) < count: + return None + self.symbols, last_data = self.symbols[:-count], self.symbols[-count:] + while squeeze and self.symbols and self.symbols[-1][2] == squeeze: + last_data.insert(0, self.symbols.pop()) + ss, es = last_data[0][0], last_data[-1][1] + if data is None: + data = last_data + item = (ss, es, symbol, data) + self.symbols.append(item) + + def frame_flush(self): + syms = self.symbols_clear() + while syms and syms[0][2] == 'IDLE': + syms.pop(0) + while syms and syms[-1][2] == 'IDLE': + syms.pop(-1) + if not syms: + return + text = [] + data = [] + for sym in syms: + if sym[2] == 'FRAME_INIT': + text.append('INIT') + data.append('INIT') + continue + if sym[2] == 'SYNC_PAD': + if not text or text[-1] != 'SYNC': + text.append('SYNC') + data.append('SYNC') + continue + if sym[2] == 'DATA_BYTE': + b = [bit[3] for bit in sym[3] if bit[2] == 'DATA_BIT'] + b = bitpack(b) + text.append('{:02x}'.format(b)) + data.append(b) + continue + if sym[2] == 'SHORT_BIT': + if not text or text[-1] != 'SHORT': + text.append('SHORT') + data.append('SHORT') + continue + if sym[2] == 'WAIT_ACK': + text.append('WAIT') + data.append('WAIT') + continue + text = ' '.join(text) + ss, es = syms[0][0], syms[-1][1] + self.putg(ss, es, [ANN_FRAME_BYTES, [text]]) + self.putpy(ss, es, 'FRAME_DATA', data) + + def carrier_flush(self): + # Force annotations if BUSY started, or if IDLE tracking started + # and kept running for long enough. This will be called before + # internal state reset, so we won't manipulate internal variables, + # and can afford to emit annotations which haven't met their + # proper end condition yet. + if self.carrier_busy_ss: + ss, es = self.carrier_busy_ss, self.samplenum + self.putg(ss, es, [ANN_CARRIER_BUSY, ['BUSY']]) + if self.carrier_idle_ss: + ss, es = self.carrier_idle_ss, self.samplenum + ss += int(self.idle_width) + if ss < es: + self.putg(ss, es, [ANN_CARRIER_IDLE, ['IDLE']]) + + def carrier_set_idle(self, on, ss, es): + if on: + # IDLE starts here, or continues. + if not self.carrier_idle_ss: + self.carrier_idle_ss = int(ss) + if not self.symbols_has_prev('IDLE'): + self.symbols_append(ss, ss, 'IDLE') + self.symbols_update_last(None, es, None) + # HACK We have seen an IDLE condition. This implementation + # loses details which are used to track IDLE, but it's more + # important to start accumulation of a new frame here. + self.frame_flush() + self.reset_state() + # end of HACK + self.carrier_is_idle = True + self.carrier_want_idle = False + return + # IDLE ends here. + if self.symbols_has_prev('IDLE'): + self.symbols_update_last(None, es, None) + self.carrier_flush() + self.carrier_is_idle = False + self.carrier_idle_ss = None + + def carrier_set_busy(self, on, snum): + self.carrier_is_busy = on + if on: + self.carrier_is_idle = None + if not self.carrier_busy_ss: + self.carrier_busy_ss = snum + return + if self.carrier_busy_ss: + self.putg(self.carrier_busy_ss, snum, [ANN_CARRIER_BUSY, ['BUSY']]) + self.carrier_busy_ss = None + self.carrier_is_busy = False + + def carrier_check(self, level, snum): + + # When HIGH is seen, immediately end IDLE and switch to BUSY. + if level: + self.carrier_set_idle(False, snum, snum) + self.carrier_set_busy(True, snum) + return + + # LOW is seen. Start tracking an IDLE period if not done yet. + if not self.carrier_idle_ss: + self.carrier_idle_ss = int(snum) + + # End BUSY when LOW persisted for an exact data byte's length. + # Start IDLE when LOW persisted for a data byte's length plus + # the user specified additional period. + span = snum - self.carrier_idle_ss + if span >= self.byte_width: + self.carrier_set_busy(False, snum) + if span >= self.idle_width: + self.carrier_set_idle(True, self.carrier_idle_ss + self.idle_width, snum) + + def span_prepare(self): + '''Prepare calculation of durations in terms of samples.''' + + # Determine samples per microsecond, and sample counts for + # several bit types, and sample count for a data byte's + # length, including optional extra time. Determine ranges + # for bit widths (tolerance margin). + + # Get times in microseconds. + mode_times = self.mode_times[self.options['mode']] + mode_times = [t * 1.0 for t in mode_times] + self.data_width, self.pad_width = mode_times + self.byte_width = self.pad_width + 9 * self.data_width + self.add_idle_width = self.options['idle_add_us'] + self.idle_width = self.byte_width + self.add_idle_width + + # Derive ranges (add tolerance) and scale to sample counts. + self.usec_width = self.samplerate / 1e6 + self.hold_high_width = 9 * self.time_tol_abs * self.usec_width + + def _get_range(width): + reladd = self.time_tol_perc / 100 + absadd = self.time_tol_abs + lower = min(width * (1 - reladd), width - absadd) + upper = max(width * (1 + reladd), width + absadd) + lower = floor(lower * self.usec_width) + upper = ceil(upper * self.usec_width) + return (lower, upper + 1) + + self.data_bit_1_range = _get_range(self.data_width * 1) + self.data_bit_2_range = _get_range(self.data_width * 2) + self.data_bit_3_range = _get_range(self.data_width * 3) + self.data_bit_4_range = _get_range(self.data_width * 4) + self.short_data_range = _get_range(self.data_width / 4) + self.pad_bit_range = _get_range(self.pad_width) + + self.data_width *= self.usec_width + self.pad_width *= self.usec_width + self.byte_width *= self.usec_width + self.idle_width *= self.usec_width + + self.lookahead_width = int(4 * self.data_width) + + def span_snum_to_us(self, count): + return count / self.usec_width + + def span_is_pad(self, span): + return span in range(*self.pad_bit_range) + + def span_is_data(self, span): + if span in range(*self.data_bit_1_range): + return 1 + if span in range(*self.data_bit_2_range): + return 2 + if span in range(*self.data_bit_3_range): + return 3 + if span in range(*self.data_bit_4_range): + return 4 + return False + + def span_is_short(self, span): + return span in range(*self.short_data_range) + + def wait_until(self, want): + '''Wait until a given location, but keep sensing carrier.''' + + # Implementor's note: Avoids skip values below 1. This version + # "may overshoot" by one sample. Which should be acceptable for + # this specific use case (can put the sample point of a bit time + # out of the center by some 4% under worst case conditions). + + want = int(want) + while True: + diff = max(want - self.samplenum, 1) + pins = self.wait([{PIN_DATA: 'e'}, {'skip': diff}]) + self.carrier_check(pins[PIN_DATA], self.samplenum) + if self.samplenum >= want: + return pins + # UNREACH + + def decode(self): + if not self.samplerate or self.samplerate < 1e6: + raise SamplerateError('Need a samplerate of at least 1MSa/s') + + # As a special case the first low period in the input capture is + # saught regardless of whether we can see its falling edge. This + # approach is also used to recover after synchronization was lost. + # + # The important condition here in the main loop is: Get the next + # edge's position, but time out after a maximum period of four + # data bits. This allows for the detection of SYNC pulses, also + # responds "soon enough" to DATA bits where edges can be few + # within a data byte. Also avoids excessive waits for unexpected + # communication errors. + # + # DATA bits within a byte are taken at fixed intervals relative + # to the SYNC-PAD's falling edge. It's essential to check the + # carrier at every edge, also during DATA bit sampling. Simple + # skips to the desired sample point could break that feature. + while True: + + # Help kick-start the IDLE condition detection after + # decoder state reset. + if not self.edges: + curr_level, = self.wait({PIN_DATA: 'l'}) + self.carrier_check(curr_level, self.samplenum) + self.edges = [self.samplenum] + continue + + # Advance to the next edge, or over a medium span without an + # edge. Prepare to classify the distance to derive bit types + # from these details. + last_snum = self.samplenum + curr_level, = self.wait([{PIN_DATA: 'e'}, {'skip': self.lookahead_width}]) + self.carrier_check(curr_level, self.samplenum) + bit_level = curr_level + edge_seen = self.matched[0] + if edge_seen: + bit_level = 1 - bit_level + if not self.edges: + self.edges = [self.samplenum] + continue + self.edges.append(self.samplenum) + curr_snum = self.samplenum + + # Check bit width (can also be multiple data bits). + span = self.edges[-1] - self.edges[-2] + is_pad = bit_level and self.span_is_pad(span) + is_data = self.span_is_data(span) + is_short = bit_level and self.span_is_short(span) + + if is_pad: + # BEWARE! Use ss value of last edge (genuinely seen, or + # inserted after a DATA byte) for PAD bit annotations. + ss, es = self.edges[-2], curr_snum + texts = ['PAD', '{:d}'.format(bit_level)] + self.putg(ss, es, [ANN_PAD_BIT, texts]) + self.symbols_append(ss, es, 'PAD_BIT', bit_level) + ss, es = self.symbols_get_last()[:2] + self.putpy(ss, es, 'PAD_BIT', bit_level) + continue + + if is_short: + ss, es = last_snum, curr_snum + texts = ['SHORT', '{:d}'.format(bit_level)] + self.putg(ss, es, [ANN_SHORT_DATA, texts]) + self.symbols_append(ss, es, 'SHORT_BIT', bit_level) + ss, es = self.symbols_get_last()[:2] + self.putpy(ss, es, 'SHORT_BIT', bit_level) + continue + + # Force IDLE period check when the decoder seeks to sync + # to the input data stream. + if not bit_level and not self.symbols and self.carrier_want_idle: + continue + + # Accept arbitrary length LOW phases after DATA bytes(!) or + # SHORT pulses, but not within a DATA byte or SYNC-PAD etc. + # This covers the late start of the next SYNC-PAD (byte of + # a frame, or ACK byte after a frame, or the start of the + # next frame). + if not bit_level: + if self.symbols_has_prev('DATA_BYTE'): + continue + if self.symbols_has_prev('SHORT_BIT'): + continue + if self.symbols_has_prev('WAIT_ACK'): + continue + + # Get (consume!) the LOW DATA bit after a PAD. + took_low = False + if is_data and not bit_level and self.symbols_has_prev('PAD_BIT'): + took_low = True + is_data -= 1 + next_snum = int(last_snum + self.data_width) + ss, es = last_snum, next_snum + texts = ['ZERO', '{:d}'.format(bit_level)] + self.putg(ss, es, [ANN_LOW_BIT, texts]) + self.symbols_append(ss, es, 'ZERO_BIT', bit_level) + ss, es = self.symbols_get_last()[:2] + self.putpy(ss, es, 'DATA_BIT', bit_level) + self.data_fall_time = last_snum + last_snum = next_snum + # Turn the combination of PAD and LOW DATA into SYNC-PAD. + # Start data bit accumulation after a SYNC-PAD was seen. + sync_pad_seq = ['PAD_BIT', 'ZERO_BIT'] + if self.symbols_has_prev(sync_pad_seq): + self.symbols_collapse(len(sync_pad_seq), 'SYNC_PAD') + ss, es = self.symbols_get_last()[:2] + self.putpy(ss, es, 'SYNC_PAD', True) + self.data_bits = [] + # Turn three subsequent SYNC-PAD into FRAME-INIT. Start the + # accumulation of frame bytes when FRAME-INIT was seen. + frame_init_seq = 3 * ['SYNC_PAD'] + if self.symbols_has_prev(frame_init_seq): + self.symbols_collapse(len(frame_init_seq), 'FRAME_INIT') + # Force a flush of the previous frame after we have + # reliably detected the start of another one. This is a + # workaround for this decoder's inability to detect the + # end of a frame after an ACK was seen or byte counts + # have been reached. We cannot assume perfect input, + # thus we leave all interpretation of frame content to + # upper layers. Do keep the recently queued FRAME_INIT + # symbol across the flush operation. + if len(self.symbols) > 1: + keep = self.symbols.pop(-1) + self.frame_flush() + self.symbols.clear() + self.symbols.append(keep) + ss, es = self.symbols_get_last()[:2] + texts = ['FRAME INIT', 'INIT', 'I'] + self.putg(ss, es, [ANN_FRAME_INIT, texts]) + self.putpy(ss, es, 'FRAME_INIT', True) + self.frame_bytes = [] + # Collapse SYNC-PAD after SHORT+ into a WAIT-ACK. Include + # all leading SHORT bits in the WAIT as well. + wait_ack_seq = ['SHORT_BIT', 'SYNC_PAD'] + if self.symbols_has_prev(wait_ack_seq): + self.symbols_collapse(len(wait_ack_seq), 'WAIT_ACK', + squeeze = 'SHORT_BIT') + ss, es = self.symbols_get_last()[:2] + texts = ['WAIT for sync response', 'WAIT response', 'WAIT', 'W'] + self.putg(ss, es, [ANN_FRAME_WAIT, texts]) + self.putpy(ss, es, 'SYNC_RESP_WAIT', True) + if took_low and not is_data: + # Start at the very next edge if we just consumed a LOW + # after a PAD bit, and the DATA bit count is exhausted. + # This improves robustness, deals with inaccurate edge + # positions. (Motivated by real world captures, the spec + # would not discuss bit time tolerances.) + continue + + # When we get here, the only remaining (the only supported) + # activity is the collection of a data byte's DATA bits. + # These are not taken by the main loop's "edge search, with + # a timeout" approach, which is "too tolerant". Instead all + # DATA bits get sampled at a fixed interval and relative to + # the SYNC-PAD's falling edge. We expect to have seen the + # data byte' SYNC-PAD before. If we haven't, the decoder is + # not yet synchronized to the input data. + if not is_data: + fast_cont = edge_seen and curr_level + ss, es = last_snum, curr_snum + texts = ['failed pulse length check', 'pulse length', 'length'] + self.putg(ss, es, [ANN_SYNC_LOSS, texts]) + self.frame_flush() + self.carrier_flush() + self.reset_state() + if fast_cont: + self.edges = [self.samplenum] + continue + if not self.symbols_has_prev('SYNC_PAD'): + # Fast reponse to the specific combination of: no-sync, + # edge seen, and current high level. In this case we + # can reset internal state, but also can continue the + # interpretation right after the most recently seen + # rising edge, which could start the next PAD time. + # Otherwise continue slow interpretation after reset. + fast_cont = edge_seen and curr_level + self.frame_flush() + self.carrier_flush() + self.reset_state() + if fast_cont: + self.edges = [self.samplenum] + continue + + # The main loop's "edge search with period timeout" approach + # can have provided up to three more DATA bits after the LOW + # bit of the SYNC-PAD. Consume them immediately in that case, + # otherwise .wait() for their sample point. Stick with float + # values for bit sample points and bit time boundaries for + # improved accuracy, only round late to integers when needed. + bit_field = [] + bit_ss = self.data_fall_time + self.data_width + for bit_idx in range(8): + bit_es = bit_ss + self.data_width + bit_snum = (bit_es + bit_ss) / 2 + if bit_snum > self.samplenum: + bit_level, = self.wait_until(bit_snum) + ss, es = ceil(bit_ss), floor(bit_es) + texts = ['{:d}'.format(bit_level)] + self.putg(ss, es, [ANN_DATA_BIT, texts]) + self.symbols_append(ss, es, 'DATA_BIT', bit_level) + ss, es = self.symbols_get_last()[:2] + self.putpy(ss, es, 'DATA_BIT', bit_level) + bit_field.append(bit_level) + if self.data_bits is not None: + self.data_bits.append(bit_level) + bit_ss = bit_es + end_snum = bit_es + curr_level, = self.wait_until(end_snum) + curr_snum = self.samplenum + + # We are at the exact _calculated_ boundary of the last DATA + # bit time. Improve robustness for those situations where + # the transmitter's and the sender's timings differ within a + # margin, and the transmitter may hold the last DATA bit's + # HIGH level for a little longer. + # + # When no falling edge is seen within the maximum tolerance + # for the last DATA bit, then this could be the combination + # of a HIGH DATA bit and a PAD bit without a LOW in between. + # Fake an edge in that case, to re-use existing code paths. + # Make sure to keep referencing times to the last SYNC pad's + # falling edge. This is the last reliable condition we have. + if curr_level: + hold = self.hold_high_width + curr_level, = self.wait([{PIN_DATA: 'l'}, {'skip': int(hold)}]) + self.carrier_check(curr_level, self.samplenum) + if self.matched[1]: + self.edges.append(curr_snum) + curr_level = 1 - curr_level + curr_snum = self.samplenum + + # Get the byte value from the bits (when available). + # TODO Has the local 'bit_field' become obsolete, or should + # self.data_bits go away? + data_byte = bitpack(bit_field) + if self.data_bits is not None: + data_byte = bitpack(self.data_bits) + self.data_bits.clear() + if self.frame_bytes is not None: + self.frame_bytes.append(data_byte) + + # Turn a sequence of a SYNC-PAD and eight DATA bits into a + # DATA-BYTE symbol. + byte_seq = ['SYNC_PAD'] + 8 * ['DATA_BIT'] + if self.symbols_has_prev(byte_seq): + self.symbols_collapse(len(byte_seq), 'DATA_BYTE') + ss, es = self.symbols_get_last()[:2] + texts = ['{:02x}'.format(data_byte)] + self.putg(ss, es, [ANN_DATA_BYTE, texts]) + self.putpy(ss, es, 'DATA_BYTE', data_byte) + + # Optionally terminate the accumulation of a frame when a + # WAIT-ACK period was followed by a DATA-BYTE? This could + # flush the current packet before the next FRAME-INIT or + # IDLE are seen, and increases usability for short input + # data (aggressive trimming). It won't help when WAIT is + # not seen, though. + sync_resp_seq = ['WAIT_ACK'] + ['DATA_BYTE'] + if self.symbols_has_prev(sync_resp_seq): + self.frame_flush() diff --git a/libsigrokdecode4DSL/decoders/pjon/__init__.py b/libsigrokdecode4DSL/decoders/pjon/__init__.py new file mode 100644 index 00000000..579fb591 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/pjon/__init__.py @@ -0,0 +1,25 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2020 Gerhard Sittig +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This protocol decoder interprets the PJON protocol on top of the PJDL +link layer (and potentially other link layers). +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/pjon/pd.py b/libsigrokdecode4DSL/decoders/pjon/pd.py new file mode 100644 index 00000000..b23cfb84 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/pjon/pd.py @@ -0,0 +1,603 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2020 Gerhard Sittig +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +# See the https://www.pjon.org/ PJON project page and especially the +# https://www.pjon.org/PJON-protocol-specification-v3.2.php protocol +# specification, which can use different link layers. + +# TODO +# - Check for the correct order of optional fields (the spec is not as +# explicit on these details as I'd expect). +# - Check decoder's robustness, completeness, and correctness when more +# captures become available. Currently there are only few, which only +# cover minimal communication, and none of the protocol's flexibility. +# The decoder was essentially written based on the available docs, and +# then took some arbitrary choices and liberties to cope with real life +# data from an example setup. Strictly speaking this decoder violates +# the spec, and errs towards the usability side. + +import sigrokdecode as srd +import struct + +ANN_RX_INFO, ANN_HDR_CFG, ANN_PKT_LEN, ANN_META_CRC, ANN_TX_INFO, \ +ANN_SVC_ID, ANN_PKT_ID, ANN_ANON_DATA, ANN_PAYLOAD, ANN_END_CRC, \ +ANN_SYN_RSP, \ +ANN_RELATION, \ +ANN_WARN, \ + = range(13) + +def calc_crc8(data): + crc = 0 + for b in data: + crc ^= b + for i in range(8): + odd = crc % 2 + crc >>= 1 + if odd: + crc ^= 0x97 + return crc + +def calc_crc32(data): + crc = 0xffffffff + for b in data: + crc ^= b + for i in range(8): + odd = crc % 2 + crc >>= 1 + if odd: + crc ^= 0xedb88320 + crc ^= 0xffffffff + return crc + +class Decoder(srd.Decoder): + api_version = 3 + id = 'pjon' + name = 'PJON' + longname = 'PJON' + desc = 'The PJON protocol.' + license = 'gplv2+' + inputs = ['pjon_link'] + outputs = [] + tags = ['Embedded/industrial'] + annotations = ( + ('rx_info', 'Receiver ID'), + ('hdr_cfg', 'Header config'), + ('pkt_len', 'Packet length'), + ('meta_crc', 'Meta CRC'), + ('tx_info', 'Sender ID'), + ('port', 'Service ID'), + ('pkt_id', 'Packet ID'), + ('anon', 'Anonymous data'), + ('payload', 'Payload'), + ('end_crc', 'End CRC'), + ('syn_rsp', 'Sync response'), + ('relation', 'Relation'), + ('warning', 'Warning'), + ) + annotation_rows = ( + ('fields', 'Fields', ( + ANN_RX_INFO, ANN_HDR_CFG, ANN_PKT_LEN, ANN_META_CRC, ANN_TX_INFO, + ANN_SVC_ID, ANN_ANON_DATA, ANN_PAYLOAD, ANN_END_CRC, ANN_SYN_RSP, + )), + ('relations', 'Relations', (ANN_RELATION,)), + ('warnings', 'Warnings', (ANN_WARN,)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.reset_frame() + + def reset_frame(self): + self.frame_ss = None + self.frame_es = None + self.frame_rx_id = None + self.frame_tx_id = None + self.frame_payload_text = None + self.frame_bytes = None + self.frame_has_ack = None + self.ack_bytes = None + self.ann_ss = None + self.ann_es = None + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def putg(self, ss, es, ann, data): + self.put(ss, es, self.out_ann, [ann, data]) + + def frame_flush(self): + if not self.frame_bytes: + return + if not self.frame_ss or not self.frame_es: + return + + # Emit "communication relation" details. + # TODO Include the service ID (port number) as well? + text = [] + if self.frame_rx_id is not None: + text.append("RX {}".format(self.frame_rx_id[-1])) + if self.frame_tx_id is not None: + text.append("TX {}".format(self.frame_tx_id[-1])) + if self.frame_payload_text is not None: + text.append("DATA {}".format(self.frame_payload_text)) + if self.frame_has_ack is not None: + text.append("ACK {:02x}".format(self.frame_has_ack)) + if text: + text = " - ".join(text) + self.putg(self.frame_ss, self.frame_es, ANN_RELATION, [text]) + + def handle_field_get_desc(self, idx = None): + '''Lookup description of a PJON frame field.''' + if not self.field_desc: + return None + if idx is None: + idx = self.field_desc_idx + if idx >= 0 and idx >= len(self.field_desc): + return None + if idx < 0 and abs(idx) > len(self.field_desc): + return None + desc = self.field_desc[idx] + return desc + + def handle_field_add_desc(self, fmt, hdl, cls = None): + '''Register description for a PJON frame field.''' + item = { + 'format': fmt, + 'width': struct.calcsize(fmt), + 'handler': hdl, + 'anncls': cls, + } + self.field_desc.append(item) + + def handle_field_seed_desc(self): + '''Seed list of PJON frame fields' descriptions.''' + + # At the start of a PJON frame, the layout of only two fields + # is known. Subsequent fields (their presence, and width) depend + # on the content of the header config field. + + self.field_desc = [] + self.handle_field_add_desc(' 15 and not self.cfg_crc32: + warn_texts.append('length above 15 needs CRC32') + if pl_len < 1: + warn_texts.append('suspicious payload length') + pl_len = 0 + if warn_texts: + warn_texts = ', '.join(warn_texts) + self.putg(self.ann_ss, self.ann_es, ANN_WARN, [warn_texts]) + pl_fmt = '>{:d}B'.format(pl_len) + + desc = self.handle_field_get_desc(-2) + desc['format'] = pl_fmt + desc['width'] = struct.calcsize(pl_fmt) + + # Have the caller emit the annotation for the packet length. + # Provide information of different detail level for zooming. + texts = [ + 'LENGTH {:d} (PAYLOAD {:d})'.format(pkt_len, pl_len), + 'LEN {:d} (PL {:d})'.format(pkt_len, pl_len), + '{:d} ({:d})'.format(pkt_len, pl_len), + '{:d}'.format(pkt_len), + ] + return texts + + def handle_field_common_crc(self, have, is_meta): + '''Process a CRC field of a PJON frame.''' + + # CRC algorithm and width are configurable, and can differ + # across meta and end checksums in a frame's fields. + caption = 'META' if is_meta else 'END' + crc_len = 8 if is_meta else 32 if self.cfg_crc32 else 8 + crc_bytes = crc_len // 8 + crc_fmt = '{:08x}' if crc_len == 32 else '{:02x}' + have_text = crc_fmt.format(have) + + # Check received against expected checksum. Emit warnings. + warn_texts = [] + data = self.frame_bytes[:-crc_bytes] + want = calc_crc32(data) if crc_len == 32 else calc_crc8(data) + if want != have: + want_text = crc_fmt.format(want) + warn_texts.append('CRC mismatch - want {} have {}'.format(want_text, have_text)) + if warn_texts: + warn_texts = ', '.join(warn_texts) + self.putg(self.ann_ss, self.ann_es, ANN_WARN, [warn_texts]) + + # Provide text representation for frame field, caller emits + # the annotation. + texts = [ + '{}_CRC {}'.format(caption, have_text), + 'CRC {}'.format(have_text), + have_text, + ] + return texts + + def handle_field_meta_crc(self, b): + '''Process initial CRC (meta) field of a PJON frame.''' + # Caller provides a list of values. We want a single scalar. + b = b[0] + return self.handle_field_common_crc(b, True) + + def handle_field_end_crc(self, b): + '''Process end CRC (total frame) field of a PJON frame.''' + # Caller provides a list of values. We want a single scalar. + b = b[0] + return self.handle_field_common_crc(b, False) + + def handle_field_common_bus(self, b): + '''Common handling of bus ID details. Used for RX and TX.''' + bus_id = b[:4] + bus_num = struct.unpack('>L', bytearray(bus_id)) + bus_txt = '.'.join(['{:d}'.format(b) for b in bus_id]) + return bus_num, bus_txt + + def handle_field_rx_bus(self, b): + '''Process receiver bus ID field of a PJON frame.''' + + # When we get here, there always should be an RX ID already. + bus_num, bus_txt = self.handle_field_common_bus(b[:4]) + rx_txt = "{} {}".format(bus_txt, self.frame_rx_id[-1]) + self.frame_rx_id = (bus_num, self.frame_rx_id[0], rx_txt) + + # Provide text representation for frame field, caller emits + # the annotation. + texts = [ + 'RX_BUS {}'.format(bus_txt), + bus_txt, + ] + return texts + + def handle_field_tx_bus(self, b): + '''Process transmitter bus ID field of a PJON frame.''' + + # The TX ID field is optional, as is the use of bus ID fields. + # In the TX info case the TX bus ID is seen before the TX ID. + bus_num, bus_txt = self.handle_field_common_bus(b[:4]) + self.frame_tx_id = (bus_num, None, bus_txt) + + # Provide text representation for frame field, caller emits + # the annotation. + texts = [ + 'TX_BUS {}'.format(bus_txt), + bus_txt, + ] + return texts + + def handle_field_tx_id(self, b): + '''Process transmitter ID field of a PJON frame.''' + + b = b[0] + + id_txt = "{:d}".format(b) + if self.frame_tx_id is None: + self.frame_tx_id = (b, id_txt) + else: + tx_txt = "{} {}".format(self.frame_tx_id[-1], id_txt) + self.frame_tx_id = (self.frame_tx_id[0], b, tx_txt) + + # Provide text representation for frame field, caller emits + # the annotation. + texts = [ + 'TX_ID {}'.format(id_txt), + id_txt, + ] + return texts + + def handle_field_payload(self, b): + '''Process payload data field of a PJON frame.''' + + text = ' '.join(['{:02x}'.format(v) for v in b]) + self.frame_payload = b[:] + self.frame_payload_text = text + + texts = [ + 'PAYLOAD {}'.format(text), + text, + ] + return texts + + def handle_field_sync_resp(self, b): + '''Process synchronous response for a PJON frame.''' + + self.frame_has_ack = b + + texts = [ + 'ACK {:02x}'.format(b), + '{:02x}'.format(b), + ] + return texts + + def decode(self, ss, es, data): + ptype, pdata = data + + # Start frame bytes accumulation when FRAME_INIT is seen. Flush + # previously accumulated frame bytes when a new frame starts. + if ptype == 'FRAME_INIT': + self.frame_flush() + self.reset_frame() + self.frame_bytes = [] + self.handle_field_seed_desc() + self.frame_ss = ss + self.frame_es = es + return + + # Use IDLE as another (earlier) trigger to flush frames. Also + # trigger flushes on FRAME-DATA which mean that the link layer + # inspection has seen the end of a protocol frame. + # + # TODO Improve usability? Emit warnings for PJON frames where + # FRAME_DATA was seen but FRAME_INIT wasn't? So that users can + # become aware of broken frames. + if ptype in ('IDLE', 'FRAME_DATA'): + self.frame_flush() + self.reset_frame() + return + + # Switch from data bytes to response bytes when WAIT is seen. + if ptype == 'SYNC_RESP_WAIT': + self.ack_bytes = [] + self.ann_ss, self.ann_es = None, None + return + + # Accumulate data bytes as they arrive. Put them in the bucket + # which corresponds to its most recently seen leader. + if ptype == 'DATA_BYTE': + b = pdata + self.frame_es = es + + # Are we collecting response bytes (ACK)? + if self.ack_bytes is not None: + if not self.ann_ss: + self.ann_ss = ss + self.ack_bytes.append(b) + self.ann_es = es + text = self.handle_field_sync_resp(b) + if text: + self.putg(self.ann_ss, self.ann_es, ANN_SYN_RSP, text) + self.ann_ss, self.ann_es = None, None + return + + # Are we collecting frame content? + if self.frame_bytes is not None: + if not self.ann_ss: + self.ann_ss = ss + self.frame_bytes.append(b) + self.ann_es = es + + # Has the field value become available yet? + desc = self.handle_field_get_desc() + if not desc: + return + width = desc.get('width', None) + if not width: + return + self.field_desc_got += 1 + if self.field_desc_got != width: + return + + # Grab most recent received field as a byte array. Get + # the values that it contains. + fmt = desc.get('format', '>B') + raw = bytearray(self.frame_bytes[-width:]) + values = struct.unpack(fmt, raw) + + # Process the value, and get its presentation. Can be + # mere formatting, or serious execution of logic. + hdl = desc.get('handler', '{!r}') + if isinstance(hdl, str): + text = [hdl.format(*values)] + elif isinstance(hdl, (list, tuple)): + text = [f.format(*values) for f in hdl] + elif hdl: + text = hdl(values) + cls = desc.get('anncls', ANN_ANON_DATA) + + # Emit annotation unless the handler routine already did. + if cls is not None and text: + self.putg(self.ann_ss, self.ann_es, cls, text) + self.ann_ss, self.ann_es = None, None + + # Advance scan position for to-get-received field. + self.field_desc_idx += 1 + self.field_desc_got = 0 + return + + # Unknown phase, not collecting. Not synced yet to the input? + return + + # Unknown or unhandled kind of link layer output. + return diff --git a/libsigrokdecode4DSL/decoders/ps2/__init__.py b/libsigrokdecode4DSL/decoders/ps2/__init__.py new file mode 100644 index 00000000..75c47687 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/ps2/__init__.py @@ -0,0 +1,26 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2016 Daniel Schulte +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program. If not, see . +## + +''' +This protocol decoder can decode PS/2 device -> host communication. + +Host -> device communication is currently not supported. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/ps2/pd.py b/libsigrokdecode4DSL/decoders/ps2/pd.py new file mode 100644 index 00000000..fc6972bb --- /dev/null +++ b/libsigrokdecode4DSL/decoders/ps2/pd.py @@ -0,0 +1,193 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2016 Daniel Schulte +## Copyright (C) 2019 DreamSourceLab +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd +from collections import namedtuple + +class Ann: + BIT, HSTART, DSTART, STOP, PARITY_OK, PARITY_ERR, DATA, WORD, ACK = range(9) + +Bit = namedtuple('Bit', 'val ss es') + +class Decoder(srd.Decoder): + api_version = 3 + id = 'ps2' + name = 'PS/2' + longname = 'PS/2' + desc = 'PS/2 keyboard/mouse interface.' + license = 'gplv2+' + inputs = ['logic'] + outputs = [] + tags = ['PC'] + channels = ( + {'id': 'clk', 'type': 0, 'name': 'Clock', 'desc': 'Clock line'}, + {'id': 'data', 'type': 107, 'name': 'Data', 'desc': 'Data line'}, + ) + options = ( + {'id': 'HtoD_Clock', 'desc': 'HtoD_Clock', + 'default': 'rise', 'values': ('rise', 'fall')}, + {'id': 'DtoH_Clock', 'desc': 'DtoH_Clock', + 'default': 'fall', 'values': ('fall', 'rise')}, + ) + annotations = ( + ('207', 'bit', 'Bit'), + ('109', 'HSTART', 'HSTART'), + ('50', 'DSTART', 'DSTART'), + ('1000', 'stop-bit', 'Stop bit'), + ('7', 'parity-ok', 'Parity OK bit'), + ('1000', 'parity-err', 'Parity error bit'), + ('40', 'data-bit', 'Data bit'), + ('65', 'word', 'Word'), + ('75', 'ACK', 'ACK'), + ) + annotation_rows = ( + ('bits', 'Bits', (0,)), + ('fields', 'Fields', (1, 2, 3, 4, 5, 6, 7, 8)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.bits = [] + self.samplenum = 0 + self.bitcount = 0 + self.state = 'NULL' + self.ss = self.es = 0 + self.HtoDss = 0 + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + self.HtoD = 1 if self.options['HtoD_Clock'] == 'rise' else 0 + self.DtoH = 1 if self.options['DtoH_Clock'] == 'fall' else 0 + def putb(self, bit, ann_idx): + b = self.bits[bit] + self.put(b.ss, b.es, self.out_ann, [ann_idx, [str(b.val)]]) + + def putx(self, bit, ann): + self.put(self.bits[bit].ss, self.bits[bit].es, self.out_ann, ann) + + def handle_bits(self, datapin): + # Ignore non start condition bits (useful during keyboard init). + if self.bitcount == 0 and datapin == 1: + self.state = 'HtoD' + (clock_pin, datapin) = self.wait({0: 'r'}) + + + # Store individual bits and their start/end samplenumbers. + self.bits.append(Bit(datapin, self.samplenum, self.samplenum)) + + # Fix up end sample numbers of the bits. + if self.bitcount > 0: + b = self.bits[self.bitcount - 1] + self.bits[self.bitcount - 1] = Bit(b.val, b.ss, self.samplenum) + if self.bitcount == 11: + self.bitwidth = self.bits[1].es - self.bits[2].es + b = self.bits[-1] + self.bits[-1] = Bit(b.val, b.ss, b.es + self.bitwidth) + + # Find all 11 bits. Start + 8 data + odd parity + stop. + if self.bitcount < 11: + self.bitcount += 1 + return + + # Extract data word. + word = 0 + for i in range(8): + word |= (self.bits[i + 1].val << i) + + # Calculate parity. + parity_ok = (bin(word).count('1') + self.bits[9].val) % 2 == 1 + + # Emit annotations. + for i in range(11): + self.putb(i, Ann.BIT) + if self.state == 'HtoD': + self.putx(0, [Ann.HSTART, ['Host Start', 'HStart', 'HS']]) + if self.state == 'DtoH': + self.putx(0, [Ann.DSTART, ['Device Start', 'Device', 'DS']]) + self.put(self.bits[1].ss, self.bits[8].es, self.out_ann, [Ann.WORD, + ['Data: %02x' % word, 'D: %02x' % word, '%02x' % word]]) + if parity_ok: + self.putx(9, [Ann.PARITY_OK, ['Parity OK', 'Par OK', 'P']]) + else: + self.putx(9, [Ann.PARITY_ERR, ['Parity error', 'Par err', 'PE']]) + self.putx(10, [Ann.STOP, ['Stop bit', 'Stop', 'St', 'T']]) + + self.bits, self.bitcount = [], 0 + self.state == 'NULL' + + def decode(self): + while True: + # Sample data bits on falling clock edge. + if self.bitcount == 0: + if self.HtoDss : + self.state = 'HtoD' + (clock_pin, data_pin) = self.wait({0: 'r',1: 'l'}) + self.handle_bits(data_pin) + (clock_pin, data_pin) = self.wait({0: 'f'}) + else: + (clock_pin, data_pin) = self.wait([{0: 'f',1: 'r'},{0: 'f',1: 'f'},{0: 'f',1: 'h'},{0: 'f',1: 'l'}]) + if (self.matched & (0b1 << 0)): + continue + if (self.matched & (0b1 << 1)): + self.state = 'HtoD' + (clock_pin, data_pin) = self.wait({0: 'r',1: 'l'}) + self.handle_bits(data_pin) + (clock_pin, data_pin) = self.wait({0: 'f'}) + if (self.matched & (0b1 << 2)): + self.state = 'HtoD' + (clock_pin, data_pin) = self.wait({0: 'r',1: 'l'}) + self.handle_bits(data_pin) + (clock_pin, data_pin) = self.wait({0: 'f'}) + if (self.matched & (0b1 << 3)): + self.state = 'DtoH' + self.handle_bits(data_pin) + if self.state == 'HtoD': + if self.HtoD : + (clock_pin, data_pin) = self.wait({0: 'r'}) + else: + (clock_pin, data_pin) = self.wait({0: 'f'}) + self.handle_bits(data_pin) + if (self.bitcount == 10): + (clock_pin, data_pin) = self.wait({0: 'r'}) + self.handle_bits(data_pin) + if (self.bitcount == 11): + (clock_pin, data_pin) = self.wait({0: 'f'}) + self.handle_bits(data_pin) + self.ss = self.samplenum + (clock_pin, data_pin) = self.wait({0: 'r'}) + self.es = self.samplenum + self.put(self.ss,self.es,self.out_ann,[Ann.ACK, ['ACK', 'ACK', 'ACK', 'A']]) + self.HtoDss = 0 + if self.state == 'DtoH': + if self.DtoH : + (clock_pin, data_pin) = self.wait({0: 'f'}) + else: + (clock_pin, data_pin) = self.wait({0: 'r'}) + self.handle_bits(data_pin) + if (self.bitcount == 11): + (clock_pin, data_pin) = self.wait([{1: 'f'},{0: 'r'}]) + if (self.matched & (0b1 << 0)): + self.handle_bits(data_pin) + self.HtoDss = 1 + if (self.matched & (0b1 << 1)): + self.handle_bits(data_pin) + self.HtoDss = 0 \ No newline at end of file diff --git a/libsigrokdecode4DSL/decoders/pwm/__init__.py b/libsigrokdecode4DSL/decoders/pwm/__init__.py new file mode 100644 index 00000000..8f039766 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/pwm/__init__.py @@ -0,0 +1,24 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 Torsten Duwe +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +Pulse-width modulation (PWM) a.k.a pulse-duration modulation (PDM) decoder. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/pwm/pd.py b/libsigrokdecode4DSL/decoders/pwm/pd.py new file mode 100644 index 00000000..d8626ee0 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/pwm/pd.py @@ -0,0 +1,141 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 Torsten Duwe +## Copyright (C) 2014 Sebastien Bourdelin +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd + +class SamplerateError(Exception): + pass + +class Decoder(srd.Decoder): + api_version = 3 + id = 'pwm' + name = 'PWM' + longname = 'Pulse-width modulation' + desc = 'Analog level encoded in duty cycle percentage.' + license = 'gplv2+' + inputs = ['logic'] + outputs = [] + tags = ['Encoding'] + channels = ( + {'id': 'data', 'name': 'Data', 'desc': 'Data line'}, + ) + options = ( + {'id': 'polarity', 'desc': 'Polarity', 'default': 'active-high', + 'values': ('active-low', 'active-high')}, + ) + annotations = ( + ('duty-cycle', 'Duty cycle'), + ('period', 'Period'), + ) + annotation_rows = ( + ('duty-cycle', 'Duty cycle', (0,)), + ('period', 'Period', (1,)), + ) + binary = ( + ('raw', 'RAW file'), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.samplerate = None + self.ss_block = self.es_block = None + + def metadata(self, key, value): + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + self.out_binary = self.register(srd.OUTPUT_BINARY) + self.out_average = \ + self.register(srd.OUTPUT_META, + meta=(float, 'Average', 'PWM base (cycle) frequency')) + + def putx(self, data): + self.put(self.ss_block, self.es_block, self.out_ann, data) + + def putp(self, period_t): + # Adjust granularity. + if period_t == 0 or period_t >= 1: + period_s = '%.1f s' % (period_t) + elif period_t <= 1e-12: + period_s = '%.1f fs' % (period_t * 1e15) + elif period_t <= 1e-9: + period_s = '%.1f ps' % (period_t * 1e12) + elif period_t <= 1e-6: + period_s = '%.1f ns' % (period_t * 1e9) + elif period_t <= 1e-3: + period_s = '%.1f μs' % (period_t * 1e6) + else: + period_s = '%.1f ms' % (period_t * 1e3) + + self.put(self.ss_block, self.es_block, self.out_ann, [1, [period_s]]) + + def putb(self, data): + self.put(self.ss_block, self.es_block, self.out_binary, data) + + def decode(self): + if not self.samplerate: + raise SamplerateError('Cannot decode without samplerate.') + + num_cycles = 0 + average = 0 + + # Wait for an "active" edge (depends on config). This starts + # the first full period of the inspected signal waveform. + self.wait({0: 'f' if self.options['polarity'] == 'active-low' else 'r'}) + self.first_samplenum = self.samplenum + + # Keep getting samples for the period's middle and terminal edges. + # At the same time that last sample starts the next period. + while True: + + # Get the next two edges. Setup some variables that get + # referenced in the calculation and in put() routines. + start_samplenum = self.samplenum + self.wait({0: 'e'}) + end_samplenum = self.samplenum + self.wait({0: 'e'}) + self.ss_block = start_samplenum + self.es_block = self.samplenum + + # Calculate the period, the duty cycle, and its ratio. + period = self.samplenum - start_samplenum + duty = end_samplenum - start_samplenum + ratio = float(duty / period) + + # Report the duty cycle in percent. + percent = float(ratio * 100) + self.putx([0, ['%f%%' % percent]]) + + # Report the duty cycle in the binary output. + self.putb([0, bytes([int(ratio * 256)])]) + + # Report the period in units of time. + period_t = float(period / self.samplerate) + self.putp(period_t) + + # Update and report the new duty cycle average. + num_cycles += 1 + average += percent + self.put(self.first_samplenum, self.es_block, self.out_average, + float(average / num_cycles)) diff --git a/libsigrokdecode4DSL/decoders/qi/__init__.py b/libsigrokdecode4DSL/decoders/qi/__init__.py new file mode 100644 index 00000000..0d49d17b --- /dev/null +++ b/libsigrokdecode4DSL/decoders/qi/__init__.py @@ -0,0 +1,25 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2015 Josef Gajdusek +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This decoder decodes demodulated data streams used by the Qi standard +for communication from the receiver to the charging station. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/qi/pd.py b/libsigrokdecode4DSL/decoders/qi/pd.py new file mode 100644 index 00000000..b750d9ce --- /dev/null +++ b/libsigrokdecode4DSL/decoders/qi/pd.py @@ -0,0 +1,244 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2015 Josef Gajdusek +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd +import operator +import collections +from functools import reduce + +end_codes = ( + 'Unknown', + 'Charge Complete', + 'Internal Fault', + 'Over Temperature', + 'Over Voltage', + 'Over Current', + 'Battery Failure', + 'Reconfigure', + 'No Response', +) + +class SamplerateError(Exception): + pass + +def calc_checksum(packet): + return reduce(operator.xor, packet[:-1]) + +def bits_to_uint(bits): + # LSB first + return reduce(lambda i, v: (i >> 1) | (v << (len(bits) - 1)), bits, 0) + +class Decoder(srd.Decoder): + api_version = 3 + id = 'qi' + name = 'Qi' + longname = 'Qi charger protocol' + desc = 'Protocol used by Qi receiver.' + license = 'gplv2+' + inputs = ['logic'] + outputs = [] + tags = ['Embedded/industrial', 'Wireless/RF'] + channels = ( + {'id': 'qi', 'name': 'Qi', 'desc': 'Demodulated Qi data line'}, + ) + annotations = ( + ('bits', 'Bits'), + ('bytes-errors', 'Bit errors'), + ('bytes-start', 'Start bits'), + ('bytes-info', 'Info bits'), + ('bytes-data', 'Data bytes'), + ('packets-data', 'Packet data'), + ('packets-checksum-ok', 'Packet checksum'), + ('packets-checksum-err', 'Packet checksum'), + ) + annotation_rows = ( + ('bits', 'Bits', (0,)), + ('bytes', 'Bytes', (1, 2, 3, 4)), + ('packets', 'Packets', (5, 6, 7)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.samplerate = None + self.reset_variables() + + def reset_variables(self): + self.counter = 0 + self.prev = None + self.state = 'IDLE' + self.lastbit = 0 + self.bytestart = 0 + self.deq = collections.deque(maxlen = 2) + self.bits = [] + self.bitsi = [0] + self.bytesi = [] + self.packet = [] + + def metadata(self, key, value): + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + self.bit_width = float(self.samplerate) / 2e3 + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + self.reset_variables() + + def packet_len(self, byte): + if 0x00 <= byte <= 0x1f: + return int(1 + (byte - 0) / 32) + if 0x20 <= byte <= 0x7f: + return int(2 + (byte - 32) / 16) + if 0x80 <= byte <= 0xdf: + return int(8 + (byte - 128) / 8) + if 0xe0 <= byte <= 0xff: + return int(20 + (byte - 224) / 4) + + def in_tolerance(self, l): + return (0.75 * self.bit_width) < l < (1.25 * self.bit_width) + + def putp(self, data): + self.put(self.bytesi[0], self.bytesi[-1], self.out_ann, [5, data]) + + def process_packet(self): + if self.packet[0] == 0x01: # Signal Strength + self.putp(['Signal Strength: %d' % self.packet[1], + 'SS: %d' % self.packet[1], 'SS']) + elif self.packet[0] == 0x02: # End Power Transfer + reason = end_codes[self.packet[1]] if self.packet[1] < len(end_codes) else 'Reserved' + self.putp(['End Power Transfer: %s' % reason, + 'EPT: %s' % reason, 'EPT']) + elif self.packet[0] == 0x03: # Control Error + val = self.packet[1] if self.packet[1] < 128 else (self.packet[1] & 0x7f) - 128 + self.putp(['Control Error: %d' % val, 'CE: %d' % val, 'CE']) + elif self.packet[0] == 0x04: # Received Power + self.putp(['Received Power: %d' % self.packet[1], + 'RP: %d' % self.packet[1], 'RP']) + elif self.packet[0] == 0x05: # Charge Status + self.putp(['Charge Status: %d' % self.packet[1], + 'CS: %d' % self.packet[1], 'CS']) + elif self.packet[0] == 0x06: # Power Control Hold-off + self.putp(['Power Control Hold-off: %dms' % self.packet[1], + 'PCH: %d' % self.packet[1]], 'PCH') + elif self.packet[0] == 0x51: # Configuration + powerclass = (self.packet[1] & 0xc0) >> 7 + maxpower = self.packet[1] & 0x3f + prop = (self.packet[3] & 0x80) >> 7 + count = self.packet[3] & 0x07 + winsize = (self.packet[4] & 0xf8) >> 3 + winoff = self.packet[4] & 0x07 + self.putp(['Configuration: Power Class = %d, Maximum Power = %d, Prop = %d,' + 'Count = %d, Window Size = %d, Window Offset = %d' % + (powerclass, maxpower, prop, count, winsize, winoff), + 'C: PC = %d MP = %d P = %d C = %d WS = %d WO = %d' % + (powerclass, maxpower, prop, count, winsize, winoff), + 'Configuration', 'C']) + elif self.packet[0] == 0x71: # Identification + version = '%d.%d' % ((self.packet[1] & 0xf0) >> 4, self.packet[1] & 0x0f) + mancode = '%02x%02x' % (self.packet[2], self.packet[3]) + devid = '%02x%02x%02x%02x' % (self.packet[4] & ~0x80, + self.packet[5], self.packet[6], self.packet[7]) + self.putp(['Identification: Version = %s, Manufacturer = %s, ' \ + 'Device = %s' % (version, mancode, devid), + 'ID: %s %s %s' % (version, mancode, devid), 'ID']) + elif self.packet[0] == 0x81: # Extended Identification + edevid = '%02x%02x%02x%02x%02x%02x%02x%02x' % self.packet[1:-1] + self.putp(['Extended Identification: %s' % edevid, + 'EI: %s' % edevid, 'EI']) + elif self.packet[0] in (0x18, 0x19, 0x28, 0x29, 0x38, 0x48, 0x58, 0x68, + 0x78, 0x85, 0xa4, 0xc4, 0xe2): # Proprietary + self.putp(['Proprietary', 'P']) + else: # Unknown + self.putp(['Unknown', '?']) + self.put(self.bytesi[-1], self.samplenum, self.out_ann, + [6, ['Checksum OK', 'OK']] if \ + calc_checksum(self.packet) == self.packet[-1] + else [6, ['Checksum error', 'ERR']]) + + def process_byte(self): + self.put(self.bytestart, self.bitsi[0], self.out_ann, + ([2, ['Start bit', 'Start', 'S']]) if self.bits[0] == 0 else + ([1, ['Start error', 'Start err', 'SE']])) + databits = self.bits[1:9] + data = bits_to_uint(databits) + parity = reduce(lambda i, v: (i + v) % 2, databits, 1) + self.put(self.bitsi[0], self.bitsi[8], self.out_ann, [4, ['%02x' % data]]) + self.put(self.bitsi[8], self.bitsi[9], self.out_ann, + ([3, ['Parity bit', 'Parity', 'P']]) if self.bits[9] == parity else + ([1, ['Parity error', 'Parity err', 'PE']])) + self.put(self.bitsi[9], self.bitsi[10], self.out_ann, + ([3, ['Stop bit', 'Stop', 'S']]) if self.bits[10] == 1 else + ([1, ['Stop error', 'Stop err', 'SE']])) + + self.bytesi.append(self.bytestart) + self.packet.append(data) + if self.packet_len(self.packet[0]) + 2 == len(self.packet): + self.process_packet() + self.bytesi.clear() + self.packet.clear() + + def add_bit(self, bit): + self.bits.append(bit) + self.bitsi.append(self.samplenum) + + if self.state == 'IDLE' and len(self.bits) >= 5 and \ + self.bits[-5:] == [1, 1, 1, 1, 0]: + self.state = 'DATA' + self.bytestart = self.bitsi[-2] + self.bits = [0] + self.bitsi = [self.samplenum] + self.packet.clear() + elif self.state == 'DATA' and len(self.bits) == 11: + self.process_byte() + self.bytestart = self.samplenum + self.bits.clear() + self.bitsi.clear() + if self.state != 'IDLE': + self.put(self.lastbit, self.samplenum, self.out_ann, [0, ['%d' % bit]]) + self.lastbit = self.samplenum + + def handle_transition(self, l, htl): + self.deq.append(l) + if len(self.deq) >= 2 and \ + (self.in_tolerance(self.deq[-1] + self.deq[-2]) or \ + htl and self.in_tolerance(l * 2) and \ + self.deq[-2] > 1.25 * self.bit_width): + self.add_bit(1) + self.deq.clear() + elif self.in_tolerance(l): + self.add_bit(0) + self.deq.clear() + elif l > (1.25 * self.bit_width): + self.state = 'IDLE' + self.bytesi.clear() + self.packet.clear() + self.bits.clear() + self.bitsi.clear() + + def decode(self): + if not self.samplerate: + raise SamplerateError('Cannot decode without samplerate.') + + (qi,) = self.wait() + self.handle_transition(self.samplenum, qi == 0) + while True: + prev = self.samplenum + (qi,) = self.wait({0: 'e'}) + self.handle_transition(self.samplenum - prev, qi == 0) diff --git a/libsigrokdecode4DSL/decoders/qspi/__init__.py b/libsigrokdecode4DSL/decoders/qspi/__init__.py new file mode 100644 index 00000000..10b74234 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/qspi/__init__.py @@ -0,0 +1,31 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2016 fenugrec +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This protocol decoder decodes the AUD (Advanced User Debugger) interface +of certain Renesas / Hitachi microcontrollers, when set in Branch Trace mode. + +AUD has two modes, this PD currently only supports "Branch Trace" mode. + +Details: +http://www.renesas.eu/products/mpumcu/superh/sh7050/sh7058/Documentation.jsp +("rej09b0046 - SH7058 Hardware manual") +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/qspi/pd.py b/libsigrokdecode4DSL/decoders/qspi/pd.py new file mode 100644 index 00000000..e0aa4f2a --- /dev/null +++ b/libsigrokdecode4DSL/decoders/qspi/pd.py @@ -0,0 +1,226 @@ +### +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2020 DreamSourceLab +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd +from collections import namedtuple + +Data = namedtuple('Data', ['ss', 'es', 'val']) + + +# Key: (CPOL, CPHA). Value: SPI mode. +# Clock polarity (CPOL) = 0/1: Clock is low/high when inactive. +# Clock phase (CPHA) = 0/1: Data is valid on the leading/trailing clock edge. +spi_mode = { + (0, 0): 0, # Mode 0 + (0, 1): 1, # Mode 1 + (1, 0): 2, # Mode 2 + (1, 1): 3, # Mode 3 +} + +class ChannelError(Exception): + pass + +class Decoder(srd.Decoder): + api_version = 3 + id = 'qspi' + name = 'QSPI' + longname = 'Quad Serial Peripheral Interface' + desc = 'Full-duplex, synchronous, serial bus.' + license = 'gplv2+' + inputs = ['logic'] + outputs = ['spi'] + tags = ['Embedded/industrial'] + channels = ( + {'id': 'clk', 'type': 0, 'name': 'CLK', 'desc': 'Clock'}, + {'id': 'io0', 'type': 107, 'name': 'IO0', 'desc': 'Data i/o 0'}, + ) + optional_channels = ( + {'id': 'io1', 'type': 107, 'name': 'IO1', 'desc': 'Data i/o 1'}, + {'id': 'io2', 'type': 107, 'name': 'IO2', 'desc': 'Data i/o 2'}, + {'id': 'io3', 'type': 107, 'name': 'IO3', 'desc': 'Data i/o 3'}, + {'id': 'cs', 'type': -1, 'name': 'CS#', 'desc': 'Chip-select'}, + ) + options = ( + {'id': 'cs_polarity', 'desc': 'CS# polarity', 'default': 'active-low', + 'values': ('active-low', 'active-high')}, + {'id': 'cpol', 'desc': 'Clock polarity (CPOL)', 'default': 0, + 'values': (0, 1)}, + {'id': 'cpha', 'desc': 'Clock phase (CPHA)', 'default': 0, + 'values': (0, 1)}, + {'id': 'bitorder', 'desc': 'Bit order', + 'default': 'msb-first', 'values': ('msb-first', 'lsb-first')}, + {'id': 'wordsize', 'desc': 'Word size', 'default': 8}, + ) + annotations = ( + ('106', 'data', 'data'), + ) + annotation_rows = ( + ('data', 'data', (0,)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.samplerate = None + self.bitcount = 0 + self.data = 0 + self.bits = [] + self.ss_block = -1 + self.samplenum = -1 + self.ss_transfer = -1 + self.cs_was_deasserted = False + self.have_cs = self.have_io1 = self.have_io3 = None + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + self.bw = (self.options['wordsize'] + 7) // 8 + + def metadata(self, key, value): + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + + def putw(self, data): + self.put(self.ss_block, self.samplenum, self.out_ann, data) + + def putdata(self): + # Pass bits and then data to the next PD up the stack. + ss, es = self.bits[-1][1], self.bits[0][2] + + # Dataword annotations. + self.put(ss, es, self.out_ann, [0, ['%02X' % self.data]]) + + def reset_decoder_state(self): + self.data = 0 + self.bits = [] + self.bitcount = 0 + + def cs_asserted(self, cs): + active_low = (self.options['cs_polarity'] == 'active-low') + return (cs == 0) if active_low else (cs == 1) + + def handle_bit(self, datapins, clk, cs): + # If this is the first bit of a dataword, save its sample number. + if self.bitcount == 0: + self.ss_block = self.samplenum + self.cs_was_deasserted = \ + not self.cs_asserted(cs) if self.have_cs else False + + bo = self.options['bitorder'] + ws = self.options['wordsize'] + if self.have_io3: + nibws = ws >> 2 + elif self.have_io1: + nibws = ws >> 1 + else: + nibws = ws + + # Receive bit into our shift register. + if self.have_io3: + for i in range(4): + if bo == 'msb-first': + self.data |= datapins[i] << (ws - 1 - self.bitcount*4 - i) + else: + self.data |= datapins[3-i] << (self.bitcount*4 + i) + elif self.have_io1: + for i in range(2): + if bo == 'msb-first': + self.data |= datapins[i+2] << (ws - 1 - self.bitcount*2 - i) + else: + self.data |= datapins[3-i] << (self.bitcount*2 + i) + else: + if bo == 'msb-first': + self.data |= datapins[3] << (ws - 1 - self.bitcount) + else: + self.data |= datapins[3] << self.bitcount + + # Guesstimate the endsample for this bit (can be overridden below). + es = self.samplenum + if self.bitcount > 0: + es += self.samplenum - self.bits[0][1] + + self.bits.insert(0, [datapins[3], self.samplenum, es]) + + if self.bitcount > 0: + self.bits[1][2] = self.samplenum + + self.bitcount += 1 + + # Continue to receive if not enough bits were received, yet. + if self.bitcount != nibws: + return + + self.putdata() + + self.reset_decoder_state() + + def find_clk_edge(self, datapins, clk, cs, first): + if self.have_cs and (first or (self.matched & (0b1 << self.have_cs))): + # Send all CS# pin value changes. + oldcs = None if first else 1 - cs + + # Reset decoder state when CS# changes (and the CS# pin is used). + self.reset_decoder_state() + + # We only care about samples if CS# is asserted. + if self.have_cs and not self.cs_asserted(cs): + return + + # Ignore sample if the clock pin hasn't changed. + if first or not (self.matched & (0b1 << 0)): + return + + # Found the correct clock edge, now get the SPI bit(s). + self.handle_bit(datapins, clk, cs) + + def decode(self): + # The CLK & IO0 input is mandatory. Other signals are (individually) + # optional. Tell stacked decoders when we don't have a CS# signal. + if not self.has_channel(0): + raise ChannelError('CLK pin required.') + self.have_io1 = self.has_channel(2) + self.have_io3 = self.has_channel(3) & self.has_channel(4) + self.have_cs = self.has_channel(5) + + # We want all CLK changes. We want all CS changes if CS is used. + # Map 'have_cs' from boolean to an integer index. This simplifies + # evaluation in other locations. + # Sample data on rising/falling clock edge (depends on mode). + mode = spi_mode[self.options['cpol'], self.options['cpha']] + if mode == 0 or mode == 3: # Sample on rising clock edge + wait_cond = [{0: 'r'}] + else: # Sample on falling clock edge + wait_cond = [{0: 'f'}] + + if self.have_cs: + self.have_cs = len(wait_cond) + wait_cond.append({5: 'e'}) + + # "Pixel compatibility" with the v2 implementation. Grab and + # process the very first sample before checking for edges. The + # previous implementation did this by seeding old values with + # None, which led to an immediate "change" in comparison. + (clk, d0, d1, d2, d3, cs) = self.wait({}) + d = (d3, d2, d1, d0); + self.find_clk_edge(d, clk, cs, True) + + while True: + (clk, d0, d1, d2, d3, cs) = self.wait(wait_cond) + d = (d3, d2, d1, d0); + self.find_clk_edge(d, clk, cs, False)# diff --git a/libsigrokdecode4DSL/decoders/rc_encode/__init__.py b/libsigrokdecode4DSL/decoders/rc_encode/__init__.py new file mode 100644 index 00000000..db78dc1e --- /dev/null +++ b/libsigrokdecode4DSL/decoders/rc_encode/__init__.py @@ -0,0 +1,36 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2018 Steve R +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This PD decodes the remote control protocol which is frequently used +within key fobs and power socket remotes. + +They contain encoding chips like the PT2262 which converts the button +pressed and address settings into a series of pulses which is then +transmitted over whatever frequency and modulation that the designer +chooses. These devices operate at a number of frequencies including 433MHz. + +This PD should also decode the HX2262 and SC5262 which are equivalents. + +The decoder also contains some additional decoding for a Maplin L95AR +remote control and will turn the received signal into which button was +pressed and what the address code DIP switches are set to. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/rc_encode/pd.py b/libsigrokdecode4DSL/decoders/rc_encode/pd.py new file mode 100644 index 00000000..daeca092 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/rc_encode/pd.py @@ -0,0 +1,167 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2018 Steve R +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd + +bitvals = ('0', '1', 'f', 'U') + +def decode_bit(edges): + # Datasheet says long pulse is 3 times short pulse. + lmin = 2 # long min multiplier + lmax = 5 # long max multiplier + eqmin = 0.5 # equal min multiplier + eqmax = 1.5 # equal max multiplier + if ( # 0 -___-___ + (edges[1] >= edges[0] * lmin and edges[1] <= edges[0] * lmax) and + (edges[2] >= edges[0] * eqmin and edges[2] <= edges[0] * eqmax) and + (edges[3] >= edges[0] * lmin and edges[3] <= edges[0] * lmax)): + return '0' + elif ( # 1 ---_---_ + (edges[0] >= edges[1] * lmin and edges[0] <= edges[1] * lmax) and + (edges[0] >= edges[2] * eqmin and edges[0] <= edges[2] * eqmax) and + (edges[0] >= edges[3] * lmin and edges[0] <= edges[3] * lmax)): + return '1' + elif ( # float ---_-___ + (edges[1] >= edges[0] * lmin and edges[1] <= edges[0] * lmax) and + (edges[2] >= edges[0] * lmin and edges[2] <= edges[0]* lmax) and + (edges[3] >= edges[0] * eqmin and edges[3] <= edges[0] * eqmax)): + return 'f' + else: + return 'U' + +def pinlabels(bit_count): + if bit_count <= 6: + return 'A%i' % (bit_count - 1) + else: + return 'A%i/D%i' % (bit_count - 1, 12 - bit_count) + +def decode_model(model, bits): + if model == 'maplin_l95ar': + address = 'Addr' # Address pins A0 to A5 + for i in range(0, 6): + address += ' %i:' % (i + 1) + ('on' if bits[i][0] == '0' else 'off') + button = 'Button' + # Button pins A6/D5 to A11/D0 + if bits[6][0] == '0' and bits[11][0] == '0': + button += ' A ON/OFF' + elif bits[7][0] == '0' and bits[11][0] == '0': + button += ' B ON/OFF' + elif bits[9][0] == '0' and bits[11][0] == '0': + button += ' C ON/OFF' + elif bits[8][0] == '0' and bits[11][0] == '0': + button += ' D ON/OFF' + else: + button += ' Unknown' + return ['%s' % address, bits[0][1], bits[5][2], \ + '%s' % button, bits[6][1], bits[11][2]] + +class Decoder(srd.Decoder): + api_version = 3 + id = 'rc_encode' + name = 'RC encode' + longname = 'Remote control encoder' + desc = 'PT2262/HX2262/SC5262 remote control encoder protocol.' + license = 'gplv2+' + inputs = ['logic'] + outputs = [] + tags = ['IC', 'IR'] + channels = ( + {'id': 'data', 'name': 'Data', 'desc': 'Data line'}, + ) + annotations = ( + ('bit-0', 'Bit 0'), + ('bit-1', 'Bit 1'), + ('bit-f', 'Bit f'), + ('bit-U', 'Bit U'), + ('bit-sync', 'Bit sync'), + ('pin', 'Pin'), + ('code-word-addr', 'Code word address'), + ('code-word-data', 'Code word data'), + ) + annotation_rows = ( + ('bits', 'Bits', (0, 1, 2, 3, 4)), + ('pins', 'Pins', (5,)), + ('code-words', 'Code words', (6, 7)), + ) + options = ( + {'id': 'remote', 'desc': 'Remote', 'default': 'none', + 'values': ('none', 'maplin_l95ar')}, + ) + + def __init__(self): + self.reset() + + def reset(self): + self.samplenumber_last = None + self.pulses = [] + self.bits = [] + self.labels = [] + self.bit_count = 0 + self.ss = None + self.es = None + self.state = 'IDLE' + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + self.model = self.options['remote'] + + def putx(self, data): + self.put(self.ss, self.es, self.out_ann, data) + + def decode(self): + while True: + self.wait({0: 'e'}) + self.state = 'DECODING' + + if not self.samplenumber_last: # Set counters to start of signal. + self.samplenumber_last = self.samplenum + self.ss = self.samplenum + continue + + if self.bit_count < 12: # Decode A0 to A11. + self.bit_count += 1 + for i in range(0, 4): # Get four pulses for each bit. + if i > 0: + self.wait({0: 'e'}) # Get next 3 edges. + samples = self.samplenum - self.samplenumber_last + self.pulses.append(samples) # Save the pulse width. + self.samplenumber_last = self.samplenum + self.es = self.samplenum + self.bits.append([decode_bit(self.pulses), self.ss, + self.es]) # Save states and times. + idx = bitvals.index(decode_bit(self.pulses)) + self.putx([idx, [decode_bit(self.pulses)]]) # Write decoded bit. + self.putx([5, [pinlabels(self.bit_count)]]) # Write pin labels. + self.pulses = [] + self.ss = self.samplenum + else: + if self.model != 'none': + self.labels = decode_model(self.model, self.bits) + self.put(self.labels[1], self.labels[2], self.out_ann, + [6, [self.labels[0]]]) # Write model decode. + self.put(self.labels[4], self.labels[5], self.out_ann, + [7, [self.labels[3]]]) # Write model decode. + samples = self.samplenum - self.samplenumber_last + self.wait({'skip': 8 * samples}) # Wait for end of sync bit. + self.es = self.samplenum + self.putx([4, ['Sync']]) # Write sync label. + self.reset() # Reset and wait for next set of pulses. + self.state = 'DECODE_TIMEOUT' + if not self.state == 'DECODE_TIMEOUT': + self.samplenumber_last = self.samplenum diff --git a/libsigrokdecode4DSL/decoders/rfm12/__init__.py b/libsigrokdecode4DSL/decoders/rfm12/__init__.py new file mode 100644 index 00000000..2fc6de7b --- /dev/null +++ b/libsigrokdecode4DSL/decoders/rfm12/__init__.py @@ -0,0 +1,25 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 Sławek Piotrowski +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This decoder stacks on top of the 'spi' PD and decodes the HopeRF RFM12 +wireless transceiver control protocol. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/rfm12/pd.py b/libsigrokdecode4DSL/decoders/rfm12/pd.py new file mode 100644 index 00000000..d3df13a9 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/rfm12/pd.py @@ -0,0 +1,497 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 Sławek Piotrowski +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd + +class Decoder(srd.Decoder): + api_version = 3 + id = 'rfm12' + name = 'RFM12' + longname = 'HopeRF RFM12' + desc = 'HopeRF RFM12 wireless transceiver control protocol.' + license = 'gplv2+' + inputs = ['spi'] + outputs = [] + tags = ['Wireless/RF'] + annotations = ( + ('cmd', 'Command'), + ('params', 'Command parameters'), + ('disabled', 'Disabled bits'), + ('return', 'Returned values'), + ('disabled_return', 'Disabled returned values'), + ('interpretation', 'Interpretation'), + ) + annotation_rows = ( + ('commands', 'Commands', (0, 1, 2)), + ('return', 'Return', (3, 4)), + ('interpretation', 'Interpretation', (5,)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.mosi_bytes, self.miso_bytes = [], [] + self.mosi_bits, self.miso_bits = [], [] + self.row_pos = [0, 0, 0] + + self.ann_to_row = [0, 0, 0, 1, 1, 2] + + # Initialize with Power-On-Reset values. + self.last_status = [0x00, 0x00] + self.last_config = 0x08 + self.last_power = 0x08 + self.last_freq = 0x680 + self.last_data_rate = 0x23 + self.last_fifo_and_reset = 0x80 + self.last_afc = 0xF7 + self.last_transceiver = 0x00 + self.last_pll = 0x77 + + def advance_ann(self, ann, length): + row = self.ann_to_row[ann] + self.row_pos[row] += length + + def putx(self, ann, length, description): + if not isinstance(description, list): + description = [description] + row = self.ann_to_row[ann] + bit = self.row_pos[row] + self.put(self.mosi_bits[bit][1], self.mosi_bits[bit + length - 1][2], + self.out_ann, [ann, description]) + bit += length + self.row_pos[row] = bit + + def describe_bits(self, data, names): + i = 0x01 << len(names) - 1 + bit = 0 + while i != 0: + if names[bit] != '': + self.putx(1 if (data & i) else 2, 1, names[bit]) + i >>= 1 + bit += 1 + + def describe_return_bits(self, data, names): + i = 0x01 << len(names) - 1 + bit = 0 + while i != 0: + if names[bit] != '': + self.putx(3 if (data & i) else 4, 1, names[bit]) + else: + self.advance_ann(3, 1) + i >>= 1 + bit += 1 + + def describe_changed_bits(self, data, old_data, names): + changes = data ^ old_data + i = 0x01 << (len(names) - 1) + bit = 0 + while i != 0: + if names[bit] != '' and changes & i: + s = ['+', 'Turning on'] if (data & i) else ['-', 'Turning off'] + self.putx(5, 1, s) + else: + self.advance_ann(5, 1) + i >>= 1 + bit += 1 + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def handle_configuration_cmd(self, cmd, ret): + self.putx(0, 8, ['Configuration command', 'Configuration']) + NAMES = [['Internal data register', 'el'], ['FIFO mode', 'ef']] + + bits = (cmd[1] & 0xC0) >> 6 + old_bits = (self.last_config & 0xC0) >> 6 + self.describe_bits(bits, NAMES) + self.describe_changed_bits(bits, old_bits, NAMES) + + FREQUENCIES = ['315', '433', '868', '915'] + f = FREQUENCIES[(cmd[1] & 0x30) >> 4] + 'MHz' + self.putx(1, 2, ['Frequency: ' + f, f]) + if cmd[1] & 0x30 != self.last_config & 0x30: + self.putx(5, 2, ['Changed', '~']) + + c = '%.1fpF' % (8.5 + (cmd[1] & 0xF) * 0.5) + self.putx(1, 4, ['Capacitance: ' + c, c]) + if cmd[1] & 0xF != self.last_config & 0xF: + self.putx(5, 4, ['Changed', '~']) + + self.last_config = cmd[1] + + def handle_power_management_cmd(self, cmd, ret): + self.putx(0, 8, ['Power management', 'Power']) + NAMES = [['Receiver chain', 'er'], ['Baseband circuit', 'ebb'], + ['Transmission', 'et'], ['Synthesizer', 'es'], + ['Crystal oscillator', 'ex'], ['Low battery detector', 'eb'], + ['Wake-up timer', 'ew'], ['Clock output off switch', 'dc']] + + self.describe_bits(cmd[1], NAMES) + + power = cmd[1] + + # Some bits imply other, even if they are set to 0. + if power & 0x80: + power |= 0x58 + if power & 0x20: + power |= 0x18 + self.describe_changed_bits(power, self.last_power, NAMES) + + self.last_power = power + + def handle_frequency_setting_cmd(self, cmd, ret): + self.putx(0, 4, ['Frequency setting', 'Frequency']) + f = ((cmd[1] & 0xF) << 8) + cmd[2] + self.putx(0, 12, ['F = %3.4f' % f]) + self.row_pos[2] -= 4 + if self.last_freq != f: + self.putx(5, 12, ['Changing', '~']) + self.last_freq = f + + def handle_data_rate_cmd(self, cmd, ret): + self.putx(0, 8, ['Data rate command', 'Data rate']) + r = cmd[1] & 0x7F + cs = (cmd[1] & 0x80) >> 7 + rate = 10000 / 29.0 / (r + 1) / (1 + 7 * cs) + self.putx(0, 8, ['%3.1fkbps' % rate]) + if self.last_data_rate != cmd[1]: + self.putx(5, 8, ['Changing', '~']) + self.last_data_rate = cmd[1] + + def handle_receiver_control_cmd(self, cmd, ret): + self.putx(0, 5, ['Receiver control command']) + s = 'interrupt input' if (cmd[0] & 0x04) else 'VDI output' + self.putx(0, 1, ['pin16 = ' + s]) + VDI_NAMES = ['Fast', 'Medium', 'Slow', 'Always on'] + vdi_speed = VDI_NAMES[cmd[0] & 0x3] + self.putx(0, 2, ['VDI: %s' % vdi_speed]) + BANDWIDTH_NAMES = ['Reserved', '400kHz', '340kHz', '270kHz', '200kHz', + '134kHz', '67kHz', 'Reserved'] + bandwidth = BANDWIDTH_NAMES[(cmd[1] & 0xE0) >> 5] + self.putx(0, 3, ['Bandwidth: %s' % bandwidth]) + LNA_GAIN_NAMES = [0, -6, -14, -20] + lna_gain = LNA_GAIN_NAMES[(cmd[1] & 0x18) >> 3] + self.putx(0, 2, ['LNA gain: %ddB' % lna_gain]) + RSSI_THRESHOLD_NAMES = ['-103', '-97', '-91', '-85', '-79', '-73', + 'Reserved', 'Reserved'] + rssi_threshold = RSSI_THRESHOLD_NAMES[cmd[1] & 0x7] + self.putx(0, 3, ['RSSI threshold: %s' % rssi_threshold]) + + def handle_data_filter_cmd(self, cmd, ret): + self.putx(0, 8, ['Data filter command']) + if cmd[1] & 0x80: + clock_recovery = 'auto' + elif cmd[1] & 0x40: + clock_recovery = 'fast' + else: + clock_recovery = 'slow' + self.putx(0, 2, ['Clock recovery: %s mode' % clock_recovery]) + self.advance_ann(0, 1) # Should always be 1. + s = 'analog' if (cmd[1] & 0x10) else 'digital' + self.putx(0, 1, ['Data filter: ' + s]) + self.advance_ann(0, 1) # Should always be 1. + self.putx(0, 3, ['DQD threshold: %d' % (cmd[1] & 0x7)]) + + def handle_fifo_and_reset_cmd(self, cmd, ret): + self.putx(0, 8, ['FIFO and reset command']) + fifo_level = (cmd[1] & 0xF0) >> 4 + self.putx(0, 4, ['FIFO trigger level: %d' % fifo_level]) + last_fifo_level = (self.last_fifo_and_reset & 0xF0) >> 4 + if fifo_level != last_fifo_level: + self.putx(5, 4, ['Changing', '~']) + else: + self.advance_ann(5, 4) + s = 'one byte' if (cmd[1] & 0x08) else 'two bytes' + self.putx(0, 1, ['Synchron length: ' + s]) + if (cmd[1] & 0x08) != (self.last_fifo_and_reset & 0x08): + self.putx(5, 1, ['Changing', '~']) + else: + self.advance_ann(5, 1) + + if cmd[1] & 0x04: + fifo_fill = 'Always' + elif cmd[1] & 0x02: + fifo_fill = 'After synchron pattern' + else: + fifo_fill = 'Never' + self.putx(0, 2, ['FIFO fill: %s' % fifo_fill]) + if (cmd[1] & 0x06) != (self.last_fifo_and_reset & 0x06): + self.putx(5, 2, ['Changing', '~']) + else: + self.advance_ann(5, 2) + + s = 'non-sensitive' if (cmd[1] & 0x01) else 'sensitive' + self.putx(0, 1, ['Reset mode: ' + s]) + if (cmd[1] & 0x01) != (self.last_fifo_and_reset & 0x01): + self.putx(5, 1, ['Changing', '~']) + else: + self.advance_ann(5, 1) + + self.last_fifo_and_reset = cmd[1] + + def handle_synchron_pattern_cmd(self, cmd, ret): + self.putx(0, 8, ['Synchron pattern command']) + if self.last_fifo_and_reset & 0x08: + self.putx(0, 8, ['Pattern: 0x2D%02X' % pattern]) + else: + self.putx(0, 8, ['Pattern: %02X' % pattern]) + + def handle_fifo_read_cmd(self, cmd, ret): + self.putx(0, 8, ['FIFO read command', 'FIFO read']) + self.putx(3, 8, ['Data: %02X' % ret[1]]) + + def handle_afc_cmd(self, cmd, ret): + self.putx(0, 8, ['AFC command']) + MODES = ['Off', 'Once', 'During receiving', 'Always'] + mode = (cmd[1] & 0xC0) >> 6 + self.putx(0, 2, ['Mode: %s' % MODES[mode]]) + if (cmd[1] & 0xC0) != (self.last_afc & 0xC0): + self.putx(5, 2, ['Changing', '~']) + else: + self.advance_ann(5, 2) + + range_limit = (cmd[1] & 0x30) >> 4 + FREQ_TABLE = [0.0, 2.5, 5.0, 7.5] + freq_delta = FREQ_TABLE[(self.last_config & 0x30) >> 4] + + if range_limit == 0: + self.putx(0, 2, ['Range: No limit']) + elif range_limit == 1: + self.putx(0, 2, ['Range: +/-%dkHz' % (15 * freq_delta)]) + elif range_limit == 2: + self.putx(0, 2, ['Range: +/-%dkHz' % (7 * freq_delta)]) + elif range_limit == 3: + self.putx(0, 2, ['Range: +/-%dkHz' % (3 * freq_delta)]) + + if (cmd[1] & 0x30) != (self.last_afc & 0x30): + self.putx(5, 2, ['Changing', '~']) + else: + self.advance_ann(5, 2) + + NAMES = ['Strobe edge', 'High accuracy mode', 'Enable offset register', + 'Enable offset calculation'] + self.describe_bits(cmd[1] & 0xF, NAMES) + self.describe_changed_bits(cmd[1] & 0xF, self.last_afc & 0xF, NAMES) + + self.last_afc = cmd[1] + + def handle_transceiver_control_cmd(self, cmd, ret): + self.putx(0, 8, ['Transceiver control command']) + self.putx(0, 4, ['FSK frequency delta: %dkHz' % (15 * ((cmd[1] & 0xF0) >> 4))]) + if cmd[1] & 0xF0 != self.last_transceiver & 0xF0: + self.putx(5, 4, ['Changing', '~']) + else: + self.advance_ann(5, 4) + + POWERS = [0, -2.5, -5, -7.5, -10, -12.5, -15, -17.5] + self.advance_ann(0, 1) + self.advance_ann(5, 1) + self.putx(0,3, ['Relative power: %dB' % (cmd[1] & 0x07)]) + if (cmd[1] & 0x07) != (self.last_transceiver & 0x07): + self.putx(5, 3, ['Changing', '~']) + else: + self.advance_ann(5, 3) + self.last_transceiver = cmd[1] + + def handle_pll_setting_cmd(self, cmd, ret): + self.putx(0, 8, ['PLL setting command']) + self.advance_ann(0, 1) + self.putx(0, 2, ['Clock buffer rise and fall time']) + self.advance_ann(0, 1) + self.advance_ann(5, 4) + NAMES = [['Delay in phase detector', 'dly'], ['Disable dithering', 'ddit']] + self.describe_bits((cmd[1] & 0xC) >> 2, NAMES) + self.describe_changed_bits((cmd[1] & 0xC) >> 2, (self.last_pll & 0xC) >> 2, NAMES) + s = '256kbps, high' if (cmd[1] & 0x01) else '86.2kbps, low' + self.putx(0, 1, ['Max bit rate: %s noise' % s]) + + self.advance_ann(5, 1) + if (cmd[1] & 0x01) != (self.last_pll & 0x01): + self.putx(5, 1, ['Changing', '~']) + + self.last_pll = cmd[1] + + def handle_transmitter_register_cmd(self, cmd, ret): + self.putx(0, 8, ['Transmitter register command', 'Transmit']) + self.putx(0, 8, ['Data: %s' % cmd[1], '%s' % cmd[1]]) + + def handle_software_reset_cmd(self, cmd, ret): + self.putx(0, 16, ['Software reset command']) + + def handle_wake_up_timer_cmd(self, cmd, ret): + self.putx(0, 3, ['Wake-up timer command', 'Timer']) + r = cmd[0] & 0x1F + m = cmd[1] + time = 1.03 * m * pow(2, r) + 0.5 + self.putx(0, 13, ['Time: %7.2f' % time]) + + def handle_low_duty_cycle_cmd(self, cmd, ret): + self.putx(0, 16, ['Low duty cycle command']) + + def handle_low_battery_detector_cmd(self, cmd, ret): + self.putx(0, 8, ['Low battery detector command']) + NAMES = ['1', '1.25', '1.66', '2', '2.5', '3.33', '5', '10'] + clock = NAMES[(cmd[1] & 0xE0) >> 5] + self.putx(0, 3, ['Clock output: %sMHz' % clock, '%sMHz' % clock]) + self.advance_ann(0, 1) + v = 2.25 + (cmd[1] & 0x0F) * 0.1 + self.putx(0, 4, ['Low battery voltage: %1.2fV' % v, '%1.2fV' % v]) + + def handle_status_read_cmd(self, cmd, ret): + self.putx(0, 8, ['Status read command', 'Status']) + NAMES = ['RGIT/FFIT', 'POR', 'RGUR/FFOV', 'WKUP', 'EXT', 'LBD', + 'FFEM', 'RSSI/ATS', 'DQD', 'CRL', 'ATGL'] + status = (ret[0] << 3) + (ret[1] >> 5) + self.row_pos[1] -= 8 + self.row_pos[2] -= 8 + self.describe_return_bits(status, NAMES) + receiver_enabled = (self.last_power & 0x80) >> 7 + + if ret[0] & 0x80: + if receiver_enabled: + s = 'Received data in FIFO' + else: + s = 'Transmit register ready' + self.putx(5, 1, s) + else: + self.advance_ann(5, 1) + if ret[0] & 0x40: + self.putx(5, 1, 'Power on Reset') + else: + self.advance_ann(5, 1) + if ret[0] & 0x20: + if receiver_enabled: + s = 'RX FIFO overflow' + else: + s = 'Transmit register under run' + self.putx(5, 1, s) + else: + self.advance_ann(5, 1) + if ret[0] & 0x10: + self.putx(5, 1, 'Wake-up timer') + else: + self.advance_ann(5, 1) + if ret[0] & 0x08: + self.putx(5, 1, 'External interrupt') + else: + self.advance_ann(5, 1) + if ret[0] & 0x04: + self.putx(5, 1, 'Low battery') + else: + self.advance_ann(5, 1) + if ret[0] & 0x02: + self.putx(5, 1, 'FIFO is empty') + else: + self.advance_ann(5, 1) + if ret[0] & 0x01: + if receiver_enabled: + s = 'Incoming signal above limit' + else: + s = 'Antenna detected RF signal' + self.putx(5, 1, s) + else: + self.advance_ann(5, 1) + if ret[1] & 0x80: + self.putx(5, 1, 'Data quality detector') + else: + self.advance_ann(5, 1) + if ret[1] & 0x40: + self.putx(5, 1, 'Clock recovery locked') + else: + self.advance_ann(5, 1) + self.advance_ann(5, 1) + + self.putx(3, 5, ['AFC offset']) + if (self.last_status[1] & 0x1F) != (ret[1] & 0x1F): + self.putx(5, 5, ['Changed', '~']) + self.last_status = ret + + def handle_cmd(self, cmd, ret): + if cmd[0] == 0x80: + self.handle_configuration_cmd(cmd, ret) + elif cmd[0] == 0x82: + self.handle_power_management_cmd(cmd, ret) + elif cmd[0] & 0xF0 == 0xA0: + self.handle_frequency_setting_cmd(cmd, ret) + elif cmd[0] == 0xC6: + self.handle_data_rate_cmd(cmd, ret) + elif cmd[0] & 0xF8 == 0x90: + self.handle_receiver_control_cmd(cmd, ret) + elif cmd[0] == 0xC2: + self.handle_data_filter_cmd(cmd, ret) + elif cmd[0] == 0xCA: + self.handle_fifo_and_reset_cmd(cmd, ret) + elif cmd[0] == 0xCE: + self.handle_synchron_pattern_cmd(cmd, ret) + elif cmd[0] == 0xB0: + self.handle_fifo_read_cmd(cmd, ret) + elif cmd[0] == 0xC4: + self.handle_afc_cmd(cmd, ret) + elif cmd[0] & 0xFE == 0x98: + self.handle_transceiver_control_cmd(cmd, ret) + elif cmd[0] == 0xCC: + self.handle_pll_setting_cmd(cmd, ret) + elif cmd[0] == 0xB8: + self.handle_transmitter_register_cmd(cmd, ret) + elif cmd[0] == 0xFE: + self.handle_software_reset_cmd(cmd, ret) + elif cmd[0] & 0xE0 == 0xE0: + self.handle_wake_up_timer_cmd(cmd, ret) + elif cmd[0] == 0xC8: + self.handle_low_duty_cycle_cmd(cmd, ret) + elif cmd[0] == 0xC0: + self.handle_low_battery_detector_cmd(cmd, ret) + elif cmd[0] == 0x00: + self.handle_status_read_cmd(cmd, ret) + else: + c = '%02x %02x' % tuple(cmd) + r = '%02x %02x' % tuple(ret) + self.putx(0, 16, ['Unknown command: %s (reply: %s)!' % (c, r)]) + + def decode(self, ss, es, data): + ptype, mosi, miso = data + + # For now, only use DATA and BITS packets. + if ptype not in ('DATA', 'BITS'): + return + + # Store the individual bit values and ss/es numbers. The next packet + # is guaranteed to be a 'DATA' packet belonging to this 'BITS' one. + if ptype == 'BITS': + if mosi is not None: + self.mosi_bits.extend(reversed(mosi)) + if miso is not None: + self.miso_bits.extend(reversed(miso)) + return + + # Append new bytes. + self.mosi_bytes.append(mosi) + self.miso_bytes.append(miso) + + # All commands consist of 2 bytes. + if len(self.mosi_bytes) < 2: + return + + self.row_pos = [0, 8, 8] + + self.handle_cmd(self.mosi_bytes, self.miso_bytes) + + self.mosi_bytes, self.miso_bytes = [], [] + self.mosi_bits, self.miso_bits = [], [] diff --git a/libsigrokdecode4DSL/decoders/rgb_led_spi/__init__.py b/libsigrokdecode4DSL/decoders/rgb_led_spi/__init__.py new file mode 100644 index 00000000..1cf62eb8 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/rgb_led_spi/__init__.py @@ -0,0 +1,25 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 Matt Ranostay +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This decoder stacks on top of the 'spi' PD and decodes generic RGB LED string +values that are clocked over SPI in RGB values. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/rgb_led_spi/pd.py b/libsigrokdecode4DSL/decoders/rgb_led_spi/pd.py new file mode 100644 index 00000000..ee94c6bf --- /dev/null +++ b/libsigrokdecode4DSL/decoders/rgb_led_spi/pd.py @@ -0,0 +1,70 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 Matt Ranostay +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd + +class Decoder(srd.Decoder): + api_version = 3 + id = 'rgb_led_spi' + name = 'RGB LED (SPI)' + longname = 'RGB LED string decoder (SPI)' + desc = 'RGB LED string protocol (RGB values clocked over SPI).' + license = 'gplv2+' + inputs = ['spi'] + outputs = [] + tags = ['Display'] + annotations = ( + ('rgb', 'RGB values'), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.ss_cmd, self.es_cmd = 0, 0 + self.mosi_bytes = [] + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def putx(self, data): + self.put(self.ss_cmd, self.es_cmd, self.out_ann, data) + + def decode(self, ss, es, data): + ptype, mosi, miso = data + + # Only care about data packets. + if ptype != 'DATA': + return + self.ss, self.es = ss, es + + if len(self.mosi_bytes) == 0: + self.ss_cmd = ss + self.mosi_bytes.append(mosi) + + # RGB value == 3 bytes + if len(self.mosi_bytes) != 3: + return + + red, green, blue = self.mosi_bytes + rgb_value = int(red) << 16 | int(green) << 8 | int(blue) + + self.es_cmd = es + self.putx([0, ['#%.6x' % rgb_value]]) + self.mosi_bytes = [] diff --git a/libsigrokdecode4DSL/decoders/rgb_led_ws281x/__init__.py b/libsigrokdecode4DSL/decoders/rgb_led_ws281x/__init__.py new file mode 100644 index 00000000..20de109f --- /dev/null +++ b/libsigrokdecode4DSL/decoders/rgb_led_ws281x/__init__.py @@ -0,0 +1,27 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2016 Vladimir Ermakov +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 3 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +WS281x RGB LED protocol decoder. + +Details: +https://cpldcpu.wordpress.com/2014/01/14/light_ws2812-library-v2-0-part-i-understanding-the-ws2812/ +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/rgb_led_ws281x/pd.py b/libsigrokdecode4DSL/decoders/rgb_led_ws281x/pd.py new file mode 100644 index 00000000..0ea283ea --- /dev/null +++ b/libsigrokdecode4DSL/decoders/rgb_led_ws281x/pd.py @@ -0,0 +1,195 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2016 Vladimir Ermakov +## Copyright (C) 2019 DreamSourceLab +## Copyright (C) 2021 Michael Miller +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 3 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd +from functools import reduce + +class SamplerateError(Exception): + pass + +class Decoder(srd.Decoder): + api_version = 3 + id = 'rgb_led_ws281x' + name = 'RGB LED WS2812+' + longname = 'RGB LED color decoder' + desc = 'Decodes colors from bus pulses for single wire RGB leds like APA106, WS2811, WS2812, WS2813, SK6812, TM1829, TM1814, and TX1812.' + license = 'gplv3+' + inputs = ['logic'] + outputs = [] + tags = ['Display', 'IC'] + channels = ( + {'id': 'din', 'name': 'DIN', 'desc': 'DIN data line'}, + ) + options = ( + {'id': 'colors', 'desc': 'Colors', 'default': 'GRB', + 'values': ( 'GRB', 'RGB', 'BRG', 'RBG', 'BGR', 'GRBW', 'RGBW', 'WRGB', 'LBGR', 'LGRB', 'LRGB', 'LRBG', 'LGBR', 'LBRG')}, + {'id': 'polarity', 'desc': 'Polarity', 'default': 'normal', + 'values': ('normal', 'inverted')}, + ) + annotations = ( + ('bit', 'Bit'), + ('reset', 'RESET'), + ('rgb', 'RGB'), + ) + annotation_rows = ( + ('bit', 'Bits', (0, 1)), + ('rgb', 'RGB', (2,)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.state = 'FIND RESET' + self.samplerate = None + self.ss_packet = None + self.ss = None + self.es = None + self.bits = [] + self.bit_ = None + self.colorsize = None + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + + def metadata(self, key, value): + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + + def handle_bits(self, samplenum): + if len(self.bits) == self.colorsize: + elems = reduce(lambda a, b: (a << 1) | b, self.bits) + if self.colorsize == 24: + if self.options['colors'] == 'GRB': + rgb = (elems & 0xff0000) >> 8 | (elems & 0x00ff00) << 8 | (elems & 0x0000ff) + elif self.options['colors'] == 'RGB': + rgb = elems + elif self.options['colors'] == 'BRG': + rgb = (elems & 0xff0000) >> 16 | (elems & 0x00ffff) << 8 + elif self.options['colors'] == 'RBG': + rgb = (elems & 0xff0000) | (elems & 0x00ff00) >> 8 | (elems & 0x0000ff) << 8 + elif self.options['colors'] == 'BGR': + rgb = (elems & 0xff0000) >> 16 | (elems & 0x00ff00) | (elems & 0x0000ff) << 16 + + self.put(self.ss_packet, samplenum, self.out_ann, + [2, ['RGB#%06x' % rgb]]) + else: + if self.options['colors'] == 'GRBW': + rgb = (elems & 0xff000000) >> 16 | (elems & 0x00ff0000) | (elems & 0x0000ff00) >> 8 + w = (elems & 0x000000ff) + elif self.options['colors'] == 'RGBW': + rgb = (elems & 0xffffff00) >> 8 + w = (elems & 0x000000ff) + elif self.options['colors'] == 'WRGB': + rgb = (elems & 0x00ffffff) + w = (elems & 0xff000000) >> 32 + elif self.options['colors'] == 'LBGR': + rgb = (elems & 0x0000ff00) | (elems & 0x00ff0000) >> 16 | (elems & 0x000000ff) << 16 + w = (elems & 0xff000000) >> 32 + elif self.options['colors'] == 'LGRB': + rgb = (elems & 0x000000ff) | (elems & 0x00ff0000) >> 8 | (elems & 0x0000ff00) << 8 + w = (elems & 0xff000000) >> 32 + elif self.options['colors'] == 'LRGB': + rgb = (elems & 0x00ffffff) + w = (elems & 0xff000000) >> 32 + elif self.options['colors'] == 'LRBG': + rgb = (elems & 0x00ff0000) | (elems & 0x0000ff00) >> 8 | (elems & 0x000000ff) << 8 + w = (elems & 0xff000000) >> 32 + elif self.options['colors'] == 'LGBR': + rgb = (elems & 0x00ffff00) >> 8 | (elems & 0x000000ff) << 16 + w = (elems & 0xff000000) >> 32 + elif self.options['colors'] == 'LBRG': + rgb = (elems & 0x00ff0000) >> 16 | (elems & 0x0000ffff) << 8 + w = (elems & 0xff000000) >> 32 + + self.put(self.ss_packet, samplenum, self.out_ann, + [2, ['RGB#%06x #%02x' % (rgb, w)]]) + + self.bits = [] + self.ss_packet = samplenum + + def check_bit_(self, samplenum): + period = samplenum - self.ss + tH_samples = self.es - self.ss + tH = tH_samples / self.samplerate + if tH >= 625e-9: + self.bit_ = True + else: + # Ideal duty for T0H: 33%, T1H: 66%. + self.bit_ = (tH_samples / period) > 0.5 + + + def decode(self): + if not self.samplerate: + raise SamplerateError('Cannot decode without samplerate.') + + if len(self.options['colors']) == 4: + self.colorsize = 32 + else: + self.colorsize = 24 + + while True: + if self.state == 'FIND RESET': + self.wait({0: 'l' if self.options['polarity'] == 'normal' else 'h'}) + self.ss = self.samplenum + self.wait({0: 'e'}) + self.es = self.samplenum + if ((self.es - self.ss) / self.samplerate > 50e-6): + self.state = 'RESET' + elif ((self.es - self.ss) / self.samplerate > 3e-6): + self.bits = [] + self.ss = self.samplenum + self.ss_packet = self.samplenum + self.wait({0: 'e'}) + self.state = 'BIT FALLING' + elif self.state == 'RESET': + self.put(self.ss, self.es, self.out_ann, [1, ['RESET', 'RST', 'R']]) + self.bits = [] + self.ss = self.samplenum + self.ss_packet = self.samplenum + self.wait({0: 'e'}) + self.state = 'BIT FALLING' + elif self.state == 'BIT FALLING': + self.es = self.samplenum + self.wait({0: 'e'}) + if ((self.samplenum - self.es) / self.samplerate > 50e-6): + self.check_bit_(self.samplenum) + self.put(self.ss, self.es, self.out_ann, + [0, ['%d' % self.bit_]]) + self.bits.append(self.bit_) + self.handle_bits(self.es) + + self.ss = self.es + self.es = self.samplenum + self.state = 'RESET' + else: + self.state = 'BIT RISING' + elif self.state == 'BIT RISING': + self.check_bit_(self.samplenum) + self.put(self.ss, self.samplenum, self.out_ann, + [0, ['%d' % self.bit_]]) + self.bits.append(self.bit_) + self.handle_bits(self.samplenum) + + self.ss = self.samplenum + self.wait({0: 'e'}) + self.state = 'BIT FALLING' diff --git a/libsigrokdecode4DSL/decoders/rtc8564/__init__.py b/libsigrokdecode4DSL/decoders/rtc8564/__init__.py new file mode 100644 index 00000000..f17e7515 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/rtc8564/__init__.py @@ -0,0 +1,25 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2012 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This decoder stacks on top of the 'i2c' PD and decodes the Epson +RTC-8564 JE/NB real-time clock (RTC) protocol. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/rtc8564/pd.py b/libsigrokdecode4DSL/decoders/rtc8564/pd.py new file mode 100644 index 00000000..b57fae64 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/rtc8564/pd.py @@ -0,0 +1,254 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2012-2014 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd +from common.srdhelper import bcd2int + +def reg_list(): + l = [] + for i in range(8 + 1): + l.append(('reg-0x%02x' % i, 'Register 0x%02x' % i)) + + return tuple(l) + +class Decoder(srd.Decoder): + api_version = 3 + id = 'rtc8564' + name = 'RTC-8564' + longname = 'Epson RTC-8564 JE/NB' + desc = 'Realtime clock module protocol.' + license = 'gplv2+' + inputs = ['i2c'] + outputs = [] + tags = ['Clock/timing'] + annotations = reg_list() + ( + ('read', 'Read date/time'), + ('write', 'Write date/time'), + ('bit-reserved', 'Reserved bit'), + ('bit-vl', 'VL bit'), + ('bit-century', 'Century bit'), + ('reg-read', 'Register read'), + ('reg-write', 'Register write'), + ) + annotation_rows = ( + ('bits', 'Bits', tuple(range(0, 8 + 1)) + (11, 12, 13)), + ('regs', 'Register access', (14, 15)), + ('date-time', 'Date/time', (9, 10)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.state = 'IDLE' + self.hours = -1 + self.minutes = -1 + self.seconds = -1 + self.days = -1 + self.weekdays = -1 + self.months = -1 + self.years = -1 + self.bits = [] + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def putx(self, data): + self.put(self.ss, self.es, self.out_ann, data) + + def putd(self, bit1, bit2, data): + self.put(self.bits[bit1][1], self.bits[bit2][2], self.out_ann, data) + + def putr(self, bit): + self.put(self.bits[bit][1], self.bits[bit][2], self.out_ann, + [11, ['Reserved bit', 'Reserved', 'Rsvd', 'R']]) + + def handle_reg_0x00(self, b): # Control register 1 + pass + + def handle_reg_0x01(self, b): # Control register 2 + ti_tp = 1 if (b & (1 << 4)) else 0 + af = 1 if (b & (1 << 3)) else 0 + tf = 1 if (b & (1 << 2)) else 0 + aie = 1 if (b & (1 << 1)) else 0 + tie = 1 if (b & (1 << 0)) else 0 + + ann = '' + + s = 'repeated' if ti_tp else 'single-shot' + ann += 'TI/TP = %d: %s operation upon fixed-cycle timer interrupt '\ + 'events\n' % (ti_tp, s) + s = '' if af else 'no ' + ann += 'AF = %d: %salarm interrupt detected\n' % (af, s) + s = '' if tf else 'no ' + ann += 'TF = %d: %sfixed-cycle timer interrupt detected\n' % (tf, s) + s = 'enabled' if aie else 'prohibited' + ann += 'AIE = %d: INT# pin output %s when an alarm interrupt '\ + 'occurs\n' % (aie, s) + s = 'enabled' if tie else 'prohibited' + ann += 'TIE = %d: INT# pin output %s when a fixed-cycle interrupt '\ + 'event occurs\n' % (tie, s) + + self.putx([1, [ann]]) + + def handle_reg_0x02(self, b): # Seconds / Voltage-low bit + vl = 1 if (b & (1 << 7)) else 0 + self.putd(7, 7, [12, ['Voltage low: %d' % vl, 'Volt. low: %d' % vl, + 'VL: %d' % vl, 'VL']]) + s = self.seconds = bcd2int(b & 0x7f) + self.putd(6, 0, [2, ['Second: %d' % s, 'Sec: %d' % s, 'S: %d' % s, 'S']]) + + def handle_reg_0x03(self, b): # Minutes + self.putr(7) + m = self.minutes = bcd2int(b & 0x7f) + self.putd(6, 0, [3, ['Minute: %d' % m, 'Min: %d' % m, 'M: %d' % m, 'M']]) + + def handle_reg_0x04(self, b): # Hours + self.putr(7) + self.putr(6) + h = self.hours = bcd2int(b & 0x3f) + self.putd(5, 0, [4, ['Hour: %d' % h, 'H: %d' % h, 'H']]) + + def handle_reg_0x05(self, b): # Days + self.putr(7) + self.putr(6) + d = self.days = bcd2int(b & 0x3f) + self.putd(5, 0, [5, ['Day: %d' % d, 'D: %d' % d, 'D']]) + + def handle_reg_0x06(self, b): # Weekdays + for i in (7, 6, 5, 4, 3): + self.putr(i) + w = self.weekdays = bcd2int(b & 0x07) + self.putd(2, 0, [6, ['Weekday: %d' % w, 'WD: %d' % w, 'WD', 'W']]) + + def handle_reg_0x07(self, b): # Months / century bit + c = 1 if (b & (1 << 7)) else 0 + self.putd(7, 7, [13, ['Century bit: %d' % c, 'Century: %d' % c, + 'Cent: %d' % c, 'C: %d' % c, 'C']]) + self.putr(6) + self.putr(5) + m = self.months = bcd2int(b & 0x1f) + self.putd(4, 0, [7, ['Month: %d' % m, 'Mon: %d' % m, 'M: %d' % m, 'M']]) + + def handle_reg_0x08(self, b): # Years + y = self.years = bcd2int(b & 0xff) + self.putx([8, ['Year: %d' % y, 'Y: %d' % y, 'Y']]) + + def handle_reg_0x09(self, b): # Alarm, minute + pass + + def handle_reg_0x0a(self, b): # Alarm, hour + pass + + def handle_reg_0x0b(self, b): # Alarm, day + pass + + def handle_reg_0x0c(self, b): # Alarm, weekday + pass + + def handle_reg_0x0d(self, b): # CLKOUT output + pass + + def handle_reg_0x0e(self, b): # Timer setting + pass + + def handle_reg_0x0f(self, b): # Down counter for fixed-cycle timer + pass + + def decode(self, ss, es, data): + cmd, databyte = data + + # Collect the 'BITS' packet, then return. The next packet is + # guaranteed to belong to these bits we just stored. + if cmd == 'BITS': + self.bits = databyte + return + + # Store the start/end samples of this I²C packet. + self.ss, self.es = ss, es + + # State machine. + if self.state == 'IDLE': + # Wait for an I²C START condition. + if cmd != 'START': + return + self.state = 'GET SLAVE ADDR' + self.ss_block = ss + elif self.state == 'GET SLAVE ADDR': + # Wait for an address write operation. + # TODO: We should only handle packets to the RTC slave (0xa2/0xa3). + if cmd != 'ADDRESS WRITE': + return + self.state = 'GET REG ADDR' + elif self.state == 'GET REG ADDR': + # Wait for a data write (master selects the slave register). + if cmd != 'DATA WRITE': + return + self.reg = databyte + self.state = 'WRITE RTC REGS' + elif self.state == 'WRITE RTC REGS': + # If we see a Repeated Start here, it's probably an RTC read. + if cmd == 'START REPEAT': + self.state = 'READ RTC REGS' + return + # Otherwise: Get data bytes until a STOP condition occurs. + if cmd == 'DATA WRITE': + r, s = self.reg, '%02X: %02X' % (self.reg, databyte) + self.putx([15, ['Write register %s' % s, 'Write reg %s' % s, + 'WR %s' % s, 'WR', 'W']]) + handle_reg = getattr(self, 'handle_reg_0x%02x' % self.reg) + handle_reg(databyte) + self.reg += 1 + # TODO: Check for NACK! + elif cmd == 'STOP': + # TODO: Handle read/write of only parts of these items. + d = '%02d.%02d.%02d %02d:%02d:%02d' % (self.days, self.months, + self.years, self.hours, self.minutes, self.seconds) + self.put(self.ss_block, es, self.out_ann, + [9, ['Write date/time: %s' % d, 'Write: %s' % d, + 'W: %s' % d]]) + self.state = 'IDLE' + else: + pass # TODO + elif self.state == 'READ RTC REGS': + # Wait for an address read operation. + # TODO: We should only handle packets to the RTC slave (0xa2/0xa3). + if cmd == 'ADDRESS READ': + self.state = 'READ RTC REGS2' + return + else: + pass # TODO + elif self.state == 'READ RTC REGS2': + if cmd == 'DATA READ': + r, s = self.reg, '%02X: %02X' % (self.reg, databyte) + self.putx([15, ['Read register %s' % s, 'Read reg %s' % s, + 'RR %s' % s, 'RR', 'R']]) + handle_reg = getattr(self, 'handle_reg_0x%02x' % self.reg) + handle_reg(databyte) + self.reg += 1 + # TODO: Check for NACK! + elif cmd == 'STOP': + d = '%02d.%02d.%02d %02d:%02d:%02d' % (self.days, self.months, + self.years, self.hours, self.minutes, self.seconds) + self.put(self.ss_block, es, self.out_ann, + [10, ['Read date/time: %s' % d, 'Read: %s' % d, + 'R: %s' % d]]) + self.state = 'IDLE' + else: + pass # TODO? diff --git a/libsigrokdecode4DSL/decoders/sae_j1850_vpw/__init__.py b/libsigrokdecode4DSL/decoders/sae_j1850_vpw/__init__.py new file mode 100644 index 00000000..6894bfde --- /dev/null +++ b/libsigrokdecode4DSL/decoders/sae_j1850_vpw/__init__.py @@ -0,0 +1,24 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2016 Anthony Symons +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +SAE J1850 Variable Pulse Width decoder. Decode GM VPW 1X and 4X Vehicle Bus. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/sae_j1850_vpw/pd.py b/libsigrokdecode4DSL/decoders/sae_j1850_vpw/pd.py new file mode 100644 index 00000000..fd2389ec --- /dev/null +++ b/libsigrokdecode4DSL/decoders/sae_j1850_vpw/pd.py @@ -0,0 +1,165 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2016 Anthony Symons +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd + +class SamplerateError(Exception): + pass + +def timeuf(t): + return int (t * 1000.0 * 1000.0) + +class Ann: + ANN_RAW, ANN_SOF, ANN_IFS, ANN_DATA, \ + ANN_PACKET = range(5) + +class Decoder(srd.Decoder): + api_version = 3 + id = 'sae_j1850_vpw' + name = 'SAE J1850 VPW' + longname = 'SAE J1850 VPW.' + desc = 'SAE J1850 Variable Pulse Width 1x and 4x.' + license = 'gplv2+' + inputs = ['logic'] + outputs = [] + tags = ['Automotive'] + channels = ( + {'id': 'data', 'name': 'Data', 'desc': 'Data line'}, + ) + annotations = ( + ('raw', 'Raw'), + ('sof', 'SOF'), + ('ifs', 'EOF/IFS'), + ('data', 'Data'), + ('packet', 'Packet'), + ) + annotation_rows = ( + ('raws', 'Raws', (Ann.ANN_RAW, Ann.ANN_SOF, Ann.ANN_IFS,)), + ('bytes', 'Bytes', (Ann.ANN_DATA,)), + ('packets', 'Packets', (Ann.ANN_PACKET,)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.state = 'IDLE' + self.samplerate = None + self.byte = 0 # the byte offset in the packet + self.mode = 0 # for by packet decode + self.data = 0 # the current byte + self.datastart = 0 # sample number this byte started at + self.csa = 0 # track the last byte seperately to retrospectively add the CS marker + self.csb = 0 + self.count = 0 # which bit number we are up to + self.active = 0 # which logic level is considered active + + # vpw timings. ideal, min and max tollerances. + # From SAE J1850 1995 rev section 23.406 + + self.sof = 200 + self.sofl = 164 + self.sofh = 245 # 240 by the spec, 245 so a 60us 4x sample will pass + self.long = 128 + self.longl = 97 + self.longh = 170 # 164 by the spec but 170 for low sample rate tolerance. + self.short = 64 + self.shortl = 24 # 35 by the spec, 24 to allow down to 6us as measured in practice for 4x @ 1mhz sampling + self.shorth = 97 + self.ifs = 240 + self.spd = 1 # set to 4 when a 4x SOF is detected (VPW high speed frame) + + def handle_bit(self, ss, es, b): + self.data |= (b << 7-self.count) # MSB-first + self.put(ss, es, self.out_ann, [Ann.ANN_RAW, ["%d" % b]]) + if self.count == 0: + self.datastart = ss + if self.count == 7: + self.csa = self.datastart # for CS + self.csb = self.samplenum # for CS + self.put(self.datastart, self.samplenum, self.out_ann, [Ann.ANN_DATA, ["%02X" % self.data]]) + # add protocol parsing here + if self.byte == 0: + self.put(self.datastart, self.samplenum, self.out_ann, [Ann.ANN_PACKET, ['Priority','Prio','P']]) + elif self.byte == 1: + self.put(self.datastart, self.samplenum, self.out_ann, [Ann.ANN_PACKET, ['Destination','Dest','D']]) + elif self.byte == 2: + self.put(self.datastart, self.samplenum, self.out_ann, [Ann.ANN_PACKET, ['Source','Src','S']]) + elif self.byte == 3: + self.put(self.datastart, self.samplenum, self.out_ann, [Ann.ANN_PACKET, ['Mode','M']]) + self.mode = self.data + elif self.mode == 1 and self.byte == 4: # mode 1 payload + self.put(self.datastart, self.samplenum, self.out_ann, [Ann.ANN_PACKET, ['Pid','P']]) + + # prepare for next byte + self.count = -1 + self.data = 0 + self.byte = self.byte + 1 # track packet offset + self.count = self.count + 1 + + def metadata(self, key, value): + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def decode(self): + if not self.samplerate: + raise SamplerateError('Cannot decode without samplerate.') + + self.wait({0: 'e'}) + es = self.samplenum + while True: + ss = es + pin, = self.wait({0: 'e'}) + es = self.samplenum + + samples = es - ss + t = timeuf(samples / self.samplerate) + if self.state == 'IDLE': # detect and set speed from the size of sof + if pin == self.active and t in range(self.sofl , self.sofh): + self.put(ss, es, self.out_ann, [Ann.ANN_RAW, ['1X SOF', 'S1', 'S']]) + self.spd = 1 + self.data = 0 + self.count = 0 + self.state = 'DATA' + elif pin == self.active and t in range(int(self.sofl / 4) , int(self.sofh / 4)): + self.put(ss, es, self.out_ann, [Ann.ANN_RAW, ['4X SOF', 'S4', '4']]) + self.spd = 4 + self.data = 0 + self.count = 0 + self.state = 'DATA' + + elif self.state == 'DATA': + if t >= int(self.ifs / self.spd): + self.state = 'IDLE' + self.put(ss, es, self.out_ann, [Ann.ANN_RAW, ["EOF/IFS", "E"]]) # EOF=239-280 IFS=281+ + self.put(self.csa, self.csb, self.out_ann, [Ann.ANN_PACKET, ['Checksum','CS','C']]) # retrospective print of CS + self.byte = 0 # reset packet offset + elif t in range(int(self.shortl / self.spd), int(self.shorth / self.spd)): + if pin == self.active: + self.handle_bit(ss, es, 1) + else: + self.handle_bit(ss, es, 0) + elif t in range(int(self.longl / self.spd), int(self.longh / self.spd)): + if pin == self.active: + self.handle_bit(ss, es, 0) + else: + self.handle_bit(ss, es, 1) diff --git a/libsigrokdecode4DSL/decoders/sda2506/__init__.py b/libsigrokdecode4DSL/decoders/sda2506/__init__.py new file mode 100644 index 00000000..bf555109 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/sda2506/__init__.py @@ -0,0 +1,24 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2018 Max Weller +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +Decoder for Siemens EEPROM SDA 2506-5. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/sda2506/pd.py b/libsigrokdecode4DSL/decoders/sda2506/pd.py new file mode 100644 index 00000000..813bff6a --- /dev/null +++ b/libsigrokdecode4DSL/decoders/sda2506/pd.py @@ -0,0 +1,144 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2018 Max Weller +## Copyright (C) 2019 DreamSourceLab +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import re +import sigrokdecode as srd + +ann_cmdbit, ann_databit, ann_cmd, ann_data, ann_warning = range(5) + +class Decoder(srd.Decoder): + api_version = 3 + id = 'sda2506' + name = 'SDA2506' + longname = 'Siemens SDA 2506-5' + desc = 'Serial nonvolatile 1-Kbit EEPROM.' + license = 'gplv2+' + inputs = ['logic'] + outputs = [] + tags = ['IC', 'Memory'] + channels = ( + {'id': 'clk', 'name': 'CLK', 'desc': 'Clock'}, + {'id': 'd', 'name': 'DATA', 'desc': 'Data'}, + {'id': 'ce', 'name': 'CE#', 'desc': 'Chip-enable'}, + ) + annotations = ( + ('cmdbit', 'Command bit'), + ('databit', 'Data bit'), + ('cmd', 'Command'), + ('data', 'Data byte'), + ('warnings', 'Human-readable warnings'), + ) + annotation_rows = ( + ('bits', 'Bits', (ann_cmdbit, ann_databit)), + ('commands', 'Commands', (ann_cmd,)), + ('data', 'Data', (ann_data,)), + ('warnings', 'Warnings', (ann_warning,)), + ) + + def __init__(self): + self.samplerate = None + self.reset() + + def reset(self): + self.cmdbits = [] + self.databits = [] + + def metadata(self, key, value): + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def putbit(self, ss, es, typ, value): + self.put(ss, es, self.out_ann, [typ, ['%s' % (value)]]) + + def putdata(self, ss, es): + value = 0 + for i in range(8): + value = (value << 1) | self.databits[i] + self.put(ss, es, self.out_ann, [ann_data, ['%02X' % (value)]]) + + def decode_bits(self, offset, width): + out = 0 + for i in range(width): + out = (out << 1) | self.cmdbits[offset + i][0] + return (out, self.cmdbits[offset + width - 1][1], self.cmdbits[offset][2]) + + def decode_field(self, name, offset, width): + val, ss, es = self.decode_bits(offset, width) + self.put(ss, es, self.out_ann, [ann_data, ['%s: %02X' % (name, val)]]) + return val + + def decode(self): + while True: + # Wait for CLK edge or CE edge. + (clk, d, ce) = self.wait([{0: 'e'}, {2: 'e'}]) + + if (self.matched & (0b1 << 0)) and ce == 1 and clk == 1: + # Rising clk edge and command mode. + bitstart = self.samplenum + self.wait({0: 'f'}) + self.cmdbits = [(d, bitstart, self.samplenum)] + self.cmdbits + if len(self.cmdbits) > 24: + self.cmdbits = self.cmdbits[0:24] + self.putbit(bitstart, self.samplenum, ann_cmdbit, d) + elif (self.matched & (0b1 << 0)) and ce == 0 and clk == 0: + # Falling clk edge and data mode. + bitstart = self.samplenum + (clk, d, ce) = self.wait([{'skip': int(2.5 * (1e6 / self.samplerate))}, {0: 'r'}, {2: 'e'}]) # Wait 25 us for data ready. + if (self.matched & (0b1 << 2)) and not (self.matched & 0b011): + self.wait([{0: 'r'}, {2: 'e'}]) + if len(self.databits) == 0: + self.datastart = bitstart + self.databits = [d] + self.databits + self.putbit(bitstart, self.samplenum, ann_databit, d) + if len(self.databits) == 8: + self.putdata(self.datastart, self.samplenum) + self.databits = [] + elif (self.matched & (0b1 << 1)) and ce == 0: + # Chip enable edge. + try: + self.decode_field('addr', 1, 7) + self.decode_field('CB', 0, 1) + if self.cmdbits[0][0] == 0: + # Beginning read command. + self.decode_field('read', 1, 7) + self.put(self.cmdbits[7][1], self.samplenum, + self.out_ann, [ann_cmd, ['read' ]]) + elif d == 0: + # Beginning write command. + self.decode_field('data', 8, 8) + addr, ss, es = self.decode_bits(1, 7) + data, ss, es = self.decode_bits(8, 8) + cmdstart = self.samplenum + self.wait({2: 'r'}) + self.put(cmdstart, self.samplenum, self.out_ann, + [ann_cmd, ['Write to %02X: %02X' % (addr, data)]]) + else: + # Beginning erase command. + val, ss, es = self.decode_bits(1, 7) + cmdstart = self.samplenum + self.wait({2: 'r'}) + self.put(cmdstart, self.samplenum, self.out_ann, + [ann_cmd, ['Erase: %02X' % (val)]]) + self.databits = [] + except Exception as ex: + self.reset() diff --git a/libsigrokdecode4DSL/decoders/sdcard_sd/__init__.py b/libsigrokdecode4DSL/decoders/sdcard_sd/__init__.py new file mode 100644 index 00000000..7c334222 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/sdcard_sd/__init__.py @@ -0,0 +1,24 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2015 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +SD card (SD mode) low-level protocol decoder. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/sdcard_sd/pd.py b/libsigrokdecode4DSL/decoders/sdcard_sd/pd.py new file mode 100644 index 00000000..292d0a6c --- /dev/null +++ b/libsigrokdecode4DSL/decoders/sdcard_sd/pd.py @@ -0,0 +1,583 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2015-2020 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd +from common.srdhelper import SrdIntEnum, SrdStrEnum +from common.sdcard import (cmd_names, acmd_names, accepted_voltages, sd_status) + +responses = '1 1b 2 3 6 7'.split() +token_fields = 'START TRANSMISSION CMD ARG CRC END'.split() +reg_card_status = 'OUT_OF_RANGE ADDRESS_ERROR BLOCK_LEN_ERROR ERASE_SEQ_ERROR \ + ERASE_PARAM WP_VIOLATION CARD_IS_LOCKED LOCK_UNLOCK_FAILED COM_CRC_ERROR \ + ILLEGAL_COMMAND CARD_ECC_FAILED CC_ERROR ERROR RSVD_DEFERRED_RESPONSE \ + CSD_OVERWRITE WP_ERASE_SKIP CARD_ECC_DISABLED ERASE_RESET CURRENT_STATE \ + READY_FOR_DATA RSVD FX_EVENT APP_CMD RSVD_SDIO AKE_SEQ_ERROR RSVD_APP_CMD \ + RSVD_TESTMODE'.split() +reg_cid = 'MID OID PNM PRV PSN RSVD MDT CRC ONE'.split() +reg_csd = 'CSD_STRUCTURE RSVD TAAC NSAC TRAN_SPEED CCC READ_BL_LEN \ + READ_BL_PARTIAL WRITE_BLK_MISALIGN READ_BLK_MISALIGN DSR_IMP C_SIZE \ + VDD_R_CURR_MIN VDD_R_CURR_MAX VDD_W_CURR_MIN VDD_W_CURR_MAX C_SIZE_MULT \ + ERASE_BLK_EN SECTOR_SIZE WP_GRP_SIZE WP_GRP_ENABLE R2W_FACTOR \ + WRITE_BL_LEN WRITE_BL_PARTIAL FILE_FORMAT_GRP COPY PERM_WRITE_PROTECT \ + TMP_WRITE_PROTECT FILE_FORMAT CRC ONE'.split() + +Pin = SrdIntEnum.from_str('Pin', 'CMD CLK DAT0 DAT1 DAT2 DAT3') + +a = ['CMD%d' % i for i in range(64)] + ['ACMD%d' % i for i in range(64)] + \ + ['RESPONSE_R' + r.upper() for r in responses] + \ + ['R_STATUS_' + r for r in reg_card_status] + \ + ['R_CID_' + r for r in reg_cid] + \ + ['R_CSD_' + r for r in reg_csd] + \ + ['BIT_' + r for r in ('0', '1')] + \ + ['F_' + f for f in token_fields] + \ + ['DECODED_BIT', 'DECODED_F'] +Ann = SrdIntEnum.from_list('Ann', a) + +s = ['GET_COMMAND_TOKEN', 'HANDLE_CMD999'] + \ + ['HANDLE_CMD%d' % i for i in range(64)] + \ + ['HANDLE_ACMD%d' % i for i in range(64)] + \ + ['GET_RESPONSE_R%s' % r.upper() for r in responses] +St = SrdStrEnum.from_list('St', s) + +class Bit: + def __init__(self, s, e, b): + self.ss, self.es, self.bit = s, e ,b + +class Decoder(srd.Decoder): + api_version = 3 + id = 'sdcard_sd' + name = 'SD card (SD mode)' + longname = 'Secure Digital card (SD mode)' + desc = 'Secure Digital card (SD mode) low-level protocol.' + license = 'gplv2+' + inputs = ['logic'] + outputs = [] + tags = ['Memory'] + channels = ( + {'id': 'cmd', 'name': 'CMD', 'desc': 'Command'}, + {'id': 'clk', 'name': 'CLK', 'desc': 'Clock'}, + ) + optional_channels = ( + {'id': 'dat0', 'name': 'DAT0', 'desc': 'Data pin 0'}, + {'id': 'dat1', 'name': 'DAT1', 'desc': 'Data pin 1'}, + {'id': 'dat2', 'name': 'DAT2', 'desc': 'Data pin 2'}, + {'id': 'dat3', 'name': 'DAT3', 'desc': 'Data pin 3'}, + ) + annotations = \ + tuple(('cmd%d' % i, 'CMD%d' % i) for i in range(64)) + \ + tuple(('acmd%d' % i, 'ACMD%d' % i) for i in range(64)) + \ + tuple(('response_r%s' % r, 'R%s' % r) for r in responses) + \ + tuple(('reg_status_' + r.lower(), 'Status: ' + r) for r in reg_card_status) + \ + tuple(('reg_cid_' + r.lower(), 'CID: ' + r) for r in reg_cid) + \ + tuple(('reg_csd_' + r.lower(), 'CSD: ' + r) for r in reg_csd) + \ + tuple(('bit_' + r, 'Bit ' + r) for r in ('0', '1')) + \ + tuple(('field-' + r.lower(), r) for r in token_fields) + \ + ( \ + ('decoded-bit', 'Decoded bit'), + ('decoded-field', 'Decoded field'), + ) + annotation_rows = ( + ('raw-bits', 'Raw bits', Ann.prefixes('BIT_')), + ('decoded-bits', 'Decoded bits', (Ann.DECODED_BIT,) + Ann.prefixes('R_')), + ('decoded-fields', 'Decoded fields', (Ann.DECODED_F,)), + ('fields', 'Fields', Ann.prefixes('F_')), + ('commands', 'Commands', Ann.prefixes('CMD ACMD RESPONSE_')), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.state = St.GET_COMMAND_TOKEN + self.token = [] + self.is_acmd = False # Indicates CMD vs. ACMD + self.cmd = None + self.last_cmd = None + self.arg = None + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def putt(self, data): + self.put(self.token[0].ss, self.token[47].es, self.out_ann, data) + + def putf(self, s, e, data): + self.put(self.token[s].ss, self.token[e].es, self.out_ann, data) + + def puta(self, s, e, data): + self.put(self.token[47 - 8 - e].ss, self.token[47 - 8 - s].es, + self.out_ann, data) + + def putc(self, desc): + cmd = Ann.ACMD0 + self.cmd if self.is_acmd else self.cmd + self.last_cmd = cmd + self.putt([cmd, ['%s: %s' % (self.cmd_str, desc), self.cmd_str, + self.cmd_str.split(' ')[0]]]) + + def putr(self, r): + self.putt([r, ['Response: %s' % r.name.split('_')[1]]]) + + def cmd_name(self, cmd): + c = acmd_names if self.is_acmd else cmd_names + return c.get(cmd, 'Unknown') + + def get_token_bits(self, cmd_pin, n): + # Get a bit, return True if we already got 'n' bits, False otherwise. + self.token.append(Bit(self.samplenum, self.samplenum, cmd_pin)) + if len(self.token) > 0: + self.token[len(self.token) - 2].es = self.samplenum + if len(self.token) < n: + return False + self.token[n - 1].es += self.token[n - 1].ss - self.token[n - 2].ss + return True + + def is_from_host(self): + # CMD[46:46]: Transmission bit (1 == host), (0 == card) + return self.token[1].bit == 1 + + def is_from_card(self): + # CMD[46:46]: Transmission bit (1 == host), (0 == card) + return self.token[1].bit == 0 + + def handle_common_token_fields(self): + s = self.token + + # Annotations for each individual bit. + for bit in range(len(self.token)): + self.putf(bit, bit, [Ann.BIT_0 + s[bit].bit, ['%d' % s[bit].bit]]) + + # CMD[47:47]: Start bit (always 0) + self.putf(0, 0, [Ann.F_START, ['Start bit', 'Start', 'S']]) + + # CMD[46:46]: Transmission bit (1 == host) + t = 'host' if s[1].bit == 1 else 'card' + self.putf(1, 1, [Ann.F_TRANSMISSION, ['Transmission: ' + t, 'T: ' + t, 'T']]) + + # CMD[45:40]: Command index (BCD; valid: 0-63) + self.cmd = int('0b' + ''.join([str(s[i].bit) for i in range(2, 8)]), 2) + c = '%s (%d)' % (self.cmd_name(self.cmd), self.cmd) + self.putf(2, 7, [Ann.F_CMD, ['Command: ' + c, 'Cmd: ' + c, + 'CMD%d' % self.cmd, 'Cmd', 'C']]) + + # CMD[39:08]: Argument + self.arg = int('0b' + ''.join([str(s[i].bit) for i in range(8, 40)]), 2) + self.putf(8, 39, [Ann.F_ARG, ['Argument: 0x%08x' % self.arg, 'Arg', 'A']]) + + # CMD[07:01]: CRC7 + self.crc = int('0b' + ''.join([str(s[i].bit) for i in range(40, 47)]), 2) + self.putf(40, 46, [Ann.F_CRC, ['CRC: 0x%x' % self.crc, 'CRC', 'C']]) + + # CMD[00:00]: End bit (always 1) + self.putf(47, 47, [Ann.F_END, ['End bit', 'End', 'E']]) + + def get_command_token(self, cmd_pin): + # Command tokens (48 bits) are sent serially (MSB-first) by the host + # (over the CMD line), either to one SD card or to multiple ones. + # + # Format: + # - Bits[47:47]: Start bit (always 0) + # - Bits[46:46]: Transmission bit (1 == host) + # - Bits[45:40]: Command index (BCD; valid: 0-63) + # - Bits[39:08]: Argument + # - Bits[07:01]: CRC7 + # - Bits[00:00]: End bit (always 1) + + if not self.get_token_bits(cmd_pin, 48): + return + if not self.is_from_host(): + # Bad state due to a decoding mistake or a protocol error, + # drop the current token to try to recover from this situation. + self.token, self.state = [], St.GET_COMMAND_TOKEN + return + + self.handle_cmd() + + def handle_cmd(self): + self.handle_common_token_fields() + + # Handle command. + s = 'ACMD' if self.is_acmd else 'CMD' + self.cmd_str = '%s%d (%s)' % (s, self.cmd, self.cmd_name(self.cmd)) + if hasattr(self, 'handle_%s%d' % (s.lower(), self.cmd)): + self.state = St['HANDLE_CMD%d' % self.cmd] + else: + self.state = St.HANDLE_CMD999 + self.putc('%s%d' % (s, self.cmd)) + + def handle_cmd0(self): + # CMD0 (GO_IDLE_STATE) -> no response + self.puta(0, 31, [Ann.DECODED_F, ['Stuff bits', 'Stuff', 'SB', 'S']]) + self.putc('Reset all SD cards') + self.token, self.state = [], St.GET_COMMAND_TOKEN + + def handle_cmd2(self): + # CMD2 (ALL_SEND_CID) -> R2 + self.puta(0, 31, [Ann.DECODED_F, ['Stuff bits', 'Stuff', 'SB', 'S']]) + self.putc('Ask card for CID number') + self.token, self.state = [], St.GET_RESPONSE_R2 + + def handle_cmd3(self): + # CMD3 (SEND_RELATIVE_ADDR) -> R6 + self.puta(0, 31, [Ann.DECODED_F, ['Stuff bits', 'Stuff', 'SB', 'S']]) + self.putc('Ask card for new relative card address (RCA)') + self.token, self.state = [], St.GET_RESPONSE_R6 + + def handle_cmd6(self): + # CMD6 (SWITCH_FUNC) -> R1 + self.putc('Switch/check card function') + self.token, self.state = [], St.GET_RESPONSE_R1 + + def handle_cmd7(self): + # CMD7 (SELECT/DESELECT_CARD) -> R1b + self.putc('Select / deselect card') + self.token, self.state = [], St.GET_RESPONSE_R6 + + def handle_cmd8(self): + # CMD8 (SEND_IF_COND) -> R7 + self.puta(12, 31, [Ann.DECODED_F, ['Reserved', 'Res', 'R']]) + self.puta(8, 11, [Ann.DECODED_F, ['Supply voltage', 'Voltage', 'VHS', 'V']]) + self.puta(0, 7, [Ann.DECODED_F, ['Check pattern', 'Check pat', 'Check', 'C']]) + self.putc('Send interface condition to card') + self.token, self.state = [], St.GET_RESPONSE_R7 + # TODO: Handle case when card doesn't reply with R7 (no reply at all). + + def handle_cmd9(self): + # CMD9 (SEND_CSD) -> R2 + self.puta(16, 31, [Ann.DECODED_F, ['RCA', 'R']]) + self.puta(0, 15, [Ann.DECODED_F, ['Stuff bits', 'Stuff', 'SB', 'S']]) + self.putc('Send card-specific data (CSD)') + self.token, self.state = [], St.GET_RESPONSE_R2 + + def handle_cmd10(self): + # CMD10 (SEND_CID) -> R2 + self.puta(16, 31, [Ann.DECODED_F, ['RCA', 'R']]) + self.puta(0, 15, [Ann.DECODED_F, ['Stuff bits', 'Stuff', 'SB', 'S']]) + self.putc('Send card identification data (CID)') + self.token, self.state = [], St.GET_RESPONSE_R2 + + def handle_cmd13(self): + # CMD13 (SEND_STATUS) -> R1 + self.puta(16, 31, [Ann.DECODED_F, ['RCA', 'R']]) + self.puta(0, 15, [Ann.DECODED_F, ['Stuff bits', 'Stuff', 'SB', 'S']]) + self.putc('Send card status register') + self.token, self.state = [], St.GET_RESPONSE_R1 + + def handle_cmd16(self): + # CMD16 (SET_BLOCKLEN) -> R1 + self.puta(0, 31, [Ann.DECODED_F, ['Block length', 'Blocklen', 'BL', 'B']]) + self.putc('Set the block length to %d bytes' % self.arg) + self.token, self.state = [], St.GET_RESPONSE_R1 + + def handle_cmd55(self): + # CMD55 (APP_CMD) -> R1 + self.puta(16, 31, [Ann.DECODED_F, ['RCA', 'R']]) + self.puta(0, 15, [Ann.DECODED_F, ['Stuff bits', 'Stuff', 'SB', 'S']]) + self.putc('Next command is an application-specific command') + self.is_acmd = True + self.token, self.state = [], St.GET_RESPONSE_R1 + + def handle_acmd6(self): + # ACMD6 (SET_BUS_WIDTH) -> R1 + self.putc('Read SD config register (SCR)') + self.token, self.state = [], St.GET_RESPONSE_R1 + + def handle_acmd13(self): + # ACMD13 (SD_STATUS) -> R1 + self.puta(0, 31, [Ann.DECODED_F, ['Stuff bits', 'Stuff', 'SB', 'S']]) + self.putc('Set SD status') + self.token, self.state = [], St.GET_RESPONSE_R1 + + def handle_acmd41(self): + # ACMD41 (SD_SEND_OP_COND) -> R3 + self.puta(0, 23, [Ann.DECODED_F, + ['VDD voltage window', 'VDD volt', 'VDD', 'V']]) + self.puta(24, 24, [Ann.DECODED_F, ['S18R']]) + self.puta(25, 27, [Ann.DECODED_F, ['Reserved', 'Res', 'R']]) + self.puta(28, 28, [Ann.DECODED_F, ['XPC']]) + self.puta(29, 29, [Ann.DECODED_F, + ['Reserved for eSD', 'Reserved', 'Res', 'R']]) + self.puta(30, 30, [Ann.DECODED_F, + ['Host capacity support info', 'Host capacity', 'HCS', 'H']]) + self.puta(31, 31, [Ann.DECODED_F, ['Reserved', 'Res', 'R']]) + self.putc('Send HCS info and activate the card init process') + self.token, self.state = [], St.GET_RESPONSE_R3 + + def handle_acmd51(self): + # ACMD51 (SEND_SCR) -> R1 + self.putc('Read SD config register (SCR)') + self.token, self.state = [], St.GET_RESPONSE_R1 + + def handle_cmd999(self): + self.token, self.state = [], St.GET_RESPONSE_R1 + + def handle_acmd999(self): + self.token, self.state = [], St.GET_RESPONSE_R1 + + def handle_reg_status(self): + self.putf(8, 8, [Ann.R_STATUS_OUT_OF_RANGE, ['OUT_OF_RANGE']]) + self.putf(9, 9, [Ann.R_STATUS_ADDRESS_ERROR, ['ADDRESS_ERROR']]) + self.putf(10, 10, [Ann.R_STATUS_BLOCK_LEN_ERROR, ['BLOCK_LEN_ERROR']]) + self.putf(11, 11, [Ann.R_STATUS_ERASE_SEQ_ERROR, ['ERASE_SEQ_ERROR']]) + self.putf(12, 12, [Ann.R_STATUS_ERASE_PARAM, ['ERASE_PARAM']]) + self.putf(13, 13, [Ann.R_STATUS_WP_VIOLATION, ['WP_VIOLATION']]) + self.putf(14, 14, [Ann.R_STATUS_CARD_IS_LOCKED, ['CARD_IS_LOCKED']]) + self.putf(15, 15, [Ann.R_STATUS_LOCK_UNLOCK_FAILED, ['LOCK_UNLOCK_FAILED']]) + self.putf(16, 16, [Ann.R_STATUS_COM_CRC_ERROR, ['COM_CRC_ERROR']]) + self.putf(17, 17, [Ann.R_STATUS_ILLEGAL_COMMAND, ['ILLEGAL_COMMAND']]) + self.putf(18, 18, [Ann.R_STATUS_CARD_ECC_FAILED, ['CARD_ECC_FAILED']]) + self.putf(19, 19, [Ann.R_STATUS_CC_ERROR, ['CC_ERROR']]) + self.putf(20, 20, [Ann.R_STATUS_ERROR, ['ERROR']]) + self.putf(21, 21, [Ann.R_STATUS_RSVD, ['Reserved', 'RSVD', 'R']]) + self.putf(22, 22, [Ann.R_STATUS_RSVD_DEFERRED_RESPONSE, ['Reserved for DEFERRED_RESPONSE', 'RSVD_DEFERRED_RESPONSE']]) + self.putf(23, 23, [Ann.R_STATUS_CSD_OVERWRITE, ['CSD_OVERWRITE']]) + self.putf(24, 24, [Ann.R_STATUS_WP_ERASE_SKIP, ['WP_ERASE_SKIP']]) + self.putf(25, 25, [Ann.R_STATUS_CARD_ECC_DISABLED, ['CARD_ECC_DISABLED']]) + self.putf(26, 26, [Ann.R_STATUS_ERASE_RESET, ['ERASE_RESET']]) + self.putf(27, 30, [Ann.R_STATUS_CURRENT_STATE, ['CURRENT_STATE']]) + self.putf(31, 31, [Ann.R_STATUS_READY_FOR_DATA, ['READY_FOR_DATA']]) + self.putf(32, 32, [Ann.R_STATUS_RSVD, ['RSVD']]) + self.putf(33, 33, [Ann.R_STATUS_FX_EVENT, ['FX_EVENT']]) + self.putf(34, 34, [Ann.R_STATUS_APP_CMD, ['APP_CMD']]) + self.putf(35, 35, [Ann.R_STATUS_RSVD_SDIO, ['Reserved for SDIO card', 'RSVD_SDIO']]) + self.putf(36, 36, [Ann.R_STATUS_AKE_SEQ_ERROR, ['AKE_SEQ_ERROR']]) + self.putf(37, 37, [Ann.R_STATUS_RSVD_APP_CMD, ['Reserved for application specific commands', 'RSVD_APP_CMD']]) + self.putf(38, 39, [Ann.R_STATUS_RSVD_TESTMODE, ['Reserved for manufacturer test mode', 'RSVD_TESTMODE']]) + + def handle_reg_cid(self): + self.putf(8, 15, [Ann.R_CID_MID, ['Manufacturer ID', 'MID']]) + self.putf(16, 31, [Ann.R_CID_OID, ['OEM/application ID', 'OID']]) + self.putf(32, 71, [Ann.R_CID_PNM, ['Product name', 'PNM']]) + self.putf(72, 79, [Ann.R_CID_PRV, ['Product revision', 'PRV']]) + self.putf(80, 111, [Ann.R_CID_PSN, ['Product serial number', 'PSN']]) + self.putf(112, 115, [Ann.R_CID_RSVD, ['Reserved', 'RSVD', 'R']]) + self.putf(116, 127, [Ann.R_CID_MDT, ['Manufacturing date', 'MDT']]) + self.putf(128, 134, [Ann.R_CID_CRC, ['CRC7 checksum', 'CRC']]) + self.putf(135, 135, [Ann.R_CID_ONE, ['Always 1', '1']]) + + def handle_reg_csd(self): + self.putf(8, 9, [Ann.R_CSD_CSD_STRUCTURE, ['CSD structure', 'CSD_STRUCTURE']]) + self.putf(10, 15, [Ann.R_CSD_RSVD, ['Reserved', 'RSVD', 'R']]) + self.putf(16, 23, [Ann.R_CSD_TAAC, ['Data read access-time - 1', 'TAAC']]) + self.putf(24, 31, [Ann.R_CSD_NSAC, ['Data read access-time - 2 in CLK cycles (NSAC * 100)', 'NSAC']]) + self.putf(32, 39, [Ann.R_CSD_TRAN_SPEED, ['Max. data transfer rate', 'TRAN_SPEED']]) + self.putf(40, 51, [Ann.R_CSD_CCC, ['Card command classes', 'CCC']]) + self.putf(52, 55, [Ann.R_CSD_READ_BL_LEN, ['Max. read data block length', 'READ_BL_LEN']]) + self.putf(56, 56, [Ann.R_CSD_READ_BL_PARTIAL, ['Partial blocks for read allowed', 'READ_BL_PARTIAL']]) + self.putf(57, 57, [Ann.R_CSD_WRITE_BLK_MISALIGN, ['Write block misalignment', 'WRITE_BLK_MISALIGN']]) + self.putf(58, 58, [Ann.R_CSD_READ_BLK_MISALIGN, ['Read block misalignment', 'READ_BLK_MISALIGN']]) + self.putf(59, 59, [Ann.R_CSD_DSR_IMP, ['DSR implemented', 'DSR_IMP']]) + self.putf(60, 61, [Ann.R_CSD_RSVD, ['Reserved', 'RSVD', 'R']]) + self.putf(62, 73, [Ann.R_CSD_C_SIZE, ['Device size', 'C_SIZE']]) + self.putf(74, 76, [Ann.R_CSD_VDD_R_CURR_MIN, ['Max. read current @VDD min', 'VDD_R_CURR_MIN']]) + self.putf(77, 79, [Ann.R_CSD_VDD_R_CURR_MAX, ['Max. read current @VDD max', 'VDD_R_CURR_MAX']]) + self.putf(80, 82, [Ann.R_CSD_VDD_W_CURR_MIN, ['Max. write current @VDD min', 'VDD_W_CURR_MIN']]) + self.putf(83, 85, [Ann.R_CSD_VDD_W_CURR_MAX, ['Max. write current @VDD max', 'VDD_W_CURR_MAX']]) + self.putf(86, 88, [Ann.R_CSD_C_SIZE_MULT, ['Device size multiplier', 'C_SIZE_MULT']]) + self.putf(89, 89, [Ann.R_CSD_ERASE_BLK_EN, ['Erase single block enable', 'ERASE_BLK_EN']]) + self.putf(90, 96, [Ann.R_CSD_SECTOR_SIZE, ['Erase sector size', 'SECTOR_SIZE']]) + self.putf(97, 103, [Ann.R_CSD_WP_GRP_SIZE, ['Write protect group size', 'WP_GRP_SIZE']]) + self.putf(104, 104, [Ann.R_CSD_WP_GRP_ENABLE, ['Write protect group enable', 'WP_GRP_ENABLE']]) + self.putf(105, 106, [Ann.R_CSD_RSVD, ['Reserved', 'RSVD', 'R']]) + self.putf(107, 109, [Ann.R_CSD_R2W_FACTOR, ['Write speed factor', 'R2W_FACTOR']]) + self.putf(110, 113, [Ann.R_CSD_WRITE_BL_LEN, ['Max. write data block length', 'WRITE_BL_LEN']]) + self.putf(114, 114, [Ann.R_CSD_WRITE_BL_PARTIAL, ['Partial blocks for write allowed', 'WRITE_BL_PARTIAL']]) + self.putf(115, 119, [Ann.R_CSD_RSVD, ['Reserved', 'RSVD']]) + self.putf(120, 120, [Ann.R_CSD_FILE_FORMAT_GRP, ['File format group', 'FILE_FORMAT_GRP']]) + self.putf(121, 121, [Ann.R_CSD_COPY, ['Copy flag', 'COPY']]) + self.putf(122, 122, [Ann.R_CSD_PERM_WRITE_PROTECT, ['Permanent write protection', 'PERM_WRITE_PROTECT']]) + self.putf(123, 123, [Ann.R_CSD_TMP_WRITE_PROTECT, ['Temporary write protection', 'TMP_WRITE_PROTECT']]) + self.putf(124, 125, [Ann.R_CSD_FILE_FORMAT, ['File format', 'FILE_FORMAT']]) + self.putf(126, 127, [Ann.R_CSD_RSVD, ['Reserved', 'RSVD', 'R']]) + self.putf(128, 134, [Ann.R_CSD_CRC, ['CRC', 'CRC', 'C']]) + self.putf(135, 135, [Ann.R_CSD_ONE, ['Always 1', '1']]) + + # Response tokens can have one of four formats (depends on content). + # They can have a total length of 48 or 136 bits. + # They're sent serially (MSB-first) by the card that the host + # addressed previously, or (synchronously) by all connected cards. + + def handle_response_r1(self, cmd_pin): + # R1: Normal response command + # - Bits[47:47]: Start bit (always 0) + # - Bits[46:46]: Transmission bit (0 == card) + # - Bits[45:40]: Command index (BCD; valid: 0-63) + # - Bits[39:08]: Card status + # - Bits[07:01]: CRC7 + # - Bits[00:00]: End bit (always 1) + if not self.get_token_bits(cmd_pin, 48): + return + assert(self.is_from_card()) + self.handle_common_token_fields() + self.putr(Ann.RESPONSE_R1) + self.puta(0, 31, [Ann.DECODED_F, ['Card status', 'Status', 'S']]) + self.handle_reg_status() + + self.token, self.state = [], St.GET_COMMAND_TOKEN + + def handle_response_r1b(self, cmd_pin): + # R1b: Same as R1 with an optional busy signal (on the data line) + if not self.get_token_bits(cmd_pin, 48): + return + assert(self.is_from_card()) + self.handle_common_token_fields() + self.puta(0, 31, [Ann.DECODED_F, ['Card status', 'Status', 'S']]) + self.putr(Ann.RESPONSE_R1B) + self.token, self.state = [], St.GET_COMMAND_TOKEN + + def handle_response_r2(self, cmd_pin): + # R2: CID/CSD register + # - Bits[135:135]: Start bit (always 0) + # - Bits[134:134]: Transmission bit (0 == card) + # - Bits[133:128]: Reserved (always 0b111111) + # - Bits[127:001]: CID or CSD register including internal CRC7 + # - Bits[000:000]: End bit (always 1) + if not self.get_token_bits(cmd_pin, 136): + return + assert(self.is_from_card()) + # Annotations for each individual bit. + for bit in range(len(self.token)): + self.putf(bit, bit, [Ann.BIT_0 + self.token[bit].bit, ['%d' % self.token[bit].bit]]) + self.putf(0, 0, [Ann.F_START, ['Start bit', 'Start', 'S']]) + t = 'host' if self.token[1].bit == 1 else 'card' + self.putf(1, 1, [Ann.F_TRANSMISSION, ['Transmission: ' + t, 'T: ' + t, 'T']]) + self.putf(2, 7, [Ann.F_CMD, ['Reserved', 'Res', 'R']]) + self.putf(8, 134, [Ann.F_ARG, ['Argument', 'Arg', 'A']]) + self.putf(135, 135, [Ann.F_END, ['End bit', 'End', 'E']]) + self.putf(8, 134, [Ann.DECODED_F, ['CID/CSD register', 'CID/CSD', 'C']]) + self.putf(0, 135, [Ann.RESPONSE_R2, ['Response: R2']]) + + if self.last_cmd in (Ann.CMD2, Ann.CMD10): + self.handle_reg_cid() + + if self.last_cmd == Ann.CMD9: + self.handle_reg_csd() + + self.token, self.state = [], St.GET_COMMAND_TOKEN + + def handle_response_r3(self, cmd_pin): + # R3: OCR register + # - Bits[47:47]: Start bit (always 0) + # - Bits[46:46]: Transmission bit (0 == card) + # - Bits[45:40]: Reserved (always 0b111111) + # - Bits[39:08]: OCR register + # - Bits[07:01]: Reserved (always 0b111111) + # - Bits[00:00]: End bit (always 1) + if not self.get_token_bits(cmd_pin, 48): + return + assert(self.is_from_card()) + self.putr(Ann.RESPONSE_R3) + # Annotations for each individual bit. + for bit in range(len(self.token)): + self.putf(bit, bit, [Ann.BIT_0 + self.token[bit].bit, ['%d' % self.token[bit].bit]]) + self.putf(0, 0, [Ann.F_START, ['Start bit', 'Start', 'S']]) + t = 'host' if self.token[1].bit == 1 else 'card' + self.putf(1, 1, [Ann.F_TRANSMISSION, ['Transmission: ' + t, 'T: ' + t, 'T']]) + self.putf(2, 7, [Ann.F_CMD, ['Reserved', 'Res', 'R']]) + self.putf(8, 39, [Ann.F_ARG, ['Argument', 'Arg', 'A']]) + self.putf(40, 46, [Ann.F_CRC, ['Reserved', 'Res', 'R']]) + self.putf(47, 47, [Ann.F_END, ['End bit', 'End', 'E']]) + self.puta(0, 31, [Ann.DECODED_F, ['OCR register', 'OCR reg', 'OCR', 'O']]) + self.token, self.state = [], St.GET_COMMAND_TOKEN + + def handle_response_r6(self, cmd_pin): + # R6: Published RCA response + # - Bits[47:47]: Start bit (always 0) + # - Bits[46:46]: Transmission bit (0 == card) + # - Bits[45:40]: Command index (always 0b000011) + # - Bits[39:24]: Argument[31:16]: New published RCA of the card + # - Bits[23:08]: Argument[15:0]: Card status bits + # - Bits[07:01]: CRC7 + # - Bits[00:00]: End bit (always 1) + if not self.get_token_bits(cmd_pin, 48): + return + assert(self.is_from_card()) + self.handle_common_token_fields() + self.puta(0, 15, [Ann.DECODED_F, ['Card status bits', 'Status', 'S']]) + self.puta(16, 31, [Ann.DECODED_F, ['Relative card address', 'RCA', 'R']]) + self.putr(Ann.RESPONSE_R6) + self.token, self.state = [], St.GET_COMMAND_TOKEN + + def handle_response_r7(self, cmd_pin): + # R7: Card interface condition + # - Bits[47:47]: Start bit (always 0) + # - Bits[46:46]: Transmission bit (0 == card) + # - Bits[45:40]: Command index (always 0b001000) + # - Bits[39:20]: Reserved bits (all-zero) + # - Bits[19:16]: Voltage accepted + # - Bits[15:08]: Echo-back of check pattern + # - Bits[07:01]: CRC7 + # - Bits[00:00]: End bit (always 1) + if not self.get_token_bits(cmd_pin, 48): + return + assert(self.is_from_card()) + self.handle_common_token_fields() + + self.putr(Ann.RESPONSE_R7) + + # Arg[31:12]: Reserved bits (all-zero) + self.puta(12, 31, [Ann.DECODED_F, ['Reserved', 'Res', 'R']]) + + # Arg[11:08]: Voltage accepted + v = ''.join(str(i.bit) for i in self.token[28:32]) + av = accepted_voltages.get(int('0b' + v, 2), 'Unknown') + self.puta(8, 11, [Ann.DECODED_F, + ['Voltage accepted: ' + av, 'Voltage', 'Volt', 'V']]) + + # Arg[07:00]: Echo-back of check pattern + self.puta(0, 7, [Ann.DECODED_F, + ['Echo-back of check pattern', 'Echo', 'E']]) + + self.token, self.state = [], St.GET_COMMAND_TOKEN + + def decode(self): + while True: + conds = {Pin.CLK: 'r'} + + # Wait for start bit (CMD = 0). + if ((self.state == St.GET_COMMAND_TOKEN or + self.state.value.startswith('GET_RESPONSE')) and + (len(self.token) == 0)): + conds.update({Pin.CMD: 'l'}) + + # Wait for a rising CLK edge. + (cmd_pin, clk, dat0, dat1, dat2, dat3) = self.wait(conds) + + # State machine. + if self.state == St.GET_COMMAND_TOKEN: + self.get_command_token(cmd_pin) + elif self.state.value.startswith('HANDLE_CMD'): + # Call the respective handler method for the command. + a, cmdstr = 'a' if self.is_acmd else '', self.state.value[10:].lower() + handle_cmd = getattr(self, 'handle_%scmd%s' % (a, cmdstr)) + handle_cmd() + # Leave ACMD mode again after the first command after CMD55. + if self.is_acmd and cmdstr not in ('55', '63'): + self.is_acmd = False + elif self.state.value.startswith('GET_RESPONSE'): + # Call the respective handler method for the response. + s = 'handle_response_%s' % self.state.value[13:].lower() + handle_response = getattr(self, s) + + # AssertionError can be raised when the token we are trying + # to handle as a response is in fact a command. + # (Transmission bit is 1). Just re-handle the token as command. + try: + handle_response(cmd_pin) + except AssertionError: + self.handle_cmd() diff --git a/libsigrokdecode4DSL/decoders/sdcard_spi/__init__.py b/libsigrokdecode4DSL/decoders/sdcard_spi/__init__.py new file mode 100644 index 00000000..a0945162 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/sdcard_spi/__init__.py @@ -0,0 +1,68 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2012 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This decoder stacks on top of the 'spi' PD and decodes the SD card +(SPI mode) low-level protocol. + +Most SD cards can be accessed via two different protocols/modes: SD mode +or SPI mode. + +All SD cards are in SD mode upon powerup. They can be switched to SPI mode +using a special method involving CMD0 (see spec). Once in SPI mode, the mode +can no longer be changed without a power-cycle of the card. + +SPI mode properties (differences to SD mode): + * The 'sdcard_spi' PD stacks on top of the 'spi' PD. This is not possible + for the 'sdcard_sd' PD, as that protocol is not SPI related at all. + Hence 'sdcard_spi' and 'sdcard_sd' are two separate PDs. + * The state machines for SPI mode and SD mode are different. + * In SPI mode, data transfers are byte-oriented (commands/data are multiples + of 8 bits, with the CS# pin asserted respectively), unlike SD mode where + commands/data are bit-oriented with parallel transmission of 1 or 4 bits. + * While the SPI mode command set has some commands in common with the + SD mode command set, they are not the same and also not a subset/superset. + Some commands are only available in SD mode (e.g. CMD2), some only + in SPI mode (e.g. CMD1). + * Response types of commands also differ between SD mode and SPI mode. + E.g. CMD9 has an R2 response in SD mode, but R1 in SPI mode. + * The commands and functions in SD mode defined after version 2.00 of the + spec are NOT supported in SPI mode. + * SPI mode: The selected SD card ALWAYS responds to commands (unlike SD mode). + * Upon data retrieval problems (read operations) the card will respond with + an error response (and no data), as opposed to a timeout in SD mode. + * SPI mode: For every data block sent to the card (write operations) the host + gets a data response token from the card. + * SDSC: A data block can be max. one card write block, min. 1 byte. + * SDHC/SDXC: Block length is fixed to 512. The block length set by CMD16 + is only used for CDM42 (not for memory data transfers). Thus, partial + read/write operations are disabled in SPI mode. + * SPI mode: Write protected commands (CMD28, CMD29, CMD30) are not supported. + * The SD mode state machine is NOT used. All commands that are supported + in SPI mode are always available. + * Per default the card is in CRC OFF mode. Exception: CMD0 (which is used to + switch to SPI mode) needs a valid CRC. + * The APP_CMD status bit is not available in SPI mode. + * TODO: Switch function command differences. + * In SPI mode cards cannot guarantee their speed class (the host should + assume class 0, no matter what the card indicates). + * The RCA register is not accessible in SPI mode. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/sdcard_spi/pd.py b/libsigrokdecode4DSL/decoders/sdcard_spi/pd.py new file mode 100644 index 00000000..962438f1 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/sdcard_spi/pd.py @@ -0,0 +1,465 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2012-2014 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd +from common.sdcard import (cmd_names, acmd_names) + +class Decoder(srd.Decoder): + api_version = 3 + id = 'sdcard_spi' + name = 'SD card (SPI mode)' + longname = 'Secure Digital card (SPI mode)' + desc = 'Secure Digital card (SPI mode) low-level protocol.' + license = 'gplv2+' + inputs = ['spi'] + outputs = [] + tags = ['Memory'] + annotations = \ + tuple(('cmd%d' % i, 'CMD%d' % i) for i in range(64)) + \ + tuple(('acmd%d' % i, 'ACMD%d' % i) for i in range(64)) + ( \ + ('r1', 'R1 reply'), + ('r1b', 'R1B reply'), + ('r2', 'R2 reply'), + ('r3', 'R3 reply'), + ('r7', 'R7 reply'), + ('bits', 'Bits'), + ('bit-warnings', 'Bit warnings'), + ) + annotation_rows = ( + ('bits', 'Bits', (133, 134)), + ('cmd-reply', 'Commands/replies', tuple(range(133))), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.state = 'IDLE' + self.ss, self.es = 0, 0 + self.ss_bit, self.es_bit = 0, 0 + self.ss_cmd, self.es_cmd = 0, 0 + self.cmd_token = [] + self.cmd_token_bits = [] + self.is_acmd = False # Indicates CMD vs. ACMD + self.blocklen = 0 + self.read_buf = [] + self.cmd_str = '' + self.is_cmd24 = False + self.cmd24_start_token_found = False + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def putx(self, data): + self.put(self.ss_cmd, self.es_cmd, self.out_ann, data) + + def putc(self, cmd, desc): + self.putx([cmd, ['%s: %s' % (self.cmd_str, desc)]]) + + def putb(self, data): + self.put(self.ss_bit, self.es_bit, self.out_ann, data) + + def cmd_name(self, cmd): + c = acmd_names if self.is_acmd else cmd_names + s = c.get(cmd, 'Unknown') + # SD mode names for CMD32/33: ERASE_WR_BLK_{START,END}. + # SPI mode names for CMD32/33: ERASE_WR_BLK_{START,END}_ADDR. + if cmd in (32, 33): + s += '_ADDR' + return s + + def handle_command_token(self, mosi, miso): + # Command tokens (6 bytes) are sent (MSB-first) by the host. + # + # Format: + # - CMD[47:47]: Start bit (always 0) + # - CMD[46:46]: Transmitter bit (1 == host) + # - CMD[45:40]: Command index (BCD; valid: 0-63) + # - CMD[39:08]: Argument + # - CMD[07:01]: CRC7 + # - CMD[00:00]: End bit (always 1) + + if len(self.cmd_token) == 0: + self.ss_cmd = self.ss + + self.cmd_token.append(mosi) + self.cmd_token_bits.append(self.mosi_bits) + + # All command tokens are 6 bytes long. + if len(self.cmd_token) < 6: + return + + self.es_cmd = self.es + + t = self.cmd_token + + # CMD or ACMD? + s = 'ACMD' if self.is_acmd else 'CMD' + + def tb(byte, bit): + return self.cmd_token_bits[5 - byte][bit] + + # Bits[47:47]: Start bit (always 0) + bit, self.ss_bit, self.es_bit = tb(5, 7)[0], tb(5, 7)[1], tb(5, 7)[2] + if bit == 0: + self.putb([134, ['Start bit: %d' % bit]]) + else: + self.putb([135, ['Start bit: %s (Warning: Must be 0!)' % bit]]) + + # Bits[46:46]: Transmitter bit (1 == host) + bit, self.ss_bit, self.es_bit = tb(5, 6)[0], tb(5, 6)[1], tb(5, 6)[2] + if bit == 1: + self.putb([134, ['Transmitter bit: %d' % bit]]) + else: + self.putb([135, ['Transmitter bit: %d (Warning: Must be 1!)' % bit]]) + + # Bits[45:40]: Command index (BCD; valid: 0-63) + cmd = self.cmd_index = t[0] & 0x3f + self.ss_bit, self.es_bit = tb(5, 5)[1], tb(5, 0)[2] + self.putb([134, ['Command: %s%d (%s)' % (s, cmd, self.cmd_name(cmd))]]) + + # Bits[39:8]: Argument + self.arg = (t[1] << 24) | (t[2] << 16) | (t[3] << 8) | t[4] + self.ss_bit, self.es_bit = tb(4, 7)[1], tb(1, 0)[2] + self.putb([134, ['Argument: 0x%04x' % self.arg]]) + + # Bits[7:1]: CRC7 + # TODO: Check CRC7. + crc = t[5] >> 1 + self.ss_bit, self.es_bit = tb(0, 7)[1], tb(0, 1)[2] + self.putb([134, ['CRC7: 0x%01x' % crc]]) + + # Bits[0:0]: End bit (always 1) + bit, self.ss_bit, self.es_bit = tb(0, 0)[0], tb(0, 0)[1], tb(0, 0)[2] + if bit == 1: + self.putb([134, ['End bit: %d' % bit]]) + else: + self.putb([135, ['End bit: %d (Warning: Must be 1!)' % bit]]) + + # Handle command. + if cmd in (0, 1, 9, 16, 17, 24, 41, 49, 55, 59): + self.state = 'HANDLE CMD%d' % cmd + self.cmd_str = '%s%d (%s)' % (s, cmd, self.cmd_name(cmd)) + else: + self.state = 'HANDLE CMD999' + a = '%s%d: %02x %02x %02x %02x %02x %02x' % ((s, cmd) + tuple(t)) + self.putx([cmd, [a]]) + + def handle_cmd0(self): + # CMD0: GO_IDLE_STATE + self.putc(0, 'Reset the SD card') + self.state = 'GET RESPONSE R1' + + def handle_cmd1(self): + # CMD1: SEND_OP_COND + self.putc(1, 'Send HCS info and activate the card init process') + hcs = (self.arg & (1 << 30)) >> 30 + self.ss_bit = self.cmd_token_bits[5 - 4][6][1] + self.es_bit = self.cmd_token_bits[5 - 4][6][2] + self.putb([134, ['HCS: %d' % hcs]]) + self.state = 'GET RESPONSE R1' + + def handle_cmd9(self): + # CMD9: SEND_CSD (128 bits / 16 bytes) + self.putc(9, 'Ask card to send its card specific data (CSD)') + if len(self.read_buf) == 0: + self.ss_cmd = self.ss + self.read_buf.append(self.miso) + # FIXME + ### if len(self.read_buf) < 16: + if len(self.read_buf) < 16 + 4: + return + self.es_cmd = self.es + self.read_buf = self.read_buf[4:] # TODO: Document or redo. + self.putx([9, ['CSD: %s' % self.read_buf]]) + # TODO: Decode all bits. + self.read_buf = [] + ### self.state = 'GET RESPONSE R1' + self.state = 'IDLE' + + def handle_cmd10(self): + # CMD10: SEND_CID (128 bits / 16 bytes) + self.putc(10, 'Ask card to send its card identification (CID)') + self.read_buf.append(self.miso) + if len(self.read_buf) < 16: + return + self.putx([10, ['CID: %s' % self.read_buf]]) + # TODO: Decode all bits. + self.read_buf = [] + self.state = 'GET RESPONSE R1' + + def handle_cmd16(self): + # CMD16: SET_BLOCKLEN + self.blocklen = self.arg + # TODO: Sanity check on block length. + self.putc(16, 'Set the block length to %d bytes' % self.blocklen) + self.state = 'GET RESPONSE R1' + + def handle_cmd17(self): + # CMD17: READ_SINGLE_BLOCK + self.putc(17, 'Read a block from address 0x%04x' % self.arg) + if len(self.read_buf) == 0: + self.ss_cmd = self.ss + self.read_buf.append(self.miso) + if len(self.read_buf) < self.blocklen + 2: # FIXME + return + self.es_cmd = self.es + self.read_buf = self.read_buf[2:] # FIXME + self.putx([17, ['Block data: %s' % self.read_buf]]) + self.read_buf = [] + self.state = 'GET RESPONSE R1' + + def handle_cmd24(self): + # CMD24: WRITE_BLOCK + self.putc(24, 'Write a block to address 0x%04x' % self.arg) + self.is_cmd24 = True + self.state = 'GET RESPONSE R1' + + def handle_cmd49(self): + self.state = 'GET RESPONSE R1' + + def handle_cmd55(self): + # CMD55: APP_CMD + self.putc(55, 'Next command is an application-specific command') + self.is_acmd = True + self.state = 'GET RESPONSE R1' + + def handle_cmd59(self): + # CMD59: CRC_ON_OFF + crc_on_off = self.arg & (1 << 0) + s = 'on' if crc_on_off == 1 else 'off' + self.putc(59, 'Turn the SD card CRC option %s' % s) + self.state = 'GET RESPONSE R1' + + def handle_acmd41(self): + # ACMD41: SD_SEND_OP_COND + self.putc(64 + 41, 'Send HCS info and activate the card init process') + self.state = 'GET RESPONSE R1' + + def handle_cmd999(self): + self.state = 'GET RESPONSE R1' + + def handle_cid_register(self): + # Card Identification (CID) register, 128bits + + cid = self.cid + + # Manufacturer ID: CID[127:120] (8 bits) + mid = cid[15] + + # OEM/Application ID: CID[119:104] (16 bits) + oid = (cid[14] << 8) | cid[13] + + # Product name: CID[103:64] (40 bits) + pnm = 0 + for i in range(12, 8 - 1, -1): + pnm <<= 8 + pnm |= cid[i] + + # Product revision: CID[63:56] (8 bits) + prv = cid[7] + + # Product serial number: CID[55:24] (32 bits) + psn = 0 + for i in range(6, 3 - 1, -1): + psn <<= 8 + psn |= cid[i] + + # RESERVED: CID[23:20] (4 bits) + + # Manufacturing date: CID[19:8] (12 bits) + # TODO + + # CRC7 checksum: CID[7:1] (7 bits) + # TODO + + # Not used, always 1: CID[0:0] (1 bit) + # TODO + + def handle_response_r1(self, res): + # The R1 response token format (1 byte). + # Sent by the card after every command except for SEND_STATUS. + + self.ss_cmd, self.es_cmd = self.miso_bits[7][1], self.miso_bits[0][2] + self.putx([65, ['R1: 0x%02x' % res]]) + + def putbit(bit, data): + b = self.miso_bits[bit] + self.ss_bit, self.es_bit = b[1], b[2] + self.putb([134, data]) + + # Bit 0: 'In idle state' bit + s = '' if (res & (1 << 0)) else 'not ' + putbit(0, ['Card is %sin idle state' % s]) + + # Bit 1: 'Erase reset' bit + s = '' if (res & (1 << 1)) else 'not ' + putbit(1, ['Erase sequence %scleared' % s]) + + # Bit 2: 'Illegal command' bit + s = 'I' if (res & (1 << 2)) else 'No i' + putbit(2, ['%sllegal command detected' % s]) + + # Bit 3: 'Communication CRC error' bit + s = 'failed' if (res & (1 << 3)) else 'was successful' + putbit(3, ['CRC check of last command %s' % s]) + + # Bit 4: 'Erase sequence error' bit + s = 'E' if (res & (1 << 4)) else 'No e' + putbit(4, ['%srror in the sequence of erase commands' % s]) + + # Bit 5: 'Address error' bit + s = 'M' if (res & (1 << 4)) else 'No m' + putbit(5, ['%sisaligned address used in command' % s]) + + # Bit 6: 'Parameter error' bit + s = '' if (res & (1 << 4)) else 'not ' + putbit(6, ['Command argument %soutside allowed range' % s]) + + # Bit 7: Always set to 0 + putbit(7, ['Bit 7 (always 0)']) + + if self.is_cmd24: + self.state = 'HANDLE DATA BLOCK CMD24' + + def handle_response_r1b(self, res): + # TODO + pass + + def handle_response_r2(self, res): + # TODO + pass + + def handle_response_r3(self, res): + # TODO + pass + + # Note: Response token formats R4 and R5 are reserved for SDIO. + + # TODO: R6? + + def handle_response_r7(self, res): + # TODO + pass + + def handle_data_cmd24(self, mosi): + if self.cmd24_start_token_found: + if len(self.read_buf) == 0: + self.ss_data = self.ss + if not self.blocklen: + # Assume a fixed block size when inspection of the + # previous traffic did not provide the respective + # parameter value. + # TODO Make the default block size a user adjustable option? + self.blocklen = 512 + self.read_buf.append(mosi) + # Wait until block transfer completed. + if len(self.read_buf) < self.blocklen: + return + self.es_data = self.es + self.put(self.ss_data, self.es_data, self.out_ann, [24, ['Block data: %s' % self.read_buf]]) + self.read_buf = [] + self.state = 'DATA RESPONSE' + elif mosi == 0xfe: + self.put(self.ss, self.es, self.out_ann, [24, ['Start Block']]) + self.cmd24_start_token_found = True + + def handle_data_response(self, miso): + # Data Response token (1 byte). + # + # Format: + # - Bits[7:5]: Don't care. + # - Bits[4:4]: Always 0. + # - Bits[3:1]: Status. + # - 010: Data accepted. + # - 101: Data rejected due to a CRC error. + # - 110: Data rejected due to a write error. + # - Bits[0:0]: Always 1. + miso &= 0x1f + if miso & 0x11 != 0x01: + # This is not the byte we are waiting for. + # Should we return to IDLE here? + return + m = self.miso_bits + self.put(m[7][1], m[5][2], self.out_ann, [134, ['Don\'t care']]) + self.put(m[4][1], m[4][2], self.out_ann, [134, ['Always 0']]) + if miso == 0x05: + self.put(m[3][1], m[1][2], self.out_ann, [134, ['Data accepted']]) + elif miso == 0x0b: + self.put(m[3][1], m[1][2], self.out_ann, [134, ['Data rejected (CRC error)']]) + elif miso == 0x0d: + self.put(m[3][1], m[1][2], self.out_ann, [134, ['Data rejected (write error)']]) + self.put(m[0][1], m[0][2], self.out_ann, [134, ['Always 1']]) + ann_class = None + if self.is_cmd24: + ann_class = 24 + if ann_class is not None: + self.put(self.ss, self.es, self.out_ann, [ann_class, ['Data Response']]) + self.state = 'IDLE' + + def decode(self, ss, es, data): + ptype, mosi, miso = data + + # For now, only use DATA and BITS packets. + if ptype not in ('DATA', 'BITS'): + return + + # Store the individual bit values and ss/es numbers. The next packet + # is guaranteed to be a 'DATA' packet belonging to this 'BITS' one. + if ptype == 'BITS': + self.miso_bits, self.mosi_bits = miso, mosi + return + + self.ss, self.es = ss, es + + # State machine. + if self.state == 'IDLE': + # Ignore stray 0xff bytes, some devices seem to send those!? + if mosi == 0xff: # TODO? + return + self.state = 'GET COMMAND TOKEN' + self.handle_command_token(mosi, miso) + elif self.state == 'GET COMMAND TOKEN': + self.handle_command_token(mosi, miso) + elif self.state.startswith('HANDLE CMD'): + self.miso, self.mosi = miso, mosi + # Call the respective handler method for the command. + a, cmdstr = 'a' if self.is_acmd else '', self.state[10:].lower() + handle_cmd = getattr(self, 'handle_%scmd%s' % (a, cmdstr)) + handle_cmd() + self.cmd_token = [] + self.cmd_token_bits = [] + # Leave ACMD mode again after the first command after CMD55. + if self.is_acmd and cmdstr != '55': + self.is_acmd = False + elif self.state.startswith('GET RESPONSE'): + # Ignore stray 0xff bytes, some devices seem to send those!? + if miso == 0xff: # TODO? + return + # Call the respective handler method for the response. + # Assume return to IDLE state, but allow response handlers + # to advance to some other state when applicable. + s = 'handle_response_%s' % self.state[13:].lower() + handle_response = getattr(self, s) + self.state = 'IDLE' + handle_response(miso) + elif self.state == 'HANDLE DATA BLOCK CMD24': + self.handle_data_cmd24(mosi) + elif self.state == 'DATA RESPONSE': + self.handle_data_response(miso) diff --git a/libsigrokdecode4DSL/decoders/sdq/__init__.py b/libsigrokdecode4DSL/decoders/sdq/__init__.py new file mode 100644 index 00000000..3fc10438 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/sdq/__init__.py @@ -0,0 +1,28 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2019-2020 Philip Åkesson +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +The SDQ protocol was developed by Texas Instruments, and is used in +devices like battery pack authentication. Apple uses SDQ in MagSafe +and Lightning connectors, as well as some batteries. + +See https://www.ti.com/lit/ds/symlink/bq26100.pdf for details. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/sdq/pd.py b/libsigrokdecode4DSL/decoders/sdq/pd.py new file mode 100644 index 00000000..66df4202 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/sdq/pd.py @@ -0,0 +1,131 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2019-2020 Philip Åkesson +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +from common.srdhelper import bitpack +import sigrokdecode as srd + +class SamplerateError(Exception): + pass + +class Pin: + SDQ, = range(1) + +class Ann: + BIT, BYTE, BREAK, = range(3) + +class Decoder(srd.Decoder): + api_version = 3 + id = 'sdq' + name = 'SDQ' + longname = 'Texas Instruments SDQ' + desc = 'Texas Instruments SDQ. The SDQ protocol is also used by Apple.' + license = 'gplv2+' + inputs = ['logic'] + outputs = [] + tags = ['Embedded/industrial'] + channels = ( + {'id': 'sdq', 'name': 'SDQ', 'desc': 'Single wire SDQ data line.'}, + ) + options = ( + {'id': 'bitrate', 'desc': 'Bit rate', 'default': 98425}, + ) + annotations = ( + ('bit', 'Bit'), + ('byte', 'Byte'), + ('break', 'Break'), + ) + annotation_rows = ( + ('bits', 'Bits', (Ann.BIT,)), + ('bytes', 'Bytes', (Ann.BYTE,)), + ('breaks', 'Breaks', (Ann.BREAK,)), + ) + + def puts(self, data): + self.put(self.startsample, self.samplenum, self.out_ann, data) + + def putetu(self, data): + self.put(self.startsample, self.startsample + int(self.bit_width), self.out_ann, data) + + def putbetu(self, data): + self.put(self.bytepos, self.startsample + int(self.bit_width), self.out_ann, data) + + def __init__(self): + self.reset() + + def reset(self): + self.samplerate = None + self.startsample = 0 + self.bits = [] + self.bytepos = 0 + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def metadata(self, key, value): + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + + def handle_bit(self, bit): + self.bits.append(bit) + self.putetu([Ann.BIT, [ + 'Bit: {:d}'.format(bit), + '{:d}'.format(bit), + ]]) + + if len(self.bits) == 8: + byte = bitpack(self.bits) + self.putbetu([Ann.BYTE, [ + 'Byte: 0x{:02x}'.format(byte), + '0x{:02x}'.format(byte), + ]]) + self.bits = [] + self.bytepos = 0 + + def handle_break(self): + self.puts([Ann.BREAK, ['Break', 'BR']]) + self.bits = [] + self.startsample = self.samplenum + self.bytepos = 0 + + def decode(self): + if not self.samplerate: + raise SamplerateError('Cannot decode without samplerate.') + self.bit_width = float(self.samplerate) / float(self.options['bitrate']) + self.half_bit_width = self.bit_width / 2.0 + # BREAK if the line is low for longer than this. + break_threshold = self.bit_width * 1.2 + + # Wait until the line is high before inspecting input data. + sdq, = self.wait({Pin.SDQ: 'h'}) + while True: + # Get the length of a low pulse (falling to rising edge). + sdq, = self.wait({Pin.SDQ: 'f'}) + self.startsample = self.samplenum + if self.bytepos == 0: + self.bytepos = self.samplenum + sdq, = self.wait({Pin.SDQ: 'r'}) + + # Check for 0 or 1 data bits, or the BREAK symbol. + delta = self.samplenum - self.startsample + if delta > break_threshold: + self.handle_break() + elif delta > self.half_bit_width: + self.handle_bit(0) + else: + self.handle_bit(1) diff --git a/libsigrokdecode4DSL/decoders/seven_segment/__init__.py b/libsigrokdecode4DSL/decoders/seven_segment/__init__.py new file mode 100644 index 00000000..ea03e4f2 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/seven_segment/__init__.py @@ -0,0 +1,24 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2019 Benedikt Otto +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This decoder decodes the output of a 7-segment display. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/seven_segment/pd.py b/libsigrokdecode4DSL/decoders/seven_segment/pd.py new file mode 100644 index 00000000..edabf04a --- /dev/null +++ b/libsigrokdecode4DSL/decoders/seven_segment/pd.py @@ -0,0 +1,136 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2019 Benedikt Otto +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd + +class ChannelError(Exception): + pass + +digits = { + (0, 0, 0, 0, 0, 0, 0): ' ', + (1, 1, 1, 1, 1, 1, 0): '0', + (0, 1, 1, 0, 0, 0, 0): '1', + (1, 1, 0, 1, 1, 0, 1): '2', + (1, 1, 1, 1, 0, 0, 1): '3', + (0, 1, 1, 0, 0, 1, 1): '4', + (1, 0, 1, 1, 0, 1, 1): '5', + (1, 0, 1, 1, 1, 1, 1): '6', + (1, 1, 1, 0, 0, 0, 0): '7', + (1, 1, 1, 1, 1, 1, 1): '8', + (1, 1, 1, 1, 0, 1, 1): '9', + (1, 1, 1, 0, 1, 1, 1): 'A', + (0, 0, 1, 1, 1, 1, 1): 'B', + (1, 0, 0, 1, 1, 1, 0): 'C', + (0, 1, 1, 1, 1, 0, 1): 'D', + (1, 0, 0, 1, 1, 1, 1): 'E', + (1, 0, 0, 0, 1, 1, 1): 'F', +} + +class Decoder(srd.Decoder): + api_version = 3 + id = 'seven_segment' + name = 'Segment-7' + longname = '7-segment display' + desc = '7-segment display protocol.' + license = 'gplv2+' + inputs = ['logic'] + outputs = [] + tags = ['Display'] + channels = ( + {'id': 'a', 'name': 'A', 'desc': 'Segment A'}, + {'id': 'b', 'name': 'B', 'desc': 'Segment B'}, + {'id': 'c', 'name': 'C', 'desc': 'Segment C'}, + {'id': 'd', 'name': 'D', 'desc': 'Segment D'}, + {'id': 'e', 'name': 'E', 'desc': 'Segment E'}, + {'id': 'f', 'name': 'F', 'desc': 'Segment F'}, + {'id': 'g', 'name': 'G', 'desc': 'Segment G'}, + ) + optional_channels = ( + {'id': 'dp', 'name': 'DP', 'desc': 'Decimal point'}, + ) + options = ( + {'id': 'polarity', 'desc': 'Expected polarity', + 'default': 'common-cathode', 'values': ('common-cathode', 'common-anode')}, + ) + annotations = ( + ('decoded-digit', 'Decoded digit'), + ) + annotation_rows = ( + ('decoded-digits', 'Decoded digits', (0,)), + ) + + def __init__(self): + self.reset() + + def reset(self): + pass + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def putb(self, ss_block, es_block, data): + self.put(ss_block, es_block, self.out_ann, data) + + def pins_to_hex(self, pins): + return digits.get(pins, None) + + def decode(self): + (s0, s1, s2, s3, s4, s5, s6, dp) = self.wait() + oldpins = (s0, s1, s2, s3, s4, s5, s6, dp) + + # Check if at least the 7 signals are present. + if False in [p in (0, 1) for p in oldpins[:7]]: + raise ChannelError('7 or 8 pins have to be present.') + + lastpos = self.samplenum + + self.have_dp = self.has_channel(7) + + conditions = [{0: 'e'}, {1: 'e'}, {2: 'e'}, {3: 'e'}, {4: 'e'}, {5: 'e'}, {6: 'e'}] + + if self.have_dp: + conditions.append({7: 'e'}) + + while True: + # Wait for any change. + (s0, s1, s2, s3, s4, s5, s6, dp) = self.wait(conditions) + pins = (s0, s1, s2, s3, s4, s5, s6, dp) + + if self.options['polarity'] == 'common-anode': + # Invert all data lines if a common anode display is used. + if self.have_dp: + oldpins = tuple((1 - state for state in oldpins)) + else: + oldpins = tuple((1 - state for state in oldpins[:7])) + + # Convert to character string. + digit = self.pins_to_hex(oldpins[:7]) + + if digit is not None: + dp = oldpins[7] + + # Check if decimal point is present and active. + if self.have_dp and dp == 1: + digit += '.' + + self.putb(lastpos, self.samplenum, [0, [digit]]) + + lastpos = self.samplenum + + oldpins = pins diff --git a/libsigrokdecode4DSL/decoders/signature/__init__.py b/libsigrokdecode4DSL/decoders/signature/__init__.py new file mode 100644 index 00000000..d37fd4a3 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/signature/__init__.py @@ -0,0 +1,25 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2019 Shirow Miura +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +Signature analysis function for troubleshooting logic circuits. +This generates the same signature as Hewlett-Packard 5004A. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/signature/pd.py b/libsigrokdecode4DSL/decoders/signature/pd.py new file mode 100644 index 00000000..946b2da7 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/signature/pd.py @@ -0,0 +1,142 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2019 Shirow Miura +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd + +symbol_map = { + 0b0000: '0', + 0b1000: '1', + 0b0100: '2', + 0b1100: '3', + 0b0010: '4', + 0b1010: '5', + 0b0110: '6', + 0b1110: '7', + 0b0001: '8', + 0b1001: '9', + 0b0101: 'A', + 0b1101: 'C', + 0b0011: 'F', + 0b1011: 'H', + 0b0111: 'P', + 0b1111: 'U', +} + +START, STOP, CLOCK, DATA = range(4) + +class Decoder(srd.Decoder): + api_version = 3 + id = 'signature' + name = 'Signature' + longname = 'Signature analysis' + desc = 'Annotate signature of logic patterns.' + license = 'gplv2+' + inputs = ['logic'] + outputs = [] + tags = ['Debug/trace', 'Util', 'Encoding'] + channels = ( + {'id': 'start', 'name': 'START', 'desc': 'START channel'}, + {'id': 'stop', 'name': 'STOP', 'desc': 'STOP channel'}, + {'id': 'clk', 'name': 'CLOCK', 'desc': 'CLOCK channel'}, + {'id': 'data', 'name': 'DATA', 'desc': 'DATA channel'}, + ) + options = ( + {'id': 'start_edge', 'desc': 'START edge polarity', + 'default': 'rising', 'values': ('rising', 'falling')}, + {'id': 'stop_edge', 'desc': 'STOP edge polarity', + 'default': 'rising', 'values': ('rising', 'falling')}, + {'id': 'clk_edge', 'desc': 'CLOCK edge polarity', + 'default': 'falling', 'values': ('rising', 'falling')}, + {'id': 'annbits', 'desc': 'Enable bit level annotations', + 'default': 'no', 'values': ('yes', 'no')}, + ) + annotations = ( + ('bit0', 'Bit0'), + ('bit1', 'Bit1'), + ('start', 'START'), + ('stop', 'STOP'), + ('signature', 'Signature') + ) + annotation_rows = ( + ('bits', 'Bits', (0, 1, 2, 3)), + ('signatures', 'Signatures', (4,)) + ) + + def __init__(self): + self.reset() + + def reset(self): + pass + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def putsig(self, ss, es, signature): + s = ''.join([symbol_map[(signature >> 0) & 0x0f], + symbol_map[(signature >> 4) & 0x0f], + symbol_map[(signature >> 8) & 0x0f], + symbol_map[(signature >> 12) & 0x0f]]) + self.put(ss, es, self.out_ann, [4, [s]]) + + def putb(self, ss, ann): + self.put(ss, self.samplenum, self.out_ann, ann) + + def decode(self): + opt = self.options + start_edge_mode_rising = opt['start_edge'] == 'rising' + stop_edge_mode_rising = opt['stop_edge'] == 'rising' + annbits = opt['annbits'] == 'yes' + gate_is_open = False + sample_start = None + started = False + last_samplenum = 0 + prev_start = 0 if start_edge_mode_rising else 1 + prev_stop = 0 if stop_edge_mode_rising else 1 + shiftreg = 0 + + while True: + start, stop, _, data = self.wait({CLOCK: opt['clk_edge']}) + if start != prev_start and not gate_is_open: + gate_is_open = (start == 1) if start_edge_mode_rising else (start == 0) + if gate_is_open: + # Start sampling. + sample_start = self.samplenum + started = True + elif stop != prev_stop and gate_is_open: + gate_is_open = not ((stop == 1) if stop_edge_mode_rising else (stop == 0)) + if not gate_is_open: + # Stop sampling. + if annbits: + self.putb(last_samplenum, [3, ['STOP', 'STP', 'P']]) + self.putsig(sample_start, self.samplenum, shiftreg) + shiftreg = 0 + sample_start = None + if gate_is_open: + if annbits: + if started: + s = '<{}>'.format(data) + self.putb(last_samplenum, [2, ['START' + s, 'STR' + s, 'S' + s]]) + started = False + else: + self.putb(last_samplenum, [data, [str(data)]]) + incoming = (bin(shiftreg & 0x0291).count('1') + data) & 1 + shiftreg = (incoming << 15) | (shiftreg >> 1) + prev_start = start + prev_stop = stop + last_samplenum = self.samplenum diff --git a/libsigrokdecode4DSL/decoders/sipi/__init__.py b/libsigrokdecode4DSL/decoders/sipi/__init__.py new file mode 100644 index 00000000..d62e3c11 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/sipi/__init__.py @@ -0,0 +1,30 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2020 Soeren Apel +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +The Serial Inter-Processor Interface (SIPI) is a higher-level protocol that runs +over the LFAST physical interface. Together, they form the NXP Zipwire interface. + +The SIPI interface is also provided by Infineon as HSST, using HSCT for transport. + +For details see https://www.nxp.com/docs/en/application-note/AN5134.pdf and +https://hitex.co.uk/fileadmin/uk-files/downloads/ShieldBuddy/tc27xD_um_v2.2.pdf +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/sipi/pd.py b/libsigrokdecode4DSL/decoders/sipi/pd.py new file mode 100644 index 00000000..5bd58fbb --- /dev/null +++ b/libsigrokdecode4DSL/decoders/sipi/pd.py @@ -0,0 +1,181 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2020 Soeren Apel +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd +from binascii import crc_hqx + +# See tc27xD_um_v2.2.pdf, Table 20-2 +# (name, addr byte count, data byte count) +command_codes = { + 0b00000: ('Read byte', 4, 0), + 0b00001: ('Read 2 byte', 4, 0), + 0b00010: ('Read 4 byte', 4, 0), + # Reserved + 0b00100: ('Write byte with ACK', 4, 4), + 0b00101: ('Write 2 byte with ACK', 4, 4), + 0b00110: ('Write 4 byte with ACK', 4, 4), + # Reserved + 0b01000: ('ACK', 0, 0), + 0b01001: ('NACK (Target Error)', 0, 0), + 0b01010: ('Read Answer with ACK', 4, 4), + # Reserved + 0b01100: ('Trigger with ACK', 0, 0), + # Reserved + # Reserved + # Reserved + # Reserved + # Reserved + 0b10010: ('Read 4-byte JTAG ID', 0, 0), + # Reserved + # Reserved + # Reserved + # Reserved + 0b10111: ('Stream 32 byte with ACK', 0, 32) + # Rest is reserved +} + + +ann_header_tag, ann_header_cmd, ann_header_ch, ann_address, ann_data, \ + ann_crc, ann_warning = range(7) + +class Decoder(srd.Decoder): + api_version = 3 + id = 'sipi' + name = 'SIPI (Zipwire)' + longname = 'NXP SIPI interface' + desc = 'Serial Inter-Processor Interface (SIPI) aka Zipwire, aka HSSL' + license = 'gplv2+' + inputs = ['lfast'] + outputs = [] + tags = ['Embedded/industrial'] + annotations = ( + ('header_tag', 'Transaction Tag'), + ('header_cmd', 'Command Code'), + ('header_ch', 'Channel'), + ('address', 'Address'), + ('data', 'Data'), + ('crc', 'CRC'), + ('warning', 'Warning'), + ) + annotation_rows = ( + ('fields', 'Fields', (ann_header_tag, ann_header_cmd, + ann_header_ch, ann_address, ann_data, ann_crc,)), + ('warnings', 'Warnings', (ann_warning,)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.byte_len = 0 + self.frame_len = 0 + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + self.out_binary = self.register(srd.OUTPUT_BINARY) + + def put_ann(self, ss, es, ann_class, value): + self.put(int(ss), int(es), self.out_ann, [ann_class, value]) + + def put_header(self, ss_header, es_header, value): + ss = ss_header + es = ss + 3 * self.bit_len + tag = (value & 0xE000) >> 13 + self.put_ann(ss, es, ann_header_tag, ['{:02X}'.format(tag)]) + + ss = es + es = ss + 5 * self.bit_len + cmd_id = (value & 0x1F00) >> 8 + cmd_name, self.addr_len, self.data_len = \ + command_codes.get(cmd_id, ('Reserved ({:02X})'.format(cmd_id), 0, 0)) + self.frame_len = 2 + 2 + self.addr_len + self.data_len # +Header +CRC + self.put_ann(ss, es, ann_header_cmd, [cmd_name]) + + # Bits 4..7 are reserved and should be 0, warn if they're not + ss = es + es = ss + 4 * self.bit_len + reserved_bits = (value & 0x00F0) >> 4 + if reserved_bits > 0: + self.put_ann(ss, es, ann_warning, ['Reserved bits #4..7 should be 0']) + + ss = es + es = ss + 3 * self.bit_len + ch = (value & 0x000E) >> 1 # See tc27xD_um_v2.2.pdf, Table 20-1 + self.put_ann(ss, es, ann_header_ch, [str(ch)]) + + # Bit 0 is reserved and should be 0, warn if it's not + if (value & 0x0001) == 0x0001: + ss = es + es = ss + self.bit_len + self.put_ann(ss, es, ann_warning, ['Reserved bit #0 should be 0']) + + def put_payload(self, data): + byte_idx = 0 + if self.addr_len > 0: + for value_tuple in data[:self.addr_len]: + ss, es, value = value_tuple + self.put_ann(ss, es, ann_address, ['{:02X}'.format(value)]) + byte_idx = self.addr_len + + if self.data_len > 0: + for value_tuple in data[byte_idx:]: + ss, es, value = value_tuple + self.put_ann(ss, es, ann_data, ['{:02X}'.format(value)]) + + def put_crc(self, ss, es, crc_value, crc_payload_data): + crc_payload = [] + for value_tuple in crc_payload_data: + crc_payload.append(value_tuple[2]) + + calculated_crc = crc_hqx(bytes(crc_payload), 0xFFFF) + + if calculated_crc == crc_value: + self.put_ann(ss, es, ann_crc, ['CRC OK']) + else: + self.put_ann(ss, es, ann_crc, ['Have {:02X} but calculated {:02X}'.format(crc_value, calculated_crc)]) + self.put_ann(ss, es, ann_warning, ['CRC mismatch']) + + def decode(self, ss, es, data): + if len(data) == 1: + self.put_ann(ss, es, ann_warning, ['Header too short']) + return + + # ss and es are now unused, we use them as local variables instead + + self.bit_len = (data[0][1] - data[0][0]) / 8.0 + + byte_idx = 0 + + ss = data[byte_idx][0] + es = data[byte_idx + 1][1] + self.put_header(ss, es, (data[byte_idx][2] << 8) + data[byte_idx + 1][2]) + byte_idx += 2 + + payload_len = self.frame_len - 2 - 2 # -Header -CRC + if payload_len > 0: + self.put_payload(data[byte_idx:-2]) + byte_idx += payload_len + + ss = data[byte_idx][0] + es = data[byte_idx + 1][1] + if byte_idx == len(data) - 2: + # CRC is calculated over header + payload bytes + self.put_crc(ss, es, (data[byte_idx][2] << 8) + data[byte_idx + 1][2], data[0:-2]) + else: + self.put_ann(ss, es, ann_warning, ['CRC incomplete or missing']) diff --git a/libsigrokdecode4DSL/decoders/sle44xx/__init__.py b/libsigrokdecode4DSL/decoders/sle44xx/__init__.py new file mode 100644 index 00000000..0eb02856 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/sle44xx/__init__.py @@ -0,0 +1,26 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2019 Federico Cerutti +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +SLE 4418/28/32/42 memory cards implement a 2-wire protocol (CLK and I/O) +for data communication, along with the RST signal which resets the card's +internal state, and can terminate currently executing long memory reads. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/sle44xx/pd.py b/libsigrokdecode4DSL/decoders/sle44xx/pd.py new file mode 100644 index 00000000..9f332077 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/sle44xx/pd.py @@ -0,0 +1,541 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2019 Federico Cerutti +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +from common.srdhelper import bitpack_lsb +import sigrokdecode as srd + +class Pin: + RST, CLK, IO, = range(3) + +class Ann: + RESET_SYM, INTR_SYM, START_SYM, STOP_SYM, BIT_SYM, \ + ATR_BYTE, CMD_BYTE, OUT_BYTE, PROC_BYTE, \ + ATR_DATA, CMD_DATA, OUT_DATA, PROC_DATA, \ + = range(13) + +class Bin: + BYTES, = range(1) + +class Decoder(srd.Decoder): + api_version = 3 + id = 'sle44xx' + name = 'SLE 44xx' + longname = 'SLE44xx memory card' + desc = 'SLE 4418/28/32/42 memory card serial protocol' + license = 'gplv2+' + inputs = ['logic'] + outputs = [] + tags = ['Memory'] + channels = ( + {'id': 'rst', 'name': 'RST', 'desc': 'Reset line'}, + {'id': 'clk', 'name': 'CLK', 'desc': 'Clock line'}, + {'id': 'io', 'name': 'I/O', 'desc': 'I/O data line'}, + ) + annotations = ( + ('reset_sym', 'Reset Symbol'), + ('intr_sym', 'Interrupt Symbol'), + ('start_sym', 'Start Symbol'), + ('stop_sym', 'Stop Symbol'), + ('bit_sym', 'Bit Symbol'), + ('atr_byte', 'ATR Byte'), + ('cmd_byte', 'Command Byte'), + ('out_byte', 'Outgoing Byte'), + ('proc_byte', 'Processing Byte'), + ('atr_data', 'ATR data'), + ('cmd_data', 'Command data'), + ('out_data', 'Outgoing data'), + ('proc_data', 'Processing data'), + ) + annotation_rows = ( + ('symbols', 'Symbols', (Ann.RESET_SYM, Ann.INTR_SYM, + Ann.START_SYM, Ann.STOP_SYM, Ann.BIT_SYM,)), + ('fields', 'Fields', (Ann.ATR_BYTE, + Ann.CMD_BYTE, Ann.OUT_BYTE, Ann.PROC_BYTE,)), + ('operations', 'Operations', (Ann.ATR_DATA, + Ann.CMD_DATA, Ann.OUT_DATA, Ann.PROC_DATA,)), + ) + binary = ( + ('bytes', 'Bytes'), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.samplerate = None + self.max_addr = 256 + self.bits = [] + self.atr_bytes = [] + self.cmd_bytes = [] + self.cmd_proc = None + self.out_len = None + self.out_bytes = [] + self.proc_state = None + self.state = None + + def metadata(self, key, value): + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + self.out_binary = self.register(srd.OUTPUT_BINARY) + + def putx(self, ss, es, cls, data): + self.put(ss, es, self.out_ann, [cls, data,]) + + def putb(self, ss, es, cls , data): + self.put(ss, es, self.out_binary, [cls, data,]) + + def snums_to_usecs(self, snum_count): + if not self.samplerate: + return None + snums_per_usec = self.samplerate / 1e6 + usecs = snum_count / snums_per_usec + return usecs + + def lookup_proto_ann_txt(self, key, variables): + ann = { + 'RESET_SYM': [Ann.RESET_SYM, 'Reset', 'R',], + 'INTR_SYM': [Ann.INTR_SYM, 'Interrupt', 'Intr', 'I',], + 'START_SYM': [Ann.START_SYM, 'Start', 'ST', 'S',], + 'STOP_SYM': [Ann.STOP_SYM, 'Stop', 'SP', 'P',], + 'BIT_SYM': [Ann.BIT_SYM, '{bit}',], + 'ATR_BYTE': [Ann.ATR_BYTE, + 'Answer To Reset: {data:02x}', + 'ATR: {data:02x}', + '{data:02x}', + ], + 'CMD_BYTE': [Ann.CMD_BYTE, + 'Command: {data:02x}', + 'Cmd: {data:02x}', + '{data:02x}', + ], + 'OUT_BYTE': [Ann.OUT_BYTE, + 'Outgoing data: {data:02x}', + 'Data: {data:02x}', + '{data:02x}', + ], + 'PROC_BYTE': [Ann.PROC_BYTE, + 'Internal processing: {data:02x}', + 'Proc: {data:02x}', + '{data:02x}', + ], + 'ATR_DATA': [Ann.ATR_DATA, + 'Answer To Reset: {data}', + 'ATR: {data}', + '{data}', + ], + 'CMD_DATA': [Ann.CMD_DATA, + 'Command: {data}', + 'Cmd: {data}', + '{data}', + ], + 'OUT_DATA': [Ann.OUT_DATA, + 'Outgoing: {data}', + 'Out: {data}', + '{data}', + ], + 'PROC_DATA': [Ann.PROC_DATA, + 'Processing: {data}', + 'Proc: {data}', + '{data}', + ], + }.get(key, None) + if ann is None: + return None, [] + cls, texts = ann[0], ann[1:] + texts = [t.format(**variables) for t in texts] + return cls, texts + + def text_for_accu_bytes(self, accu): + if not accu: + return None, None, None, None + ss, es = accu[0][1], accu[-1][2] + data = [a[0] for a in accu] + text = " ".join(['{:02x}'.format(a) for a in data]) + return ss, es, data, text + + def flush_queued(self): + '''Flush previously accumulated operations details.''' + + # Can be called when either the completion of an operation got + # detected (reliably), or when some kind of reset condition was + # met while a potential previously observed operation has not + # been postprocessed yet (best effort). Should not harm when the + # routine gets invoked while no data was collected yet, or was + # flushed already. + # BEWARE! Will void internal state. Should really only get called + # "between operations", NOT between fields of an operation. + + if self.atr_bytes: + key = 'ATR_DATA' + ss, es, _, text = self.text_for_accu_bytes(self.atr_bytes) + cls, texts = self.lookup_proto_ann_txt(key, {'data': text}) + self.putx(ss, es, cls, texts) + + if self.cmd_bytes: + key = 'CMD_DATA' + ss, es, _, text = self.text_for_accu_bytes(self.cmd_bytes) + cls, texts = self.lookup_proto_ann_txt(key, {'data': text}) + self.putx(ss, es, cls, texts) + + if self.out_bytes: + key = 'OUT_DATA' + ss, es, _, text = self.text_for_accu_bytes(self.out_bytes) + cls, texts = self.lookup_proto_ann_txt(key, {'data': text}) + self.putx(ss, es, cls, texts) + + if self.proc_state: + key = 'PROC_DATA' + ss = self.proc_state['ss'] + es = self.proc_state['es'] + clk = self.proc_state['clk'] + high = self.proc_state['io1'] + text = '{clk} clocks, I/O {high}'.format(clk = clk, high = int(high)) + usecs = self.snums_to_usecs(es - ss) + if usecs: + msecs = usecs / 1000 + text = '{msecs:.2f} ms, {text}'.format(msecs = msecs, text = text) + cls, texts = self.lookup_proto_ann_txt(key, {'data': text}) + self.putx(ss, es, cls, texts) + + self.atr_bytes = None + self.cmd_bytes = None + self.cmd_proc = None + self.out_len = None + self.out_bytes = None + self.proc_state = None + self.state = None + + def handle_reset(self, ss, es, has_clk): + self.flush_queued() + key = '{}_SYM'.format('RESET' if has_clk else 'INTR') + cls, texts = self.lookup_proto_ann_txt(key, {}) + self.putx(ss, es, cls, texts) + self.bits = [] + self.state = 'ATR' if has_clk else None + + def handle_command(self, ss, is_start): + if is_start: + self.flush_queued() + key = '{}_SYM'.format('START' if is_start else 'STOP') + cls, texts = self.lookup_proto_ann_txt(key, {}) + self.putx(ss, ss, cls, texts) + self.bits = [] + self.state = 'CMD' if is_start else 'DATA' + + def command_check(self, ctrl, addr, data): + '''Interpret CTRL/ADDR/DATA command entry.''' + + # See the Siemens Datasheet section 2.3 Commands. The abbreviated + # text variants are my guesses, terse for readability at coarser + # zoom levels. + codes_table = { + 0x30: { + 'fmt': [ + 'read main memory, addr {addr:02x}', + 'RD-M @{addr:02x}', + ], + 'len': lambda ctrl, addr, data: self.max_addr - addr, + }, + 0x31: { + 'fmt': [ + 'read security memory', + 'RD-S', + ], + 'len': 4, + }, + 0x33: { + 'fmt': [ + 'compare verification data, addr {addr:02x}, data {data:02x}', + 'CMP-V @{addr:02x} ={data:02x}', + ], + 'proc': True, + }, + 0x34: { + 'fmt': [ + 'read protection memory, addr {addr:02x}', + 'RD-P @{addr:02x}', + ], + 'len': 4, + }, + 0x38: { + 'fmt': [ + 'update main memory, addr {addr:02x}, data {data:02x}', + 'WR-M @{addr:02x} ={data:02x}', + ], + 'proc': True, + }, + 0x39: { + 'fmt': [ + 'update security memory, addr {addr:02x}, data {data:02x}', + 'WR-S @{addr:02x} ={data:02x}', + ], + 'proc': True, + }, + 0x3c: { + 'fmt': [ + 'write protection memory, addr {addr:02x}, data {data:02x}', + 'WR-P @{addr:02x} ={data:02x}', + ], + 'proc': True, + }, + } + code = codes_table.get(ctrl, {}) + dflt_fmt = [ + 'unknown, ctrl {ctrl:02x}, addr {addr:02x}, data {data:02x}', + 'UNK-{ctrl:02x} @{addr:02x}, ={data:02x}', + ] + fmt = code.get('fmt', dflt_fmt) + if not isinstance(fmt, (list, tuple,)): + fmt = [fmt,] + texts = [f.format(ctrl = ctrl, addr = addr, data = data) for f in fmt] + length = code.get('len', None) + if callable(length): + length = length(ctrl, addr, data) + is_proc = code.get('proc', False) + return texts, length, is_proc + + def processing_start(self, ss, es, io_high): + self.proc_state = { + 'ss': ss or es, + 'es': es or ss, + 'clk': 0, + 'io1': bool(io_high), + } + + def processing_update(self, es, clk_inc, io_high): + if es is not None and es > self.proc_state['es']: + self.proc_state['es'] = es + self.proc_state['clk'] += clk_inc + if io_high: + self.proc_state['io1'] = True + + def handle_data_byte(self, ss, es, data, bits): + '''Accumulate CMD or OUT data bytes.''' + + if self.state == 'ATR': + if not self.atr_bytes: + self.atr_bytes = [] + self.atr_bytes.append([data, ss, es, bits,]) + if len(self.atr_bytes) == 4: + self.flush_queued() + return + + if self.state == 'CMD': + if not self.cmd_bytes: + self.cmd_bytes = [] + self.cmd_bytes.append([data, ss, es, bits,]) + if len(self.cmd_bytes) == 3: + ctrl, addr, data = [c[0] for c in self.cmd_bytes] + texts, length, proc = self.command_check(ctrl, addr, data) + # Immediately emit the annotation to not lose the text, + # and to support zoom levels for this specific case. + ss, es = self.cmd_bytes[0][1], self.cmd_bytes[-1][2] + cls = Ann.CMD_DATA + self.putx(ss, es, cls, texts) + self.cmd_bytes = [] + # Prepare to continue either at OUT or PROC after CMD. + self.out_len = length + self.cmd_proc = bool(proc) + self.state = None + return + + if self.state == 'OUT': + if not self.out_bytes: + self.out_bytes = [] + self.out_bytes.append([data, ss, es, bits,]) + if self.out_len is not None and len(self.out_bytes) == self.out_len: + self.flush_queued() + return + + def handle_data_bit(self, ss, es, bit): + '''Gather 8 bits of data (or track processing progress).''' + + # Switch late from DATA to either OUT or PROC. We can tell the + # type and potentially fixed length at the end of CMD already, + # but a START/STOP condition may void this information. So we + # do the switch at the first data bit after CMD. + # In the OUT case data bytes get accumulated, until either the + # expected byte count is reached, or another CMD starts. In the + # PROC case a high I/O level terminates execution. + if self.state == 'DATA': + if self.out_len: + self.state = 'OUT' + elif self.cmd_proc: + self.state = 'PROC' + self.processing_start(ss or es, es or ss, bit == 1) + else: + # Implementor's note: Handle unknown situations like + # outgoing data bytes, for the user's convenience. This + # will show OUT bytes even if it's just processing CLK + # cycles with constant or irrelevant I/O bit patterns. + self.state = 'OUT' + if self.state == 'PROC': + high = bit == 1 + if ss is not None: + self.processing_update(ss, 0, high) + if es is not None: + self.processing_update(es, 1, high) + if high: + self.flush_queued() + return + + # This routine gets called two times per bit value. Track the + # bit's value and ss timestamp when the bit period starts. And + # update the es timestamp at the end of the bit's validity. + if ss is not None: + self.bits.append([bit, ss, es or ss]) + return + if es is None: + # Unexpected invocation. Could be a glitch or invalid input + # data, or an interaction with RESET/START/STOP conditions. + self.bits = [] + return + if not self.bits: + return + if bit is not None: + self.bits[-1][0] = bit + # TODO Check for consistent bit level at ss and es when + # the information was available? Is bit data sampled at + # different clock edges depending whether data is sent + # or received? + self.bits[-1][2] = es + # Emit the bit's annotation. See if a byte was received. + bit, ss, es = self.bits[-1] + cls, texts = self.lookup_proto_ann_txt('BIT_SYM', {'bit': bit}) + self.putx(ss, es, cls, texts) + if len(self.bits) < 8: + return + + # Get the data byte value, and the byte's ss/es. Emit the byte's + # annotation and binary output. Pass the byte to upper layers. + # TODO Vary annotation classes with the byte's position within + # a field? To tell CTRL/ADDR/DATA of a CMD entry apart? + bits = self.bits + self.bits = [] + data = bitpack_lsb(bits, 0) + ss = bits[0][1] + es = bits[-1][2] + + key = '{}_BYTE'.format(self.state) + cls, texts = self.lookup_proto_ann_txt(key, {'data': data}) + if cls: + self.putx(ss, es, cls, texts) + self.putb(ss, es, Bin.BYTES, bytes([data])) + + self.handle_data_byte(ss, es, data, bits) + + def decode(self): + '''Decoder's main data interpretation loop.''' + + # Signal conditions tracked by the protocol decoder: + # - Rising and falling RST edges, which span the width of a + # high-active RESET pulse. RST has highest priority, no + # other activity can take place in this period. + # - Rising and falling CLK edges when RST is active. The + # CLK pulse when RST is asserted will reset the card's + # address counter. RST alone can terminate memory reads. + # - Rising and falling CLK edges when RST is inactive. This + # determines the period where BIT values are valid. + # - I/O edges during high CLK. These are START and STOP + # conditions that tell COMMAND and DATA phases apart. + # - Rise of I/O during internal processing. This expression + # is an unconditional part of the .wait() condition set. It + # is assumed that skipping this match in many cases is more + # efficient than the permanent re-construction of the .wait() + # condition list in every loop iteration, and preferrable to + # the maintainance cost of duplicating RST and CLK handling + # when checking I/O during internal processing. + ( + COND_RESET_START, COND_RESET_STOP, + COND_RSTCLK_START, COND_RSTCLK_STOP, + COND_DATA_START, COND_DATA_STOP, + COND_CMD_START, COND_CMD_STOP, + COND_PROC_IOH, + ) = range(9) + conditions = [ + {Pin.RST: 'r'}, + {Pin.RST: 'f'}, + {Pin.RST: 'h', Pin.CLK: 'r'}, + {Pin.RST: 'h', Pin.CLK: 'f'}, + {Pin.RST: 'l', Pin.CLK: 'r'}, + {Pin.RST: 'l', Pin.CLK: 'f'}, + {Pin.CLK: 'h', Pin.IO: 'f'}, + {Pin.CLK: 'h', Pin.IO: 'r'}, + {Pin.RST: 'l', Pin.IO: 'r'}, + ] + + ss_reset = es_reset = ss_clk = es_clk = None + while True: + + is_outgoing = self.state == 'OUT' + is_processing = self.state == 'PROC' + pins = self.wait(conditions) + io = pins[Pin.IO] + + # Handle RESET conditions, including an optional CLK pulse + # while RST is asserted. + if self.matched[COND_RESET_START]: + self.flush_queued() + ss_reset = self.samplenum + es_reset = ss_clk = es_clk = None + continue + if self.matched[COND_RESET_STOP]: + es_reset = self.samplenum + self.handle_reset(ss_reset or 0, es_reset, ss_clk and es_clk) + ss_reset = es_reset = ss_clk = es_clk = None + continue + if self.matched[COND_RSTCLK_START]: + ss_clk = self.samplenum + es_clk = None + continue + if self.matched[COND_RSTCLK_STOP]: + es_clk = self.samplenum + continue + + # Handle data bits' validity boundaries. Also covers the + # periodic check for high I/O level and update of details + # during internal processing. + if self.matched[COND_DATA_START]: + self.handle_data_bit(self.samplenum, None, io) + continue + if self.matched[COND_DATA_STOP]: + self.handle_data_bit(None, self.samplenum, None) + continue + + # Additional check for idle I/O during internal processing, + # independent of CLK edges this time. This assures that the + # decoder ends processing intervals as soon as possible, at + # the most precise timestamp. + if is_processing and self.matched[COND_PROC_IOH]: + self.handle_data_bit(self.samplenum, self.samplenum, io) + continue + + # The START/STOP conditions are only applicable outside of + # "outgoing data" or "internal processing" periods. This is + # what the data sheet specifies. + if not is_outgoing and not is_processing: + if self.matched[COND_CMD_START]: + self.handle_command(self.samplenum, True) + continue + if self.matched[COND_CMD_STOP]: + self.handle_command(self.samplenum, False) + continue diff --git a/libsigrokdecode4DSL/decoders/spdif/__init__.py b/libsigrokdecode4DSL/decoders/spdif/__init__.py new file mode 100644 index 00000000..3f5109a7 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/spdif/__init__.py @@ -0,0 +1,25 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 Guenther Wenninger +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +S/PDIF (Sony/Philips Digital Interface Format) is a serial bus for +transmitting audio data. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/spdif/pd.py b/libsigrokdecode4DSL/decoders/spdif/pd.py new file mode 100644 index 00000000..532bf825 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/spdif/pd.py @@ -0,0 +1,246 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 Guenther Wenninger +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd + +class SamplerateError(Exception): + pass + +class Decoder(srd.Decoder): + api_version = 3 + id = 'spdif' + name = 'S/PDIF' + longname = 'Sony/Philips Digital Interface Format' + desc = 'Serial bus for connecting digital audio devices.' + license = 'gplv2+' + inputs = ['logic'] + outputs = [] + tags = ['Audio', 'PC'] + channels = ( + {'id': 'data', 'name': 'Data', 'desc': 'Data line'}, + ) + annotations = ( + ('bitrate', 'Bitrate / baudrate'), + ('preamble', 'Preamble'), + ('bits', 'Bits'), + ('aux', 'Auxillary-audio-databits'), + ('samples', 'Audio Samples'), + ('validity', 'Data Valid'), + ('subcode', 'Subcode data'), + ('chan_stat', 'Channnel Status'), + ('parity', 'Parity Bit'), + ) + annotation_rows = ( + ('info', 'Info', (0, 1, 3, 5, 6, 7, 8)), + ('bits', 'Bits', (2,)), + ('samples', 'Samples', (4,)), + ) + + def putx(self, ss, es, data): + self.put(ss, es, self.out_ann, data) + + def puty(self, data): + self.put(self.ss_edge, self.samplenum, self.out_ann, data) + + def __init__(self): + self.reset() + + def reset(self): + self.state = 'GET FIRST PULSE WIDTH' + self.ss_edge = None + self.first_edge = True + self.samplenum_prev_edge = 0 + self.pulse_width = 0 + + self.clocks = [] + self.range1 = 0 + self.range2 = 0 + + self.preamble_state = 0 + self.preamble = [] + self.seen_preamble = False + self.last_preamble = 0 + + self.first_one = True + self.subframe = [] + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def metadata(self, key, value): + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + + def get_pulse_type(self): + if self.range1 == 0 or self.range2 == 0: + return -1 + if self.pulse_width >= self.range2: + return 2 + elif self.pulse_width >= self.range1: + return 0 + else: + return 1 + + def find_first_pulse_width(self): + if self.pulse_width != 0: + self.clocks.append(self.pulse_width) + self.state = 'GET SECOND PULSE WIDTH' + + def find_second_pulse_width(self): + if self.pulse_width > (self.clocks[0] * 1.3) or \ + self.pulse_width < (self.clocks[0] * 0.7): + self.clocks.append(self.pulse_width) + self.state = 'GET THIRD PULSE WIDTH' + + def find_third_pulse_width(self): + if not ((self.pulse_width > (self.clocks[0] * 1.3) or \ + self.pulse_width < (self.clocks[0] * 0.7)) \ + and (self.pulse_width > (self.clocks[1] * 1.3) or \ + self.pulse_width < (self.clocks[1] * 0.7))): + return + + self.clocks.append(self.pulse_width) + self.clocks.sort() + self.range1 = (self.clocks[0] + self.clocks[1]) / 2 + self.range2 = (self.clocks[1] + self.clocks[2]) / 2 + spdif_bitrate = int(self.samplerate / (self.clocks[2] / 1.5)) + self.ss_edge = 0 + + self.puty([0, ['Signal Bitrate: %d Mbit/s (=> %d kHz)' % \ + (spdif_bitrate, (spdif_bitrate/ (2 * 32)))]]) + + clock_period_nsec = 1000000000 / spdif_bitrate + + self.last_preamble = self.samplenum + + # We are done recovering the clock, now let's decode the data stream. + self.state = 'DECODE STREAM' + + def decode_stream(self): + pulse = self.get_pulse_type() + + if not self.seen_preamble: + # This is probably the start of a preamble, decode it. + if pulse == 2: + self.preamble.append(self.get_pulse_type()) + self.state = 'DECODE PREAMBLE' + self.ss_edge = self.samplenum - self.pulse_width - 1 + return + + # We've seen a preamble. + if pulse == 1 and self.first_one: + self.first_one = False + self.subframe.append([pulse, self.samplenum - \ + self.pulse_width - 1, self.samplenum]) + elif pulse == 1 and not self.first_one: + self.subframe[-1][2] = self.samplenum + self.putx(self.subframe[-1][1], self.samplenum, [2, ['1']]) + self.bitcount += 1 + self.first_one = True + else: + self.subframe.append([pulse, self.samplenum - \ + self.pulse_width - 1, self.samplenum]) + self.putx(self.samplenum - self.pulse_width - 1, + self.samplenum, [2, ['0']]) + self.bitcount += 1 + + if self.bitcount == 28: + aux_audio_data = self.subframe[0:4] + sam, sam_rot = '', '' + for a in aux_audio_data: + sam = sam + str(a[0]) + sam_rot = str(a[0]) + sam_rot + sample = self.subframe[4:24] + for s in sample: + sam = sam + str(s[0]) + sam_rot = str(s[0]) + sam_rot + validity = self.subframe[24:25] + subcode_data = self.subframe[25:26] + channel_status = self.subframe[26:27] + parity = self.subframe[27:28] + + self.putx(aux_audio_data[0][1], aux_audio_data[3][2], \ + [3, ['Aux 0x%x' % int(sam, 2), '0x%x' % int(sam, 2)]]) + self.putx(sample[0][1], sample[19][2], \ + [3, ['Sample 0x%x' % int(sam, 2), '0x%x' % int(sam, 2)]]) + self.putx(aux_audio_data[0][1], sample[19][2], \ + [4, ['Audio 0x%x' % int(sam_rot, 2), '0x%x' % int(sam_rot, 2)]]) + if validity[0][0] == 0: + self.putx(validity[0][1], validity[0][2], [5, ['V']]) + else: + self.putx(validity[0][1], validity[0][2], [5, ['E']]) + self.putx(subcode_data[0][1], subcode_data[0][2], + [6, ['S: %d' % subcode_data[0][0]]]) + self.putx(channel_status[0][1], channel_status[0][2], + [7, ['C: %d' % channel_status[0][0]]]) + self.putx(parity[0][1], parity[0][2], [8, ['P: %d' % parity[0][0]]]) + + self.subframe = [] + self.seen_preamble = False + self.bitcount = 0 + + def decode_preamble(self): + if self.preamble_state == 0: + self.preamble.append(self.get_pulse_type()) + self.preamble_state = 1 + elif self.preamble_state == 1: + self.preamble.append(self.get_pulse_type()) + self.preamble_state = 2 + elif self.preamble_state == 2: + self.preamble.append(self.get_pulse_type()) + self.preamble_state = 0 + self.state = 'DECODE STREAM' + if self.preamble == [2, 0, 1, 0]: + self.puty([1, ['Preamble W', 'W']]) + elif self.preamble == [2, 2, 1, 1]: + self.puty([1, ['Preamble M', 'M']]) + elif self.preamble == [2, 1, 1, 2]: + self.puty([1, ['Preamble B', 'B']]) + else: + self.puty([1, ['Unknown Preamble', 'Unknown Prea.', 'U']]) + self.preamble = [] + self.seen_preamble = True + self.bitcount = 0 + self.first_one = True + + self.last_preamble = self.samplenum + + def decode(self): + if not self.samplerate: + raise SamplerateError('Cannot decode without samplerate.') + + # Throw away first detected edge as it might be mangled data. + self.wait({0: 'e'}) + + while True: + # Wait for any edge (rising or falling). + (data,) = self.wait({0: 'e'}) + self.pulse_width = self.samplenum - self.samplenum_prev_edge - 1 + self.samplenum_prev_edge = self.samplenum + + if self.state == 'GET FIRST PULSE WIDTH': + self.find_first_pulse_width() + elif self.state == 'GET SECOND PULSE WIDTH': + self.find_second_pulse_width() + elif self.state == 'GET THIRD PULSE WIDTH': + self.find_third_pulse_width() + elif self.state == 'DECODE STREAM': + self.decode_stream() + elif self.state == 'DECODE PREAMBLE': + self.decode_preamble() diff --git a/libsigrokdecode4DSL/decoders/spiflash/__init__.py b/libsigrokdecode4DSL/decoders/spiflash/__init__.py new file mode 100644 index 00000000..151bd3e2 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/spiflash/__init__.py @@ -0,0 +1,30 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2012-2015 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This decoder stacks on top of the 'spi' PD and decodes the xx25 series +SPI (NOR) flash chip protocol. + +It currently supports the MX25L1605D/MX25L3205D/MX25L6405D. + +Details: +http://www.macronix.com/QuickPlace/hq/PageLibrary4825740B00298A3B.nsf/h_Index/3F21BAC2E121E17848257639003A3146/$File/MX25L1605D-3205D-6405D-1.5.pdf +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/spiflash/lists.py b/libsigrokdecode4DSL/decoders/spiflash/lists.py new file mode 100644 index 00000000..5c366bee --- /dev/null +++ b/libsigrokdecode4DSL/decoders/spiflash/lists.py @@ -0,0 +1,144 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2015 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +from collections import OrderedDict + +# OrderedDict which maps command IDs to their names and descriptions. +# Please keep this sorted by command ID. +# Don't forget to update 'Ann' in pd.py if you add/remove items here. +cmds = OrderedDict([ + (0x01, ('WRSR', 'Write status register')), + (0x02, ('PP', 'Page program')), + (0x03, ('READ', 'Read data')), + (0x04, ('WRDI', 'Write disable')), + (0x05, ('RDSR', 'Read status register')), + (0x06, ('WREN', 'Write enable')), + (0x0b, ('FAST/READ', 'Fast read data')), + (0x20, ('SE', 'Sector erase')), + (0x2b, ('RDSCUR', 'Read security register')), + (0x2f, ('WRSCUR', 'Write security register')), + (0x35, ('RDSR2', 'Read status register 2')), + (0x60, ('CE', 'Chip erase')), + (0x70, ('ESRY', 'Enable SO to output RY/BY#')), + (0x80, ('DSRY', 'Disable SO to output RY/BY#')), + (0x82, ('WRITE1', 'Main memory page program through buffer 1 with built-in erase')), + (0x85, ('WRITE2', 'Main memory page program through buffer 2 with built-in erase')), + (0x90, ('REMS', 'Read electronic manufacturer & device ID')), + (0x9f, ('RDID', 'Read identification')), + (0xab, ('RDP/RES', 'Release from deep powerdown / Read electronic ID')), + (0xad, ('CP', 'Continuously program mode')), + (0xb1, ('ENSO', 'Enter secured OTP')), + (0xb9, ('DP', 'Deep power down')), + (0xbb, ('2READ', '2x I/O read')), # a.k.a. "Fast read dual I/O". + (0xc1, ('EXSO', 'Exit secured OTP')), + (0xc7, ('CE2', 'Chip erase')), # Alternative command ID + (0xd7, ('STATUS', 'Status register read')), + (0xd8, ('BE', 'Block erase')), + (0xef, ('REMS2', 'Read ID for 2x I/O mode')), +]) + +device_name = { + 'adesto': { + 0x00: 'AT45Dxxx family, standard series', + }, + 'fidelix': { + 0x15: 'FM25Q32', + }, + 'macronix': { + 0x14: 'MX25L1605D', + 0x15: 'MX25L3205D', + 0x16: 'MX25L6405D', + }, + 'winbond': { + 0x13: 'W25Q80DV', + }, +} + +chips = { + # Adesto + 'adesto_at45db161e': { + 'vendor': 'Adesto', + 'model': 'AT45DB161E', + 'res_id': 0xff, # The chip doesn't emit an ID here. + 'rems_id': 0xffff, # Not supported by the chip. + 'rems2_id': 0xffff, # Not supported by the chip. + 'rdid_id': 0x1f26000100, # RDID and 2 extra "EDI" bytes. + 'page_size': 528, # Configurable, could also be 512 bytes. + 'sector_size': 128 * 1024, + 'block_size': 4 * 1024, + }, + # FIDELIX + 'fidelix_fm25q32': { + 'vendor': 'FIDELIX', + 'model': 'FM25Q32', + 'res_id': 0x15, + 'rems_id': 0xa115, + 'rems2_id': 0xa115, + 'rdid_id': 0xa14016, + 'page_size': 256, + 'sector_size': 4 * 1024, + 'block_size': 64 * 1024, + }, + # Macronix + 'macronix_mx25l1605d': { + 'vendor': 'Macronix', + 'model': 'MX25L1605D', + 'res_id': 0x14, + 'rems_id': 0xc214, + 'rems2_id': 0xc214, + 'rdid_id': 0xc22015, + 'page_size': 256, + 'sector_size': 4 * 1024, + 'block_size': 64 * 1024, + }, + 'macronix_mx25l3205d': { + 'vendor': 'Macronix', + 'model': 'MX25L3205D', + 'res_id': 0x15, + 'rems_id': 0xc215, + 'rems2_id': 0xc215, + 'rdid_id': 0xc22016, + 'page_size': 256, + 'sector_size': 4 * 1024, + 'block_size': 64 * 1024, + }, + 'macronix_mx25l6405d': { + 'vendor': 'Macronix', + 'model': 'MX25L6405D', + 'res_id': 0x16, + 'rems_id': 0xc216, + 'rems2_id': 0xc216, + 'rdid_id': 0xc22017, + 'page_size': 256, + 'sector_size': 4 * 1024, + 'block_size': 64 * 1024, + }, + # Winbond + 'winbond_w25q80dv': { + 'vendor': 'Winbond', + 'model': 'W25Q80DV', + 'res_id': 0x13, + 'rems_id': 0xef13, + 'rems2_id': 0xffff, # Not supported by the chip. + 'rdid_id': 0xef4014, + 'page_size': 256, + 'sector_size': 4 * 1024, + 'block_size': 64 * 1024, # Configurable, could also be 32 * 1024 bytes. + }, +} diff --git a/libsigrokdecode4DSL/decoders/spiflash/pd.py b/libsigrokdecode4DSL/decoders/spiflash/pd.py new file mode 100644 index 00000000..5ee22740 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/spiflash/pd.py @@ -0,0 +1,539 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2011-2016 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd +from .lists import * + +L = len(cmds) + +# Don't forget to keep this in sync with 'cmds' is lists.py. +class Ann: + WRSR, PP, READ, WRDI, RDSR, WREN, FAST_READ, SE, RDSCUR, WRSCUR, \ + RDSR2, CE, ESRY, DSRY, WRITE1, WRITE2, REMS, RDID, RDP_RES, CP, ENSO, DP, \ + READ2X, EXSO, CE2, STATUS, BE, REMS2, \ + BIT, FIELD, WARN = range(L + 3) + +def cmd_annotation_classes(): + return tuple([tuple([cmd[0].lower(), cmd[1]]) for cmd in cmds.values()]) + +def decode_dual_bytes(sio0, sio1): + # Given a byte in SIO0 (MOSI) of even bits and a byte in + # SIO1 (MISO) of odd bits, return a tuple of two bytes. + def combine_byte(even, odd): + result = 0 + for bit in range(4): + if even & (1 << bit): + result |= 1 << (bit*2) + if odd & (1 << bit): + result |= 1 << ((bit*2) + 1) + return result + return (combine_byte(sio0 >> 4, sio1 >> 4), combine_byte(sio0, sio1)) + +def decode_status_reg(data): + # TODO: Additional per-bit(s) self.put() calls with correct start/end. + + # Bits[0:0]: WIP (write in progress) + s = 'W' if (data & (1 << 0)) else 'No w' + ret = '%srite operation in progress.\n' % s + + # Bits[1:1]: WEL (write enable latch) + s = '' if (data & (1 << 1)) else 'not ' + ret += 'Internal write enable latch is %sset.\n' % s + + # Bits[5:2]: Block protect bits + # TODO: More detailed decoding (chip-dependent). + ret += 'Block protection bits (BP3-BP0): 0x%x.\n' % ((data & 0x3c) >> 2) + + # Bits[6:6]: Continuously program mode (CP mode) + s = '' if (data & (1 << 6)) else 'not ' + ret += 'Device is %sin continuously program mode (CP mode).\n' % s + + # Bits[7:7]: SRWD (status register write disable) + s = 'not ' if (data & (1 << 7)) else '' + ret += 'Status register writes are %sallowed.\n' % s + + return ret + +class Decoder(srd.Decoder): + api_version = 3 + id = 'spiflash' + name = 'SPI flash' + longname = 'SPI flash chips' + desc = 'xx25 series SPI (NOR) flash chip protocol.' + license = 'gplv2+' + inputs = ['spi'] + outputs = [] + tags = ['IC', 'Memory'] + annotations = cmd_annotation_classes() + ( + ('bit', 'Bit'), + ('field', 'Field'), + ('warning', 'Warning'), + ) + annotation_rows = ( + ('bits', 'Bits', (L + 0,)), + ('fields', 'Fields', (L + 1,)), + ('commands', 'Commands', tuple(range(len(cmds)))), + ('warnings', 'Warnings', (L + 2,)), + ) + options = ( + {'id': 'chip', 'desc': 'Chip', 'default': tuple(chips.keys())[0], + 'values': tuple(chips.keys())}, + {'id': 'format', 'desc': 'Data format', 'default': 'hex', + 'values': ('hex', 'ascii')}, + ) + + def __init__(self): + self.reset() + + def reset(self): + self.device_id = -1 + self.on_end_transaction = None + self.end_current_transaction() + self.writestate = 0 + + # Build dict mapping command keys to handler functions. Each + # command in 'cmds' (defined in lists.py) has a matching + # handler self.handle_. + def get_handler(cmd): + s = 'handle_%s' % cmds[cmd][0].lower().replace('/', '_') + return getattr(self, s) + self.cmd_handlers = dict((cmd, get_handler(cmd)) for cmd in cmds.keys()) + + def end_current_transaction(self): + if self.on_end_transaction is not None: # Callback for CS# transition. + self.on_end_transaction() + self.on_end_transaction = None + self.state = None + self.cmdstate = 1 + self.addr = 0 + self.data = [] + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + self.chip = chips[self.options['chip']] + self.vendor = self.options['chip'].split('_')[0] + + def putx(self, data): + # Simplification, most annotations span exactly one SPI byte/packet. + self.put(self.ss, self.es, self.out_ann, data) + + def putf(self, data): + self.put(self.ss_field, self.es_field, self.out_ann, data) + + def putc(self, data): + self.put(self.ss_cmd, self.es_cmd, self.out_ann, data) + + def device(self): + return device_name[self.vendor].get(self.device_id, 'Unknown') + + def vendor_device(self): + return '%s %s' % (self.chip['vendor'], self.device()) + + def cmd_ann_list(self): + x, s = cmds[self.state][0], cmds[self.state][1] + return ['Command: %s (%s)' % (s, x), 'Command: %s' % s, + 'Cmd: %s' % s, 'Cmd: %s' % x, x] + + def cmd_vendor_dev_list(self): + c, d = cmds[self.state], 'Device = %s' % self.vendor_device() + return ['%s (%s): %s' % (c[1], c[0], d), '%s: %s' % (c[1], d), + '%s: %s' % (c[0], d), d, self.vendor_device()] + + def emit_cmd_byte(self): + self.ss_cmd = self.ss + self.putx([Ann.FIELD, self.cmd_ann_list()]) + self.addr = 0 + + def emit_addr_bytes(self, mosi): + self.addr |= (mosi << ((4 - self.cmdstate) * 8)) + b = ((3 - (self.cmdstate - 2)) * 8) - 1 + self.putx([Ann.BIT, + ['Address bits %d..%d: 0x%02x' % (b, b - 7, mosi), + 'Addr bits %d..%d: 0x%02x' % (b, b - 7, mosi), + 'Addr bits %d..%d' % (b, b - 7), 'A%d..A%d' % (b, b - 7)]]) + if self.cmdstate == 2: + self.ss_field = self.ss + if self.cmdstate == 4: + self.es_field = self.es + self.putf([Ann.FIELD, ['Address: 0x%06x' % self.addr, + 'Addr: 0x%06x' % self.addr, '0x%06x' % self.addr]]) + + def handle_wren(self, mosi, miso): + self.putx([Ann.WREN, self.cmd_ann_list()]) + self.writestate = 1 + + def handle_wrdi(self, mosi, miso): + self.putx([Ann.WRDI, self.cmd_ann_list()]) + self.writestate = 0 + + def handle_rdid(self, mosi, miso): + if self.cmdstate == 1: + # Byte 1: Master sends command ID. + self.emit_cmd_byte() + elif self.cmdstate == 2: + # Byte 2: Slave sends the JEDEC manufacturer ID. + self.putx([Ann.FIELD, ['Manufacturer ID: 0x%02x' % miso]]) + elif self.cmdstate == 3: + # Byte 3: Slave sends the memory type. + self.putx([Ann.FIELD, ['Memory type: 0x%02x' % miso]]) + elif self.cmdstate == 4: + # Byte 4: Slave sends the device ID. + self.device_id = miso + self.putx([Ann.FIELD, ['Device ID: 0x%02x' % miso]]) + + if self.cmdstate == 4: + self.es_cmd = self.es + self.putc([Ann.RDID, self.cmd_vendor_dev_list()]) + self.state = None + else: + self.cmdstate += 1 + + def handle_rdsr(self, mosi, miso): + # Read status register: Master asserts CS#, sends RDSR command, + # reads status register byte. If CS# is kept asserted, the status + # register can be read continuously / multiple times in a row. + # When done, the master de-asserts CS# again. + if self.cmdstate == 1: + # Byte 1: Master sends command ID. + self.emit_cmd_byte() + elif self.cmdstate >= 2: + # Bytes 2-x: Slave sends status register as long as master clocks. + self.es_cmd = self.es + self.putx([Ann.BIT, [decode_status_reg(miso)]]) + self.putx([Ann.FIELD, ['Status register']]) + self.putc([Ann.RDSR, self.cmd_ann_list()]) + # Set write latch state. + self.writestate = 1 if (miso & (1 << 1)) else 0 + self.cmdstate += 1 + + def handle_rdsr2(self, mosi, miso): + # Read status register 2: Master asserts CS#, sends RDSR2 command, + # reads status register 2 byte. If CS# is kept asserted, the status + # register 2 can be read continuously / multiple times in a row. + # When done, the master de-asserts CS# again. + if self.cmdstate == 1: + # Byte 1: Master sends command ID. + self.emit_cmd_byte() + elif self.cmdstate >= 2: + # Bytes 2-x: Slave sends status register 2 as long as master clocks. + self.es_cmd = self.es + # TODO: Decode status register 2 correctly. + self.putx([Ann.BIT, [decode_status_reg(miso)]]) + self.putx([Ann.FIELD, ['Status register 2']]) + self.putc([Ann.RDSR2, self.cmd_ann_list()]) + self.cmdstate += 1 + + def handle_wrsr(self, mosi, miso): + # Write status register: Master asserts CS#, sends WRSR command, + # writes 1 or 2 status register byte(s). + # When done, the master de-asserts CS# again. If this doesn't happen + # the WRSR command will not be executed. + if self.cmdstate == 1: + # Byte 1: Master sends command ID. + self.emit_cmd_byte() + elif self.cmdstate == 2: + # Byte 2: Master sends status register 1. + self.putx([Ann.BIT, [decode_status_reg(mosi)]]) + self.putx([Ann.FIELD, ['Status register 1']]) + # Set write latch state. + self.writestate = 1 if (miso & (1 << 1)) else 0 + elif self.cmdstate == 3: + # Byte 3: Master sends status register 2. + # TODO: Decode status register 2 correctly. + self.putx([Ann.BIT, [decode_status_reg(mosi)]]) + self.putx([Ann.FIELD, ['Status register 2']]) + self.es_cmd = self.es + self.putc([Ann.WRSR, self.cmd_ann_list()]) + self.cmdstate += 1 + + def handle_read(self, mosi, miso): + # Read data bytes: Master asserts CS#, sends READ command, sends + # 3-byte address, reads >= 1 data bytes, de-asserts CS#. + if self.cmdstate == 1: + # Byte 1: Master sends command ID. + self.emit_cmd_byte() + elif self.cmdstate in (2, 3, 4): + # Bytes 2/3/4: Master sends read address (24bits, MSB-first). + self.emit_addr_bytes(mosi) + elif self.cmdstate >= 5: + # Bytes 5-x: Master reads data bytes (until CS# de-asserted). + self.es_field = self.es # Will be overwritten for each byte. + if self.cmdstate == 5: + self.ss_field = self.ss + self.on_end_transaction = lambda: self.output_data_block('Data', Ann.READ) + self.data.append(miso) + self.cmdstate += 1 + + def handle_write_common(self, mosi, miso, ann): + # Write data bytes: Master asserts CS#, sends WRITE command, sends + # 3-byte address, writes >= 1 data bytes, de-asserts CS#. + if self.cmdstate == 1: + # Byte 1: Master sends command ID. + self.emit_cmd_byte() + if self.writestate == 0: + self.putc([Ann.WARN, ['Warning: WREN might be missing']]) + elif self.cmdstate in (2, 3, 4): + # Bytes 2/3/4: Master sends write address (24bits, MSB-first). + self.emit_addr_bytes(mosi) + elif self.cmdstate >= 5: + # Bytes 5-x: Master writes data bytes (until CS# de-asserted). + self.es_field = self.es # Will be overwritten for each byte. + if self.cmdstate == 5: + self.ss_field = self.ss + self.on_end_transaction = lambda: self.output_data_block('Data', ann) + self.data.append(mosi) + self.cmdstate += 1 + + def handle_write1(self, mosi, miso): + self.handle_write_common(mosi, miso, Ann.WRITE1) + + def handle_write2(self, mosi, miso): + self.handle_write_common(mosi, miso, Ann.WRITE2) + + def handle_fast_read(self, mosi, miso): + # Fast read: Master asserts CS#, sends FAST READ command, sends + # 3-byte address + 1 dummy byte, reads >= 1 data bytes, de-asserts CS#. + if self.cmdstate == 1: + # Byte 1: Master sends command ID. + self.emit_cmd_byte() + elif self.cmdstate in (2, 3, 4): + # Bytes 2/3/4: Master sends read address (24bits, MSB-first). + self.emit_addr_bytes(mosi) + elif self.cmdstate == 5: + self.putx([Ann.BIT, ['Dummy byte: 0x%02x' % mosi]]) + elif self.cmdstate >= 6: + # Bytes 6-x: Master reads data bytes (until CS# de-asserted). + self.es_field = self.es # Will be overwritten for each byte. + if self.cmdstate == 6: + self.ss_field = self.ss + self.on_end_transaction = lambda: self.output_data_block('Data', Ann.FAST_READ) + self.data.append(miso) + self.cmdstate += 1 + + def handle_2read(self, mosi, miso): + # 2x I/O read (fast read dual I/O): Master asserts CS#, sends 2READ + # command, sends 3-byte address + 1 dummy byte, reads >= 1 data bytes, + # de-asserts CS#. All data after the command is sent via two I/O pins. + # MOSI = SIO0 = even bits, MISO = SIO1 = odd bits. + if self.cmdstate != 1: + b1, b2 = decode_dual_bytes(mosi, miso) + if self.cmdstate == 1: + # Byte 1: Master sends command ID. + self.emit_cmd_byte() + elif self.cmdstate == 2: + # Bytes 2/3(/4): Master sends read address (24bits, MSB-first). + # Handle bytes 2 and 3 here. + self.emit_addr_bytes(b1) + self.cmdstate = 3 + self.emit_addr_bytes(b2) + elif self.cmdstate == 4: + # Byte 5: Dummy byte. Also handle byte 4 (address LSB) here. + self.emit_addr_bytes(b1) + self.cmdstate = 5 + self.putx([Ann.BIT, ['Dummy byte: 0x%02x' % b2]]) + elif self.cmdstate >= 6: + # Bytes 6-x: Master reads data bytes (until CS# de-asserted). + self.es_field = self.es # Will be overwritten for each byte. + if self.cmdstate == 6: + self.ss_field = self.ss + self.on_end_transaction = lambda: self.output_data_block('Data', Ann.READ2X) + self.data.append(b1) + self.data.append(b2) + self.cmdstate += 1 + + def handle_status(self, mosi, miso): + if self.cmdstate == 1: + # Byte 1: Master sends command ID. + self.emit_cmd_byte() + self.on_end_transaction = lambda: self.putc([Ann.STATUS, [cmds[self.state][1]]]) + else: + # Will be overwritten for each byte. + self.es_cmd = self.es + self.es_field = self.es + if self.cmdstate == 2: + self.ss_field = self.ss + self.putx([Ann.BIT, ['Status register byte %d: 0x%02x' % ((self.cmdstate % 2) + 1, miso)]]) + self.cmdstate += 1 + + # TODO: Warn/abort if we don't see the necessary amount of bytes. + def handle_se(self, mosi, miso): + if self.cmdstate == 1: + # Byte 1: Master sends command ID. + self.emit_cmd_byte() + if self.writestate == 0: + self.putx([Ann.WARN, ['Warning: WREN might be missing']]) + elif self.cmdstate in (2, 3, 4): + # Bytes 2/3/4: Master sends sector address (24bits, MSB-first). + self.emit_addr_bytes(mosi) + + if self.cmdstate == 4: + self.es_cmd = self.es + d = 'Erase sector %d (0x%06x)' % (self.addr, self.addr) + self.putc([Ann.SE, [d]]) + # TODO: Max. size depends on chip, check that too if possible. + if self.addr % 4096 != 0: + # Sector addresses must be 4K-aligned (same for all 3 chips). + self.putc([Ann.WARN, ['Warning: Invalid sector address!']]) + self.state = None + else: + self.cmdstate += 1 + + def handle_be(self, mosi, miso): + pass # TODO + + def handle_ce(self, mosi, miso): + self.putx([Ann.CE, self.cmd_ann_list()]) + if self.writestate == 0: + self.putx([Ann.WARN, ['Warning: WREN might be missing']]) + + def handle_ce2(self, mosi, miso): + self.putx([Ann.CE2, self.cmd_ann_list()]) + if self.writestate == 0: + self.putx([Ann.WARN, ['Warning: WREN might be missing']]) + + def handle_pp(self, mosi, miso): + # Page program: Master asserts CS#, sends PP command, sends 3-byte + # page address, sends >= 1 data bytes, de-asserts CS#. + if self.cmdstate == 1: + # Byte 1: Master sends command ID. + self.emit_cmd_byte() + elif self.cmdstate in (2, 3, 4): + # Bytes 2/3/4: Master sends page address (24bits, MSB-first). + self.emit_addr_bytes(mosi) + elif self.cmdstate >= 5: + # Bytes 5-x: Master sends data bytes (until CS# de-asserted). + self.es_field = self.es # Will be overwritten for each byte. + if self.cmdstate == 5: + self.ss_field = self.ss + self.on_end_transaction = lambda: self.output_data_block('Data', Ann.PP) + self.data.append(mosi) + self.cmdstate += 1 + + def handle_cp(self, mosi, miso): + pass # TODO + + def handle_dp(self, mosi, miso): + pass # TODO + + def handle_rdp_res(self, mosi, miso): + if self.cmdstate == 1: + # Byte 1: Master sends command ID. + self.emit_cmd_byte() + elif self.cmdstate in (2, 3, 4): + # Bytes 2/3/4: Master sends three dummy bytes. + self.putx([Ann.FIELD, ['Dummy byte: %02x' % mosi]]) + elif self.cmdstate == 5: + # Byte 5: Slave sends device ID. + self.es_cmd = self.es + self.device_id = miso + self.putx([Ann.FIELD, ['Device ID: %s' % self.device()]]) + d = 'Device = %s' % self.vendor_device() + self.putc([Ann.RDP_RES, self.cmd_vendor_dev_list()]) + self.state = None + self.cmdstate += 1 + + def handle_rems(self, mosi, miso): + if self.cmdstate == 1: + # Byte 1: Master sends command ID. + self.emit_cmd_byte() + elif self.cmdstate in (2, 3): + # Bytes 2/3: Master sends two dummy bytes. + self.putx([Ann.FIELD, ['Dummy byte: 0x%02x' % mosi]]) + elif self.cmdstate == 4: + # Byte 4: Master sends 0x00 or 0x01. + # 0x00: Master wants manufacturer ID as first reply byte. + # 0x01: Master wants device ID as first reply byte. + self.manufacturer_id_first = True if (mosi == 0x00) else False + d = 'manufacturer' if (mosi == 0x00) else 'device' + self.putx([Ann.FIELD, ['Master wants %s ID first' % d]]) + elif self.cmdstate == 5: + # Byte 5: Slave sends manufacturer ID (or device ID). + self.ids = [miso] + d = 'Manufacturer' if self.manufacturer_id_first else 'Device' + self.putx([Ann.FIELD, ['%s ID: 0x%02x' % (d, miso)]]) + elif self.cmdstate == 6: + # Byte 6: Slave sends device ID (or manufacturer ID). + self.ids.append(miso) + d = 'Device' if self.manufacturer_id_first else 'Manufacturer' + self.putx([Ann.FIELD, ['%s ID: 0x%02x' % (d, miso)]]) + + if self.cmdstate == 6: + id_ = self.ids[1] if self.manufacturer_id_first else self.ids[0] + self.device_id = id_ + self.es_cmd = self.es + self.putc([Ann.REMS, self.cmd_vendor_dev_list()]) + self.state = None + else: + self.cmdstate += 1 + + def handle_rems2(self, mosi, miso): + pass # TODO + + def handle_enso(self, mosi, miso): + pass # TODO + + def handle_exso(self, mosi, miso): + pass # TODO + + def handle_rdscur(self, mosi, miso): + pass # TODO + + def handle_wrscur(self, mosi, miso): + pass # TODO + + def handle_esry(self, mosi, miso): + pass # TODO + + def handle_dsry(self, mosi, miso): + pass # TODO + + def output_data_block(self, label, idx): + # Print accumulated block of data + # (called on CS# de-assert via self.on_end_transaction callback). + self.es_cmd = self.es # End on the CS# de-assert sample. + if self.options['format'] == 'hex': + s = ' '.join([('%02x' % b) for b in self.data]) + else: + s = ''.join(map(chr, self.data)) + self.putf([Ann.FIELD, ['%s (%d bytes)' % (label, len(self.data))]]) + self.putc([idx, ['%s (addr 0x%06x, %d bytes): %s' % \ + (cmds[self.state][1], self.addr, len(self.data), s)]]) + + def decode(self, ss, es, data): + ptype, mosi, miso = data + + self.ss, self.es = ss, es + + if ptype == 'CS-CHANGE': + self.end_current_transaction() + + if ptype != 'DATA': + return + + # If we encountered a known chip command, enter the resp. state. + if self.state is None: + self.state = mosi + self.cmdstate = 1 + + # Handle commands. + try: + self.cmd_handlers[self.state](mosi, miso) + except KeyError: + self.putx([Ann.BIT, ['Unknown command: 0x%02x' % mosi]]) + self.state = None diff --git a/libsigrokdecode4DSL/decoders/ssi32/__init__.py b/libsigrokdecode4DSL/decoders/ssi32/__init__.py new file mode 100644 index 00000000..cd6890fa --- /dev/null +++ b/libsigrokdecode4DSL/decoders/ssi32/__init__.py @@ -0,0 +1,28 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2016 Robert Bosch Car Multimedia GmbH +## Authors: Oleksij Rempel +## +## +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This decoder stacks on top of the 'spi' PD and decodes the Bosch +SSI32 protocol. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/ssi32/pd.py b/libsigrokdecode4DSL/decoders/ssi32/pd.py new file mode 100644 index 00000000..51608039 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/ssi32/pd.py @@ -0,0 +1,127 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2016 Robert Bosch Car Multimedia GmbH +## Authors: Oleksij Rempel +## +## +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd + +class Decoder(srd.Decoder): + api_version = 3 + id = 'ssi32' + name = 'SSI32' + longname = 'Synchronous Serial Interface (32bit)' + desc = 'Synchronous Serial Interface (32bit) protocol.' + license = 'gplv2+' + inputs = ['spi'] + outputs = [] + tags = ['Embedded/industrial'] + options = ( + {'id': 'msgsize', 'desc': 'Message size', 'default': 64}, + ) + annotations = ( + ('ctrl-tx', 'CTRL TX'), + ('ack-tx', 'ACK TX'), + ('ctrl-rx', 'CTRL RX'), + ('ack-rx', 'ACK RX'), + ) + annotation_rows = ( + ('tx', 'TX', (0, 1)), + ('rx', 'RX', (2, 3)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.ss_cmd, self.es_cmd = 0, 0 + self.mosi_bytes = [] + self.miso_bytes = [] + self.es_array = [] + self.rx_size = 0 + self.tx_size = 0 + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def putx(self, data): + self.put(self.ss_cmd, self.es_cmd, self.out_ann, data) + + def reset_data(self): + self.mosi_bytes = [] + self.miso_bytes = [] + self.es_array = [] + + def handle_ack(self): + # Only first byte should have ACK data, other 3 bytes are reserved. + self.es_cmd = self.es_array[0] + self.putx([1, ['> ACK:0x%02x' % (self.mosi_bytes[0])]]) + self.putx([3, ['< ACK:0x%02x' % (self.miso_bytes[0])]]) + + def handle_ctrl(self): + mosi = miso = '' + self.tx_size = self.mosi_bytes[2] + self.rx_size = self.miso_bytes[2] + + if self.tx_size > 0: + mosi = ', DATA:0x' + ''.join(format(x, '02x') for x in self.mosi_bytes[4:self.tx_size + 4]) + if self.rx_size > 0: + miso = ', DATA:0x' + ''.join(format(x, '02x') for x in self.miso_bytes[4:self.rx_size + 4]) + + self.es_cmd = self.es_array[self.tx_size + 3] + self.putx([0, ['> CTRL:0x%02x, LUN:0x%02x, SIZE:0x%02x, CRC:0x%02x%s' + % (self.mosi_bytes[0], self.mosi_bytes[1], + self.mosi_bytes[2], self.mosi_bytes[3], mosi)]]) + + self.es_cmd = self.es_array[self.rx_size + 3] + self.putx([2, ['< CTRL:0x%02x, LUN:0x%02x, SIZE:0x%02x, CRC:0x%02x%s' + % (self.miso_bytes[0], self.miso_bytes[1], + self.miso_bytes[2], self.miso_bytes[3], miso)]]) + + def decode(self, ss, es, data): + ptype = data[0] + if ptype == 'CS-CHANGE': + self.reset_data() + return + + # Don't care about anything else. + if ptype != 'DATA': + return + mosi, miso = data[1:] + + self.ss, self.es = ss, es + + if len(self.mosi_bytes) == 0: + self.ss_cmd = ss + self.mosi_bytes.append(mosi) + self.miso_bytes.append(miso) + self.es_array.append(es) + + if self.mosi_bytes[0] & 0x80: + if len(self.mosi_bytes) < 4: + return + + self.handle_ack() + self.reset_data() + else: + if len(self.mosi_bytes) < self.options['msgsize']: + return + + self.handle_ctrl() + self.reset_data() diff --git a/libsigrokdecode4DSL/decoders/st25r39xx_spi/__init__.py b/libsigrokdecode4DSL/decoders/st25r39xx_spi/__init__.py new file mode 100644 index 00000000..3803789e --- /dev/null +++ b/libsigrokdecode4DSL/decoders/st25r39xx_spi/__init__.py @@ -0,0 +1,31 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2019-2020 Benjamin Vernoux +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This decoder stacks on top of the SPI PD and decodes the st25r39xx High performance NFC universal device and EMVCo reader protocol (SPI mode). + +It has been successfully tested with the st25r3916, other chips of this family may or may not be fully supported but not been verified. + +Please note that the SPI interface uses clock polarity 0 and clock phase 1, which is not the default setting. + +Details: +https://www.st.com/resource/en/datasheet/st25r3916.pdf +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/st25r39xx_spi/lists.py b/libsigrokdecode4DSL/decoders/st25r39xx_spi/lists.py new file mode 100644 index 00000000..14297654 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/st25r39xx_spi/lists.py @@ -0,0 +1,231 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2019-2020 Benjamin Vernoux +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## +## v0.1 - 17 September 2019 B.VERNOUX using ST25R3916 Datasheet DS12484 Rev 1 (January 2019) +## v0.2 - 28 April 2020 B.VERNOUX using ST25R3916 Datasheet DS12484 Rev 2 (December 2019) https://www.st.com/resource/en/datasheet/st25r3916.pdf +## v0.3 - 17 June 2020 B.VERNOUX using ST25R3916 Datasheet DS12484 Rev 3 (04 June 2020) https://www.st.com/resource/en/datasheet/st25r3916.pdf + +## ST25R3916 Datasheet DS12484 Rev 3 (04 June 2020) §4.4 Direct commands +dir_cmd = { +# addr: 'name' +# Set Default + 0xC0: 'SET_DEFAULT', + 0xC1: 'SET_DEFAULT', +# Stop All Activities + 0xC2: 'STOP', + 0xC3: 'STOP', +# Transmit With CRC + 0xC4: 'TXCRC', +# Transmit Without CRC + 0xC5: 'TXNOCRC', +# Transmit REQA + 0xC6: 'TXREQA', +# Transmit WUPA + 0xC7: 'TXWUPA', +# NFC Initial Field ON + 0xC8: 'NFCINITFON', +# NFC Response Field ON + 0xC9: 'NFCRESFON', +# Go to Sense (Idle) + 0xCD: 'GOIDLE', +# Go to Sleep (Halt) + 0xCE: 'GOHALT', +# Mask Receive Data / Stops receivers and RX decoders + 0xD0: 'STOPRX', +# Unmask Receive Data / Starts receivers and RX decoders + 0xD1: 'STARRX', +# Change AM Modulation state + 0xD2: 'SETAMSTATE', +# Measure Amplitude + 0xD3: 'MAMP', +# Reset RX Gain + 0xD5: 'RSTRXGAIN', +# Adjust Regulators + 0xD6: 'ADJREG', +# Calibrate Driver Timing + 0xD8: 'CALDRVTIM', +# Measure Phase + 0xD9: 'MPHASE', +# Clear RSSI + 0xDA: 'CLRRSSI', +# Clear FIFO + 0xDB: 'CLRFIFO', +# Enter Transparent Mode + 0xDC: 'TRMODE', +# Calibrate Capacitive Sensor + 0xDD: 'CALCAPA', +# Measure Capacitance + 0xDE: 'MCAPA', +# Measure Power Supply + 0xDF: 'MPOWER', +# Start General Purpose Timer + 0xE0: 'STARGPTIM', +# Start Wake-up Timer + 0xE1: 'STARWTIM', +# Start Mask-receive Timer + 0xE2: 'STARMSKTIM', +# Start No-response Timer + 0xE3: 'STARNRESPTIM', +# Start PPON2 Timer + 0xE4: 'STARPPON2TIM', +# Stop No-response Timer + 0xE8: 'STOPNRESTIM', +# RFU / Not Used + 0xFA: 'RFU', +# Register Space-B Access + 0xFB: 'REGSPACEB', +# Register Test access + 0xFC: 'TESTACCESS' +# Other codes => RFU / Not Used +} + +## ST25R3916 Datasheet DS12484 Rev 2 (December 2019) §4.5 Registers Table 17. List of registers - Space A +## ST25R3916 Datasheet DS12484 Rev 2 (December 2019) §4.3.3 Serial peripheral interface (SPI) Table 11. SPI operation modes +regsSpaceA = { +# addr: 'name' +# §4.5 Registers Table 17. List of registers - Space A +# IO configuration + 0x00: 'IOCFG1', + 0x01: 'IOCFG2', +# Operation control and mode definition + 0x02: 'OPCTRL', + 0x03: 'MODEDEF', + 0x04: 'BITRATE', +# Protocol configuration + 0x05: 'TYPEA', + 0x06: 'TYPEB', + 0x07: 'TYPEBF', + 0x08: 'NFCIP1', + 0x09: 'STREAM', + 0x0A: 'AUX', +# Receiver configuration + 0x0B: 'RXCFG1', + 0x0C: 'RXCFG2', + 0x0D: 'RXCFG3', + 0x0E: 'RXCFG4', +# Timer definition + 0x0F: 'MSKRXTIM', + 0x10: 'NRESPTIM1', + 0x11: 'NRESPTIM2', + 0x12: 'TIMEMV', + 0x13: 'GPTIM1', + 0x14: 'GPTIM2', + 0x15: 'PPON2', +# Interrupt and associated reporting + 0x16: 'MSKMAINIRQ', + 0x17: 'MSKTIMNFCIRQ', + 0x18: 'MSKERRWAKEIRQ', + 0x19: 'TARGIRQ', + 0x1A: 'MAINIRQ', + 0x1B: 'TIMNFCIRQ', + 0x1C: 'ERRWAKEIRQ', + 0x1D: 'TARGIRQ', + 0x1E: 'FIFOSTAT1', + 0x1F: 'FIFOSTAT2', + 0x20: 'COLLDISP', + 0x21: 'TARGDISP', +# Definition of number of transmitted bytes + 0x22: 'NBTXB1', + 0x23: 'NBTXB2', + 0x24: 'BITRATEDET', +# A/D converter output + 0x25: 'ADCONVOUT', +# Antenna calibration + 0x26: 'ANTTUNECTRL1', + 0x27: 'ANTTUNECTRL2', +# Antenna driver and modulation + 0x28: 'TXDRV', + 0x29: 'TARGMOD', +# External field detector threshold + 0x2A: 'EXTFIELDON', + 0x2B: 'EXTFIELDOFF', +# Regulator + 0x2C: 'REGVDDCTRL', +# Receiver state display + 0x2D: 'RSSIDISP', + 0x2E: 'GAINSTATE', +# Capacitive sensor + 0x2F: 'CAPACTRL', + 0x30: 'CAPADISP', +# Auxiliary display + 0x31: 'AUXDISP', +# Wake-up + 0x32: 'WAKETIMCTRL', + 0x33: 'AMPCFG', + 0x34: 'AMPREF', + 0x35: 'AMPAAVGDISP', + 0x36: 'AMPDISP', + 0x37: 'PHASECFG', + 0x38: 'PHASEREF', + 0x39: 'PHASEAAVGDISP', + 0x3A: 'PHASEDISP', + 0x3B: 'CAPACFG', + 0x3C: 'CAPAREF', + 0x3D: 'CAPAAAVGDISP', + 0x3E: 'CAPADISP', +# IC identity + 0x3F: 'ICIDENT', +## ST25R3916 Datasheet DS12484 Rev 2 (December 2019) §4.3.3 Serial peripheral interface (SPI) Table 11. SPI operation modes + 0xA0: 'PT_memLoadA', + 0xA8: 'PT_memLoadF', + 0xAC: 'PT_memLoadTSN', + 0xBF: 'PT_memRead' +} + +## ST25R3916 Datasheet DS12484 Rev 2 (December 2019) §4.5 Registers Table 18. List of registers - Space B +regsSpaceB = { +# addr: 'name' +# §4.5 Registers Table 18. List of registers - Space B +# Protocol configuration + 0x05: 'EMDSUPPRCONF', + 0x06: 'SUBCSTARTIM', +# Receiver configuration + 0x0B: 'P2PRXCONF', + 0x0C: 'CORRCONF1', + 0x0D: 'CORRCONF2', +# Timer definition + 0x0F: 'SQUELSHTIM', + 0x15: 'NFCGUARDTIM', +# Antenna driver and modulation + 0x28: 'AUXMODSET', + 0x29: 'TXDRVTIM', +# External field detector threshold + 0x2A: 'RESAMMODE', + 0x2B: 'TXDRVTIMDISP', +# Regulator + 0x2C: 'REGDISP', +# Protection + 0x30: 'OSHOOTCONF1', + 0x31: 'OSHOOTCONF2', + 0x32: 'USHOOTCONF1', + 0x33: 'USHOOTCONF2' +} + +## ST25R3916 Datasheet DS12484 Rev 2 (December 2019) §4.4.17 Test access +regsTest = { +# addr: 'name' +# §4.4.17 Test access (Typo in datasheet it is not register 0x00 but 0x01) + 0x01: 'ANTSTOBS' +} + +## Optional TODO add important status bit fields / ANN_STATUS +## Interrupt and associated reporting => Registers Space A from Address (hex) 0x16 to 0x21 +## §4.5.58 RSSI display register +## §4.5.59 Gain reduction state register +## ... + diff --git a/libsigrokdecode4DSL/decoders/st25r39xx_spi/pd.py b/libsigrokdecode4DSL/decoders/st25r39xx_spi/pd.py new file mode 100644 index 00000000..1b0df073 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/st25r39xx_spi/pd.py @@ -0,0 +1,350 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2019-2020 Benjamin Vernoux +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd +from collections import namedtuple +from common.srdhelper import SrdIntEnum +from .lists import * + +Ann = SrdIntEnum.from_str('Ann', 'BURST_READ BURST_WRITE \ + BURST_READB BURST_WRITEB BURST_READT BURST_WRITET \ + DIRECTCMD FIFO_WRITE FIFO_READ STATUS WARN') + +Pos = namedtuple('Pos', ['ss', 'es']) +Data = namedtuple('Data', ['mosi', 'miso']) + +class Decoder(srd.Decoder): + api_version = 3 + id = 'st25r39xx_spi' + name = 'ST25R39xx (SPI mode)' + longname = 'STMicroelectronics ST25R39xx' + desc = 'High performance NFC universal device and EMVCo reader protocol.' + license = 'gplv2+' + inputs = ['spi'] + outputs = [] + tags = ['IC', 'Wireless/RF'] + annotations = ( + ('Read', 'Burst register read'), + ('Write', 'Burst register write'), + ('ReadB', 'Burst register SpaceB read'), + ('WriteB', 'Burst register SpaceB write'), + ('ReadT', 'Burst register Test read'), + ('WriteT', 'Burst register Test write'), + ('Cmd', 'Direct command'), + ('FIFOW', 'FIFO write'), + ('FIFOR', 'FIFO read'), + ('status_reg', 'Status register'), + ('warning', 'Warning'), + ) + annotation_rows = ( + ('regs', 'Regs', (Ann.prefixes('BURST_'))), + ('cmds', 'Commands', (Ann.DIRECTCMD,)), + ('data', 'Data', (Ann.prefixes('FIFO_'))), + ('status', 'Status register', (Ann.STATUS,)), + ('warnings', 'Warnings', (Ann.WARN,)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.next() + self.requirements_met = True + self.cs_was_released = False + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def warn(self, pos, msg): + '''Put a warning message 'msg' at 'pos'.''' + self.put(pos.ss, pos.es, self.out_ann, [Ann.WARN, [msg]]) + + def putp(self, pos, ann, msg): + '''Put an annotation message 'msg' at 'pos'.''' + self.put(pos.ss, pos.es, self.out_ann, [ann, [msg]]) + + def putp2(self, pos, ann, msg1, msg2): + '''Put an annotation message 'msg' at 'pos'.''' + self.put(pos.ss, pos.es, self.out_ann, [ann, [msg1, msg2]]) + + def next(self): + '''Resets the decoder after a complete command was decoded.''' + # 'True' for the first byte after CS# went low. + self.first = True + + # The current command, and the minimum and maximum number + # of data bytes to follow. + self.cmd = None + self.min = 0 + self.max = 0 + + # Used to collect the bytes after the command byte + # (and the start/end sample number). + self.mb = [] + self.ss_mb = -1 + self.es_mb = -1 + + def mosi_bytes(self): + '''Returns the collected MOSI bytes of a multi byte command.''' + return [b.mosi for b in self.mb] + + def miso_bytes(self): + '''Returns the collected MISO bytes of a multi byte command.''' + return [b.miso for b in self.mb] + + def decode_command(self, pos, b): + '''Decodes the command byte 'b' at position 'pos' and prepares + the decoding of the following data bytes.''' + c = self.parse_command(b) + if c is None: + self.warn(pos, 'Unknown command') + return + + self.cmd, self.dat, self.min, self.max = c + + if self.cmd == 'Cmd': + self.putp(pos, Ann.DIRECTCMD, self.format_command()) + else: + # Don't output anything now, the command is merged with + # the data bytes following it. + self.ss_mb = pos.ss + + def format_command(self): + '''Returns the label for the current command.''' + if self.cmd in ('Write', 'Read', 'WriteB', 'ReadB', 'WriteT', 'ReadT', 'FIFO Write', 'FIFO Read'): + return self.cmd + if self.cmd == 'Cmd': + reg = dir_cmd.get(self.dat, 'Unknown direct command') + return '{} {}'.format(self.cmd, reg) + else: + return 'TODO Cmd {}'.format(self.cmd) + + def parse_command(self, b): + '''Parses the command byte. + Returns a tuple consisting of: + - the name of the command + - additional data needed to dissect the following bytes + - minimum number of following bytes + - maximum number of following bytes (None for infinite) + ''' + addr = b & 0x3F + # previous command was 'Space B' + if self.cmd == 'Space B': + if (b & 0xC0) == 0x00: + return ('WriteB', addr, 1, 99999) + if (b & 0xC0) == 0x40: + return ('ReadB', addr, 1, 99999) + else: + self.warn(pos, 'Unknown address/command combination') + # previous command was 'TestAccess' + elif self.cmd == 'TestAccess': + if (b & 0xC0) == 0x00: + return ('WriteT', addr, 1, 99999) + if (b & 0xC0) == 0x40: + return ('ReadT', addr, 1, 99999) + else: + self.warn(pos, 'Unknown address/command combination') + else: + # Space A regs or other operation modes (except Space B) + # Register Write 0b00xxxxxx 0x00 to 0x3F => 'Write' + # Register Read 0b01xxxxxx 0x40 to 0x7F => 'Read' + if (b <= 0x7F): + if (b & 0xC0) == 0x00: + return ('Write', addr, 1, 99999) + if (b & 0xC0) == 0x40: + return ('Read', addr, 1, 99999) + else: + self.warn(pos, 'Unknown address/command combination') + else: + # FIFO Load 0b10000000 0x80 => 'FIFO Write' + # PT_memory loadA-config 0b10100000 0xA0 => 'Write' + # PT_memory loadF-config 0b10101000 0xA8 => 'Write' + # PT_memory loadTSN data 0b10101100 0xAC => 'Write' + # PT_memory Read 0b10111111 0xBF => 'Read' + # FIFO Read 0b10011111 0x9F => 'FIFO Read' + # Direct Command 0b11xxx1xx 0xC0 to 0xE8 => 'Cmd' + # Register Space-B Access 0b11111011 0xFB => 'Space B' + # Register Test Access 0b11111100 0xFC => 'TestAccess' + if b == 0x80: + return ('FIFO Write', b, 1, 99999) + if b == 0xA0: + return ('Write', b, 1, 99999) + if b == 0xA8: + return ('Write', b, 1, 99999) + if b == 0xAC: + return ('Write', b, 1, 99999) + if b == 0xBF: + return ('Read', b, 1, 99999) + if b == 0x9F: + return ('FIFO Read', b, 1, 99999) + if (b >= 0x0C and b <= 0xE8) : + return ('Cmd', b, 0, 0) + if b == 0xFB: + return ('Space B', b, 0, 0) + if b == 0xFC: + return ('TestAccess', b, 0, 0) + else: + self.warn(pos, 'Unknown address/command combination') + + def decode_reg(self, pos, ann, regid, data): + '''Decodes a register. + pos -- start and end sample numbers of the register + ann -- the annotation number that is used to output the register. + regid -- may be either an integer used as a key for the 'regs' + dictionary, or a string directly containing a register name.' + data -- the register content. + ''' + if type(regid) == int: + if (ann == Ann.FIFO_READ) or (ann == Ann.FIFO_WRITE): + name = '' + elif (ann == Ann.BURST_READB) or (ann == Ann.BURST_WRITEB): + # Get the name of the register. + if regid not in regsSpaceB: + self.warn(pos, 'Unknown register SpaceB') + return + name = '{} ({:02X})'.format(regsSpaceB[regid], regid) + elif (ann == Ann.BURST_READT) or (ann == Ann.BURST_WRITET): + # Get the name of the register. + if regid not in regsTest: + self.warn(pos, 'Unknown register Test') + return + name = '{} ({:02X})'.format(regsTest[regid], regid) + else: + # Get the name of the register. + if regid not in regsSpaceA: + self.warn(pos, 'Unknown register SpaceA') + return + name = '{} ({:02X})'.format(regsSpaceA[regid], regid) + else: + name = regid + + if regid == 'STATUS' and ann == Ann.STATUS: + label = 'Status' + self.decode_status_reg(pos, ann, data, label) + else: + label = '{}: {}'.format(self.format_command(), name) + self.decode_mb_data(pos, ann, data, label) + + def decode_status_reg(self, pos, ann, data, label): + '''Decodes the data bytes 'data' of a status register at position + 'pos'. The decoded data is prefixed with 'label'.''' + + def decode_mb_data(self, pos, ann, data, label): + '''Decodes the data bytes 'data' of a multibyte command at position + 'pos'. The decoded data is prefixed with 'label'.''' + + def escape(b): + return '{:02X}'.format(b) + + data = ' '.join([escape(b) for b in data]) + if (ann == Ann.FIFO_WRITE) or (ann == Ann.FIFO_READ): + text = '{}{}'.format(label, data) + else: + text = '{} = {}'.format(label, data) + self.putp(pos, ann, text) + + def finish_command(self, pos): + '''Decodes the remaining data bytes at position 'pos'.''' + if self.cmd == 'Write': + self.decode_reg(pos, Ann.BURST_WRITE, self.dat, self.mosi_bytes()) + elif self.cmd == 'Read': + self.decode_reg(pos, Ann.BURST_READ, self.dat, self.miso_bytes()) + elif self.cmd == 'WriteB': + self.decode_reg(pos, Ann.BURST_WRITEB, self.dat, self.mosi_bytes()) + elif self.cmd == 'ReadB': + self.decode_reg(pos, Ann.BURST_READB, self.dat, self.miso_bytes()) + elif self.cmd == 'WriteT': + self.decode_reg(pos, Ann.BURST_WRITET, self.dat, self.mosi_bytes()) + elif self.cmd == 'ReadT': + self.decode_reg(pos, Ann.BURST_READT, self.dat, self.miso_bytes()) + elif self.cmd == 'FIFO Write': + self.decode_reg(pos, Ann.FIFO_WRITE, self.dat, self.mosi_bytes()) + elif self.cmd == 'FIFO Read': + self.decode_reg(pos, Ann.FIFO_READ, self.dat, self.miso_bytes()) + elif self.cmd == 'Cmd': + self.decode_reg(pos, Ann.DIRECTCMD, self.dat, self.mosi_bytes()) + else: + self.warn(pos, 'Unhandled command {}'.format(self.cmd)) + + def decode(self, ss, es, data): + if not self.requirements_met: + return + + ptype, data1, data2 = data + + if ptype == 'CS-CHANGE': + if data1 is None: + if data2 is None: + self.requirements_met = False + raise ChannelError('CS# pin required.') + elif data2 == 1: + self.cs_was_released = True + + if data1 == 0 and data2 == 1: + # Rising edge, the complete command is transmitted, process + # the bytes that were sent after the command byte. + if self.cmd: + # Check if we got the minimum number of data bytes + # after the command byte. + if len(self.mb) < self.min: + self.warn((ss, ss), 'Missing data bytes') + elif self.mb: + self.finish_command(Pos(self.ss_mb, self.es_mb)) + + self.next() + self.cs_was_released = True + + elif ptype == 'DATA' and self.cs_was_released: + mosi, miso = data1, data2 + pos = Pos(ss, es) + + if miso is None or mosi is None: + self.requirements_met = False + raise ChannelError('Both MISO and MOSI pins are required.') + + if self.first: + # Register Space-B Access 0b11111011 0xFB => 'Space B' + if mosi == 0xFB: + self.first = True + # First MOSI byte 'Space B' command. + self.decode_command(pos, mosi) + # First MISO byte is always the status register. + #self.decode_reg(pos, ANN_STATUS, 'STATUS', [miso]) + # Register TestAccess Access 0b11111100 0xFC => 'TestAccess' + elif mosi == 0xFC: + self.first = True + # First MOSI byte 'TestAccess' command. + self.decode_command(pos, mosi) + # First MISO byte is always the status register. + #self.decode_reg(pos, ANN_STATUS, 'STATUS', [miso]) + else: + self.first = False + # First MOSI byte is always the command. + self.decode_command(pos, mosi) + # First MISO byte is always the status register. + #self.decode_reg(pos, ANN_STATUS, 'STATUS', [miso]) + else: + if not self.cmd or len(self.mb) >= self.max: + self.warn(pos, 'Excess byte') + else: + # Collect the bytes after the command byte. + if self.ss_mb == -1: + self.ss_mb = ss + self.es_mb = es + self.mb.append(Data(mosi, miso)) diff --git a/libsigrokdecode4DSL/decoders/st7735/__init__.py b/libsigrokdecode4DSL/decoders/st7735/__init__.py new file mode 100644 index 00000000..771578c6 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/st7735/__init__.py @@ -0,0 +1,27 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2018 Aleksander Alekseev +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This decoder decodes the Sitronix ST7735 TFT controller protocol. + +Details: +http://www.displayfuture.com/Display/datasheet/controller/ST7735.pdf +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/st7735/pd.py b/libsigrokdecode4DSL/decoders/st7735/pd.py new file mode 100644 index 00000000..252b1887 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/st7735/pd.py @@ -0,0 +1,173 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2018 Aleksander Alekseev +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . + +import sigrokdecode as srd + +MAX_DATA_LEN = 128 + +# Command ID -> name, short description +META = { + 0x00: {'name': 'NOP ', 'desc': 'No operation'}, + 0x01: {'name': 'SWRESET', 'desc': 'Software reset'}, + 0x04: {'name': 'RDDID ', 'desc': 'Read display ID'}, + 0x09: {'name': 'RDDST ', 'desc': 'Read display status'}, + 0x10: {'name': 'SLPIN ', 'desc': 'Sleep in & booster off'}, + 0x11: {'name': 'SLPOUT ', 'desc': 'Sleep out & booster on'}, + 0x12: {'name': 'PTLON ', 'desc': 'Partial mode on'}, + 0x13: {'name': 'NORON ', 'desc': 'Partial off (normal)'}, + 0x20: {'name': 'INVOFF ', 'desc': 'Display inversion off'}, + 0x21: {'name': 'INVON ', 'desc': 'Display inversion on'}, + 0x28: {'name': 'DISPOFF', 'desc': 'Display off'}, + 0x29: {'name': 'DISPON ', 'desc': 'Display on'}, + 0x2A: {'name': 'CASET ', 'desc': 'Column address set'}, + 0x2B: {'name': 'RASET ', 'desc': 'Row address set'}, + 0x2C: {'name': 'RAMWR ', 'desc': 'Memory write'}, + 0x2E: {'name': 'RAMRD ', 'desc': 'Memory read'}, + 0x30: {'name': 'PTLAR ', 'desc': 'Partial start/end address set'}, + 0x36: {'name': 'MADCTL ', 'desc': 'Memory data address control'}, + 0x3A: {'name': 'COLMOD ', 'desc': 'Interface pixel format'}, + 0xB1: {'name': 'FRMCTR1', 'desc': 'Frame rate control (in normal mode / full colors)'}, + 0xB2: {'name': 'FRMCTR2', 'desc': 'Frame rate control (in idle mode / 8-colors)'}, + 0xB3: {'name': 'FRMCTR3', 'desc': 'Frame rate control (in partial mode / full colors) '}, + 0xB4: {'name': 'INVCTR ', 'desc': 'Display inversion control'}, + 0xB6: {'name': 'DISSET5', 'desc': 'Display function set 5'}, + 0xC0: {'name': 'PWCTR1 ', 'desc': 'Power control 1'}, + 0xC1: {'name': 'PWCTR2 ', 'desc': 'Power control 2'}, + 0xC2: {'name': 'PWCTR3 ', 'desc': 'Power control 3'}, + 0xC3: {'name': 'PWCTR4 ', 'desc': 'Power control 4'}, + 0xC4: {'name': 'PWCTR5 ', 'desc': 'Power control 5'}, + 0xC5: {'name': 'VMCTR1 ', 'desc': 'VCOM control 1'}, + 0xDA: {'name': 'RDID1 ', 'desc': 'Read ID1'}, + 0xDB: {'name': 'RDID2 ', 'desc': 'Read ID2'}, + 0xDC: {'name': 'RDID3 ', 'desc': 'Read ID3'}, + 0xDD: {'name': 'RDID4 ', 'desc': 'Read ID4'}, + 0xFC: {'name': 'PWCTR6 ', 'desc': 'Power control 6'}, + 0xE0: {'name': 'GMCTRP1', 'desc': 'Gamma \'+\'polarity correction characteristics setting'}, + 0xE1: {'name': 'GMCTRN1', 'desc': 'Gamma \'-\'polarity correction characteristics setting'}, +} + +class Ann: + BITS, CMD, DATA, DESC = range(4) + +class Decoder(srd.Decoder): + api_version = 3 + id = 'st7735' + name = 'ST7735' + longname = 'Sitronix ST7735' + desc = 'Sitronix ST7735 TFT controller protocol.' + license = 'gplv2+' + inputs = ['logic'] + outputs = [] + tags = ['Display', 'IC'] + channels = ( + {'id': 'cs', 'name': 'CS#', 'desc': 'Chip-select'}, + {'id': 'clk', 'name': 'CLK', 'desc': 'Clock'}, + {'id': 'mosi', 'name': 'MOSI', 'desc': 'Master out, slave in'}, + {'id': 'dc', 'name': 'DC', 'desc': 'Data or command'} + ) + annotations = ( + ('bit', 'Bit'), + ('command', 'Command'), + ('data', 'Data'), + ('description', 'Description'), + ) + annotation_rows = ( + ('bits', 'Bits', (Ann.BITS,)), + ('fields', 'Fields', (Ann.CMD, Ann.DATA)), + ('description', 'Description', (Ann.DESC,)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.accum_byte = 0 + self.accum_bits_num = 0 + self.bit_ss = -1 + self.byte_ss = -1 + self.current_bit = -1 + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def put_desc(self, ss, es, cmd, data): + if cmd == -1: + return + if META[cmd]: + self.put(ss, es, self.out_ann, [Ann.DESC, + ['%s: %s' % (META[cmd]['name'].strip(), META[cmd]['desc'])]]) + else: + # Default description: + dots = '' + if len(data) == MAX_DATA_LEN: + data = data[:-1] + dots = '...' + data_str = '(none)' + if len(data) > 0: + data_str = ' '.join(['%02X' % b for b in data]) + self.put(ss, es, self.out_ann, [Ann.DESC, + ['Unknown command: %02X. Data: %s%s' % (cmd, data_str, dots)]]) + + def decode(self): + current_cmd = -1 + current_data = [] + desc_ss = -1 + desc_es = -1 + self.reset() + while True: + # Check data on both CLK edges. + (cs, clk, mosi, dc) = self.wait({1: 'e'}) + + if cs == 1: # Wait for CS = low, ignore the rest. + self.reset() + continue + + if clk == 1: + # Read one bit. + self.bit_ss = self.samplenum + if self.accum_bits_num == 0: + self.byte_ss = self.samplenum + self.current_bit = mosi + + if (clk == 0) and (self.current_bit >= 0): + # Process one bit. + self.put(self.bit_ss, self.samplenum, self.out_ann, + [Ann.BITS, [str(self.current_bit)]]) + self.accum_byte = (self.accum_byte << 1) | self.current_bit # MSB-first. + self.accum_bits_num += 1 + if self.accum_bits_num == 8: + # Process one byte. + ann = Ann.DATA if dc else Ann.CMD # DC = low for commands. + self.put(self.byte_ss, self.samplenum, self.out_ann, + [ann, ['%02X' % self.accum_byte]]) + if ann == Ann.CMD: + self.put_desc(desc_ss, desc_es, current_cmd, current_data) + desc_ss = self.byte_ss + desc_es = self.samplenum # For cmds without data. + current_cmd = self.accum_byte + current_data = [] + else: + if len(current_data) < MAX_DATA_LEN: + current_data += [self.accum_byte] + desc_es = self.samplenum + + self.accum_bits_num = 0 + self.accum_byte = 0 + self.byte_ss = -1 + self.current_bit = -1 + self.bit_ss = -1 diff --git a/libsigrokdecode4DSL/decoders/st7789/__init__.py b/libsigrokdecode4DSL/decoders/st7789/__init__.py new file mode 100644 index 00000000..5a57c5be --- /dev/null +++ b/libsigrokdecode4DSL/decoders/st7789/__init__.py @@ -0,0 +1,7 @@ +""" +This decoder decodes the Sitronix ST7789 TFT controller protocol. + +Details: https://www.newhavendisplay.com/appnotes/datasheets/LCDs/ST7789V.pdf +""" + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/st7789/pd.py b/libsigrokdecode4DSL/decoders/st7789/pd.py new file mode 100644 index 00000000..b8141ac7 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/st7789/pd.py @@ -0,0 +1,296 @@ +import sigrokdecode as srd + +COMMAND_MAP = { + 0x00: {"name": "NOP", "desc": "Empty command",}, + 0x01: {"name": "SWRESET", "desc": "Software Reset",}, + 0x04: {"name": "RDDID", "desc": "Read Display ID",}, + 0x09: {"name": "RDDST", "desc": "Read Display Status",}, + 0x0A: {"name": "RDDPM", "desc": "Read Display Power Mode",}, + 0x0B: {"name": "RDDMADCTL", "desc": "Read Display MADCTL",}, + 0x0C: {"name": "RDDCOLMOD", "desc": "Read Display Pixel Format",}, + 0x0D: {"name": "RDDIM", "desc": "Read Display Image Mode",}, + 0x0E: {"name": "RDDSM", "desc": "Read Display Signal Mode",}, + 0x0F: {"name": "RDDSDR", "desc": "Read Display Self-Diagnostic Result",}, + 0x10: {"name": "SLPIN", "desc": "Sleep in",}, + 0x11: {"name": "SLPOUT", "desc": "Sleep Out",}, + 0x12: {"name": "PTLON", "desc": "Partial Display Mode On",}, + 0x13: {"name": "NORON", "desc": "Normal Display Mode On",}, + 0x20: {"name": "INVOFF", "desc": "Display Inversion Off",}, + 0x21: {"name": "INVON", "desc": "Display Inversion On",}, + 0x26: {"name": "GAMSET", "desc": "Gamma Set",}, + 0x28: {"name": "DISPOFF", "desc": "Display Off",}, + 0x29: {"name": "DISPON", "desc": "Display On",}, + 0x2A: {"name": "CASET", "desc": "Column Address Set",}, + 0x2B: {"name": "RASET", "desc": "Row Address Set",}, + 0x2C: {"name": "RAMWR", "desc": "Memory Write",}, + 0x2E: {"name": "RAMRD", "desc": "Memory Read",}, + 0x30: {"name": "PTLAR", "desc": "Partial Area",}, + 0x33: {"name": "VSCRDEF", "desc": "Vertical Scrolling Definition",}, + 0x34: {"name": "TEOFF", "desc": "Tearing Effect Line OFF",}, + 0x35: {"name": "TEON", "desc": "Tearing Effect Line On",}, + 0x36: {"name": "MADCTL", "desc": "Memory Data Access Control",}, + 0x37: {"name": "VSCSAD", "desc": "Vertical Scroll Start Address of RAM",}, + 0x38: {"name": "IDMOFF", "desc": "Idle Mode Off",}, + 0x39: {"name": "IDMON", "desc": "Idle mode on",}, + 0x3A: {"name": "COLMOD", "desc": "Interface Pixel Format",}, + 0x3C: {"name": "WRMEMC", "desc": "Write Memory Continue",}, + 0x3E: {"name": "RDMEMC", "desc": "Read Memory Continue",}, + 0x44: {"name": "STE", "desc": "Set Tear Scanline",}, + 0x45: {"name": "GSCAN", "desc": "Get Scanline",}, + 0x51: {"name": "WRDISBV", "desc": "Write Display Brightness",}, + 0x52: {"name": "RDDISBV", "desc": "Read Display Brightness Value",}, + 0x53: {"name": "WRCTRLD", "desc": "Write CTRL Display",}, + 0x54: {"name": "RDCTRLD", "desc": "Read CTRL Value Display",}, + 0x55: { + "name": "WRCACE", + "desc": "Write Content Adaptive Brightness Control and Color Enhancement", + }, + 0x56: {"name": "RDCABC", "desc": "Read Content Adaptive Brightness Control",}, + 0x5E: {"name": "WRCABCMB", "desc": "Write CABC Minimum Brightness",}, + 0x5F: {"name": "RDCABCMB", "desc": "Read CABC Minimum Brightness",}, + 0x68: { + "name": "RDABCSDR", + "desc": "Read Automatic Brightness Control Self-Diagnostic Result", + }, + 0xDA: {"name": "RDID1", "desc": "Read ID1",}, + 0xDB: {"name": "RDID2", "desc": "Read ID2",}, + 0xDC: {"name": "RDID3", "desc": "Read ID3",}, + 0xB0: {"name": "RAMCTRL", "desc": "RAM Control",}, + 0xB1: {"name": "RGBCTRL", "desc": "RGB Interface Control",}, + 0xB2: {"name": "PORCTRL", "desc": "Porch Setting",}, + 0xB3: { + "name": "FRCTRL1", + "desc": "Frame Rate Control 1 (In partial mode/ idle colors)", + }, + 0xB5: {"name": "PARCTRL", "desc": "Partial mode Control",}, + 0xB7: {"name": "GCTRL", "desc": "Gate Control",}, + 0xB8: {"name": "GTADJ", "desc": "Gate On Timing Adjustment",}, + 0xBA: {"name": "DGMEN", "desc": "Digital Gamma Enable",}, + 0xBB: {"name": "VCOMS", "desc": "VCOMS Setting",}, + 0xC0: {"name": "LCMCTRL", "desc": "LCM Control",}, + 0xC1: {"name": "IDSET", "desc": "ID Code Setting",}, + 0xC2: {"name": "VDVVRHEN", "desc": "VDV and VRH Command Enable",}, + 0xC3: {"name": "VRHS", "desc": "VRH Set",}, + 0xC4: {"name": "VDVS", "desc": "VDV Set",}, + 0xC5: {"name": "VCMOFSET", "desc": "VCOMS Offset Set",}, + 0xC6: {"name": "FRCTRL2", "desc": "Frame Rate Control in Normal Mode",}, + 0xC7: {"name": "CABCCTRL", "desc": "CABC Control",}, + 0xC8: {"name": "REGSEL1", "desc": "Register Value Selection 1",}, + 0xCA: {"name": "REGSEL2", "desc": "Register Value Selection 2",}, + 0xCC: {"name": "PWMFRSEL", "desc": "PWM Frequency Selection",}, + 0xD0: {"name": "PWCTRL1", "desc": "Power Control 1",}, + 0xD2: {"name": "VAPVANEN", "desc": "Enable VAP/VAN signal output",}, + 0xDF: {"name": "CMD2EN", "desc": "Command 2 Enable",}, + 0xE0: {"name": "PVGAMCTRL", "desc": "Positive Voltage Gamma Control",}, + 0xE1: {"name": "NVGAMCTRL", "desc": "Negative Voltage Gamma Control",}, + 0xE2: {"name": "DGMLUTR", "desc": "Digital Gamma Look-up Table for Red",}, + 0xE3: {"name": "DGMLUTB", "desc": "Digital Gamma Look-up Table for Blue",}, + 0xE4: {"name": "GATECTRL", "desc": "Gate Control",}, + 0xE7: {"name": "SPI2EN", "desc": "SPI2 Enable",}, + 0xE8: {"name": "PWCTRL2", "desc": "Power Control 2",}, + 0xE9: {"name": "EQCTRL", "desc": "Equalize time control",}, + 0xEC: {"name": "PROMCTRL", "desc": "Program Mode Control",}, + 0xFA: {"name": "PROMEN", "desc": "Program Mode Enable",}, + 0xFC: {"name": "NVMSET", "desc": "NVM Setting",}, + 0xFE: {"name": "PROMACT", "desc": "Program action",}, +} + + +def _get_annotation_index(annotations, name): + for index, annotation in enumerate(annotations): + if annotation[0] == name: + return index + raise RuntimeError(f"Unknown annotation {repr(name)}: {repr(annotations)}") + + +class Decoder(srd.Decoder): + api_version = 3 + id = "st7789" + name = "ST7789" + longname = "Sitronix ST7789" + desc = "Sitronix ST7789 TFT controller protocol." + license = "gplv2+" + inputs = ["logic"] + outputs = [] + channels = ( + {"id": "csx", "name": "CSX", "desc": "Chip selection signal"}, + {"id": "dcx", "name": "DCX", "desc": "Clock signal"}, + {"id": "sdo", "name": "SDO", "desc": "Serial output data"}, + {"id": "wrx", "name": "WRX", "desc": "Command / data"}, + ) + optional_channels = tuple() + tags = ["Display", "SPI"] + annotations = ( + ("bit", "Bit"), + ("command", "Command"), + ("data", "Data"), + ("cmd_data", "Command + Data"), + ("asserted", "Assertion"), + ) + + annotation_rows = ( + ("bits", "Bits", (_get_annotation_index(annotations, "bit"),)), + ( + "bytes", + "Bytes", + ( + _get_annotation_index(annotations, "command"), + _get_annotation_index(annotations, "data"), + ), + ), + ( + "cmd_data", + "Command + Data", + (_get_annotation_index(annotations, "cmd_data"),), + ), + ("asserted", "Assertion", (_get_annotation_index(annotations, "asserted"),)), + ) + + def _get_channel_index(self, name): + for index, channel in enumerate(self.channels): + if channel["name"] == name: + return index + raise RuntimeError("Implementation bug.") + + def __init__(self): + self.reset() + + def reset(self): + pass + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def _get_cmd_str(self, cmd): + if cmd in COMMAND_MAP: + return COMMAND_MAP[cmd]["name"] + "(%02X)" % cmd + else: + return "Unknown(%02X)" % cmd + + def _get_cmd_data_str(self, cmd, data_list): + cmd_str = self._get_cmd_str(cmd) + + if data_list: + ret_str = f"{cmd_str}: " + for v in data_list: + ret_str += " %02X" % v + return ret_str + else: + return cmd_str + + def decode(self): + last_cmd = None + last_cmd_data_sample_startnum = None + last_cmd_data_sample_endnum = None + last_cmd_data_list = [] + + while True: + self.wait({self._get_channel_index("CSX"): "f"}) + csx_start_samplenum = self.samplenum + + bit = None + bit_count = 0 + byte = 0 + byte_sample_startnum = None + + while True: + # FIXME {self._get_channel_index("DCX"): "r"} + (csx, dcx, sdo, wrx) = self.wait( + [ + {self._get_channel_index("CSX"): "r"}, + {self._get_channel_index("DCX"): "e"}, + ] + ) + if csx == 1: + self.put( + csx_start_samplenum, + self.samplenum, + self.out_ann, + [ + _get_annotation_index(self.annotations, "asserted"), + ["Asserted"], + ], + ) + + if last_cmd is not None: + self.put( + last_cmd_data_sample_startnum, + last_cmd_data_sample_endnum, + self.out_ann, + [ + _get_annotation_index(self.annotations, "cmd_data"), + [self._get_cmd_data_str(last_cmd, last_cmd_data_list)], + ], + ) + last_cmd = None + last_cmd_data_sample_startnum = None + last_cmd_data_sample_endnum = None + last_cmd_data_list = [] + + break + + if dcx == 1 and bit is None: + bit = sdo + bit_start_samplenum = self.samplenum + bit_count += 1 + byte = (byte << 1) | bit + if byte_sample_startnum is None: + byte_sample_startnum = self.samplenum + + if dcx == 0 and bit is not None: + self.put( + bit_start_samplenum, + self.samplenum, + self.out_ann, + [_get_annotation_index(self.annotations, "bit"), [str(bit)]], + ) + bit = None + if bit_count == 8: + if wrx: + last_cmd_data_sample_endnum = self.samplenum + last_cmd_data_list.append(byte) + self.put( + byte_sample_startnum, + self.samplenum, + self.out_ann, + [ + _get_annotation_index(self.annotations, "data"), + ["Data(%02X)" % byte], + ], + ) + else: + self.put( + byte_sample_startnum, + self.samplenum, + self.out_ann, + [ + _get_annotation_index(self.annotations, "command"), + [self._get_cmd_str(byte)], + ], + ) + + if last_cmd is not None: + self.put( + last_cmd_data_sample_startnum, + last_cmd_data_sample_endnum, + self.out_ann, + [ + _get_annotation_index( + self.annotations, "cmd_data" + ), + [ + self._get_cmd_data_str( + last_cmd, last_cmd_data_list + ) + ], + ], + ) + last_cmd_data_list = [] + + last_cmd = byte + last_cmd_data_sample_startnum = byte_sample_startnum + last_cmd_data_sample_endnum = self.samplenum + + byte = 0 + bit_count = 0 + byte_sample_startnum = None diff --git a/libsigrokdecode4DSL/decoders/stepper_motor/__init__.py b/libsigrokdecode4DSL/decoders/stepper_motor/__init__.py new file mode 100644 index 00000000..9126104f --- /dev/null +++ b/libsigrokdecode4DSL/decoders/stepper_motor/__init__.py @@ -0,0 +1,25 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2015 Petteri Aimonen +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This PD decodes the stepper motor controller signals (step / dir) and +shows the step speed and absolute position of the stepper motor. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/stepper_motor/pd.py b/libsigrokdecode4DSL/decoders/stepper_motor/pd.py new file mode 100644 index 00000000..2a7009a0 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/stepper_motor/pd.py @@ -0,0 +1,95 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2015 Petteri Aimonen +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd + +class Decoder(srd.Decoder): + api_version = 3 + id = 'stepper_motor' + name = 'Stepper motor' + longname = 'Stepper motor position / speed' + desc = 'Absolute position and movement speed from step/dir.' + license = 'gplv2+' + inputs = ['logic'] + outputs = [] + tags = ['Embedded/industrial'] + channels = ( + {'id': 'step', 'name': 'Step', 'desc': 'Step pulse'}, + {'id': 'dir', 'name': 'Direction', 'desc': 'Direction select'}, + ) + options = ( + {'id': 'unit', 'desc': 'Unit', 'default': 'steps', + 'values': ('steps', 'mm')}, + {'id': 'steps_per_mm', 'desc': 'Steps per mm', 'default': 100.0}, + ) + annotations = ( + ('speed', 'Speed'), + ('position', 'Position') + ) + annotation_rows = ( + ('speed', 'Speed', (0,)), + ('position', 'Position', (1,)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.samplerate = None + self.oldstep = None + self.ss_prev_step = None + self.pos = 0 + self.prev_speed = None + self.prev_pos = None + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + if self.options['unit'] == 'steps': + self.scale = 1 + self.format = '%0.0f' + self.unit = 'steps' + else: + self.scale = self.options['steps_per_mm'] + self.format = '%0.2f' + self.unit = 'mm' + + def step(self, ss, direction): + if self.ss_prev_step is not None: + if self.samplerate: + delta = ss - self.ss_prev_step + speed = self.samplerate / delta / self.scale + speed_txt = self.format % speed + self.put(self.ss_prev_step, ss, self.out_ann, + [0, [speed_txt + ' ' + self.unit + '/s', speed_txt]]) + pos_txt = self.format % (self.pos / self.scale) + self.put(self.ss_prev_step, ss, self.out_ann, + [1, [pos_txt + ' ' + self.unit, pos_txt]]) + + self.pos += (1 if direction else -1) + self.ss_prev_step = ss + + def metadata(self, key, value): + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + + def decode(self): + while True: + (step, direction) = self.wait({0: 'r'}) + self.step(self.samplenum, direction) diff --git a/libsigrokdecode4DSL/decoders/swd/__init__.py b/libsigrokdecode4DSL/decoders/swd/__init__.py new file mode 100644 index 00000000..a141239b --- /dev/null +++ b/libsigrokdecode4DSL/decoders/swd/__init__.py @@ -0,0 +1,34 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 Angus Gratton +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This PD decodes the ARM SWD (version 1) protocol, as described in the +"ARM Debug Interface v5.2" Architecture Specification. + +Not supported: + * Turnaround periods other than the default 1, as set in DLCR.TURNROUND + (should be trivial to add) + * SWD protocol version 2 (multi-drop support, etc.) + +Details: +http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ihi0031c/index.html +(Registration required) +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/swd/pd.py b/libsigrokdecode4DSL/decoders/swd/pd.py new file mode 100644 index 00000000..3f81e03d --- /dev/null +++ b/libsigrokdecode4DSL/decoders/swd/pd.py @@ -0,0 +1,350 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 Angus Gratton +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd +import re + +''' +OUTPUT_PYTHON format: + +Packet: +[, ] + +: + - 'AP_READ' (AP read) + - 'DP_READ' (DP read) + - 'AP_WRITE' (AP write) + - 'DP_WRITE' (DP write) + - 'LINE_RESET' (line reset sequence) + +: + - tuple of address, ack state, data for the given sequence +''' + +swd_states = [ + 'IDLE', # Idle/unknown + 'REQUEST', # Request phase (first 8 bits) + 'ACK', # Ack phase (next 3 bits) + 'READ', # Reading phase (next 32 bits for reads) + 'WRITE', # Writing phase (next 32 bits for write) + 'DPARITY', # Data parity phase +] + +# Regexes for matching SWD data out of bitstring ('1' / '0' characters) format +RE_SWDSWITCH = re.compile(bin(0xE79E)[:1:-1] + '$') +RE_SWDREQ = re.compile(r'1(?P.)(?P.)(?P..)(?P.)01$') +RE_IDLE = re.compile('0' * 50 + '$') + +# Sample edges +RISING = 1 +FALLING = 0 + +ADDR_DP_SELECT = 0x8 +ADDR_DP_CTRLSTAT = 0x4 + +BIT_SELECT_CTRLSEL = 1 +BIT_CTRLSTAT_ORUNDETECT = 1 + +ANNOTATIONS = ['reset', 'enable', 'read', 'write', 'ack', 'data', 'parity'] + +class Decoder(srd.Decoder): + api_version = 3 + id = 'swd' + name = 'SWD' + longname = 'Serial Wire Debug' + desc = 'Two-wire protocol for debug access to ARM CPUs.' + license = 'gplv2+' + inputs = ['logic'] + outputs = ['swd'] + tags = ['Debug/trace'] + channels = ( + {'id': 'swclk', 'name': 'SWCLK', 'desc': 'Master clock'}, + {'id': 'swdio', 'name': 'SWDIO', 'desc': 'Data input/output'}, + ) + options = ( + {'id': 'strict_start', + 'desc': 'Wait for a line reset before starting to decode', + 'default': 'no', 'values': ('yes', 'no')}, + ) + annotations = ( + ('reset', 'RESET'), + ('enable', 'ENABLE'), + ('read', 'READ'), + ('write', 'WRITE'), + ('ack', 'ACK'), + ('data', 'DATA'), + ('parity', 'PARITY'), + ) + + def __init__(self): + self.reset() + + def reset(self): + # SWD data/clock state + self.state = 'UNKNOWN' + self.sample_edge = RISING + self.ack = None # Ack state of the current phase + self.ss_req = 0 # Start sample of current req + self.turnaround = 0 # Number of turnaround edges to ignore before continuing + self.bits = '' # Bits from SWDIO are accumulated here, matched against expected sequences + self.samplenums = [] # Sample numbers that correspond to the samples in self.bits + self.linereset_count = 0 + + # SWD debug port state + self.data = None + self.addr = None + self.rw = None # Are we inside an SWD read or a write? + self.ctrlsel = 0 # 'ctrlsel' is bit 0 in the SELECT register. + self.orundetect = 0 # 'orundetect' is bit 0 in the CTRLSTAT register. + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + self.out_python = self.register(srd.OUTPUT_PYTHON) + if self.options['strict_start'] == 'no': + self.state = 'REQ' # No need to wait for a LINE RESET. + + def putx(self, ann, length, data): + '''Output annotated data.''' + ann = ANNOTATIONS.index(ann) + try: + ss = self.samplenums[-length] + except IndexError: + ss = self.samplenums[0] + if self.state == 'REQ': + self.ss_req = ss + es = self.samplenum + self.put(ss, es, self.out_ann, [ann, [data]]) + + def putp(self, ptype, pdata): + self.put(self.ss_req, self.samplenum, self.out_python, [ptype, pdata]) + + def put_python_data(self): + '''Emit Python data item based on current SWD packet contents.''' + ptype = { + ('AP', 'R'): 'AP_READ', + ('AP', 'W'): 'AP_WRITE', + ('DP', 'R'): 'DP_READ', + ('DP', 'W'): 'DP_WRITE', + }[(self.apdp, self.rw)] + self.putp(ptype, (self.addr, self.data, self.ack)) + + def decode(self): + while True: + # Wait for any clock edge. + (clk, dio) = self.wait({0: 'e'}) + + # Count rising edges with DIO held high, + # as a line reset (50+ high edges) can happen from any state. + if clk == RISING: + if dio == 1: + self.linereset_count += 1 + else: + if self.linereset_count >= 50: + self.putx('reset', self.linereset_count, 'LINERESET') + self.putp('LINE_RESET', None) + self.reset_state() + self.linereset_count = 0 + + # Otherwise, we only care about either rising or falling edges + # (depending on sample_edge, set according to current state). + if clk != self.sample_edge: + continue + + # Turnaround bits get skipped. + if self.turnaround > 0: + self.turnaround -= 1 + continue + + self.bits += str(dio) + self.samplenums.append(self.samplenum) + { + 'UNKNOWN': self.handle_unknown_edge, + 'REQ': self.handle_req_edge, + 'ACK': self.handle_ack_edge, + 'DATA': self.handle_data_edge, + 'DPARITY': self.handle_dparity_edge, + }[self.state]() + + def next_state(self): + '''Step to the next SWD state, reset internal counters accordingly.''' + self.bits = '' + self.samplenums = [] + self.linereset_count = 0 + if self.state == 'UNKNOWN': + self.state = 'REQ' + self.sample_edge = RISING + self.turnaround = 0 + elif self.state == 'REQ': + self.state = 'ACK' + self.sample_edge = FALLING + self.turnaround = 1 + elif self.state == 'ACK': + self.state = 'DATA' + self.sample_edge = RISING if self.rw == 'W' else FALLING + self.turnaround = 0 if self.rw == 'R' else 2 + elif self.state == 'DATA': + self.state = 'DPARITY' + elif self.state == 'DPARITY': + #self.put_python_data() + self.state = 'REQ' + self.sample_edge = RISING + self.turnaround = 1 if self.rw == 'R' else 0 + + def reset_state(self): + '''Line reset (or equivalent), wait for a new pending SWD request.''' + #if self.state != 'REQ': # Emit a Python data item. + # self.put_python_data() + # Clear state. + self.bits = '' + self.samplenums = [] + self.linereset_count = 0 + self.turnaround = 0 + self.sample_edge = RISING + self.data = '' + self.ack = None + self.state = 'REQ' + + def handle_unknown_edge(self): + ''' + Clock edge in the UNKNOWN state. + In the unknown state, clock edges get ignored until we see a line + reset (which is detected in the decode method, not here.) + ''' + pass + + def handle_req_edge(self): + '''Clock edge in the REQ state (waiting for SWD r/w request).''' + # Check for a JTAG->SWD enable sequence. + m = re.search(RE_SWDSWITCH, self.bits) + if m is not None: + self.putx('enable', 16, 'JTAG->SWD') + self.reset_state() + return + + # Or a valid SWD Request packet. + m = re.search(RE_SWDREQ, self.bits) + if m is not None: + calc_parity = sum([int(x) for x in m.group('rw') + m.group('apdp') + m.group('addr')]) % 2 + parity = '' if str(calc_parity) == m.group('parity') else 'E' + self.rw = 'R' if m.group('rw') == '1' else 'W' + self.apdp = 'AP' if m.group('apdp') == '1' else 'DP' + self.addr = int(m.group('addr')[::-1], 2) << 2 + self.putx('read' if self.rw == 'R' else 'write', 8, self.get_address_description()) + self.next_state() + return + + def handle_ack_edge(self): + '''Clock edge in the ACK state (waiting for complete ACK sequence).''' + if len(self.bits) < 3: + return + if self.bits == '100': + self.putx('ack', 3, 'OK') + self.ack = 'OK' + self.next_state() + elif self.bits == '001': + self.putx('ack', 3, 'FAULT') + self.ack = 'FAULT' + if self.orundetect == 1: + self.next_state() + else: + self.reset_state() + self.turnaround = 1 + elif self.bits == '010': + self.putx('ack', 3, 'WAIT') + self.ack = 'WAIT' + if self.orundetect == 1: + self.next_state() + else: + self.reset_state() + self.turnaround = 1 + elif self.bits == '111': + self.putx('ack', 3, 'NOREPLY') + self.ack = 'NOREPLY' + self.reset_state() + else: + self.putx('ack', 3, 'ERROR') + self.ack = 'ERROR' + self.reset_state() + + def handle_data_edge(self): + '''Clock edge in the DATA state (waiting for 32 bits to clock past).''' + if len(self.bits) < 32: + return + self.data = 0 + self.dparity = 0 + for x in range(32): + if self.bits[x] == '1': + self.data += (1 << x) + self.dparity += 1 + self.dparity = self.dparity % 2 + + self.putx('data', 32, '0x%08x' % self.data) + self.next_state() + + def handle_dparity_edge(self): + '''Clock edge in the DPARITY state (clocking in parity bit).''' + if str(self.dparity) != self.bits: + self.putx('parity', 1, str(self.dparity) + self.bits) # PARITY ERROR + elif self.rw == 'W': + self.handle_completed_write() + self.next_state() + + def handle_completed_write(self): + ''' + Update internal state of the debug port based on a completed + write operation. + ''' + if self.apdp != 'DP': + return + elif self.addr == ADDR_DP_SELECT: + self.ctrlsel = self.data & BIT_SELECT_CTRLSEL + elif self.addr == ADDR_DP_CTRLSTAT and self.ctrlsel == 0: + self.orundetect = self.data & BIT_CTRLSTAT_ORUNDETECT + + def get_address_description(self): + ''' + Return a human-readable description of the currently selected address, + for annotated results. + ''' + if self.apdp == 'DP': + if self.rw == 'R': + # Tables 2-4 & 2-5 in ADIv5.2 spec ARM document IHI 0031C + return { + 0: 'IDCODE', + 0x4: 'R CTRL/STAT' if self.ctrlsel == 0 else 'R DLCR', + 0x8: 'RESEND', + 0xC: 'RDBUFF' + }[self.addr] + elif self.rw == 'W': + # Tables 2-4 & 2-5 in ADIv5.2 spec ARM document IHI 0031C + return { + 0: 'W ABORT', + 0x4: 'W CTRL/STAT' if self.ctrlsel == 0 else 'W DLCR', + 0x8: 'W SELECT', + 0xC: 'W RESERVED' + }[self.addr] + elif self.apdp == 'AP': + if self.rw == 'R': + return 'R AP%x' % self.addr + elif self.rw == 'W': + return 'W AP%x' % self.addr + + # Any legitimate operations shouldn't fall through to here, probably + # a decoder bug. + return '? %s%s%x' % (self.rw, self.apdp, self.addr) diff --git a/libsigrokdecode4DSL/decoders/swim/__init__.py b/libsigrokdecode4DSL/decoders/swim/__init__.py new file mode 100644 index 00000000..cd18b857 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/swim/__init__.py @@ -0,0 +1,29 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2018 Mike Jagdis +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + +''' +SWIM is a single wire interface for STM8 series 8-bit microcontrollers +that allows non-intrusive read/wite access to be performed on-the-fly +to the memory and registers of the MCU for debug and flashing purposes. + +See the STMicroelectronics document UM0470 for details. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/swim/pd.py b/libsigrokdecode4DSL/decoders/swim/pd.py new file mode 100644 index 00000000..8c12ea7b --- /dev/null +++ b/libsigrokdecode4DSL/decoders/swim/pd.py @@ -0,0 +1,304 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2018 Mike Jagdis +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + +import math +import sigrokdecode as srd + +class SamplerateError(Exception): + pass + +class Decoder(srd.Decoder): + api_version = 3 + id = 'swim' + name = 'SWIM' + longname = 'STM8 SWIM bus' + desc = 'STM8 Single Wire Interface Module (SWIM) protocol.' + license = 'gplv2+' + inputs = ['logic'] + outputs = [] + tags = ['Debug/trace'] + options = ( + {'id': 'debug', 'desc': 'Debug', 'default': 'no', 'values': ('yes', 'no') }, + ) + channels = ( + {'id': 'swim', 'name': 'SWIM', 'desc': 'SWIM data line'}, + ) + annotations = ( + ('bit', 'Bit'), + ('enterseq', 'SWIM enter sequence'), + ('start-host', 'Start bit (host)'), + ('start-target', 'Start bit (target)'), + ('parity', 'Parity bit'), + ('ack', 'Acknowledgement'), + ('nack', 'Negative acknowledgement'), + ('byte-write', 'Byte write'), + ('byte-read', 'Byte read'), + ('cmd-unknown', 'Unknown SWIM command'), + ('cmd', 'SWIM command'), + ('bytes', 'Byte count'), + ('address', 'Address'), + ('data-write', 'Data write'), + ('data-read', 'Data read'), + ('debug', 'Debug'), + ) + annotation_rows = ( + ('bits', 'Bits', (0,)), + ('framing', 'Framing', (2, 3, 4, 5, 6, 7, 8)), + ('protocol', 'Protocol', (1, 9, 10, 11, 12, 13, 14)), + ('debug', 'Debug', (15,)), + ) + binary = ( + ('tx', 'Dump of data written to target'), + ('rx', 'Dump of data read from target'), + ) + + def __init__(self): + # SWIM clock for the target is normally HSI/2 where HSI is 8MHz +- 5% + # although the divisor can be removed by setting the SWIMCLK bit in + # the CLK_SWIMCCR register. There is no standard for the host so we + # will be generous and assume it is using an 8MHz +- 10% oscillator. + # We do not need to be accurate. We just need to avoid treating enter + # sequence pulses as bits. A synchronization frame will cause this + # to be adjusted. + self.HSI = 8000000 + self.HSI_min = self.HSI * 0.9 + self.HSI_max = self.HSI * 1.1 + self.swim_clock = self.HSI_min / 2 + + self.eseq_edge = [[-1, None], [-1, None]] + self.eseq_pairnum = 0 + self.eseq_pairstart = None + + self.reset() + + def reset(self): + self.bit_edge = [[-1, None], [-1, None]] + self.bit_maxlen = -1 + self.bitseq_len = 0 + self.bitseq_end = None + self.proto_state = 'CMD' + + def metadata(self, key, value): + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + + def adjust_timings(self): + # A low-speed bit is 22 SWIM clocks long. + # There are options to shorten bits to 10 clocks or use HSI rather + # than HSI/2 as the SWIM clock but the longest valid bit should be no + # more than this many samples. This does not need to be accurate. + # It exists simply to prevent bits extending unecessarily far into + # trailing bus-idle periods. This will be adjusted every time we see + # a synchronization frame or start bit in order to show idle periods + # as accurately as possible. + self.bit_reflen = math.ceil(self.samplerate * 22 / self.swim_clock) + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + self.out_binary = self.register(srd.OUTPUT_BINARY) + + if not self.samplerate: + raise SamplerateError('Cannot decode without samplerate.') + + # A synchronization frame is a low that lasts for more than 64 but no + # more than 128 SWIM clock periods based on the standard SWIM clock. + # Note: we also allow for the possibility that the SWIM clock divisor + # has been disabled here. + self.sync_reflen_min = math.floor(self.samplerate * 64 / self.HSI_max) + self.sync_reflen_max = math.ceil(self.samplerate * 128 / (self.HSI_min / 2)) + + self.debug = True if self.options['debug'] == 'yes' else False + + # The SWIM entry sequence is 4 pulses at 2kHz followed by 4 at 1kHz. + self.eseq_reflen = math.ceil(self.samplerate / 2048) + + self.adjust_timings() + + def protocol(self): + if self.proto_state == 'CMD': + # Command + if self.bitseq_value == 0x00: + self.put(self.bitseq_start, self.bitseq_end, self.out_ann, [10, ['system reset', 'SRST', '!']]) + elif self.bitseq_value == 0x01: + self.proto_state = 'N' + self.put(self.bitseq_start, self.bitseq_end, self.out_ann, [10, ['read on-the-fly', 'ROTF', 'r']]) + elif self.bitseq_value == 0x02: + self.proto_state = 'N' + self.put(self.bitseq_start, self.bitseq_end, self.out_ann, [10, ['write on-the-fly', 'WOTF', 'w']]) + else: + self.put(self.bitseq_start, self.bitseq_end, self.out_ann, [9, ['unknown', 'UNK']]) + elif self.proto_state == 'N': + # Number of bytes + self.proto_byte_count = self.bitseq_value + self.proto_state = '@E' + self.put(self.bitseq_start, self.bitseq_end, self.out_ann, [11, ['byte count 0x%02x' % self.bitseq_value, 'bytes 0x%02x' % self.bitseq_value, '0x%02x' % self.bitseq_value, '%02x' % self.bitseq_value, '%x' % self.bitseq_value]]) + elif self.proto_state == '@E': + # Address byte 1 + self.proto_addr = self.bitseq_value + self.proto_addr_start = self.bitseq_start + self.proto_state = '@H' + elif self.proto_state == '@H': + # Address byte 2 + self.proto_addr = (self.proto_addr << 8) | self.bitseq_value + self.proto_state = '@L' + elif self.proto_state == '@L': + # Address byte 3 + self.proto_addr = (self.proto_addr << 8) | self.bitseq_value + self.proto_state = 'D' + self.put(self.proto_addr_start, self.bitseq_end, self.out_ann, [12, ['address 0x%06x' % self.proto_addr, 'addr 0x%06x' % self.proto_addr, '0x%06x' % self.proto_addr, '%06x' %self.proto_addr, '%x' % self.proto_addr]]) + else: + if self.proto_byte_count > 0: + self.proto_byte_count -= 1 + if self.proto_byte_count == 0: + self.proto_state = 'CMD' + + self.put(self.bitseq_start, self.bitseq_end, self.out_ann, [13 + self.bitseq_dir, ['0x%02x' % self.bitseq_value, '%02x' % self.bitseq_value, '%x' % self.bitseq_value]]) + self.put(self.bitseq_start, self.bitseq_end, self.out_binary, [0 + self.bitseq_dir, bytes([self.bitseq_value])]) + if self.debug: + self.put(self.bitseq_start, self.bitseq_end, self.out_ann, [15, ['%d more' % self.proto_byte_count, '%d' % self.proto_byte_count]]) + + def bitseq(self, bitstart, bitend, bit): + if self.bitseq_len == 0: + # Looking for start of a bit sequence (command or byte). + self.bit_reflen = bitend - bitstart + self.bitseq_value = 0 + self.bitseq_dir = bit + self.bitseq_len = 1 + self.put(bitstart, bitend, self.out_ann, [2 + self.bitseq_dir, ['start', 's']]) + elif (self.proto_state == 'CMD' and self.bitseq_len == 4) or (self.proto_state != 'CMD' and self.bitseq_len == 9): + # Parity bit + self.bitseq_end = bitstart + self.bitseq_len += 1 + + self.put(bitstart, bitend, self.out_ann, [4, ['parity', 'par', 'p']]) + + # The start bit is not data but was used for parity calculation. + self.bitseq_value &= 0xff + self.put(self.bitseq_start, self.bitseq_end, self.out_ann, [7 + self.bitseq_dir, ['0x%02x' % self.bitseq_value, '%02x' % self.bitseq_value, '%x' % self.bitseq_value]]) + elif (self.proto_state == 'CMD' and self.bitseq_len == 5) or (self.proto_state != 'CMD' and self.bitseq_len == 10): + # ACK/NACK bit. + if bit: + self.put(bitstart, bitend, self.out_ann, [5, ['ack', 'a']]) + else: + self.put(bitstart, bitend, self.out_ann, [6, ['nack', 'n']]) + + # We only pass data that was ack'd up the stack. + if bit: + self.protocol() + + self.bitseq_len = 0 + else: + if self.bitseq_len == 1: + self.bitseq_start = bitstart + self.bitseq_value = (self.bitseq_value << 1) | bit + self.bitseq_len += 1 + + def bit(self, start, mid, end): + if mid - start >= end - mid: + self.put(start, end, self.out_ann, [0, ['0']]) + bit = 0 + else: + self.put(start, end, self.out_ann, [0, ['1']]) + bit = 1 + + self.bitseq(start, end, bit) + + def detect_synchronize_frame(self, start, end): + # Strictly speaking, synchronization frames are only recognised when + # SWIM is active. A falling edge on reset disables SWIM and an enter + # sequence is needed to re-enable it. However we do not want to be + # reliant on seeing the NRST pin just for that and we also want to be + # able to decode SWIM even if we just sample parts of the dialogue. + # For this reason we limit ourselves to only recognizing + # synchronization frames that have believable lengths based on our + # knowledge of the range of possible SWIM clocks. + if self.samplenum - self.eseq_edge[1][1] >= self.sync_reflen_min and self.samplenum - self.eseq_edge[1][1] <= self.sync_reflen_max: + self.put(self.eseq_edge[1][1], self.samplenum, self.out_ann, [1, ['synchronization frame', 'synchronization', 'sync', 's']]) + + # A low that lasts for more than 64 SWIM clock periods causes a + # reset of the SWIM communication state machine and will switch + # the SWIM to low-speed mode (SWIM_CSR.HS is cleared). + self.reset() + + # The low SHOULD last 128 SWIM clocks. This is used to + # resynchronize in order to allow for variation in the frequency + # of the internal RC oscillator. + self.swim_clock = 128 * (self.samplerate / (self.samplenum - self.eseq_edge[1][1])) + self.adjust_timings() + + def eseq_potential_start(self, start, end): + self.eseq_pairstart = start + self.eseq_reflen = end - start + self.eseq_pairnum = 1 + + def detect_enter_sequence(self, start, end): + # According to the spec the enter sequence is four pulses at 2kHz + # followed by four at 1kHz. We do not check the frequency but simply + # check the lengths of successive pulses against the first. This means + # we have no need to account for the accuracy (or lack of) of the + # host's oscillator. + if self.eseq_pairnum == 0 or abs(self.eseq_reflen - (end - start)) > 2: + self.eseq_potential_start(start, end) + + elif self.eseq_pairnum < 4: + # The next three pulses should be the same length as the first. + self.eseq_pairnum += 1 + + if self.eseq_pairnum == 4: + self.eseq_reflen /= 2 + else: + # The final four pulses should each be half the length of the + # initial pair. Again, a mismatch causes us to reset and use the + # current pulse as a new potential enter sequence start. + self.eseq_pairnum += 1 + if self.eseq_pairnum == 8: + # Four matching pulses followed by four more that match each + # other but are half the length of the first 4. SWIM is active! + self.put(self.eseq_pairstart, end, self.out_ann, [1, ['enter sequence', 'enter seq', 'enter', 'ent', 'e']]) + self.eseq_pairnum = 0 + + def decode(self): + while True: + if self.bit_maxlen >= 0: + (swim,) = self.wait() + self.bit_maxlen -= 1 + else: + (swim,) = self.wait({0: 'e'}) + + if swim != self.eseq_edge[1][0]: + if swim == 1 and self.eseq_edge[1][1] is not None: + self.detect_synchronize_frame(self.eseq_edge[1][1], self.samplenum) + if self.eseq_edge[0][1] is not None: + self.detect_enter_sequence(self.eseq_edge[0][1], self.samplenum) + self.eseq_edge.pop(0) + self.eseq_edge.append([swim, self.samplenum]) + + if (swim != self.bit_edge[1][0] and (swim != 1 or self.bit_edge[1][0] != -1)) or self.bit_maxlen == 0: + if self.bit_maxlen == 0 and self.bit_edge[1][0] == 1: + swim = -1 + + if self.bit_edge[1][0] != 0 and swim == 0: + self.bit_maxlen = self.bit_reflen + + if self.bit_edge[0][0] == 0 and self.bit_edge[1][0] == 1 and self.samplenum - self.bit_edge[0][1] <= self.bit_reflen + 10: + self.bit(self.bit_edge[0][1], self.bit_edge[1][1], self.samplenum) + + self.bit_edge.pop(0) + self.bit_edge.append([swim, self.samplenum]) diff --git a/libsigrokdecode4DSL/decoders/t55xx/__init__.py b/libsigrokdecode4DSL/decoders/t55xx/__init__.py new file mode 100644 index 00000000..01184350 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/t55xx/__init__.py @@ -0,0 +1,25 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2015 Benjamin Larsson +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +T55xx is a 100-150kHz RFID protocol according to the Atmel e555x +downlink/write protocol (pulse interval coding). +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/t55xx/pd.py b/libsigrokdecode4DSL/decoders/t55xx/pd.py new file mode 100644 index 00000000..d345d318 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/t55xx/pd.py @@ -0,0 +1,326 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2015 Benjamin Larsson +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd + +class SamplerateError(Exception): + pass + +class Decoder(srd.Decoder): + api_version = 3 + id = 't55xx' + name = 'T55xx' + longname = 'RFID T55xx' + desc = 'T55xx 100-150kHz RFID protocol.' + license = 'gplv2+' + inputs = ['logic'] + outputs = [] + tags = ['IC', 'RFID'] + channels = ( + {'id': 'data', 'name': 'Data', 'desc': 'Data line'}, + ) + options = ( + {'id': 'coilfreq', 'desc': 'Coil frequency', 'default': 125000}, + {'id': 'start_gap', 'desc': 'Start gap min', 'default': 20}, + {'id': 'w_gap', 'desc': 'Write gap min', 'default': 20}, + {'id': 'w_one_min', 'desc': 'Write one min', 'default': 48}, + {'id': 'w_one_max', 'desc': 'Write one max', 'default': 63}, + {'id': 'w_zero_min', 'desc': 'Write zero min', 'default': 16}, + {'id': 'w_zero_max', 'desc': 'Write zero max', 'default': 31}, + {'id': 'em4100_decode', 'desc': 'EM4100 decode', 'default': 'on', + 'values': ('on', 'off')}, + ) + annotations = ( + ('bit_value', 'Bit value'), + ('start_gap', 'Start gap'), + ('write_gap', 'Write gap'), + ('write_mode_exit', 'Write mode exit'), + ('bit', 'Bit'), + ('opcode', 'Opcode'), + ('lock', 'Lock'), + ('data', 'Data'), + ('password', 'Password'), + ('address', 'Address'), + ('bitrate', 'Bitrate'), + ) + annotation_rows = ( + ('bits', 'Bits', (0,)), + ('structure', 'Structure', (1, 2, 3, 4)), + ('fields', 'Fields', (5, 6, 7, 8, 9)), + ('decode', 'Decode', (10,)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.samplerate = None + self.last_samplenum = None + self.lastlast_samplenum = None + self.state = 'START_GAP' + self.bits_pos = [[0 for col in range(3)] for row in range(70)] + self.br_string = ['RF/8', 'RF/16', 'RF/32', 'RF/40', + 'RF/50', 'RF/64', 'RF/100', 'RF/128'] + self.mod_str1 = ['Direct', 'Manchester', 'Biphase', 'Reserved'] + self.mod_str2 = ['Direct', 'PSK1', 'PSK2', 'PSK3', 'FSK1', 'FSK2', + 'FSK1a', 'FSK2a'] + self.pskcf_str = ['RF/2', 'RF/4', 'RF/8', 'Reserved'] + self.em4100_decode1_partial = 0 + + def metadata(self, key, value): + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + self.field_clock = self.samplerate / self.options['coilfreq'] + self.wzmax = self.options['w_zero_max'] * self.field_clock + self.wzmin = self.options['w_zero_min'] * self.field_clock + self.womax = self.options['w_one_max'] * self.field_clock + self.womin = self.options['w_one_min'] * self.field_clock + self.startgap = self.options['start_gap'] * self.field_clock + self.writegap = self.options['w_gap'] * self.field_clock + self.nogap = 64 * self.field_clock + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def decode_config(self, idx): + safer_key = self.bits_pos[idx][0]<<3 | self.bits_pos[idx+1][0]<<2 | \ + self.bits_pos[idx+2][0]<<1 | self.bits_pos[idx+3][0] + self.put(self.bits_pos[idx][1], self.bits_pos[idx+3][2], self.out_ann, + [10, ['Safer Key' + ': %X' % safer_key,'%X' % safer_key]]) + bitrate = self.bits_pos[idx+11][0]<<2 | self.bits_pos[idx+12][0]<<1 | \ + self.bits_pos[idx+13][0] + self.put(self.bits_pos[idx+11][1], self.bits_pos[idx+13][2], + self.out_ann, [10, ['Data Bit Rate: ' + \ + self.br_string[bitrate], self.br_string[bitrate]]]) + modulation1 = self.bits_pos[idx+15][0]<<1 | self.bits_pos[idx+16][0] + modulation2 = self.bits_pos[idx+17][0]<<2 | \ + self.bits_pos[idx+18][0]<<1 | self.bits_pos[idx+19][0] + if modulation1 == 0: + mod_string = self.mod_str2[modulation2] + else: + mod_string = self.mod_str1[modulation1] + self.put(self.bits_pos[idx+15][1], self.bits_pos[idx+19][2], + self.out_ann, [10, ['Modulation: ' + mod_string, mod_string]]) + psk_cf = self.bits_pos[idx+20][0]<<1 | self.bits_pos[idx+21][0] + self.put(self.bits_pos[idx+20][1], self.bits_pos[idx+21][2], + self.out_ann, [10, ['PSK-CF: ' + self.pskcf_str[psk_cf], + self.pskcf_str[psk_cf]]]) + self.put(self.bits_pos[idx+22][1], self.bits_pos[idx+22][2], + self.out_ann, [10, ['AOR' + ': %d' % \ + (self.bits_pos[idx+22][0]),'%d' % (self.bits_pos[idx+22][0])]]) + maxblock = self.bits_pos[idx+24][0]<<2 | self.bits_pos[idx+25][0]<<1 | \ + self.bits_pos[idx+26][0] + self.put(self.bits_pos[idx+24][1], self.bits_pos[idx+26][2], + self.out_ann, [10, ['Max-Block' + ': %d' % maxblock, + '%d' % maxblock]]) + self.put(self.bits_pos[idx+27][1], self.bits_pos[idx+27][2], + self.out_ann, [10, ['PWD' + ': %d' % \ + (self.bits_pos[idx+27][0]),'%d' % (self.bits_pos[idx+27][0])]]) + self.put(self.bits_pos[idx+28][1], self.bits_pos[idx+28][2], + self.out_ann, [10, ['ST-sequence terminator' + ': %d' % \ + (self.bits_pos[idx+28][0]),'%d' % (self.bits_pos[idx+28][0])]]) + self.put(self.bits_pos[idx+31][1], self.bits_pos[idx+31][2], + self.out_ann, [10, ['POR delay' + ': %d' % \ + (self.bits_pos[idx+31][0]),'%d' % (self.bits_pos[idx+31][0])]]) + + def put4bits(self, idx): + bits = self.bits_pos[idx][0]<<3 | self.bits_pos[idx+1][0]<<2 | \ + self.bits_pos[idx+2][0]<<1 | self.bits_pos[idx+3][0] + self.put(self.bits_pos[idx][1], self.bits_pos[idx+3][2], self.out_ann, + [10, ['%X' % bits]]) + + def em4100_decode1(self, idx): + self.put(self.bits_pos[idx][1], self.bits_pos[idx+8][2], self.out_ann, + [10, ['EM4100 header', 'EM header', 'Header', 'H']]) + self.put4bits(idx+9) + self.put4bits(idx+14) + self.put4bits(idx+19) + self.put4bits(idx+24) + self.em4100_decode1_partial = self.bits_pos[idx+29][0]<<3 | \ + self.bits_pos[idx+30][0]<<2 | self.bits_pos[idx+31][0]<<1 + self.put(self.bits_pos[idx+29][1], self.bits_pos[idx+31][2], + self.out_ann, [10, ['Partial nibble']]) + + def em4100_decode2(self, idx): + if self.em4100_decode1_partial != 0: + bits = self.em4100_decode1_partial + self.bits_pos[idx][0] + self.put(self.bits_pos[idx][1], self.bits_pos[idx][2], + self.out_ann, [10, ['%X' % bits]]) + self.em4100_decode1_partial = 0 + else: + self.put(self.bits_pos[idx][1], self.bits_pos[idx][2], + self.out_ann, [10, ['Partial nibble']]) + + self.put4bits(idx+2) + self.put4bits(idx+7) + self.put4bits(idx+12) + self.put4bits(idx+17) + self.put4bits(idx+22) + self.put(self.bits_pos[idx+27][1], self.bits_pos[idx+31][2], + self.out_ann, [10, ['EM4100 trailer']]) + + def get_32_bits(self, idx): + retval = 0 + for i in range(0, 32): + retval <<= 1 + retval |= self.bits_pos[i+idx][0] + return retval + + def get_3_bits(self, idx): + retval = self.bits_pos[idx][0]<<2 | self.bits_pos[idx+1][0]<<1 | \ + self.bits_pos[idx+2][0] + return retval + + def put_fields(self): + if (self.bit_nr == 70): + self.put(self.bits_pos[0][1], self.bits_pos[1][2], self.out_ann, + [5, ['Opcode' + ': %d%d' % (self.bits_pos[0][0], + self.bits_pos[1][0]), '%d%d' % (self.bits_pos[0][0], + self.bits_pos[1][0])]]) + password = self.get_32_bits(2) + self.put(self.bits_pos[2][1], self.bits_pos[33][2], self.out_ann, + [8, ['Password' + ': %X' % password, '%X' % password]]) + self.put(self.bits_pos[34][1], self.bits_pos[34][2], self.out_ann, + [6, ['Lock' + ': %X' % self.bits_pos[34][0], + '%X' % self.bits_pos[34][0]]]) + data = self.get_32_bits(35) + self.put(self.bits_pos[35][1], self.bits_pos[66][2], self.out_ann, + [7, ['Data' + ': %X' % data, '%X' % data]]) + addr = self.get_3_bits(67) + self.put(self.bits_pos[67][1], self.bits_pos[69][2], self.out_ann, + [9, ['Addr' + ': %X' % addr, '%X' % addr]]) + if addr == 0: + self.decode_config(35) + if addr == 7: + self.put(self.bits_pos[35][1], self.bits_pos[66][2], + self.out_ann, [10, ['Password' + ': %X' % data, + '%X' % data]]) + # If we are programming EM4100 data we can decode it halfway. + if addr == 1 and self.options['em4100_decode'] == 'on': + self.em4100_decode1(35) + if addr == 2 and self.options['em4100_decode'] == 'on': + self.em4100_decode2(35) + + if (self.bit_nr == 38): + self.put(self.bits_pos[0][1], self.bits_pos[1][2], self.out_ann, + [5, ['Opcode' + ': %d%d' % (self.bits_pos[0][0], + self.bits_pos[1][0]), '%d%d' % (self.bits_pos[0][0], + self.bits_pos[1][0])]]) + self.put(self.bits_pos[2][1], self.bits_pos[2][2], self.out_ann, + [6, ['Lock' + ': %X' % self.bits_pos[2][0], + '%X' % self.bits_pos[2][0]]]) + data = self.get_32_bits(3) + self.put(self.bits_pos[3][1], self.bits_pos[34][2], self.out_ann, + [7, ['Data' + ': %X' % data, '%X' % data]]) + addr = self.get_3_bits(35) + self.put(self.bits_pos[35][1], self.bits_pos[37][2], self.out_ann, + [9, ['Addr' + ': %X' % addr, '%X' % addr]]) + if addr == 0: + self.decode_config(3) + if addr == 7: + self.put(self.bits_pos[3][1], self.bits_pos[34][2], + self.out_ann, [10, ['Password' + ': %X' % data, + '%X' % data]]) + # If we are programming EM4100 data we can decode it halfway. + if addr == 1 and self.options['em4100_decode'] == 'on': + self.em4100_decode1(3) + if addr == 2 and self.options['em4100_decode'] == 'on': + self.em4100_decode2(3) + + if (self.bit_nr == 2): + self.put(self.bits_pos[0][1], self.bits_pos[1][2], self.out_ann, + [5, ['Opcode' + ': %d%d' % (self.bits_pos[0][0], + self.bits_pos[1][0]), '%d%d' % (self.bits_pos[0][0], + self.bits_pos[1][0])]]) + self.bit_nr = 0 + + def add_bits_pos(self, bit, bit_start, bit_end): + if self.bit_nr < 70: + self.bits_pos[self.bit_nr][0] = bit + self.bits_pos[self.bit_nr][1] = bit_start + self.bits_pos[self.bit_nr][2] = bit_end + self.bit_nr += 1 + + def decode(self): + if not self.samplerate: + raise SamplerateError('Cannot decode without samplerate.') + + self.last_samplenum = 0 + self.lastlast_samplenum = 0 + self.last_edge = 0 + self.oldpl = 0 + self.oldpp = 0 + self.oldsamplenum = 0 + self.last_bit_pos = 0 + self.old_gap_start = 0 + self.old_gap_end = 0 + self.gap_detected = 0 + self.bit_nr = 0 + + while True: + (pin,) = self.wait({0: 'e'}) + + pl = self.samplenum - self.oldsamplenum + pp = pin + samples = self.samplenum - self.last_samplenum + + if self.state == 'WRITE_GAP': + if pl > self.writegap: + self.gap_detected = 1 + self.put(self.last_samplenum, self.samplenum, + self.out_ann, [2, ['Write gap']]) + if (self.last_samplenum-self.old_gap_end) > self.nogap: + self.gap_detected = 0 + self.state = 'START_GAP' + self.put(self.old_gap_end, self.last_samplenum, + self.out_ann, [3, ['Write mode exit']]) + self.put_fields() + + if self.state == 'START_GAP': + if pl > self.startgap: + self.gap_detected = 1 + self.put(self.last_samplenum, self.samplenum, + self.out_ann, [1, ['Start gap']]) + self.state = 'WRITE_GAP' + + if self.gap_detected == 1: + self.gap_detected = 0 + if (self.last_samplenum - self.old_gap_end) > self.wzmin \ + and (self.last_samplenum - self.old_gap_end) < self.wzmax: + self.put(self.old_gap_end, self.last_samplenum, + self.out_ann, [0, ['0']]) + self.put(self.old_gap_end, self.last_samplenum, + self.out_ann, [4, ['Bit']]) + self.add_bits_pos(0, self.old_gap_end, + self.last_samplenum) + if (self.last_samplenum - self.old_gap_end) > self.womin \ + and (self.last_samplenum - self.old_gap_end) < self.womax: + self.put(self.old_gap_end, self.last_samplenum, + self.out_ann, [0, ['1']]) + self.put(self.old_gap_end, self.last_samplenum, + self.out_ann, [4, ['Bit']]) + self.add_bits_pos(1, self.old_gap_end, self.last_samplenum) + + self.old_gap_start = self.last_samplenum + self.old_gap_end = self.samplenum + + self.oldpl = pl + self.oldpp = pp + self.oldsamplenum = self.samplenum + self.last_samplenum = self.samplenum diff --git a/libsigrokdecode4DSL/decoders/tca6408a/__init__.py b/libsigrokdecode4DSL/decoders/tca6408a/__init__.py new file mode 100644 index 00000000..5373c311 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/tca6408a/__init__.py @@ -0,0 +1,25 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 alberink +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This decoder stacks on top of the 'i2c' PD and decodes the Texas Instruments +TCA6408A 8-bit I²C I/O expander protocol. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/tca6408a/pd.py b/libsigrokdecode4DSL/decoders/tca6408a/pd.py new file mode 100644 index 00000000..49245174 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/tca6408a/pd.py @@ -0,0 +1,132 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2012 Uwe Hermann +## Copyright (C) 2013 Matt Ranostay +## Copyright (C) 2014 alberink +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd + +class Decoder(srd.Decoder): + api_version = 3 + id = 'tca6408a' + name = 'TI TCA6408A' + longname = 'Texas Instruments TCA6408A' + desc = 'Texas Instruments TCA6408A 8-bit I²C I/O expander.' + license = 'gplv2+' + inputs = ['i2c'] + outputs = [] + tags = ['Embedded/industrial', 'IC'] + annotations = ( + ('register', 'Register type'), + ('value', 'Register value'), + ('warnings', 'Warning messages'), + ) + annotation_rows = ( + ('regs', 'Registers', (0, 1)), + ('warnings', 'Warnings', (2,)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.state = 'IDLE' + self.chip = -1 + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def putx(self, data): + self.put(self.ss, self.es, self.out_ann, data) + + def handle_reg_0x00(self, b): + self.putx([1, ['State of inputs: %02X' % b]]) + + def handle_reg_0x01(self, b): + self.putx([1, ['Outputs set: %02X' % b ]]) + + def handle_reg_0x02(self, b): + self.putx([1, ['Polarity inverted: %02X' % b]]) + + def handle_reg_0x03(self, b): + self.putx([1, ['Configuration: %02X' % b]]) + + def handle_write_reg(self, b): + if b == 0: + self.putx([0, ['Input port', 'In', 'I']]) + elif b == 1: + self.putx([0, ['Output port', 'Out', 'O']]) + elif b == 2: + self.putx([0, ['Polarity inversion register', 'Pol', 'P']]) + elif b == 3: + self.putx([0, ['Configuration register', 'Conf', 'C']]) + + def check_correct_chip(self, addr): + if addr not in (0x20, 0x21): + self.putx([2, ['Warning: I²C slave 0x%02X not a TCA6408A ' + 'compatible chip.' % addr]]) + self.state = 'IDLE' + + def decode(self, ss, es, data): + cmd, databyte = data + + # Store the start/end samples of this I²C packet. + self.ss, self.es = ss, es + + # State machine. + if self.state == 'IDLE': + # Wait for an I²C START condition. + if cmd != 'START': + return + self.state = 'GET SLAVE ADDR' + elif self.state == 'GET SLAVE ADDR': + self.chip = databyte + self.state = 'GET REG ADDR' + elif self.state == 'GET REG ADDR': + # Wait for a data write (master selects the slave register). + if cmd in ('ADDRESS READ', 'ADDRESS WRITE'): + self.check_correct_chip(databyte) + if cmd != 'DATA WRITE': + return + self.reg = databyte + self.handle_write_reg(self.reg) + self.state = 'WRITE IO REGS' + elif self.state == 'WRITE IO REGS': + # If we see a Repeated Start here, the master wants to read. + if cmd == 'START REPEAT': + self.state = 'READ IO REGS' + return + # Otherwise: Get data bytes until a STOP condition occurs. + if cmd == 'DATA WRITE': + handle_reg = getattr(self, 'handle_reg_0x%02x' % self.reg) + handle_reg(databyte) + elif cmd == 'STOP': + self.state = 'IDLE' + self.chip = -1 + elif self.state == 'READ IO REGS': + # Wait for an address read operation. + if cmd == 'ADDRESS READ': + self.state = 'READ IO REGS2' + self.chip = databyte + return + elif self.state == 'READ IO REGS2': + if cmd == 'DATA READ': + handle_reg = getattr(self, 'handle_reg_0x%02x' % self.reg) + handle_reg(databyte) + elif cmd == 'STOP': + self.state = 'IDLE' diff --git a/libsigrokdecode4DSL/decoders/tdm_audio/__init__.py b/libsigrokdecode4DSL/decoders/tdm_audio/__init__.py new file mode 100644 index 00000000..a95e16e3 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/tdm_audio/__init__.py @@ -0,0 +1,25 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2019 Ben Dooks +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +TDM Audio is an audio serial bus for moving audio data between devices +(usually on the same board) which can carry one or more channels of data. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/tdm_audio/pd.py b/libsigrokdecode4DSL/decoders/tdm_audio/pd.py new file mode 100644 index 00000000..e805706f --- /dev/null +++ b/libsigrokdecode4DSL/decoders/tdm_audio/pd.py @@ -0,0 +1,116 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2019 Ben Dooks +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd + +MAX_CHANNELS = 8 + +class Decoder(srd.Decoder): + api_version = 3 + id = 'tdm_audio' + name = 'TDM audio' + longname = 'Time division multiplex audio' + desc = 'TDM multi-channel audio protocol.' + license = 'gplv2+' + inputs = ['logic'] + outputs = [] + tags = ['Audio'] + channels = ( + { 'id': 'clock', 'name': 'Bitclk', 'desc': 'Data bit clock' }, + { 'id': 'frame', 'name': 'Framesync', 'desc': 'Frame sync' }, + { 'id': 'data', 'name': 'Data', 'desc': 'Serial data' }, + ) + options = ( + {'id': 'bps', 'desc': 'Bits per sample', 'default': 16 }, + {'id': 'channels', 'desc': 'Channels per frame', 'default': MAX_CHANNELS }, + {'id': 'edge', 'desc': 'Clock edge to sample on', 'default': 'rising', 'values': ('rising', 'falling') } + ) + annotations = tuple(('ch%d' % i, 'Ch%d' % i) for i in range(MAX_CHANNELS)) + annotation_rows = tuple(('ch%d-vals' % i, 'Ch%d' % i, (i,)) for i in range(MAX_CHANNELS)) + + def __init__(self): + self.reset() + + def reset(self): + self.samplerate = None + self.channels = MAX_CHANNELS + self.channel = 0 + self.bitdepth = 16 + self.bitcount = 0 + self.samplecount = 0 + self.lastsync = 0 + self.lastframe = 0 + self.data = 0 + self.ss_block = None + + def metdatadata(self, key, value): + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + self.bitdepth = self.options['bps'] + self.edge = self.options['edge'] + + def decode(self): + while True: + # Wait for edge of clock (sample on rising/falling edge). + clock, frame, data = self.wait({0: self.edge[0]}) + + self.data = (self.data << 1) | data + self.bitcount += 1 + + if self.ss_block is not None: + if self.bitcount >= self.bitdepth: + self.bitcount = 0 + self.channel += 1 + + c1 = 'Channel %d' % self.channel + c2 = 'C%d' % self.channel + c3 = '%d' % self.channel + if self.bitdepth <= 8: + v = '%02x' % self.data + elif self.bitdepth <= 16: + v = '%04x' % self.data + else: + v = '%08x' % self.data + + if self.channel < self.channels: + ch = self.channel + else: + ch = 0 + + self.put(self.ss_block, self.samplenum, self.out_ann, + [ch, ['%s: %s' % (c1, v), '%s: %s' % (c2, v), + '%s: %s' % (c3, v)]]) + self.data = 0 + self.ss_block = self.samplenum + self.samplecount += 1 + + # Check for new frame. + # Note, frame may be a single clock, or active for the first + # sample in the frame. + if frame != self.lastframe and frame == 1: + self.channel = 0 + self.bitcount = 0 + self.data = 0 + if self.ss_block is None: + self.ss_block = 0 + + self.lastframe = frame diff --git a/libsigrokdecode4DSL/decoders/timing/__init__.py b/libsigrokdecode4DSL/decoders/timing/__init__.py new file mode 100644 index 00000000..179487b3 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/timing/__init__.py @@ -0,0 +1,24 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 Torsten Duwe +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +Timing decoder, find the time between edges. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/timing/pd.py b/libsigrokdecode4DSL/decoders/timing/pd.py new file mode 100644 index 00000000..20ca2c4e --- /dev/null +++ b/libsigrokdecode4DSL/decoders/timing/pd.py @@ -0,0 +1,128 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 Torsten Duwe +## Copyright (C) 2014 Sebastien Bourdelin +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd +from collections import deque + +class SamplerateError(Exception): + pass + +def normalize_time(t): + if abs(t) >= 1.0: + return '%.3f s (%.3f Hz)' % (t, (1/t)) + elif abs(t) >= 0.001: + if 1/t/1000 < 1: + return '%.3f ms (%.3f Hz)' % (t * 1000.0, (1/t)) + else: + return '%.3f ms (%.3f kHz)' % (t * 1000.0, (1/t)/1000) + elif abs(t) >= 0.000001: + if 1/t/1000/1000 < 1: + return '%.3f μs (%.3f kHz)' % (t * 1000.0 * 1000.0, (1/t)/1000) + else: + return '%.3f μs (%.3f MHz)' % (t * 1000.0 * 1000.0, (1/t)/1000/1000) + elif abs(t) >= 0.000000001: + if 1/t/1000/1000/1000: + return '%.3f ns (%.3f MHz)' % (t * 1000.0 * 1000.0 * 1000.0, (1/t)/1000/1000) + else: + return '%.3f ns (%.3f GHz)' % (t * 1000.0 * 1000.0 * 1000.0, (1/t)/1000/1000/1000) + else: + return '%f' % t + +class Decoder(srd.Decoder): + api_version = 3 + id = 'timing' + name = 'Timing' + longname = 'Timing calculation with frequency and averaging' + desc = 'Calculate time between edges.' + license = 'gplv2+' + inputs = ['logic'] + outputs = [] + tags = ['Clock/timing', 'Util'] + channels = ( + {'id': 'data', 'name': 'Data', 'desc': 'Data line'}, + ) + annotations = ( + ('time', 'Time'), + ('average', 'Average'), + ('delta', 'Delta'), + ) + annotation_rows = ( + ('time', 'Time', (0,)), + ('average', 'Average', (1,)), + ('delta', 'Delta', (2,)), + ) + options = ( + { 'id': 'avg_period', 'desc': 'Averaging period', 'default': 100 }, + { 'id': 'edge', 'desc': 'Edges to check', 'default': 'any', 'values': ('any', 'rising', 'falling') }, + { 'id': 'delta', 'desc': 'Show delta from last', 'default': 'no', 'values': ('yes', 'no') }, + ) + + def __init__(self): + self.reset() + + def reset(self): + self.samplerate = None + self.last_samplenum = None + self.last_n = deque() + self.chunks = 0 + self.level_changed = False + self.last_t = None + + def metadata(self, key, value): + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + self.edge = self.options['edge'] + + def decode(self): + if not self.samplerate: + raise SamplerateError('Cannot decode without samplerate.') + while True: + if self.edge == 'rising': + self.wait({0: 'r'}) + elif self.edge == 'falling': + self.wait({0: 'f'}) + else: + self.wait({0: 'e'}) + + if not self.last_samplenum: + self.last_samplenum = self.samplenum + continue + samples = self.samplenum - self.last_samplenum + t = samples / self.samplerate + + if t > 0: + self.last_n.append(t) + if len(self.last_n) > self.options['avg_period']: + self.last_n.popleft() + + self.put(self.last_samplenum, self.samplenum, self.out_ann, + [0, [normalize_time(t)]]) + if self.options['avg_period'] > 0: + self.put(self.last_samplenum, self.samplenum, self.out_ann, + [1, [normalize_time(sum(self.last_n) / len(self.last_n))]]) + if self.last_t and self.options['delta'] == 'yes': + self.put(self.last_samplenum, self.samplenum, self.out_ann, + [2, [normalize_time(t - self.last_t)]]) + + self.last_t = t + self.last_samplenum = self.samplenum diff --git a/libsigrokdecode4DSL/decoders/tlc5620/__init__.py b/libsigrokdecode4DSL/decoders/tlc5620/__init__.py new file mode 100644 index 00000000..a642ef64 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/tlc5620/__init__.py @@ -0,0 +1,24 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2012 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +The Texas Instruments TLC5620 is an 8-bit quad DAC. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/tlc5620/pd.py b/libsigrokdecode4DSL/decoders/tlc5620/pd.py new file mode 100644 index 00000000..eec040bc --- /dev/null +++ b/libsigrokdecode4DSL/decoders/tlc5620/pd.py @@ -0,0 +1,210 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2012-2015 Uwe Hermann +## Copyright (C) 2019 DreamSourceLab +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd + +dacs = { + 0: 'DACA', + 1: 'DACB', + 2: 'DACC', + 3: 'DACD', +} + +class Decoder(srd.Decoder): + api_version = 3 + id = 'tlc5620' + name = 'TI TLC5620' + longname = 'Texas Instruments TLC5620' + desc = 'Texas Instruments TLC5620 8-bit quad DAC.' + license = 'gplv2+' + inputs = ['logic'] + outputs = [] + tags = ['IC', 'Analog/digital'] + channels = ( + {'id': 'clk', 'name': 'CLK', 'desc': 'Serial interface clock'}, + {'id': 'data', 'name': 'DATA', 'desc': 'Serial interface data'}, + ) + optional_channels = ( + {'id': 'load', 'name': 'LOAD', 'desc': 'Serial interface load control'}, + {'id': 'ldac', 'name': 'LDAC', 'desc': 'Load DAC'}, + ) + options = ( + {'id': 'vref_a', 'desc': 'Reference voltage DACA (V)', 'default': 3.3}, + {'id': 'vref_b', 'desc': 'Reference voltage DACB (V)', 'default': 3.3}, + {'id': 'vref_c', 'desc': 'Reference voltage DACC (V)', 'default': 3.3}, + {'id': 'vref_d', 'desc': 'Reference voltage DACD (V)', 'default': 3.3}, + ) + annotations = ( + ('dac-select', 'DAC select'), + ('gain', 'Gain'), + ('value', 'DAC value'), + ('data-latch', 'Data latch point'), + ('ldac-fall', 'LDAC falling edge'), + ('bit', 'Bit'), + ('reg-write', 'Register write'), + ('voltage-update', 'Voltage update'), + ('voltage-update-all', 'Voltage update (all DACs)'), + ('invalid-cmd', 'Invalid command'), + ) + annotation_rows = ( + ('bits', 'Bits', (5,)), + ('fields', 'Fields', (0, 1, 2)), + ('registers', 'Registers', (6, 7)), + ('voltage-updates', 'Voltage updates', (8,)), + ('events', 'Events', (3, 4)), + ('errors', 'Errors', (9,)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.bits = [] + self.ss_dac_first = None + self.ss_dac = self.es_dac = 0 + self.ss_gain = self.es_gain = 0 + self.ss_value = self.es_value = 0 + self.dac_select = self.gain = self.dac_value = None + self.dacval = {'A': '?', 'B': '?', 'C': '?', 'D': '?'} + self.gains = {'A': '?', 'B': '?', 'C': '?', 'D': '?'} + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def handle_11bits(self): + # Only look at the last 11 bits, the rest is ignored by the TLC5620. + if len(self.bits) > 11: + self.bits = self.bits[-11:] + + # If there are less than 11 bits, something is probably wrong. + if len(self.bits) < 11: + ss, es = self.samplenum, self.samplenum + if len(self.bits) >= 2: + ss = self.bits[0][1] + es = self.bits[-1][1] + (self.bits[1][1] - self.bits[0][1]) + self.put(ss, es, self.out_ann, [9, ['Command too short']]) + self.bits = [] + return False + + self.ss_dac = self.bits[0][1] + self.es_dac = self.ss_gain = self.bits[2][1] + self.es_gain = self.ss_value = self.bits[3][1] + self.clock_width = self.es_gain - self.ss_gain + self.es_value = self.bits[10][1] + self.clock_width # Guessed. + + if self.ss_dac_first is None: + self.ss_dac_first = self.ss_dac + + s = ''.join(str(i[0]) for i in self.bits[:2]) + self.dac_select = s = dacs[int(s, 2)] + self.put(self.ss_dac, self.es_dac, self.out_ann, + [0, ['DAC select: %s' % s, 'DAC sel: %s' % s, + 'DAC: %s' % s, 'D: %s' % s, s, s[3]]]) + + self.gain = g = 1 + self.bits[2][0] + self.put(self.ss_gain, self.es_gain, self.out_ann, + [1, ['Gain: x%d' % g, 'G: x%d' % g, 'x%d' % g]]) + + s = ''.join(str(i[0]) for i in self.bits[3:]) + self.dac_value = v = int(s, 2) + self.put(self.ss_value, self.es_value, self.out_ann, + [2, ['DAC value: %d' % v, 'Value: %d' % v, 'Val: %d' % v, + 'V: %d' % v, '%d' % v]]) + + # Emit an annotation for each bit. + for i in range(1, 11): + self.put(self.bits[i - 1][1], self.bits[i][1], self.out_ann, + [5, [str(self.bits[i - 1][0])]]) + self.put(self.bits[10][1], self.bits[10][1] + self.clock_width, + self.out_ann, [5, [str(self.bits[10][0])]]) + + self.bits = [] + + return True + + def handle_falling_edge_load(self): + if not self.handle_11bits(): + return + s, v, g = self.dac_select, self.dac_value, self.gain + self.put(self.samplenum, self.samplenum, self.out_ann, + [3, ['Falling edge on LOAD', 'LOAD fall', 'F']]) + vref = self.options['vref_%s' % self.dac_select[3].lower()] + v = '%.2fV' % (vref * (v / 256) * self.gain) + if self.ldac == 0: + # If LDAC is low, the voltage is set immediately. + self.put(self.ss_dac, self.es_value, self.out_ann, + [7, ['Setting %s voltage to %s' % (s, v), + '%s=%s' % (s, v)]]) + else: + # If LDAC is high, the voltage is not set immediately, but rather + # stored in a register. When LDAC goes low all four DAC voltages + # (DAC A/B/C/D) will be set at the same time. + self.put(self.ss_dac, self.es_value, self.out_ann, + [6, ['Setting %s register value to %s' % \ + (s, v), '%s=%s' % (s, v)]]) + # Save the last value the respective DAC was set to. + self.dacval[self.dac_select[-1]] = str(self.dac_value) + self.gains[self.dac_select[-1]] = self.gain + + def handle_falling_edge_ldac(self): + self.put(self.samplenum, self.samplenum, self.out_ann, + [4, ['Falling edge on LDAC', 'LDAC fall', 'LDAC', 'L']]) + + # Don't emit any annotations if we didn't see any register writes. + if self.ss_dac_first is None: + return + + # Calculate voltages based on Vref and the per-DAC gain. + dacval = {} + for key, val in self.dacval.items(): + if val == '?': + dacval[key] = '?' + else: + vref = self.options['vref_%s' % key.lower()] + v = vref * (int(val) / 256) * self.gains[key] + dacval[key] = '%.2fV' % v + + s = ''.join(['DAC%s=%s ' % (d, dacval[d]) for d in 'ABCD']).strip() + self.put(self.ss_dac_first, self.samplenum, self.out_ann, + [8, ['Updating voltages: %s' % s, s, s.replace('DAC', '')]]) + self.ss_dac_first = None + + def handle_new_dac_bit(self, datapin): + self.bits.append([datapin, self.samplenum]) + + def decode(self): + while True: + # DATA is shifted in the DAC on the falling CLK edge (MSB-first). + # A falling edge of LOAD will latch the data. + + # Wait for one (or multiple) of the following conditions: + # a) Falling edge on CLK, and/or + # b) Falling edge on LOAD, and/or + # b) Falling edge on LDAC + (clk, data, load, ldac) = self.wait([{0: 'f'}, {2: 'f'}, {3: 'f'}]) + self.ldac = ldac + + # Handle those conditions (one or more) that matched this time. + if (self.matched & (0b1 << 0)): + self.handle_new_dac_bit(data) + if (self.matched & (0b1 << 1)): + self.handle_falling_edge_load() + if (self.matched & (0b1 << 2)): + self.handle_falling_edge_ldac() diff --git a/libsigrokdecode4DSL/decoders/usb_packet/__init__.py b/libsigrokdecode4DSL/decoders/usb_packet/__init__.py new file mode 100644 index 00000000..5cd7c56b --- /dev/null +++ b/libsigrokdecode4DSL/decoders/usb_packet/__init__.py @@ -0,0 +1,43 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2012 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This decoder stacks on top of the 'usb_signalling' PD and decodes the USB +(low-speed and full-speed) packet protocol. + +Protocol layer (USB spec, chapter 8): + +Bit/byte ordering: Bits are sent onto the bus LSB-first. Multibyte fields +are transmitted in little-endian order (i.e., LSB to MSB). + +SYNC field: All packets begin with a SYNC field (8 bits). + +Packet field format: Packets start with an SOP (Start Of Packet) delimiter +that is part of the SYNC field, and end with an EOP (End Of Packet). + +PID: A PID (packet identifier) follows the SYNC field of every packet. A PID +consists of a 4-bit packet type field, and a 4 bit check field. +The check field is the one's complement of the packet type field. + +Details: +https://en.wikipedia.org/wiki/USB +http://www.usb.org/developers/docs/ +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/usb_packet/pd.py b/libsigrokdecode4DSL/decoders/usb_packet/pd.py new file mode 100644 index 00000000..e262074e --- /dev/null +++ b/libsigrokdecode4DSL/decoders/usb_packet/pd.py @@ -0,0 +1,397 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2011 Gareth McMullin +## Copyright (C) 2012-2014 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd + +''' +OUTPUT_PYTHON format: + +Packet: +[, ] + +, : + - 'SYNC', + - 'PID', + - 'ADDR', + - 'EP', + - 'CRC5', + - 'CRC5 ERROR', + - 'CRC16', + - 'CRC16 ERROR', + - 'EOP', + - 'FRAMENUM', + - 'DATABYTE', + - 'HUBADDR', + - 'SC', + - 'PORT', + - 'S', + - 'E/U', + - 'ET', + - 'PACKET', [, , ] + +, , : + - 'TOKEN', 'OUT', [, , , , , ] + - 'TOKEN', 'IN', [, , , , , ] + - 'TOKEN', 'SOF', [, , , , ] + - 'TOKEN', 'SETUP', [, , , , , ] + - 'DATA', 'DATA0', [, , , , ] + - 'DATA', 'DATA1', [, , , , ] + - 'DATA', 'DATA2', [, , , , ] + - 'DATA', 'MDATA', [, , , , ] + - 'HANDSHAKE', 'ACK', [, , ] + - 'HANDSHAKE', 'NAK', [, , ] + - 'HANDSHAKE', 'STALL', [, , ] + - 'HANDSHAKE', 'NYET', [, , ] + - 'SPECIAL', 'PRE', [, , , , , ] + - 'SPECIAL', 'ERR', [, , ] + - 'SPECIAL', 'SPLIT', + [, , , , , , , , , ] + - 'SPECIAL', 'PING', [, , , , , ] + - 'SPECIAL', 'Reserved', None + +: SYNC field bitstring, normally '00000001' (8 chars). +: Packet ID bitstring, e.g. '11000011' for DATA0 (8 chars). +: Address field number, 0-127 (7 bits). +: Endpoint number, 0-15 (4 bits). +: CRC-5 number (5 bits). +: CRC-16 number (16 bits). +: End of packet marker. List of symbols, usually ['SE0', 'SE0', 'J']. +: USB (micro)frame number, 0-2047 (11 bits). +: A single data byte, e.g. 0x55. +: List of data bytes, e.g. [0x55, 0xaa, 0x99] (0 - 1024 bytes). +: TODO +: TODO +: TODO +: TODO +: TODO +: TODO +''' + +# Packet IDs (PIDs). +# The first 4 bits are the 'packet type' field, the last 4 bits are the +# 'check field' (each bit in the check field must be the inverse of the resp. +# bit in the 'packet type' field; if not, that's a 'PID error'). +# For the 4-bit strings, the left-most '1' or '0' is the LSB, i.e. it's sent +# to the bus first. +pids = { + # Tokens + '10000111': ['OUT', 'Address & EP number in host-to-function transaction'], + '10010110': ['IN', 'Address & EP number in function-to-host transaction'], + '10100101': ['SOF', 'Start-Of-Frame marker & frame number'], + '10110100': ['SETUP', 'Address & EP number in host-to-function transaction for SETUP to a control pipe'], + + # Data + # Note: DATA2 and MDATA are HS-only. + '11000011': ['DATA0', 'Data packet PID even'], + '11010010': ['DATA1', 'Data packet PID odd'], + '11100001': ['DATA2', 'Data packet PID HS, high bandwidth isosynchronous transaction in a microframe'], + '11110000': ['MDATA', 'Data packet PID HS for split and high-bandwidth isosynchronous transactions'], + + # Handshake + '01001011': ['ACK', 'Receiver accepts error-free packet'], + '01011010': ['NAK', 'Receiver cannot accept or transmitter cannot send'], + '01111000': ['STALL', 'EP halted or control pipe request unsupported'], + '01101001': ['NYET', 'No response yet from receiver'], + + # Special + '00111100': ['PRE', 'Host-issued preamble; enables downstream bus traffic to low-speed devices'], + #'00111100': ['ERR', 'Split transaction error handshake'], + '00011110': ['SPLIT', 'HS split transaction token'], + '00101101': ['PING', 'HS flow control probe for a bulk/control EP'], + '00001111': ['Reserved', 'Reserved PID'], +} + +def get_category(pidname): + if pidname in ('OUT', 'IN', 'SOF', 'SETUP'): + return 'TOKEN' + elif pidname in ('DATA0', 'DATA1', 'DATA2', 'MDATA'): + return 'DATA' + elif pidname in ('ACK', 'NAK', 'STALL', 'NYET'): + return 'HANDSHAKE' + else: + return 'SPECIAL' + +def ann_index(pidname): + l = ['OUT', 'IN', 'SOF', 'SETUP', 'DATA0', 'DATA1', 'DATA2', 'MDATA', + 'ACK', 'NAK', 'STALL', 'NYET', 'PRE', 'ERR', 'SPLIT', 'PING', + 'Reserved'] + if pidname not in l: + return 28 + return l.index(pidname) + 11 + +def bitstr_to_num(bitstr): + if not bitstr: + return 0 + l = list(bitstr) + l.reverse() + return int(''.join(l), 2) + +def reverse_number(num, count): + out = list(count * '0') + for i in range(0, count): + if num >> i & 1: + out[i] = '1' + return int(''.join(out), 2) + +def calc_crc5(bitstr): + poly5 = 0x25 + crc5 = 0x1f + for bit in bitstr: + crc5 <<= 1 + if int(bit) != (crc5 >> 5): + crc5 ^= poly5 + crc5 &= 0x1f + crc5 ^= 0x1f + return reverse_number(crc5, 5) + +def calc_crc16(bitstr): + poly16 = 0x18005 + crc16 = 0xffff + for bit in bitstr: + crc16 <<= 1 + if int(bit) != (crc16 >> 16): + crc16 ^= poly16 + crc16 &= 0xffff + crc16 ^= 0xffff + return reverse_number(crc16, 16) + +class Decoder(srd.Decoder): + api_version = 3 + id = 'usb_packet' + name = 'USB packet' + longname = 'Universal Serial Bus (LS/FS) packet' + desc = 'USB (low-speed and full-speed) packet protocol.' + license = 'gplv2+' + inputs = ['usb_signalling'] + outputs = ['usb_packet'] + tags = ['PC'] + options = ( + {'id': 'signalling', 'desc': 'Signalling', + 'default': 'full-speed', 'values': ('full-speed', 'low-speed')}, + ) + annotations = ( + ('sync-ok', 'SYNC'), + ('sync-err', 'SYNC (error)'), + ('pid', 'PID'), + ('framenum', 'FRAMENUM'), + ('addr', 'ADDR'), + ('ep', 'EP'), + ('crc5-ok', 'CRC5'), + ('crc5-err', 'CRC5 (error)'), + ('data', 'DATA'), + ('crc16-ok', 'CRC16'), + ('crc16-err', 'CRC16 (error)'), + ('packet-out', 'Packet: OUT'), + ('packet-in', 'Packet: IN'), + ('packet-sof', 'Packet: SOF'), + ('packet-setup', 'Packet: SETUP'), + ('packet-data0', 'Packet: DATA0'), + ('packet-data1', 'Packet: DATA1'), + ('packet-data2', 'Packet: DATA2'), + ('packet-mdata', 'Packet: MDATA'), + ('packet-ack', 'Packet: ACK'), + ('packet-nak', 'Packet: NAK'), + ('packet-stall', 'Packet: STALL'), + ('packet-nyet', 'Packet: NYET'), + ('packet-pre', 'Packet: PRE'), + ('packet-err', 'Packet: ERR'), + ('packet-split', 'Packet: SPLIT'), + ('packet-ping', 'Packet: PING'), + ('packet-reserved', 'Packet: Reserved'), + ('packet-invalid', 'Packet: Invalid'), + ) + annotation_rows = ( + ('fields', 'Packet fields', tuple(range(10 + 1))), + ('packet', 'Packets', tuple(range(11, 28 + 1))), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.bits = [] + self.packet = [] + self.packet_summary = '' + self.ss = self.es = None + self.ss_packet = self.es_packet = None + self.state = 'WAIT FOR SOP' + + def putpb(self, data): + self.put(self.ss, self.es, self.out_python, data) + + def putb(self, data): + self.put(self.ss, self.es, self.out_ann, data) + + def putpp(self, data): + self.put(self.ss_packet, self.es_packet, self.out_python, data) + + def putp(self, data): + self.put(self.ss_packet, self.es_packet, self.out_ann, data) + + def start(self): + self.out_python = self.register(srd.OUTPUT_PYTHON) + self.out_ann = self.register(srd.OUTPUT_ANN) + + def handle_packet(self): + packet = '' + for (bit, ss, es) in self.bits: + packet += bit + + if len(packet) < 8: + self.putp([28, ['Invalid packet (shorter than 8 bits)']]) + return + + # Bits[0:7]: SYNC + sync = packet[:7 + 1] + self.ss, self.es = self.bits[0][1], self.bits[7][2] + # The SYNC pattern for low-speed/full-speed is KJKJKJKK (00000001). + if sync != '00000001': + self.putpb(['SYNC ERROR', sync]) + self.putb([1, ['SYNC ERROR: %s' % sync, 'SYNC ERR: %s' % sync, + 'SYNC ERR', 'SE', 'S']]) + else: + self.putpb(['SYNC', sync]) + self.putb([0, ['SYNC: %s' % sync, 'SYNC', 'S']]) + self.packet.append(sync) + + if len(packet) < 16: + self.putp([28, ['Invalid packet (shorter than 16 bits)']]) + return + + # Bits[8:15]: PID + pid = packet[8:15 + 1] + pidname = pids.get(pid, ('UNKNOWN', 'Unknown PID'))[0] + self.ss, self.es = self.bits[8][1], self.bits[15][2] + self.putpb(['PID', pidname]) + self.putb([2, ['PID: %s' % pidname, pidname, pidname[0]]]) + self.packet.append(pid) + self.packet_summary += pidname + + if pidname in ('OUT', 'IN', 'SOF', 'SETUP', 'PING'): + if len(packet) < 32: + self.putp([28, ['Invalid packet (shorter than 32 bits)']]) + return + + if pidname == 'SOF': + # Bits[16:26]: Framenum + framenum = bitstr_to_num(packet[16:26 + 1]) + self.ss, self.es = self.bits[16][1], self.bits[26][2] + self.putpb(['FRAMENUM', framenum]) + self.putb([3, ['Frame: %d' % framenum, 'Frame', 'Fr', 'F']]) + self.packet.append(framenum) + self.packet_summary += ' %d' % framenum + else: + # Bits[16:22]: Addr + addr = bitstr_to_num(packet[16:22 + 1]) + self.ss, self.es = self.bits[16][1], self.bits[22][2] + self.putpb(['ADDR', addr]) + self.putb([4, ['Address: %d' % addr, 'Addr: %d' % addr, + 'Addr', 'A']]) + self.packet.append(addr) + self.packet_summary += ' ADDR %d' % addr + + # Bits[23:26]: EP + ep = bitstr_to_num(packet[23:26 + 1]) + self.ss, self.es = self.bits[23][1], self.bits[26][2] + self.putpb(['EP', ep]) + self.putb([5, ['Endpoint: %d' % ep, 'EP: %d' % ep, 'EP', 'E']]) + self.packet.append(ep) + self.packet_summary += ' EP %d' % ep + + # Bits[27:31]: CRC5 + crc5 = bitstr_to_num(packet[27:31 + 1]) + crc5_calc = calc_crc5(packet[16:27]) + self.ss, self.es = self.bits[27][1], self.bits[31][2] + if crc5 == crc5_calc: + self.putpb(['CRC5', crc5]) + self.putb([6, ['CRC5: 0x%02X' % crc5, 'CRC5', 'C']]) + else: + self.putpb(['CRC5 ERROR', crc5]) + self.putb([7, ['CRC5 ERROR: 0x%02X' % crc5, 'CRC5 ERR', 'CE', 'C']]) + self.packet.append(crc5) + elif pidname in ('DATA0', 'DATA1', 'DATA2', 'MDATA'): + # Bits[16:packetlen-16]: Data + data = packet[16:-16] + # TODO: len(data) must be a multiple of 8. + databytes = [] + self.packet_summary += ' [' + for i in range(0, len(data), 8): + db = bitstr_to_num(data[i:i + 8]) + self.ss, self.es = self.bits[16 + i][1], self.bits[23 + i][2] + self.putpb(['DATABYTE', db]) + self.putb([8, ['Databyte: %02X' % db, 'Data: %02X' % db, + 'DB: %02X' % db, '%02X' % db]]) + databytes.append(db) + self.packet_summary += ' %02X' % db + self.packet_summary += ' ]' + + # Convenience Python output (no annotation) for all bytes together. + self.ss, self.es = self.bits[16][1], self.bits[-16][2] + self.putpb(['DATABYTES', databytes]) + self.packet.append(databytes) + + # Bits[packetlen-16:packetlen]: CRC16 + crc16 = bitstr_to_num(packet[-16:]) + crc16_calc = calc_crc16(packet[16:-16]) + self.ss, self.es = self.bits[-16][1], self.bits[-1][2] + if crc16 == crc16_calc: + self.putpb(['CRC16', crc16]) + self.putb([9, ['CRC16: 0x%04X' % crc16, 'CRC16', 'C']]) + else: + self.putpb(['CRC16 ERROR', crc16]) + self.putb([10, ['CRC16 ERROR: 0x%04X' % crc16, 'CRC16 ERR', 'CE', 'C']]) + self.packet.append(crc16) + elif pidname in ('ACK', 'NAK', 'STALL', 'NYET', 'ERR'): + pass # Nothing to do, these only have SYNC+PID+EOP fields. + elif pidname in ('PRE'): + pass # Nothing to do, PRE only has SYNC+PID fields. + else: + pass # TODO: Handle 'SPLIT' and possibly 'Reserved' packets. + + # Output a (summary of) the whole packet. + pcategory, pname, pinfo = get_category(pidname), pidname, self.packet + self.putpp(['PACKET', [pcategory, pname, pinfo]]) + self.putp([ann_index(pidname), ['%s' % self.packet_summary]]) + + self.packet, self.packet_summary = [], '' + + def decode(self, ss, es, data): + (ptype, pdata) = data + + # We only care about certain packet types for now. + if ptype not in ('SOP', 'BIT', 'EOP', 'ERR'): + return + + # State machine. + if self.state == 'WAIT FOR SOP': + if ptype != 'SOP': + return + self.ss_packet = ss + self.state = 'GET BIT' + elif self.state == 'GET BIT': + if ptype == 'BIT': + self.bits.append([pdata, ss, es]) + elif ptype == 'EOP' or ptype == 'ERR': + self.es_packet = es + self.handle_packet() + self.packet, self.packet_summary = [], '' + self.bits, self.state = [], 'WAIT FOR SOP' + else: + pass # TODO: Error diff --git a/libsigrokdecode4DSL/decoders/usb_power_delivery/__init__.py b/libsigrokdecode4DSL/decoders/usb_power_delivery/__init__.py new file mode 100644 index 00000000..43dfd5d6 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/usb_power_delivery/__init__.py @@ -0,0 +1,24 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2015 Google, Inc +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +USB Power Delivery - baseband protocol decoder / checker. +''' + +from .pd import * diff --git a/libsigrokdecode4DSL/decoders/usb_power_delivery/pd.py b/libsigrokdecode4DSL/decoders/usb_power_delivery/pd.py new file mode 100644 index 00000000..45077f27 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/usb_power_delivery/pd.py @@ -0,0 +1,639 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2015 Google, Inc +## Copyright (C) 2018 davidanger +## Copyright (C) 2018 Peter Hazenberg +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd +import struct +import zlib # for crc32 + +# BMC encoding with a 600kHz datarate +UI_US = 1000000/600000.0 + +# Threshold to discriminate half-1 from 0 in Binary Mark Conding +THRESHOLD_US = (UI_US + 2 * UI_US) / 2 + +# Control Message type +CTRL_TYPES = { + 0: 'reserved', + 1: 'GOOD CRC', + 2: 'GOTO MIN', + 3: 'ACCEPT', + 4: 'REJECT', + 5: 'PING', + 6: 'PS RDY', + 7: 'GET SOURCE CAP', + 8: 'GET SINK CAP', + 9: 'DR SWAP', + 10: 'PR SWAP', + 11: 'VCONN SWAP', + 12: 'WAIT', + 13: 'SOFT RESET', + 14: 'reserved', + 15: 'reserved', + 16: 'Not Supported', + 17: 'Get_Source_Cap_Extended', + 18: 'Get_Status', + 19: 'FR_Swap', + 20: 'Get_PPS_Status', + 21: 'Get_Country_Codes', +} + +# Data message type +DATA_TYPES = { + 1: 'SOURCE CAP', + 2: 'REQUEST', + 3: 'BIST', + 4: 'SINK CAP', + 5: 'Battery_Status', + 6: 'Alert', + 7: 'Get_Country_Info', + 15: 'VDM' +} + +# 4b5b encoding of the symbols +DEC4B5B = [ + 0x10, # Error 00000 + 0x10, # Error 00001 + 0x10, # Error 00010 + 0x10, # Error 00011 + 0x10, # Error 00100 + 0x10, # Error 00101 + 0x13, # Sync-3 00110 + 0x14, # RST-1 00111 + 0x10, # Error 01000 + 0x01, # 1 = 0001 01001 + 0x04, # 4 = 0100 01010 + 0x05, # 5 = 0101 01011 + 0x10, # Error 01100 + 0x16, # EOP 01101 + 0x06, # 6 = 0110 01110 + 0x07, # 7 = 0111 01111 + 0x10, # Error 10000 + 0x12, # Sync-2 10001 + 0x08, # 8 = 1000 10010 + 0x09, # 9 = 1001 10011 + 0x02, # 2 = 0010 10100 + 0x03, # 3 = 0011 10101 + 0x0A, # A = 1010 10110 + 0x0B, # B = 1011 10111 + 0x11, # Sync-1 11000 + 0x15, # RST-2 11001 + 0x0C, # C = 1100 11010 + 0x0D, # D = 1101 11011 + 0x0E, # E = 1110 11100 + 0x0F, # F = 1111 11101 + 0x00, # 0 = 0000 11110 + 0x10, # Error 11111 +] +SYM_ERR = 0x10 +SYNC1 = 0x11 +SYNC2 = 0x12 +SYNC3 = 0x13 +RST1 = 0x14 +RST2 = 0x15 +EOP = 0x16 +SYNC_CODES = [SYNC1, SYNC2, SYNC3] +HRST_CODES = [RST1, RST1, RST1, RST2] + +SOP_SEQUENCES = [ + (SYNC1, SYNC1, SYNC1, SYNC2), + (SYNC1, SYNC1, SYNC3, SYNC3), + (SYNC1, SYNC3, SYNC1, SYNC3), + (SYNC1, RST2, RST2, SYNC3), + (SYNC1, RST2, SYNC3, SYNC2), + (RST1, SYNC1, RST1, SYNC3), + (RST1, RST1, RST1, RST2), +] +START_OF_PACKETS = { + SOP_SEQUENCES[0]: 'SOP', + SOP_SEQUENCES[1]: "SOP'", + SOP_SEQUENCES[2]: 'SOP"', + SOP_SEQUENCES[3]: "SOP' Debug", + SOP_SEQUENCES[4]: 'SOP" Debug', + SOP_SEQUENCES[5]: 'Cable Reset', + SOP_SEQUENCES[6]: 'Hard Reset', +} + +SYM_NAME = [ + ['0x0', '0'], + ['0x1', '1'], + ['0x2', '2'], + ['0x3', '3'], + ['0x4', '4'], + ['0x5', '5'], + ['0x6', '6'], + ['0x7', '7'], + ['0x8', '8'], + ['0x9', '9'], + ['0xA', 'A'], + ['0xB', 'B'], + ['0xC', 'C'], + ['0xD', 'D'], + ['0xE', 'E'], + ['0xF', 'F'], + ['ERROR', 'X'], + ['SYNC-1', 'S1'], + ['SYNC-2', 'S2'], + ['SYNC-3', 'S3'], + ['RST-1', 'R1'], + ['RST-2', 'R2'], + ['EOP', '#'], +] + +RDO_FLAGS = { + (1 << 23): 'unchunked', + (1 << 24): 'no_suspend', + (1 << 25): 'comm_cap', + (1 << 26): 'cap_mismatch', + (1 << 27): 'give_back' +} + +BIST_MODES = { + 0: 'Receiver', + 1: 'Transmit', + 2: 'Counters', + 3: 'Carrier 0', + 4: 'Carrier 1', + 5: 'Carrier 2', + 6: 'Carrier 3', + 7: 'Eye', +} + +VDM_CMDS = { + 1: 'Disc Ident', + 2: 'Disc SVID', + 3: 'Disc Mode', + 4: 'Enter Mode', + 5: 'Exit Mode', + 6: 'Attention', + # 16..31: SVID Specific Commands + # DisplayPort Commands + 16: 'DP Status', + 17: 'DP Configure', +} +VDM_ACK = ['REQ', 'ACK', 'NAK', 'BSY'] + + +class SamplerateError(Exception): + pass + +class Decoder(srd.Decoder): + api_version = 3 + id = 'usb_power_delivery' + name = 'USB PD' + longname = 'USB Power Delivery' + desc = 'USB Power Delivery protocol.' + license = 'gplv2+' + inputs = ['logic'] + outputs = ['usb_pd'] + tags = ['PC'] + channels = ( + {'id': 'cc1', 'name': 'CC1', 'desc': 'Configuration Channel 1'}, + ) + optional_channels = ( + {'id': 'cc2', 'name': 'CC2', 'desc': 'Configuration Channel 2'}, + ) + options = ( + {'id': 'fulltext', 'desc': 'Full text decoding of packets', + 'default': 'no', 'values': ('yes', 'no')}, + ) + annotations = ( + ('type', 'Packet Type'), + ('preamble', 'Preamble'), + ('sop', 'Start of Packet'), + ('header', 'Header'), + ('data', 'Data'), + ('crc', 'Checksum'), + ('eop', 'End Of Packet'), + ('sym', '4b5b symbols'), + ('warnings', 'Warnings'), + ('src', 'Source Message'), + ('snk', 'Sink Message'), + ('payload', 'Payload'), + ('text', 'Plain text'), + ) + annotation_rows = ( + ('4b5b', 'Symbols', (7,)), + ('phase', 'Parts', (1, 2, 3, 4, 5, 6)), + ('payload', 'Payload', (11,)), + ('type', 'Type', (0, 9, 10)), + ('warnings', 'Warnings', (8,)), + ('text', 'Full text', (12,)), + ) + binary = ( + ('raw-data', 'RAW binary data'), + ) + + stored_pdos = {} + + def get_request(self, rdo): + pos = (rdo >> 28) & 7 + + op_ma = ((rdo >> 10) & 0x3ff) * 0.01 + max_ma = (rdo & 0x3ff) * 0.01 + + mark = self.cap_mark[pos] + if mark == 3: + op_v = ((rdo >> 9) & 0x7ff) * 0.02 + op_a = (rdo & 0x3f) * 0.05 + t_settings = '%gV %gA' % (op_v, op_a) + elif mark == 2: + op_w = ((rdo >> 10) & 0x3ff) * 0.25 + mp_w = (rdo & 0x3ff) * 0.25 + t_settings = '%gW (operating)' % op_w + else: + op_a = ((rdo >> 10) & 0x3ff) * 0.01 + max_a = (rdo & 0x3ff) * 0.01 + t_settings = '%gA (operating) / %gA (max)' % (op_a, max_a) + + t_flags = '' + for f in sorted(RDO_FLAGS.keys(), reverse = True): + if rdo & f: + t_flags += ' [' + RDO_FLAGS[f] + ']' + + if pos in self.stored_pdos.keys(): + t_pdo = '#%d: %s' % (pos, self.stored_pdos[pos]) + else: + t_pdo = '#%d' % (pos) + + return '(PDO %s) %s%s' % (t_pdo, t_settings, t_flags) + + def get_source_sink_cap(self, pdo, idx, source): + t1 = (pdo >> 30) & 3 + self.cap_mark[idx] = t1 + + flags = {} + if t1 == 0: + t_name = 'Fixed' + if source: + flags = { + (1 << 29): 'dual_role_power', + (1 << 28): 'suspend', + (1 << 27): 'unconstrained', + (1 << 26): 'comm_cap', + (1 << 25): 'dual_role_data', + (1 << 24): 'unchunked', + } + else: # Sink + flags = { + (1 << 29): 'dual_role_power', + (1 << 28): 'high_capability', + (1 << 27): 'unconstrained', + (1 << 26): 'comm_cap', + (1 << 25): 'dual_role_data', + (0b01 << 23): 'fr_swap default power', + (0b10 << 23): 'fr_swap 1.5 A', + (0b11 << 23): 'fr_swap 3.0 A', + } + mv = ((pdo >> 10) & 0x3ff) * 0.05 + ma = ((pdo >> 0) & 0x3ff) * 0.01 + p = '%gV %gA (%gW)' % (mv, ma, mv*ma) + self.stored_pdos[idx] = '%s %gV' % (t_name, mv) + elif t1 == 1: + t_name = 'Battery' + flags = {} # No flags defined for Battery PDO in PD 3.0 spec + minv = ((pdo >> 10) & 0x3ff) * 0.05 + maxv = ((pdo >> 20) & 0x3ff) * 0.05 + mw = ((pdo >> 0) & 0x3ff) * 0.25 + p = '%g/%gV %gW' % (minv, maxv, mw) + self.stored_pdos[idx] = '%s %g/%gV' % (t_name, minv, maxv) + elif t1 == 2: + t_name = 'Variable' + flags = {} # No flags defined for Variable PDO in PD 3.0 spec + minv = ((pdo >> 10) & 0x3ff) * 0.05 + maxv = ((pdo >> 20) & 0x3ff) * 0.05 + ma = ((pdo >> 0) & 0x3ff) * 0.01 + p = '%g/%gV %gA' % (minv, maxv, ma) + self.stored_pdos[idx] = '%s %g/%gV' % (t_name, minv, maxv) + elif t1 == 3: + t2 = (pdo >> 28) & 3 + if t2 == 0: + t_name = 'Programmable|PPS' + flags = { + (1 << 29): 'power_limited', + } + minv = ((pdo >> 8) & 0xff) * 0.1 + maxv = ((pdo >> 17) & 0xff) * 0.1 + ma = ((pdo >> 0) & 0xff) * 0.05 + p = '%g/%gV %gA' % (minv, maxv, ma) + if (pdo >> 27) & 0x1: + p += ' [limited]' + self.stored_pdos[idx] = '%s %g/%gV' % (t_name, minv, maxv) + else: + t_name = 'Reserved APDO: '+bin(t2) + p = '[raw: %s]' % (bin(pdo)) + self.stored_pdos[idx] = '%s %s' % (t_name, p) + t_flags = '' + for f in sorted(flags.keys(), reverse = True): + if pdo & f: + t_flags += ' [' + flags[f] + ']' + return '[%s] %s%s' % (t_name, p, t_flags) + + def get_vdm(self, idx, data): + if idx == 0: # VDM header + vid = data >> 16 + struct = data & (1 << 15) + txt = 'VDM' + if struct: # Structured VDM + cmd = data & 0x1f + src = data & (1 << 5) + ack = (data >> 6) & 3 + pos = (data >> 8) & 7 + ver = (data >> 13) & 3 + txt = VDM_ACK[ack] + ' ' + txt += VDM_CMDS[cmd] if cmd in VDM_CMDS else 'cmd?' + txt += ' pos %d' % (pos) if pos else ' ' + else: # Unstructured VDM + txt = 'unstruct [%04x]' % (data & 0x7fff) + txt += ' SVID:%04x' % (vid) + else: # VDM payload + txt = 'VDO:%08x' % (data) + return txt + + def get_bist(self, idx, data): + mode = data >> 28 + counter = data & 0xffff + mode_name = BIST_MODES[mode] if mode in BIST_MODES else 'INVALID' + if mode == 2: + mode_name = 'Counter[= %d]' % (counter) + # TODO: Check all 0 bits are 0 / emit warnings. + return 'mode %s' % (mode_name) if idx == 0 else 'invalid BRO' + + def putpayload(self, s0, s1, idx): + t = self.head_type() + txt = '['+str(idx+1)+'] ' + if t == 2: + txt += self.get_request(self.data[idx]) + elif t == 1 or t == 4: + txt += self.get_source_sink_cap(self.data[idx], idx+1, t==1) + elif t == 15: + txt += self.get_vdm(idx, self.data[idx]) + elif t == 3: + txt += self.get_bist(idx, self.data[idx]) + self.putx(s0, s1, [11, [txt, txt]]) + self.text += ' - ' + txt + + def puthead(self): + ann_type = 9 if self.head_power_role() else 10 + role = 'SRC' if self.head_power_role() else 'SNK' + if self.head_data_role() != self.head_power_role(): + role += '/DFP' if self.head_data_role() else '/UFP' + t = self.head_type() + if self.head_count() == 0: + shortm = CTRL_TYPES[t] + else: + shortm = DATA_TYPES[t] if t in DATA_TYPES else 'DAT???' + + longm = '(r{:d}) {:s}[{:d}]: {:s}'.format(self.head_rev(), role, self.head_id(), shortm) + self.putx(0, -1, [ann_type, [longm, shortm]]) + self.text += longm + + def head_id(self): + return (self.head >> 9) & 7 + + def head_power_role(self): + return (self.head >> 8) & 1 + + def head_data_role(self): + return (self.head >> 5) & 1 + + def head_rev(self): + return ((self.head >> 6) & 3) + 1 + + def head_type(self): + return self.head & 0xF + + def head_count(self): + return (self.head >> 12) & 7 + + def putx(self, s0, s1, data): + self.put(self.edges[s0], self.edges[s1], self.out_ann, data) + + def putwarn(self, longm, shortm): + self.putx(0, -1, [8, [longm, shortm]]) + + def compute_crc32(self): + bdata = struct.pack('= 3: + return START_OF_PACKETS[seq] + return None + + def scan_eop(self): + for i in range(len(self.bits) - 19): + k = (self.get_sym(i, rec=False), self.get_sym(i+5, rec=False), + self.get_sym(i+10, rec=False), self.get_sym(i+15, rec=False)) + sym = START_OF_PACKETS.get(k, None) + if not sym: + sym = self.find_corrupted_sop(k) + # We have an interesting symbol sequence. + if sym: + # Annotate the preamble. + self.putx(0, i, [1, ['Preamble', '...']]) + # Annotate each symbol. + self.rec_sym(i, k[0]) + self.rec_sym(i+5, k[1]) + self.rec_sym(i+10, k[2]) + self.rec_sym(i+15, k[3]) + if sym == 'Hard Reset': + self.text += 'HRST' + return -1 # Hard reset + elif sym == 'Cable Reset': + self.text += 'CRST' + return -1 # Cable reset + else: + self.putx(i, i+20, [2, [sym, 'S']]) + return i+20 + self.putx(0, len(self.bits), [1, ['Junk???', 'XXX']]) + self.text += 'Junk???' + self.putwarn('No start of packet found', 'XXX') + return -1 # No Start Of Packet + + def __init__(self): + self.reset() + + def reset(self): + self.samplerate = None + self.idx = 0 + self.packet_seq = 0 + self.previous = 0 + self.startsample = None + self.bits = [] + self.edges = [] + self.bad = [] + self.half_one = False + self.start_one = 0 + self.stored_pdos = {} + self.cap_mark = [0, 0, 0, 0, 0, 0, 0, 0] + + def metadata(self, key, value): + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + # 0 is 2 UI, space larger than 1.5x 0 is definitely wrong. + self.maxbit = self.us2samples(3 * UI_US) + # Duration threshold between half 1 and 0. + self.threshold = self.us2samples(THRESHOLD_US) + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + self.out_binary = self.register(srd.OUTPUT_BINARY) + self.out_bitrate = self.register( + srd.OUTPUT_META, + meta=(int, 'Bitrate', 'Bitrate during the packet') + ) + + def us2samples(self, us): + return int(us * self.samplerate / 1000000) + + def decode_packet(self): + self.data = [] + self.idx = 0 + self.text = '' + + if len(self.edges) < 50: + return # Not a real PD packet + + self.packet_seq += 1 + tstamp = float(self.startsample) / self.samplerate + self.text += '#%-4d (%8.6fms): ' % (self.packet_seq, tstamp*1000) + + self.idx = self.scan_eop() + if self.idx < 0: + # Full text trace of the issue. + self.putx(0, self.idx, [12, [self.text, '...']]) + return # No real packet: ABORT. + + # Packet header + self.head = self.get_short() + self.putx(self.idx-20, self.idx, [3, ['H:%04x' % (self.head), 'HD']]) + self.puthead() + + # Decode data payload + for i in range(self.head_count()): + self.data.append(self.get_word()) + self.putx(self.idx-40, self.idx, + [4, ['[%d]%08x' % (i, self.data[i]), 'D%d' % (i)]]) + self.putpayload(self.idx-40, self.idx, i) + + # CRC check + self.crc = self.get_word() + ccrc = self.compute_crc32() + if self.crc != ccrc: + self.putwarn('Bad CRC %08x != %08x' % (self.crc, ccrc), 'CRC!') + self.putx(self.idx-40, self.idx, [5, ['CRC:%08x' % (self.crc), 'CRC']]) + + # End of Packet + if len(self.bits) >= self.idx + 5 and self.get_sym(self.idx) == EOP: + self.putx(self.idx, self.idx + 5, [6, ['EOP', 'E']]) + self.idx += 5 + else: + self.putwarn('No EOP', 'EOP!') + # Full text trace + if self.options['fulltext'] == 'yes': + self.putx(0, self.idx, [12, [self.text, '...']]) + + # Meta data for bitrate + ss, es = self.edges[0], self.edges[-1] + bitrate = self.samplerate*len(self.bits) / float(es - ss) + self.put(es, ss, self.out_bitrate, int(bitrate)) + # Raw binary data (BMC decoded) + self.put(es, ss, self.out_binary, [0, bytes(self.bits)]) + + def decode(self): + if not self.samplerate: + raise SamplerateError('Cannot decode without samplerate.') + while True: + self.wait([{0: 'e'}, {1: 'e'}, {'skip': int(self.samplerate/1e3)}]) + + # First sample of the packet, just record the start date. + if not self.startsample: + self.startsample = self.samplenum + self.previous = self.samplenum + continue + + diff = self.samplenum - self.previous + + # Large idle: use it as the end of packet. + if diff > self.maxbit: + # The last edge of the packet. + self.edges.append(self.previous) + # Export the packet. + self.decode_packet() + # Reset for next packet. + self.startsample = self.samplenum + self.bits = [] + self.edges = [] + self.bad = [] + self.half_one = False + self.start_one = 0 + else: # Add the bit to the packet. + is_zero = diff > self.threshold + if is_zero and not self.half_one: + self.bits.append(0) + self.edges.append(self.previous) + elif not is_zero and self.half_one: + self.bits.append(1) + self.edges.append(self.start_one) + self.half_one = False + elif not is_zero and not self.half_one: + self.half_one = True + self.start_one = self.previous + else: # Invalid BMC sequence + self.bad.append((self.start_one, self.previous)) + # TODO: Try to recover. + self.bits.append(0) + self.edges.append(self.previous) + self.half_one = False + self.previous = self.samplenum diff --git a/libsigrokdecode4DSL/decoders/usb_request/__init__.py b/libsigrokdecode4DSL/decoders/usb_request/__init__.py new file mode 100644 index 00000000..66723dc2 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/usb_request/__init__.py @@ -0,0 +1,49 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2015 Stefan Brüns +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This decoder stacks on top of the 'usb_packet' PD and decodes the USB +(low-speed and full-speed) transactions. + +Transactions and requests are tracked per device address and endpoint. + +Tracking of CONTROL requests is quite accurate, as these always start with +a SETUP token and are completed by an IN or OUT transaction, the status +packet. All transactions during the DATA stage are combined. + +For BULK and INTERRUPT requests, each transaction starts with an IN or OUT +request, and is considered completed after the first transaction containing +data has been ACKed. Normally a request is only completed after a short or +zero length packet, but this would require knowledge about the max packet +size of an endpoint. + +All INTERRUPT requests are treated as BULK requests, as on the link layer +both are identical. + +The PCAP binary output contains 'SUBMIT' and 'COMPLETE' records. For +CONTROL request, the SUBMIT contains the SETUP request, the data is +either contained in the SUBMIT (Host-to-Device) or the COMPLETE +(Device-to-Host) record. + +Details: +https://en.wikipedia.org/wiki/USB +http://www.usb.org/developers/docs/ +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/usb_request/pd.py b/libsigrokdecode4DSL/decoders/usb_request/pd.py new file mode 100644 index 00000000..49b0b350 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/usb_request/pd.py @@ -0,0 +1,371 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2015 Stefan Brüns +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd +import struct + +class SamplerateError(Exception): + pass + +class pcap_usb_pkt(): + # Linux usbmon format, see Documentation/usb/usbmon.txt + h = b'\x00\x00\x00\x00' # ID part 1 + h += b'\x00\x00\x00\x00' # ID part 2 + h += b'C' # 'S'ubmit / 'C'omplete / 'E'rror + h += b'\x03' # ISO (0), Intr, Control, Bulk (3) + h += b'\x00' # Endpoint + h += b'\x00' # Device address + h += b'\x00\x00' # Bus number + h += b'-' # Setup tag - 0: Setup present, '-' otherwise + h += b'<' # Data tag - '<' no data, 0 otherwise + # Timestamp + h += b'\x00\x00\x00\x00' # TS seconds part 1 + h += b'\x00\x00\x00\x00' # TS seconds part 2 + h += b'\x00\x00\x00\x00' # TS useconds + # + h += b'\x00\x00\x00\x00' # Status 0: OK + h += b'\x00\x00\x00\x00' # URB length + h += b'\x00\x00\x00\x00' # Data length + # Setup packet data, valid if setup tag == 0 + h += b'\x00' # bmRequestType + h += b'\x00' # bRequest + h += b'\x00\x00' # wValue + h += b'\x00\x00' # wIndex + h += b'\x00\x00' # wLength + # + h += b'\x00\x00\x00\x00' # ISO/interrupt interval + h += b'\x00\x00\x00\x00' # ISO start frame + h += b'\x00\x00\x00\x00' # URB flags + h += b'\x00\x00\x00\x00' # Number of ISO descriptors + + def __init__(self, req, ts, is_submit): + self.header = bytearray(pcap_usb_pkt.h) + self.data = b'' + self.set_urbid(req['id']) + self.set_urbtype('S' if is_submit else 'C') + self.set_timestamp(ts) + self.set_addr_ep(req['addr'], req['ep']) + if req['type'] in ('SETUP IN', 'SETUP OUT'): + self.set_transfertype(2) # Control + self.set_setup(req['setup_data']) + if req['type'] in ('BULK IN'): + self.set_addr_ep(req['addr'], 0x80 | req['ep']) + self.set_data(req['data']) + + def set_urbid(self, urbid): + self.header[4:8] = struct.pack('>I', urbid) + + def set_urbtype(self, urbtype): + self.header[8] = ord(urbtype) + + def set_transfertype(self, transfertype): + self.header[9] = transfertype + + def set_addr_ep(self, addr, ep): + self.header[11] = addr + self.header[10] = ep + + def set_timestamp(self, ts): + self.timestamp = ts + self.header[20:24] = struct.pack('>I', ts[0]) # seconds + self.header[24:28] = struct.pack('>I', ts[1]) # microseconds + + def set_data(self, data): + self.data = data + self.header[15] = 0 + self.header[36:40] = struct.pack('>I', len(data)) + + def set_setup(self, data): + self.header[14] = 0 + self.header[40:48] = data + + def packet(self): + return bytes(self.header) + bytes(self.data) + + def record_header(self): + # See https://wiki.wireshark.org/Development/LibpcapFileFormat. + (secs, usecs) = self.timestamp + h = struct.pack('>I', secs) # TS seconds + h += struct.pack('>I', usecs) # TS microseconds + # No truncation, so both lengths are the same. + h += struct.pack('>I', len(self)) # Captured len (usb hdr + data) + h += struct.pack('>I', len(self)) # Original len + return h + + def __len__(self): + return 64 + len(self.data) + +class Decoder(srd.Decoder): + api_version = 3 + id = 'usb_request' + name = 'USB request' + longname = 'Universal Serial Bus (LS/FS) transaction/request' + desc = 'USB (low-speed/full-speed) transaction/request protocol.' + license = 'gplv2+' + inputs = ['usb_packet'] + outputs = ['usb_request'] + tags = ['PC'] + annotations = ( + ('request-setup-read', 'Setup: Device-to-host'), + ('request-setup-write', 'Setup: Host-to-device'), + ('request-bulk-read', 'Bulk: Device-to-host'), + ('request-bulk-write', 'Bulk: Host-to-device'), + ('errors', 'Unexpected packets'), + ) + annotation_rows = ( + ('request', 'USB requests', tuple(range(4))), + ('errors', 'Errors', (4,)), + ) + binary = ( + ('pcap', 'PCAP format'), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.samplerate = None + self.request = {} + self.request_id = 0 + self.transaction_state = 'IDLE' + self.ss_transaction = None + self.es_transaction = None + self.transaction_ep = None + self.transaction_addr = None + self.wrote_pcap_header = False + + def putr(self, ss, es, data): + self.put(ss, es, self.out_ann, data) + + def putb(self, ts, data): + self.put(ts, ts, self.out_binary, data) + + def pcap_global_header(self): + # See https://wiki.wireshark.org/Development/LibpcapFileFormat. + h = b'\xa1\xb2\xc3\xd4' # Magic, indicate microsecond ts resolution + h += b'\x00\x02' # Major version 2 + h += b'\x00\x04' # Minor version 4 + h += b'\x00\x00\x00\x00' # Correction vs. UTC, seconds + h += b'\x00\x00\x00\x00' # Timestamp accuracy + h += b'\xff\xff\xff\xff' # Max packet len + # LINKTYPE_USB_LINUX_MMAPPED 220 + # Linux usbmon format, see Documentation/usb/usbmon.txt. + h += b'\x00\x00\x00\xdc' # Link layer + return h + + def metadata(self, key, value): + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + if self.samplerate: + self.secs_per_sample = float(1) / float(self.samplerate) + + def start(self): + self.out_binary = self.register(srd.OUTPUT_BINARY) + self.out_ann = self.register(srd.OUTPUT_ANN) + + def handle_transfer(self): + request_started = 0 + request_end = self.handshake in ('ACK', 'STALL', 'timeout') + ep = self.transaction_ep + addr = self.transaction_addr + + # Handle protocol STALLs, condition lasts until next SETUP transfer (8.5.3.4) + if self.transaction_type == 'SETUP' and (addr, ep) in self.request: + request = self.request[(addr,ep)] + if request['type'] in ('SETUP IN', 'SETUP OUT'): + request['es'] = self.ss_transaction + self.handle_request(0, 1) + + if not (addr, ep) in self.request: + self.request[(addr, ep)] = {'setup_data': [], 'data': [], + 'type': None, 'ss': self.ss_transaction, 'es': None, + 'id': self.request_id, 'addr': addr, 'ep': ep} + self.request_id += 1 + request_started = 1 + request = self.request[(addr,ep)] + + if request_end: + request['es'] = self.es_transaction + request['handshake'] = self.handshake + + # BULK or INTERRUPT transfer + if request['type'] in (None, 'BULK IN') and self.transaction_type == 'IN': + request['type'] = 'BULK IN' + request['data'] += self.transaction_data + self.handle_request(request_started, request_end) + elif request['type'] in (None, 'BULK OUT') and self.transaction_type == 'OUT': + request['type'] = 'BULK OUT' + request['data'] += self.transaction_data + self.handle_request(request_started, request_end) + + # CONTROL, SETUP stage + elif request['type'] is None and self.transaction_type == 'SETUP': + request['setup_data'] = self.transaction_data + request['wLength'] = struct.unpack(' transaction_timeout: + self.es_transaction = transaction_timeout + self.handshake = 'timeout' + self.handle_transfer() + self.transaction_state = 'IDLE' + + if self.transaction_state != 'IDLE': + self.putr(ss, es, [4, ['ERR: received %s token in state %s' % + (pname, self.transaction_state)]]) + return + + sync, pid, addr, ep, crc5 = pinfo + self.transaction_data = [] + self.ss_transaction = ss + self.es_transaction = es + self.transaction_state = 'TOKEN RECEIVED' + self.transaction_ep = ep + self.transaction_addr = addr + self.transaction_type = pname # IN OUT SETUP + + elif pcategory == 'DATA': + if self.transaction_state != 'TOKEN RECEIVED': + self.putr(ss, es, [4, ['ERR: received %s token in state %s' % + (pname, self.transaction_state)]]) + return + + self.transaction_data = pinfo[2] + self.transaction_state = 'DATA RECEIVED' + + elif pcategory == 'HANDSHAKE': + if self.transaction_state not in ('TOKEN RECEIVED', 'DATA RECEIVED'): + self.putr(ss, es, [4, ['ERR: received %s token in state %s' % + (pname, self.transaction_state)]]) + return + + self.handshake = pname + self.transaction_state = 'IDLE' + self.es_transaction = es + self.handle_transfer() + + elif pname == 'PRE': + return + + else: + self.putr(ss, es, [4, ['ERR: received unhandled %s token in state %s' % + (pname, self.transaction_state)]]) + return diff --git a/libsigrokdecode4DSL/decoders/usb_signalling/__init__.py b/libsigrokdecode4DSL/decoders/usb_signalling/__init__.py new file mode 100644 index 00000000..eae18870 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/usb_signalling/__init__.py @@ -0,0 +1,50 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2012 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This PD decodes the USB (low-speed and full-speed) signalling protocol. + +Electrical/signalling layer (USB spec, chapter 7): + +USB signalling consists of two signal lines, both driven at 3.3V +logic levels. The signals are DP (D+) and DM (D-), and normally operate in +differential mode. + +Low-speed: The state where DP=1,DM=0 is K, the state DP=0,DM=1 is J. +Full-speed: The state where DP=1,DM=0 is J, the state DP=0,DM=1 is K. + +A state SE0 is defined where DP=DM=0. This common mode signal is used to +signal a reset or end of packet (EOP). A state SE1 is defined where DP=DM=1. + +Data transmitted on the USB is encoded with NRZI. A transition from J to K +or vice-versa indicates a logic 0, while no transition indicates a logic 1. +If 6 ones are transmitted consecutively, a zero is inserted to force a +transition. This is known as bit stuffing. + +Data is transferred at a rate of 1.5Mbit/s (low-speed) / 12Mbit/s (full-speed). + +The SE0 transmitted to signal an end-of-packet is two bit intervals long +(low-speed: 1.25uS - 1.50uS, full-speed: 160ns - 175ns). + +Details: +https://en.wikipedia.org/wiki/USB +http://www.usb.org/developers/docs/ +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/usb_signalling/pd.py b/libsigrokdecode4DSL/decoders/usb_signalling/pd.py new file mode 100644 index 00000000..65a2b35c --- /dev/null +++ b/libsigrokdecode4DSL/decoders/usb_signalling/pd.py @@ -0,0 +1,352 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2011 Gareth McMullin +## Copyright (C) 2012-2013 Uwe Hermann +## Copyright (C) 2019 DreamSourceLab +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd + +''' +OUTPUT_PYTHON format: + +Packet: +[, ] + +, : + - 'SOP', None + - 'SYM', + - 'BIT', + - 'STUFF BIT', None + - 'EOP', None + - 'ERR', None + - 'KEEP ALIVE', None + - 'RESET', None + +: + - 'J', 'K', 'SE0', or 'SE1' + +: + - '0' or '1' + - Note: Symbols like SE0, SE1, and the J that's part of EOP don't yield 'BIT'. +''' + +# Low-/full-speed symbols. +# Note: Low-speed J and K are inverted compared to the full-speed J and K! +symbols = { + 'low-speed': { + # (, ): + (0, 0): 'SE0', + (1, 0): 'K', + (0, 1): 'J', + (1, 1): 'SE1', + }, + 'full-speed': { + # (, ): + (0, 0): 'SE0', + (1, 0): 'J', + (0, 1): 'K', + (1, 1): 'SE1', + }, + 'automatic': { + # (, ): + (0, 0): 'SE0', + (1, 0): 'FS_J', + (0, 1): 'LS_J', + (1, 1): 'SE1', + }, + # After a PREamble PID, the bus segment between Host and Hub uses LS + # signalling rate and FS signalling polarity (USB 2.0 spec, 11.8.4: "For + # both upstream and downstream low-speed data, the hub is responsible for + # inverting the polarity of the data before transmitting to/from a + # low-speed port."). + 'low-speed-rp': { + # (, ): + (0, 0): 'SE0', + (1, 0): 'J', + (0, 1): 'K', + (1, 1): 'SE1', + }, +} + +bitrates = { + 'low-speed': 1500000, # 1.5Mb/s (+/- 1.5%) + 'low-speed-rp': 1500000, # 1.5Mb/s (+/- 1.5%) + 'full-speed': 12000000, # 12Mb/s (+/- 0.25%) + 'automatic': None +} + +sym_annotation = { + 'J': [0, ['J']], + 'K': [1, ['K']], + 'SE0': [2, ['SE0', '0']], + 'SE1': [3, ['SE1', '1']], +} + +class SamplerateError(Exception): + pass + +class Decoder(srd.Decoder): + api_version = 3 + id = 'usb_signalling' + name = 'USB signalling' + longname = 'Universal Serial Bus (LS/FS) signalling' + desc = 'USB (low-speed/full-speed) signalling protocol.' + license = 'gplv2+' + inputs = ['logic'] + outputs = ['usb_signalling'] + tags = ['PC'] + channels = ( + {'id': 'dp', 'name': 'D+', 'desc': 'USB D+ signal'}, + {'id': 'dm', 'name': 'D-', 'desc': 'USB D- signal'}, + ) + options = ( + {'id': 'signalling', 'desc': 'Signalling', + 'default': 'automatic', 'values': ('automatic', 'full-speed', 'low-speed')}, + ) + annotations = ( + ('sym-j', 'J symbol'), + ('sym-k', 'K symbol'), + ('sym-se0', 'SE0 symbol'), + ('sym-se1', 'SE1 symbol'), + ('sop', 'Start of packet (SOP)'), + ('eop', 'End of packet (EOP)'), + ('bit', 'Bit'), + ('stuffbit', 'Stuff bit'), + ('error', 'Error'), + ('keep-alive', 'Low-speed keep-alive'), + ('reset', 'Reset'), + ) + annotation_rows = ( + ('bits', 'Bits', (4, 5, 6, 7, 8, 9, 10)), + ('symbols', 'Symbols', (0, 1, 2, 3)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.samplerate = None + self.oldsym = 'J' # The "idle" state is J. + self.ss_block = None + self.samplenum = 0 + self.bitrate = None + self.bitwidth = None + self.samplepos = None + self.samplenum_target = None + self.samplenum_edge = None + self.samplenum_lastedge = 0 + self.edgepins = None + self.consecutive_ones = 0 + self.bits = None + self.state = 'IDLE' + + def start(self): + self.out_python = self.register(srd.OUTPUT_PYTHON) + self.out_ann = self.register(srd.OUTPUT_ANN) + + def metadata(self, key, value): + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + self.signalling = self.options['signalling'] + if self.signalling != 'automatic': + self.update_bitrate() + + def update_bitrate(self): + self.bitrate = bitrates[self.signalling] + self.bitwidth = float(self.samplerate) / float(self.bitrate) + + def putpx(self, data): + s = self.samplenum_edge + self.put(s, s, self.out_python, data) + + def putx(self, data): + s = self.samplenum_edge + self.put(s, s, self.out_ann, data) + + def putpm(self, data): + e = self.samplenum_edge + self.put(self.ss_block, e, self.out_python, data) + + def putm(self, data): + e = self.samplenum_edge + self.put(self.ss_block, e, self.out_ann, data) + + def putpb(self, data): + s, e = self.samplenum_lastedge, self.samplenum_edge + self.put(s, e, self.out_python, data) + + def putb(self, data): + s, e = self.samplenum_lastedge, self.samplenum_edge + self.put(s, e, self.out_ann, data) + + def set_new_target_samplenum(self): + self.samplepos += self.bitwidth + self.samplenum_target = int(self.samplepos) + self.samplenum_lastedge = self.samplenum_edge + self.samplenum_edge = int(self.samplepos - (self.bitwidth / 2)) + + def wait_for_sop(self, sym): + # Wait for a Start of Packet (SOP), i.e. a J->K symbol change. + if sym != 'K' or self.oldsym != 'J': + return + self.consecutive_ones = 0 + self.bits = '' + self.update_bitrate() + self.samplepos = self.samplenum - (self.bitwidth / 2) + 0.5 + self.set_new_target_samplenum() + self.putpx(['SOP', None]) + self.putx([4, ['SOP', 'S']]) + self.state = 'GET BIT' + + def handle_bit(self, b): + if self.consecutive_ones == 6: + if b == '0': + # Stuff bit. + self.putpb(['STUFF BIT', None]) + self.putb([7, ['Stuff bit: 0', 'SB: 0', '0']]) + self.consecutive_ones = 0 + else: + self.putpb(['ERR', None]) + self.putb([8, ['Bit stuff error', 'BS ERR', 'B']]) + self.state = 'IDLE' + else: + # Normal bit (not a stuff bit). + self.putpb(['BIT', b]) + self.putb([6, ['%s' % b]]) + if b == '1': + self.consecutive_ones += 1 + else: + self.consecutive_ones = 0 + + def get_eop(self, sym): + # EOP: SE0 for >= 1 bittime (usually 2 bittimes), then J. + self.set_new_target_samplenum() + self.putpb(['SYM', sym]) + self.putb(sym_annotation[sym]) + self.oldsym = sym + if sym == 'SE0': + pass + elif sym == 'J': + # Got an EOP. + self.putpm(['EOP', None]) + self.putm([5, ['EOP', 'E']]) + self.state = 'WAIT IDLE' + else: + self.putpm(['ERR', None]) + self.putm([8, ['EOP Error', 'EErr', 'E']]) + self.state = 'IDLE' + + def get_bit(self, sym): + self.set_new_target_samplenum() + b = '0' if self.oldsym != sym else '1' + self.oldsym = sym + if sym == 'SE0': + # Start of an EOP. Change state, save edge + self.state = 'GET EOP' + self.ss_block = self.samplenum_lastedge + else: + self.handle_bit(b) + self.putpb(['SYM', sym]) + self.putb(sym_annotation[sym]) + if len(self.bits) <= 16: + self.bits += b + if len(self.bits) == 16 and self.bits == '0000000100111100': + # Sync and low-speed PREamble seen + self.putpx(['EOP', None]) + self.state = 'IDLE' + self.signalling = 'low-speed-rp' + self.update_bitrate() + self.oldsym = 'J' + if b == '0': + edgesym = symbols[self.signalling][tuple(self.edgepins)] + if edgesym not in ('SE0', 'SE1'): + if edgesym == sym: + self.bitwidth = self.bitwidth - (0.001 * self.bitwidth) + self.samplepos = self.samplepos - (0.01 * self.bitwidth) + else: + self.bitwidth = self.bitwidth + (0.001 * self.bitwidth) + self.samplepos = self.samplepos + (0.01 * self.bitwidth) + + def handle_idle(self, sym): + self.samplenum_edge = self.samplenum + se0_length = float(self.samplenum - self.samplenum_lastedge) / self.samplerate + if se0_length > 2.5e-6: # 2.5us + self.putpb(['RESET', None]) + self.putb([10, ['Reset', 'Res', 'R']]) + self.signalling = self.options['signalling'] + elif se0_length > 1.2e-6 and self.signalling == 'low-speed': + self.putpb(['KEEP ALIVE', None]) + self.putb([9, ['Keep-alive', 'KA', 'A']]) + + if self.options['signalling'] == 'automatic' and sym == 'FS_J': + self.signalling = 'full-speed' + elif self.options['signalling'] == 'automatic' and sym == 'LS_J': + self.signalling = 'low-speed' + else: + self.signalling = self.options['signalling'] + self.update_bitrate() + + self.oldsym = 'J' + self.state = 'IDLE' + + def decode(self): + if not self.samplerate: + raise SamplerateError('Cannot decode without samplerate.') + + # Seed internal state from the very first sample. + (dp, dm) = self.wait() + sym = symbols[self.options['signalling']][(dp, dm)] + self.handle_idle(sym) + + while True: + # State machine. + if self.state == 'IDLE': + # Wait for any edge on either DP and/or DM. + (dp, dm) = self.wait([{0: 'e'}, {1: 'e'}]) + sym = symbols[self.signalling][(dp, dm)] + if sym == 'SE0': + self.samplenum_lastedge = self.samplenum + self.state = 'WAIT IDLE' + else: + self.wait_for_sop(sym) + self.edgepins = (dp, dm) + elif self.state in ('GET BIT', 'GET EOP'): + # Wait until we're in the middle of the desired bit. + if (self.samplenum_edge > self.samplenum): + (dp, dm) = self.wait([{'skip': self.samplenum_edge - self.samplenum}]) + self.edgepins = (dp, dm) + if (self.samplenum_target > self.samplenum): + (dp, dm) = self.wait([{'skip': self.samplenum_target - self.samplenum}]) + + sym = symbols[self.signalling][(dp, dm)] + if self.state == 'GET BIT': + self.get_bit(sym) + elif self.state == 'GET EOP': + self.get_eop(sym) + elif self.state == 'WAIT IDLE': + # Skip "all-low" input. Wait for high level on either DP or DM. + (dp, dm) = self.wait() + while not dp and not dm: + (dp, dm) = self.wait([{0: 'h'}, {1: 'h'}]) + if self.samplenum - self.samplenum_lastedge > 1: + sym = symbols[self.options['signalling']][(dp, dm)] + self.handle_idle(sym) + else: + sym = symbols[self.signalling][(dp, dm)] + self.wait_for_sop(sym) + self.edgepins = (dp, dm) diff --git a/libsigrokdecode4DSL/decoders/wiegand/__init__.py b/libsigrokdecode4DSL/decoders/wiegand/__init__.py new file mode 100644 index 00000000..d7d9a8c7 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/wiegand/__init__.py @@ -0,0 +1,28 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2016 Sean Burford +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +The Wiegand interface is a de facto wiring standard commonly used to connect +a card swipe mechanism to the rest of an electronic entry system. + +Details: +https://en.wikipedia.org/wiki/Wiegand_interface +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/wiegand/pd.py b/libsigrokdecode4DSL/decoders/wiegand/pd.py new file mode 100644 index 00000000..a93be109 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/wiegand/pd.py @@ -0,0 +1,148 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2016 Sean Burford +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd + +class SamplerateError(Exception): + pass + +class Decoder(srd.Decoder): + api_version = 3 + id = 'wiegand' + name = 'Wiegand' + longname = 'Wiegand interface' + desc = 'Wiegand interface for electronic entry systems.' + license = 'gplv2+' + inputs = ['logic'] + outputs = [] + tags = ['Embedded/industrial', 'RFID'] + channels = ( + {'id': 'd0', 'name': 'D0', 'desc': 'Data 0 line'}, + {'id': 'd1', 'name': 'D1', 'desc': 'Data 1 line'}, + ) + options = ( + {'id': 'active', 'desc': 'Data lines active level', + 'default': 'low', 'values': ('low', 'high')}, + {'id': 'bitwidth_ms', 'desc': 'Single bit width in milliseconds', + 'default': 4, 'values': (1, 2, 4, 8, 16, 32)}, + ) + annotations = ( + ('bits', 'Bits'), + ('state', 'State'), + ) + annotation_rows = ( + ('bits', 'Binary value', (0,)), + ('state', 'Stream state', (1,)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.samplerate = None + self._samples_per_bit = 10 + + self._d0_prev = None + self._d1_prev = None + + self._state = None + self.ss_state = None + + self.ss_bit = None + self.es_bit = None + self._bit = None + self._bits = [] + + def start(self): + 'Register output types and verify user supplied decoder values.' + self.out_ann = self.register(srd.OUTPUT_ANN) + self._active = 1 if self.options['active'] == 'high' else 0 + self._inactive = 1 - self._active + + def metadata(self, key, value): + 'Receive decoder metadata about the data stream.' + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + if self.samplerate: + ms_per_sample = 1000 * (1.0 / self.samplerate) + ms_per_bit = float(self.options['bitwidth_ms']) + self._samples_per_bit = int(max(1, int(ms_per_bit / ms_per_sample))) + + def _update_state(self, state, bit=None): + 'Update state and bit values when they change.' + if self._bit is not None: + self._bits.append(self._bit) + self.put(self.ss_bit, self.samplenum, self.out_ann, + [0, [str(self._bit)]]) + self._bit = bit + self.ss_bit = self.samplenum + if bit is not None: + # Set a timeout so that the final bit ends. + self.es_bit = self.samplenum + self._samples_per_bit + else: + self.es_bit = None + + if state != self._state: + ann = None + if self._state == 'data': + accum_bits = ''.join(str(x) for x in self._bits) + ann = [1, ['%d bits %s' % (len(self._bits), accum_bits), + '%d bits' % len(self._bits)]] + elif self._state == 'invalid': + ann = [1, [self._state]] + if ann: + self.put(self.ss_state, self.samplenum, self.out_ann, ann) + self.ss_state = self.samplenum + self._state = state + self._bits = [] + + def decode(self): + if not self.samplerate: + raise SamplerateError('Cannot decode without samplerate.') + while True: + # TODO: Come up with more appropriate self.wait() conditions. + (d0, d1) = self.wait() + + if d0 == self._d0_prev and d1 == self._d1_prev: + if self.es_bit and self.samplenum >= self.es_bit: + if (d0, d1) == (self._inactive, self._inactive): + self._update_state('idle') + else: + self._update_state('invalid') + continue + + if self._state in (None, 'idle', 'data'): + if (d0, d1) == (self._active, self._inactive): + self._update_state('data', 0) + elif (d0, d1) == (self._inactive, self._active): + self._update_state('data', 1) + elif (d0, d1) == (self._active, self._active): + self._update_state('invalid') + elif self._state == 'invalid': + # Wait until we see an idle state before leaving invalid. + # This prevents inverted lines from being misread. + if (d0, d1) == (self._inactive, self._inactive): + self._update_state('idle') + + self._d0_prev, self._d1_prev = d0, d1 + + def report(self): + return '%s: %s D0 %d D1 %d (active on %d), %d samples per bit' % ( + self.name, self._state, self._d0_prev, self._d1_prev, + self._active, self._samples_per_bit) diff --git a/libsigrokdecode4DSL/decoders/x2444m/__init__.py b/libsigrokdecode4DSL/decoders/x2444m/__init__.py new file mode 100644 index 00000000..70d21466 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/x2444m/__init__.py @@ -0,0 +1,25 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2018 Stefan Petersen +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This decoder stacks on top of the 'spi' PD and decodes the Xicor X2444M/P +nonvolatile static RAM protocol. +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/x2444m/pd.py b/libsigrokdecode4DSL/decoders/x2444m/pd.py new file mode 100644 index 00000000..290cc368 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/x2444m/pd.py @@ -0,0 +1,111 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2018 Stefan Petersen +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import re +import sigrokdecode as srd + +registers = { + 0x80: ['WRDS', 0, lambda _: ''], + 0x81: ['STO', 1, lambda _: ''], + 0x82: ['SLEEP', 2, lambda _: ''], + 0x83: ['WRITE', 3, lambda v: '0x%x' % v], + 0x84: ['WREN', 4, lambda _: ''], + 0x85: ['RCL', 5, lambda _: ''], + 0x86: ['READ', 6, lambda v: '0x%x' % v], + 0x87: ['READ', 7, lambda v: '0x%x' % v], +} + +class Decoder(srd.Decoder): + api_version = 3 + id = 'x2444m' + name = 'X2444M/P' + longname = 'Xicor X2444M/P' + desc = 'Xicor X2444M/P nonvolatile static RAM protocol.' + license = 'gplv2+' + inputs = ['spi'] + outputs = [] + tags = ['IC', 'Memory'] + annotations = ( + ('wrds', 'Write disable'), + ('sto', 'Store RAM data in EEPROM'), + ('sleep', 'Enter sleep mode'), + ('write', 'Write data into RAM'), + ('wren', 'Write enable'), + ('rcl', 'Recall EEPROM data into RAM'), + ('read', 'Data read from RAM'), + ('read', 'Data read from RAM'), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.cs_start = 0 + self.cs_asserted = False + self.cmd_digit = 0 + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def putreadwrite(self, ss, es, reg, idx, addr, value): + self.put(ss, es, self.out_ann, + [idx, ['%s: %s => 0x%4.4x' % (reg, addr, value), + '%s: %s => 0x%4.4x' % (reg[0], addr, value), reg[0]]]) + + def putcmd(self, ss, es, reg, idx): + self.put(ss, es, self.out_ann, [idx, [reg, reg[0]]]) + + def decode(self, ss, es, data): + ptype, mosi, miso = data + + if ptype == 'DATA': + if not self.cs_asserted: + return + + if self.cmd_digit == 0: + self.addr = mosi + self.addr_start = ss + elif self.cmd_digit > 0: + self.read_value = (self.read_value << 8) + miso + self.write_value = (self.write_value << 8) + mosi + self.cmd_digit += 1 + elif ptype == 'CS-CHANGE': + self.cs_asserted = (miso == 1) + # When not asserted, CS has just changed from asserted to deasserted. + if not self.cs_asserted: + # Only one digit, simple command. Else read/write. + if self.cmd_digit == 1: + name, idx, decoder = registers[self.addr & 0x87] + self.putcmd(self.addr_start, es, name, idx) + elif self.cmd_digit > 1: + name, idx, decoder = registers[self.addr & 0x87] + if name == 'READ': + value = self.read_value + elif name == 'WRITE': + value = self.write_value + else: + value = 0 + self.putreadwrite(self.addr_start, es, name, idx, + decoder((self.addr >> 3) & 0x0f), value) + + if self.cs_asserted: + self.cs_start = ss + self.cmd_digit = 0 + self.read_value = 0 + self.write_value = 0 diff --git a/libsigrokdecode4DSL/decoders/xfp/__init__.py b/libsigrokdecode4DSL/decoders/xfp/__init__.py new file mode 100644 index 00000000..72f35950 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/xfp/__init__.py @@ -0,0 +1,38 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2013 Bert Vermeulen +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +This PD decodes the XFP I²C management interface structures/protocol. + +XFP modules include an I²C interface, used to monitor and control various +aspects of the module. The specification defines an I²C slave at address +0x50 (0xa0) which returns 128 bytes of a standard structure ("lower memory"), +and, after setting a table number in lower memory, a set of 256 "higher +memory" tables, which can be mapped to different subdevices on the XFP. + +Only one table is defined in the specification: table 0x01, the default on +module startup. Other table are either reserved for future expansion, or +available for vendor-specific extensions. This decoder supports both lower +memory and table 0x01. + +Details: +ftp://ftp.seagate.com/sff/INF-8077.PDF (XFP specification) +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/xfp/pd.py b/libsigrokdecode4DSL/decoders/xfp/pd.py new file mode 100644 index 00000000..ded76946 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/xfp/pd.py @@ -0,0 +1,482 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2013 Bert Vermeulen +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 3 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd +from common.plugtrx import (MODULE_ID, ALARM_THRESHOLDS, AD_READOUTS, GCS_BITS, + CONNECTOR, TRANSCEIVER, SERIAL_ENCODING, XMIT_TECH, CDR, DEVICE_TECH, + ENHANCED_OPTS, AUX_TYPES) + +class Decoder(srd.Decoder): + api_version = 3 + id = 'xfp' + name = 'XFP' + longname = '10 Gigabit Small Form Factor Pluggable Module (XFP)' + desc = 'XFP I²C management interface structures/protocol' + license = 'gplv3+' + inputs = ['i2c'] + outputs = [] + tags = ['Networking'] + annotations = ( + ('fieldnames-and-values', 'XFP structure field names and values'), + ('fields', 'XFP structure fields'), + ) + + def __init__(self): + self.reset() + + def reset(self): + # Received data items, used as an index into samplenum/data + self.cnt = -1 + # Start/end sample numbers per data item + self.sn = [] + # Multi-byte structure buffer + self.buf = [] + # Filled in by address 0x7f in low memory + self.cur_highmem_page = 0 + # Filled in by extended ID value in table 2 + self.have_clei = False + # Handlers for each field in the structure, keyed by the end + # index of that field. Each handler is fed all unhandled bytes + # up until that point, so mark unused space with the dummy + # handler self.ignore(). + self.MAP_LOWER_MEMORY = { + 0: self.module_id, + 1: self.signal_cc, + 57: self.alarm_warnings, + 59: self.vps, + 69: self.ignore, + 71: self.ber, + 75: self.wavelength_cr, + 79: self.fec_cr, + 95: self.int_ctrl, + 109: self.ad_readout, + 111: self.gcs, + 117: self.ignore, + 118: self.ignore, + 122: self.ignore, + 126: self.ignore, + 127: self.page_select, + } + self.MAP_HIGH_TABLE_1 = { + 128: self.module_id, + 129: self.ext_module_id, + 130: self.connector, + 138: self.transceiver, + 139: self.serial_encoding, + 140: self.br_min, + 141: self.br_max, + 142: self.link_length_smf, + 143: self.link_length_e50, + 144: self.link_length_50um, + 145: self.link_length_625um, + 146: self.link_length_copper, + 147: self.device_tech, + 163: self.vendor, + 164: self.cdr, + 167: self.vendor_oui, + 183: self.vendor_pn, + 185: self.vendor_rev, + 187: self.wavelength, + 189: self.wavelength_tolerance, + 190: self.max_case_temp, + 191: self.ignore, + 195: self.power_supply, + 211: self.vendor_sn, + 219: self.manuf_date, + 220: self.diag_mon, + 221: self.enhanced_opts, + 222: self.aux_mon, + 223: self.ignore, + 255: self.maybe_ascii, + } + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def decode(self, ss, es, data): + cmd, data = data + + # We only care about actual data bytes that are read (for now). + if cmd != 'DATA READ': + return + + self.cnt += 1 + self.sn.append([ss, es]) + + self.buf.append(data) + if self.cnt < 0x80: + if self.cnt in self.MAP_LOWER_MEMORY: + self.MAP_LOWER_MEMORY[self.cnt](self.buf) + self.buf.clear() + elif self.cnt < 0x0100 and self.cur_highmem_page == 0x01: + # Serial ID memory map + if self.cnt in self.MAP_HIGH_TABLE_1: + self.MAP_HIGH_TABLE_1[self.cnt](self.buf) + self.buf.clear() + + # Annotation helper + def annotate(self, key, value, start_cnt=None, end_cnt=None): + if start_cnt is None: + start_cnt = self.cnt - len(self.buf) + 1 + if end_cnt is None: + end_cnt = self.cnt + self.put(self.sn[start_cnt][0], self.sn[end_cnt][1], + self.out_ann, [0, [key + ": " + value]]) + self.put(self.sn[start_cnt][0], self.sn[end_cnt][1], + self.out_ann, [1, [value]]) + + # Placeholder handler, needed to advance the buffer past unused or + # reserved space in the structures. + def ignore(self, data): + pass + + # Show as ASCII if possible + def maybe_ascii(self, data): + for i in range(len(data)): + if data[i] >= 0x20 and data[i] < 0x7f: + cnt = self.cnt - len(data) + 1 + self.annotate("Vendor ID", chr(data[i]), cnt, cnt) + + # Convert 16-bit two's complement values, with each increment + # representing 1/256C, to degrees Celsius. + def to_temp(self, value): + if value & 0x8000: + value = -((value ^ 0xffff) + 1) + temp = value / 256.0 + return "%.1f C" % temp + + # TX bias current in uA. Each increment represents 0.2uA + def to_current(self, value): + current = value / 500000.0 + return "%.1f mA" % current + + # Power in mW, with each increment representing 0.1uW + def to_power(self, value): + power = value / 10000.0 + return "%.2f mW" % power + + # Wavelength in increments of 0.05nm + def to_wavelength(self, value): + wl = value / 20 + return "%d nm" % wl + + # Wavelength in increments of 0.005nm + def to_wavelength_tolerance(self, value): + wl = value / 200.0 + return "%.1f nm" % wl + + def module_id(self, data): + self.annotate("Module identifier", MODULE_ID.get(data[0], "Unknown")) + + def signal_cc(self, data): + # No good data available. + if (data[0] != 0x00): + self.annotate("Signal Conditioner Control", "%.2x" % data[0]) + + def alarm_warnings(self, data): + cnt_idx = self.cnt - len(data) + idx = 0 + while idx < 56: + if idx == 8: + # Skip over reserved A/D flag thresholds + idx += 8 + value = (data[idx] << 8) | data[idx + 1] + if value != 0: + name = ALARM_THRESHOLDS.get(idx, "...") + if idx in (0, 2, 4, 6): + self.annotate(name, self.to_temp(value), + cnt_idx + idx, cnt_idx + idx + 1) + elif idx in (16, 18, 20, 22): + self.annotate(name, self.to_current(value), + cnt_idx + idx, cnt_idx + idx + 1) + elif idx in (24, 26, 28, 30, 32, 34, 36, 38): + self.annotate(name, self.to_power(value), + cnt_idx + idx, cnt_idx + idx + 1) + else: + self.annotate(name, "%d" % name, value, cnt_idx + idx, + cnt_idx + idx + 1) + idx += 2 + + def vps(self, data): + # No good data available. + if (data != [0, 0]): + self.annotate("VPS", "%.2x%.2x" % (data[0], data[1])) + + def ber(self, data): + # No good data available. + if (data != [0, 0]): + self.annotate("BER", str(data)) + + def wavelength_cr(self, data): + # No good data available. + if (data != [0, 0, 0, 0]): + self.annotate("WCR", str(data)) + + def fec_cr(self, data): + if (data != [0, 0, 0, 0]): + self.annotate("FEC", str(data)) + + def int_ctrl(self, data): + # No good data available. Also boring. + out = [] + for d in data: + out.append("%.2x" % d) + self.annotate("Interrupt bits", ' '.join(out)) + + def ad_readout(self, data): + cnt_idx = self.cnt - len(data) + 1 + idx = 0 + while idx < 14: + if idx == 2: + # Skip over reserved field + idx += 2 + value = (data[idx] << 8) | data[idx + 1] + name = AD_READOUTS.get(idx, "...") + if value != 0: + if idx == 0: + self.annotate(name, self.to_temp(value), + cnt_idx + idx, cnt_idx + idx + 1) + elif idx == 4: + self.annotate(name, self.to_current(value), + cnt_idx + idx, cnt_idx + idx + 1) + elif idx in (6, 8): + self.annotate(name, self.to_power(value), + cnt_idx + idx, cnt_idx + idx + 1) + else: + self.annotate(name, str(value), cnt_idx + idx, + cnt_idx + idx + 1) + idx += 2 + + def gcs(self, data): + allbits = (data[0] << 8) | data[1] + out = [] + for b in range(13): + if allbits & 0x8000: + out.append(GCS_BITS[b]) + allbits <<= 1 + self.annotate("General Control/Status", ', '.join(out)) + + def page_select(self, data): + self.cur_highmem_page = data[0] + + def ext_module_id(self, data): + out = ["Power level %d module" % ((data[0] >> 6) + 1)] + if data[0] & 0x20 == 0: + out.append("CDR") + if data[0] & 0x10 == 0: + out.append("TX ref clock input required") + if data[0] & 0x08 == 0: + self.have_clei = True + self.annotate("Extended id", ', '.join(out)) + + def connector(self, data): + if data[0] in CONNECTOR: + self.annotate("Connector", CONNECTOR[data[0]]) + + def transceiver(self, data): + out = [] + for t in range(8): + if data[t] == 0: + continue + value = data[t] + for b in range(8): + if value & 0x80: + if len(TRANSCEIVER[t]) < b + 1: + out.append("(unknown)") + else: + out.append(TRANSCEIVER[t][b]) + value <<= 1 + self.annotate("Transceiver compliance", ', '.join(out)) + + def serial_encoding(self, data): + out = [] + value = data[0] + for b in range(8): + if value & 0x80: + if len(SERIAL_ENCODING) < b + 1: + out.append("(unknown)") + else: + out.append(SERIAL_ENCODING[b]) + value <<= 1 + self.annotate("Serial encoding support", ', '.join(out)) + + def br_min(self, data): + # Increments represent 100Mb/s + rate = data[0] / 10.0 + self.annotate("Minimum bit rate", "%.3f GB/s" % rate) + + def br_max(self, data): + # Increments represent 100Mb/s + rate = data[0] / 10.0 + self.annotate("Maximum bit rate", "%.3f GB/s" % rate) + + def link_length_smf(self, data): + if data[0] == 0: + length = "(standard)" + elif data[0] == 255: + length = "> 254 km" + else: + length = "%d km" % data[0] + self.annotate("Link length (SMF)", length) + + def link_length_e50(self, data): + if data[0] == 0: + length = "(standard)" + elif data[0] == 255: + length = "> 508 m" + else: + length = "%d m" % (data[0] * 2) + self.annotate("Link length (extended, 50μm MMF)", length) + + def link_length_50um(self, data): + if data[0] == 0: + length = "(standard)" + elif data[0] == 255: + length = "> 254 m" + else: + length = "%d m" % data[0] + self.annotate("Link length (50μm MMF)", length) + + def link_length_625um(self, data): + if data[0] == 0: + length = "(standard)" + elif data[0] == 255: + length = "> 254 m" + else: + length = "%d m" % (data[0]) + self.annotate("Link length (62.5μm MMF)", length) + + def link_length_copper(self, data): + if data[0] == 0: + length = "(unknown)" + elif data[0] == 255: + length = "> 254 m" + else: + length = "%d m" % (data[0] * 2) + self.annotate("Link length (copper)", length) + + def device_tech(self, data): + out = [] + xmit = data[0] >> 4 + if xmit <= len(XMIT_TECH) - 1: + out.append("%s transmitter" % XMIT_TECH[xmit]) + dev = data[0] & 0x0f + for b in range(4): + out.append(DEVICE_TECH[b][(dev >> (3 - b)) & 0x01]) + self.annotate("Device technology", ', '.join(out)) + + def vendor(self, data): + name = bytes(data).strip().decode('ascii').strip('\x00') + if name: + self.annotate("Vendor", name) + + def cdr(self, data): + out = [] + value = data[0] + for b in range(8): + if value & 0x80: + out.append(CDR[b]) + value <<= 1 + self.annotate("CDR support", ', '.join(out)) + + def vendor_oui(self, data): + if data != [0, 0, 0]: + self.annotate("Vendor OUI", "%.2X-%.2X-%.2X" % tuple(data)) + + def vendor_pn(self, data): + name = bytes(data).strip().decode('ascii').strip('\x00') + if name: + self.annotate("Vendor part number", name) + + def vendor_rev(self, data): + name = bytes(data).strip().decode('ascii').strip('\x00') + if name: + self.annotate("Vendor revision", name) + + def wavelength(self, data): + value = (data[0] << 8) | data[1] + self.annotate("Wavelength", self.to_wavelength(value)) + + def wavelength_tolerance(self, data): + value = (data[0] << 8) | data[1] + self.annotate("Wavelength tolerance", self.to_wavelength_tolerance(value)) + + def max_case_temp(self, data): + self.annotate("Maximum case temperature", "%d C" % data[0]) + + def power_supply(self, data): + out = [] + self.annotate("Max power dissipation", + "%.3f W" % (data[0] * 0.02), self.cnt - 3, self.cnt - 3) + self.annotate("Max power dissipation (powered down)", + "%.3f W" % (data[1] * 0.01), self.cnt - 2, self.cnt - 2) + value = (data[2] >> 4) * 0.050 + self.annotate("Max current required (5V supply)", + "%.3f A" % value, self.cnt - 1, self.cnt - 1) + value = (data[2] & 0x0f) * 0.100 + self.annotate("Max current required (3.3V supply)", + "%.3f A" % value, self.cnt - 1, self.cnt - 1) + value = (data[3] >> 4) * 0.100 + self.annotate("Max current required (1.8V supply)", + "%.3f A" % value, self.cnt, self.cnt) + value = (data[3] & 0x0f) * 0.050 + self.annotate("Max current required (-5.2V supply)", + "%.3f A" % value, self.cnt, self.cnt) + + def vendor_sn(self, data): + name = bytes(data).strip().decode('ascii').strip('\x00') + if name: + self.annotate("Vendor serial number", name) + + def manuf_date(self, data): + y = int(bytes(data[0:2])) + 2000 + m = int(bytes(data[2:4])) + d = int(bytes(data[4:6])) + mnf = "%.4d-%.2d-%.2d" % (y, m, d) + lot = bytes(data[6:]).strip().decode('ascii').strip('\x00') + if lot: + mnf += " lot " + lot + self.annotate("Manufacturing date", mnf) + + def diag_mon(self, data): + out = [] + if data[0] & 0x10: + out.append("BER support") + else: + out.append("no BER support") + if data[0] & 0x08: + out.append("average power measurement") + else: + out.append("OMA power measurement") + self.annotate("Diagnostic monitoring", ', '.join(out)) + + def enhanced_opts(self, data): + out = [] + value = data[0] + for b in range(8): + if value & 0x80: + out.append(ENHANCED_OPTS[b]) + value <<= 1 + self.annotate("Enhanced option support", ', '.join(out)) + + def aux_mon(self, data): + aux = AUX_TYPES[data[0] >> 4] + self.annotate("AUX1 monitoring", aux) + aux = AUX_TYPES[data[0] & 0x0f] + self.annotate("AUX2 monitoring", aux) diff --git a/libsigrokdecode4DSL/decoders/xy2-100/__init__.py b/libsigrokdecode4DSL/decoders/xy2-100/__init__.py new file mode 100644 index 00000000..676e1aff --- /dev/null +++ b/libsigrokdecode4DSL/decoders/xy2-100/__init__.py @@ -0,0 +1,28 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2019 Uli Huber +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +XY2-100 is a serial bus for connecting galvo systems to controllers + +Details: + +http://www.newson.be/doc.php?id=XY2-100 +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/xy2-100/pd.py b/libsigrokdecode4DSL/decoders/xy2-100/pd.py new file mode 100644 index 00000000..47c4182c --- /dev/null +++ b/libsigrokdecode4DSL/decoders/xy2-100/pd.py @@ -0,0 +1,242 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2019 Uli Huber +## Copyright (C) 2020 Soeren Apel +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd + +ann_bit, ann_stat_bit, ann_type, ann_command, ann_parameter, ann_parity, ann_pos, ann_status, ann_warning = range(9) +frame_type_none, frame_type_command, frame_type_16bit_pos, frame_type_18bit_pos = range(4) + +class Decoder(srd.Decoder): + api_version = 3 + id = 'xy2-100' + name = 'XY2-100' + longname = 'XY2-100(E) and XY-200(E) galvanometer protocol' + desc = 'Serial protocol for galvanometer positioning in laser systems' + license = 'gplv2+' + inputs = ['logic'] + outputs = [] + + tags = ['Embedded/industrial'] + + channels = ( + {'id': 'clk', 'name': 'CLK', 'desc': 'Clock'}, + {'id': 'sync', 'name': 'SYNC', 'desc': 'Sync'}, + {'id': 'data', 'name': 'DATA', 'desc': 'X, Y or Z axis data'}, + ) + optional_channels = ( + {'id': 'status', 'name': 'STAT', 'desc': 'X, Y or Z axis status'}, + ) + + annotations = ( + ('bit', 'Data Bit'), + ('stat_bit', 'Status Bit'), + ('type', 'Frame Type'), + ('command', 'Command'), + ('parameter', 'Parameter'), + ('parity', 'Parity'), + ('position', 'Position'), + ('status', 'Status'), + ('warning', 'Human-readable warnings'), + ) + annotation_rows = ( + ('bits', 'Data Bits', (ann_bit,)), + ('stat_bits', 'Status Bits', (ann_stat_bit,)), + ('data', 'Data', (ann_type, ann_command, ann_parameter, ann_parity)), + ('positions', 'Positions', (ann_pos,)), + ('statuses', 'Statuses', (ann_status,)), + ('warnings', 'Warnings', (ann_warning,)), + ) + + def __init__(self): + self.samplerate = None + self.reset() + + def reset(self): + self.bits = [] + self.stat_bits = [] + self.stat_skip_bit = True + + def metadata(self, key, value): + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def put_ann(self, ss, es, ann_class, value): + self.put(ss, es, self.out_ann, [ann_class, value]) + + def process_bit(self, sync, bit_ss, bit_es, bit_value): + self.put_ann(bit_ss, bit_es, ann_bit, ['%d' % bit_value]) + self.bits.append((bit_ss, bit_es, bit_value)) + + if sync == 0: + if len(self.bits) < 20: + self.put_ann(self.bits[0][0], bit_es, ann_warning, ['Not enough data bits']) + self.reset() + return + + # Bit structure: + # 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 + # T --------------- 18-bit pos ----------------- PARITY or + # -TYPE-- ------------ 16-bit pos -------------- PARITY or + # -TYPE-- -8-bit command -8-bit parameter value- PARITY + + # Calculate parity, excluding the parity bit itself + parity = 0 + for ss, es, value in self.bits[:-1]: + parity ^= value + + par_ss, par_es, par_value = self.bits[19] + parity_even = 0 + parity_odd = 0 + if (par_value == parity): + parity_even = 1 + else: + parity_odd = 1 + + type_1_value = self.bits[0][2] + type_3_value = (self.bits[0][2] << 2) | (self.bits[1][2] << 1) | self.bits[2][2] + + # Determine frame type + type = frame_type_none + parity_status = ['X', 'Unknown'] + type_ss = self.bits[0][0] + type_es = self.bits[2][1] + + ### 18-bit position + if (type_1_value == 1) and (parity_odd == 1): + type = frame_type_18bit_pos + type_es = self.bits[0][1] + self.put_ann(self.bits[0][0], bit_es, ann_warning, ['Careful: 18-bit position frames with wrong parity and command frames with wrong parity cannot be identified']) + ### 16-bit position + elif (type_3_value == 1): + type = frame_type_16bit_pos + if (parity_even == 1): + parity_status = ['OK'] + else: + parity_status = ['NOK'] + self.put_ann(self.bits[0][0], bit_es, ann_warning, ['Parity error', 'PE']) + ### Command + elif (type_3_value == 7) and (parity_even == 1): + type = frame_type_command + self.put_ann(self.bits[0][0], bit_es, ann_warning, ['Careful: 18-bit position frames with wrong parity and command frames with wrong parity cannot be identified']) + ### Other + else: + self.put_ann(self.bits[0][0], bit_es, ann_warning, ['Error', 'Unknown command or parity error']) + self.reset() + return + + # Output command and parity annotations + if (type == frame_type_16bit_pos): + self.put_ann(type_ss, type_es, ann_type, ['16 bit Position Frame', '16 bit Pos', 'Pos', 'P']) + if (type == frame_type_18bit_pos): + self.put_ann(type_ss, type_es, ann_type, ['18 bit Position Frame', '18 bit Pos', 'Pos', 'P']) + if (type == frame_type_command): + self.put_ann(type_ss, type_es, ann_type, ['Command Frame', 'Command', 'C']) + + self.put_ann(par_ss, par_es, ann_parity, parity_status) + + # Output value + if (type == frame_type_16bit_pos) or (type == frame_type_18bit_pos): + pos = 0 + + if (type == frame_type_16bit_pos): + count = 15 + for ss, es, value in self.bits[3:19]: + pos |= value << count + count -= 1 + pos = pos if pos < 32768 else pos - 65536 + else: + count = 17 + for ss, es, value in self.bits[3:19]: + pos |= value << count + count -= 1 + pos = pos if pos < 131072 else pos - 262144 + + self.put_ann(type_es, par_ss, ann_pos, ['%d' % pos]) + + if (type == frame_type_command): + count = 7 + cmd = 0 + cmd_es = 0 + for ss, es, value in self.bits[3:11]: + cmd |= value << count + count -= 1 + cmd_es = es + self.put_ann(type_es, cmd_es, ann_command, ['Command 0x%X' % cmd, 'Cmd 0x%X' % cmd, '0x%X' % cmd]) + + count = 7 + param = 0 + for ss, es, value in self.bits[11:19]: + param |= value << count + count -= 1 + self.put_ann(cmd_es, par_ss, ann_parameter, ['Parameter 0x%X / %d' % (param, param), '0x%X / %d' % (param, param),'0x%X' % param]) + + self.reset() + + def process_stat_bit(self, sync, bit_ss, bit_es, bit_value): + if self.stat_skip_bit: + self.stat_skip_bit = False + return + + self.put_ann(bit_ss, bit_es, ann_stat_bit, ['%d' % bit_value]) + self.stat_bits.append((bit_ss, bit_es, bit_value)) + + if (sync == 0) and (len(self.stat_bits) == 19): + stat_ss = self.stat_bits[0][0] + stat_es = self.stat_bits[18][1] + + status = 0 + count = 18 + for ss, es, value in self.stat_bits: + status |= value << count + count -= 1 + self.put_ann(stat_ss, stat_es, ann_status, ['Status 0x%X' % status, '0x%X' % status]) + + def decode(self): + bit_ss = None + bit_es = None + bit_value = 0 + stat_ss = None + stat_es = None + stat_value = 0 + sync_value = 0 + has_stat = self.has_channel(3) + + while True: + # Wait for any edge on clk + clk, sync, data, stat = self.wait({0: 'e'}) + + if clk == 1: + stat_value = stat + + bit_es = self.samplenum + if bit_ss: + self.process_bit(sync_value, bit_ss, bit_es, bit_value) + bit_ss = self.samplenum + else: + bit_value = data + sync_value = sync + + stat_es = self.samplenum + if stat_ss and has_stat: + self.process_stat_bit(sync_value, stat_ss, stat_es, stat_value) + stat_ss = self.samplenum diff --git a/libsigrokdecode4DSL/decoders/z80/__init__.py b/libsigrokdecode4DSL/decoders/z80/__init__.py new file mode 100644 index 00000000..52ff9bac --- /dev/null +++ b/libsigrokdecode4DSL/decoders/z80/__init__.py @@ -0,0 +1,36 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 Daniel Elstner +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 3 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +The Zilog Z80 is an 8-bit microprocessor compatible with the Intel 8080. + +In addition to the 8-bit data bus, this decoder requires the input signals +/M1 (machine cycle), /RD (read) and /WR (write) to do its work. An explicit +clock signal is not required. However, the Z80 CPU clock may be used as +sampling clock, if applicable. + +Notes on the Z80 opcode format and descriptions of both documented and +"undocumented" opcodes are available here: + +Details: +http://www.z80.info/decoding.htm +http://clrhome.org/table/ +''' + +from .pd import Decoder diff --git a/libsigrokdecode4DSL/decoders/z80/pd.py b/libsigrokdecode4DSL/decoders/z80/pd.py new file mode 100644 index 00000000..9af310e2 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/z80/pd.py @@ -0,0 +1,359 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 Daniel Elstner +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 3 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +import sigrokdecode as srd +from functools import reduce +from .tables import instr_table_by_prefix +import string + +class Ann: + ADDR, MEMRD, MEMWR, IORD, IOWR, INSTR, ROP, WOP, WARN = range(9) +class Row: + ADDRBUS, DATABUS, INSTRUCTIONS, OPERANDS, WARNINGS = range(5) +class Pin: + D0, D7 = 0, 7 + M1, RD, WR, MREQ, IORQ = range(8, 13) + A0, A15 = 13, 28 +class Cycle: + NONE, MEMRD, MEMWR, IORD, IOWR, FETCH, INTACK = range(7) + +# Provide custom format type 'H' for hexadecimal output +# with leading decimal digit (assembler syntax). +class AsmFormatter(string.Formatter): + def format_field(self, value, format_spec): + if format_spec.endswith('H'): + result = format(value, format_spec[:-1] + 'X') + return result if result[0] in string.digits else '0' + result + else: + return format(value, format_spec) + +formatter = AsmFormatter() + +ann_data_cycle_map = { + Cycle.MEMRD: Ann.MEMRD, + Cycle.MEMWR: Ann.MEMWR, + Cycle.IORD: Ann.IORD, + Cycle.IOWR: Ann.IOWR, + Cycle.FETCH: Ann.MEMRD, + Cycle.INTACK: Ann.IORD, +} + +def reduce_bus(bus): + if 0xFF in bus: + return None # unassigned bus channels + else: + return reduce(lambda a, b: (a << 1) | b, reversed(bus)) + +def signed_byte(byte): + return byte if byte < 128 else byte - 256 + +class Decoder(srd.Decoder): + api_version = 3 + id = 'z80' + name = 'Z80' + longname = 'Zilog Z80 CPU' + desc = 'Zilog Z80 microprocessor disassembly.' + license = 'gplv3+' + inputs = ['logic'] + outputs = [] + tags = ['Retro computing'] + channels = tuple({ + 'id': 'd%d' % i, + 'name': 'D%d' % i, + 'desc': 'Data bus line %d' % i + } for i in range(8) + ) + ( + {'id': 'm1', 'name': '/M1', 'desc': 'Machine cycle 1'}, + {'id': 'rd', 'name': '/RD', 'desc': 'Memory or I/O read'}, + {'id': 'wr', 'name': '/WR', 'desc': 'Memory or I/O write'}, + ) + optional_channels = ( + {'id': 'mreq', 'name': '/MREQ', 'desc': 'Memory request'}, + {'id': 'iorq', 'name': '/IORQ', 'desc': 'I/O request'}, + ) + tuple({ + 'id': 'a%d' % i, + 'name': 'A%d' % i, + 'desc': 'Address bus line %d' % i + } for i in range(16) + ) + annotations = ( + ('addr', 'Memory or I/O address'), + ('memrd', 'Byte read from memory'), + ('memwr', 'Byte written to memory'), + ('iord', 'Byte read from I/O port'), + ('iowr', 'Byte written to I/O port'), + ('instr', 'Z80 CPU instruction'), + ('rop', 'Value of input operand'), + ('wop', 'Value of output operand'), + ('warn', 'Warning message'), + ) + annotation_rows = ( + ('addrbus', 'Address bus', (Ann.ADDR,)), + ('databus', 'Data bus', (Ann.MEMRD, Ann.MEMWR, Ann.IORD, Ann.IOWR)), + ('instructions', 'Instructions', (Ann.INSTR,)), + ('operands', 'Operands', (Ann.ROP, Ann.WOP)), + ('warnings', 'Warnings', (Ann.WARN,)) + ) + + def __init__(self): + self.reset() + + def reset(self): + self.prev_cycle = Cycle.NONE + self.op_state = self.state_IDLE + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + self.bus_data = None + self.samplenum = None + self.addr_start = None + self.data_start = None + self.dasm_start = None + self.pend_addr = None + self.pend_data = None + self.ann_data = None + self.ann_dasm = None + self.prev_cycle = Cycle.NONE + self.op_state = self.state_IDLE + self.instr_len = 0 + + def decode(self): + while True: + # TODO: Come up with more appropriate self.wait() conditions. + (d0, d1, d2, d3, d4, d5, d6, d7, m1, rd, wr, mreq, iorq, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15) = self.wait() + pins = (d0, d1, d2, d3, d4, d5, d6, d7, m1, rd, wr, mreq, iorq, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15) + cycle = Cycle.NONE + if pins[Pin.MREQ] != 1: # default to asserted + if pins[Pin.RD] == 0: + cycle = Cycle.FETCH if pins[Pin.M1] == 0 else Cycle.MEMRD + elif pins[Pin.WR] == 0: + cycle = Cycle.MEMWR + elif pins[Pin.IORQ] == 0: # default to not asserted + if pins[Pin.M1] == 0: + cycle = Cycle.INTACK + elif pins[Pin.RD] == 0: + cycle = Cycle.IORD + elif pins[Pin.WR] == 0: + cycle = Cycle.IOWR + + if cycle != Cycle.NONE: + self.bus_data = reduce_bus(pins[Pin.D0:Pin.D7+1]) + if cycle != self.prev_cycle: + if self.prev_cycle == Cycle.NONE: + self.on_cycle_begin(reduce_bus(pins[Pin.A0:Pin.A15+1])) + elif cycle == Cycle.NONE: + self.on_cycle_end() + else: + self.on_cycle_trans() + self.prev_cycle = cycle + + def on_cycle_begin(self, bus_addr): + if self.pend_addr is not None: + self.put_text(self.addr_start, Ann.ADDR, + '{:04X}'.format(self.pend_addr)) + self.addr_start = self.samplenum + self.pend_addr = bus_addr + + def on_cycle_end(self): + self.instr_len += 1 + self.op_state = self.op_state() + if self.ann_dasm is not None: + self.put_disasm() + if self.op_state == self.state_RESTART: + self.op_state = self.state_IDLE() + + if self.ann_data is not None: + self.put_text(self.data_start, self.ann_data, + '{:02X}'.format(self.pend_data)) + self.data_start = self.samplenum + self.pend_data = self.bus_data + self.ann_data = ann_data_cycle_map[self.prev_cycle] + + def on_cycle_trans(self): + self.put_text(self.samplenum - 1, Ann.WARN, + 'Illegal transition between control states') + self.pend_addr = None + self.ann_data = None + self.ann_dasm = None + + def put_disasm(self): + text = formatter.format(self.mnemonic, r=self.arg_reg, d=self.arg_dis, + j=self.arg_dis+self.instr_len, i=self.arg_imm, + ro=self.arg_read, wo=self.arg_write) + self.put_text(self.dasm_start, self.ann_dasm, text) + self.ann_dasm = None + self.dasm_start = self.samplenum + + def put_text(self, ss, ann_idx, ann_text): + self.put(ss, self.samplenum, self.out_ann, [ann_idx, [ann_text]]) + + def state_RESTART(self): + return self.state_IDLE + + def state_IDLE(self): + if self.prev_cycle != Cycle.FETCH: + return self.state_IDLE + self.want_dis = 0 + self.want_imm = 0 + self.want_read = 0 + self.want_write = 0 + self.want_wr_be = False + self.op_repeat = False + self.arg_dis = 0 + self.arg_imm = 0 + self.arg_read = 0 + self.arg_write = 0 + self.arg_reg = '' + self.mnemonic = '' + self.instr_pend = False + self.read_pend = False + self.write_pend = False + self.dasm_start = self.samplenum + self.op_prefix = 0 + self.instr_len = 0 + if self.bus_data in (0xCB, 0xED, 0xDD, 0xFD): + return self.state_PRE1 + else: + return self.state_OPCODE + + def state_PRE1(self): + if self.prev_cycle != Cycle.FETCH: + self.mnemonic = 'Prefix not followed by fetch' + self.ann_dasm = Ann.WARN + return self.state_RESTART + self.op_prefix = self.pend_data + if self.op_prefix in (0xDD, 0xFD): + if self.bus_data == 0xCB: + return self.state_PRE2 + if self.bus_data in (0xDD, 0xED, 0xFD): + return self.state_PRE1 + return self.state_OPCODE + + def state_PRE2(self): + if self.prev_cycle != Cycle.MEMRD: + self.mnemonic = 'Missing displacement' + self.ann_dasm = Ann.WARN + return self.state_RESTART + self.op_prefix = (self.op_prefix << 8) | self.pend_data + return self.state_PREDIS + + def state_PREDIS(self): + if self.prev_cycle != Cycle.MEMRD: + self.mnemonic = 'Missing opcode' + self.ann_dasm = Ann.WARN + return self.state_RESTART + self.arg_dis = signed_byte(self.pend_data) + return self.state_OPCODE + + def state_OPCODE(self): + (table, self.arg_reg) = instr_table_by_prefix[self.op_prefix] + self.op_prefix = 0 + instruction = table.get(self.pend_data, None) + if instruction is None: + self.mnemonic = 'Invalid instruction' + self.ann_dasm = Ann.WARN + return self.state_RESTART + (self.want_dis, self.want_imm, self.want_read, want_write, + self.op_repeat, self.mnemonic) = instruction + self.want_write = abs(want_write) + self.want_wr_be = (want_write < 0) + if self.want_dis > 0: + return self.state_POSTDIS + if self.want_imm > 0: + return self.state_IMM1 + self.ann_dasm = Ann.INSTR + if self.want_read > 0 and self.prev_cycle in (Cycle.MEMRD, Cycle.IORD): + return self.state_ROP1 + if self.want_write > 0 and self.prev_cycle in (Cycle.MEMWR, Cycle.IOWR): + return self.state_WOP1 + return self.state_RESTART + + def state_POSTDIS(self): + self.arg_dis = signed_byte(self.pend_data) + if self.want_imm > 0: + return self.state_IMM1 + self.ann_dasm = Ann.INSTR + if self.want_read > 0 and self.prev_cycle in (Cycle.MEMRD, Cycle.IORD): + return self.state_ROP1 + if self.want_write > 0 and self.prev_cycle in (Cycle.MEMWR, Cycle.IOWR): + return self.state_WOP1 + return self.state_RESTART + + def state_IMM1(self): + self.arg_imm = self.pend_data + if self.want_imm > 1: + return self.state_IMM2 + self.ann_dasm = Ann.INSTR + if self.want_read > 0 and self.prev_cycle in (Cycle.MEMRD, Cycle.IORD): + return self.state_ROP1 + if self.want_write > 0 and self.prev_cycle in (Cycle.MEMWR, Cycle.IOWR): + return self.state_WOP1 + return self.state_RESTART + + def state_IMM2(self): + self.arg_imm |= self.pend_data << 8 + self.ann_dasm = Ann.INSTR + if self.want_read > 0 and self.prev_cycle in (Cycle.MEMRD, Cycle.IORD): + return self.state_ROP1 + if self.want_write > 0 and self.prev_cycle in (Cycle.MEMWR, Cycle.IOWR): + return self.state_WOP1 + return self.state_RESTART + + def state_ROP1(self): + self.arg_read = self.pend_data + if self.want_read < 2: + self.mnemonic = '{ro:02X}' + self.ann_dasm = Ann.ROP + if self.want_write > 0: + return self.state_WOP1 + if self.want_read > 1: + return self.state_ROP2 + if self.op_repeat and self.prev_cycle in (Cycle.MEMRD, Cycle.IORD): + return self.state_ROP1 + return self.state_RESTART + + def state_ROP2(self): + self.arg_read |= self.pend_data << 8 + self.mnemonic = '{ro:04X}' + self.ann_dasm = Ann.ROP + if self.want_write > 0 and self.prev_cycle in (Cycle.MEMWR, Cycle.IOWR): + return self.state_WOP1 + return self.state_RESTART + + def state_WOP1(self): + self.arg_write = self.pend_data + if self.want_read > 1: + return self.state_ROP2 + if self.want_write > 1: + return self.state_WOP2 + self.mnemonic = '{wo:02X}' + self.ann_dasm = Ann.WOP + if self.want_read > 0 and self.op_repeat and \ + self.prev_cycle in (Cycle.MEMRD, Cycle.IORD): + return self.state_ROP1 + return self.state_RESTART + + def state_WOP2(self): + if self.want_wr_be: + self.arg_write = (self.arg_write << 8) | self.pend_data + else: + self.arg_write |= self.pend_data << 8 + self.mnemonic = '{wo:04X}' + self.ann_dasm = Ann.WOP + return self.state_RESTART diff --git a/libsigrokdecode4DSL/decoders/z80/tables.py b/libsigrokdecode4DSL/decoders/z80/tables.py new file mode 100644 index 00000000..ec77dae3 --- /dev/null +++ b/libsigrokdecode4DSL/decoders/z80/tables.py @@ -0,0 +1,1083 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 Daniel Elstner +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 3 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +''' +Instruction tuple: (d, i, ro, wo, rep, format string) + + The placeholders d and i are the number of bytes in the instruction + used for the displacement and the immediate operand, respectively. An + operand consisting of more than one byte is assembled in little endian + order. + The placeholders ro and wo are the number of bytes the instruction + is expected to read or write, respectively. These counts are used + for both memory and I/O access, but not for immediate operands. + A negative value indicates that the operand byte order is big endian + rather than the usual little endian. + The placeholder rep is a boolean used to mark repeating instructions. + The format string should refer to the {d} and {i} operands by name. + Displacements are interpreted as signed integers, whereas immediate + operands are always read as unsigned. The tables for instructions + operating on the IX/IY index registers additionally use {r} in the + format string as a placeholder for the register name. + Relative jump instructions may specify {j} instead of {d} to output + the displacement relative to the start of the instruction. +''' + +# Instructions without a prefix +main_instructions = { + 0x00: (0, 0, 0, 0, False, 'NOP'), + 0x01: (0, 2, 0, 0, False, 'LD BC,{i:04H}h'), + 0x02: (0, 0, 0, 1, False, 'LD (BC),A'), + 0x03: (0, 0, 0, 0, False, 'INC BC'), + 0x04: (0, 0, 0, 0, False, 'INC B'), + 0x05: (0, 0, 0, 0, False, 'DEC B'), + 0x06: (0, 1, 0, 0, False, 'LD B,{i:02H}h'), + 0x07: (0, 0, 0, 0, False, 'RLCA'), + 0x08: (0, 0, 0, 0, False, 'EX AF,AF\''), + 0x09: (0, 0, 0, 0, False, 'ADD HL,BC'), + 0x0A: (0, 0, 1, 0, False, 'LD A,(BC)'), + 0x0B: (0, 0, 0, 0, False, 'DEC BC'), + 0x0C: (0, 0, 0, 0, False, 'INC C'), + 0x0D: (0, 0, 0, 0, False, 'DEC C'), + 0x0E: (0, 1, 0, 0, False, 'LD C,{i:02H}h'), + 0x0F: (0, 0, 0, 0, False, 'RRCA'), + + 0x10: (1, 0, 0, 0, False, 'DJNZ ${j:+d}'), + 0x11: (0, 2, 0, 0, False, 'LD DE,{i:04H}h'), + 0x12: (0, 0, 0, 1, False, 'LD (DE),A'), + 0x13: (0, 0, 0, 0, False, 'INC DE'), + 0x14: (0, 0, 0, 0, False, 'INC D'), + 0x15: (0, 0, 0, 0, False, 'DEC D'), + 0x16: (0, 1, 0, 0, False, 'LD D,{i:02H}h'), + 0x17: (0, 0, 0, 0, False, 'RLA'), + 0x18: (1, 0, 0, 0, False, 'JR ${j:+d}'), + 0x19: (0, 0, 0, 0, False, 'ADD HL,DE'), + 0x1A: (0, 0, 1, 0, False, 'LD A,(DE)'), + 0x1B: (0, 0, 0, 0, False, 'DEC DE'), + 0x1C: (0, 0, 0, 0, False, 'INC E'), + 0x1D: (0, 0, 0, 0, False, 'DEC E'), + 0x1E: (0, 1, 0, 0, False, 'LD E,{i:02H}h'), + 0x1F: (0, 0, 0, 0, False, 'RRA'), + + 0x20: (1, 0, 0, 0, False, 'JR NZ,${j:+d}'), + 0x21: (0, 2, 0, 0, False, 'LD HL,{i:04H}h'), + 0x22: (0, 2, 0, 2, False, 'LD ({i:04H}h),HL'), + 0x23: (0, 0, 0, 0, False, 'INC HL'), + 0x24: (0, 0, 0, 0, False, 'INC H'), + 0x25: (0, 0, 0, 0, False, 'DEC H'), + 0x26: (0, 1, 0, 0, False, 'LD H,{i:02H}h'), + 0x27: (0, 0, 0, 0, False, 'DAA'), + 0x28: (1, 0, 0, 0, False, 'JR Z,${j:+d}'), + 0x29: (0, 0, 0, 0, False, 'ADD HL,HL'), + 0x2A: (0, 2, 2, 0, False, 'LD HL,({i:04H}h)'), + 0x2B: (0, 0, 0, 0, False, 'DEC HL'), + 0x2C: (0, 0, 0, 0, False, 'INC L'), + 0x2D: (0, 0, 0, 0, False, 'DEC L'), + 0x2E: (0, 1, 0, 0, False, 'LD L,{i:02H}h'), + 0x2F: (0, 0, 0, 0, False, 'CPL'), + + 0x30: (1, 0, 0, 0, False, 'JR NC,${j:+d}'), + 0x31: (0, 2, 0, 0, False, 'LD SP,{i:04H}h'), + 0x32: (0, 2, 0, 1, False, 'LD ({i:04H}h),A'), + 0x33: (0, 0, 0, 0, False, 'INC SP'), + 0x34: (0, 0, 1, 1, False, 'INC (HL)'), + 0x35: (0, 0, 1, 1, False, 'DEC (HL)'), + 0x36: (0, 1, 0, 1, False, 'LD (HL),{i:02H}h'), + 0x37: (0, 0, 0, 0, False, 'SCF'), + 0x38: (1, 0, 0, 0, False, 'JR C,${j:+d}'), + 0x39: (0, 0, 0, 0, False, 'ADD HL,SP'), + 0x3A: (0, 2, 1, 0, False, 'LD A,({i:04H}h)'), + 0x3B: (0, 0, 0, 0, False, 'DEC SP'), + 0x3C: (0, 0, 0, 0, False, 'INC A'), + 0x3D: (0, 0, 0, 0, False, 'DEC A'), + 0x3E: (0, 1, 0, 0, False, 'LD A,{i:02H}h'), + 0x3F: (0, 0, 0, 0, False, 'CCF'), + + 0x40: (0, 0, 0, 0, False, 'LD B,B'), + 0x41: (0, 0, 0, 0, False, 'LD B,C'), + 0x42: (0, 0, 0, 0, False, 'LD B,D'), + 0x43: (0, 0, 0, 0, False, 'LD B,E'), + 0x44: (0, 0, 0, 0, False, 'LD B,H'), + 0x45: (0, 0, 0, 0, False, 'LD B,L'), + 0x46: (0, 0, 1, 0, False, 'LD B,(HL)'), + 0x47: (0, 0, 0, 0, False, 'LD B,A'), + 0x48: (0, 0, 0, 0, False, 'LD C,B'), + 0x49: (0, 0, 0, 0, False, 'LD C,C'), + 0x4A: (0, 0, 0, 0, False, 'LD C,D'), + 0x4B: (0, 0, 0, 0, False, 'LD C,E'), + 0x4C: (0, 0, 0, 0, False, 'LD C,H'), + 0x4D: (0, 0, 0, 0, False, 'LD C,L'), + 0x4E: (0, 0, 1, 0, False, 'LD C,(HL)'), + 0x4F: (0, 0, 0, 0, False, 'LD C,A'), + + 0x50: (0, 0, 0, 0, False, 'LD D,B'), + 0x51: (0, 0, 0, 0, False, 'LD D,C'), + 0x52: (0, 0, 0, 0, False, 'LD D,D'), + 0x53: (0, 0, 0, 0, False, 'LD D,E'), + 0x54: (0, 0, 0, 0, False, 'LD D,H'), + 0x55: (0, 0, 0, 0, False, 'LD D,L'), + 0x56: (0, 0, 1, 0, False, 'LD D,(HL)'), + 0x57: (0, 0, 0, 0, False, 'LD D,A'), + 0x58: (0, 0, 0, 0, False, 'LD E,B'), + 0x59: (0, 0, 0, 0, False, 'LD E,C'), + 0x5A: (0, 0, 0, 0, False, 'LD E,D'), + 0x5B: (0, 0, 0, 0, False, 'LD E,E'), + 0x5C: (0, 0, 0, 0, False, 'LD E,H'), + 0x5D: (0, 0, 0, 0, False, 'LD E,L'), + 0x5E: (0, 0, 1, 0, False, 'LD E,(HL)'), + 0x5F: (0, 0, 0, 0, False, 'LD E,A'), + + 0x60: (0, 0, 0, 0, False, 'LD H,B'), + 0x61: (0, 0, 0, 0, False, 'LD H,C'), + 0x62: (0, 0, 0, 0, False, 'LD H,D'), + 0x63: (0, 0, 0, 0, False, 'LD H,E'), + 0x64: (0, 0, 0, 0, False, 'LD H,H'), + 0x65: (0, 0, 0, 0, False, 'LD H,L'), + 0x66: (0, 0, 1, 0, False, 'LD H,(HL)'), + 0x67: (0, 0, 0, 0, False, 'LD H,A'), + 0x68: (0, 0, 0, 0, False, 'LD L,B'), + 0x69: (0, 0, 0, 0, False, 'LD L,C'), + 0x6A: (0, 0, 0, 0, False, 'LD L,D'), + 0x6B: (0, 0, 0, 0, False, 'LD L,E'), + 0x6C: (0, 0, 0, 0, False, 'LD L,H'), + 0x6D: (0, 0, 0, 0, False, 'LD L,L'), + 0x6E: (0, 0, 1, 0, False, 'LD L,(HL)'), + 0x6F: (0, 0, 0, 0, False, 'LD L,A'), + + 0x70: (0, 0, 0, 1, False, 'LD (HL),B'), + 0x71: (0, 0, 0, 1, False, 'LD (HL),C'), + 0x72: (0, 0, 0, 1, False, 'LD (HL),D'), + 0x73: (0, 0, 0, 1, False, 'LD (HL),E'), + 0x74: (0, 0, 0, 1, False, 'LD (HL),H'), + 0x75: (0, 0, 0, 1, False, 'LD (HL),L'), + 0x76: (0, 0, 0, 0, False, 'HALT'), + 0x77: (0, 0, 0, 1, False, 'LD (HL),A'), + 0x78: (0, 0, 0, 0, False, 'LD A,B'), + 0x79: (0, 0, 0, 0, False, 'LD A,C'), + 0x7A: (0, 0, 0, 0, False, 'LD A,D'), + 0x7B: (0, 0, 0, 0, False, 'LD A,E'), + 0x7C: (0, 0, 0, 0, False, 'LD A,H'), + 0x7D: (0, 0, 0, 0, False, 'LD A,L'), + 0x7E: (0, 0, 1, 0, False, 'LD A,(HL)'), + 0x7F: (0, 0, 0, 0, False, 'LD A,A'), + + 0x80: (0, 0, 0, 0, False, 'ADD A,B'), + 0x81: (0, 0, 0, 0, False, 'ADD A,C'), + 0x82: (0, 0, 0, 0, False, 'ADD A,D'), + 0x83: (0, 0, 0, 0, False, 'ADD A,E'), + 0x84: (0, 0, 0, 0, False, 'ADD A,H'), + 0x85: (0, 0, 0, 0, False, 'ADD A,L'), + 0x86: (0, 0, 1, 0, False, 'ADD A,(HL)'), + 0x87: (0, 0, 0, 0, False, 'ADD A,A'), + 0x88: (0, 0, 0, 0, False, 'ADC A,B'), + 0x89: (0, 0, 0, 0, False, 'ADC A,C'), + 0x8A: (0, 0, 0, 0, False, 'ADC A,D'), + 0x8B: (0, 0, 0, 0, False, 'ADC A,E'), + 0x8C: (0, 0, 0, 0, False, 'ADC A,H'), + 0x8D: (0, 0, 0, 0, False, 'ADC A,L'), + 0x8E: (0, 0, 1, 0, False, 'ADC A,(HL)'), + 0x8F: (0, 0, 0, 0, False, 'ADC A,A'), + + 0x90: (0, 0, 0, 0, False, 'SUB B'), + 0x91: (0, 0, 0, 0, False, 'SUB C'), + 0x92: (0, 0, 0, 0, False, 'SUB D'), + 0x93: (0, 0, 0, 0, False, 'SUB E'), + 0x94: (0, 0, 0, 0, False, 'SUB H'), + 0x95: (0, 0, 0, 0, False, 'SUB L'), + 0x96: (0, 0, 1, 0, False, 'SUB (HL)'), + 0x97: (0, 0, 0, 0, False, 'SUB A'), + 0x98: (0, 0, 0, 0, False, 'SBC A,B'), + 0x99: (0, 0, 0, 0, False, 'SBC A,C'), + 0x9A: (0, 0, 0, 0, False, 'SBC A,D'), + 0x9B: (0, 0, 0, 0, False, 'SBC A,E'), + 0x9C: (0, 0, 0, 0, False, 'SBC A,H'), + 0x9D: (0, 0, 0, 0, False, 'SBC A,L'), + 0x9E: (0, 0, 1, 0, False, 'SBC A,(HL)'), + 0x9F: (0, 0, 0, 0, False, 'SBC A,A'), + + 0xA0: (0, 0, 0, 0, False, 'AND B'), + 0xA1: (0, 0, 0, 0, False, 'AND C'), + 0xA2: (0, 0, 0, 0, False, 'AND D'), + 0xA3: (0, 0, 0, 0, False, 'AND E'), + 0xA4: (0, 0, 0, 0, False, 'AND H'), + 0xA5: (0, 0, 0, 0, False, 'AND L'), + 0xA6: (0, 0, 1, 0, False, 'AND (HL)'), + 0xA7: (0, 0, 0, 0, False, 'AND A'), + 0xA8: (0, 0, 0, 0, False, 'XOR B'), + 0xA9: (0, 0, 0, 0, False, 'XOR C'), + 0xAA: (0, 0, 0, 0, False, 'XOR D'), + 0xAB: (0, 0, 0, 0, False, 'XOR E'), + 0xAC: (0, 0, 0, 0, False, 'XOR H'), + 0xAD: (0, 0, 0, 0, False, 'XOR L'), + 0xAE: (0, 0, 1, 0, False, 'XOR (HL)'), + 0xAF: (0, 0, 0, 0, False, 'XOR A'), + + 0xB0: (0, 0, 0, 0, False, 'OR B'), + 0xB1: (0, 0, 0, 0, False, 'OR C'), + 0xB2: (0, 0, 0, 0, False, 'OR D'), + 0xB3: (0, 0, 0, 0, False, 'OR E'), + 0xB4: (0, 0, 0, 0, False, 'OR H'), + 0xB5: (0, 0, 0, 0, False, 'OR L'), + 0xB6: (0, 0, 1, 0, False, 'OR (HL)'), + 0xB7: (0, 0, 0, 0, False, 'OR A'), + 0xB8: (0, 0, 0, 0, False, 'CP B'), + 0xB9: (0, 0, 0, 0, False, 'CP C'), + 0xBA: (0, 0, 0, 0, False, 'CP D'), + 0xBB: (0, 0, 0, 0, False, 'CP E'), + 0xBC: (0, 0, 0, 0, False, 'CP H'), + 0xBD: (0, 0, 0, 0, False, 'CP L'), + 0xBE: (0, 0, 1, 0, False, 'CP (HL)'), + 0xBF: (0, 0, 0, 0, False, 'CP A'), + + 0xC0: (0, 0, 2, 0, False, 'RET NZ'), + 0xC1: (0, 0, 2, 0, False, 'POP BC'), + 0xC2: (0, 2, 0, 0, False, 'JP NZ,{i:04H}h'), + 0xC3: (0, 2, 0, 0, False, 'JP {i:04H}h'), + 0xC4: (0, 2, 0,-2, False, 'CALL NZ,{i:04H}h'), + 0xC5: (0, 0, 0,-2, False, 'PUSH BC'), + 0xC6: (0, 1, 0, 0, False, 'ADD A,{i:02H}h'), + 0xC7: (0, 0, 0,-2, False, 'RST 00h'), + 0xC8: (0, 0, 2, 0, False, 'RET Z'), + 0xC9: (0, 0, 2, 0, False, 'RET'), + 0xCA: (0, 2, 0, 0, False, 'JP Z,{i:04H}h'), + + 0xCC: (0, 2, 0,-2, False, 'CALL Z,{i:04H}h'), + 0xCD: (0, 2, 0,-2, False, 'CALL {i:04H}h'), + 0xCE: (0, 1, 0, 0, False, 'ADC A,{i:02H}h'), + 0xCF: (0, 0, 0,-2, False, 'RST 08h'), + + 0xD0: (0, 0, 2, 0, False, 'RET NC'), + 0xD1: (0, 0, 2, 0, False, 'POP DE'), + 0xD2: (0, 2, 0, 0, False, 'JP NC,{i:04H}h'), + 0xD3: (0, 1, 0, 1, False, 'OUT ({i:02H}h),A'), + 0xD4: (0, 2, 0,-2, False, 'CALL NC,{i:04H}h'), + 0xD5: (0, 0, 0,-2, False, 'PUSH DE'), + 0xD6: (0, 1, 0, 0, False, 'SUB {i:02H}h'), + 0xD7: (0, 0, 0,-2, False, 'RST 10h'), + 0xD8: (0, 0, 2, 0, False, 'RET C'), + 0xD9: (0, 0, 0, 0, False, 'EXX'), + 0xDA: (0, 2, 0, 0, False, 'JP C,{i:04H}h'), + 0xDB: (0, 1, 1, 0, False, 'IN A,({i:02H}h)'), + 0xDC: (0, 2, 0,-2, False, 'CALL C,{i:04H}h'), + + 0xDE: (0, 1, 0, 0, False, 'SBC A,{i:02H}h'), + 0xDF: (0, 0, 0,-2, False, 'RST 18h'), + + 0xE0: (0, 0, 2, 0, False, 'RET PO'), + 0xE1: (0, 0, 2, 0, False, 'POP HL'), + 0xE2: (0, 2, 0, 0, False, 'JP PO,{i:04H}h'), + 0xE3: (0, 0, 2, 2, False, 'EX (SP),HL'), + 0xE4: (0, 2, 0,-2, False, 'CALL PO,{i:04H}h'), + 0xE5: (0, 0, 0,-2, False, 'PUSH HL'), + 0xE6: (0, 1, 0, 0, False, 'AND {i:02H}h'), + 0xE7: (0, 0, 0,-2, False, 'RST 20h'), + 0xE8: (0, 0, 2, 0, False, 'RET PE'), + 0xE9: (0, 0, 0, 0, False, 'JP (HL)'), + 0xEA: (0, 2, 0, 0, False, 'JP PE,{i:04H}h'), + 0xEB: (0, 0, 0, 0, False, 'EX DE,HL'), + 0xEC: (0, 2, 0,-2, False, 'CALL PE,{i:04H}h'), + + 0xEE: (0, 1, 0, 0, False, 'XOR {i:02H}h'), + 0xEF: (0, 0, 0,-2, False, 'RST 28h'), + + 0xF0: (0, 0, 2, 0, False, 'RET P'), + 0xF1: (0, 0, 2, 0, False, 'POP AF'), + 0xF2: (0, 2, 0, 0, False, 'JP P,{i:04H}h'), + 0xF3: (0, 0, 0, 0, False, 'DI'), + 0xF4: (0, 2, 0,-2, False, 'CALL P,{i:04H}h'), + 0xF5: (0, 0, 0,-2, False, 'PUSH AF'), + 0xF6: (0, 1, 0, 0, False, 'OR {i:02H}h'), + 0xF7: (0, 0, 0,-2, False, 'RST 30h'), + 0xF8: (0, 0, 2, 0, False, 'RET M'), + 0xF9: (0, 0, 0, 0, False, 'LD SP,HL'), + 0xFA: (0, 2, 0, 0, False, 'JP M,{i:04H}h'), + 0xFB: (0, 0, 0, 0, False, 'EI'), + 0xFC: (0, 2, 0,-2, False, 'CALL M,{i:04H}h'), + + 0xFE: (0, 1, 0, 0, False, 'CP {i:02H}h'), + 0xFF: (0, 0, 0,-2, False, 'RST 38h') +} + +# Instructions with ED prefix +extended_instructions = { + 0x40: (0, 0, 1, 0, False, 'IN B,(C)'), + 0x41: (0, 0, 0, 1, False, 'OUT (C),B'), + 0x42: (0, 0, 0, 0, False, 'SBC HL,BC'), + 0x43: (0, 2, 0, 2, False, 'LD ({i:04H}h),BC'), + 0x44: (0, 0, 0, 0, False, 'NEG'), + 0x45: (0, 0, 2, 0, False, 'RETN'), + 0x46: (0, 0, 0, 0, False, 'IM 0'), + 0x47: (0, 0, 0, 0, False, 'LD I,A'), + 0x48: (0, 0, 1, 0, False, 'IN C,(C)'), + 0x49: (0, 0, 0, 1, False, 'OUT (C),C'), + 0x4A: (0, 0, 0, 0, False, 'ADC HL,BC'), + 0x4B: (0, 2, 2, 0, False, 'LD BC,({i:04H}h)'), + 0x4C: (0, 0, 0, 0, False, 'NEG'), + 0x4D: (0, 0, 2, 0, False, 'RETI'), + 0x4E: (0, 0, 0, 0, False, 'IM 0/1'), + 0x4F: (0, 0, 0, 0, False, 'LD R,A'), + + 0x50: (0, 0, 1, 0, False, 'IN D,(C)'), + 0x51: (0, 0, 0, 1, False, 'OUT (C),D'), + 0x52: (0, 0, 0, 0, False, 'SBC HL,DE'), + 0x53: (0, 2, 0, 2, False, 'LD ({i:04H}h),DE'), + 0x54: (0, 0, 0, 0, False, 'NEG'), + 0x55: (0, 0, 2, 0, False, 'RETN'), + 0x56: (0, 0, 0, 0, False, 'IM 1'), + 0x57: (0, 0, 0, 0, False, 'LD A,I'), + 0x58: (0, 0, 1, 0, False, 'IN E,(C)'), + 0x59: (0, 0, 0, 1, False, 'OUT (C),E'), + 0x5A: (0, 0, 0, 0, False, 'ADC HL,DE'), + 0x5B: (0, 2, 2, 0, False, 'LD DE,({i:04H}h)'), + 0x5C: (0, 0, 0, 0, False, 'NEG'), + 0x5D: (0, 0, 2, 0, False, 'RETN'), + 0x5E: (0, 0, 0, 0, False, 'IM 2'), + 0x5F: (0, 0, 0, 0, False, 'LD A,R'), + + 0x60: (0, 0, 1, 0, False, 'IN H,(C)'), + 0x61: (0, 0, 0, 1, False, 'OUT (C),H'), + 0x62: (0, 0, 0, 0, False, 'SBC HL,HL'), + 0x63: (0, 2, 0, 2, False, 'LD ({i:04H}h),HL'), + 0x64: (0, 0, 0, 0, False, 'NEG'), + 0x65: (0, 0, 2, 0, False, 'RETN'), + 0x66: (0, 0, 0, 0, False, 'IM 0'), + 0x67: (0, 0, 1, 1, False, 'RRD'), + 0x68: (0, 0, 1, 0, False, 'IN L,(C)'), + 0x69: (0, 0, 0, 1, False, 'OUT (C),L'), + 0x6A: (0, 0, 0, 0, False, 'ADC HL,HL'), + 0x6B: (0, 2, 2, 0, False, 'LD HL,({i:04H}h)'), + 0x6C: (0, 0, 0, 0, False, 'NEG'), + 0x6D: (0, 0, 2, 0, False, 'RETN'), + 0x6E: (0, 0, 0, 0, False, 'IM 0/1'), + 0x6F: (0, 0, 1, 1, False, 'RLD'), + + 0x70: (0, 0, 1, 0, False, 'IN (C)'), + 0x71: (0, 0, 0, 1, False, 'OUT (C),0'), + 0x72: (0, 0, 0, 0, False, 'SBC HL,SP'), + 0x73: (0, 2, 0, 2, False, 'LD ({i:04H}h),SP'), + 0x74: (0, 0, 0, 0, False, 'NEG'), + 0x75: (0, 0, 2, 0, False, 'RETN'), + 0x76: (0, 0, 0, 0, False, 'IM 1'), + + 0x78: (0, 0, 1, 0, False, 'IN A,(C)'), + 0x79: (0, 0, 0, 1, False, 'OUT (C),A'), + 0x7A: (0, 0, 0, 0, False, 'ADC HL,SP'), + 0x7B: (0, 2, 2, 0, False, 'LD SP,({i:04H}h)'), + 0x7C: (0, 0, 0, 0, False, 'NEG'), + 0x7D: (0, 0, 2, 0, False, 'RETN'), + 0x7E: (0, 0, 0, 0, False, 'IM 2'), + + 0xA0: (0, 0, 1, 1, False, 'LDI'), + 0xA1: (0, 0, 1, 0, False, 'CPI'), + 0xA2: (0, 0, 1, 1, False, 'INI'), + 0xA3: (0, 0, 1, 1, False, 'OUTI'), + + 0xA8: (0, 0, 1, 1, False, 'LDD'), + 0xA9: (0, 0, 1, 0, False, 'CPD'), + 0xAA: (0, 0, 1, 1, False, 'IND'), + 0xAB: (0, 0, 1, 1, False, 'OUTD'), + + 0xB0: (0, 0, 1, 1, True, 'LDIR'), + 0xB1: (0, 0, 1, 0, True, 'CPIR'), + 0xB2: (0, 0, 1, 1, True, 'INIR'), + 0xB3: (0, 0, 1, 1, True, 'OTIR'), + + 0xB8: (0, 0, 1, 1, True, 'LDDR'), + 0xB9: (0, 0, 1, 0, True, 'CPDR'), + 0xBA: (0, 0, 1, 1, True, 'INDR'), + 0xBB: (0, 0, 1, 1, True, 'OTDR') +} + +# Instructions with CB prefix +bit_instructions = { + 0x00: (0, 0, 0, 0, False, 'RLC B'), + 0x01: (0, 0, 0, 0, False, 'RLC C'), + 0x02: (0, 0, 0, 0, False, 'RLC D'), + 0x03: (0, 0, 0, 0, False, 'RLC E'), + 0x04: (0, 0, 0, 0, False, 'RLC H'), + 0x05: (0, 0, 0, 0, False, 'RLC L'), + 0x06: (0, 0, 1, 1, False, 'RLC (HL)'), + 0x07: (0, 0, 0, 0, False, 'RLC A'), + 0x08: (0, 0, 0, 0, False, 'RRC B'), + 0x09: (0, 0, 0, 0, False, 'RRC C'), + 0x0A: (0, 0, 0, 0, False, 'RRC D'), + 0x0B: (0, 0, 0, 0, False, 'RRC E'), + 0x0C: (0, 0, 0, 0, False, 'RRC H'), + 0x0D: (0, 0, 0, 0, False, 'RRC L'), + 0x0E: (0, 0, 1, 1, False, 'RRC (HL)'), + 0x0F: (0, 0, 0, 0, False, 'RRC A'), + + 0x10: (0, 0, 0, 0, False, 'RL B'), + 0x11: (0, 0, 0, 0, False, 'RL C'), + 0x12: (0, 0, 0, 0, False, 'RL D'), + 0x13: (0, 0, 0, 0, False, 'RL E'), + 0x14: (0, 0, 0, 0, False, 'RL H'), + 0x15: (0, 0, 0, 0, False, 'RL L'), + 0x16: (0, 0, 1, 1, False, 'RL (HL)'), + 0x17: (0, 0, 0, 0, False, 'RL A'), + 0x18: (0, 0, 0, 0, False, 'RR B'), + 0x19: (0, 0, 0, 0, False, 'RR C'), + 0x1A: (0, 0, 0, 0, False, 'RR D'), + 0x1B: (0, 0, 0, 0, False, 'RR E'), + 0x1C: (0, 0, 0, 0, False, 'RR H'), + 0x1D: (0, 0, 0, 0, False, 'RR L'), + 0x1E: (0, 0, 1, 1, False, 'RR (HL)'), + 0x1F: (0, 0, 0, 0, False, 'RR A'), + + 0x20: (0, 0, 0, 0, False, 'SLA B'), + 0x21: (0, 0, 0, 0, False, 'SLA C'), + 0x22: (0, 0, 0, 0, False, 'SLA D'), + 0x23: (0, 0, 0, 0, False, 'SLA E'), + 0x24: (0, 0, 0, 0, False, 'SLA H'), + 0x25: (0, 0, 0, 0, False, 'SLA L'), + 0x26: (0, 0, 1, 1, False, 'SLA (HL)'), + 0x27: (0, 0, 0, 0, False, 'SLA A'), + 0x28: (0, 0, 0, 0, False, 'SRA B'), + 0x29: (0, 0, 0, 0, False, 'SRA C'), + 0x2A: (0, 0, 0, 0, False, 'SRA D'), + 0x2B: (0, 0, 0, 0, False, 'SRA E'), + 0x2C: (0, 0, 0, 0, False, 'SRA H'), + 0x2D: (0, 0, 0, 0, False, 'SRA L'), + 0x2E: (0, 0, 1, 1, False, 'SRA (HL)'), + 0x2F: (0, 0, 0, 0, False, 'SRA A'), + + 0x30: (0, 0, 0, 0, False, 'SLL B'), + 0x31: (0, 0, 0, 0, False, 'SLL C'), + 0x32: (0, 0, 0, 0, False, 'SLL D'), + 0x33: (0, 0, 0, 0, False, 'SLL E'), + 0x34: (0, 0, 0, 0, False, 'SLL H'), + 0x35: (0, 0, 0, 0, False, 'SLL L'), + 0x36: (0, 0, 1, 1, False, 'SLL (HL)'), + 0x37: (0, 0, 0, 0, False, 'SLL A'), + 0x38: (0, 0, 0, 0, False, 'SRL B'), + 0x39: (0, 0, 0, 0, False, 'SRL C'), + 0x3A: (0, 0, 0, 0, False, 'SRL D'), + 0x3B: (0, 0, 0, 0, False, 'SRL E'), + 0x3C: (0, 0, 0, 0, False, 'SRL H'), + 0x3D: (0, 0, 0, 0, False, 'SRL L'), + 0x3E: (0, 0, 1, 1, False, 'SRL (HL)'), + 0x3F: (0, 0, 0, 0, False, 'SRL A'), + + 0x40: (0, 0, 0, 0, False, 'BIT 0,B'), + 0x41: (0, 0, 0, 0, False, 'BIT 0,C'), + 0x42: (0, 0, 0, 0, False, 'BIT 0,D'), + 0x43: (0, 0, 0, 0, False, 'BIT 0,E'), + 0x44: (0, 0, 0, 0, False, 'BIT 0,H'), + 0x45: (0, 0, 0, 0, False, 'BIT 0,L'), + 0x46: (0, 0, 1, 0, False, 'BIT 0,(HL)'), + 0x47: (0, 0, 0, 0, False, 'BIT 0,A'), + 0x48: (0, 0, 0, 0, False, 'BIT 1,B'), + 0x49: (0, 0, 0, 0, False, 'BIT 1,C'), + 0x4A: (0, 0, 0, 0, False, 'BIT 1,D'), + 0x4B: (0, 0, 0, 0, False, 'BIT 1,E'), + 0x4C: (0, 0, 0, 0, False, 'BIT 1,H'), + 0x4D: (0, 0, 0, 0, False, 'BIT 1,L'), + 0x4E: (0, 0, 1, 0, False, 'BIT 1,(HL)'), + 0x4F: (0, 0, 0, 0, False, 'BIT 1,A'), + + 0x50: (0, 0, 0, 0, False, 'BIT 2,B'), + 0x51: (0, 0, 0, 0, False, 'BIT 2,C'), + 0x52: (0, 0, 0, 0, False, 'BIT 2,D'), + 0x53: (0, 0, 0, 0, False, 'BIT 2,E'), + 0x54: (0, 0, 0, 0, False, 'BIT 2,H'), + 0x55: (0, 0, 0, 0, False, 'BIT 2,L'), + 0x56: (0, 0, 1, 0, False, 'BIT 2,(HL)'), + 0x57: (0, 0, 0, 0, False, 'BIT 2,A'), + 0x58: (0, 0, 0, 0, False, 'BIT 3,B'), + 0x59: (0, 0, 0, 0, False, 'BIT 3,C'), + 0x5A: (0, 0, 0, 0, False, 'BIT 3,D'), + 0x5B: (0, 0, 0, 0, False, 'BIT 3,E'), + 0x5C: (0, 0, 0, 0, False, 'BIT 3,H'), + 0x5D: (0, 0, 0, 0, False, 'BIT 3,L'), + 0x5E: (0, 0, 1, 0, False, 'BIT 3,(HL)'), + 0x5F: (0, 0, 0, 0, False, 'BIT 3,A'), + + 0x60: (0, 0, 0, 0, False, 'BIT 4,B'), + 0x61: (0, 0, 0, 0, False, 'BIT 4,C'), + 0x62: (0, 0, 0, 0, False, 'BIT 4,D'), + 0x63: (0, 0, 0, 0, False, 'BIT 4,E'), + 0x64: (0, 0, 0, 0, False, 'BIT 4,H'), + 0x65: (0, 0, 0, 0, False, 'BIT 4,L'), + 0x66: (0, 0, 1, 0, False, 'BIT 4,(HL)'), + 0x67: (0, 0, 0, 0, False, 'BIT 4,A'), + 0x68: (0, 0, 0, 0, False, 'BIT 5,B'), + 0x69: (0, 0, 0, 0, False, 'BIT 5,C'), + 0x6A: (0, 0, 0, 0, False, 'BIT 5,D'), + 0x6B: (0, 0, 0, 0, False, 'BIT 5,E'), + 0x6C: (0, 0, 0, 0, False, 'BIT 5,H'), + 0x6D: (0, 0, 0, 0, False, 'BIT 5,L'), + 0x6E: (0, 0, 1, 0, False, 'BIT 5,(HL)'), + 0x6F: (0, 0, 0, 0, False, 'BIT 5,A'), + + 0x70: (0, 0, 0, 0, False, 'BIT 6,B'), + 0x71: (0, 0, 0, 0, False, 'BIT 6,C'), + 0x72: (0, 0, 0, 0, False, 'BIT 6,D'), + 0x73: (0, 0, 0, 0, False, 'BIT 6,E'), + 0x74: (0, 0, 0, 0, False, 'BIT 6,H'), + 0x75: (0, 0, 0, 0, False, 'BIT 6,L'), + 0x76: (0, 0, 1, 0, False, 'BIT 6,(HL)'), + 0x77: (0, 0, 0, 0, False, 'BIT 6,A'), + 0x78: (0, 0, 0, 0, False, 'BIT 7,B'), + 0x79: (0, 0, 0, 0, False, 'BIT 7,C'), + 0x7A: (0, 0, 0, 0, False, 'BIT 7,D'), + 0x7B: (0, 0, 0, 0, False, 'BIT 7,E'), + 0x7C: (0, 0, 0, 0, False, 'BIT 7,H'), + 0x7D: (0, 0, 0, 0, False, 'BIT 7,L'), + 0x7E: (0, 0, 1, 0, False, 'BIT 7,(HL)'), + 0x7F: (0, 0, 0, 0, False, 'BIT 7,A'), + + 0x80: (0, 0, 0, 0, False, 'RES 0,B'), + 0x81: (0, 0, 0, 0, False, 'RES 0,C'), + 0x82: (0, 0, 0, 0, False, 'RES 0,D'), + 0x83: (0, 0, 0, 0, False, 'RES 0,E'), + 0x84: (0, 0, 0, 0, False, 'RES 0,H'), + 0x85: (0, 0, 0, 0, False, 'RES 0,L'), + 0x86: (0, 0, 1, 1, False, 'RES 0,(HL)'), + 0x87: (0, 0, 0, 0, False, 'RES 0,A'), + 0x88: (0, 0, 0, 0, False, 'RES 1,B'), + 0x89: (0, 0, 0, 0, False, 'RES 1,C'), + 0x8A: (0, 0, 0, 0, False, 'RES 1,D'), + 0x8B: (0, 0, 0, 0, False, 'RES 1,E'), + 0x8C: (0, 0, 0, 0, False, 'RES 1,H'), + 0x8D: (0, 0, 0, 0, False, 'RES 1,L'), + 0x8E: (0, 0, 1, 1, False, 'RES 1,(HL)'), + 0x8F: (0, 0, 0, 0, False, 'RES 1,A'), + + 0x90: (0, 0, 0, 0, False, 'RES 2,B'), + 0x91: (0, 0, 0, 0, False, 'RES 2,C'), + 0x92: (0, 0, 0, 0, False, 'RES 2,D'), + 0x93: (0, 0, 0, 0, False, 'RES 2,E'), + 0x94: (0, 0, 0, 0, False, 'RES 2,H'), + 0x95: (0, 0, 0, 0, False, 'RES 2,L'), + 0x96: (0, 0, 1, 1, False, 'RES 2,(HL)'), + 0x97: (0, 0, 0, 0, False, 'RES 2,A'), + 0x98: (0, 0, 0, 0, False, 'RES 3,B'), + 0x99: (0, 0, 0, 0, False, 'RES 3,C'), + 0x9A: (0, 0, 0, 0, False, 'RES 3,D'), + 0x9B: (0, 0, 0, 0, False, 'RES 3,E'), + 0x9C: (0, 0, 0, 0, False, 'RES 3,H'), + 0x9D: (0, 0, 0, 0, False, 'RES 3,L'), + 0x9E: (0, 0, 1, 1, False, 'RES 3,(HL)'), + 0x9F: (0, 0, 0, 0, False, 'RES 3,A'), + + 0xA0: (0, 0, 0, 0, False, 'RES 4,B'), + 0xA1: (0, 0, 0, 0, False, 'RES 4,C'), + 0xA2: (0, 0, 0, 0, False, 'RES 4,D'), + 0xA3: (0, 0, 0, 0, False, 'RES 4,E'), + 0xA4: (0, 0, 0, 0, False, 'RES 4,H'), + 0xA5: (0, 0, 0, 0, False, 'RES 4,L'), + 0xA6: (0, 0, 1, 1, False, 'RES 4,(HL)'), + 0xA7: (0, 0, 0, 0, False, 'RES 4,A'), + 0xA8: (0, 0, 0, 0, False, 'RES 5,B'), + 0xA9: (0, 0, 0, 0, False, 'RES 5,C'), + 0xAA: (0, 0, 0, 0, False, 'RES 5,D'), + 0xAB: (0, 0, 0, 0, False, 'RES 5,E'), + 0xAC: (0, 0, 0, 0, False, 'RES 5,H'), + 0xAD: (0, 0, 0, 0, False, 'RES 5,L'), + 0xAE: (0, 0, 1, 1, False, 'RES 5,(HL)'), + 0xAF: (0, 0, 0, 0, False, 'RES 5,A'), + + 0xB0: (0, 0, 0, 0, False, 'RES 6,B'), + 0xB1: (0, 0, 0, 0, False, 'RES 6,C'), + 0xB2: (0, 0, 0, 0, False, 'RES 6,D'), + 0xB3: (0, 0, 0, 0, False, 'RES 6,E'), + 0xB4: (0, 0, 0, 0, False, 'RES 6,H'), + 0xB5: (0, 0, 0, 0, False, 'RES 6,L'), + 0xB6: (0, 0, 1, 1, False, 'RES 6,(HL)'), + 0xB7: (0, 0, 0, 0, False, 'RES 6,A'), + 0xB8: (0, 0, 0, 0, False, 'RES 7,B'), + 0xB9: (0, 0, 0, 0, False, 'RES 7,C'), + 0xBA: (0, 0, 0, 0, False, 'RES 7,D'), + 0xBB: (0, 0, 0, 0, False, 'RES 7,E'), + 0xBC: (0, 0, 0, 0, False, 'RES 7,H'), + 0xBD: (0, 0, 0, 0, False, 'RES 7,L'), + 0xBE: (0, 0, 1, 1, False, 'RES 7,(HL)'), + 0xBF: (0, 0, 0, 0, False, 'RES 7,A'), + + 0xC0: (0, 0, 0, 0, False, 'SET 0,B'), + 0xC1: (0, 0, 0, 0, False, 'SET 0,C'), + 0xC2: (0, 0, 0, 0, False, 'SET 0,D'), + 0xC3: (0, 0, 0, 0, False, 'SET 0,E'), + 0xC4: (0, 0, 0, 0, False, 'SET 0,H'), + 0xC5: (0, 0, 0, 0, False, 'SET 0,L'), + 0xC6: (0, 0, 1, 1, False, 'SET 0,(HL)'), + 0xC7: (0, 0, 0, 0, False, 'SET 0,A'), + 0xC8: (0, 0, 0, 0, False, 'SET 1,B'), + 0xC9: (0, 0, 0, 0, False, 'SET 1,C'), + 0xCA: (0, 0, 0, 0, False, 'SET 1,D'), + 0xCB: (0, 0, 0, 0, False, 'SET 1,E'), + 0xCC: (0, 0, 0, 0, False, 'SET 1,H'), + 0xCD: (0, 0, 0, 0, False, 'SET 1,L'), + 0xCE: (0, 0, 1, 1, False, 'SET 1,(HL)'), + 0xCF: (0, 0, 0, 0, False, 'SET 1,A'), + + 0xD0: (0, 0, 0, 0, False, 'SET 2,B'), + 0xD1: (0, 0, 0, 0, False, 'SET 2,C'), + 0xD2: (0, 0, 0, 0, False, 'SET 2,D'), + 0xD3: (0, 0, 0, 0, False, 'SET 2,E'), + 0xD4: (0, 0, 0, 0, False, 'SET 2,H'), + 0xD5: (0, 0, 0, 0, False, 'SET 2,L'), + 0xD6: (0, 0, 1, 1, False, 'SET 2,(HL)'), + 0xD7: (0, 0, 0, 0, False, 'SET 2,A'), + 0xD8: (0, 0, 0, 0, False, 'SET 3,B'), + 0xD9: (0, 0, 0, 0, False, 'SET 3,C'), + 0xDA: (0, 0, 0, 0, False, 'SET 3,D'), + 0xDB: (0, 0, 0, 0, False, 'SET 3,E'), + 0xDC: (0, 0, 0, 0, False, 'SET 3,H'), + 0xDD: (0, 0, 0, 0, False, 'SET 3,L'), + 0xDE: (0, 0, 1, 1, False, 'SET 3,(HL)'), + 0xDF: (0, 0, 0, 0, False, 'SET 3,A'), + + 0xE0: (0, 0, 0, 0, False, 'SET 4,B'), + 0xE1: (0, 0, 0, 0, False, 'SET 4,C'), + 0xE2: (0, 0, 0, 0, False, 'SET 4,D'), + 0xE3: (0, 0, 0, 0, False, 'SET 4,E'), + 0xE4: (0, 0, 0, 0, False, 'SET 4,H'), + 0xE5: (0, 0, 0, 0, False, 'SET 4,L'), + 0xE6: (0, 0, 1, 1, False, 'SET 4,(HL)'), + 0xE7: (0, 0, 0, 0, False, 'SET 4,A'), + 0xE8: (0, 0, 0, 0, False, 'SET 5,B'), + 0xE9: (0, 0, 0, 0, False, 'SET 5,C'), + 0xEA: (0, 0, 0, 0, False, 'SET 5,D'), + 0xEB: (0, 0, 0, 0, False, 'SET 5,E'), + 0xEC: (0, 0, 0, 0, False, 'SET 5,H'), + 0xED: (0, 0, 0, 0, False, 'SET 5,L'), + 0xEE: (0, 0, 1, 1, False, 'SET 5,(HL)'), + 0xEF: (0, 0, 0, 0, False, 'SET 5,A'), + + 0xF0: (0, 0, 0, 0, False, 'SET 6,B'), + 0xF1: (0, 0, 0, 0, False, 'SET 6,C'), + 0xF2: (0, 0, 0, 0, False, 'SET 6,D'), + 0xF3: (0, 0, 0, 0, False, 'SET 6,E'), + 0xF4: (0, 0, 0, 0, False, 'SET 6,H'), + 0xF5: (0, 0, 0, 0, False, 'SET 6,L'), + 0xF6: (0, 0, 1, 1, False, 'SET 6,(HL)'), + 0xF7: (0, 0, 0, 0, False, 'SET 6,A'), + 0xF8: (0, 0, 0, 0, False, 'SET 7,B'), + 0xF9: (0, 0, 0, 0, False, 'SET 7,C'), + 0xFA: (0, 0, 0, 0, False, 'SET 7,D'), + 0xFB: (0, 0, 0, 0, False, 'SET 7,E'), + 0xFC: (0, 0, 0, 0, False, 'SET 7,H'), + 0xFD: (0, 0, 0, 0, False, 'SET 7,L'), + 0xFE: (0, 0, 1, 1, False, 'SET 7,(HL)'), + 0xFF: (0, 0, 0, 0, False, 'SET 7,A') +} + +# Instructions with DD or FD prefix +index_instructions = { + 0x09: (0, 0, 0, 0, False, 'ADD {r},BC'), + + 0x19: (0, 0, 0, 0, False, 'ADD {r},DE'), + + 0x21: (0, 2, 0, 0, False, 'LD {r},{i:04H}h'), + 0x22: (0, 2, 0, 2, False, 'LD ({i:04H}h),{r}'), + 0x23: (0, 0, 0, 0, False, 'INC {r}'), + 0x24: (0, 0, 0, 0, False, 'INC {r}h'), + 0x25: (0, 0, 0, 0, False, 'DEC {r}h'), + 0x26: (0, 1, 0, 0, False, 'LD {r}h,{i:02H}h'), + + 0x29: (0, 0, 0, 0, False, 'ADD {r},{r}'), + 0x2A: (0, 2, 2, 0, False, 'LD {r},({i:04H}h)'), + 0x2B: (0, 0, 0, 0, False, 'DEC {r}'), + 0x2C: (0, 0, 0, 0, False, 'INC {r}l'), + 0x2D: (0, 0, 0, 0, False, 'DEC {r}l'), + 0x2E: (0, 1, 0, 0, False, 'LD {r}l,{i:02H}h'), + + 0x34: (1, 0, 1, 1, False, 'INC ({r}{d:+d})'), + 0x35: (1, 0, 1, 1, False, 'DEC ({r}{d:+d})'), + 0x36: (1, 1, 0, 1, False, 'LD ({r}{d:+d}),{i:02H}h'), + + 0x39: (0, 0, 0, 0, False, 'ADD {r},SP'), + + 0x44: (0, 0, 0, 0, False, 'LD B,{r}h'), + 0x45: (0, 0, 0, 0, False, 'LD B,{r}l'), + 0x46: (1, 0, 1, 0, False, 'LD B,({r}{d:+d})'), + + 0x4C: (0, 0, 0, 0, False, 'LD C,{r}h'), + 0x4D: (0, 0, 0, 0, False, 'LD C,{r}l'), + 0x4E: (1, 0, 1, 0, False, 'LD C,({r}{d:+d})'), + + 0x54: (0, 0, 0, 0, False, 'LD D,{r}h'), + 0x55: (0, 0, 0, 0, False, 'LD D,{r}l'), + 0x56: (1, 0, 1, 0, False, 'LD D,({r}{d:+d})'), + + 0x5C: (0, 0, 0, 0, False, 'LD E,{r}h'), + 0x5D: (0, 0, 0, 0, False, 'LD E,{r}l'), + 0x5E: (1, 0, 1, 0, False, 'LD E,({r}{d:+d})'), + + 0x60: (0, 0, 0, 0, False, 'LD {r}h,B'), + 0x61: (0, 0, 0, 0, False, 'LD {r}h,C'), + 0x62: (0, 0, 0, 0, False, 'LD {r}h,D'), + 0x63: (0, 0, 0, 0, False, 'LD {r}h,E'), + 0x64: (0, 0, 0, 0, False, 'LD {r}h,{r}h'), + 0x65: (0, 0, 0, 0, False, 'LD {r}h,{r}l'), + 0x66: (1, 0, 1, 0, False, 'LD H,({r}{d:+d})'), + 0x67: (0, 0, 0, 0, False, 'LD {r}h,A'), + 0x68: (0, 0, 0, 0, False, 'LD {r}l,B'), + 0x69: (0, 0, 0, 0, False, 'LD {r}l,C'), + 0x6A: (0, 0, 0, 0, False, 'LD {r}l,D'), + 0x6B: (0, 0, 0, 0, False, 'LD {r}l,E'), + 0x6C: (0, 0, 0, 0, False, 'LD {r}l,{r}h'), + 0x6D: (0, 0, 0, 0, False, 'LD {r}l,{r}l'), + 0x6E: (1, 0, 1, 0, False, 'LD L,({r}{d:+d})'), + 0x6F: (0, 0, 0, 0, False, 'LD {r}l,A'), + + 0x70: (1, 0, 0, 1, False, 'LD ({r}{d:+d}),B'), + 0x71: (1, 0, 0, 1, False, 'LD ({r}{d:+d}),C'), + 0x72: (1, 0, 0, 1, False, 'LD ({r}{d:+d}),D'), + 0x73: (1, 0, 0, 1, False, 'LD ({r}{d:+d}),E'), + 0x74: (1, 0, 0, 1, False, 'LD ({r}{d:+d}),H'), + 0x75: (1, 0, 0, 1, False, 'LD ({r}{d:+d}),L'), + + 0x77: (1, 0, 0, 1, False, 'LD ({r}{d:+d}),A'), + + 0x7C: (0, 0, 0, 0, False, 'LD A,{r}h'), + 0x7D: (0, 0, 0, 0, False, 'LD A,{r}l'), + 0x7E: (1, 0, 1, 0, False, 'LD A,({r}{d:+d})'), + + 0x84: (0, 0, 0, 0, False, 'ADD A,{r}h'), + 0x85: (0, 0, 0, 0, False, 'ADD A,{r}l'), + 0x86: (1, 0, 1, 0, False, 'ADD A,({r}{d:+d})'), + + 0x8C: (0, 0, 0, 0, False, 'ADC A,{r}h'), + 0x8D: (0, 0, 0, 0, False, 'ADC A,{r}l'), + 0x8E: (1, 0, 1, 0, False, 'ADC A,({r}{d:+d})'), + + 0x94: (0, 0, 0, 0, False, 'SUB {r}h'), + 0x95: (0, 0, 0, 0, False, 'SUB {r}l'), + 0x96: (1, 0, 1, 0, False, 'SUB ({r}{d:+d})'), + + 0x9C: (0, 0, 0, 0, False, 'SBC A,{r}h'), + 0x9D: (0, 0, 0, 0, False, 'SBC A,{r}l'), + 0x9E: (1, 0, 1, 0, False, 'SBC A,({r}{d:+d})'), + + 0xA4: (0, 0, 0, 0, False, 'AND {r}h'), + 0xA5: (0, 0, 0, 0, False, 'AND {r}l'), + 0xA6: (1, 0, 1, 0, False, 'AND ({r}{d:+d})'), + + 0xAC: (0, 0, 0, 0, False, 'XOR {r}h'), + 0xAD: (0, 0, 0, 0, False, 'XOR {r}l'), + 0xAE: (1, 0, 1, 0, False, 'XOR ({r}{d:+d})'), + + 0xB4: (0, 0, 0, 0, False, 'OR {r}h'), + 0xB5: (0, 0, 0, 0, False, 'OR {r}l'), + 0xB6: (1, 0, 1, 0, False, 'OR ({r}{d:+d})'), + + 0xBC: (0, 0, 0, 0, False, 'CP {r}h'), + 0xBD: (0, 0, 0, 0, False, 'CP {r}l'), + 0xBE: (1, 0, 1, 0, False, 'CP ({r}{d:+d})'), + + 0xE1: (0, 0, 2, 0, False, 'POP {r}'), + + 0xE3: (0, 0, 2, 2, False, 'EX (SP),{r}'), + + 0xE5: (0, 0, 0,-2, False, 'PUSH {r}'), + + 0xE9: (0, 0, 0, 0, False, 'JP ({r})'), + + 0xF9: (0, 0, 0, 0, False, 'LD SP,{r}') +} + +# Instructions with DD CB or FD CB prefix. +# For these instructions, the displacement precedes the opcode byte. +# This is handled as a special case in the code, and thus the entries +# in this table specify 0 for the displacement length. +index_bit_instructions = { + 0x00: (0, 0, 1, 1, False, 'RLC ({r}{d:+d}),B'), + 0x01: (0, 0, 1, 1, False, 'RLC ({r}{d:+d}),C'), + 0x02: (0, 0, 1, 1, False, 'RLC ({r}{d:+d}),D'), + 0x03: (0, 0, 1, 1, False, 'RLC ({r}{d:+d}),E'), + 0x04: (0, 0, 1, 1, False, 'RLC ({r}{d:+d}),H'), + 0x05: (0, 0, 1, 1, False, 'RLC ({r}{d:+d}),L'), + 0x06: (0, 0, 1, 1, False, 'RLC ({r}{d:+d})'), + 0x07: (0, 0, 1, 1, False, 'RLC ({r}{d:+d}),A'), + 0x08: (0, 0, 1, 1, False, 'RRC ({r}{d:+d}),B'), + 0x09: (0, 0, 1, 1, False, 'RRC ({r}{d:+d}),C'), + 0x0A: (0, 0, 1, 1, False, 'RRC ({r}{d:+d}),D'), + 0x0B: (0, 0, 1, 1, False, 'RRC ({r}{d:+d}),E'), + 0x0C: (0, 0, 1, 1, False, 'RRC ({r}{d:+d}),H'), + 0x0D: (0, 0, 1, 1, False, 'RRC ({r}{d:+d}),L'), + 0x0E: (0, 0, 1, 1, False, 'RRC ({r}{d:+d})'), + 0x0F: (0, 0, 1, 1, False, 'RRC ({r}{d:+d}),A'), + + 0x10: (0, 0, 1, 1, False, 'RL ({r}{d:+d}),B'), + 0x11: (0, 0, 1, 1, False, 'RL ({r}{d:+d}),C'), + 0x12: (0, 0, 1, 1, False, 'RL ({r}{d:+d}),D'), + 0x13: (0, 0, 1, 1, False, 'RL ({r}{d:+d}),E'), + 0x14: (0, 0, 1, 1, False, 'RL ({r}{d:+d}),H'), + 0x15: (0, 0, 1, 1, False, 'RL ({r}{d:+d}),L'), + 0x16: (0, 0, 1, 1, False, 'RL ({r}{d:+d})'), + 0x17: (0, 0, 1, 1, False, 'RL ({r}{d:+d}),A'), + 0x18: (0, 0, 1, 1, False, 'RR ({r}{d:+d}),B'), + 0x19: (0, 0, 1, 1, False, 'RR ({r}{d:+d}),C'), + 0x1A: (0, 0, 1, 1, False, 'RR ({r}{d:+d}),D'), + 0x1B: (0, 0, 1, 1, False, 'RR ({r}{d:+d}),E'), + 0x1C: (0, 0, 1, 1, False, 'RR ({r}{d:+d}),H'), + 0x1D: (0, 0, 1, 1, False, 'RR ({r}{d:+d}),L'), + 0x1E: (0, 0, 1, 1, False, 'RR ({r}{d:+d})'), + 0x1F: (0, 0, 1, 1, False, 'RR ({r}{d:+d}),A'), + + 0x20: (0, 0, 1, 1, False, 'SLA ({r}{d:+d}),B'), + 0x21: (0, 0, 1, 1, False, 'SLA ({r}{d:+d}),C'), + 0x22: (0, 0, 1, 1, False, 'SLA ({r}{d:+d}),D'), + 0x23: (0, 0, 1, 1, False, 'SLA ({r}{d:+d}),E'), + 0x24: (0, 0, 1, 1, False, 'SLA ({r}{d:+d}),H'), + 0x25: (0, 0, 1, 1, False, 'SLA ({r}{d:+d}),L'), + 0x26: (0, 0, 1, 1, False, 'SLA ({r}{d:+d})'), + 0x27: (0, 0, 1, 1, False, 'SLA ({r}{d:+d}),A'), + 0x28: (0, 0, 1, 1, False, 'SRA ({r}{d:+d}),B'), + 0x29: (0, 0, 1, 1, False, 'SRA ({r}{d:+d}),C'), + 0x2A: (0, 0, 1, 1, False, 'SRA ({r}{d:+d}),D'), + 0x2B: (0, 0, 1, 1, False, 'SRA ({r}{d:+d}),E'), + 0x2C: (0, 0, 1, 1, False, 'SRA ({r}{d:+d}),H'), + 0x2D: (0, 0, 1, 1, False, 'SRA ({r}{d:+d}),L'), + 0x2E: (0, 0, 1, 1, False, 'SRA ({r}{d:+d})'), + 0x2F: (0, 0, 1, 1, False, 'SRA ({r}{d:+d}),A'), + + 0x30: (0, 0, 1, 1, False, 'SLL ({r}{d:+d}),B'), + 0x31: (0, 0, 1, 1, False, 'SLL ({r}{d:+d}),C'), + 0x32: (0, 0, 1, 1, False, 'SLL ({r}{d:+d}),D'), + 0x33: (0, 0, 1, 1, False, 'SLL ({r}{d:+d}),E'), + 0x34: (0, 0, 1, 1, False, 'SLL ({r}{d:+d}),H'), + 0x35: (0, 0, 1, 1, False, 'SLL ({r}{d:+d}),L'), + 0x36: (0, 0, 1, 1, False, 'SLL ({r}{d:+d})'), + 0x37: (0, 0, 1, 1, False, 'SLL ({r}{d:+d}),A'), + 0x38: (0, 0, 1, 1, False, 'SRL ({r}{d:+d}),B'), + 0x39: (0, 0, 1, 1, False, 'SRL ({r}{d:+d}),C'), + 0x3A: (0, 0, 1, 1, False, 'SRL ({r}{d:+d}),D'), + 0x3B: (0, 0, 1, 1, False, 'SRL ({r}{d:+d}),E'), + 0x3C: (0, 0, 1, 1, False, 'SRL ({r}{d:+d}),H'), + 0x3D: (0, 0, 1, 1, False, 'SRL ({r}{d:+d}),L'), + 0x3E: (0, 0, 1, 1, False, 'SRL ({r}{d:+d})'), + 0x3F: (0, 0, 1, 1, False, 'SRL ({r}{d:+d}),A'), + + 0x40: (0, 0, 1, 0, False, 'BIT 0,({r}{d:+d})'), + 0x41: (0, 0, 1, 0, False, 'BIT 0,({r}{d:+d})'), + 0x42: (0, 0, 1, 0, False, 'BIT 0,({r}{d:+d})'), + 0x43: (0, 0, 1, 0, False, 'BIT 0,({r}{d:+d})'), + 0x44: (0, 0, 1, 0, False, 'BIT 0,({r}{d:+d})'), + 0x45: (0, 0, 1, 0, False, 'BIT 0,({r}{d:+d})'), + 0x46: (0, 0, 1, 0, False, 'BIT 0,({r}{d:+d})'), + 0x47: (0, 0, 1, 0, False, 'BIT 0,({r}{d:+d})'), + 0x48: (0, 0, 1, 0, False, 'BIT 1,({r}{d:+d})'), + 0x49: (0, 0, 1, 0, False, 'BIT 1,({r}{d:+d})'), + 0x4A: (0, 0, 1, 0, False, 'BIT 1,({r}{d:+d})'), + 0x4B: (0, 0, 1, 0, False, 'BIT 1,({r}{d:+d})'), + 0x4C: (0, 0, 1, 0, False, 'BIT 1,({r}{d:+d})'), + 0x4D: (0, 0, 1, 0, False, 'BIT 1,({r}{d:+d})'), + 0x4E: (0, 0, 1, 0, False, 'BIT 1,({r}{d:+d})'), + 0x4F: (0, 0, 1, 0, False, 'BIT 1,({r}{d:+d})'), + + 0x50: (0, 0, 1, 0, False, 'BIT 2,({r}{d:+d})'), + 0x51: (0, 0, 1, 0, False, 'BIT 2,({r}{d:+d})'), + 0x52: (0, 0, 1, 0, False, 'BIT 2,({r}{d:+d})'), + 0x53: (0, 0, 1, 0, False, 'BIT 2,({r}{d:+d})'), + 0x54: (0, 0, 1, 0, False, 'BIT 2,({r}{d:+d})'), + 0x55: (0, 0, 1, 0, False, 'BIT 2,({r}{d:+d})'), + 0x56: (0, 0, 1, 0, False, 'BIT 2,({r}{d:+d})'), + 0x57: (0, 0, 1, 0, False, 'BIT 2,({r}{d:+d})'), + 0x58: (0, 0, 1, 0, False, 'BIT 3,({r}{d:+d})'), + 0x59: (0, 0, 1, 0, False, 'BIT 3,({r}{d:+d})'), + 0x5A: (0, 0, 1, 0, False, 'BIT 3,({r}{d:+d})'), + 0x5B: (0, 0, 1, 0, False, 'BIT 3,({r}{d:+d})'), + 0x5C: (0, 0, 1, 0, False, 'BIT 3,({r}{d:+d})'), + 0x5D: (0, 0, 1, 0, False, 'BIT 3,({r}{d:+d})'), + 0x5E: (0, 0, 1, 0, False, 'BIT 3,({r}{d:+d})'), + 0x5F: (0, 0, 1, 0, False, 'BIT 3,({r}{d:+d})'), + + 0x60: (0, 0, 1, 0, False, 'BIT 4,({r}{d:+d})'), + 0x61: (0, 0, 1, 0, False, 'BIT 4,({r}{d:+d})'), + 0x62: (0, 0, 1, 0, False, 'BIT 4,({r}{d:+d})'), + 0x63: (0, 0, 1, 0, False, 'BIT 4,({r}{d:+d})'), + 0x64: (0, 0, 1, 0, False, 'BIT 4,({r}{d:+d})'), + 0x65: (0, 0, 1, 0, False, 'BIT 4,({r}{d:+d})'), + 0x66: (0, 0, 1, 0, False, 'BIT 4,({r}{d:+d})'), + 0x67: (0, 0, 1, 0, False, 'BIT 4,({r}{d:+d})'), + 0x68: (0, 0, 1, 0, False, 'BIT 5,({r}{d:+d})'), + 0x69: (0, 0, 1, 0, False, 'BIT 5,({r}{d:+d})'), + 0x6A: (0, 0, 1, 0, False, 'BIT 5,({r}{d:+d})'), + 0x6B: (0, 0, 1, 0, False, 'BIT 5,({r}{d:+d})'), + 0x6C: (0, 0, 1, 0, False, 'BIT 5,({r}{d:+d})'), + 0x6D: (0, 0, 1, 0, False, 'BIT 5,({r}{d:+d})'), + 0x6E: (0, 0, 1, 0, False, 'BIT 5,({r}{d:+d})'), + 0x6F: (0, 0, 1, 0, False, 'BIT 5,({r}{d:+d})'), + + 0x70: (0, 0, 1, 0, False, 'BIT 6,({r}{d:+d})'), + 0x71: (0, 0, 1, 0, False, 'BIT 6,({r}{d:+d})'), + 0x72: (0, 0, 1, 0, False, 'BIT 6,({r}{d:+d})'), + 0x73: (0, 0, 1, 0, False, 'BIT 6,({r}{d:+d})'), + 0x74: (0, 0, 1, 0, False, 'BIT 6,({r}{d:+d})'), + 0x75: (0, 0, 1, 0, False, 'BIT 6,({r}{d:+d})'), + 0x76: (0, 0, 1, 0, False, 'BIT 6,({r}{d:+d})'), + 0x77: (0, 0, 1, 0, False, 'BIT 6,({r}{d:+d})'), + 0x78: (0, 0, 1, 0, False, 'BIT 7,({r}{d:+d})'), + 0x79: (0, 0, 1, 0, False, 'BIT 7,({r}{d:+d})'), + 0x7A: (0, 0, 1, 0, False, 'BIT 7,({r}{d:+d})'), + 0x7B: (0, 0, 1, 0, False, 'BIT 7,({r}{d:+d})'), + 0x7C: (0, 0, 1, 0, False, 'BIT 7,({r}{d:+d})'), + 0x7D: (0, 0, 1, 0, False, 'BIT 7,({r}{d:+d})'), + 0x7E: (0, 0, 1, 0, False, 'BIT 7,({r}{d:+d})'), + 0x7F: (0, 0, 1, 0, False, 'BIT 7,({r}{d:+d})'), + + 0x80: (0, 0, 1, 1, False, 'RES 0,({r}{d:+d}),B'), + 0x81: (0, 0, 1, 1, False, 'RES 0,({r}{d:+d}),C'), + 0x82: (0, 0, 1, 1, False, 'RES 0,({r}{d:+d}),D'), + 0x83: (0, 0, 1, 1, False, 'RES 0,({r}{d:+d}),E'), + 0x84: (0, 0, 1, 1, False, 'RES 0,({r}{d:+d}),H'), + 0x85: (0, 0, 1, 1, False, 'RES 0,({r}{d:+d}),L'), + 0x86: (0, 0, 1, 1, False, 'RES 0,({r}{d:+d})'), + 0x87: (0, 0, 1, 1, False, 'RES 0,({r}{d:+d}),A'), + 0x88: (0, 0, 1, 1, False, 'RES 1,({r}{d:+d}),B'), + 0x89: (0, 0, 1, 1, False, 'RES 1,({r}{d:+d}),C'), + 0x8A: (0, 0, 1, 1, False, 'RES 1,({r}{d:+d}),D'), + 0x8B: (0, 0, 1, 1, False, 'RES 1,({r}{d:+d}),E'), + 0x8C: (0, 0, 1, 1, False, 'RES 1,({r}{d:+d}),H'), + 0x8D: (0, 0, 1, 1, False, 'RES 1,({r}{d:+d}),L'), + 0x8E: (0, 0, 1, 1, False, 'RES 1,({r}{d:+d})'), + 0x8F: (0, 0, 1, 1, False, 'RES 1,({r}{d:+d}),A'), + + 0x90: (0, 0, 1, 1, False, 'RES 2,({r}{d:+d}),B'), + 0x91: (0, 0, 1, 1, False, 'RES 2,({r}{d:+d}),C'), + 0x92: (0, 0, 1, 1, False, 'RES 2,({r}{d:+d}),D'), + 0x93: (0, 0, 1, 1, False, 'RES 2,({r}{d:+d}),E'), + 0x94: (0, 0, 1, 1, False, 'RES 2,({r}{d:+d}),H'), + 0x95: (0, 0, 1, 1, False, 'RES 2,({r}{d:+d}),L'), + 0x96: (0, 0, 1, 1, False, 'RES 2,({r}{d:+d})'), + 0x97: (0, 0, 1, 1, False, 'RES 2,({r}{d:+d}),A'), + 0x98: (0, 0, 1, 1, False, 'RES 3,({r}{d:+d}),B'), + 0x99: (0, 0, 1, 1, False, 'RES 3,({r}{d:+d}),C'), + 0x9A: (0, 0, 1, 1, False, 'RES 3,({r}{d:+d}),D'), + 0x9B: (0, 0, 1, 1, False, 'RES 3,({r}{d:+d}),E'), + 0x9C: (0, 0, 1, 1, False, 'RES 3,({r}{d:+d}),H'), + 0x9D: (0, 0, 1, 1, False, 'RES 3,({r}{d:+d}),L'), + 0x9E: (0, 0, 1, 1, False, 'RES 3,({r}{d:+d})'), + 0x9F: (0, 0, 1, 1, False, 'RES 3,({r}{d:+d}),A'), + + 0xA0: (0, 0, 1, 1, False, 'RES 4,({r}{d:+d}),B'), + 0xA1: (0, 0, 1, 1, False, 'RES 4,({r}{d:+d}),C'), + 0xA2: (0, 0, 1, 1, False, 'RES 4,({r}{d:+d}),D'), + 0xA3: (0, 0, 1, 1, False, 'RES 4,({r}{d:+d}),E'), + 0xA4: (0, 0, 1, 1, False, 'RES 4,({r}{d:+d}),H'), + 0xA5: (0, 0, 1, 1, False, 'RES 4,({r}{d:+d}),L'), + 0xA6: (0, 0, 1, 1, False, 'RES 4,({r}{d:+d})'), + 0xA7: (0, 0, 1, 1, False, 'RES 4,({r}{d:+d}),A'), + 0xA8: (0, 0, 1, 1, False, 'RES 5,({r}{d:+d}),B'), + 0xA9: (0, 0, 1, 1, False, 'RES 5,({r}{d:+d}),C'), + 0xAA: (0, 0, 1, 1, False, 'RES 5,({r}{d:+d}),D'), + 0xAB: (0, 0, 1, 1, False, 'RES 5,({r}{d:+d}),E'), + 0xAC: (0, 0, 1, 1, False, 'RES 5,({r}{d:+d}),H'), + 0xAD: (0, 0, 1, 1, False, 'RES 5,({r}{d:+d}),L'), + 0xAE: (0, 0, 1, 1, False, 'RES 5,({r}{d:+d})'), + 0xAF: (0, 0, 1, 1, False, 'RES 5,({r}{d:+d}),A'), + + 0xB0: (0, 0, 1, 1, False, 'RES 6,({r}{d:+d}),B'), + 0xB1: (0, 0, 1, 1, False, 'RES 6,({r}{d:+d}),C'), + 0xB2: (0, 0, 1, 1, False, 'RES 6,({r}{d:+d}),D'), + 0xB3: (0, 0, 1, 1, False, 'RES 6,({r}{d:+d}),E'), + 0xB4: (0, 0, 1, 1, False, 'RES 6,({r}{d:+d}),H'), + 0xB5: (0, 0, 1, 1, False, 'RES 6,({r}{d:+d}),L'), + 0xB6: (0, 0, 1, 1, False, 'RES 6,({r}{d:+d})'), + 0xB7: (0, 0, 1, 1, False, 'RES 6,({r}{d:+d}),A'), + 0xB8: (0, 0, 1, 1, False, 'RES 7,({r}{d:+d}),B'), + 0xB9: (0, 0, 1, 1, False, 'RES 7,({r}{d:+d}),C'), + 0xBA: (0, 0, 1, 1, False, 'RES 7,({r}{d:+d}),D'), + 0xBB: (0, 0, 1, 1, False, 'RES 7,({r}{d:+d}),E'), + 0xBC: (0, 0, 1, 1, False, 'RES 7,({r}{d:+d}),H'), + 0xBD: (0, 0, 1, 1, False, 'RES 7,({r}{d:+d}),L'), + 0xBE: (0, 0, 1, 1, False, 'RES 7,({r}{d:+d})'), + 0xBF: (0, 0, 1, 1, False, 'RES 7,({r}{d:+d}),A'), + + 0xC0: (0, 0, 1, 1, False, 'SET 0,({r}{d:+d}),B'), + 0xC1: (0, 0, 1, 1, False, 'SET 0,({r}{d:+d}),C'), + 0xC2: (0, 0, 1, 1, False, 'SET 0,({r}{d:+d}),D'), + 0xC3: (0, 0, 1, 1, False, 'SET 0,({r}{d:+d}),E'), + 0xC4: (0, 0, 1, 1, False, 'SET 0,({r}{d:+d}),H'), + 0xC5: (0, 0, 1, 1, False, 'SET 0,({r}{d:+d}),L'), + 0xC6: (0, 0, 1, 1, False, 'SET 0,({r}{d:+d})'), + 0xC7: (0, 0, 1, 1, False, 'SET 0,({r}{d:+d}),A'), + 0xC8: (0, 0, 1, 1, False, 'SET 1,({r}{d:+d}),B'), + 0xC9: (0, 0, 1, 1, False, 'SET 1,({r}{d:+d}),C'), + 0xCA: (0, 0, 1, 1, False, 'SET 1,({r}{d:+d}),D'), + 0xCB: (0, 0, 1, 1, False, 'SET 1,({r}{d:+d}),E'), + 0xCC: (0, 0, 1, 1, False, 'SET 1,({r}{d:+d}),H'), + 0xCD: (0, 0, 1, 1, False, 'SET 1,({r}{d:+d}),L'), + 0xCE: (0, 0, 1, 1, False, 'SET 1,({r}{d:+d})'), + 0xCF: (0, 0, 1, 1, False, 'SET 1,({r}{d:+d}),A'), + + 0xD0: (0, 0, 1, 1, False, 'SET 2,({r}{d:+d}),B'), + 0xD1: (0, 0, 1, 1, False, 'SET 2,({r}{d:+d}),C'), + 0xD2: (0, 0, 1, 1, False, 'SET 2,({r}{d:+d}),D'), + 0xD3: (0, 0, 1, 1, False, 'SET 2,({r}{d:+d}),E'), + 0xD4: (0, 0, 1, 1, False, 'SET 2,({r}{d:+d}),H'), + 0xD5: (0, 0, 1, 1, False, 'SET 2,({r}{d:+d}),L'), + 0xD6: (0, 0, 1, 1, False, 'SET 2,({r}{d:+d})'), + 0xD7: (0, 0, 1, 1, False, 'SET 2,({r}{d:+d}),A'), + 0xD8: (0, 0, 1, 1, False, 'SET 3,({r}{d:+d}),B'), + 0xD9: (0, 0, 1, 1, False, 'SET 3,({r}{d:+d}),C'), + 0xDA: (0, 0, 1, 1, False, 'SET 3,({r}{d:+d}),D'), + 0xDB: (0, 0, 1, 1, False, 'SET 3,({r}{d:+d}),E'), + 0xDC: (0, 0, 1, 1, False, 'SET 3,({r}{d:+d}),H'), + 0xDD: (0, 0, 1, 1, False, 'SET 3,({r}{d:+d}),L'), + 0xDE: (0, 0, 1, 1, False, 'SET 3,({r}{d:+d})'), + 0xDF: (0, 0, 1, 1, False, 'SET 3,({r}{d:+d}),A'), + + 0xE0: (0, 0, 1, 1, False, 'SET 4,({r}{d:+d}),B'), + 0xE1: (0, 0, 1, 1, False, 'SET 4,({r}{d:+d}),C'), + 0xE2: (0, 0, 1, 1, False, 'SET 4,({r}{d:+d}),D'), + 0xE3: (0, 0, 1, 1, False, 'SET 4,({r}{d:+d}),E'), + 0xE4: (0, 0, 1, 1, False, 'SET 4,({r}{d:+d}),H'), + 0xE5: (0, 0, 1, 1, False, 'SET 4,({r}{d:+d}),L'), + 0xE6: (0, 0, 1, 1, False, 'SET 4,({r}{d:+d})'), + 0xE7: (0, 0, 1, 1, False, 'SET 4,({r}{d:+d}),A'), + 0xE8: (0, 0, 1, 1, False, 'SET 5,({r}{d:+d}),B'), + 0xE9: (0, 0, 1, 1, False, 'SET 5,({r}{d:+d}),C'), + 0xEA: (0, 0, 1, 1, False, 'SET 5,({r}{d:+d}),D'), + 0xEB: (0, 0, 1, 1, False, 'SET 5,({r}{d:+d}),E'), + 0xEC: (0, 0, 1, 1, False, 'SET 5,({r}{d:+d}),H'), + 0xED: (0, 0, 1, 1, False, 'SET 5,({r}{d:+d}),L'), + 0xEE: (0, 0, 1, 1, False, 'SET 5,({r}{d:+d})'), + 0xEF: (0, 0, 1, 1, False, 'SET 5,({r}{d:+d}),A'), + + 0xF0: (0, 0, 1, 1, False, 'SET 6,({r}{d:+d}),B'), + 0xF1: (0, 0, 1, 1, False, 'SET 6,({r}{d:+d}),C'), + 0xF2: (0, 0, 1, 1, False, 'SET 6,({r}{d:+d}),D'), + 0xF3: (0, 0, 1, 1, False, 'SET 6,({r}{d:+d}),E'), + 0xF4: (0, 0, 1, 1, False, 'SET 6,({r}{d:+d}),H'), + 0xF5: (0, 0, 1, 1, False, 'SET 6,({r}{d:+d}),L'), + 0xF6: (0, 0, 1, 1, False, 'SET 6,({r}{d:+d})'), + 0xF7: (0, 0, 1, 1, False, 'SET 6,({r}{d:+d}),A'), + 0xF8: (0, 0, 1, 1, False, 'SET 7,({r}{d:+d}),B'), + 0xF9: (0, 0, 1, 1, False, 'SET 7,({r}{d:+d}),C'), + 0xFA: (0, 0, 1, 1, False, 'SET 7,({r}{d:+d}),D'), + 0xFB: (0, 0, 1, 1, False, 'SET 7,({r}{d:+d}),E'), + 0xFC: (0, 0, 1, 1, False, 'SET 7,({r}{d:+d}),H'), + 0xFD: (0, 0, 1, 1, False, 'SET 7,({r}{d:+d}),L'), + 0xFE: (0, 0, 1, 1, False, 'SET 7,({r}{d:+d})'), + 0xFF: (0, 0, 1, 1, False, 'SET 7,({r}{d:+d}),A') +} + +instr_table_by_prefix = { + 0: (main_instructions, ''), + 0xED: (extended_instructions, ''), + 0xCB: (bit_instructions, ''), + 0xDD: (index_instructions, 'IX'), + 0xFD: (index_instructions, 'IY'), + 0xDDCB: (index_bit_instructions, 'IX'), + 0xFDCB: (index_bit_instructions, 'IY') +}