From: Uwe Hermann Date: Mon, 26 Dec 2016 21:20:08 +0000 (+0100) Subject: Backport current PDs from git mainline. X-Git-Tag: libsigrokdecode-0.4.1~17 X-Git-Url: https://sigrok.org/gitaction?a=commitdiff_plain;h=fa5f451084e3c11600395acdedb7c5cd4d4221a3;p=libsigrokdecode.git Backport current PDs from git mainline. --- diff --git a/decoders/adns5020/pd.py b/decoders/adns5020/pd.py index bcdb52a..972227e 100644 --- a/decoders/adns5020/pd.py +++ b/decoders/adns5020/pd.py @@ -45,7 +45,7 @@ class Decoder(srd.Decoder): name = 'ADNS-5020' longname = 'Avago ADNS-5020 optical mouse sensor' desc = 'Bidirectional command and data over an SPI-like protocol.' - license = 'gplv2' + license = 'gplv2+' inputs = ['spi'] outputs = ['adns5020'] annotations = ( @@ -59,7 +59,7 @@ class Decoder(srd.Decoder): ('warnings', 'Warnings', (2,)), ) - def __init__(self, **kwargs): + def __init__(self): self.ss_cmd, self.es_cmd = 0, 0 self.mosi_bytes = [] diff --git a/decoders/am230x/pd.py b/decoders/am230x/pd.py index 2b54cde..483dc9b 100644 --- a/decoders/am230x/pd.py +++ b/decoders/am230x/pd.py @@ -123,7 +123,7 @@ class Decoder(srd.Decoder): checksum += self.bits2num(bitlist[i-8:i]) return checksum % 256 - def __init__(self, **kwargs): + def __init__(self): self.samplerate = None self.reset() diff --git a/decoders/arm_etmv3/pd.py b/decoders/arm_etmv3/pd.py index 0083fdb..367ceb8 100644 --- a/decoders/arm_etmv3/pd.py +++ b/decoders/arm_etmv3/pd.py @@ -170,7 +170,7 @@ class Decoder(srd.Decoder): 'default': 'alternative', 'values': ('alternative', 'original')}, ) - def __init__(self, **kwargs): + def __init__(self): self.buf = [] self.syncbuf = [] self.prevsample = 0 diff --git a/decoders/arm_itm/pd.py b/decoders/arm_itm/pd.py index f6a0212..384999c 100644 --- a/decoders/arm_itm/pd.py +++ b/decoders/arm_itm/pd.py @@ -80,7 +80,7 @@ class Decoder(srd.Decoder): ('function', 'Current function', (11,)), ) - def __init__(self, **kwargs): + def __init__(self): self.buf = [] self.syncbuf = [] self.swpackets = {} diff --git a/decoders/arm_tpiu/pd.py b/decoders/arm_tpiu/pd.py index ba01aa0..d111311 100644 --- a/decoders/arm_tpiu/pd.py +++ b/decoders/arm_tpiu/pd.py @@ -42,12 +42,12 @@ class Decoder(srd.Decoder): ('data', 'Stream data', (1,)), ) - def __init__(self, **kwargs): + def __init__(self): self.buf = [] self.syncbuf = [] self.prevsample = 0 self.stream = 0 - self.stream_ss = None + self.ss_stream = None self.bytenum = 0 def start(self): @@ -57,14 +57,14 @@ class Decoder(srd.Decoder): def stream_changed(self, ss, stream): if self.stream != stream: if self.stream != 0: - self.put(self.stream_ss, ss, self.out_ann, - [0, ["Stream %d" % self.stream, "S%d" % self.stream]]) + self.put(self.ss_stream, ss, self.out_ann, + [0, ['Stream %d' % self.stream, 'S%d' % self.stream]]) self.stream = stream - self.stream_ss = ss + 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_ann, [1, ['0x%02x' % byte]]) self.put(ss, es, self.out_python, ['DATA', 0, (byte, [])]) def process_frame(self, buf): diff --git a/decoders/aud/__init__.py b/decoders/aud/__init__.py new file mode 100644 index 0000000..aaa31ff --- /dev/null +++ b/decoders/aud/__init__.py @@ -0,0 +1,32 @@ +## +## 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, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + +''' +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/decoders/aud/pd.py b/decoders/aud/pd.py new file mode 100644 index 0000000..a5330f9 --- /dev/null +++ b/decoders/aud/pd.py @@ -0,0 +1,114 @@ +## +## 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, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + +# 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 = 2 + id = 'aud' + name = 'AUD' + longname = 'Advanced User Debugger' + desc = 'Renesas/Hitachi Advanced User Debugger (AUD) protocol.' + license = 'gplv2+' + inputs = ['logic'] + outputs = ['aud'] + 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.ncnt = 0 + self.nmax = 0 + self.addr = 0 + self.lastaddr = 0 + self.samplenum = 0 + self.oldclk = 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 find_clk_edge(self, clk, sync, datapins): + # Ignore sample if there's no edge. + if clk == self.oldclk: + return + self.oldclk = clk + # Ignore falling edges. + if clk == 0: + return + + # 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, ss, es, data): + for (self.samplenum, pins) in data: + clk = pins[0] + sync = pins[1] + d = pins[2:] + self.find_clk_edge(clk, sync, d) diff --git a/decoders/avr_isp/pd.py b/decoders/avr_isp/pd.py index 0914b5b..16e3d70 100644 --- a/decoders/avr_isp/pd.py +++ b/decoders/avr_isp/pd.py @@ -51,7 +51,7 @@ class Decoder(srd.Decoder): ('dev', 'Device', (9,)), ) - def __init__(self, **kwargs): + def __init__(self): self.state = 'IDLE' self.mosi_bytes, self.miso_bytes = [], [] self.ss_cmd, self.es_cmd = 0, 0 diff --git a/decoders/avr_pdi/__init__.py b/decoders/avr_pdi/__init__.py new file mode 100644 index 0000000..ebe647b --- /dev/null +++ b/decoders/avr_pdi/__init__.py @@ -0,0 +1,41 @@ +## +## 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/decoders/avr_pdi/pd.py b/decoders/avr_pdi/pd.py new file mode 100644 index 0000000..1568fdf --- /dev/null +++ b/decoders/avr_pdi/pd.py @@ -0,0 +1,586 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2011-2014 Uwe Hermann +## 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 . +## + +# 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 = 2 + id = 'avr_pdi' + name = 'AVR PDI' + longname = 'Atmel Program and Debug Interface' + desc = 'Atmel proprietary interface for the ATxmega MCU.' + license = 'gplv2+' + inputs = ['logic'] + outputs = ['pdi'] + 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.samplerate = None + # Detect input changes and clock edges. + self.prev_pins = None + self.prev_clock = 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 find_clk_edge(self, samplenum, clock_pin, data_pin): + # Ignore the sample if the clock pin has not changed. + if clock_pin == self.prev_clock: + return + self.prev_clock = clock_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 = 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, ss, es, data): + for samplenum, pins in data: + + # Ignore identical samples. + if self.prev_pins == pins: + continue + self.prev_pins = pins + + # Have DATA processed at appropriate clock edges. + clock_pin, data_pin = pins[0], pins[1] + self.find_clk_edge(samplenum, clock_pin, data_pin) diff --git a/decoders/can/pd.py b/decoders/can/pd.py index ffc3652..c92877b 100644 --- a/decoders/can/pd.py +++ b/decoders/can/pd.py @@ -64,7 +64,7 @@ class Decoder(srd.Decoder): ('fields', 'Fields', tuple(range(15)) + (16,)), ) - def __init__(self, **kwargs): + def __init__(self): self.samplerate = None self.reset_variables() diff --git a/decoders/common/__init__.py b/decoders/common/__init__.py new file mode 100644 index 0000000..c597431 --- /dev/null +++ b/decoders/common/__init__.py @@ -0,0 +1,20 @@ +## +## 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, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + diff --git a/decoders/common/plugtrx/__init__.py b/decoders/common/plugtrx/__init__.py new file mode 100644 index 0000000..8dd0822 --- /dev/null +++ b/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/decoders/common/plugtrx/mod.py b/decoders/common/plugtrx/mod.py new file mode 100644 index 0000000..3d1b66d --- /dev/null +++ b/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/decoders/common/sdcard/__init__.py b/decoders/common/sdcard/__init__.py new file mode 100644 index 0000000..cf09d9d --- /dev/null +++ b/decoders/common/sdcard/__init__.py @@ -0,0 +1,21 @@ +## +## 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, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + +from .mod import * diff --git a/decoders/common/sdcard/mod.py b/decoders/common/sdcard/mod.py new file mode 100644 index 0000000..f553cf1 --- /dev/null +++ b/decoders/common/sdcard/mod.py @@ -0,0 +1,186 @@ +## +## 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, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + +# 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". +} + +card_status = { + 0: 'Reserved for manufacturer test mode', + 1: 'Reserved for manufacturer test mode', + 2: 'Reserved for application specific commands', + 3: 'AKE_SEQ_ERROR', + 4: 'Reserved for SDIO card', + 5: 'APP_CMD', + 6: 'Unknown', + 7: 'Unknown', + 8: 'READY_FOR_DATA', + 9: 'CURRENT_STATE', # CURRENT_STATE is a 4-bit value (decimal: 0..15). + 10: 'CURRENT_STATE', + 11: 'CURRENT_STATE', + 12: 'CURRENT_STATE', + 13: 'ERASE_RESET', + 14: 'CARD_ECC_DISABLED', + 15: 'WP_ERASE_SKIP', + 16: 'CSD_OVERWRITE', + 17: 'Reserved for DEFERRED_RESPONSE', # See eSD addendum + 18: 'Reserved', + 19: 'ERROR', + 20: 'CC_ERROR', + 21: 'CARD_ECC_FAILED', + 22: 'ILLEGAL_COMMAND', + 23: 'COM_CRC_ERROR', + 24: 'LOCK_UNLOCK_FAILED', + 25: 'CARD_IS_LOCKED', + 26: 'WP_VIOLATION', + 27: 'ERASE_PARAM', + 28: 'ERASE_SEQ_ERROR', + 29: 'BLOCK_LEN_ERROR', + 30: 'ADDRESS_ERROR', + 31: 'OUT_OF_RANGE', +} + +sd_status = { + # 311:0: Reserved for manufacturer + # 391:312: Reserved +} diff --git a/decoders/common/srdhelper/__init__.py b/decoders/common/srdhelper/__init__.py new file mode 100644 index 0000000..cf09d9d --- /dev/null +++ b/decoders/common/srdhelper/__init__.py @@ -0,0 +1,21 @@ +## +## 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, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + +from .mod import * diff --git a/decoders/common/srdhelper/mod.py b/decoders/common/srdhelper/mod.py new file mode 100644 index 0000000..e65ab17 --- /dev/null +++ b/decoders/common/srdhelper/mod.py @@ -0,0 +1,23 @@ +## +## 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, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + +# Return the specified BCD number (max. 8 bits) as integer. +def bcd2int(b): + return (b & 0x0f) + ((b >> 4) * 10) diff --git a/decoders/dcf77/pd.py b/decoders/dcf77/pd.py index adee403..0f1d3d1 100644 --- a/decoders/dcf77/pd.py +++ b/decoders/dcf77/pd.py @@ -20,10 +20,7 @@ import sigrokdecode as srd import calendar - -# Return the specified BCD number (max. 8 bits) as integer. -def bcd2int(b): - return (b & 0x0f) + ((b >> 4) * 10) +from common.srdhelper import bcd2int class SamplerateError(Exception): pass @@ -68,7 +65,7 @@ class Decoder(srd.Decoder): ('warnings', 'Warnings', (19,)), ) - def __init__(self, **kwargs): + def __init__(self): self.samplerate = None self.state = 'WAIT FOR RISING EDGE' self.oldpins = None diff --git a/decoders/dmx512/__init__.py b/decoders/dmx512/__init__.py new file mode 100644 index 0000000..9761dd5 --- /dev/null +++ b/decoders/dmx512/__init__.py @@ -0,0 +1,26 @@ +## +## 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, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + +''' +DMX512 (Digital MultipleX 512) is a protocol based on RS485, used to control +professional lighting fixtures. +''' + +from .pd import Decoder diff --git a/decoders/dmx512/pd.py b/decoders/dmx512/pd.py new file mode 100644 index 0000000..e684b13 --- /dev/null +++ b/decoders/dmx512/pd.py @@ -0,0 +1,170 @@ +## +## 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, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + +import sigrokdecode as srd + +class Decoder(srd.Decoder): + api_version = 2 + id = 'dmx512' + name = 'DMX512' + longname = 'Digital MultipleX 512' + desc = 'Professional lighting control protocol.' + license = 'gplv2+' + inputs = ['logic'] + outputs = ['dmx512'] + channels = ( + {'id': 'dmx', 'name': 'DMX data', 'desc': 'Any DMX data line'}, + ) + 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.samplerate = None + self.sample_usec = None + self.samplenum = -1 + self.run_start = -1 + self.run_bit = 0 + 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, ss, es, data): + if not self.samplerate: + raise SamplerateError('Cannot decode without samplerate.') + for (self.samplenum, pins) in data: + # Seek for an interval with no state change with a length between + # 88 and 1000000 us (BREAK). + if self.state == 'FIND BREAK': + if self.run_bit == pins[0]: + continue + runlen = (self.samplenum - self.run_start) * self.sample_usec + if runlen > 88 and runlen < 1000000: + self.putr([1, ['Break']]) + self.bit_break = self.run_bit + self.state = 'MARK MAB' + self.channel = 0 + elif runlen >= 1000000: + # Error condition. + self.putr([10, ['Invalid break length']]) + self.run_bit = pins[0] + self.run_start = self.samplenum + # Directly following the BREAK is the MARK AFTER BREAK. + elif self.state == 'MARK MAB': + if self.run_bit == pins[0]: + continue + self.putr([2, ['MAB']]) + self.state = 'READ BYTE' + self.channel = 0 + self.bit = 0 + self.aggreg = pins[0] + 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': + self.next_sample = self.run_start + (self.bit + 1) * self.skip_per_bit + self.aggreg += pins[0] + if self.samplenum != self.next_sample: + continue + bit_value = 0 if round(self.aggreg/self.skip_per_bit) == self.bit_break else 1 + + if self.bit == 0: + self.byte = 0 + self.putr([3, ['Start bit']]) + if bit_value != 0: + # (Possibly) invalid start bit, mark but don't fail. + self.put(self.samplenum, self.samplenum, + self.out_ann, [10, ['Invalid start bit']]) + elif self.bit >= 9: + self.put(self.samplenum - self.skip_per_bit, + self.samplenum, self.out_ann, [4, ['Stop bit']]) + if bit_value != 1: + # Invalid stop bit, mark. + self.put(self.samplenum, self.samplenum, + self.out_ann, [10, ['Invalid stop bit']]) + if self.bit == 10: + # On invalid 2nd stop bit, search for new break. + self.run_bit = pins[0] + self.state = 'FIND BREAK' + else: + # Label and process one bit. + self.put(self.samplenum - self.skip_per_bit, + self.samplenum, self.out_ann, [0, [str(bit_value)]]) + self.byte |= bit_value << (self.bit - 1) + + # Label a complete byte. + if self.bit == 10: + if self.channel == 0: + d = [5, ['Start code']] + else: + d = [6, ['Channel ' + str(self.channel)]] + self.put(self.run_start, self.next_sample, self.out_ann, d) + self.put(self.run_start + self.skip_per_bit, + self.next_sample - 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.run_bit = pins[0] + self.state = 'MARK IFT' + + self.aggreg = pins[0] + self.bit += 1 + # Mark the INTERFRAME-TIME between bytes / INTERPACKET-TIME between packets. + elif self.state == 'MARK IFT': + if self.run_bit == pins[0]: + continue + if self.channel > 512: + self.putr([8, ['Interpacket']]) + self.state = 'FIND BREAK' + self.run_bit = pins[0] + self.run_start = self.samplenum + else: + self.putr([7, ['Interframe']]) + self.state = 'READ BYTE' + self.bit = 0 + self.run_start = self.samplenum diff --git a/decoders/ds1307/pd.py b/decoders/ds1307/pd.py index b784b6d..f181fd9 100644 --- a/decoders/ds1307/pd.py +++ b/decoders/ds1307/pd.py @@ -21,6 +21,7 @@ import re import sigrokdecode as srd +from common.srdhelper import bcd2int days_of_week = ( 'Sunday', 'Monday', 'Tuesday', 'Wednesday', @@ -51,10 +52,6 @@ def regs_and_bits(): l += [('bit-' + re.sub('\/| ', '-', b).lower(), b + ' bit') for b in bits] return tuple(l) -# Return the specified BCD number (max. 8 bits) as integer. -def bcd2int(b): - return (b & 0x0f) + ((b >> 4) * 10) - class Decoder(srd.Decoder): api_version = 2 id = 'ds1307' @@ -78,7 +75,7 @@ class Decoder(srd.Decoder): ('warnings', 'Warnings', (28,)), ) - def __init__(self, **kwargs): + def __init__(self): self.state = 'IDLE' self.hours = -1 self.minutes = -1 diff --git a/decoders/edid/pd.py b/decoders/edid/pd.py index b154de7..389fbda 100644 --- a/decoders/edid/pd.py +++ b/decoders/edid/pd.py @@ -90,7 +90,7 @@ class Decoder(srd.Decoder): ('fields', 'Fields', (0,)), ) - def __init__(self, **kwargs): + def __init__(self): self.state = None # Received data items, used as an index into samplenum/data self.cnt = 0 @@ -140,10 +140,10 @@ class Decoder(srd.Decoder): [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]]]) + ['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]]]) + ['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']]) diff --git a/decoders/eeprom24xx/pd.py b/decoders/eeprom24xx/pd.py index 386431e..73269a6 100644 --- a/decoders/eeprom24xx/pd.py +++ b/decoders/eeprom24xx/pd.py @@ -73,7 +73,7 @@ class Decoder(srd.Decoder): ('binary', 'Binary'), ) - def __init__(self, **kwargs): + def __init__(self): self.reset() def start(self): diff --git a/decoders/em4100/pd.py b/decoders/em4100/pd.py index d1a7ba3..0768f9d 100644 --- a/decoders/em4100/pd.py +++ b/decoders/em4100/pd.py @@ -5,8 +5,8 @@ ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by -## the Free Software Foundation; either data 2 of the License, or -## (at your option) any later data. +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -62,7 +62,7 @@ class Decoder(srd.Decoder): ('tags', 'Tags', (9,)), ) - def __init__(self, **kwargs): + def __init__(self): self.samplerate = None self.oldpin = None self.last_samplenum = None @@ -74,12 +74,12 @@ class Decoder(srd.Decoder): self.oldpl = 0 self.oldsamplenum = 0 self.last_bit_pos = 0 - self.first_ss = 0 + self.ss_first = 0 self.first_one = 0 self.state = 'HEADER' self.data = 0 self.data_bits = 0 - self.data_ss = 0 + self.ss_data = 0 self.data_parity = 0 self.payload_cnt = 0 self.data_col_parity = [0, 0, 0, 0, 0, 0] @@ -105,14 +105,14 @@ class Decoder(srd.Decoder): if self.first_one > 0: self.first_one += 1 if self.first_one == 9: - self.put(self.first_ss, es, self.out_ann, + 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.first_ss = ss + self.ss_first = ss if bit == 0: self.first_one = 0 @@ -121,14 +121,14 @@ class Decoder(srd.Decoder): if self.state == 'PAYLOAD': self.payload_cnt += 1 if self.data_bits == 0: - self.data_ss = ss + 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.data_ss, ss, self.out_ann, + 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 @@ -150,7 +150,7 @@ class Decoder(srd.Decoder): if self.state == 'TRAILER': self.payload_cnt += 1 if self.data_bits == 0: - self.data_ss = ss + self.ss_data = ss self.data = 0 self.data_parity = 0 self.data_bits += 1 @@ -172,7 +172,7 @@ class Decoder(srd.Decoder): # 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.first_ss, es, self.out_ann, + self.put(self.ss_first, es, self.out_ann, [9, ['Tag: %010X' % self.tag, 'Tag', 'T']]) self.tag = 0 diff --git a/decoders/em4305/__init__.py b/decoders/em4305/__init__.py new file mode 100644 index 0000000..1c1896a --- /dev/null +++ b/decoders/em4305/__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, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + +''' +EM4305 is a 100-150kHz RFID protocol. +''' + +from .pd import Decoder diff --git a/decoders/em4305/pd.py b/decoders/em4305/pd.py new file mode 100644 index 0000000..73ed955 --- /dev/null +++ b/decoders/em4305/pd.py @@ -0,0 +1,396 @@ +## +## 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, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + +import sigrokdecode as srd + +class SamplerateError(Exception): + pass + +class Decoder(srd.Decoder): + api_version = 2 + id = 'em4305' + name = 'EM4305' + longname = 'RFID EM4205/EM4305' + desc = 'EM4205/EM4305 100-150kHz RFID protocol.' + license = 'gplv2+' + inputs = ['logic'] + outputs = ['em4305'] + 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.samplerate = None + self.oldpin = 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, ss, es, data): + if not self.samplerate: + raise SamplerateError('Cannot decode without samplerate.') + for (self.samplenum, (pin,)) in data: + # Ignore identical samples early on (for performance reasons). + if self.oldpin == pin: + continue + + if self.oldpin is None: + self.oldpin = pin + self.last_samplenum = self.samplenum + self.oldsamplenum = 0 + self.old_gap_end = 0 + self.gap_detected = 0 + self.bit_nr = 0 + continue + + if self.oldpin != pin: + 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 + self.oldpin = pin diff --git a/decoders/gpib/__init__.py b/decoders/gpib/__init__.py new file mode 100644 index 0000000..3dae563 --- /dev/null +++ b/decoders/gpib/__init__.py @@ -0,0 +1,25 @@ +## +## 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, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + +''' +This protocol decoder can decode the GPIB (IEEE-488) protocol. +''' + +from .pd import Decoder diff --git a/decoders/gpib/pd.py b/decoders/gpib/pd.py new file mode 100644 index 0000000..0712966 --- /dev/null +++ b/decoders/gpib/pd.py @@ -0,0 +1,189 @@ +## +## 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, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + +import sigrokdecode as srd + +class Decoder(srd.Decoder): + api_version = 2 + id = 'gpib' + name = 'GPIB' + longname = 'General Purpose Interface Bus' + desc = 'IEEE-488 GPIB / HPIB protocol.' + license = 'gplv2+' + inputs = ['logic'] + outputs = ['gpib'] + 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.olddav = None + self.items = [] + self.itemcount = 0 + self.saved_item = None + self.saved_ATN = False + self.saved_EOI = False + self.samplenum = 0 + self.oldpins = None + 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 find_falling_dav_edge(self, dav, datapins): + # Ignore sample if the DAV pin hasn't changed. + if dav == self.olddav: + return + self.olddav = dav + # Sample on falling DAV edge. + if dav == 1: + return + + # Found the correct DAV edge, now get the bits. + self.handle_bits(datapins) + + def decode(self, ss, es, data): + lsn = self.options['sample_total'] + + for (self.samplenum, pins) in data: + if lsn > 0: + if (lsn - self.samplenum) == 1: # Show the last data word. + self.handle_bits(pins) + + # Ignore identical samples early on (for performance reasons). + if self.oldpins == pins: + continue + self.oldpins = pins + + self.find_falling_dav_edge(pins[9], pins) diff --git a/decoders/guess_bitrate/pd.py b/decoders/guess_bitrate/pd.py index 250d519..92d3520 100644 --- a/decoders/guess_bitrate/pd.py +++ b/decoders/guess_bitrate/pd.py @@ -42,7 +42,7 @@ class Decoder(srd.Decoder): def putx(self, data): self.put(self.ss_edge, self.samplenum, self.out_ann, data) - def __init__(self, **kwargs): + def __init__(self): self.olddata = None self.ss_edge = None self.first_transition = True diff --git a/decoders/i2c/pd.py b/decoders/i2c/pd.py index 2b8b302..a056ef5 100644 --- a/decoders/i2c/pd.py +++ b/decoders/i2c/pd.py @@ -108,7 +108,7 @@ class Decoder(srd.Decoder): ('data-write', 'Data write'), ) - def __init__(self, **kwargs): + def __init__(self): self.samplerate = None self.ss = self.es = self.ss_byte = -1 self.samplenum = None diff --git a/decoders/i2cdemux/pd.py b/decoders/i2cdemux/pd.py index 68b75a0..5e83a21 100644 --- a/decoders/i2cdemux/pd.py +++ b/decoders/i2cdemux/pd.py @@ -30,7 +30,7 @@ class Decoder(srd.Decoder): inputs = ['i2c'] outputs = [] # TODO: Only known at run-time. - def __init__(self, **kwargs): + def __init__(self): self.packets = [] # Local cache of I²C packets self.slaves = [] # List of known slave addresses self.stream = -1 # Current output stream diff --git a/decoders/i2cfilter/pd.py b/decoders/i2cfilter/pd.py index 3c02a2e..c3f148f 100644 --- a/decoders/i2cfilter/pd.py +++ b/decoders/i2cfilter/pd.py @@ -38,7 +38,7 @@ class Decoder(srd.Decoder): 'values': ('read', 'write', 'both')} ) - def __init__(self, **kwargs): + def __init__(self): self.curslave = -1 self.curdirection = None self.packets = [] # Local cache of I²C packets diff --git a/decoders/i2s/pd.py b/decoders/i2s/pd.py index 6b94c10..3287a72 100644 --- a/decoders/i2s/pd.py +++ b/decoders/i2s/pd.py @@ -59,7 +59,7 @@ class Decoder(srd.Decoder): ('wav', 'WAV file'), ) - def __init__(self, **kwargs): + def __init__(self): self.samplerate = None self.oldsck = 1 self.oldws = 1 diff --git a/decoders/ir_nec/pd.py b/decoders/ir_nec/pd.py index d14c7d3..8c589fa 100644 --- a/decoders/ir_nec/pd.py +++ b/decoders/ir_nec/pd.py @@ -99,7 +99,7 @@ class Decoder(srd.Decoder): [11, ['%s: %s' % (dev, btn[0]), '%s: %s' % (dev, btn[1]), '%s' % btn[1]]]) - def __init__(self, **kwargs): + def __init__(self): self.state = 'IDLE' self.ss_bit = self.ss_start = self.ss_other_edge = self.ss_remote = 0 self.data = self.count = self.active = self.old_ir = None diff --git a/decoders/ir_rc5/pd.py b/decoders/ir_rc5/pd.py index e1dd42f..ae20d3a 100644 --- a/decoders/ir_rc5/pd.py +++ b/decoders/ir_rc5/pd.py @@ -56,7 +56,7 @@ class Decoder(srd.Decoder): ('fields', 'Fields', (1, 2, 3, 4, 5, 6)), ) - def __init__(self, **kwargs): + def __init__(self): self.samplerate = None self.samplenum = None self.edges, self.bits, self.ss_es_bits = [], [], [] diff --git a/decoders/jitter/pd.py b/decoders/jitter/pd.py index ba1bffa..c3579a8 100644 --- a/decoders/jitter/pd.py +++ b/decoders/jitter/pd.py @@ -63,7 +63,7 @@ class Decoder(srd.Decoder): ('ascii-float', 'Jitter values as newline-separated ASCII floats'), ) - def __init__(self, **kwargs): + def __init__(self): self.state = 'CLK' self.samplerate = None self.oldpin = None @@ -91,17 +91,17 @@ class Decoder(srd.Decoder): def putx(self, delta): # Adjust granularity. if delta == 0 or delta >= 1: - delta_s = "%.1fs" % (delta) + delta_s = '%.1fs' % (delta) elif delta <= 1e-12: - delta_s = "%.1ffs" % (delta * 1e15) + delta_s = '%.1ffs' % (delta * 1e15) elif delta <= 1e-9: - delta_s = "%.1fps" % (delta * 1e12) + delta_s = '%.1fps' % (delta * 1e12) elif delta <= 1e-6: - delta_s = "%.1fns" % (delta * 1e9) + delta_s = '%.1fns' % (delta * 1e9) elif delta <= 1e-3: - delta_s = "%.1fμs" % (delta * 1e6) + delta_s = '%.1fμs' % (delta * 1e6) else: - delta_s = "%.1fms" % (delta * 1e3) + delta_s = '%.1fms' % (delta * 1e3) self.put(self.clk_start, self.sig_start, self.out_ann, [0, [delta_s]]) diff --git a/decoders/jtag/pd.py b/decoders/jtag/pd.py index aaa9b6a..83d4b28 100644 --- a/decoders/jtag/pd.py +++ b/decoders/jtag/pd.py @@ -88,7 +88,7 @@ class Decoder(srd.Decoder): ('states', 'States', tuple(range(15 + 1))), ) - def __init__(self, **kwargs): + def __init__(self): # self.state = 'TEST-LOGIC-RESET' self.state = 'RUN-TEST/IDLE' self.oldstate = None diff --git a/decoders/jtag_stm32/pd.py b/decoders/jtag_stm32/pd.py index bc3a4c7..ff5bb77 100644 --- a/decoders/jtag_stm32/pd.py +++ b/decoders/jtag_stm32/pd.py @@ -156,7 +156,7 @@ class Decoder(srd.Decoder): ('warnings', 'Warnings', (3,)), ) - def __init__(self, **kwargs): + def __init__(self): self.state = 'IDLE' self.samplenums = None diff --git a/decoders/lm75/pd.py b/decoders/lm75/pd.py index 6b2bfa8..50e3c91 100644 --- a/decoders/lm75/pd.py +++ b/decoders/lm75/pd.py @@ -62,7 +62,7 @@ class Decoder(srd.Decoder): ('warnings', 'Human-readable warnings'), ) - def __init__(self, **kwargs): + def __init__(self): self.state = 'IDLE' self.reg = 0x00 # Currently selected register self.databytes = [] diff --git a/decoders/lpc/pd.py b/decoders/lpc/pd.py index 5e25db4..9368394 100644 --- a/decoders/lpc/pd.py +++ b/decoders/lpc/pd.py @@ -136,7 +136,7 @@ class Decoder(srd.Decoder): ('warnings', 'Warnings', (0,)), ) - def __init__(self, **kwargs): + def __init__(self): self.state = 'IDLE' self.oldlclk = -1 self.samplenum = 0 diff --git a/decoders/maxim_ds28ea00/pd.py b/decoders/maxim_ds28ea00/pd.py index 229331f..c7ff7df 100644 --- a/decoders/maxim_ds28ea00/pd.py +++ b/decoders/maxim_ds28ea00/pd.py @@ -48,7 +48,7 @@ class Decoder(srd.Decoder): ('text', 'Human-readable text'), ) - def __init__(self, **kwargs): + def __init__(self): self.trn_beg = 0 self.trn_end = 0 self.state = 'ROM' diff --git a/decoders/mdio/__init__.py b/decoders/mdio/__init__.py index d9028a3..98213b9 100644 --- a/decoders/mdio/__init__.py +++ b/decoders/mdio/__init__.py @@ -1,29 +1,39 @@ ## ## This file is part of the libsigrokdecode project. ## -## Copyright (C) 2014 Aurelien Jacobs +## Copyright (C) 2016 Elias Oenal +## All rights reserved. ## -## This program is free software; you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published by -## the Free Software Foundation; either version 2 of the License, or -## (at your option) any later version. +## Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are met: ## -## This program is distributed in the hope that it will be useful, -## but WITHOUT ANY WARRANTY; without even the implied warranty of -## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -## GNU General Public License for more details. +## 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. ## -## 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 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, with a clock line (MDC) and a bi-directional -data line (MDIO). +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/decoders/mdio/pd.py b/decoders/mdio/pd.py index bb1f53f..873079a 100644 --- a/decoders/mdio/pd.py +++ b/decoders/mdio/pd.py @@ -1,21 +1,29 @@ ## ## This file is part of the libsigrokdecode project. ## -## Copyright (C) 2014 Aurelien Jacobs +## Copyright (C) 2016 Elias Oenal +## All rights reserved. ## -## This program is free software; you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published by -## the Free Software Foundation; either version 2 of the License, or -## (at your option) any later version. +## Redistribution and use in source and binary forms, with or without +## modification, are permitted provided that the following conditions are met: ## -## This program is distributed in the hope that it will be useful, -## but WITHOUT ANY WARRANTY; without even the implied warranty of -## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -## GNU General Public License for more details. +## 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. ## -## 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 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 @@ -26,235 +34,297 @@ class Decoder(srd.Decoder): name = 'MDIO' longname = 'Management Data Input/Output' desc = 'Half-duplex sync serial bus for MII management between MAC and PHY.' - license = 'gplv2+' + license = 'bsd' inputs = ['logic'] outputs = ['mdio'] 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 = ( - ('mdio-data', 'MDIO data'), - ('mdio-bits', 'MDIO bits'), - ('errors', 'Human-readable errors'), + ('bit-val', 'Bit value'), + ('bit-num', 'Bit number'), + ('frame', 'Frame'), + ('frame-idle', 'Bus idle state'), + ('frame-error', 'Frame error'), + ('decode', 'Decode'), ) annotation_rows = ( - ('mdio-data', 'MDIO data', (0,)), - ('mdio-bits', 'MDIO bits', (1,)), - ('other', 'Other', (2,)), + ('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.oldmdc = 0 - self.ss_block = -1 + self.last_mdc = 1 + self.illegal_bus = 0 self.samplenum = -1 - self.oldpins = None + 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 putw(self, data): - self.put(self.ss_block, self.samplenum, self.out_ann, data) + 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 putbit(self, mdio, start, stop): - # Bit annotations. - self.put(start, stop, self.out_ann, [1, ['%d' % mdio]]) + def putff(self, data): + self.put(self.ss_frame_field, self.samplenum, self.out_ann, data) def putdata(self): - # FIXME: Only pass data, no bits. - # Pass MDIO bits and then data to the next PD up the stack. - ss, es = self.mdiobits[-1][1], self.mdiobits[0][2] - - # self.put(ss, es, self.out_python, ['BITS', self.mdiobits]) - self.put(ss, es, self.out_python, ['DATA', self.mdiodata]) - - # Bit annotations. - for bit in self.mdiobits: - self.put(bit[1], bit[2], self.out_ann, [1, ['%d' % bit[0]]]) - - # Error annotation if an error happened. - if self.error: - self.put(self.ss_bit, self.es_error, self.out_ann, [2, [self.error]]) - return - - op = 'READ' if self.operation else 'WRITE' - - # Dataword annotations. - if self.ss_preamble != -1: - self.put(self.ss_preamble, self.ss_start, self.out_ann, [0, ['PREAMBLE']]) - self.put(self.ss_start, self.ss_operation, self.out_ann, [0, ['START']]) - self.put(self.ss_operation, self.ss_phy, self.out_ann, [0, [op]]) - self.put(self.ss_phy, self.ss_reg, self.out_ann, [0, ['PHY: %d' % self.phy]]) - self.put(self.ss_reg, self.ss_turnaround, self.out_ann, [0, ['REG: %d' % self.reg]]) - self.put(self.ss_turnaround, self.ss_data, self.out_ann, [0, ['TURNAROUND']]) - self.put(self.ss_data, self.es_data, self.out_ann, [0, ['DATA: %04X' % self.data]]) + 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 ' % self.clause45_addr) + + 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.mdiodata = 0 self.mdiobits = [] - self.bitcount = 0 - self.ss_preamble = -1 - self.ss_start = -1 - self.ss_operation = -1 - self.ss_phy = -1 - self.ss_reg = -1 - self.ss_turnaround = -1 - self.ss_data = -1 - self.phy = 0 - self.phy_bits = 0 - self.reg = 0 - self.reg_bits = 0 - self.data = 0 - self.data_bits = 0 - self.state = 'PREAMBLE' - self.error = None - - def parse_preamble(self, mdio): - if self.ss_preamble == -1: - self.ss_preamble = self.samplenum - if mdio != 1: - self.error = 'Invalid preamble: could not find 32 consecutive bits set to 1' - self.state = 'ERROR' - elif self.bitcount == 31: - self.state = 'START' + 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 - def parse_start(self, mdio): - if self.ss_start == -1: - if mdio != 0: - self.error = 'Invalid start bits: should be 01' - self.state = 'ERROR' + if mdio: + self.opcode = 2 else: - self.ss_start = self.samplenum + self.opcode = 0 else: - if mdio != 1: - self.error = 'Invalid start bits: should be 01' - self.state = 'ERROR' + if self.clause45: + self.state = 'PRTAD' + self.opcode += mdio else: - self.state = 'OPERATION' - - def parse_operation(self, mdio): - if self.ss_operation == -1: - self.ss_operation = self.samplenum - self.operation = mdio - else: - if mdio == self.operation: - self.error = 'Invalid operation bits' - self.state = 'ERROR' + 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: - self.state = 'PHY' - - def parse_phy(self, mdio): - if self.ss_phy == -1: - self.ss_phy = self.samplenum - self.phy_bits += 1 - self.phy |= mdio << (5 - self.phy_bits) - if self.phy_bits == 5: - self.state = 'REG' - - def parse_reg(self, mdio): - if self.ss_reg == -1: - self.ss_reg = self.samplenum - self.reg_bits += 1 - self.reg |= mdio << (5 - self.reg_bits) - if self.reg_bits == 5: - self.state = 'TURNAROUND' - - def parse_turnaround(self, mdio): - if self.ss_turnaround == -1: - if self.operation == 0 and mdio != 1: - self.error = 'Invalid turnaround bits' - self.state = 'ERROR' + 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: - self.ss_turnaround = self.samplenum + 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: - self.error = 'Invalid turnaround bits' - self.state = 'ERROR' - else: - self.state = 'DATA' - - def parse_data(self, mdio): - if self.ss_data == -1: - self.ss_data = self.samplenum - self.data_bits += 1 - self.data |= mdio << (16 - self.data_bits) - if self.data_bits == 16: - self.es_data = self.samplenum + int((self.samplenum - self.ss_data) / 15) - self.state = 'DONE' - - def parse_error(self, mdio): - if self.bitcount == 63: - self.es_error = self.samplenum + int((self.samplenum - self.ss_bit) / 63) - self.state = 'DONE' - - def handle_bit(self, mdio): - # If this is the first bit of a command, save its sample number. - if self.bitcount == 0: - self.ss_bit = self.samplenum - # No preamble? - if mdio == 0: - self.state = 'START' - - # Guesstimate the endsample for this bit (can be overridden below). - es = self.samplenum - if self.bitcount > 0: - es += self.samplenum - self.mdiobits[0][1] - - self.mdiobits.insert(0, [mdio, self.samplenum, es]) - - if self.bitcount > 0: - self.bitsamples = (self.samplenum - self.ss_bit) / self.bitcount - self.mdiobits[1][2] = self.samplenum - - if self.state == 'PREAMBLE': - self.parse_preamble(mdio) - elif self.state == 'START': - self.parse_start(mdio) - elif self.state == 'OPERATION': - self.parse_operation(mdio) - elif self.state == 'PHY': - self.parse_phy(mdio) - elif self.state == 'REG': - self.parse_reg(mdio) - elif self.state == 'TURNAROUND': - self.parse_turnaround(mdio) - elif self.state == 'DATA': - self.parse_data(mdio) - elif self.state == 'ERROR': - self.parse_error(mdio) - - self.bitcount += 1 - if self.state == 'DONE': - self.putdata() - self.reset_decoder_state() - - def find_mdc_edge(self, mdc, mdio): - # Output the current error annotation if the clock stopped running - if self.state == 'ERROR' and self.samplenum - self.clocksample > (1.5 * self.bitsamples): - self.es_error = self.clocksample + int((self.clocksample - self.ss_bit) / self.bitcount) + 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, ['TURNAROUND', 'TA', 'T']]) + if self.ta_invalid: + self.putff([4, ['TURNAROUND%s' % self.ta_invalid, + '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() - # Ignore sample if the clock pin hasn't changed. - if mdc == self.oldmdc: - return + 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] - self.oldmdc = mdc + def handle_bit(self, mdio): + self.bitcount += 1 + self.mdiobits.insert(0, [mdio, self.samplenum, -1]) - if mdc == 0: # Sample on rising clock edge. - return + 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]) - # Found the correct clock edge, now get/handle the bit(s). - self.clocksample = self.samplenum - self.handle_bit(mdio) + self.process_state(self.state, mdio) def decode(self, ss, es, data): for (self.samplenum, pins) in data: # Ignore identical samples early on (for performance reasons). - if self.oldpins == pins: + if self.last_mdc == pins[0]: + continue + self.last_mdc = pins[0] + if pins[0] == 0: # Check for rising edge. continue - self.oldpins, (mdc, mdio) = pins, pins - self.find_mdc_edge(mdc, mdio) + # Found the correct clock edge, now get/handle the bit(s). + self.handle_bit(pins[1]) diff --git a/decoders/midi/lists.py b/decoders/midi/lists.py index c72f5c9..ce4c1ae 100644 --- a/decoders/midi/lists.py +++ b/decoders/midi/lists.py @@ -1,7 +1,8 @@ ## ## This file is part of the libsigrokdecode project. ## -## Copyright (C) 2013 Uwe Hermann +## 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 @@ -18,40 +19,41 @@ ## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA ## +# Each status byte has 3 string names, each shorter than the previous status_bytes = { # Channel voice messages - 0x80: 'note off', - 0x90: 'note on', # However, velocity = 0 means "note off". - 0xa0: 'polyphonic key pressure / aftertouch', - 0xb0: 'control change', - 0xc0: 'program change', - 0xd0: 'channel pressure / aftertouch', - 0xe0: 'pitch bend change', + 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)', + 0xf0: ['system exclusive', 'SysEx', 'SE'], # System common messages - 0xf1: 'MIDI time code quarter frame', - 0xf2: 'song position pointer', - 0xf3: 'song select', - 0xf4: 'undefined', - 0xf5: 'undefined', - 0xf6: 'tune request', - 0xf7: 'end of system exclusive (EOX)', + 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', - 0xf9: 'undefined', - 0xfa: 'start', - 0xfb: 'continue', - 0xfc: 'stop', - 0xfd: 'undefined', - 0xfe: 'active sensing', - 0xff: 'system reset', + 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) @@ -156,37 +158,37 @@ universal_sysex_realtime = { # Note: Not all IDs are used/listed, i.e. there are some "holes". sysex_manufacturer_ids = { - # American group - (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', + # 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.', @@ -306,32 +308,32 @@ sysex_manufacturer_ids = { (0x00, 0x01, 0x02): 'Crystal Semiconductor', (0x00, 0x01, 0x03): 'Rockwell Semiconductor', - # European group - (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', + # 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', @@ -378,92 +380,462 @@ sysex_manufacturer_ids = { (0x00, 0x20, 0x2d): 'Blue Chip Music Tech', (0x00, 0x20, 0x2e): 'BEE OH Corp', - # Japanese group - (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.', + # 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', - 0x01: 'modulation wheel/lever', - 0x02: 'breath controller', - # 0x03: undefined - 0x04: 'foot controller', - 0x05: 'portamento time', - 0x06: 'data entry MSB', - 0x07: 'channel volume (formerly main volume)', - 0x08: 'balance', - # 0x09: undefined - 0x0a: 'pan', - 0x0b: 'expression controller', - 0x0c: 'effect control 1', - 0x0d: 'effect control 2', - # 0x0e-0x0f: undefined - 0x10: 'general purpose controller 1', - 0x11: 'general purpose controller 2', - 0x12: 'general purpose controller 3', - 0x13: 'general purpose controller 4', - # 0x14-0x1f: undefined - # 0x20-0x3f: LSB for values 0x00-0x1f - 0x40: 'damper pedal (sustain)', - 0x41: 'portamento on/off', - 0x42: 'sostenuto', - 0x43: 'soft pedal', - 0x44: 'legato footswitch', # vv: 00-3f = normal, 40-7f = legato - 0x45: 'hold 2', - 0x46: 'sound controller 1 (default: sound variation)', - 0x47: 'sound controller 2 (default: timbre / harmonic intensity)', - 0x48: 'sound controller 3 (default: release time)', - 0x49: 'sound controller 4 (default: attack time)', - 0x4a: 'sound controller 5 (default: brightness)', - 0x4b: 'sound controller 6 (GM2 default: decay time)', - 0x4c: 'sound controller 7 (GM2 default: vibrato rate)', - 0x4d: 'sound controller 8 (GM2 default: vibrato depth)', - 0x4e: 'sound controller 9 (GM2 default: vibrato delay)', - 0x4f: 'sound controller 10', - 0x50: 'general purpose controller 5', - 0x51: 'general purpose controller 6', - 0x52: 'general purpose controller 7', - 0x53: 'general purpose controller 8', - 0x54: 'portamento control', + 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)', - 0x5c: 'effects 2 depth (formerly tremolo depth)', - 0x5d: 'effects 3 depth (formerly chorus depth)', - 0x5e: 'effects 4 depth (formerly celeste/detune depth)', - 0x5f: 'effects 5 depth (formerly phaser depth)', - 0x60: 'data increment', - 0x61: 'data decrement', - 0x62: 'non-registered parameter number LSB', - 0x63: 'non-registered parameter number MSB', - 0x64: 'registered parameter number LSB', - 0x65: 'registered parameter number MSB', + 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', - 0x79: 'reset all controllers', - 0x7a: 'local control on/off', - 0x7b: 'all notes off', - 0x7c: 'omni mode off', # all notes off - 0x7d: 'omni mode on', # all notes off - 0x7e: 'poly mode off', # mono mode on, all notes off - 0x7f: 'poly mode on', # mono mode off, all notes off + 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/decoders/midi/pd.py b/decoders/midi/pd.py index 5915976..18c439f 100644 --- a/decoders/midi/pd.py +++ b/decoders/midi/pd.py @@ -1,7 +1,8 @@ ## ## This file is part of the libsigrokdecode project. ## -## Copyright (C) 2013 Uwe Hermann +## 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 @@ -35,11 +36,19 @@ class Decoder(srd.Decoder): outputs = ['midi'] 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, **kwargs): - self.cmd = [] + def __init__(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 @@ -51,130 +60,524 @@ class Decoder(srd.Decoder): def putx(self, data): self.put(self.ss_block, self.es_block, self.out_ann, data) - def handle_channel_msg_0x80(self): + 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) < 3: + if len(c) < 2: + self.check_for_garbage_flush(is_flushed) return self.es_block = self.es - msg, chan, note, velocity = c[0] & 0xf0, (c[0] & 0x0f) + 1, c[1], c[2] - self.putx([0, ['Channel %d: %s (note = %d, velocity = %d)' % \ - (chan, status_bytes[msg], note, velocity)]]) + 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): + 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) < 3: + if len(c) < 2: + self.check_for_garbage_flush(is_flushed) return self.es_block = self.es - msg, chan, note, velocity = c[0] & 0xf0, (c[0] & 0x0f) + 1, c[1], c[2] - s = 'note off' if (velocity == 0) else status_bytes[msg] - self.putx([0, ['Channel %d: %s (note = %d, velocity = %d)' % \ - (chan, s, note, velocity)]]) + 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): + def handle_channel_msg_0xa0(self, is_flushed): # Polyphonic key pressure / aftertouch: An kk vv # n = channel, kk = polyphonic key pressure, vv = pressure value - pass # TODO + 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) - chan, vv = (self.cmd[0] & 0x0f) + 1, self.cmd[2] - t = 'normal' if vv <= 0x3f else 'legato' - self.putx([0, ['Channel %d: control function \'%s\' = %s' % \ - (chan, control_functions[0x44], t)]]) + 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 - chan, kk = (self.cmd[0] & 0x0f) + 1, self.cmd[2] - self.putx([0, ['Channel %d: control function \'%s\' (source note ' \ - '= %d)' % (chan, control_functions[0x54], kk)]]) + 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 - chan, fn, param = (c[0] & 0x0f) + 1, c[1], c[2] - ctrl_fn = control_functions.get(fn, 'undefined') - self.putx([0, ['Channel %d: control change to function \'%s\' ' \ - '(param = 0x%02x)' % (chan, ctrl_fn, param)]]) + 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): + 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) and (c[1] in range(0x78, 0x7f + 1)): - # This is not a control change, but rather a channel mode message. - # TODO: Handle channel mode messages. - return - if len(c) < 3: + if len(c) < 2: + self.check_for_garbage_flush(is_flushed) return self.es_block = self.es - handle_ctrl = getattr(self, 'handle_controller_0x%02x' % c[1], + 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): + def handle_channel_msg_0xc0(self, is_flushed): # Program change: Cn pp # n = channel, pp = program number (0 - 127) - pass # TODO + 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): + def handle_channel_msg_0xd0(self, is_flushed): # Channel pressure / aftertouch: Dn vv # n = channel, vv = pressure value - pass # TODO + 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): + 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 - pass # TODO + 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): - msg_type = self.cmd[0] & 0xf0 - self.putx([0, ['Unknown channel message type: 0x%02x' % msg_type]]) - # TODO: Handle properly. + 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): - self.cmd.append(newbyte) - msg_type = self.cmd[0] & 0xf0 + 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() + handle_msg(newbyte is None) def handle_sysex_msg(self, newbyte): - # SysEx message: 1 status byte, x data bytes, EOX byte - self.cmd.append(newbyte) - if newbyte != 0xf7: # EOX + # 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 - # TODO: Get message ID, vendor ID, message contents, etc. - self.putx([0, ['SysEx message']]) + 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): - pass # TODO + # 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 - self.putx([0, ['System realtime message: %s' % status_bytes[newbyte]]]) + 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 - self.ss, self.es = ss, es - # We're only interested in the byte value (not individual bits). pdata = pdata[0] @@ -183,29 +586,39 @@ class Decoder(srd.Decoder): # - 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 self.state == 'IDLE': - # Wait until we see a status byte (bit 7 must be set). - if pdata < 0x80: - return # TODO: How to handle? Ignore? + 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. - self.ss_block = ss - if pdata in range(0x80, 0xef + 1): - self.state = 'HANDLE CHANNEL MSG' - elif pdata == 0xf0: - self.state = 'HANDLE SYSEX MSG' - elif pdata in range(0xf1, 0xf7 + 1): - self.state = 'HANDLE SYSCOMMON MSG' - elif pdata in range(0xf8, 0xff + 1): - self.state = 'HANDLE SYSREALTIME MSG' + 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 self.state == 'HANDLE CHANNEL MSG': - self.handle_channel_msg(pdata) - elif self.state == 'HANDLE SYSEX MSG': - self.handle_sysex_msg(pdata) - elif self.state == 'HANDLE SYSCOMMON MSG': - self.handle_syscommon_msg(pdata) - elif self.state == 'HANDLE SYSREALTIME MSG': - self.handle_sysrealtime_msg(pdata) + 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/decoders/mlx90614/pd.py b/decoders/mlx90614/pd.py index aa1ead5..0a3abba 100644 --- a/decoders/mlx90614/pd.py +++ b/decoders/mlx90614/pd.py @@ -34,7 +34,7 @@ class Decoder(srd.Decoder): ('kelvin', 'Temperature in Kelvin'), ) - def __init__(self, **kwargs): + def __init__(self): self.state = 'IGNORE START REPEAT' self.data = [] diff --git a/decoders/modbus/pd.py b/decoders/modbus/pd.py index d427844..3420f99 100644 --- a/decoders/modbus/pd.py +++ b/decoders/modbus/pd.py @@ -816,7 +816,7 @@ class Decoder(srd.Decoder): name = 'Modbus' longname = 'Modbus RTU over RS232/RS485' desc = 'Modbus RTU protocol for industrial applications.' - license = 'gplv2+' + license = 'gplv3+' inputs = ['uart'] outputs = ['modbus'] annotations = ( @@ -846,7 +846,7 @@ class Decoder(srd.Decoder): 'values': ('RX', 'TX')}, ) - def __init__(self, **kwargs): + def __init__(self): self.ADUSc = None # Start off with empty slave -> client ADU. self.ADUCs = None # Start off with empty client -> slave ADU. diff --git a/decoders/mrf24j40/pd.py b/decoders/mrf24j40/pd.py index 286fa52..8ef7017 100644 --- a/decoders/mrf24j40/pd.py +++ b/decoders/mrf24j40/pd.py @@ -27,7 +27,7 @@ class Decoder(srd.Decoder): name = 'MRF24J40' longname = 'Microchip MRF24J40' desc = 'IEEE 802.15.4 2.4 GHz RF tranceiver chip.' - license = 'gplv2' + license = 'gplv2+' inputs = ['spi'] outputs = ['mrf24j40'] annotations = ( @@ -43,7 +43,7 @@ class Decoder(srd.Decoder): ('warnings', 'Warnings', (4,)), ) - def __init__(self, **kwargs): + def __init__(self): self.ss_cmd, self.es_cmd = 0, 0 self.mosi_bytes = [] self.miso_bytes = [] diff --git a/decoders/mxc6225xu/pd.py b/decoders/mxc6225xu/pd.py index 962f963..832eb06 100644 --- a/decoders/mxc6225xu/pd.py +++ b/decoders/mxc6225xu/pd.py @@ -72,7 +72,7 @@ class Decoder(srd.Decoder): ('text', 'Human-readable text'), ) - def __init__(self, **kwargs): + def __init__(self): self.state = 'IDLE' def start(self): diff --git a/decoders/nrf24l01/pd.py b/decoders/nrf24l01/pd.py index 2337d4b..d46eea4 100644 --- a/decoders/nrf24l01/pd.py +++ b/decoders/nrf24l01/pd.py @@ -94,7 +94,7 @@ class Decoder(srd.Decoder): ('warnings', 'Warnings', (ann_warn,)), ) - def __init__(self, **kwargs): + def __init__(self): self.next() self.requirements_met = True self.cs_was_released = False diff --git a/decoders/nunchuk/pd.py b/decoders/nunchuk/pd.py index 0861cba..48a7099 100644 --- a/decoders/nunchuk/pd.py +++ b/decoders/nunchuk/pd.py @@ -47,7 +47,7 @@ class Decoder(srd.Decoder): ('warnings', 'Warnings', (14,)), ) - def __init__(self, **kwargs): + def __init__(self): self.state = 'IDLE' self.sx = self.sy = self.ax = self.ay = self.az = self.bz = self.bc = -1 self.databytecount = 0 diff --git a/decoders/onewire_link/pd.py b/decoders/onewire_link/pd.py index 2be0242..bd64aa1 100644 --- a/decoders/onewire_link/pd.py +++ b/decoders/onewire_link/pd.py @@ -93,7 +93,7 @@ class Decoder(srd.Decoder): def putrs(self, data): self.put(self.rise, self.samplenum, self.out_ann, data) - def __init__(self, **kwargs): + def __init__(self): self.samplerate = None self.samplenum = 0 self.state = 'WAIT FOR FALLING EDGE' diff --git a/decoders/onewire_network/pd.py b/decoders/onewire_network/pd.py index bddc4a8..1a7567a 100644 --- a/decoders/onewire_network/pd.py +++ b/decoders/onewire_network/pd.py @@ -45,7 +45,7 @@ class Decoder(srd.Decoder): ('text', 'Human-readable text'), ) - def __init__(self, **kwargs): + def __init__(self): self.ss_block = 0 self.es_block = 0 self.state = 'COMMAND' diff --git a/decoders/pan1321/pd.py b/decoders/pan1321/pd.py index b70defc..a8938c6 100644 --- a/decoders/pan1321/pd.py +++ b/decoders/pan1321/pd.py @@ -39,7 +39,7 @@ class Decoder(srd.Decoder): ('warnings', 'Human-readable warnings'), ) - def __init__(self, **kwargs): + def __init__(self): self.cmd = ['', ''] self.ss_block = None diff --git a/decoders/ps2/__init__.py b/decoders/ps2/__init__.py new file mode 100644 index 0000000..75c4768 --- /dev/null +++ b/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/decoders/ps2/pd.py b/decoders/ps2/pd.py new file mode 100644 index 0000000..6392f1a --- /dev/null +++ b/decoders/ps2/pd.py @@ -0,0 +1,144 @@ +## +## 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, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + +import sigrokdecode as srd +from collections import namedtuple + +class Ann: + BIT, START, STOP, PARITY_OK, PARITY_ERR, DATA, WORD = range(7) + +Bit = namedtuple('Bit', 'val ss es') + +class Decoder(srd.Decoder): + api_version = 2 + id = 'ps2' + name = 'PS/2' + longname = 'PS/2' + desc = 'PS/2 keyboard/mouse interface.' + license = 'gplv2+' + inputs = ['logic'] + outputs = ['ps2'] + channels = ( + {'id': 'clk', 'name': 'Clock', 'desc': 'Clock line'}, + {'id': 'data', 'name': 'Data', 'desc': 'Data line'}, + ) + annotations = ( + ('bit', 'Bit'), + ('start-bit', 'Start bit'), + ('stop-bit', 'Stop bit'), + ('parity-ok', 'Parity OK bit'), + ('parity-err', 'Parity error bit'), + ('data-bit', 'Data bit'), + ('word', 'Word'), + ) + annotation_rows = ( + ('bits', 'Bits', (0,)), + ('fields', 'Fields', (1, 2, 3, 4, 5, 6)), + ) + + def __init__(self): + self.bits = [] + self.prev_pins = None + self.prev_clock = None + self.samplenum = 0 + self.clock_was_high = False + self.bitcount = 0 + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + 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: + return + + # 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) + self.putx(0, [Ann.START, ['Start bit', 'Start', 'S']]) + 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 + + def find_clk_edge(self, clock_pin, data_pin): + # Ignore sample if the clock pin hasn't changed. + if clock_pin == self.prev_clock: + return + self.prev_clock = clock_pin + + # Sample on falling clock edge. + if clock_pin == 1: + return + + # Found the correct clock edge, now get the bits. + self.handle_bits(data_pin) + + def decode(self, ss, es, data): + for (self.samplenum, pins) in data: + clock_pin, data_pin = pins[0], pins[1] + + # Ignore identical samples. + if self.prev_pins == pins: + continue + self.prev_pins = pins + + if clock_pin == 0 and not self.clock_was_high: + continue + self.clock_was_high = True + + self.find_clk_edge(clock_pin, data_pin) diff --git a/decoders/pwm/pd.py b/decoders/pwm/pd.py index 7bf21f4..6ca46b1 100644 --- a/decoders/pwm/pd.py +++ b/decoders/pwm/pd.py @@ -49,8 +49,8 @@ class Decoder(srd.Decoder): ('raw', 'RAW file'), ) - def __init__(self, **kwargs): - self.ss = self.es = None + def __init__(self): + self.ss_block = self.es_block = None self.first_transition = True self.first_samplenum = None self.start_samplenum = None @@ -72,7 +72,7 @@ class Decoder(srd.Decoder): meta=(float, 'Average', 'PWM base (cycle) frequency')) def putx(self, data): - self.put(self.ss, self.es, self.out_ann, data) + self.put(self.ss_block, self.es_block, self.out_ann, data) def putp(self, period_t): # Adjust granularity. @@ -89,7 +89,7 @@ class Decoder(srd.Decoder): else: period_s = '%.1f ms' % (period_t * 1e3) - self.put(self.ss, self.es, self.out_ann, [1, [period_s]]) + self.put(self.ss_block, self.es_block, self.out_ann, [1, [period_s]]) def putb(self, data): self.put(self.num_cycles, self.num_cycles, self.out_binary, data) @@ -122,10 +122,10 @@ class Decoder(srd.Decoder): ratio = float(duty / period) # This interval starts at this edge. - self.ss = self.start_samplenum + self.ss_block = self.start_samplenum # Store the new rising edge position and the ending # edge interval. - self.start_samplenum = self.es = self.samplenum + self.start_samplenum = self.es_block = self.samplenum # Report the duty cycle in percent. percent = float(ratio * 100) @@ -141,10 +141,10 @@ class Decoder(srd.Decoder): # Update and report the new duty cycle average. self.num_cycles += 1 self.average += percent - self.put(self.first_samplenum, self.es, self.out_average, + self.put(self.first_samplenum, self.es_block, self.out_average, float(self.average / self.num_cycles)) else: # Falling edge - self.end_samplenum = self.ss = self.samplenum + self.end_samplenum = self.ss_block = self.samplenum self.oldpin = pins[0] diff --git a/decoders/qi/pd.py b/decoders/qi/pd.py index cba4ce9..6db4bf5 100644 --- a/decoders/qi/pd.py +++ b/decoders/qi/pd.py @@ -73,7 +73,7 @@ class Decoder(srd.Decoder): ('packets', 'Packets', (5, 6, 7)), ) - def __init__(self, **kwargs): + def __init__(self): self.samplerate = None self.reset_variables() diff --git a/decoders/rfm12/pd.py b/decoders/rfm12/pd.py index 9bcbd9d..46e5b07 100644 --- a/decoders/rfm12/pd.py +++ b/decoders/rfm12/pd.py @@ -43,7 +43,7 @@ class Decoder(srd.Decoder): ('interpretation', 'Interpretation', (5,)), ) - def __init__(self, **kwargs): + def __init__(self): self.mosi_bytes, self.miso_bytes = [], [] self.mosi_bits, self.miso_bits = [], [] self.row_pos = [0, 0, 0] diff --git a/decoders/rgb_led_spi/pd.py b/decoders/rgb_led_spi/pd.py index c6e1032..9a465ad 100644 --- a/decoders/rgb_led_spi/pd.py +++ b/decoders/rgb_led_spi/pd.py @@ -26,14 +26,14 @@ class Decoder(srd.Decoder): name = 'RGB LED (SPI)' longname = 'RGB LED string decoder (SPI)' desc = 'RGB LED string protocol (RGB values clocked over SPI).' - license = 'gplv2' + license = 'gplv2+' inputs = ['spi'] outputs = ['rgb_led_spi'] annotations = ( ('rgb', 'RGB values'), ) - def __init__(self, **kwargs): + def __init__(self): self.ss_cmd, self.es_cmd = 0, 0 self.mosi_bytes = [] diff --git a/decoders/rgb_led_ws281x/__init__.py b/decoders/rgb_led_ws281x/__init__.py new file mode 100644 index 0000000..63135a4 --- /dev/null +++ b/decoders/rgb_led_ws281x/__init__.py @@ -0,0 +1,28 @@ +## +## 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, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + +''' +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/decoders/rgb_led_ws281x/pd.py b/decoders/rgb_led_ws281x/pd.py new file mode 100644 index 0000000..6f65960 --- /dev/null +++ b/decoders/rgb_led_ws281x/pd.py @@ -0,0 +1,128 @@ +## +## 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, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + +import sigrokdecode as srd +from functools import reduce + +class SamplerateError(Exception): + pass + +class Decoder(srd.Decoder): + api_version = 2 + id = 'rgb_led_ws281x' + name = 'RGB LED (WS281x)' + longname = 'RGB LED string decoder (WS281x)' + desc = 'RGB LED string protocol (WS281x).' + license = 'gplv3+' + inputs = ['logic'] + outputs = ['rgb_led_ws281x'] + channels = ( + {'id': 'din', 'name': 'DIN', 'desc': 'DIN data line'}, + ) + annotations = ( + ('bit', 'Bit'), + ('reset', 'RESET'), + ('rgb', 'RGB'), + ) + annotation_rows = ( + ('bit', 'Bits', (0, 1)), + ('rgb', 'RGB', (2,)), + ) + + def __init__(self): + self.samplerate = None + self.oldpin = None + self.ss_packet = None + self.ss = None + self.es = None + self.bits = [] + self.inreset = False + + 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) == 24: + grb = reduce(lambda a, b: (a << 1) | b, self.bits) + rgb = (grb & 0xff0000) >> 8 | (grb & 0x00ff00) << 8 | (grb & 0x0000ff) + self.put(self.ss_packet, samplenum, self.out_ann, + [2, ['#%06x' % rgb]]) + self.bits = [] + self.ss_packet = None + + def decode(self, ss, es, data): + if not self.samplerate: + raise SamplerateError('Cannot decode without samplerate.') + + for (samplenum, (pin, )) in data: + if self.oldpin is None: + self.oldpin = pin + continue + + # Check RESET condition (manufacturer recommends 50 usec minimal, + # but real minimum is ~10 usec). + if not self.inreset and not pin and self.es is not None and \ + (samplenum - self.es) / self.samplerate > 50e-6: + + # Decode last bit value. + tH = (self.es - self.ss) / self.samplerate + bit_ = True if tH >= 625e-9 else False + + self.bits.append(bit_) + self.handle_bits(self.es) + + self.put(self.ss, self.es, self.out_ann, [0, ['%d' % bit_]]) + self.put(self.es, samplenum, self.out_ann, + [1, ['RESET', 'RST', 'R']]) + + self.inreset = True + self.bits = [] + self.ss_packet = None + self.ss = None + + if not self.oldpin and pin: + # Rising edge. + if self.ss and self.es: + period = samplenum - self.ss + duty = self.es - self.ss + # Ideal duty for T0H: 33%, T1H: 66%. + bit_ = (duty / period) > 0.5 + + self.put(self.ss, samplenum, self.out_ann, + [0, ['%d' % bit_]]) + + self.bits.append(bit_) + self.handle_bits(samplenum) + + if self.ss_packet is None: + self.ss_packet = samplenum + + self.ss = samplenum + + elif self.oldpin and not pin: + # Falling edge. + self.inreset = False + self.es = samplenum + + self.oldpin = pin diff --git a/decoders/rtc8564/pd.py b/decoders/rtc8564/pd.py index 24a68fb..4ecc15a 100644 --- a/decoders/rtc8564/pd.py +++ b/decoders/rtc8564/pd.py @@ -19,10 +19,7 @@ ## import sigrokdecode as srd - -# Return the specified BCD number (max. 8 bits) as integer. -def bcd2int(b): - return (b & 0x0f) + ((b >> 4) * 10) +from common.srdhelper import bcd2int def reg_list(): l = [] @@ -55,7 +52,7 @@ class Decoder(srd.Decoder): ('date-time', 'Date/time', (9, 10)), ) - def __init__(self, **kwargs): + def __init__(self): self.state = 'IDLE' self.hours = -1 self.minutes = -1 diff --git a/decoders/sdcard_sd/__init__.py b/decoders/sdcard_sd/__init__.py index a05f21a..c43f623 100644 --- a/decoders/sdcard_sd/__init__.py +++ b/decoders/sdcard_sd/__init__.py @@ -1,5 +1,5 @@ ## -## This file is part of the sigrok project. +## This file is part of the libsigrokdecode project. ## ## Copyright (C) 2015 Uwe Hermann ## diff --git a/decoders/sdcard_sd/pd.py b/decoders/sdcard_sd/pd.py index a1eac2b..79c2074 100644 --- a/decoders/sdcard_sd/pd.py +++ b/decoders/sdcard_sd/pd.py @@ -1,5 +1,5 @@ ## -## This file is part of the sigrok project. +## This file is part of the libsigrokdecode project. ## ## Copyright (C) 2015 Uwe Hermann ## @@ -19,7 +19,7 @@ ## import sigrokdecode as srd -from .lists import * +from common.sdcard import (cmd_names, acmd_names, accepted_voltages, card_status, sd_status) class Decoder(srd.Decoder): api_version = 2 @@ -61,7 +61,7 @@ class Decoder(srd.Decoder): ('cmd', 'Commands', tuple(range(128))), ) - def __init__(self, **kwargs): + def __init__(self): self.state = 'GET COMMAND TOKEN' self.token = [] self.oldpins = None diff --git a/decoders/sdcard_spi/pd.py b/decoders/sdcard_spi/pd.py index bc761ee..45490ad 100644 --- a/decoders/sdcard_spi/pd.py +++ b/decoders/sdcard_spi/pd.py @@ -19,57 +19,7 @@ ## import sigrokdecode as srd - -# Normal commands (CMD) -cmd_names = { - 0: 'GO_IDLE_STATE', - 1: 'SEND_OP_COND', - 6: 'SWITCH_FUNC', - 8: 'SEND_IF_COND', - 9: 'SEND_CSD', - 10: 'SEND_CID', - 12: 'STOP_TRANSMISSION', - 13: 'SEND_STATUS', - 16: 'SET_BLOCKLEN', - 17: 'READ_SINGLE_BLOCK', - 18: 'READ_MULTIPLE_BLOCK', - 24: 'WRITE_BLOCK', - 25: 'WRITE_MULTIPLE_BLOCK', - 27: 'PROGRAM_CSD', - 28: 'SET_WRITE_PROT', - 29: 'CLR_WRITE_PROT', - 30: 'SEND_WRITE_PROT', - 32: 'ERASE_WR_BLK_START_ADDR', - 33: 'ERASE_WR_BLK_END_ADDR', - 38: 'ERASE', - 42: 'LOCK_UNLOCK', - 55: 'APP_CMD', - 56: 'GEN_CMD', - 58: 'READ_OCR', - 59: 'CRC_ON_OFF', - # CMD60-63: Reserved for manufacturer -} - -# Application-specific commands (ACMD) -acmd_names = { - 13: 'SD_STATUS', - 18: 'Reserved for SD security applications', - 22: 'SEND_NUM_WR_BLOCKS', - 23: 'SET_WR_BLK_ERASE_COUNT', - 25: 'Reserved for SD security applications', - 26: 'Reserved for SD security applications', - 38: 'Reserved for SD security applications', - 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', - 51: 'SEND_SCR', -} +from common.sdcard import (cmd_names, acmd_names) class Decoder(srd.Decoder): api_version = 2 @@ -96,7 +46,7 @@ class Decoder(srd.Decoder): ('cmd-reply', 'Commands/replies', tuple(range(134))), ) - def __init__(self, **kwargs): + def __init__(self): self.state = 'IDLE' self.ss, self.es = 0, 0 self.ss_bit, self.es_bit = 0, 0 @@ -122,7 +72,12 @@ class Decoder(srd.Decoder): def cmd_name(self, cmd): c = acmd_names if self.is_acmd else cmd_names - return c.get(cmd, 'Unknown') + 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. diff --git a/decoders/spdif/pd.py b/decoders/spdif/pd.py index e6977e2..da3ca28 100644 --- a/decoders/spdif/pd.py +++ b/decoders/spdif/pd.py @@ -58,7 +58,7 @@ class Decoder(srd.Decoder): def puty(self, data): self.put(self.ss_edge, self.samplenum, self.out_ann, data) - def __init__(self, **kwargs): + def __init__(self): self.state = 'GET FIRST PULSE WIDTH' self.olddata = None self.ss_edge = None diff --git a/decoders/spi/pd.py b/decoders/spi/pd.py index fc8c7d0..ce007e2 100644 --- a/decoders/spi/pd.py +++ b/decoders/spi/pd.py @@ -152,6 +152,7 @@ class Decoder(srd.Decoder): 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 putw(self, data): self.put(self.ss_block, self.samplenum, self.out_ann, data) @@ -165,10 +166,12 @@ class Decoder(srd.Decoder): if self.have_miso: ss, es = self.misobits[-1][1], self.misobits[0][2] - self.put(ss, es, self.out_binary, [0, bytes([so])]) + 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] - self.put(ss, es, self.out_binary, [1, bytes([si])]) + 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]) diff --git a/decoders/spiflash/lists.py b/decoders/spiflash/lists.py index 4ed6aaf..ba9f2c2 100644 --- a/decoders/spiflash/lists.py +++ b/decoders/spiflash/lists.py @@ -18,41 +18,63 @@ ## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA ## -# Dict which maps command IDs to their names and descriptions. -cmds = { - 0x06: ('WREN', 'Write enable'), - 0x04: ('WRDI', 'Write disable'), - 0x9f: ('RDID', 'Read identification'), - 0x05: ('RDSR', 'Read status register'), - 0x01: ('WRSR', 'Write status register'), - 0x03: ('READ', 'Read data'), - 0x0b: ('FAST/READ', 'Fast read data'), - 0xbb: ('2READ', '2x I/O read'), - 0x20: ('SE', 'Sector erase'), - 0xd8: ('BE', 'Block erase'), - 0x60: ('CE', 'Chip erase'), - 0xc7: ('CE2', 'Chip erase'), # Alternative command ID - 0x02: ('PP', 'Page program'), - 0xad: ('CP', 'Continuously program mode'), - 0xb9: ('DP', 'Deep power down'), - 0xab: ('RDP/RES', 'Release from deep powerdown / Read electronic ID'), - 0x90: ('REMS', 'Read electronic manufacturer & device ID'), - 0xef: ('REMS2', 'Read ID for 2x I/O mode'), - 0xb1: ('ENSO', 'Enter secured OTP'), - 0xc1: ('EXSO', 'Exit secured OTP'), - 0x2b: ('RDSCUR', 'Read security register'), - 0x2f: ('WRSCUR', 'Write security register'), - 0x70: ('ESRY', 'Enable SO to output RY/BY#'), - 0x80: ('DSRY', 'Disable SO to output RY/BY#'), -} +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#')), + (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 + (0xd8, ('BE', 'Block erase')), + (0xef, ('REMS2', 'Read ID for 2x I/O mode')), +]) device_name = { - 0x14: 'MX25L1605D', - 0x15: 'MX25L3205D', - 0x16: 'MX25L6405D', + 'fidelix': { + 0x15: 'FM25Q32', + }, + 'macronix': { + 0x14: 'MX25L1605D', + 0x15: 'MX25L3205D', + 0x16: 'MX25L6405D', + }, } chips = { + # 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', diff --git a/decoders/spiflash/pd.py b/decoders/spiflash/pd.py index 0dfa035..bc5f9ca 100644 --- a/decoders/spiflash/pd.py +++ b/decoders/spiflash/pd.py @@ -1,7 +1,7 @@ ## ## This file is part of the libsigrokdecode project. ## -## Copyright (C) 2011-2015 Uwe Hermann +## 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 @@ -21,9 +21,31 @@ 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, REMS, RDID, RDP_RES, CP, ENSO, DP, READ2X, \ + EXSO, CE2, 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. @@ -59,21 +81,40 @@ class Decoder(srd.Decoder): inputs = ['spi'] outputs = ['spiflash'] annotations = cmd_annotation_classes() + ( - ('bits', 'Bits'), - ('bits2', 'Bits2'), - ('warnings', 'Warnings'), + ('bit', 'Bit'), + ('field', 'Field'), + ('warning', 'Warning'), ) annotation_rows = ( - ('bits', 'Bits', (24, 25)), - ('commands', 'Commands', tuple(range(23 + 1))), - ('warnings', 'Warnings', (26,)), + ('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, **kwargs): + def __init__(self): + self.device_id = -1 + self.on_end_transaction = None + self.end_current_transaction() + + # 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 @@ -82,43 +123,78 @@ class Decoder(srd.Decoder): 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 putb(self, data): - self.put(self.block_ss, self.block_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([0, ['Command: %s' % cmds[self.state][1]]]) + self.putx([Ann.WREN, self.cmd_ann_list()]) self.state = None def handle_wrdi(self, mosi, miso): pass # TODO - # TODO: Check/display device ID / name def handle_rdid(self, mosi, miso): if self.cmdstate == 1: # Byte 1: Master sends command ID. - self.ss_block = self.ss - self.putx([2, ['Command: %s' % cmds[self.state][1]]]) + self.emit_cmd_byte() elif self.cmdstate == 2: # Byte 2: Slave sends the JEDEC manufacturer ID. - self.putx([2, ['Manufacturer ID: 0x%02x' % miso]]) + self.putx([Ann.FIELD, ['Manufacturer ID: 0x%02x' % miso]]) elif self.cmdstate == 3: - # Byte 3: Slave sends the memory type (0x20 for this chip). - self.putx([2, ['Memory type: 0x%02x' % miso]]) + # 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([2, ['Device ID: 0x%02x' % miso]]) + self.putx([Ann.FIELD, ['Device ID: 0x%02x' % miso]]) if self.cmdstate == 4: - # TODO: Check self.device_id is valid & exists in device_names. - # TODO: Same device ID? Check! - d = 'Device: Macronix %s' % device_name[self.device_id] - self.put(self.ss_block, self.es, self.out_ann, [0, [d]]) + self.es_cmd = self.es + self.putc([Ann.RDID, self.cmd_vendor_dev_list()]) self.state = None else: self.cmdstate += 1 @@ -130,52 +206,69 @@ class Decoder(srd.Decoder): # When done, the master de-asserts CS# again. if self.cmdstate == 1: # Byte 1: Master sends command ID. - self.putx([3, ['Command: %s' % cmds[self.state][1]]]) + self.emit_cmd_byte() elif self.cmdstate >= 2: # Bytes 2-x: Slave sends status register as long as master clocks. - if self.cmdstate <= 3: # TODO: While CS# asserted. - self.putx([24, ['Status register: 0x%02x' % miso]]) - self.putx([25, [decode_status_reg(miso)]]) - - if self.cmdstate == 3: # TODO: If CS# got de-asserted. - self.state = None - return + 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()]) + 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): - pass # TODO + # 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(miso)]]) + self.putx([Ann.FIELD, ['Status register 1']]) + elif self.cmdstate == 3: + # Byte 3: Master sends status register 2. + # TODO: Decode status register 2 correctly. + self.putx([Ann.BIT, [decode_status_reg(miso)]]) + 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.putx([5, ['Command: %s' % cmds[self.state][1]]]) + self.emit_cmd_byte() elif self.cmdstate in (2, 3, 4): # Bytes 2/3/4: Master sends read address (24bits, MSB-first). - self.addr |= (mosi << ((4 - self.cmdstate) * 8)) - # self.putx([0, ['Read address, byte %d: 0x%02x' % \ - # (4 - self.cmdstate, mosi)]]) - if self.cmdstate == 4: - self.putx([24, ['Read address: 0x%06x' % self.addr]]) - self.addr = 0 + self.emit_addr_bytes(mosi) elif self.cmdstate >= 5: # Bytes 5-x: Master reads data bytes (until CS# de-asserted). - # TODO: For now we hardcode 256 bytes per READ command. - if self.cmdstate <= 256 + 4: # TODO: While CS# asserted. - self.data.append(miso) - # self.putx([0, ['New read byte: 0x%02x' % miso]]) - - if self.cmdstate == 256 + 4: # TODO: If CS# got de-asserted. - # s = ', '.join(map(hex, self.data)) - s = ''.join(map(chr, self.data)) - self.putx([24, ['Read data']]) - self.putx([25, ['Read data: %s' % s]]) - self.data = [] - self.state = None - return - + 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_fast_read(self, mosi, miso): @@ -183,60 +276,70 @@ class Decoder(srd.Decoder): # 3-byte address + 1 dummy byte, reads >= 1 data bytes, de-asserts CS#. if self.cmdstate == 1: # Byte 1: Master sends command ID. - self.putx([5, ['Command: %s' % cmds[self.state][1]]]) + self.emit_cmd_byte() elif self.cmdstate in (2, 3, 4): # Bytes 2/3/4: Master sends read address (24bits, MSB-first). - self.putx([24, ['AD%d: 0x%02x' % (self.cmdstate - 1, mosi)]]) - if self.cmdstate == 2: - self.block_ss = self.ss - self.addr |= (mosi << ((4 - self.cmdstate) * 8)) + self.emit_addr_bytes(mosi) elif self.cmdstate == 5: - self.putx([24, ['Dummy byte: 0x%02x' % mosi]]) - self.block_es = self.es - self.putb([5, ['Read address: 0x%06x' % self.addr]]) - self.addr = 0 + self.putx([Ann.BIT, ['Dummy byte: 0x%02x' % mosi]]) elif self.cmdstate >= 6: # Bytes 6-x: Master reads data bytes (until CS# de-asserted). - # TODO: For now we hardcode 32 bytes per FAST READ command. + self.es_field = self.es # Will be overwritten for each byte. if self.cmdstate == 6: - self.block_ss = self.ss - if self.cmdstate <= 32 + 5: # TODO: While CS# asserted. - self.data.append(miso) - if self.cmdstate == 32 + 5: # TODO: If CS# got de-asserted. - self.block_es = self.es - s = ' '.join([hex(b)[2:] for b in self.data]) - self.putb([25, ['Read data: %s' % s]]) - self.data = [] - self.state = None - return - + 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): - pass # TODO + # 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 # TODO: Warn/abort if we don't see the necessary amount of bytes. # TODO: Warn if WREN was not seen before. def handle_se(self, mosi, miso): if self.cmdstate == 1: # Byte 1: Master sends command ID. - self.addr = 0 - self.ss_block = self.ss - self.putx([8, ['Command: %s' % cmds[self.state][1]]]) + self.emit_cmd_byte() elif self.cmdstate in (2, 3, 4): # Bytes 2/3/4: Master sends sector address (24bits, MSB-first). - self.addr |= (mosi << ((4 - self.cmdstate) * 8)) - # self.putx([0, ['Sector address, byte %d: 0x%02x' % \ - # (4 - self.cmdstate, mosi)]]) + 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.put(self.ss_block, self.es, self.out_ann, [24, [d]]) + 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). - d = 'Warning: Invalid sector address!' - self.put(self.ss_block, self.es, self.out_ann, [101, [d]]) + self.putc([Ann.WARN, ['Warning: Invalid sector address!']]) self.state = None else: self.cmdstate += 1 @@ -255,31 +358,17 @@ class Decoder(srd.Decoder): # page address, sends >= 1 data bytes, de-asserts CS#. if self.cmdstate == 1: # Byte 1: Master sends command ID. - self.putx([12, ['Command: %s' % cmds[self.state][1]]]) + self.emit_cmd_byte() elif self.cmdstate in (2, 3, 4): # Bytes 2/3/4: Master sends page address (24bits, MSB-first). - self.addr |= (mosi << ((4 - self.cmdstate) * 8)) - # self.putx([0, ['Page address, byte %d: 0x%02x' % \ - # (4 - self.cmdstate, mosi)]]) - if self.cmdstate == 4: - self.putx([24, ['Page address: 0x%06x' % self.addr]]) - self.addr = 0 + self.emit_addr_bytes(mosi) elif self.cmdstate >= 5: # Bytes 5-x: Master sends data bytes (until CS# de-asserted). - # TODO: For now we hardcode 256 bytes per page / PP command. - if self.cmdstate <= 256 + 4: # TODO: While CS# asserted. - self.data.append(mosi) - # self.putx([0, ['New data byte: 0x%02x' % mosi]]) - - if self.cmdstate == 256 + 4: # TODO: If CS# got de-asserted. - # s = ', '.join(map(hex, self.data)) - s = ''.join(map(chr, self.data)) - self.putx([24, ['Page data']]) - self.putx([25, ['Page data: %s' % s]]) - self.data = [] - self.state = None - return - + 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): @@ -289,38 +378,52 @@ class Decoder(srd.Decoder): pass # TODO def handle_rdp_res(self, mosi, miso): - pass # TODO + 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.ss_block = self.ss - self.putx([16, ['Command: %s' % cmds[self.state][1]]]) + self.emit_cmd_byte() elif self.cmdstate in (2, 3): # Bytes 2/3: Master sends two dummy bytes. - # TODO: Check dummy bytes? Check reply from device? - self.putx([24, ['Dummy byte: %s' % mosi]]) + 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([24, ['Master wants %s ID first' % d]]) + 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([24, ['%s ID' % d]]) + 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 = 'Manufacturer' if self.manufacturer_id_first else 'Device' - self.putx([24, ['%s ID' % d]]) + 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.putx([24, ['Device: Macronix %s' % device_name[id]]]) + 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 @@ -346,34 +449,37 @@ class Decoder(srd.Decoder): def handle_dsry(self, mosi, miso): pass # TODO - def decode(self, ss, es, data): + 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 - # if ptype == 'DATA': - # self.putx([0, ['MOSI: 0x%02x, MISO: 0x%02x' % (mosi, miso)]]) + self.ss, self.es = ss, es - # if ptype == 'CS-CHANGE': - # if mosi == 1 and miso == 0: - # self.putx([0, ['Asserting CS#']]) - # elif mosi == 0 and miso == 1: - # self.putx([0, ['De-asserting CS#']]) + if ptype == 'CS-CHANGE': + self.end_current_transaction() if ptype != 'DATA': return - self.ss, self.es = ss, es - # If we encountered a known chip command, enter the resp. state. if self.state is None: self.state = mosi self.cmdstate = 1 # Handle commands. - if self.state in cmds: - s = 'handle_%s' % cmds[self.state][0].lower().replace('/', '_') - handle_reg = getattr(self, s) - handle_reg(mosi, miso) - else: - self.putx([24, ['Unknown command: 0x%02x' % mosi]]) + 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/decoders/ssi32/__init__.py b/decoders/ssi32/__init__.py new file mode 100644 index 0000000..fb7b4ed --- /dev/null +++ b/decoders/ssi32/__init__.py @@ -0,0 +1,29 @@ +## +## 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, 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 Bosch +SSI32 protocol. +''' + +from .pd import Decoder diff --git a/decoders/ssi32/pd.py b/decoders/ssi32/pd.py new file mode 100644 index 0000000..8c68e0d --- /dev/null +++ b/decoders/ssi32/pd.py @@ -0,0 +1,124 @@ +## +## 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, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + +import sigrokdecode as srd + +class Decoder(srd.Decoder): + api_version = 2 + id = 'ssi32' + name = 'SSI32' + longname = 'Synchronous Serial Interface (32bit)' + desc = 'Synchronous Serial Interface (32bit) protocol.' + license = 'gplv2+' + inputs = ['spi'] + outputs = ['ssi32'] + 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.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(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() + 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() + else: + if len(self.mosi_bytes) < self.options['msgsize']: + return + + self.handle_ctrl() + self.reset() diff --git a/decoders/stepper_motor/pd.py b/decoders/stepper_motor/pd.py index 1527d48..7664fc4 100644 --- a/decoders/stepper_motor/pd.py +++ b/decoders/stepper_motor/pd.py @@ -50,9 +50,9 @@ class Decoder(srd.Decoder): ('position', 'Position', (1,)), ) - def __init__(self, **kwargs): + def __init__(self): self.oldstep = None - self.prev_step_ss = None + self.ss_prev_step = None self.pos = 0 self.prev_speed = None self.prev_pos = None @@ -70,18 +70,18 @@ class Decoder(srd.Decoder): self.unit = 'mm' def step(self, ss, direction): - if self.prev_step_ss is not None: - delta = ss - self.prev_step_ss + if self.ss_prev_step is not None: + delta = ss - self.ss_prev_step speed = self.samplerate / delta / self.scale speed_txt = self.format % speed pos_txt = self.format % (self.pos / self.scale) - self.put(self.prev_step_ss, ss, self.out_ann, + self.put(self.ss_prev_step, ss, self.out_ann, [0, [speed_txt + ' ' + self.unit + '/s', speed_txt]]) - self.put(self.prev_step_ss, ss, self.out_ann, + 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.prev_step_ss = ss + self.ss_prev_step = ss def metadata(self, key, value): if key == srd.SRD_CONF_SAMPLERATE: diff --git a/decoders/swd/pd.py b/decoders/swd/pd.py index d53d149..3414c35 100644 --- a/decoders/swd/pd.py +++ b/decoders/swd/pd.py @@ -92,7 +92,7 @@ class Decoder(srd.Decoder): ('parity', 'PARITY'), ) - def __init__(self, **kwargs): + def __init__(self): # SWD data/clock state self.state = 'UNKNOWN' self.oldclk = -1 diff --git a/decoders/t55xx/__init__.py b/decoders/t55xx/__init__.py new file mode 100644 index 0000000..8f0f8a4 --- /dev/null +++ b/decoders/t55xx/__init__.py @@ -0,0 +1,26 @@ +## +## 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, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + +''' +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/decoders/t55xx/pd.py b/decoders/t55xx/pd.py new file mode 100644 index 0000000..57838f0 --- /dev/null +++ b/decoders/t55xx/pd.py @@ -0,0 +1,331 @@ +## +## 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, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + +import sigrokdecode as srd + +class SamplerateError(Exception): + pass + +class Decoder(srd.Decoder): + api_version = 2 + id = 't55xx' + name = 'T55xx' + longname = 'RFID T55xx' + desc = 'T55xx 100-150kHz RFID protocol.' + license = 'gplv2+' + inputs = ['logic'] + outputs = ['t55xx'] + 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.samplerate = None + self.oldpin = 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, ss, es, data): + if not self.samplerate: + raise SamplerateError('Cannot decode without samplerate.') + for (self.samplenum, (pin,)) in data: + # Ignore identical samples early on (for performance reasons). + if self.oldpin == pin: + continue + + if self.oldpin is None: + 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 + + self.old_gap_start = 0 + self.old_gap_end = 0 + self.gap_detected = 0 + self.bit_nr = 0 + continue + + if self.oldpin != pin: + 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 + self.oldpin = pin diff --git a/decoders/tca6408a/pd.py b/decoders/tca6408a/pd.py index 1f90245..a794547 100644 --- a/decoders/tca6408a/pd.py +++ b/decoders/tca6408a/pd.py @@ -41,7 +41,7 @@ class Decoder(srd.Decoder): ('warnings', 'Warnings', (2,)), ) - def __init__(self, **kwargs): + def __init__(self): self.state = 'IDLE' self.chip = -1 diff --git a/decoders/timing/pd.py b/decoders/timing/pd.py index 6bca120..98677b9 100644 --- a/decoders/timing/pd.py +++ b/decoders/timing/pd.py @@ -20,19 +20,29 @@ ## import sigrokdecode as srd +from collections import deque class SamplerateError(Exception): pass def normalize_time(t): if t >= 1.0: - return '%.3f s' % t + return '%.3f s (%.3f Hz)' % (t, (1/t)) elif t >= 0.001: - return '%.3f ms' % (t * 1000.0) + 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 t >= 0.000001: - return '%.3f μs' % (t * 1000.0 * 1000.0) + 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 t >= 0.000000001: - return '%.3f ns' % (t * 1000.0 * 1000.0 * 1000.0) + 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 @@ -40,7 +50,7 @@ class Decoder(srd.Decoder): api_version = 2 id = 'timing' name = 'Timing' - longname = 'Timing calculation' + longname = 'Timing calculation with frequency and averaging' desc = 'Calculate time between edges.' license = 'gplv2+' inputs = ['logic'] @@ -50,15 +60,22 @@ class Decoder(srd.Decoder): ) annotations = ( ('time', 'Time'), + ('average', 'Average'), ) annotation_rows = ( ('time', 'Time', (0,)), + ('average', 'Average', (1,)), + ) + options = ( + { 'id': 'avg_period', 'desc': 'Averaging period', 'default': 100 }, ) - def __init__(self, **kwargs): + def __init__(self): self.samplerate = None self.oldpin = None self.last_samplenum = None + self.last_n = deque() + self.chunks = 0 def metadata(self, key, value): if key == srd.SRD_CONF_SAMPLERATE: @@ -71,24 +88,36 @@ class Decoder(srd.Decoder): if not self.samplerate: raise SamplerateError('Cannot decode without samplerate.') - for (samplenum, (pin,)) in data: - # Ignore identical samples early on (for performance reasons). - if self.oldpin == pin: - continue - + for (self.samplenum, (pin,)) in data: if self.oldpin is None: self.oldpin = pin - self.last_samplenum = samplenum + self.last_samplenum = self.samplenum continue if self.oldpin != pin: - samples = samplenum - self.last_samplenum + samples = self.samplenum - self.last_samplenum t = samples / self.samplerate + self.chunks += 1 + + # Don't insert the first chunk into the averaging as it is + # not complete probably. + if self.last_samplenum is None or self.chunks < 2: + # Report the timing normalized. + self.put(self.last_samplenum, self.samplenum, self.out_ann, + [0, [normalize_time(t)]]) + else: + if t > 0: + self.last_n.append(t) + + if len(self.last_n) > self.options['avg_period']: + self.last_n.popleft() - # Report the timing normalized. - self.put(self.last_samplenum, samplenum, self.out_ann, - [0, [normalize_time(t)]]) + # Report the timing normalized. + self.put(self.last_samplenum, self.samplenum, self.out_ann, + [0, [normalize_time(t)]]) + self.put(self.last_samplenum, self.samplenum, self.out_ann, + [1, [normalize_time(sum(self.last_n) / len(self.last_n))]]) # Store data for next round. - self.last_samplenum = samplenum + self.last_samplenum = self.samplenum self.oldpin = pin diff --git a/decoders/tlc5620/pd.py b/decoders/tlc5620/pd.py index df42bfb..317e4ed 100644 --- a/decoders/tlc5620/pd.py +++ b/decoders/tlc5620/pd.py @@ -71,7 +71,7 @@ class Decoder(srd.Decoder): ('errors', 'Errors', (9,)), ) - def __init__(self, **kwargs): + def __init__(self): self.oldpins = self.oldclk = self.oldload = self.oldldac = None self.bits = [] self.ss_dac_first = None diff --git a/decoders/uart/pd.py b/decoders/uart/pd.py index db1065d..0fa0e7f 100644 --- a/decoders/uart/pd.py +++ b/decoders/uart/pd.py @@ -31,7 +31,7 @@ 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 512 (as the data can be up to 9 bits in size). + 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). @@ -102,7 +102,7 @@ class Decoder(srd.Decoder): 'values': (0.0, 0.5, 1.0, 1.5)}, {'id': 'bit_order', 'desc': 'Bit order', 'default': 'lsb-first', 'values': ('lsb-first', 'msb-first')}, - {'id': 'format', 'desc': 'Data format', 'default': 'ascii', + {'id': 'format', 'desc': 'Data format', 'default': 'hex', 'values': ('ascii', 'dec', 'hex', 'oct', 'bin')}, {'id': 'invert_rx', 'desc': 'Invert RX?', 'default': 'no', 'values': ('yes', 'no')}, @@ -160,13 +160,13 @@ class Decoder(srd.Decoder): s, halfbit = self.startsample[rxtx], self.bit_width / 2.0 self.put(s - floor(halfbit), self.samplenum + ceil(halfbit), self.out_binary, data) - def __init__(self, **kwargs): + def __init__(self): self.samplerate = None self.samplenum = 0 self.frame_start = [-1, -1] self.startbit = [-1, -1] self.cur_data_bit = [0, 0] - self.databyte = [0, 0] + self.datavalue = [0, 0] self.paritybit = [-1, -1] self.stopbit1 = [-1, -1] self.startsample = [-1, -1] @@ -179,6 +179,7 @@ class Decoder(srd.Decoder): 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: @@ -223,14 +224,16 @@ class Decoder(srd.Decoder): self.startbit[rxtx] = signal - # The startbit must be 0. If not, we report an error. + # 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[rxtx] != 0: self.putp(['INVALID STARTBIT', rxtx, self.startbit[rxtx]]) self.putg([rxtx + 10, ['Frame error', 'Frame err', 'FE']]) - # TODO: Abort? Ignore rest of the frame? + self.state[rxtx] = 'WAIT FOR START BIT' + return self.cur_data_bit[rxtx] = 0 - self.databyte[rxtx] = 0 + self.datavalue[rxtx] = 0 self.startsample[rxtx] = -1 self.state[rxtx] = 'GET DATA BITS' @@ -249,12 +252,12 @@ class Decoder(srd.Decoder): # Get the next data bit in LSB-first or MSB-first fashion. if self.options['bit_order'] == 'lsb-first': - self.databyte[rxtx] >>= 1 - self.databyte[rxtx] |= \ + self.datavalue[rxtx] >>= 1 + self.datavalue[rxtx] |= \ (signal << (self.options['num_data_bits'] - 1)) else: - self.databyte[rxtx] <<= 1 - self.databyte[rxtx] |= (signal << 0) + self.datavalue[rxtx] <<= 1 + self.datavalue[rxtx] |= (signal << 0) self.putg([rxtx + 12, ['%d' % signal]]) @@ -270,25 +273,60 @@ class Decoder(srd.Decoder): self.state[rxtx] = 'GET PARITY BIT' self.putpx(rxtx, ['DATA', rxtx, - (self.databyte[rxtx], self.databits[rxtx])]) - - b, f = self.databyte[rxtx], self.options['format'] - if f == 'ascii': - c = chr(b) if b in range(30, 126 + 1) else '[%02X]' % b - self.putx(rxtx, [rxtx, [c]]) - elif f == 'dec': - self.putx(rxtx, [rxtx, [str(b)]]) - elif f == 'hex': - self.putx(rxtx, [rxtx, [hex(b)[2:].zfill(2).upper()]]) - elif f == 'oct': - self.putx(rxtx, [rxtx, [oct(b)[2:].zfill(3)]]) - elif f == 'bin': - self.putx(rxtx, [rxtx, [bin(b)[2:].zfill(8)]]) - - self.putbin(rxtx, [rxtx, bytes([b])]) - self.putbin(rxtx, [2, bytes([b])]) + (self.datavalue[rxtx], self.databits[rxtx])]) + + b = self.datavalue[rxtx] + formatted = self.format_value(b) + if formatted is not None: + self.putx(rxtx, [rxtx, [formatted]]) + + bdata = b.to_bytes(self.bw, byteorder='big') + self.putbin(rxtx, [rxtx, bdata]) + self.putbin(rxtx, [2, bdata]) + + self.databits[rxtx] = [] + + 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) - self.databits = [[], []] + return None def get_parity_bit(self, rxtx, signal): # If no parity is used/configured, skip to the next state immediately. @@ -305,7 +343,7 @@ class Decoder(srd.Decoder): self.state[rxtx] = 'GET STOP BITS' if parity_ok(self.options['parity_type'], self.paritybit[rxtx], - self.databyte[rxtx], self.options['num_data_bits']): + self.datavalue[rxtx], self.options['num_data_bits']): self.putp(['PARITYBIT', rxtx, self.paritybit[rxtx]]) self.putg([rxtx + 4, ['Parity bit', 'Parity', 'P']]) else: diff --git a/decoders/usb_power_delivery/__init__.py b/decoders/usb_power_delivery/__init__.py index 669fb48..4ba626a 100644 --- a/decoders/usb_power_delivery/__init__.py +++ b/decoders/usb_power_delivery/__init__.py @@ -18,8 +18,8 @@ ## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA ## -""" -USB Power Delivery - baseband protocol decoder / checker -""" +''' +USB Power Delivery - baseband protocol decoder / checker. +''' from .pd import * diff --git a/decoders/usb_power_delivery/pd.py b/decoders/usb_power_delivery/pd.py index ad28d97..48db41b 100644 --- a/decoders/usb_power_delivery/pd.py +++ b/decoders/usb_power_delivery/pd.py @@ -439,7 +439,7 @@ class Decoder(srd.Decoder): self.putwarn('No start of packet found', 'XXX') return -1 # No Start Of Packet - def __init__(self, **kwargs): + def __init__(self): self.samplerate = None self.idx = 0 self.packet_seq = 0 @@ -471,8 +471,6 @@ class Decoder(srd.Decoder): ) def us2samples(self, us): - if not self.samplerate: - raise SamplerateError('Need the samplerate.') return int(us * self.samplerate / 1000000) def decode_packet(self): diff --git a/decoders/usb_request/pd.py b/decoders/usb_request/pd.py index 79539c6..bb7aedc 100644 --- a/decoders/usb_request/pd.py +++ b/decoders/usb_request/pd.py @@ -137,13 +137,12 @@ class Decoder(srd.Decoder): ) def __init__(self): - self.samplerate = 8e6 # None - self.secs_per_sample = float(1) / float(self.samplerate) + self.samplerate = None self.request = {} self.request_id = 0 self.transaction_state = 'IDLE' - self.transaction_ss = None - self.transaction_es = None + self.ss_transaction = None + self.es_transaction = None self.transaction_ep = None self.transaction_addr = None self.wrote_pcap_header = False @@ -183,7 +182,7 @@ class Decoder(srd.Decoder): addr = self.transaction_addr if not (addr, ep) in self.request: self.request[(addr, ep)] = {'setup_data': [], 'data': [], - 'type': None, 'ss': self.transaction_ss, 'es': None, + 'type': None, 'ss': self.ss_transaction, 'es': None, 'id': self.request_id, 'addr': addr, 'ep': ep} self.request_id += 1 request_started = 1 @@ -193,16 +192,16 @@ class Decoder(srd.Decoder): if request['type'] in (None, 'BULK IN') and self.transaction_type == 'IN': request['type'] = 'BULK IN' request['data'] += self.transaction_data - request['es'] = self.transaction_es + request['es'] = self.es_transaction 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 - request['es'] = self.transaction_es + request['es'] = self.es_transaction self.handle_request(request_started, request_end) # CONTROL, SETUP stage - elif request['type'] == None and self.transaction_type == 'SETUP': + elif request['type'] is None and self.transaction_type == 'SETUP': request['setup_data'] = self.transaction_data request['wLength'] = struct.unpack(' transaction_timeout: - self.transaction_es = transaction_timeout + self.es_transaction = transaction_timeout self.handshake = 'timeout' self.handle_transfer() self.transaction_state = 'IDLE' @@ -324,8 +323,8 @@ class Decoder(srd.Decoder): sync, pid, addr, ep, crc5 = pinfo self.transaction_data = [] - self.transaction_ss = ss - self.transaction_es = es + self.ss_transaction = ss + self.es_transaction = es self.transaction_state = 'TOKEN RECEIVED' self.transaction_ep = ep self.transaction_addr = addr @@ -348,7 +347,7 @@ class Decoder(srd.Decoder): self.handshake = pname self.transaction_state = 'IDLE' - self.transaction_es = es + self.es_transaction = es self.handle_transfer() elif pname == 'PRE': diff --git a/decoders/wiegand/__init__.py b/decoders/wiegand/__init__.py new file mode 100644 index 0000000..20f51f8 --- /dev/null +++ b/decoders/wiegand/__init__.py @@ -0,0 +1,29 @@ +## +## 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, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + +''' +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/decoders/wiegand/pd.py b/decoders/wiegand/pd.py new file mode 100644 index 0000000..a42c9d2 --- /dev/null +++ b/decoders/wiegand/pd.py @@ -0,0 +1,134 @@ +## +## 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, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + +import sigrokdecode as srd + +class Decoder(srd.Decoder): + api_version = 2 + id = 'wiegand' + name = 'Wiegand' + longname = 'Wiegand interface' + desc = 'Wiegand interface for electronic entry systems.' + license = 'gplv2+' + inputs = ['logic'] + outputs = ['wiegand'] + 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._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 = self.options['active'] == 'high' and 1 or 0 + self._inactive = 1 - self._active + + def metadata(self, key, value): + 'Receive decoder metadata about the data stream.' + if key == srd.SRD_CONF_SAMPLERATE: + ms_per_sample = 1000 * (1.0 / value) + 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, ss, es, data): + for self.samplenum, (d0, d1) in data: + 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/decoders/xfp/pd.py b/decoders/xfp/pd.py index 41aca0d..27135e5 100644 --- a/decoders/xfp/pd.py +++ b/decoders/xfp/pd.py @@ -18,177 +18,9 @@ ## import sigrokdecode as srd - -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", -] +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 = 2 @@ -204,7 +36,7 @@ class Decoder(srd.Decoder): ('fields', 'XFP structure fields'), ) - def __init__(self, **kwargs): + def __init__(self): # Received data items, used as an index into samplenum/data self.cnt = -1 # Start/end sample numbers per data item diff --git a/decoders/z80/pd.py b/decoders/z80/pd.py index e0d7ae4..a8acf53 100644 --- a/decoders/z80/pd.py +++ b/decoders/z80/pd.py @@ -110,7 +110,7 @@ class Decoder(srd.Decoder): ('warnings', 'Warnings', (Ann.WARN,)) ) - def __init__(self, **kwargs): + def __init__(self): self.prev_cycle = Cycle.NONE self.op_state = self.state_IDLE