From: Uwe Hermann Date: Sun, 12 Jul 2015 17:14:49 +0000 (+0200) Subject: Backport current PDs from git mainline. X-Git-Tag: libsigrokdecode-0.3.1~4 X-Git-Url: https://sigrok.org/gitaction?a=commitdiff_plain;h=c9cab77330c04d2d68aa341185d77485a17659d4;p=libsigrokdecode.git Backport current PDs from git mainline. --- diff --git a/decoders/adns5020/__init__.py b/decoders/adns5020/__init__.py new file mode 100644 index 0000000..a0cc63c --- /dev/null +++ b/decoders/adns5020/__init__.py @@ -0,0 +1,26 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2015 Karl Palsson +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, 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 ADNS-5020 optical mouse +sensor commands and data. Use MOSI for the SDIO shared line. +''' + +from .pd import Decoder diff --git a/decoders/adns5020/pd.py b/decoders/adns5020/pd.py new file mode 100644 index 0000000..bcdb52a --- /dev/null +++ b/decoders/adns5020/pd.py @@ -0,0 +1,113 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2015 Karl Palsson +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + +import sigrokdecode as srd + +regs = { + 0: 'Product_ID', + 1: 'Revision_ID', + 2: 'Motion', + 3: 'Delta_X', + 4: 'Delta_Y', + 5: 'SQUAL', + 6: 'Shutter_Upper', + 7: 'Shutter_Lower', + 8: 'Maximum_Pixel', + 9: 'Pixel_Sum', + 0xa: 'Minimum_Pixel', + 0xb: 'Pixel_Grab', + 0xd: 'Mouse_Control', + 0x3a: 'Chip_Reset', + 0x3f: 'Inv_Rev_ID', + 0x63: 'Motion_Burst', +} + +class Decoder(srd.Decoder): + api_version = 2 + id = 'adns5020' + name = 'ADNS-5020' + longname = 'Avago ADNS-5020 optical mouse sensor' + desc = 'Bidirectional command and data over an SPI-like protocol.' + license = 'gplv2' + inputs = ['spi'] + outputs = ['adns5020'] + annotations = ( + ('read', 'Register read commands'), + ('write', 'Register write commands'), + ('warning', 'Warnings'), + ) + annotation_rows = ( + ('read', 'Read', (0,)), + ('write', 'Write', (1,)), + ('warnings', 'Warnings', (2,)), + ) + + def __init__(self, **kwargs): + self.ss_cmd, self.es_cmd = 0, 0 + self.mosi_bytes = [] + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def putx(self, data): + self.put(self.ss_cmd, self.es_cmd, self.out_ann, data) + + def put_warn(self, pos, msg): + self.put(pos[0], pos[1], self.out_ann, [2, [msg]]) + + def decode(self, ss, es, data): + ptype = data[0] + if ptype == 'CS-CHANGE': + # If we transition high mid-stream, toss out our data and restart. + cs_old, cs_new = data[1:] + if cs_old is not None and cs_old == 0 and cs_new == 1: + if len(self.mosi_bytes) not in [0, 2]: + self.put_warn([self.ss_cmd, es], 'Misplaced CS#!') + self.mosi_bytes = [] + return + + # Don't care about anything else. + if ptype != 'DATA': + return + mosi, miso = data[1:] + + self.ss, self.es = ss, es + + if len(self.mosi_bytes) == 0: + self.ss_cmd = ss + self.mosi_bytes.append(mosi) + + # Writes/reads are mostly two transfers (burst mode is different). + if len(self.mosi_bytes) != 2: + return + + self.es_cmd = es + cmd, arg = self.mosi_bytes + write = cmd & 0x80 + reg = cmd & 0x7f + reg_desc = regs.get(reg, 'Reserved %#x' % reg) + if reg > 0x63: + reg_desc = 'Unknown' + if write: + self.putx([1, ['%s: %#x' % (reg_desc, arg)]]) + else: + self.putx([0, ['%s: %d' % (reg_desc, arg)]]) + + self.mosi_bytes = [] diff --git a/decoders/am230x/__init__.py b/decoders/am230x/__init__.py new file mode 100644 index 0000000..51b8488 --- /dev/null +++ b/decoders/am230x/__init__.py @@ -0,0 +1,37 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 Johannes Roemer +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + +''' +This decoder handles the proprietary single wire communication protocol used +by the Aosong AM230x/DHTxx/RHTxx series of digital humidity and temperature +sensors. + +Sample rate: +A sample rate of at least 200kHz is recommended to properly detect all the +elements of the protocol. + +Options: +The AM230x and DHTxx/RHTxx digital humidity and temperature sensors use the +same single-wire protocol with different encoding of the measured values. +Therefore the option 'device' must be used to properly decode the +communication of the respective sensor. +''' + +from .pd import Decoder diff --git a/decoders/am230x/pd.py b/decoders/am230x/pd.py new file mode 100644 index 0000000..2b54cde --- /dev/null +++ b/decoders/am230x/pd.py @@ -0,0 +1,235 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 Johannes Roemer +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + +import sigrokdecode as srd + +# Define valid timing values (in microseconds). +timing = { + 'START LOW' : {'min': 750, 'max': 25000}, + 'START HIGH' : {'min': 10, 'max': 10000}, + 'RESPONSE LOW' : {'min': 50, 'max': 90}, + 'RESPONSE HIGH' : {'min': 50, 'max': 90}, + 'BIT LOW' : {'min': 45, 'max': 90}, + 'BIT 0 HIGH' : {'min': 20, 'max': 35}, + 'BIT 1 HIGH' : {'min': 65, 'max': 80}, +} + +class SamplerateError(Exception): + pass + +class Decoder(srd.Decoder): + api_version = 2 + id = 'am230x' + name = 'AM230x/DHTxx/RHTxx' + longname = 'Aosong AM230x/DHTxx/RHTxx' + desc = 'Aosong AM230x/DHTxx/RHTxx humidity/temperature sensor protocol.' + license = 'gplv2+' + inputs = ['logic'] + outputs = ['am230x'] + channels = ( + {'id': 'sda', 'name': 'SDA', 'desc': 'Single wire serial data line'}, + ) + options = ( + {'id': 'device', 'desc': 'Device type', + 'default': 'am230x', 'values': ('am230x/rht', 'dht11')}, + ) + annotations = ( + ('start', 'Start'), + ('response', 'Response'), + ('bit', 'Bit'), + ('end', 'End'), + ('byte', 'Byte'), + ('humidity', 'Relative humidity in percent'), + ('temperature', 'Temperature in degrees Celsius'), + ('checksum', 'Checksum'), + ) + annotation_rows = ( + ('bits', 'Bits', (0, 1, 2, 3)), + ('bytes', 'Bytes', (4,)), + ('results', 'Results', (5, 6, 7)), + ) + + def putfs(self, data): + self.put(self.fall, self.samplenum, self.out_ann, data) + + def putb(self, data): + self.put(self.bytepos[-1], self.samplenum, self.out_ann, data) + + def putv(self, data): + self.put(self.bytepos[-2], self.samplenum, self.out_ann, data) + + def reset(self): + self.state = 'WAIT FOR START LOW' + self.samplenum = 0 + self.fall = 0 + self.rise = 0 + self.bits = [] + self.bytepos = [] + + def is_valid(self, name): + dt = 0 + if name.endswith('LOW'): + dt = self.samplenum - self.fall + elif name.endswith('HIGH'): + dt = self.samplenum - self.rise + if dt >= self.cnt[name]['min'] and dt <= self.cnt[name]['max']: + return True + return False + + def bits2num(self, bitlist): + number = 0 + for i in range(len(bitlist)): + number += bitlist[-1 - i] * 2**i + return number + + def calculate_humidity(self, bitlist): + h = 0 + if self.options['device'] == 'dht11': + h = self.bits2num(bitlist[0:8]) + else: + h = self.bits2num(bitlist) / 10 + return h + + def calculate_temperature(self, bitlist): + t = 0 + if self.options['device'] == 'dht11': + t = self.bits2num(bitlist[0:8]) + else: + t = self.bits2num(bitlist[1:]) / 10 + if bitlist[0] == 1: + t = -t + return t + + def calculate_checksum(self, bitlist): + checksum = 0 + for i in range(8, len(bitlist) + 1, 8): + checksum += self.bits2num(bitlist[i-8:i]) + return checksum % 256 + + def __init__(self, **kwargs): + self.samplerate = None + self.reset() + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def metadata(self, key, value): + if key != srd.SRD_CONF_SAMPLERATE: + return + self.samplerate = value + # Convert microseconds to sample counts. + self.cnt = {} + for e in timing: + self.cnt[e] = {} + for t in timing[e]: + self.cnt[e][t] = timing[e][t] * self.samplerate / 1000000 + + def handle_byte(self, bit): + self.bits.append(bit) + self.putfs([2, ['Bit: %d' % bit, '%d' % bit]]) + self.fall = self.samplenum + self.state = 'WAIT FOR BIT HIGH' + if len(self.bits) % 8 == 0: + byte = self.bits2num(self.bits[-8:]) + self.putb([4, ['Byte: %#04x' % byte, '%#04x' % byte]]) + if len(self.bits) == 16: + h = self.calculate_humidity(self.bits[-16:]) + self.putv([5, ['Humidity: %.1f %%' % h, 'RH = %.1f %%' % h]]) + elif len(self.bits) == 32: + t = self.calculate_temperature(self.bits[-16:]) + self.putv([6, ['Temperature: %.1f °C' % t, 'T = %.1f °C' % t]]) + elif len(self.bits) == 40: + parity = self.bits2num(self.bits[-8:]) + if parity == self.calculate_checksum(self.bits[0:32]): + self.putb([7, ['Checksum: OK', 'OK']]) + else: + self.putb([7, ['Checksum: not OK', 'NOK']]) + self.state = 'WAIT FOR END' + self.bytepos.append(self.samplenum) + + def decode(self, ss, es, data): + if not self.samplerate: + raise SamplerateError('Cannot decode without samplerate.') + for (self.samplenum, (sda,)) in data: + # State machine. + if self.state == 'WAIT FOR START LOW': + if sda != 0: + continue + self.fall = self.samplenum + self.state = 'WAIT FOR START HIGH' + elif self.state == 'WAIT FOR START HIGH': + if sda != 1: + continue + if self.is_valid('START LOW'): + self.rise = self.samplenum + self.state = 'WAIT FOR RESPONSE LOW' + else: + self.reset() + elif self.state == 'WAIT FOR RESPONSE LOW': + if sda != 0: + continue + if self.is_valid('START HIGH'): + self.putfs([0, ['Start', 'S']]) + self.fall = self.samplenum + self.state = 'WAIT FOR RESPONSE HIGH' + else: + self.reset() + elif self.state == 'WAIT FOR RESPONSE HIGH': + if sda != 1: + continue + if self.is_valid('RESPONSE LOW'): + self.rise = self.samplenum + self.state = 'WAIT FOR FIRST BIT' + else: + self.reset() + elif self.state == 'WAIT FOR FIRST BIT': + if sda != 0: + continue + if self.is_valid('RESPONSE HIGH'): + self.putfs([1, ['Response', 'R']]) + self.fall = self.samplenum + self.bytepos.append(self.samplenum) + self.state = 'WAIT FOR BIT HIGH' + else: + self.reset() + elif self.state == 'WAIT FOR BIT HIGH': + if sda != 1: + continue + if self.is_valid('BIT LOW'): + self.rise = self.samplenum + self.state = 'WAIT FOR BIT LOW' + else: + self.reset() + elif self.state == 'WAIT FOR BIT LOW': + if sda != 0: + continue + if self.is_valid('BIT 0 HIGH'): + bit = 0 + elif self.is_valid('BIT 1 HIGH'): + bit = 1 + else: + self.reset() + continue + self.handle_byte(bit) + elif self.state == 'WAIT FOR END': + if sda != 1: + continue + self.putfs([3, ['End', 'E']]) + self.reset() diff --git a/decoders/arm_etmv3/__init__.py b/decoders/arm_etmv3/__init__.py new file mode 100644 index 0000000..7955139 --- /dev/null +++ b/decoders/arm_etmv3/__init__.py @@ -0,0 +1,26 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2015 Petteri Aimonen +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + +''' +This decoder stacks on top of the 'uart' decoder and decodes packets of +the ARMv7m Embedded Trace Macroblock v3.x. +''' + +from .pd import Decoder diff --git a/decoders/arm_etmv3/pd.py b/decoders/arm_etmv3/pd.py new file mode 100644 index 0000000..0083fdb --- /dev/null +++ b/decoders/arm_etmv3/pd.py @@ -0,0 +1,564 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2015 Petteri Aimonen +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + +import sigrokdecode as srd +import subprocess +import re + +# See ETMv3 Signal Protocol table 7-11: 'Encoding of Exception[8:0]'. +exc_names = [ + 'No exception', 'IRQ1', 'IRQ2', 'IRQ3', 'IRQ4', 'IRQ5', 'IRQ6', 'IRQ7', + 'IRQ0', 'UsageFault', 'NMI', 'SVC', 'DebugMon', 'MemManage', 'PendSV', + 'SysTick', 'Reserved', 'Reset', 'BusFault', 'Reserved', 'Reserved' +] + +for i in range(8, 496): + exc_names.append('IRQ%d' % i) + +def parse_varint(bytes): + '''Parse an integer where the top bit is the continuation bit. + Returns value and number of parsed bytes.''' + v = 0 + for i, b in enumerate(bytes): + v |= (b & 0x7F) << (i * 7) + if b & 0x80 == 0: + return v, i+1 + return v, len(bytes) + +def parse_uint(bytes): + '''Parse little-endian integer.''' + v = 0 + for i, b in enumerate(bytes): + v |= b << (i * 8) + return v + +def parse_exc_info(bytes): + '''Parse exception information bytes from a branch packet.''' + if len(bytes) < 1: + return None + + excv, exclen = parse_varint(bytes) + if bytes[exclen - 1] & 0x80 != 0x00: + return None # Exception info not complete. + + if exclen == 2 and excv & (1 << 13): + # Exception byte 1 was skipped, fix up the decoding. + excv = (excv & 0x7F) | ((excv & 0x3F80) << 7) + + ns = excv & 1 + exc = ((excv >> 1) & 0x0F) | ((excv >> 7) & 0x1F0) + cancel = (excv >> 5) & 1 + altisa = (excv >> 6) & 1 + hyp = (excv >> 12) & 1 + resume = (excv >> 14) & 0x0F + return (ns, exc, cancel, altisa, hyp, resume) + +def parse_branch_addr(bytes, ref_addr, cpu_state, branch_enc): + '''Parse encoded branch address. + Returns addr, addrlen, cpu_state, exc_info. + Returns None if packet is not yet complete''' + + addr, addrlen = parse_varint(bytes) + + if bytes[addrlen-1] & 0x80 != 0x00: + return None # Branch address not complete. + + addr_bits = 7 * addrlen + + have_exc_info = False + if branch_enc == 'original': + if addrlen == 5 and bytes[4] & 0x40: + have_exc_info = True + elif branch_enc == 'alternative': + addr_bits -= 1 # Top bit of address indicates exc_info. + if addrlen >= 2 and addr & (1 << addr_bits): + have_exc_info = True + addr &= ~(1 << addr_bits) + + exc_info = None + if have_exc_info: + exc_info = parse_exc_info(bytes[addrlen:]) + if exc_info is None: + return None # Exception info not complete. + + if addrlen == 5: + # Possible change in CPU state. + if bytes[4] & 0xB8 == 0x08: + cpu_state = 'arm' + elif bytes[4] & 0xB0 == 0x10: + cpu_state = 'thumb' + elif bytes[4] & 0xA0 == 0x20: + cpu_state = 'jazelle' + else: + raise NotImplementedError('Unhandled branch byte 4: 0x%02x' % bytes[4]) + + # Shift the address according to current CPU state. + if cpu_state == 'arm': + addr = (addr & 0xFFFFFFFE) << 1 + addr_bits += 1 + elif cpu_state == 'thumb': + addr = addr & 0xFFFFFFFE + elif cpu_state == 'jazelle': + addr = (addr & 0xFFFFFFFFE) >> 1 + addr_bits -= 1 + else: + raise NotImplementedError('Unhandled state: ' + cpu_state) + + # If the address wasn't full, fill in with the previous address. + if addrlen < 5: + addr |= ref_addr & (0xFFFFFFFF << addr_bits) + + return addr, addrlen, cpu_state, exc_info + +class Decoder(srd.Decoder): + api_version = 2 + id = 'arm_etmv3' + name = 'ARM ETMv3' + longname = 'ARM Embedded Trace Macroblock' + desc = 'Decode ETM instruction trace packets.' + license = 'gplv2+' + inputs = ['uart'] + outputs = ['arm_etmv3'] + annotations = ( + ('trace', 'Trace info'), + ('branch', 'Branches'), + ('exception', 'Exceptions'), + ('execution', 'Instruction execution'), + ('data', 'Data access'), + ('pc', 'Program counter'), + ('instr_e', 'Executed instructions'), + ('instr_n', 'Not executed instructions'), + ('source', 'Source code'), + ('location', 'Current location'), + ('function', 'Current function'), + ) + annotation_rows = ( + ('trace', 'Trace info', (0,)), + ('flow', 'Code flow', (1, 2, 3,)), + ('data', 'Data access', (4,)), + ('pc', 'Program counter', (5,)), + ('instruction', 'Instructions', (6, 7,)), + ('source', 'Source code', (8,)), + ('location', 'Current location', (9,)), + ('function', 'Current function', (10,)), + ) + options = ( + {'id': 'objdump', 'desc': 'objdump path', + 'default': 'arm-none-eabi-objdump'}, + {'id': 'objdump_opts', 'desc': 'objdump options', + 'default': '-lSC'}, + {'id': 'elffile', 'desc': '.elf path', + 'default': ''}, + {'id': 'branch_enc', 'desc': 'Branch encoding', + 'default': 'alternative', 'values': ('alternative', 'original')}, + ) + + def __init__(self, **kwargs): + self.buf = [] + self.syncbuf = [] + self.prevsample = 0 + self.last_branch = 0 + self.cpu_state = 'arm' + self.current_pc = 0 + self.current_loc = None + self.current_func = None + self.next_instr_lookup = {} + self.file_lookup = {} + self.func_lookup = {} + self.disasm_lookup = {} + self.source_lookup = {} + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + self.load_objdump() + + def load_objdump(self): + '''Parse disassembly obtained from objdump into two tables: + next_instr_lookup: Find the next PC addr from current PC. + disasm_lookup: Find the instruction text from current PC. + source_lookup: Find the source code line from current PC. + ''' + if not (self.options['objdump'] and self.options['elffile']): + return + + opts = [self.options['objdump']] + opts += self.options['objdump_opts'].split() + opts += [self.options['elffile']] + + try: + disasm = subprocess.check_output(opts) + except subprocess.CalledProcessError: + return + + disasm = disasm.decode('utf-8', 'replace') + + instpat = re.compile('\s*([0-9a-fA-F]+):\t+([0-9a-fA-F ]+)\t+([a-zA-Z][^;]+)\s*;?.*') + branchpat = re.compile('(b|bl|b..|bl..|cbnz|cbz)(?:\.[wn])?\s+(?:r[0-9]+,\s*)?([0-9a-fA-F]+)') + filepat = re.compile('[^\s]+[/\\\\]([a-zA-Z0-9._-]+:[0-9]+)(?:\s.*)?') + funcpat = re.compile('[0-9a-fA-F]+\s*<([^>]+)>:.*') + + prev_src = '' + prev_file = '' + prev_func = '' + + for line in disasm.split('\n'): + m = instpat.match(line) + if m: + addr = int(m.group(1), 16) + raw = m.group(2) + disas = m.group(3).strip().replace('\t', ' ') + self.disasm_lookup[addr] = disas + self.source_lookup[addr] = prev_src + self.file_lookup[addr] = prev_file + self.func_lookup[addr] = prev_func + + # Next address in direct sequence. + ilen = len(raw.replace(' ', '')) // 2 + next_n = addr + ilen + + # Next address if branch is taken. + bm = branchpat.match(disas) + if bm: + next_e = int(bm.group(2), 16) + else: + next_e = next_n + + self.next_instr_lookup[addr] = (next_n, next_e) + else: + m = funcpat.match(line) + if m: + prev_func = m.group(1) + prev_src = None + else: + m = filepat.match(line) + if m: + prev_file = m.group(1) + prev_src = None + else: + prev_src = line.strip() + + def flush_current_loc(self): + if self.current_loc is not None: + ss, es, loc, src = self.current_loc + if loc: + self.put(ss, es, self.out_ann, [9, [loc]]) + if src: + self.put(ss, es, self.out_ann, [8, [src]]) + self.current_loc = None + + def flush_current_func(self): + if self.current_func is not None: + ss, es, func = self.current_func + if func: + self.put(ss, es, self.out_ann, [10, [func]]) + self.current_func = None + + def instructions_executed(self, exec_status): + '''Advance program counter based on executed instructions. + Argument is a list of False for not executed and True for executed + instructions. + ''' + + if len(exec_status) == 0: + return + + tdelta = max(1, (self.prevsample - self.startsample) / len(exec_status)) + + for i, exec_status in enumerate(exec_status): + pc = self.current_pc + default_next = pc + 2 if self.cpu_state == 'thumb' else pc + 4 + target_n, target_e = self.next_instr_lookup.get(pc, (default_next, default_next)) + ss = self.startsample + round(tdelta * i) + es = self.startsample + round(tdelta * (i+1)) + + self.put(ss, es, self.out_ann, + [5, ['PC 0x%08x' % pc, '0x%08x' % pc, '%08x' % pc]]) + + new_loc = self.file_lookup.get(pc) + new_src = self.source_lookup.get(pc) + new_dis = self.disasm_lookup.get(pc) + new_func = self.func_lookup.get(pc) + + # Report source line only when it changes. + if self.current_loc is not None: + if new_loc != self.current_loc[2] or new_src != self.current_loc[3]: + self.flush_current_loc() + + if self.current_loc is None: + self.current_loc = [ss, es, new_loc, new_src] + else: + self.current_loc[1] = es + + # Report function name only when it changes. + if self.current_func is not None: + if new_func != self.current_func[2]: + self.flush_current_func() + + if self.current_func is None: + self.current_func = [ss, es, new_func] + else: + self.current_func[1] = es + + # Report instruction every time. + if new_dis: + if exec_status: + a = [6, ['Executed: ' + new_dis, new_dis, new_dis.split()[0]]] + else: + a = [7, ['Not executed: ' + new_dis, new_dis, new_dis.split()[0]]] + self.put(ss, es, self.out_ann, a) + + if exec_status: + self.current_pc = target_e + else: + self.current_pc = target_n + + def get_packet_type(self, byte): + '''Identify packet type based on its first byte. + See ARM IHI0014Q section "ETMv3 Signal Protocol" "Packet Types" + ''' + if byte & 0x01 == 0x01: + return 'branch' + elif byte == 0x00: + return 'a_sync' + elif byte == 0x04: + return 'cyclecount' + elif byte == 0x08: + return 'i_sync' + elif byte == 0x0C: + return 'trigger' + elif byte & 0xF3 in (0x20, 0x40, 0x60): + return 'ooo_data' + elif byte == 0x50: + return 'store_failed' + elif byte == 0x70: + return 'i_sync' + elif byte & 0xDF in (0x54, 0x58, 0x5C): + return 'ooo_place' + elif byte == 0x3C: + return 'vmid' + elif byte & 0xD3 == 0x02: + return 'data' + elif byte & 0xFB == 0x42: + return 'timestamp' + elif byte == 0x62: + return 'data_suppressed' + elif byte == 0x66: + return 'ignore' + elif byte & 0xEF == 0x6A: + return 'value_not_traced' + elif byte == 0x6E: + return 'context_id' + elif byte == 0x76: + return 'exception_exit' + elif byte == 0x7E: + return 'exception_entry' + elif byte & 0x81 == 0x80: + return 'p_header' + else: + return 'unknown' + + def fallback(self, buf): + ptype = self.get_packet_type(buf[0]) + return [0, ['Unhandled ' + ptype + ': ' + ' '.join(['%02x' % b for b in buf])]] + + def handle_a_sync(self, buf): + if buf[-1] == 0x80: + return [0, ['Synchronization']] + + def handle_exception_exit(self, buf): + return [2, ['Exception exit']] + + def handle_exception_entry(self, buf): + return [2, ['Exception entry']] + + def handle_i_sync(self, buf): + contextid_bytes = 0 # This is the default ETM config. + + if len(buf) < 6: + return None # Packet definitely not full yet. + + if buf[0] == 0x08: # No cycle count. + cyclecount = None + idx = 1 + contextid_bytes # Index to info byte. + elif buf[0] == 0x70: # With cycle count. + cyclecount, cyclen = parse_varint(buf[1:6]) + idx = 1 + cyclen + contextid_bytes + + if len(buf) <= idx + 4: + return None + infobyte = buf[idx] + addr = parse_uint(buf[idx+1:idx+5]) + + reasoncode = (infobyte >> 5) & 3 + reason = ('Periodic', 'Tracing enabled', 'After overflow', 'Exit from debug')[reasoncode] + jazelle = (infobyte >> 4) & 1 + nonsec = (infobyte >> 3) & 1 + altisa = (infobyte >> 2) & 1 + hypervisor = (infobyte >> 1) & 1 + thumb = addr & 1 + addr &= 0xFFFFFFFE + + if reasoncode == 0 and self.current_pc != addr: + self.put(self.startsample, self.prevsample, self.out_ann, + [0, ['WARN: Unexpected PC change 0x%08x -> 0x%08x' % \ + (self.current_pc, addr)]]) + elif reasoncode != 0: + # Reset location when the trace has been interrupted. + self.flush_current_loc() + self.flush_current_func() + + self.last_branch = addr + self.current_pc = addr + + if jazelle: + self.cpu_state = 'jazelle' + elif thumb: + self.cpu_state = 'thumb' + else: + self.cpu_state = 'arm' + + cycstr = '' + if cyclecount is not None: + cycstr = ', cyclecount %d' % cyclecount + + if infobyte & 0x80: # LSIP packet + self.put(self.startsample, self.prevsample, self.out_ann, + [0, ['WARN: LSIP I-Sync packet not implemented']]) + + return [0, ['I-Sync: %s, PC 0x%08x, %s state%s' % \ + (reason, addr, self.cpu_state, cycstr), \ + 'I-Sync: %s 0x%08x' % (reason, addr)]] + + def handle_trigger(self, buf): + return [0, ['Trigger event', 'Trigger']] + + def handle_p_header(self, buf): + # Only non cycle-accurate mode supported. + if buf[0] & 0x83 == 0x80: + n = (buf[0] >> 6) & 1 + e = (buf[0] >> 2) & 15 + + self.instructions_executed([1] * e + [0] * n) + + if n: + return [3, ['%d instructions executed, %d skipped due to ' \ + 'condition codes' % (e, n), + '%d ins exec, %d skipped' % (e, n), + '%dE,%dN' % (e, n)]] + else: + return [3, ['%d instructions executed' % e, + '%d ins exec' % e, '%dE' % e]] + elif buf[0] & 0xF3 == 0x82: + i1 = (buf[0] >> 3) & 1 + i2 = (buf[0] >> 2) & 1 + self.instructions_executed([not i1, not i2]) + txt1 = ('executed', 'skipped') + txt2 = ('E', 'S') + return [3, ['Instruction 1 %s, instruction 2 %s' % (txt1[i1], txt1[i2]), + 'I1 %s, I2 %s' % (txt2[i1], txt2[i2]), + '%s,%s' % (txt2[i1], txt2[i2])]] + else: + return self.fallback(buf) + + def handle_branch(self, buf): + if buf[-1] & 0x80 != 0x00: + return None # Not complete yet. + + brinfo = parse_branch_addr(buf, self.last_branch, self.cpu_state, + self.options['branch_enc']) + + if brinfo is None: + return None # Not complete yet. + + addr, addrlen, cpu_state, exc_info = brinfo + self.last_branch = addr + self.current_pc = addr + + txt = '' + + if cpu_state != self.cpu_state: + txt += ', to %s state' % cpu_state + self.cpu_state = cpu_state + + annidx = 1 + + if exc_info: + annidx = 2 + ns, exc, cancel, altisa, hyp, resume = exc_info + if ns: + txt += ', to non-secure state' + if exc: + if exc < len(exc_names): + txt += ', exception %s' % exc_names[exc] + else: + txt += ', exception 0x%02x' % exc + if cancel: + txt += ', instr cancelled' + if altisa: + txt += ', to AltISA' + if hyp: + txt += ', to hypervisor' + if resume: + txt += ', instr resume 0x%02x' % resume + + return [annidx, ['Branch to 0x%08x%s' % (addr, txt), + 'B 0x%08x%s' % (addr, txt)]] + + def decode(self, ss, es, data): + ptype, rxtx, pdata = data + + if ptype != 'DATA': + return + + # Reset packet if there is a long pause between bytes. + # This helps getting the initial synchronization. + self.byte_len = es - ss + if ss - self.prevsample > 16 * self.byte_len: + self.flush_current_loc() + self.flush_current_func() + self.buf = [] + self.prevsample = es + + self.buf.append(pdata[0]) + + # Store the start time of the packet. + if len(self.buf) == 1: + self.startsample = ss + + # Keep separate buffer for detection of sync packets. + # Sync packets override everything else, so that we can regain sync + # even if some packets are corrupted. + self.syncbuf = self.syncbuf[-4:] + [pdata[0]] + if self.syncbuf == [0x00, 0x00, 0x00, 0x00, 0x80]: + self.buf = self.syncbuf + self.syncbuf = [] + + # See if it is ready to be decoded. + ptype = self.get_packet_type(self.buf[0]) + if hasattr(self, 'handle_' + ptype): + func = getattr(self, 'handle_' + ptype) + data = func(self.buf) + else: + data = self.fallback(self.buf) + + if data is not None: + if data: + self.put(self.startsample, es, self.out_ann, data) + self.buf = [] diff --git a/decoders/arm_itm/__init__.py b/decoders/arm_itm/__init__.py new file mode 100644 index 0000000..d9789a4 --- /dev/null +++ b/decoders/arm_itm/__init__.py @@ -0,0 +1,26 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2015 Petteri Aimonen +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + +''' +This decoder stacks on top of the 'uart' or 'arm_tpiu' PD and decodes the +ARM Cortex-M processor trace data from Instrumentation Trace Macroblock. +''' + +from .pd import Decoder diff --git a/decoders/arm_itm/pd.py b/decoders/arm_itm/pd.py new file mode 100644 index 0000000..e32cce3 --- /dev/null +++ b/decoders/arm_itm/pd.py @@ -0,0 +1,335 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2015 Petteri Aimonen +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + +import sigrokdecode as srd +import string +import subprocess + +ARM_EXCEPTIONS = { + 0: 'Thread', + 1: 'Reset', + 2: 'NMI', + 3: 'HardFault', + 4: 'MemManage', + 5: 'BusFault', + 6: 'UsageFault', + 11: 'SVCall', + 12: 'Debug Monitor', + 14: 'PendSV', + 15: 'SysTick', +} + +class Decoder(srd.Decoder): + api_version = 2 + id = 'arm_itm' + name = 'ARM ITM' + longname = 'ARM Instrumentation Trace Macroblock' + desc = 'Trace data from Cortex-M / ARMv7m ITM module.' + license = 'gplv2+' + inputs = ['uart'] + outputs = ['arm_itm'] + options = ( + {'id': 'addr2line', 'desc': 'addr2line path', + 'default': 'arm-none-eabi-addr2line'}, + {'id': 'addr2line_opts', 'desc': 'addr2line options', + 'default': '-f -C -s -p'}, + {'id': 'elffile', 'desc': '.elf path', 'default': ''}, + ) + annotations = ( + ('trace', 'Trace information'), + ('timestamp', 'Timestamp'), + ('software', 'Software message'), + ('dwt_event', 'DWT event'), + ('dwt_watchpoint', 'DWT watchpoint'), + ('dwt_exc', 'Exception trace'), + ('dwt_pc', 'Program counter'), + ('mode_thread', 'Current mode: thread'), + ('mode_irq', 'Current mode: IRQ'), + ('mode_exc', 'Current mode: Exception'), + ('location', 'Current location') + ) + annotation_rows = ( + ('trace', 'Trace information', (0, 1)), + ('software', 'Software trace', (2,)), + ('dwt_event', 'DWT event', (3,)), + ('dwt_watchpoint', 'DWT watchpoint', (4,)), + ('dwt_exc', 'Exception trace', (5,)), + ('dwt_pc', 'Program counter', (6,)), + ('mode', 'Current mode', (7, 8, 9,)), + ('location', 'Current location', (10,)), + ) + + def __init__(self, **kwargs): + self.buf = [] + self.syncbuf = [] + self.swpackets = {} + self.prevsample = 0 + self.dwt_timestamp = 0 + self.current_mode = None + self.current_loc = None + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def get_packet_type(self, byte): + '''Identify packet type based on its first byte. + See ARMv7-M_ARM.pdf section "Debug ITM and DWT" "Packet Types" + ''' + if byte & 0x7F == 0: + return 'sync' + elif byte == 0x70: + return 'overflow' + elif byte & 0x0F == 0 and byte & 0xF0 != 0: + return 'timestamp' + elif byte & 0x0F == 0x08: + return 'sw_extension' + elif byte & 0x0F == 0x0C: + return 'hw_extension' + elif byte & 0x0F == 0x04: + return 'reserved' + elif byte & 0x04 == 0x00: + return 'software' + else: + return 'hardware' + + def mode_change(self, new_mode): + if self.current_mode is not None: + start, mode = self.current_mode + if mode.startswith('Thread'): + ann_idx = 7 + elif mode.startswith('IRQ'): + ann_idx = 8 + else: + ann_idx = 9 + self.put(start, self.startsample, self.out_ann, [ann_idx, [mode]]) + + if new_mode is None: + self.current_mode = None + else: + self.current_mode = (self.startsample, new_mode) + + def location_change(self, new_pc): + if self.options['addr2line'] and self.options['elffile']: + opts = [self.options['addr2line'], '-e', self.options['elffile']] + opts += self.options['addr2line_opts'].split() + opts += ['0x%08x' % new_pc] + + try: + new_loc = subprocess.check_output(opts) + except subprocess.CalledProcessError: + return + + new_loc = new_loc.decode('utf-8', 'replace').strip() + + if self.current_loc is not None: + start, loc = self.current_loc + if loc == new_loc: + return # Still on same line. + self.put(start, self.startsample, self.out_ann, [10, [loc]]) + + self.current_loc = (self.startsample, new_loc) + + def fallback(self, buf): + ptype = self.get_packet_type(buf[0]) + return [0, [('Unhandled %s: ' % ptype) + ' '.join(['%02x' % b for b in buf])]] + + def handle_overflow(self, buf): + return [0, ['Overflow']] + + def handle_hardware(self, buf): + '''Handle packets from hardware source, i.e. DWT block.''' + plen = (0, 1, 2, 4)[buf[0] & 0x03] + pid = buf[0] >> 3 + if len(buf) != plen + 1: + return None # Not complete yet. + + if pid == 0: + text = 'DWT events:' + if buf[1] & 0x20: + text += ' Cyc' + if buf[1] & 0x10: + text += ' Fold' + if buf[1] & 0x08: + text += ' LSU' + if buf[1] & 0x04: + text += ' Sleep' + if buf[1] & 0x02: + text += ' Exc' + if buf[1] & 0x01: + text += ' CPI' + return [3, [text]] + elif pid == 1: + excnum = ((buf[2] & 1) << 8) | buf[1] + event = (buf[2] >> 4) + excstr = ARM_EXCEPTIONS.get(excnum, 'IRQ %d' % (excnum - 16)) + if event == 1: + self.mode_change(excstr) + return [5, ['Enter: ' + excstr, 'E ' + excstr]] + elif event == 2: + self.mode_change(None) + return [5, ['Exit: ' + excstr, 'X ' + excstr]] + elif event == 3: + self.mode_change(excstr) + return [5, ['Resume: ' + excstr, 'R ' + excstr]] + elif pid == 2: + pc = buf[1] | (buf[2] << 8) | (buf[3] << 16) | (buf[4] << 24) + self.location_change(pc) + return [6, ['PC: 0x%08x' % pc]] + elif (buf[0] & 0xC4) == 0x84: + comp = (buf[0] & 0x30) >> 4 + what = 'Read' if (buf[0] & 0x08) == 0 else 'Write' + if plen == 1: + data = '0x%02x' % (buf[1]) + elif plen == 2: + data = '0x%04x' % (buf[1] | (buf[2] << 8)) + else: + data = '0x%08x' % (buf[1] | (buf[2] << 8) | (buf[3] << 16) | (buf[4] << 24)) + return [4, ['Watchpoint %d: %s data %s' % (comp, what, data), + 'WP%d: %s %s' % (comp, what[0], data)]] + elif (buf[0] & 0xCF) == 0x47: + comp = (buf[0] & 0x30) >> 4 + addr = buf[1] | (buf[2] << 8) | (buf[3] << 16) | (buf[4] << 24) + self.location_change(addr) + return [4, ['Watchpoint %d: PC 0x%08x' % (comp, addr), + 'WP%d: PC 0x%08x' % (comp, addr)]] + elif (buf[0] & 0xCF) == 0x4E: + comp = (buf[0] & 0x30) >> 4 + offset = buf[1] | (buf[2] << 8) + return [4, ['Watchpoint %d: address 0x????%04x' % (comp, offset), + 'WP%d: A 0x%04x' % (comp, offset)]] + + return self.fallback(buf) + + def handle_software(self, buf): + '''Handle packets generated by software running on the CPU.''' + plen = (0, 1, 2, 4)[buf[0] & 0x03] + pid = buf[0] >> 3 + if len(buf) != plen + 1: + return None # Not complete yet. + + if plen == 1 and chr(buf[1]) in string.printable: + self.add_delayed_sw(pid, chr(buf[1])) + return [] # Handled but no data to output. + + self.push_delayed_sw() + + if plen == 1: + return [2, ['%d: 0x%02x' % (pid, buf[1])]] + elif plen == 2: + return [2, ['%d: 0x%02x%02x' % (pid, buf[2], buf[1])]] + elif plen == 4: + return [2, ['%d: 0x%02x%02x%02x%02x' % (pid, buf[4], buf[3], buf[2], buf[1])]] + + def handle_timestamp(self, buf): + '''Handle timestamp packets, which indicate the time of some DWT event packet.''' + if buf[-1] & 0x80 != 0: + return None # Not complete yet. + + if buf[0] & 0x80 == 0: + tc = 0 + ts = buf[0] >> 4 + else: + tc = (buf[0] & 0x30) >> 4 + ts = buf[1] & 0x7F + if len(buf) > 2: + ts |= (buf[2] & 0x7F) << 7 + if len(buf) > 3: + ts |= (buf[3] & 0x7F) << 14 + if len(buf) > 4: + ts |= (buf[4] & 0x7F) << 21 + + self.dwt_timestamp += ts + + if tc == 0: + msg = '(exact)' + elif tc == 1: + msg = '(timestamp delayed)' + elif tc == 2: + msg = '(event delayed)' + elif tc == 3: + msg = '(event and timestamp delayed)' + + return [1, ['Timestamp: %d %s' % (self.dwt_timestamp, msg)]] + + def add_delayed_sw(self, pid, c): + '''We join printable characters from software source so that printed + strings are easy to read. Joining is done by PID so that different + sources do not get confused with each other.''' + if self.swpackets.get(pid) is not None: + self.swpackets[pid][1] = self.prevsample + self.swpackets[pid][2] += c + else: + self.swpackets[pid] = [self.startsample, self.prevsample, c] + + def push_delayed_sw(self): + for pid, packet in self.swpackets.items(): + if packet is None: + continue + ss, prevtime, text = packet + # Heuristic criterion: Text has ended if at least 16 byte + # durations after previous received byte. Actual delay depends + # on printf implementation on target. + if self.prevsample - prevtime > 16 * self.byte_len: + self.put(ss, prevtime, self.out_ann, [2, ['%d: "%s"' % (pid, text)]]) + self.swpackets[pid] = None + + def decode(self, ss, es, data): + ptype, rxtx, pdata = data + + # For now, ignore all UART packets except the actual data packets. + if ptype != 'DATA': + return + + self.byte_len = es - ss + + # Reset packet if there is a long pause between bytes. + # TPIU framing can introduce small pauses, but more than 1 frame + # should reset packet. + if ss - self.prevsample > 16 * self.byte_len: + self.push_delayed_sw() + self.buf = [] + self.prevsample = es + + # Build up the current packet byte by byte. + self.buf.append(pdata[0]) + + # Store the start time of the packet. + if len(self.buf) == 1: + self.startsample = ss + + # Keep separate buffer for detection of sync packets. + # Sync packets override everything else, so that we can regain sync + # even if some packets are corrupted. + self.syncbuf = self.syncbuf[-5:] + [pdata[0]] + if self.syncbuf == [0, 0, 0, 0, 0, 0x80]: + self.buf = self.syncbuf + + # See if it is ready to be decoded. + ptype = self.get_packet_type(self.buf[0]) + if hasattr(self, 'handle_' + ptype): + func = getattr(self, 'handle_' + ptype) + data = func(self.buf) + else: + data = self.fallback(self.buf) + + if data is not None: + if data: + self.put(self.startsample, es, self.out_ann, data) + self.buf = [] diff --git a/decoders/arm_tpiu/__init__.py b/decoders/arm_tpiu/__init__.py new file mode 100644 index 0000000..4958df8 --- /dev/null +++ b/decoders/arm_tpiu/__init__.py @@ -0,0 +1,28 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2015 Petteri Aimonen +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + +''' +This decoder stacks on top of the 'uart' decoder and decodes the frame format +of ARMv7m Trace Port Interface Unit. It filters the data coming from various +trace sources (such as ARMv7m ITM and ETM blocks) into separate streams that +can be further decoded by other PDs. +''' + +from .pd import Decoder diff --git a/decoders/arm_tpiu/pd.py b/decoders/arm_tpiu/pd.py new file mode 100644 index 0000000..ba01aa0 --- /dev/null +++ b/decoders/arm_tpiu/pd.py @@ -0,0 +1,128 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2015 Petteri Aimonen +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, 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 = 'arm_tpiu' + name = 'ARM TPIU' + longname = 'ARM Trace Port Interface Unit' + desc = 'Filter TPIU formatted trace data into separate streams.' + license = 'gplv2+' + inputs = ['uart'] + outputs = ['uart'] # Emulate uart output so that arm_itm/arm_etm can stack. + options = ( + {'id': 'stream', 'desc': 'Stream index', 'default': 1}, + {'id': 'sync_offset', 'desc': 'Initial sync offset', 'default': 0}, + ) + annotations = ( + ('stream', 'Current stream'), + ('data', 'Stream data'), + ) + annotation_rows = ( + ('stream', 'Current stream', (0,)), + ('data', 'Stream data', (1,)), + ) + + def __init__(self, **kwargs): + self.buf = [] + self.syncbuf = [] + self.prevsample = 0 + self.stream = 0 + self.stream_ss = None + self.bytenum = 0 + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + self.out_python = self.register(srd.OUTPUT_PYTHON) + + def stream_changed(self, ss, stream): + if self.stream != stream: + if self.stream != 0: + self.put(self.stream_ss, ss, self.out_ann, + [0, ["Stream %d" % self.stream, "S%d" % self.stream]]) + self.stream = stream + self.stream_ss = ss + + def emit_byte(self, ss, es, byte): + if self.stream == self.options['stream']: + self.put(ss, es, self.out_ann, [1, ["0x%02x" % byte]]) + self.put(ss, es, self.out_python, ['DATA', 0, (byte, [])]) + + def process_frame(self, buf): + # Byte 15 contains the lowest bits of bytes 0, 2, ... 14. + lowbits = buf[15][2] + + for i in range(0, 15, 2): + # Odd bytes can be stream ID or data. + delayed_stream_change = None + lowbit = (lowbits >> (i // 2)) & 0x01 + if buf[i][2] & 0x01 != 0: + if lowbit: + delayed_stream_change = buf[i][2] >> 1 + else: + self.stream_changed(buf[i][0], buf[i][2] >> 1) + else: + byte = buf[i][2] | lowbit + self.emit_byte(buf[i][0], buf[i][1], byte) + + # Even bytes are always data. + if i < 14: + self.emit_byte(buf[i+1][0], buf[i+1][1], buf[i+1][2]) + + # The stream change can be delayed to occur after the data byte. + if delayed_stream_change is not None: + self.stream_changed(buf[i+1][1], delayed_stream_change) + + def decode(self, ss, es, data): + ptype, rxtx, pdata = data + + if ptype != 'DATA': + return + + # Reset packet if there is a long pause between bytes. + self.byte_len = es - ss + if ss - self.prevsample > self.byte_len: + self.buf = [] + self.prevsample = es + + self.buf.append((ss, es, pdata[0])) + self.bytenum += 1 + + # Allow skipping N first bytes of the data. By adjusting the sync + # value, one can get initial synchronization as soon as the trace + # starts. + if self.bytenum < self.options['sync_offset']: + self.buf = [] + return + + # Keep separate buffer for detection of sync packets. + # Sync packets override everything else, so that we can regain sync + # even if some packets are corrupted. + self.syncbuf = self.syncbuf[-3:] + [pdata[0]] + if self.syncbuf == [0xFF, 0xFF, 0xFF, 0x7F]: + self.buf = [] + self.syncbuf = [] + return + + if len(self.buf) == 16: + self.process_frame(self.buf) + self.buf = [] diff --git a/decoders/avr_isp/__init__.py b/decoders/avr_isp/__init__.py index 4c4a900..75a9fe2 100644 --- a/decoders/avr_isp/__init__.py +++ b/decoders/avr_isp/__init__.py @@ -23,5 +23,4 @@ This decoder stacks on top of the 'spi' PD and decodes the In-System Programming (ISP) protocol of some Atmel AVR 8-bit microcontrollers. ''' -from .pd import * - +from .pd import Decoder diff --git a/decoders/avr_isp/parts.py b/decoders/avr_isp/parts.py index b9747f8..8e437b9 100644 --- a/decoders/avr_isp/parts.py +++ b/decoders/avr_isp/parts.py @@ -40,4 +40,3 @@ part = { (0x01, 0x02): 'Device locked', # TODO: Lots more entries. } - diff --git a/decoders/avr_isp/pd.py b/decoders/avr_isp/pd.py index a6b9c80..0914b5b 100644 --- a/decoders/avr_isp/pd.py +++ b/decoders/avr_isp/pd.py @@ -54,15 +54,15 @@ class Decoder(srd.Decoder): def __init__(self, **kwargs): self.state = 'IDLE' self.mosi_bytes, self.miso_bytes = [], [] - self.cmd_ss, self.cmd_es = 0, 0 + self.ss_cmd, self.es_cmd = 0, 0 self.xx, self.yy, self.zz, self.mm = 0, 0, 0, 0 - self.device_ss = None + self.ss_device = None def start(self): self.out_ann = self.register(srd.OUTPUT_ANN) def putx(self, data): - self.put(self.cmd_ss, self.cmd_es, self.out_ann, data) + self.put(self.ss_cmd, self.es_cmd, self.out_ann, data) def handle_cmd_programming_enable(self, cmd, ret): # Programming enable. @@ -99,7 +99,7 @@ class Decoder(srd.Decoder): # Store for later. self.mm = cmd[3] - self.device_ss = self.cmd_ss + self.ss_device = self.ss_cmd # Sanity check on reply. if ret[1] != 0x30 or ret[2] != cmd[1] or ret[0] != self.yy: @@ -112,7 +112,7 @@ class Decoder(srd.Decoder): p = part[(self.part_fam_flash_size, self.part_number)] data = [9, ['Device: Atmel %s' % p]] - self.put(self.device_ss, self.cmd_es, self.out_ann, data) + self.put(self.ss_device, self.es_cmd, self.out_ann, data) # Sanity check on reply. if ret[1] != 0x30 or ret[2] != self.xx or ret[0] != self.mm: @@ -192,7 +192,7 @@ class Decoder(srd.Decoder): self.ss, self.es = ss, es if len(self.mosi_bytes) == 0: - self.cmd_ss = ss + self.ss_cmd = ss # Append new bytes. self.mosi_bytes.append(mosi) @@ -202,10 +202,9 @@ class Decoder(srd.Decoder): if len(self.mosi_bytes) < 4: return - self.cmd_es = es + self.es_cmd = es self.handle_command(self.mosi_bytes, self.miso_bytes) self.mosi_bytes = [] self.miso_bytes = [] - diff --git a/decoders/can/__init__.py b/decoders/can/__init__.py index 38731c2..60dc85f 100644 --- a/decoders/can/__init__.py +++ b/decoders/can/__init__.py @@ -27,5 +27,4 @@ the digital output side of a CAN transceiver IC such as the Microchip MCP-2515DM-BM). ''' -from .pd import * - +from .pd import Decoder diff --git a/decoders/can/pd.py b/decoders/can/pd.py index bf38b7f..df2cbee 100644 --- a/decoders/can/pd.py +++ b/decoders/can/pd.py @@ -20,6 +20,9 @@ import sigrokdecode as srd +class SamplerateError(Exception): + pass + class Decoder(srd.Decoder): api_version = 2 id = 'can' @@ -358,8 +361,8 @@ class Decoder(srd.Decoder): self.curbit += 1 def decode(self, ss, es, data): - if self.samplerate is None: - raise Exception("Cannot decode without samplerate.") + if not self.samplerate: + raise SamplerateError('Cannot decode without samplerate.') for (self.samplenum, pins) in data: (can_rx,) = pins @@ -376,6 +379,3 @@ class Decoder(srd.Decoder): if not self.reached_bit(self.curbit): continue self.handle_bit(can_rx) - else: - raise Exception("Invalid state: %s" % self.state) - diff --git a/decoders/dcf77/__init__.py b/decoders/dcf77/__init__.py index 04805c2..8423028 100644 --- a/decoders/dcf77/__init__.py +++ b/decoders/dcf77/__init__.py @@ -26,5 +26,4 @@ Details: http://en.wikipedia.org/wiki/DCF77 ''' -from .pd import * - +from .pd import Decoder diff --git a/decoders/dcf77/pd.py b/decoders/dcf77/pd.py index fc97a1d..adee403 100644 --- a/decoders/dcf77/pd.py +++ b/decoders/dcf77/pd.py @@ -25,6 +25,9 @@ import calendar def bcd2int(b): return (b & 0x0f) + ((b >> 4) * 10) +class SamplerateError(Exception): + pass + class Decoder(srd.Decoder): api_version = 2 id = 'dcf77' @@ -239,14 +242,14 @@ class Decoder(srd.Decoder): # Even parity over date bits (36-58): DCF77 bit 58. parity = self.datebits.count(1) s = 'OK' if ((parity % 2) == 0) else 'INVALID!' - self.putx([16, ['Date parity: %s' % s, 'DP: %s' %s]]) + self.putx([16, ['Date parity: %s' % s, 'DP: %s' % s]]) self.datebits = [] else: raise Exception('Invalid DCF77 bit: %d' % c) def decode(self, ss, es, data): - if self.samplerate is None: - raise Exception("Cannot decode without samplerate.") + if not self.samplerate: + raise SamplerateError('Cannot decode without samplerate.') for (self.samplenum, pins) in data: # Ignore identical samples early on (for performance reasons). @@ -310,8 +313,4 @@ class Decoder(srd.Decoder): self.state = 'WAIT FOR RISING EDGE' - else: - raise Exception('Invalid state: %s' % self.state) - self.oldval = val - diff --git a/decoders/ds1307/__init__.py b/decoders/ds1307/__init__.py index 5bc28c3..7166229 100644 --- a/decoders/ds1307/__init__.py +++ b/decoders/ds1307/__init__.py @@ -23,5 +23,4 @@ This decoder stacks on top of the 'i2c' PD and decodes the Dallas DS1307 real-time clock (RTC) specific registers and commands. ''' -from .pd import * - +from .pd import Decoder diff --git a/decoders/ds1307/pd.py b/decoders/ds1307/pd.py index 68943d4..b784b6d 100644 --- a/decoders/ds1307/pd.py +++ b/decoders/ds1307/pd.py @@ -1,7 +1,7 @@ ## ## This file is part of the libsigrokdecode project. -## -## Copyright (C) 2012 Uwe Hermann +## +## Copyright (C) 2012-2014 Uwe Hermann ## Copyright (C) 2013 Matt Ranostay ## ## This program is free software; you can redistribute it and/or modify @@ -19,17 +19,37 @@ ## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA ## +import re import sigrokdecode as srd -days_of_week = [ - 'Sunday', - 'Monday', - 'Tuesday', - 'Wednesday', - 'Thursday', - 'Friday', - 'Saturday', -] +days_of_week = ( + 'Sunday', 'Monday', 'Tuesday', 'Wednesday', + 'Thursday', 'Friday', 'Saturday', +) + +regs = ( + 'Seconds', 'Minutes', 'Hours', 'Day', 'Date', 'Month', 'Year', + 'Control', 'RAM', +) + +bits = ( + 'Clock halt', 'Seconds', 'Reserved', 'Minutes', '12/24 hours', 'AM/PM', + 'Hours', 'Day', 'Date', 'Month', 'Year', 'OUT', 'SQWE', 'RS', 'RAM', +) + +rates = { + 0b00: '1Hz', + 0b01: '4096kHz', + 0b10: '8192kHz', + 0b11: '32768kHz', +} + +DS1307_I2C_ADDRESS = 0x68 + +def regs_and_bits(): + l = [('reg-' + r.lower(), r + ' register') for r in regs] + l += [('bit-' + re.sub('\/| ', '-', b).lower(), b + ' bit') for b in bits] + return tuple(l) # Return the specified BCD number (max. 8 bits) as integer. def bcd2int(b): @@ -44,8 +64,18 @@ class Decoder(srd.Decoder): license = 'gplv2+' inputs = ['i2c'] outputs = ['ds1307'] - annotations = ( - ('text', 'Human-readable text'), + annotations = regs_and_bits() + ( + ('read-datetime', 'Read date/time'), + ('write-datetime', 'Write date/time'), + ('reg-read', 'Register read'), + ('reg-write', 'Register write'), + ('warnings', 'Warnings'), + ) + annotation_rows = ( + ('bits', 'Bits', tuple(range(9, 24))), + ('regs', 'Registers', tuple(range(9))), + ('date-time', 'Date/time', (24, 25, 26, 27)), + ('warnings', 'Warnings', (28,)), ) def __init__(self, **kwargs): @@ -57,6 +87,7 @@ class Decoder(srd.Decoder): self.date = -1 self.months = -1 self.years = -1 + self.bits = [] def start(self): self.out_ann = self.register(srd.OUTPUT_ANN) @@ -64,40 +95,124 @@ class Decoder(srd.Decoder): def putx(self, data): self.put(self.ss, self.es, self.out_ann, data) - def handle_reg_0x00(self, b): # Seconds - self.seconds = bcd2int(b & 0x7f) - self.putx([0, ['Seconds: %d' % self.seconds]]) + def putd(self, bit1, bit2, data): + self.put(self.bits[bit1][1], self.bits[bit2][2], self.out_ann, data) + + def putr(self, bit): + self.put(self.bits[bit][1], self.bits[bit][2], self.out_ann, + [11, ['Reserved bit', 'Reserved', 'Rsvd', 'R']]) - def handle_reg_0x01(self, b): # Minutes - self.minutes = bcd2int(b & 0x7f) - self.putx([0, ['Minutes: %d' % self.minutes]]) + def handle_reg_0x00(self, b): # Seconds (0-59) / Clock halt bit + self.putd(7, 0, [0, ['Seconds', 'Sec', 'S']]) + ch = 1 if (b & (1 << 7)) else 0 + self.putd(7, 7, [9, ['Clock halt: %d' % ch, 'Clk hlt: %d' % ch, + 'CH: %d' % ch, 'CH']]) + s = self.seconds = bcd2int(b & 0x7f) + self.putd(6, 0, [10, ['Second: %d' % s, 'Sec: %d' % s, 'S: %d' % s, 'S']]) - def handle_reg_0x02(self, b): # Hours - self.hours = bcd2int(b & 0x3f) - self.putx([0, ['Hours: %d' % self.hours]]) + def handle_reg_0x01(self, b): # Minutes (0-59) + self.putd(7, 0, [1, ['Minutes', 'Min', 'M']]) + self.putr(7) + m = self.minutes = bcd2int(b & 0x7f) + self.putd(6, 0, [12, ['Minute: %d' % m, 'Min: %d' % m, 'M: %d' % m, 'M']]) - def handle_reg_0x03(self, b): # Day of week - self.days = bcd2int(b & 0x7) - self.putx([0, ['Day of Week: %s' % days_of_week[self.days - 1]]]) + def handle_reg_0x02(self, b): # Hours (1-12+AM/PM or 0-23) + self.putd(7, 0, [2, ['Hours', 'H']]) + self.putr(7) + ampm_mode = True if (b & (1 << 6)) else False + if ampm_mode: + self.putd(6, 6, [13, ['12-hour mode', '12h mode', '12h']]) + a = 'AM' if (b & (1 << 6)) else 'PM' + self.putd(5, 5, [14, [a, a[0]]]) + h = self.hours = bcd2int(b & 0x1f) + self.putd(4, 0, [15, ['Hour: %d' % h, 'H: %d' % h, 'H']]) + else: + self.putd(6, 6, [13, ['24-hour mode', '24h mode', '24h']]) + h = self.hours = bcd2int(b & 0x3f) + self.putd(5, 0, [15, ['Hour: %d' % h, 'H: %d' % h, 'H']]) - def handle_reg_0x04(self, b): # Date - self.date = bcd2int(b & 0x3f) - self.putx([0, ['Days: %d' % self.date]]) + def handle_reg_0x03(self, b): # Day / day of week (1-7) + self.putd(7, 0, [3, ['Day of week', 'Day', 'D']]) + for i in (7, 6, 5, 4, 3): + self.putr(i) + w = self.days = bcd2int(b & 0x07) + ws = days_of_week[self.days - 1] + self.putd(2, 0, [16, ['Weekday: %s' % ws, 'WD: %s' % ws, 'WD', 'W']]) - def handle_reg_0x05(self, b): # Month - self.months = bcd2int(b & 0x1f) - self.putx([0, ['Months: %d' % self.months]]) + def handle_reg_0x04(self, b): # Date (1-31) + self.putd(7, 0, [4, ['Date', 'D']]) + for i in (7, 6): + self.putr(i) + d = self.date = bcd2int(b & 0x3f) + self.putd(5, 0, [17, ['Date: %d' % d, 'D: %d' % d, 'D']]) - def handle_reg_0x06(self, b): # Year - self.years = bcd2int(b & 0xff) + 2000; - self.putx([0, ['Years: %d' % self.years]]) + def handle_reg_0x05(self, b): # Month (1-12) + self.putd(7, 0, [5, ['Month', 'Mon', 'M']]) + for i in (7, 6, 5): + self.putr(i) + m = self.months = bcd2int(b & 0x1f) + self.putd(4, 0, [18, ['Month: %d' % m, 'Mon: %d' % m, 'M: %d' % m, 'M']]) + + def handle_reg_0x06(self, b): # Year (0-99) + self.putd(7, 0, [6, ['Year', 'Y']]) + y = self.years = bcd2int(b & 0xff) + self.years += 2000 + self.putd(7, 0, [19, ['Year: %d' % y, 'Y: %d' % y, 'Y']]) def handle_reg_0x07(self, b): # Control Register - pass + self.putd(7, 0, [7, ['Control', 'Ctrl', 'C']]) + for i in (6, 5, 3, 2): + self.putr(i) + o = 1 if (b & (1 << 7)) else 0 + s = 1 if (b & (1 << 4)) else 0 + s2 = 'en' if (b & (1 << 4)) else 'dis' + r = rates[b & 0x03] + self.putd(7, 7, [20, ['Output control: %d' % o, + 'OUT: %d' % o, 'O: %d' % o, 'O']]) + self.putd(4, 4, [21, ['Square wave output: %sabled' % s2, + 'SQWE: %sabled' % s2, 'SQWE: %d' % s, 'S: %d' % s, 'S']]) + self.putd(1, 0, [22, ['Square wave output rate: %s' % r, + 'Square wave rate: %s' % r, 'SQW rate: %s' % r, 'Rate: %s' % r, + 'RS: %s' % s, 'RS', 'R']]) + + def handle_reg_0x3f(self, b): # RAM (bytes 0x08-0x3f) + self.putd(7, 0, [8, ['RAM', 'R']]) + self.putd(7, 0, [23, ['SRAM: 0x%02X' % b, '0x%02X' % b]]) + + def output_datetime(self, cls, rw): + # TODO: Handle read/write of only parts of these items. + d = '%s, %02d.%02d.%4d %02d:%02d:%02d' % ( + days_of_week[self.days - 1], self.date, self.months, + self.years, self.hours, self.minutes, self.seconds) + self.put(self.ss_block, self.es, self.out_ann, + [cls, ['%s date/time: %s' % (rw, d)]]) + + def handle_reg(self, b): + r = self.reg if self.reg < 8 else 0x3f + fn = getattr(self, 'handle_reg_0x%02x' % r) + fn(b) + # Honor address auto-increment feature of the DS1307. When the + # address reaches 0x3f, it will wrap around to address 0. + self.reg += 1 + if self.reg > 0x3f: + self.reg = 0 + + def is_correct_chip(self, addr): + if addr == DS1307_I2C_ADDRESS: + return True + self.put(self.ss_block, self.es, self.out_ann, + [28, ['Ignoring non-DS1307 data (slave 0x%02X)' % addr]]) + return False def decode(self, ss, es, data): cmd, databyte = data + # Collect the 'BITS' packet, then return. The next packet is + # guaranteed to belong to these bits we just stored. + if cmd == 'BITS': + self.bits = databyte + return + # Store the start/end samples of this I²C packet. self.ss, self.es = ss, es @@ -107,12 +222,14 @@ class Decoder(srd.Decoder): if cmd != 'START': return self.state = 'GET SLAVE ADDR' - self.block_start_sample = ss + self.ss_block = ss elif self.state == 'GET SLAVE ADDR': # Wait for an address write operation. - # TODO: We should only handle packets to the RTC slave (0x68). if cmd != 'ADDRESS WRITE': return + if not self.is_correct_chip(databyte): + self.state = 'IDLE' + return self.state = 'GET REG ADDR' elif self.state == 'GET REG ADDR': # Wait for a data write (master selects the slave register). @@ -121,49 +238,27 @@ class Decoder(srd.Decoder): self.reg = databyte self.state = 'WRITE RTC REGS' elif self.state == 'WRITE RTC REGS': - # If we see a Repeated Start here, it's probably an RTC read. + # If we see a Repeated Start here, it's an RTC read. if cmd == 'START REPEAT': self.state = 'READ RTC REGS' return # Otherwise: Get data bytes until a STOP condition occurs. if cmd == 'DATA WRITE': - handle_reg = getattr(self, 'handle_reg_0x%02x' % self.reg) - handle_reg(databyte) - self.reg += 1 - # TODO: Check for NACK! + self.handle_reg(databyte) elif cmd == 'STOP': - # TODO: Handle read/write of only parts of these items. - d = '%s, %02d.%02d.%02d %02d:%02d:%02d' % ( - days_of_week[self.days - 1], self.date, self.months, - self.years, self.hours, self.minutes, self.seconds) - self.put(self.block_start_sample, es, self.out_ann, - [0, ['Written date/time: %s' % d]]) + self.output_datetime(25, 'Written') self.state = 'IDLE' - else: - pass # TODO elif self.state == 'READ RTC REGS': # Wait for an address read operation. - # TODO: We should only handle packets to the RTC slave (0x68). - if cmd == 'ADDRESS READ': - self.state = 'READ RTC REGS2' + if cmd != 'ADDRESS READ': + return + if not self.is_correct_chip(databyte): + self.state = 'IDLE' return - else: - pass # TODO + self.state = 'READ RTC REGS2' elif self.state == 'READ RTC REGS2': if cmd == 'DATA READ': - handle_reg = getattr(self, 'handle_reg_0x%02x' % self.reg) - handle_reg(databyte) - self.reg += 1 - # TODO: Check for NACK! + self.handle_reg(databyte) elif cmd == 'STOP': - d = '%s, %02d.%02d.%02d %02d:%02d:%02d' % ( - days_of_week[self.days - 1], self.date, self.months, - self.years, self.hours, self.minutes, self.seconds) - self.put(self.block_start_sample, es, self.out_ann, - [0, ['Read date/time: %s' % d]]) + self.output_datetime(24, 'Read') self.state = 'IDLE' - else: - pass # TODO? - else: - raise Exception('Invalid state: %s' % self.state) - diff --git a/decoders/edid/__init__.py b/decoders/edid/__init__.py index 8683006..de544d3 100644 --- a/decoders/edid/__init__.py +++ b/decoders/edid/__init__.py @@ -36,5 +36,4 @@ More information on EDID is available here: https://en.wikipedia.org/wiki/Extended_display_identification_data ''' -from .pd import * - +from .pd import Decoder diff --git a/decoders/edid/pd.py b/decoders/edid/pd.py index 8af10ae..b154de7 100644 --- a/decoders/edid/pd.py +++ b/decoders/edid/pd.py @@ -85,6 +85,10 @@ class Decoder(srd.Decoder): ('fields', 'EDID structure fields'), ('sections', 'EDID structure sections'), ) + annotation_rows = ( + ('sections', 'Sections', (1,)), + ('fields', 'Fields', (0,)), + ) def __init__(self, **kwargs): self.state = None @@ -109,7 +113,6 @@ class Decoder(srd.Decoder): self.sn.append([ss, es]) self.cache.append(data) # debug -# self.put(ss, es, self.out_ann, [0, ['%d: [%.2x]' % (self.cnt, data)]]) if self.state is None: # Wait for the EDID header @@ -120,24 +123,43 @@ class Decoder(srd.Decoder): self.cache = self.cache[-8:] self.cnt = 8 self.state = 'edid' - self.put(ss, es, self.out_ann, [0, ['EDID header']]) + self.put(self.sn[0][0], es, self.out_ann, + [ANN_SECTIONS, ['Header']]) + self.put(self.sn[0][0], es, self.out_ann, + [ANN_FIELDS, ['Header pattern']]) elif self.state == 'edid': if self.cnt == OFF_VERSION: self.decode_vid(-10) self.decode_pid(-8) self.decode_serial(-6) self.decode_mfrdate(-2) + self.put(self.sn[OFF_VENDOR][0], es, self.out_ann, + [ANN_SECTIONS, ['Vendor/product']]) elif self.cnt == OFF_BASIC: - version = 'EDID version: %d.%d' % (self.cache[-2], self.cache[-1]) - self.put(ss, es, self.out_ann, [0, [version]]) + self.put(self.sn[OFF_VERSION][0], es, self.out_ann, + [ANN_SECTIONS, ['EDID Version']]) + self.put(self.sn[OFF_VERSION][0], self.sn[OFF_VERSION][1], + self.out_ann, [ANN_FIELDS, + ["Version %d" % self.cache[-2]]]) + self.put(self.sn[OFF_VERSION+1][0], self.sn[OFF_VERSION+1][1], + self.out_ann, [ANN_FIELDS, + [ "Revision %d" % self.cache[-1]]]) elif self.cnt == OFF_CHROM: + self.put(self.sn[OFF_BASIC][0], es, self.out_ann, + [ANN_SECTIONS, ['Basic display']]) self.decode_basicdisplay(-5) elif self.cnt == OFF_EST_TIMING: + self.put(self.sn[OFF_CHROM][0], es, self.out_ann, + [ANN_SECTIONS, ['Color characteristics']]) self.decode_chromaticity(-10) elif self.cnt == OFF_STD_TIMING: + self.put(self.sn[OFF_EST_TIMING][0], es, self.out_ann, + [ANN_SECTIONS, ['Established timings']]) self.decode_est_timing(-3) elif self.cnt == OFF_DET_TIMING: - self.decode_std_timing(-16) + self.put(self.sn[OFF_STD_TIMING][0], es, self.out_ann, + [ANN_SECTIONS, ['Standard timings']]) + self.decode_std_timing(self.cnt - 16) elif self.cnt == OFF_NUM_EXT: self.decode_descriptors(-72) elif self.cnt == OFF_CHECKSUM: @@ -311,7 +333,7 @@ class Decoder(srd.Decoder): modestr += est_modes[i] + ', ' if modestr: self.ann_field(offset, offset+2, - 'Supported establised modes: %s' % modestr[:-2]) + 'Supported established modes: %s' % modestr[:-2]) def decode_std_timing(self, offset): modestr = '' @@ -326,8 +348,8 @@ class Decoder(srd.Decoder): refresh = (self.cache[offset+i+1] & 0x3f) + 60 modestr += '%dx%d@%dHz, ' % (x, y, refresh) if modestr: - self.ann_field(offset, offset+2, - 'Supported standard modes: %s' % modestr[:-2]) + self.ann_field(offset, offset + 15, + 'Supported standard modes: %s' % modestr[:-2]) def decode_detailed_timing(self, offset): if offset == -72 and self.have_preferred_timing: @@ -336,7 +358,7 @@ class Decoder(srd.Decoder): else: section = 'Detailed' section += ' timing descriptor' - self.put(self.sn[offset][0], self.sn[offset+18][1], + self.put(self.sn[offset][0], self.sn[offset+17][1], self.out_ann, [ANN_SECTIONS, [section]]) pixclock = float((self.cache[offset+1] << 8) + self.cache[offset]) / 100 @@ -346,38 +368,36 @@ class Decoder(srd.Decoder): self.ann_field(offset+2, offset+4, 'Horizontal active: %d' % horiz_active) horiz_blank = ((self.cache[offset+4] & 0x0f) << 8) + self.cache[offset+3] - self.ann_field(offset+3, offset+4, 'Horizontal blanking: %d' % horiz_blank) + self.ann_field(offset+2, offset+4, 'Horizontal blanking: %d' % horiz_blank) vert_active = ((self.cache[offset+7] & 0xf0) << 4) + self.cache[offset+5] self.ann_field(offset+5, offset+7, 'Vertical active: %d' % vert_active) vert_blank = ((self.cache[offset+7] & 0x0f) << 8) + self.cache[offset+6] - self.ann_field(offset+6, offset+7, 'Vertical blanking: %d' % vert_blank) + self.ann_field(offset+5, offset+7, 'Vertical blanking: %d' % vert_blank) horiz_sync_off = ((self.cache[offset+11] & 0xc0) << 2) + self.cache[offset+8] self.ann_field(offset+8, offset+11, 'Horizontal sync offset: %d' % horiz_sync_off) horiz_sync_pw = ((self.cache[offset+11] & 0x30) << 4) + self.cache[offset+9] - self.ann_field(offset+9, offset+11, 'Horizontal sync pulse width: %d' % horiz_sync_pw) + self.ann_field(offset+8, offset+11, 'Horizontal sync pulse width: %d' % horiz_sync_pw) vert_sync_off = ((self.cache[offset+11] & 0x0c) << 2) \ + ((self.cache[offset+10] & 0xf0) >> 4) - self.ann_field(offset+10, offset+11, 'Vertical sync offset: %d' % vert_sync_off) + self.ann_field(offset+8, offset+11, 'Vertical sync offset: %d' % vert_sync_off) vert_sync_pw = ((self.cache[offset+11] & 0x03) << 4) \ + (self.cache[offset+10] & 0x0f) - self.ann_field(offset+10, offset+11, 'Vertical sync pulse width: %d' % vert_sync_pw) + self.ann_field(offset+8, offset+11, 'Vertical sync pulse width: %d' % vert_sync_pw) horiz_size = ((self.cache[offset+14] & 0xf0) << 4) + self.cache[offset+12] vert_size = ((self.cache[offset+14] & 0x0f) << 8) + self.cache[offset+13] self.ann_field(offset+12, offset+14, 'Physical size: %dx%dmm' % (horiz_size, vert_size)) horiz_border = self.cache[offset+15] - if horiz_border: - self.ann_field(offset+15, offset+15, 'Horizontal border: %d pixels' % horiz_border) + self.ann_field(offset+15, offset+15, 'Horizontal border: %d pixels' % horiz_border) vert_border = self.cache[offset+16] - if vert_border: - self.ann_field(offset+16, offset+16, 'Vertical border: %d lines' % vert_border) + self.ann_field(offset+16, offset+16, 'Vertical border: %d lines' % vert_border) features = 'Flags: ' if self.cache[offset+17] & 0x80: @@ -418,16 +438,22 @@ class Decoder(srd.Decoder): tag = self.cache[offset+3] if tag == 0xff: # Monitor serial number + self.put(self.sn[offset][0], self.sn[offset+17][1], self.out_ann, + [ANN_SECTIONS, ['Serial number']]) text = bytes(self.cache[offset+5:][:13]).decode(encoding='cp437', errors='replace') - self.ann_field(offset, offset+17, 'Serial number: %s' % text.strip()) + self.ann_field(offset, offset+17, text.strip()) elif tag == 0xfe: # Text + self.put(self.sn[offset][0], self.sn[offset+17][1], self.out_ann, + [ANN_SECTIONS, ['Text']]) text = bytes(self.cache[offset+5:][:13]).decode(encoding='cp437', errors='replace') - self.ann_field(offset, offset+17, 'Info: %s' % text.strip()) + self.ann_field(offset, offset+17, text.strip()) elif tag == 0xfc: # Monitor name + self.put(self.sn[offset][0], self.sn[offset+17][1], self.out_ann, + [ANN_SECTIONS, ['Monitor name']]) text = bytes(self.cache[offset+5:][:13]).decode(encoding='cp437', errors='replace') - self.ann_field(offset, offset+17, 'Model name: %s' % text.strip()) + self.ann_field(offset, offset+17, text.strip()) elif tag == 0xfd: # Monitor range limits self.put(self.sn[offset][0], self.sn[offset+17][1], self.out_ann, @@ -465,4 +491,3 @@ class Decoder(srd.Decoder): else: if self.cache[i+2] == 0 or self.cache[i+4] == 0: self.decode_descriptor(i) - diff --git a/decoders/eeprom24xx/__init__.py b/decoders/eeprom24xx/__init__.py new file mode 100644 index 0000000..de19da4 --- /dev/null +++ b/decoders/eeprom24xx/__init__.py @@ -0,0 +1,26 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + +''' +This decoder stacks on top of the 'i2c' PD and decodes the +industry standard 24xx series serial EEPROM protocol. +''' + +from .pd import Decoder diff --git a/decoders/eeprom24xx/lists.py b/decoders/eeprom24xx/lists.py new file mode 100644 index 0000000..ff4b3ed --- /dev/null +++ b/decoders/eeprom24xx/lists.py @@ -0,0 +1,171 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + +# +# Chip specific properties: +# +# - vendor: chip manufacturer +# - model: chip model +# - size: total EEPROM size (in number of bytes) +# - page_size: page size (in number of bytes) +# - page_wraparound: Whether writes wrap-around at page boundaries +# - addr_bytes: number of EEPROM address bytes used +# - addr_pins: number of address pins (A0/A1/A2) on this chip +# - max_speed: max. supported I²C speed (in kHz) +# +chips = { + # Generic chip (128 bytes, 8 bytes page size) + 'generic': { + 'vendor': '', + 'model': 'Generic', + 'size': 128, + 'page_size': 8, + 'page_wraparound': True, + 'addr_bytes': 1, + 'addr_pins': 3, + 'max_speed': 400, + }, + + # Microchip + 'microchip_24aa65': { + 'vendor': 'Microchip', + 'model': '24AA65', + 'size': 8 * 1024, + 'page_size': 64, # Actually 8, but there are 8 pages of "input cache" + 'page_wraparound': True, + 'addr_bytes': 2, + 'addr_pins': 3, + 'max_speed': 400, + }, + 'microchip_24lc65': { + 'vendor': 'Microchip', + 'model': '24LC65', + 'size': 8 * 1024, + 'page_size': 64, # Actually 8, but there are 8 pages of "input cache" + 'page_wraparound': True, + 'addr_bytes': 2, + 'addr_pins': 3, + 'max_speed': 400, + }, + 'microchip_24c65': { + 'vendor': 'Microchip', + 'model': '24C65', + 'size': 8 * 1024, + 'page_size': 64, # Actually 8, but there are 8 pages of "input cache" + 'page_wraparound': True, + 'addr_bytes': 2, + 'addr_pins': 3, + 'max_speed': 400, + }, + 'microchip_24aa64': { + 'vendor': 'Microchip', + 'model': '24AA64', + 'size': 8 * 1024, + 'page_size': 32, + 'page_wraparound': True, + 'addr_bytes': 2, + 'addr_pins': 3, + 'max_speed': 400, # 100 for VCC < 2.5V + }, + 'microchip_24lc64': { + 'vendor': 'Microchip', + 'model': '24LC64', + 'size': 8 * 1024, + 'page_size': 32, + 'page_wraparound': True, + 'addr_bytes': 2, + 'addr_pins': 3, + 'max_speed': 400, + }, + 'microchip_24aa02uid': { + 'vendor': 'Microchip', + 'model': '24AA02UID', + 'size': 256, + 'page_size': 8, + 'page_wraparound': True, + 'addr_bytes': 1, + 'addr_pins': 0, # Pins A0, A1, A2 not used + 'max_speed': 400, + }, + 'microchip_24aa025uid': { + 'vendor': 'Microchip', + 'model': '24AA025UID', + 'size': 256, + 'page_size': 16, + 'page_wraparound': True, + 'addr_bytes': 1, + 'addr_pins': 3, + 'max_speed': 400, + }, + 'microchip_24aa025uid_sot23': { + 'vendor': 'Microchip', + 'model': '24AA025UID (SOT-23)', + 'size': 256, + 'page_size': 16, + 'page_wraparound': True, + 'addr_bytes': 1, + 'addr_pins': 2, # SOT-23 package: A2 not available + 'max_speed': 400, + }, + + # Siemens + 'siemens_slx_24c01': { + 'vendor': 'Siemens', + 'model': 'SLx 24C01', + 'size': 128, + 'page_size': 8, + 'page_wraparound': True, + 'addr_bytes': 1, + 'addr_pins': 0, # Pins A0, A1, A2 are not connected (NC) + 'max_speed': 400, + }, + 'siemens_slx_24c02': { + 'vendor': 'Siemens', + 'model': 'SLx 24C02', + 'size': 256, + 'page_size': 8, + 'page_wraparound': True, + 'addr_bytes': 1, + 'addr_pins': 0, # Pins A0, A1, A2 are not connected (NC) + 'max_speed': 400, + }, + + # ST + 'st_m24c01': { + 'vendor': 'ST', + 'model': 'M24C01', + 'size': 128, + 'page_size': 16, + 'page_wraparound': True, + 'addr_bytes': 1, + 'addr_pins': 3, # Called E0, E1, E2 on this chip. + 'max_speed': 400, + }, + 'st_m24c02': { + 'vendor': 'ST', + 'model': 'M24C02', + 'size': 256, + 'page_size': 16, + 'page_wraparound': True, + 'addr_bytes': 1, + 'addr_pins': 3, # Called E0, E1, E2 on this chip. + 'max_speed': 400, + }, +} diff --git a/decoders/eeprom24xx/pd.py b/decoders/eeprom24xx/pd.py new file mode 100644 index 0000000..0738c06 --- /dev/null +++ b/decoders/eeprom24xx/pd.py @@ -0,0 +1,430 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + +import sigrokdecode as srd +from .lists import * + +class Decoder(srd.Decoder): + api_version = 2 + id = 'eeprom24xx' + name = '24xx EEPROM' + longname = '24xx I²C EEPROM' + desc = '24xx series I²C EEPROM protocol.' + license = 'gplv2+' + inputs = ['i2c'] + outputs = ['eeprom24xx'] + options = ( + {'id': 'chip', 'desc': 'Chip', 'default': 'generic', + 'values': tuple(chips.keys())}, + {'id': 'addr_counter', 'desc': 'Initial address counter value', + 'default': 0}, + ) + annotations = ( + # Warnings + ('warnings', 'Warnings'), + # Bits/bytes + ('control-code', 'Control code'), + ('address-pin', 'Address pin (A0/A1/A2)'), + ('rw-bit', 'Read/write bit'), + ('word-addr-byte', 'Word address byte'), + ('data-byte', 'Data byte'), + # Fields + ('control-word', 'Control word'), + ('word-addr', 'Word address'), + ('data', 'Data'), + # Operations + ('byte-write', 'Byte write'), + ('page-write', 'Page write'), + ('cur-addr-read', 'Current address read'), + ('random-read', 'Random read'), + ('seq-random-read', 'Sequential random read'), + ('seq-cur-addr-read', 'Sequential current address read'), + ('ack-polling', 'Acknowledge polling'), + ('set-bank-addr', 'Set bank address'), # SBA. Only 34AA04. + ('read-bank-addr', 'Read bank address'), # RBA. Only 34AA04. + ('set-wp', 'Set write protection'), # SWP + ('clear-all-wp', 'Clear all write protection'), # CWP + ('read-wp', 'Read write protection status'), # RPS + ) + annotation_rows = ( + ('bits-bytes', 'Bits/bytes', (1, 2, 3, 4, 5)), + ('fields', 'Fields', (6, 7, 8)), + ('ops', 'Operations', tuple(range(9, 21))), + ('warnings', 'Warnings', (0,)), + ) + binary = ( + ('binary', 'Binary'), + ) + + def __init__(self, **kwargs): + self.reset() + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + self.out_bin = self.register(srd.OUTPUT_BINARY) + self.chip = chips[self.options['chip']] + self.addr_counter = self.options['addr_counter'] + + def putb(self, data): + self.put(self.ss_block, self.es_block, self.out_ann, data) + + def putbin(self, data): + self.put(self.ss_block, self.es_block, self.out_bin, data) + + def putbits(self, bit1, bit2, bits, data): + self.put(bits[bit1][1], bits[bit2][2], self.out_ann, data) + + def reset(self): + self.state = 'WAIT FOR START' + self.packets = [] + self.bytebuf = [] + self.is_cur_addr_read = False + self.is_random_access_read = False + self.is_seq_random_read = False + self.is_byte_write = False + self.is_page_write = False + + def packet_append(self): + self.packets.append([self.ss, self.es, self.cmd, self.databyte, self.bits]) + if self.cmd in ('DATA READ', 'DATA WRITE'): + self.bytebuf.append(self.databyte) + + def hexbytes(self, idx): + return ' '.join(['%02X' % b for b in self.bytebuf[idx:]]) + + def put_control_word(self, bits): + s = ''.join(['%d' % b[0] for b in reversed(bits[4:])]) + self.putbits(7, 4, bits, [1, ['Control code bits: ' + s, + 'Control code: ' + s, 'Ctrl code: ' + s, 'Ctrl code', 'Ctrl', 'C']]) + for i in reversed(range(self.chip['addr_pins'])): + self.putbits(i + 1, i + 1, bits, + [2, ['Address bit %d: %d' % (i, bits[i + 1][0]), + 'Addr bit %d' % i, 'A%d' % i, 'A']]) + s1 = 'read' if bits[0][0] == 1 else 'write' + s2 = 'R' if bits[0][0] == 1 else 'W' + self.putbits(0, 0, bits, [3, ['R/W bit: ' + s1, 'R/W', 'RW', s2]]) + self.putbits(7, 0, bits, [6, ['Control word', 'Control', 'CW', 'C']]) + + def put_word_addr(self, p): + if self.chip['addr_bytes'] == 1: + a = p[1][3] + self.put(p[1][0], p[1][1], self.out_ann, + [4, ['Word address byte: %02X' % a, 'Word addr byte: %02X' % a, + 'Addr: %02X' % a, 'A: %02X' % a, '%02X' % a]]) + self.put(p[1][0], p[1][1], self.out_ann, [7, ['Word address', + 'Word addr', 'Addr', 'A']]) + self.addr_counter = a + else: + a = p[1][3] + self.put(p[1][0], p[1][1], self.out_ann, + [4, ['Word address high byte: %02X' % a, + 'Word addr high byte: %02X' % a, + 'Addr high: %02X' % a, 'AH: %02X' % a, '%02X' % a]]) + a = p[2][3] + self.put(p[2][0], p[2][1], self.out_ann, + [4, ['Word address low byte: %02X' % a, + 'Word addr low byte: %02X' % a, + 'Addr low: %02X' % a, 'AL: %02X' % a, '%02X' % a]]) + self.put(p[1][0], p[2][1], self.out_ann, [7, ['Word address', + 'Word addr', 'Addr', 'A']]) + self.addr_counter = (p[1][3] << 8) | p[2][3] + + def put_data_byte(self, p): + if self.chip['addr_bytes'] == 1: + s = '%02X' % self.addr_counter + else: + s = '%04X' % self.addr_counter + self.put(p[0], p[1], self.out_ann, [5, ['Data byte %s: %02X' % \ + (s, p[3]), 'Data byte: %02X' % p[3], \ + 'Byte: %02X' % p[3], 'DB: %02X' % p[3], '%02X' % p[3]]]) + + def put_data_bytes(self, idx, cls, s): + for p in self.packets[idx:]: + self.put_data_byte(p) + self.addr_counter += 1 + self.put(self.packets[idx][0], self.packets[-1][1], self.out_ann, + [8, ['Data', 'D']]) + a = ''.join(['%s' % c[0] for c in s.split()]).upper() + self.putb([cls, ['%s (%s): %s' % (s, self.addr_and_len(), \ + self.hexbytes(self.chip['addr_bytes'])), + '%s (%s)' % (s, self.addr_and_len()), s, a, s[0]]]) + self.putbin((0, bytes(self.bytebuf[self.chip['addr_bytes']:]))) + + def addr_and_len(self): + if self.chip['addr_bytes'] == 1: + a = '%02X' % self.bytebuf[0] + else: + a = '%02X%02X' % tuple(self.bytebuf[:2]) + num_data_bytes = len(self.bytebuf) - self.chip['addr_bytes'] + d = '%d bytes' % num_data_bytes + if num_data_bytes <= 1: + d = d[:-1] + return 'addr=%s, %s' % (a, d) + + def decide_on_seq_or_rnd_read(self): + if len(self.bytebuf) < 2: + self.reset() + return + if len(self.bytebuf) == 2: + self.is_random_access_read = True + else: + self.is_seq_random_read = True + + def put_operation(self): + idx = 1 + self.chip['addr_bytes'] + if self.is_byte_write: + # Byte write: word address, one data byte. + self.put_word_addr(self.packets) + self.put_data_bytes(idx, 9, 'Byte write') + elif self.is_page_write: + # Page write: word address, two or more data bytes. + self.put_word_addr(self.packets) + intitial_addr = self.addr_counter + self.put_data_bytes(idx, 10, 'Page write') + num_bytes_to_write = len(self.packets[idx:]) + if num_bytes_to_write > self.chip['page_size']: + self.putb([0, ['Warning: Wrote %d bytes but page size is ' + 'only %d bytes!' % (num_bytes_to_write, + self.chip['page_size'])]]) + page1 = int(intitial_addr / self.chip['page_size']) + page2 = int((self.addr_counter - 1) / self.chip['page_size']) + if page1 != page2: + self.putb([0, ['Warning: Page write crossed page boundary ' + 'from page %d to %d!' % (page1, page2)]]) + elif self.is_cur_addr_read: + # Current address read: no word address, one data byte. + self.put_data_byte(self.packets[1]) + self.put(self.packets[1][0], self.packets[-1][1], self.out_ann, + [8, ['Data', 'D']]) + self.putb([11, ['Current address read: %02X' % self.bytebuf[0], + 'Current address read', 'Cur addr read', 'CAR', 'C']]) + self.putbin((0, bytes([self.bytebuf[0]]))) + self.addr_counter += 1 + elif self.is_random_access_read: + # Random access read: word address, one data byte. + self.put_control_word(self.packets[idx][4]) + self.put_word_addr(self.packets) + self.put_data_bytes(idx + 1, 12, 'Random access read') + elif self.is_seq_random_read: + # Sequential random read: word address, two or more data bytes. + self.put_control_word(self.packets[idx][4]) + self.put_word_addr(self.packets) + self.put_data_bytes(idx + 1, 13, 'Sequential random read') + + def handle_wait_for_start(self): + # Wait for an I²C START condition. + if self.cmd not in ('START', 'START REPEAT'): + return + self.ss_block = self.ss + self.state = 'GET CONTROL WORD' + + def handle_get_control_word(self): + # The packet after START must be an ADDRESS READ or ADDRESS WRITE. + if self.cmd not in ('ADDRESS READ', 'ADDRESS WRITE'): + self.reset() + return + self.packet_append() + self.put_control_word(self.bits) + self.state = '%s GET ACK NACK AFTER CONTROL WORD' % self.cmd[8] + + def handle_r_get_ack_nack_after_control_word(self): + if self.cmd == 'ACK': + self.state = 'R GET WORD ADDR OR BYTE' + elif self.cmd == 'NACK': + self.es_block = self.es + self.putb([0, ['Warning: No reply from slave!']]) + self.reset() + else: + self.reset() + + def handle_r_get_word_addr_or_byte(self): + if self.cmd == 'STOP': + self.es_block = self.es + self.putb([0, ['Warning: Slave replied, but master aborted!']]) + self.reset() + return + elif self.cmd != 'DATA READ': + self.reset() + return + self.packet_append() + self.state = 'R GET ACK NACK AFTER WORD ADDR OR BYTE' + + def handle_r_get_ack_nack_after_word_addr_or_byte(self): + if self.cmd == 'ACK': + self.state = 'R GET RESTART' + elif self.cmd == 'NACK': + self.is_cur_addr_read = True + self.state = 'GET STOP AFTER LAST BYTE' + else: + self.reset() + + def handle_r_get_restart(self): + if self.cmd == 'RESTART': + self.state = 'R READ BYTE' + else: + self.reset() + + def handle_r_read_byte(self): + if self.cmd == 'DATA READ': + self.packet_append() + self.state = 'R GET ACK NACK AFTER BYTE WAS READ' + else: + self.reset() + + def handle_r_get_ack_nack_after_byte_was_read(self): + if self.cmd == 'ACK': + self.state = 'R READ BYTE' + elif self.cmd == 'NACK': + # It's either a RANDOM READ or a SEQUENTIAL READ. + self.state = 'GET STOP AFTER LAST BYTE' + else: + self.reset() + + def handle_w_get_ack_nack_after_control_word(self): + if self.cmd == 'ACK': + self.state = 'W GET WORD ADDR' + elif self.cmd == 'NACK': + self.es_block = self.es + self.putb([0, ['Warning: No reply from slave!']]) + self.reset() + else: + self.reset() + + def handle_w_get_word_addr(self): + if self.cmd == 'STOP': + self.es_block = self.es + self.putb([0, ['Warning: Slave replied, but master aborted!']]) + self.reset() + return + elif self.cmd != 'DATA WRITE': + self.reset() + return + self.packet_append() + self.state = 'W GET ACK AFTER WORD ADDR' + + def handle_w_get_ack_after_word_addr(self): + if self.cmd == 'ACK': + self.state = 'W DETERMINE EEPROM READ OR WRITE' + else: + self.reset() + + def handle_w_determine_eeprom_read_or_write(self): + if self.cmd == 'START REPEAT': + # It's either a RANDOM ACCESS READ or SEQUENTIAL RANDOM READ. + self.state = 'R2 GET CONTROL WORD' + elif self.cmd == 'DATA WRITE': + self.packet_append() + self.state = 'W GET ACK NACK AFTER BYTE WAS WRITTEN' + else: + self.reset() + + def handle_w_write_byte(self): + if self.cmd == 'DATA WRITE': + self.packet_append() + self.state = 'W GET ACK NACK AFTER BYTE WAS WRITTEN' + elif self.cmd == 'STOP': + if len(self.bytebuf) < 2: + self.reset() + return + self.es_block = self.es + if len(self.bytebuf) == 2: + self.is_byte_write = True + else: + self.is_page_write = True + self.put_operation() + self.reset() + elif self.cmd == 'START REPEAT': + # It's either a RANDOM ACCESS READ or SEQUENTIAL RANDOM READ. + self.state = 'R2 GET CONTROL WORD' + else: + self.reset() + + def handle_w_get_ack_nack_after_byte_was_written(self): + if self.cmd == 'ACK': + self.state = 'W WRITE BYTE' + else: + self.reset() + + def handle_r2_get_control_word(self): + if self.cmd == 'ADDRESS READ': + self.packet_append() + self.state = 'R2 GET ACK AFTER ADDR READ' + else: + self.reset() + + def handle_r2_get_ack_after_addr_read(self): + if self.cmd == 'ACK': + self.state = 'R2 READ BYTE' + else: + self.reset() + + def handle_r2_read_byte(self): + if self.cmd == 'DATA READ': + self.packet_append() + self.state = 'R2 GET ACK NACK AFTER BYTE WAS READ' + elif self.cmd == 'STOP': + self.decide_on_seq_or_rnd_read() + self.es_block = self.es + self.putb([0, ['Warning: STOP expected after a NACK (not ACK)']]) + self.put_operation() + self.reset() + else: + self.reset() + + def handle_r2_get_ack_nack_after_byte_was_read(self): + if self.cmd == 'ACK': + self.state = 'R2 READ BYTE' + elif self.cmd == 'NACK': + self.decide_on_seq_or_rnd_read() + self.state = 'GET STOP AFTER LAST BYTE' + else: + self.reset() + + def handle_get_stop_after_last_byte(self): + if self.cmd == 'STOP': + self.es_block = self.es + self.put_operation() + self.reset() + elif self.cmd == 'START REPEAT': + self.es_block = self.es + self.putb([0, ['Warning: STOP expected (not RESTART)']]) + self.put_operation() + self.reset() + self.ss_block = self.ss + self.state = 'GET CONTROL WORD' + else: + self.reset() + + def decode(self, ss, es, data): + self.cmd, self.databyte = data + + # Collect the 'BITS' packet, then return. The next packet is + # guaranteed to belong to these bits we just stored. + if self.cmd == 'BITS': + self.bits = self.databyte + return + + # Store the start/end samples of this I²C packet. + self.ss, self.es = ss, es + + # State machine. + s = 'handle_%s' % self.state.lower().replace(' ', '_') + handle_state = getattr(self, s) + handle_state() diff --git a/decoders/guess_bitrate/__init__.py b/decoders/guess_bitrate/__init__.py index 9c7e6a4..cd8f15e 100644 --- a/decoders/guess_bitrate/__init__.py +++ b/decoders/guess_bitrate/__init__.py @@ -34,5 +34,4 @@ recommended to use a logic analyzer samplerate that is much higher than the expected bitrate/baudrate that might be used on the channel. ''' -from .pd import * - +from .pd import Decoder diff --git a/decoders/guess_bitrate/pd.py b/decoders/guess_bitrate/pd.py index e86316f..250d519 100644 --- a/decoders/guess_bitrate/pd.py +++ b/decoders/guess_bitrate/pd.py @@ -20,6 +20,9 @@ import sigrokdecode as srd +class SamplerateError(Exception): + pass + class Decoder(srd.Decoder): api_version = 2 id = 'guess_bitrate' @@ -50,11 +53,11 @@ class Decoder(srd.Decoder): def metadata(self, key, value): if key == srd.SRD_CONF_SAMPLERATE: - self.samplerate = value; + self.samplerate = value def decode(self, ss, es, data): - if self.samplerate is None: - raise Exception("Cannot decode without samplerate.") + if not self.samplerate: + raise SamplerateError('Cannot decode without samplerate.') for (self.samplenum, pins) in data: data = pins[0] @@ -64,22 +67,21 @@ class Decoder(srd.Decoder): continue # Initialize first self.olddata with the first sample value. - if self.olddata == None: + if self.olddata is None: self.olddata = data continue # Get the smallest distance between two transitions # and use that to calculate the bitrate/baudrate. - if self.first_transition == True: + if self.first_transition: self.ss_edge = self.samplenum self.first_transition = False else: b = self.samplenum - self.ss_edge - if self.bitwidth == None or b < self.bitwidth: + if self.bitwidth is None or b < self.bitwidth: self.bitwidth = b bitrate = int(float(self.samplerate) / float(b)) self.putx([0, ['%d' % bitrate]]) self.ss_edge = self.samplenum self.olddata = data - diff --git a/decoders/i2c/__init__.py b/decoders/i2c/__init__.py index ba40245..c3b0cd5 100644 --- a/decoders/i2c/__init__.py +++ b/decoders/i2c/__init__.py @@ -23,5 +23,4 @@ I²C (Inter-Integrated Circuit) is a bidirectional, multi-master bus using two signals (SCL = serial clock line, SDA = serial data line). ''' -from .pd import * - +from .pd import Decoder diff --git a/decoders/i2c/pd.py b/decoders/i2c/pd.py index 2d6e5c2..ce0d699 100644 --- a/decoders/i2c/pd.py +++ b/decoders/i2c/pd.py @@ -63,6 +63,9 @@ proto = { 'DATA WRITE': [9, 'Data write', 'DW'], } +class SamplerateError(Exception): + pass + class Decoder(srd.Decoder): api_version = 2 id = 'i2c' @@ -107,7 +110,7 @@ class Decoder(srd.Decoder): def __init__(self, **kwargs): self.samplerate = None - self.ss = self.es = self.byte_ss = -1 + self.ss = self.es = self.ss_byte = -1 self.samplenum = None self.bitcount = 0 self.databyte = 0 @@ -179,7 +182,7 @@ class Decoder(srd.Decoder): # Remember the start of the first data/address bit. if self.bitcount == 0: - self.byte_ss = self.samplenum + self.ss_byte = self.samplenum # Store individual bits and their start/end samplenumbers. # In the list, index 0 represents the LSB (I²C transmits MSB-first). @@ -216,7 +219,7 @@ class Decoder(srd.Decoder): cmd = 'DATA READ' bin_class = 2 - self.ss, self.es = self.byte_ss, self.samplenum + self.bitwidth + self.ss, self.es = self.ss_byte, self.samplenum + self.bitwidth self.putp(['BITS', self.bits]) self.putp([cmd, d]) @@ -230,7 +233,7 @@ class Decoder(srd.Decoder): self.ss, self.es = self.samplenum, self.samplenum + self.bitwidth w = ['Write', 'Wr', 'W'] if self.wr else ['Read', 'Rd', 'R'] self.putx([proto[cmd][0], w]) - self.ss, self.es = self.byte_ss, self.samplenum + self.ss, self.es = self.ss_byte, self.samplenum self.putx([proto[cmd][0], ['%s: %02X' % (proto[cmd][1], d), '%s: %02X' % (proto[cmd][2], d), '%02X' % d]]) @@ -253,7 +256,7 @@ class Decoder(srd.Decoder): # Meta bitrate elapsed = 1 / float(self.samplerate) * (self.samplenum - self.pdu_start + 1) bitrate = int(1 / elapsed * self.pdu_bits) - self.put(self.byte_ss, self.samplenum, self.out_bitrate, bitrate) + self.put(self.ss_byte, self.samplenum, self.out_bitrate, bitrate) cmd = 'STOP' self.ss, self.es = self.samplenum, self.samplenum @@ -265,8 +268,8 @@ class Decoder(srd.Decoder): self.bits = [] def decode(self, ss, es, data): - if self.samplerate is None: - raise Exception("Cannot decode without samplerate.") + if not self.samplerate: + raise SamplerateError('Cannot decode without samplerate.') for (self.samplenum, pins) in data: # Ignore identical samples early on (for performance reasons). @@ -293,9 +296,6 @@ class Decoder(srd.Decoder): elif self.state == 'FIND ACK': if self.is_data_bit(scl, sda): self.get_ack(scl, sda) - else: - raise Exception('Invalid state: %s' % self.state) # Save current SDA/SCL values for the next round. self.oldscl, self.oldsda = scl, sda - diff --git a/decoders/i2cdemux/__init__.py b/decoders/i2cdemux/__init__.py index c5202c5..75fcd86 100644 --- a/decoders/i2cdemux/__init__.py +++ b/decoders/i2cdemux/__init__.py @@ -25,5 +25,4 @@ It takes an I²C stream as input and outputs multiple I²C streams, each stream containing only I²C packets for one specific I²C slave. ''' -from .pd import * - +from .pd import Decoder diff --git a/decoders/i2cdemux/pd.py b/decoders/i2cdemux/pd.py index e5cf47a..68b75a0 100644 --- a/decoders/i2cdemux/pd.py +++ b/decoders/i2cdemux/pd.py @@ -75,4 +75,3 @@ class Decoder(srd.Decoder): self.stream = -1 else: pass # Do nothing, only add the I²C packet to our cache. - diff --git a/decoders/i2cfilter/__init__.py b/decoders/i2cfilter/__init__.py index 6cbab8a..be97bf0 100644 --- a/decoders/i2cfilter/__init__.py +++ b/decoders/i2cfilter/__init__.py @@ -34,5 +34,4 @@ Both of these are optional; if no options are specified the entire payload of the I²C session will be output. ''' -from .pd import * - +from .pd import Decoder diff --git a/decoders/i2cfilter/pd.py b/decoders/i2cfilter/pd.py index f1ac6a4..3c02a2e 100644 --- a/decoders/i2cfilter/pd.py +++ b/decoders/i2cfilter/pd.py @@ -39,7 +39,6 @@ class Decoder(srd.Decoder): ) def __init__(self, **kwargs): - self.state = None self.curslave = -1 self.curdirection = None self.packets = [] # Local cache of I²C packets @@ -48,8 +47,6 @@ class Decoder(srd.Decoder): self.out_python = self.register(srd.OUTPUT_PYTHON, proto_id='i2c') if self.options['address'] not in range(0, 127 + 1): raise Exception('Invalid slave (must be 0..127).') - if self.options['direction'] not in ('both', 'read', 'write'): - raise Exception('Invalid direction (valid: read/write/both).') # Grab I²C packets into a local cache, until an I²C STOP condition # packet comes along. At some point before that STOP condition, there @@ -90,4 +87,3 @@ class Decoder(srd.Decoder): self.packets = [] else: pass # Do nothing, only add the I²C packet to our cache. - diff --git a/decoders/i2s/__init__.py b/decoders/i2s/__init__.py index b9c0ed7..e114cd0 100644 --- a/decoders/i2s/__init__.py +++ b/decoders/i2s/__init__.py @@ -27,5 +27,4 @@ http://www.nxp.com/acrobat_download/various/I2SBUS.pdf http://en.wikipedia.org/wiki/I2s ''' -from .pd import * - +from .pd import Decoder diff --git a/decoders/i2s/pd.py b/decoders/i2s/pd.py index 989a5a5..ee642d7 100644 --- a/decoders/i2s/pd.py +++ b/decoders/i2s/pd.py @@ -33,6 +33,9 @@ Packet: : integer ''' +class SamplerateError(Exception): + pass + class Decoder(srd.Decoder): api_version = 2 id = 'i2s' @@ -64,7 +67,7 @@ class Decoder(srd.Decoder): self.data = 0 self.samplesreceived = 0 self.first_sample = None - self.start_sample = None + self.ss_block = None self.wordlength = -1 self.wrote_wav_header = False @@ -78,23 +81,23 @@ class Decoder(srd.Decoder): self.samplerate = value def putpb(self, data): - self.put(self.start_sample, self.samplenum, self.out_python, data) + self.put(self.ss_block, self.samplenum, self.out_python, data) def putbin(self, data): - self.put(self.start_sample, self.samplenum, self.out_bin, data) + self.put(self.ss_block, self.samplenum, self.out_bin, data) def putb(self, data): - self.put(self.start_sample, self.samplenum, self.out_ann, data) + self.put(self.ss_block, self.samplenum, self.out_ann, data) def report(self): # Calculate the sample rate. samplerate = '?' - if self.start_sample != None and \ - self.first_sample != None and \ - self.start_sample > self.first_sample: + if self.ss_block is not None and \ + self.first_sample is not None and \ + self.ss_block > self.first_sample: samplerate = '%d' % (self.samplesreceived * - self.samplerate / (self.start_sample - + self.samplerate / (self.ss_block - self.first_sample)) return 'I²S: %d %d-bit samples received at %sHz' % \ @@ -128,8 +131,8 @@ class Decoder(srd.Decoder): return bytes([lo, hi]) def decode(self, ss, es, data): - if self.samplerate is None: - raise Exception("Cannot decode without samplerate.") + if not self.samplerate: + raise SamplerateError('Cannot decode without samplerate.') for self.samplenum, (sck, ws, sd) in data: # Ignore sample if the bit clock hasn't changed. @@ -148,7 +151,7 @@ class Decoder(srd.Decoder): continue # Only submit the sample, if we received the beginning of it. - if self.start_sample != None: + if self.ss_block is not None: if not self.wrote_wav_header: self.put(0, 0, self.out_bin, (0, self.wav_header())) @@ -176,11 +179,10 @@ class Decoder(srd.Decoder): # Reset decoder state. self.data = 0 self.bitcount = 0 - self.start_sample = self.samplenum + self.ss_block = self.samplenum # Save the first sample position. - if self.first_sample == None: + if self.first_sample is None: self.first_sample = self.samplenum self.oldws = ws - diff --git a/decoders/ir_nec/__init__.py b/decoders/ir_nec/__init__.py index 84bf428..6b15f19 100644 --- a/decoders/ir_nec/__init__.py +++ b/decoders/ir_nec/__init__.py @@ -22,5 +22,4 @@ NEC is a pulse-distance based infrared remote control protocol. ''' -from .pd import * - +from .pd import Decoder diff --git a/decoders/ir_nec/lists.py b/decoders/ir_nec/lists.py index c9dc595..3f730d9 100644 --- a/decoders/ir_nec/lists.py +++ b/decoders/ir_nec/lists.py @@ -49,4 +49,3 @@ command = { 68: ['AV', 'AV'], }.items())), } - diff --git a/decoders/ir_nec/pd.py b/decoders/ir_nec/pd.py index 94e232f..d14c7d3 100644 --- a/decoders/ir_nec/pd.py +++ b/decoders/ir_nec/pd.py @@ -21,6 +21,9 @@ import sigrokdecode as srd from .lists import * +class SamplerateError(Exception): + pass + class Decoder(srd.Decoder): api_version = 2 id = 'ir_nec' @@ -148,8 +151,8 @@ class Decoder(srd.Decoder): return ret == 0 def decode(self, ss, es, data): - if self.samplerate is None: - raise Exception("Cannot decode without samplerate.") + if not self.samplerate: + raise SamplerateError('Cannot decode without samplerate.') for (self.samplenum, pins) in data: self.ir = pins[0] @@ -199,8 +202,5 @@ class Decoder(srd.Decoder): self.putremote() self.ss_bit = self.ss_start = self.samplenum self.state = 'IDLE' - else: - raise Exception('Invalid state: %s' % self.state) self.old_ir = self.ir - diff --git a/decoders/ir_rc5/__init__.py b/decoders/ir_rc5/__init__.py index 085082f..8f5f5a3 100644 --- a/decoders/ir_rc5/__init__.py +++ b/decoders/ir_rc5/__init__.py @@ -22,5 +22,4 @@ RC-5 is a biphase/manchester based infrared remote control protocol. ''' -from .pd import * - +from .pd import Decoder diff --git a/decoders/ir_rc5/lists.py b/decoders/ir_rc5/lists.py index 8a81ef7..2ac227e 100644 --- a/decoders/ir_rc5/lists.py +++ b/decoders/ir_rc5/lists.py @@ -92,4 +92,3 @@ command = { 55: ['Recording', 'Rec'], }.items())), } - diff --git a/decoders/ir_rc5/pd.py b/decoders/ir_rc5/pd.py index 38b4793..e1dd42f 100644 --- a/decoders/ir_rc5/pd.py +++ b/decoders/ir_rc5/pd.py @@ -21,6 +21,9 @@ import sigrokdecode as srd from .lists import * +class SamplerateError(Exception): + pass + class Decoder(srd.Decoder): api_version = 2 id = 'ir_rc5' @@ -56,7 +59,7 @@ class Decoder(srd.Decoder): def __init__(self, **kwargs): self.samplerate = None self.samplenum = None - self.edges, self.bits, self.bits_ss_es = [], [], [] + self.edges, self.bits, self.ss_es_bits = [], [], [] self.state = 'IDLE' def start(self): @@ -70,7 +73,7 @@ class Decoder(srd.Decoder): self.halfbit = int((self.samplerate * 0.00178) / 2.0) def putb(self, bit1, bit2, data): - ss, es = self.bits_ss_es[bit1][0], self.bits_ss_es[bit2][1] + ss, es = self.ss_es_bits[bit1][0], self.ss_es_bits[bit2][1] self.put(ss, es, self.out_ann, data) def handle_bits(self): @@ -80,9 +83,9 @@ class Decoder(srd.Decoder): if i == 0: ss = max(0, self.bits[0][0] - self.halfbit) else: - ss = self.bits_ss_es[i - 1][1] + ss = self.ss_es_bits[i - 1][1] es = self.bits[i][0] + self.halfbit - self.bits_ss_es.append([ss, es]) + self.ss_es_bits.append([ss, es]) self.putb(i, i, [0, ['%d' % self.bits[i][1]]]) # Bits[0:0]: Startbit 1 s = ['Startbit1: %d' % b[0][1], 'SB1: %d' % b[0][1], 'SB1', 'S1', 'S'] @@ -129,12 +132,12 @@ class Decoder(srd.Decoder): return 'e' # Error, invalid edge distance. def reset_decoder_state(self): - self.edges, self.bits, self.bits_ss_es = [], [], [] + self.edges, self.bits, self.ss_es_bits = [], [], [] self.state = 'IDLE' def decode(self, ss, es, data): - if self.samplerate is None: - raise Exception("Cannot decode without samplerate.") + if not self.samplerate: + raise SamplerateError('Cannot decode without samplerate.') for (self.samplenum, pins) in data: self.ir = pins[0] @@ -168,11 +171,9 @@ class Decoder(srd.Decoder): if edge == 's': self.state = 'MID0' bit = 0 if edge == 's' else None - else: - raise Exception('Invalid state: %s' % self.state) self.edges.append(self.samplenum) - if bit != None: + if bit is not None: self.bits.append([self.samplenum, bit]) if len(self.bits) == 14: @@ -180,4 +181,3 @@ class Decoder(srd.Decoder): self.reset_decoder_state() self.old_ir = self.ir - diff --git a/decoders/jitter/__init__.py b/decoders/jitter/__init__.py new file mode 100644 index 0000000..e223e73 --- /dev/null +++ b/decoders/jitter/__init__.py @@ -0,0 +1,29 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 Sebastien Bourdelin +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + +''' +This protocol decoder retrieves the timing jitter between two digital signals. + +It allows to define a clock source channel and a resulting signal channel. +Each time a significant edge is detected in the clock source, we calculate the +elapsed time before the resulting signal answers and report the timing jitter. +''' + +from .pd import Decoder diff --git a/decoders/jitter/pd.py b/decoders/jitter/pd.py new file mode 100644 index 0000000..d6db8e5 --- /dev/null +++ b/decoders/jitter/pd.py @@ -0,0 +1,203 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 Sebastien Bourdelin +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + +import sigrokdecode as srd + +# Helper dictionary for edge detection. +edge_detector = { + 'rising': lambda x, y: bool(not x and y), + 'falling': lambda x, y: bool(x and not y), + 'both': lambda x, y: bool(x ^ y), +} + +class SamplerateError(Exception): + pass + +class Decoder(srd.Decoder): + api_version = 2 + id = 'jitter' + name = 'Jitter' + longname = 'Timing jitter calculation' + desc = 'Retrieves the timing jitter between two digital signals.' + license = 'gplv2+' + inputs = ['logic'] + outputs = ['jitter'] + channels = ( + {'id': 'clk', 'name': 'Clock', 'desc': 'Clock reference channel'}, + {'id': 'sig', 'name': 'Resulting signal', 'desc': 'Resulting signal controlled by the clock'}, + ) + options = ( + {'id': 'clk_polarity', 'desc': 'Clock edge polarity', + 'default': 'rising', 'values': ('rising', 'falling', 'both')}, + {'id': 'sig_polarity', 'desc': 'Resulting signal edge polarity', + 'default': 'rising', 'values': ('rising', 'falling', 'both')}, + ) + annotations = ( + ('jitter', 'Jitter value'), + ('clk_missed', 'Clock missed'), + ('sig_missed', 'Signal missed'), + ) + annotation_rows = ( + ('jitter', 'Jitter values', (0,)), + ('clk_missed', 'Clock missed', (1,)), + ('sig_missed', 'Signal missed', (2,)), + ) + binary = ( + ('ascii-float', 'Jitter values as newline-separated ASCII floats'), + ) + + def __init__(self, **kwargs): + self.state = 'CLK' + self.samplerate = None + self.oldpin = None + self.oldclk = self.oldsig = None + self.clk_start = None + self.sig_start = None + self.clk_missed = 0 + self.sig_missed = 0 + + def start(self): + self.clk_edge = edge_detector[self.options['clk_polarity']] + self.sig_edge = edge_detector[self.options['sig_polarity']] + self.out_ann = self.register(srd.OUTPUT_ANN) + self.out_bin = self.register(srd.OUTPUT_BINARY) + self.out_clk_missed = self.register(srd.OUTPUT_META, + meta=(int, 'Clock missed', 'Clock transition missed')) + self.out_sig_missed = self.register(srd.OUTPUT_META, + meta=(int, 'Signal missed', 'Resulting signal transition missed')) + + def metadata(self, key, value): + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + + # Helper function for jitter time annotations. + def putx(self, delta): + # Adjust granularity. + if delta == 0 or delta >= 1: + delta_s = u"%us" % (delta) + elif delta <= 1e-12: + delta_s = u"%.1ffs" % (delta * 1e15) + elif delta <= 1e-9: + delta_s = u"%.1fps" % (delta * 1e12) + elif delta <= 1e-6: + delta_s = u"%.1fns" % (delta * 1e9) + elif delta <= 1e-3: + delta_s = u"%.1fμs" % (delta * 1e6) + else: + delta_s = u"%.1fms" % (delta * 1e3) + + self.put(self.clk_start, self.sig_start, self.out_ann, [0, [delta_s]]) + + # Helper function for ASCII float jitter values (one value per line). + def putb(self, delta): + if delta is None: + return + # Format the delta to an ASCII float value terminated by a newline. + x = str(delta) + '\n' + self.put(self.clk_start, self.sig_start, self.out_bin, + (0, x.encode('UTF-8'))) + + # Helper function for missed clock and signal annotations. + def putm(self, data): + self.put(self.samplenum, self.samplenum, self.out_ann, data) + + def handle_clk(self, clk, sig): + if self.clk_start == self.samplenum: + # Clock transition already treated. + # We have done everything we can with this sample. + return True + + if self.clk_edge(self.oldclk, clk): + # Clock edge found. + # We note the sample and move to the next state. + self.clk_start = self.samplenum + self.state = 'SIG' + return False + else: + if self.sig_start is not None \ + and self.sig_start != self.samplenum \ + and self.sig_edge(self.oldsig, sig): + # If any transition in the resulting signal + # occurs while we are waiting for a clock, + # we increase the missed signal counter. + self.sig_missed += 1 + self.put(self.samplenum, self.samplenum, self.out_sig_missed, self.sig_missed) + self.putm([2, ['Missed signal', 'MS']]) + # No clock edge found, we have done everything we + # can with this sample. + return True + + def handle_sig(self, clk, sig): + if self.sig_start == self.samplenum: + # Signal transition already treated. + # We have done everything we can with this sample. + return True + + if self.sig_edge(self.oldsig, sig): + # Signal edge found. + # We note the sample, calculate the jitter + # and move to the next state. + self.sig_start = self.samplenum + self.state = 'CLK' + # Calculate and report the timing jitter. + delta = (self.sig_start - self.clk_start) / self.samplerate + self.putx(delta) + self.putb(delta) + return False + else: + if self.clk_start != self.samplenum \ + and self.clk_edge(self.oldclk, clk): + # If any transition in the clock signal + # occurs while we are waiting for a resulting + # signal, we increase the missed clock counter. + self.clk_missed += 1 + self.put(self.samplenum, self.samplenum, self.out_clk_missed, self.clk_missed) + self.putm([1, ['Missed clock', 'MC']]) + # No resulting signal edge found, we have done + # everything we can with this sample. + return True + + def decode(self, ss, es, data): + if not self.samplerate: + raise SamplerateError('Cannot decode without samplerate.') + + for (self.samplenum, pins) in data: + # We are only interested in transitions. + if self.oldpin == pins: + continue + + self.oldpin, (clk, sig) = pins, pins + + if self.oldclk is None and self.oldsig is None: + self.oldclk, self.oldsig = clk, sig + + # State machine: + # For each sample we can move 2 steps forward in the state machine. + while True: + # Clock state has the lead. + if self.state == 'CLK': + if self.handle_clk(clk, sig): + break + if self.state == 'SIG': + if self.handle_sig(clk, sig): + break + + # Save current CLK/SIG values for the next round. + self.oldclk, self.oldsig = clk, sig diff --git a/decoders/jtag/__init__.py b/decoders/jtag/__init__.py index 047e54a..863ef09 100644 --- a/decoders/jtag/__init__.py +++ b/decoders/jtag/__init__.py @@ -28,5 +28,4 @@ https://en.wikipedia.org/wiki/Joint_Test_Action_Group http://focus.ti.com/lit/an/ssya002c/ssya002c.pdf ''' -from .pd import * - +from .pd import Decoder diff --git a/decoders/jtag/pd.py b/decoders/jtag/pd.py index 25f2634..49077ed 100644 --- a/decoders/jtag/pd.py +++ b/decoders/jtag/pd.py @@ -139,14 +139,11 @@ class Decoder(srd.Decoder): elif self.state == 'UPDATE-IR': self.state = 'SELECT-DR-SCAN' if (tms) else 'RUN-TEST/IDLE' - else: - raise Exception('Invalid state: %s' % self.state) - def handle_rising_tck_edge(self, tdi, tdo, tck, tms): # Rising TCK edges always advance the state machine. self.advance_state_machine(tms) - if self.first == True: + if self.first: # Save the start sample and item for later (no output yet). self.ss_item = self.samplenum self.first = False @@ -217,4 +214,3 @@ class Decoder(srd.Decoder): self.handle_rising_tck_edge(tdi, tdo, tck, tms) self.oldtck = tck - diff --git a/decoders/jtag_stm32/__init__.py b/decoders/jtag_stm32/__init__.py index 8a28528..9d60c1c 100644 --- a/decoders/jtag_stm32/__init__.py +++ b/decoders/jtag_stm32/__init__.py @@ -27,5 +27,4 @@ https://en.wikipedia.org/wiki/STM32 http://www.st.com/internet/com/TECHNICAL_RESOURCES/TECHNICAL_LITERATURE/REFERENCE_MANUAL/CD00171190.pdf (e.g. chapter 31.7: "JTAG debug port") ''' -from .pd import * - +from .pd import Decoder diff --git a/decoders/jtag_stm32/pd.py b/decoders/jtag_stm32/pd.py index 5aac07f..f2dd3c7 100644 --- a/decoders/jtag_stm32/pd.py +++ b/decoders/jtag_stm32/pd.py @@ -218,6 +218,3 @@ class Decoder(srd.Decoder): handle_reg(cmd, val) if cmd == 'DR TDO': # TODO: Assumes 'DR TDI' comes before 'DR TDO' self.state = 'IDLE' - else: - raise Exception('Invalid state: %s' % self.state) - diff --git a/decoders/lm75/__init__.py b/decoders/lm75/__init__.py index d096418..1026df1 100644 --- a/decoders/lm75/__init__.py +++ b/decoders/lm75/__init__.py @@ -23,5 +23,4 @@ This decoder stacks on top of the 'i2c' PD and decodes the National LM75 (and compatibles) temperature sensor protocol. ''' -from .pd import * - +from .pd import Decoder diff --git a/decoders/lm75/pd.py b/decoders/lm75/pd.py index 89d6c61..6b2bfa8 100644 --- a/decoders/lm75/pd.py +++ b/decoders/lm75/pd.py @@ -76,7 +76,7 @@ class Decoder(srd.Decoder): def putb(self, data): # Helper for annotations which span a block of I²C packets. - self.put(self.block_start, self.block_end, self.out_ann, data) + self.put(self.ss_block, self.es_block, self.out_ann, data) def warn_upon_invalid_slave(self, addr): # LM75 and compatible devices have a 7-bit I²C slave address where @@ -102,11 +102,11 @@ class Decoder(srd.Decoder): def handle_temperature_reg(self, b, s, rw): # Common helper for the temperature/T_HYST/T_OS registers. if len(self.databytes) == 0: - self.block_start = self.ss + self.ss_block = self.ss self.databytes.append(b) return self.databytes.append(b) - self.block_end = self.es + self.es_block = self.es self.output_temperature(s, rw) self.databytes = [] @@ -181,6 +181,3 @@ class Decoder(srd.Decoder): else: # self.putx([0, ['Ignoring: %s (data=%s)' % (cmd, databyte)]]) pass - else: - raise Exception('Invalid state: %s' % self.state) - diff --git a/decoders/lpc/__init__.py b/decoders/lpc/__init__.py index 62a4307..2c2c430 100644 --- a/decoders/lpc/__init__.py +++ b/decoders/lpc/__init__.py @@ -23,5 +23,4 @@ LPC (Low-Pin Count) is a protocol for low-bandwidth devices used on some PC mainboards, such as the "BIOS chip" or the so-called "Super I/O". ''' -from .pd import * - +from .pd import Decoder diff --git a/decoders/lpc/pd.py b/decoders/lpc/pd.py index 6e03966..5e25db4 100644 --- a/decoders/lpc/pd.py +++ b/decoders/lpc/pd.py @@ -346,7 +346,7 @@ class Decoder(srd.Decoder): if self.state == 'IDLE': # A valid LPC cycle starts with LFRAME# being asserted (low). if lframe != 0: - continue + continue self.ss_block = self.samplenum self.state = 'GET START' self.lad = -1 @@ -365,6 +365,3 @@ class Decoder(srd.Decoder): self.handle_get_data(lad, lad_bits) elif self.state == 'GET TAR2': self.handle_get_tar2(lad, lad_bits) - else: - raise Exception('Invalid state: %s' % self.state) - diff --git a/decoders/maxim_ds28ea00/__init__.py b/decoders/maxim_ds28ea00/__init__.py index 34d1d08..049f96a 100644 --- a/decoders/maxim_ds28ea00/__init__.py +++ b/decoders/maxim_ds28ea00/__init__.py @@ -23,5 +23,4 @@ This decoder stacks on top of the 'onewire_network' PD and decodes the Maxim DS28EA00 1-Wire digital thermometer protocol. ''' -from .pd import * - +from .pd import Decoder diff --git a/decoders/maxim_ds28ea00/pd.py b/decoders/maxim_ds28ea00/pd.py index 4693cd9..229331f 100644 --- a/decoders/maxim_ds28ea00/pd.py +++ b/decoders/maxim_ds28ea00/pd.py @@ -88,6 +88,3 @@ class Decoder(srd.Decoder): self.putx([0, ['Temperature conversion status: 0x%02x' % val]]) elif self.state in [s.upper() for s in command.values()]: self.putx([0, ['TODO \'%s\': 0x%02x' % (self.state, val)]]) - else: - raise Exception('Invalid state: %s' % self.state) - diff --git a/decoders/mdio/__init__.py b/decoders/mdio/__init__.py new file mode 100644 index 0000000..d9028a3 --- /dev/null +++ b/decoders/mdio/__init__.py @@ -0,0 +1,29 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 Aurelien Jacobs +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU 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 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). + +MDIO is also known as SMI (Serial Management Interface). +''' + +from .pd import Decoder diff --git a/decoders/mdio/pd.py b/decoders/mdio/pd.py new file mode 100644 index 0000000..bb1f53f --- /dev/null +++ b/decoders/mdio/pd.py @@ -0,0 +1,260 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 Aurelien Jacobs +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU 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 = 'mdio' + name = 'MDIO' + longname = 'Management Data Input/Output' + desc = 'Half-duplex sync serial bus for MII management between MAC and PHY.' + license = 'gplv2+' + inputs = ['logic'] + outputs = ['mdio'] + channels = ( + {'id': 'mdc', 'name': 'MDC', 'desc': 'Clock'}, + {'id': 'mdio', 'name': 'MDIO', 'desc': 'Data'}, + ) + annotations = ( + ('mdio-data', 'MDIO data'), + ('mdio-bits', 'MDIO bits'), + ('errors', 'Human-readable errors'), + ) + annotation_rows = ( + ('mdio-data', 'MDIO data', (0,)), + ('mdio-bits', 'MDIO bits', (1,)), + ('other', 'Other', (2,)), + ) + + def __init__(self): + self.oldmdc = 0 + self.ss_block = -1 + self.samplenum = -1 + self.oldpins = None + 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, start, stop): + # Bit annotations. + self.put(start, stop, self.out_ann, [1, ['%d' % mdio]]) + + 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]]) + + 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' + + def parse_start(self, mdio): + if self.ss_start == -1: + if mdio != 0: + self.error = 'Invalid start bits: should be 01' + self.state = 'ERROR' + else: + self.ss_start = self.samplenum + else: + if mdio != 1: + self.error = 'Invalid start bits: should be 01' + self.state = 'ERROR' + 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' + 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' + else: + self.ss_turnaround = self.samplenum + 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) + self.putdata() + self.reset_decoder_state() + + # Ignore sample if the clock pin hasn't changed. + if mdc == self.oldmdc: + return + + self.oldmdc = mdc + + if mdc == 0: # Sample on rising clock edge. + return + + # Found the correct clock edge, now get/handle the bit(s). + self.clocksample = self.samplenum + self.handle_bit(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: + continue + self.oldpins, (mdc, mdio) = pins, pins + + self.find_mdc_edge(mdc, mdio) diff --git a/decoders/midi/__init__.py b/decoders/midi/__init__.py index a453346..4dbcf96 100644 --- a/decoders/midi/__init__.py +++ b/decoders/midi/__init__.py @@ -26,5 +26,4 @@ MIDI is layered on top of the UART (async serial) protocol, with a fixed baud rate of 31250 baud (+/- 1%) and 8n1 settings. Bytes are sent LSB-first. ''' -from .pd import * - +from .pd import Decoder diff --git a/decoders/midi/lists.py b/decoders/midi/lists.py index bc0f9b8..c72f5c9 100644 --- a/decoders/midi/lists.py +++ b/decoders/midi/lists.py @@ -267,7 +267,7 @@ sysex_manufacturer_ids = { (0x00, 0x00, 0x5c): 'AT&T Bell Labs', (0x00, 0x00, 0x5e): 'Symetrix', (0x00, 0x00, 0x5f): 'MIDI the World', - + (0x00, 0x00, 0x60): 'Desper Products', (0x00, 0x00, 0x61): 'Micros\'N MIDI', (0x00, 0x00, 0x62): 'Accordians Intl', @@ -467,4 +467,3 @@ control_functions = { 0x7e: 'poly mode off', # mono mode on, all notes off 0x7f: 'poly mode on', # mono mode off, all notes off } - diff --git a/decoders/midi/pd.py b/decoders/midi/pd.py index b1f0053..5915976 100644 --- a/decoders/midi/pd.py +++ b/decoders/midi/pd.py @@ -70,7 +70,7 @@ class Decoder(srd.Decoder): c = self.cmd if len(c) < 3: return - self.es_block = self.ss + 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)' % \ @@ -162,7 +162,7 @@ class Decoder(srd.Decoder): def handle_sysrealtime_msg(self, newbyte): # System realtime message: 0b11111ttt (t = message type) - self.es_block = self.ss + self.es_block = self.es self.putx([0, ['System realtime message: %s' % status_bytes[newbyte]]]) self.cmd, self.state = [], 'IDLE' @@ -175,6 +175,9 @@ class Decoder(srd.Decoder): self.ss, self.es = ss, es + # We're only interested in the byte value (not individual bits). + pdata = pdata[0] + # Short MIDI overview: # - Status bytes are 0x80-0xff, data bytes are 0x00-0x7f. # - Most messages: 1 status byte, 1-2 data bytes. @@ -206,6 +209,3 @@ class Decoder(srd.Decoder): self.handle_syscommon_msg(pdata) elif self.state == 'HANDLE SYSREALTIME MSG': self.handle_sysrealtime_msg(pdata) - else: - raise Exception('Invalid state: %s' % self.state) - diff --git a/decoders/mlx90614/__init__.py b/decoders/mlx90614/__init__.py index 29cb2a9..e985c91 100644 --- a/decoders/mlx90614/__init__.py +++ b/decoders/mlx90614/__init__.py @@ -23,5 +23,4 @@ This decoder stacks on top of the 'i2c' PD and decodes the Melexis MLX90614 infrared thermometer protocol. ''' -from .pd import * - +from .pd import Decoder diff --git a/decoders/mlx90614/pd.py b/decoders/mlx90614/pd.py index edc770d..aa1ead5 100644 --- a/decoders/mlx90614/pd.py +++ b/decoders/mlx90614/pd.py @@ -73,6 +73,3 @@ class Decoder(srd.Decoder): self.putx([1, ['Temperature: %3.2f K' % kelvin]]) self.state = 'IGNORE START REPEAT' self.data = [] - else: - raise Exception('Invalid state: %s' % self.state) - diff --git a/decoders/mrf24j40/__init__.py b/decoders/mrf24j40/__init__.py new file mode 100644 index 0000000..f0820bd --- /dev/null +++ b/decoders/mrf24j40/__init__.py @@ -0,0 +1,26 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2015 Karl Palsson +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, 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 Microchip MRF24J40 +IEEE 802.15.4 2.4 GHz RF tranceiver commands and data. +''' + +from .pd import Decoder diff --git a/decoders/mrf24j40/lists.py b/decoders/mrf24j40/lists.py new file mode 100644 index 0000000..c81975b --- /dev/null +++ b/decoders/mrf24j40/lists.py @@ -0,0 +1,166 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2015 Karl Palsson +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + +sregs = { + 0: 'RXMCR', + 1: 'PANIDL', + 2: 'PANIDH', + 3: 'SADRL', + 4: 'SADRH', + 5: 'EADR0', + 6: 'EADR1', + 7: 'EADR2', + 8: 'EADR3', + 9: 'EADR4', + 0xa: 'EADR5', + 0xb: 'EADR6', + 0xc: 'EADR7', + 0xd: 'RXFLUSH', + 0xe: 'Reserved', + 0xf: 'Reserved', + 0x10: 'ORDER', + 0x11: 'TXMCR', + 0x12: 'ACKTMOUT', + 0x13: 'ESLOTG1', + 0x14: 'SYMTICKL', + 0x15: 'SYMTICKH', + 0x16: 'PACON0', + 0x17: 'PACON1', + 0x18: 'PACON2', + 0x19: 'Reserved', + 0x1a: 'TXBCON0', + 0x1b: 'TXNCON', + 0x1c: 'TXG1CON', + 0x1d: 'TXG2CON', + 0x1e: 'ESLOTG23', + 0x1f: 'ESLOTG45', + 0x20: 'ESLOTG67', + 0x21: 'TXPEND', + 0x22: 'WAKECON', + 0x23: 'FRMOFFSET', + 0x24: 'TXSTAT', + 0x25: 'TXBCON1', + 0x26: 'GATECLK', + 0x27: 'TXTIME', + 0x28: 'HSYMTIMRL', + 0x29: 'HSYMTIMRH', + 0x2a: 'SOFTRST', + 0x2b: 'Reserved', + 0x2c: 'SECCON0', + 0x2d: 'SECCON1', + 0x2e: 'TXSTBL', + 0x3f: 'Reserved', + 0x30: 'RXSR', + 0x31: 'INTSTAT', + 0x32: 'INTCON', + 0x33: 'GPIO', + 0x34: 'TRISGPIO', + 0x35: 'SLPACK', + 0x36: 'RFCTL', + 0x37: 'SECCR2', + 0x38: 'BBREG0', + 0x39: 'BBREG1', + 0x3a: 'BBREG2', + 0x3b: 'BBREG3', + 0x3c: 'BBREG4', + 0x3d: 'Reserved', + 0x3e: 'BBREG6', + 0x3f: 'CCAEDTH', +} + +lregs = { + 0x200: 'RFCON0', + 0x201: 'RFCON1', + 0x202: 'RFCON2', + 0x203: 'RFCON3', + 0x204: 'Reserved', + 0x205: 'RFCON5', + 0x206: 'RFCON6', + 0x207: 'RFCON7', + 0x208: 'RFCON8', + 0x209: 'SLPCAL0', + 0x20A: 'SLPCAL1', + 0x20B: 'SLPCAL2', + 0x20C: 'Reserved', + 0x20D: 'Reserved', + 0x20E: 'Reserved', + 0x20F: 'RFSTATE', + 0x210: 'RSSI', + 0x211: 'SLPCON0', + 0x212: 'Reserved', + 0x213: 'Reserved', + 0x214: 'Reserved', + 0x215: 'Reserved', + 0x216: 'Reserved', + 0x217: 'Reserved', + 0x218: 'Reserved', + 0x219: 'Reserved', + 0x21A: 'Reserved', + 0x21B: 'Reserved', + 0x21C: 'Reserved', + 0x21D: 'Reserved', + 0x21E: 'Reserved', + 0x21F: 'Reserved', + 0x220: 'SLPCON1', + 0x221: 'Reserved', + 0x222: 'WAKETIMEL', + 0x223: 'WAKETIMEH', + 0x224: 'REMCNTL', + 0x225: 'REMCNTH', + 0x226: 'MAINCNT0', + 0x227: 'MAINCNT1', + 0x228: 'MAINCNT2', + 0x229: 'MAINCNT3', + 0x22A: 'Reserved', + 0x22B: 'Reserved', + 0x22C: 'Reserved', + 0x22D: 'Reserved', + 0x22E: 'Reserved', + 0x22F: 'TESTMODE', + 0x230: 'ASSOEADR0', + 0x231: 'ASSOEADR1', + 0x232: 'ASSOEADR2', + 0x233: 'ASSOEADR3', + 0x234: 'ASSOEADR4', + 0x235: 'ASSOEADR5', + 0x236: 'ASSOEADR6', + 0x237: 'ASSOEADR7', + 0x238: 'ASSOSADR0', + 0x239: 'ASSOSADR1', + 0x23A: 'Reserved', + 0x23B: 'Reserved', + 0x23C: 'Unimplemented', + 0x23D: 'Unimplemented', + 0x23E: 'Unimplemented', + 0x23F: 'Unimplemented', + 0x240: 'UPNONCE0', + 0x241: 'UPNONCE1', + 0x242: 'UPNONCE2', + 0x243: 'UPNONCE3', + 0x244: 'UPNONCE4', + 0x245: 'UPNONCE5', + 0x246: 'UPNONCE6', + 0x247: 'UPNONCE7', + 0x248: 'UPNONCE8', + 0x249: 'UPNONCE9', + 0x24A: 'UPNONCE10', + 0x24B: 'UPNONCE11', + 0x24C: 'UPNONCE12' +} diff --git a/decoders/mrf24j40/pd.py b/decoders/mrf24j40/pd.py new file mode 100644 index 0000000..286fa52 --- /dev/null +++ b/decoders/mrf24j40/pd.py @@ -0,0 +1,134 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2015 Karl Palsson +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + +import sigrokdecode as srd +from .lists import * + +class Decoder(srd.Decoder): + api_version = 2 + id = 'mrf24j40' + name = 'MRF24J40' + longname = 'Microchip MRF24J40' + desc = 'IEEE 802.15.4 2.4 GHz RF tranceiver chip.' + license = 'gplv2' + inputs = ['spi'] + outputs = ['mrf24j40'] + annotations = ( + ('sread', 'Short register read commands'), + ('swrite', 'Short register write commands'), + ('lread', 'Long register read commands'), + ('lwrite', 'Long register write commands'), + ('warning', 'Warnings'), + ) + annotation_rows = ( + ('read', 'Read', (0, 2)), + ('write', 'Write', (1, 3)), + ('warnings', 'Warnings', (4,)), + ) + + def __init__(self, **kwargs): + self.ss_cmd, self.es_cmd = 0, 0 + self.mosi_bytes = [] + self.miso_bytes = [] + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def putx(self, data): + self.put(self.ss_cmd, self.es_cmd, self.out_ann, data) + + def putw(self, pos, msg): + self.put(pos[0], pos[1], self.out_ann, [4, [msg]]) + + def reset(self): + self.mosi_bytes = [] + self.miso_bytes = [] + + def handle_short(self): + write = self.mosi_bytes[0] & 0x1 + reg = (self.mosi_bytes[0] >> 1) & 0x3f + reg_desc = sregs.get(reg, 'illegal') + if write: + self.putx([1, ['%s: %#x' % (reg_desc, self.mosi_bytes[1])]]) + else: + self.putx([0, ['%s: %#x' % (reg_desc, self.miso_bytes[1])]]) + + def handle_long(self): + dword = self.mosi_bytes[0] << 8 | self.mosi_bytes[1] + write = dword & (0x1 << 4) + reg = dword >> 5 & 0x3ff + if reg >= 0x0: + reg_desc = 'TX:%#x' % reg + if reg >= 0x80: + reg_desc = 'TX beacon:%#x' % reg + if reg >= 0x100: + reg_desc = 'TX GTS1:%#x' % reg + if reg >= 0x180: + reg_desc = 'TX GTS2:%#x' % reg + if reg >= 0x200: + reg_desc = lregs.get(reg, 'illegal') + if reg >= 0x280: + reg_desc = 'Security keys:%#x' % reg + if reg >= 0x2c0: + reg_desc = 'Reserved:%#x' % reg + if reg >= 0x300: + reg_desc = 'RX:%#x' % reg + + if write: + self.putx([3, ['%s: %#x' % (reg_desc, self.mosi_bytes[2])]]) + else: + self.putx([2, ['%s: %#x' % (reg_desc, self.miso_bytes[2])]]) + + def decode(self, ss, es, data): + ptype = data[0] + if ptype == 'CS-CHANGE': + # If we transition high mid-stream, toss out our data and restart. + cs_old, cs_new = data[1:] + if cs_old is not None and cs_old == 0 and cs_new == 1: + if len(self.mosi_bytes) not in (0, 2, 3): + self.putw([self.ss_cmd, es], 'Misplaced CS!') + self.reset() + return + + # Don't care about anything else. + if ptype != 'DATA': + return + mosi, miso = data[1:] + + self.ss, self.es = ss, es + + if len(self.mosi_bytes) == 0: + self.ss_cmd = ss + self.mosi_bytes.append(mosi) + self.miso_bytes.append(miso) + + # Everything is either 2 bytes or 3 bytes. + if len(self.mosi_bytes) < 2: + return + + if self.mosi_bytes[0] & 0x80: + if len(self.mosi_bytes) == 3: + self.es_cmd = es + self.handle_long() + self.reset() + else: + self.es_cmd = es + self.handle_short() + self.reset() diff --git a/decoders/mx25lxx05d/__init__.py b/decoders/mx25lxx05d/__init__.py deleted file mode 100644 index 71d7b3c..0000000 --- a/decoders/mx25lxx05d/__init__.py +++ /dev/null @@ -1,32 +0,0 @@ -## -## This file is part of the libsigrokdecode project. -## -## Copyright (C) 2012 Uwe Hermann -## -## This program is free software; you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published by -## the Free Software Foundation; either version 2 of the License, or -## (at your option) any later version. -## -## This program is distributed in the hope that it will be useful, -## but WITHOUT ANY WARRANTY; without even the implied warranty of -## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -## GNU General Public License for more details. -## -## You should have received a copy of the GNU General Public License -## along with this program; if not, 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 Macronix -MX25Lxx05D SPI (NOR) flash chip protocol. - -It works for the MX25L1605D/MX25L3205D/MX25L6405D. - -Details: -http://www.macronix.com/QuickPlace/hq/PageLibrary4825740B00298A3B.nsf/h_Index/3F21BAC2E121E17848257639003A3146/$File/MX25L1605D-3205D-6405D-1.5.pdf -''' - -from .pd import * - diff --git a/decoders/mx25lxx05d/pd.py b/decoders/mx25lxx05d/pd.py deleted file mode 100644 index a6c72d8..0000000 --- a/decoders/mx25lxx05d/pd.py +++ /dev/null @@ -1,375 +0,0 @@ -## -## This file is part of the libsigrokdecode project. -## -## Copyright (C) 2011-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 -## - -import sigrokdecode as srd - -# 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#'), -} - -device_name = { - 0x14: 'MX25L1605D', - 0x15: 'MX25L3205D', - 0x16: 'MX25L6405D', -} - -def cmd_annotation_classes(): - return tuple([tuple([cmd[0].lower(), cmd[1]]) for cmd in cmds.values()]) - -def decode_status_reg(data): - # TODO: Additional per-bit(s) self.put() calls with correct start/end. - - # Bits[0:0]: WIP (write in progress) - s = 'W' if (data & (1 << 0)) else 'No w' - ret = '%srite operation in progress.\n' % s - - # Bits[1:1]: WEL (write enable latch) - s = '' if (data & (1 << 1)) else 'not ' - ret += 'Internal write enable latch is %sset.\n' % s - - # Bits[5:2]: Block protect bits - # TODO: More detailed decoding (chip-dependent). - ret += 'Block protection bits (BP3-BP0): 0x%x.\n' % ((data & 0x3c) >> 2) - - # Bits[6:6]: Continuously program mode (CP mode) - s = '' if (data & (1 << 6)) else 'not ' - ret += 'Device is %sin continuously program mode (CP mode).\n' % s - - # Bits[7:7]: SRWD (status register write disable) - s = 'not ' if (data & (1 << 7)) else '' - ret += 'Status register writes are %sallowed.\n' % s - - return ret - -class Decoder(srd.Decoder): - api_version = 2 - id = 'mx25lxx05d' - name = 'MX25Lxx05D' - longname = 'Macronix MX25Lxx05D' - desc = 'SPI (NOR) flash chip protocol.' - license = 'gplv2+' - inputs = ['logic'] - outputs = ['mx25lxx05d'] - annotations = cmd_annotation_classes() + ( - ('bits', 'Bits'), - ('bits2', 'Bits2'), - ('warnings', 'Warnings'), - ) - annotation_rows = ( - ('bits', 'Bits', (24, 25)), - ('commands', 'Commands', tuple(range(23 + 1))), - ('warnings', 'Warnings', (26,)), - ) - - def __init__(self, **kwargs): - self.state = None - self.cmdstate = 1 - self.addr = 0 - self.data = [] - - def start(self): - self.out_ann = self.register(srd.OUTPUT_ANN) - - def putx(self, data): - # Simplification, most annotations span exactly one SPI byte/packet. - self.put(self.ss, self.es, self.out_ann, data) - - def handle_wren(self, mosi, miso): - self.putx([0, ['Command: %s' % cmds[self.state][1]]]) - 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.start_sample = self.ss - self.putx([2, ['Command: %s' % cmds[self.state][1]]]) - elif self.cmdstate == 2: - # Byte 2: Slave sends the JEDEC manufacturer ID. - self.putx([2, ['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]]) - elif self.cmdstate == 4: - # Byte 4: Slave sends the device ID. - self.device_id = miso - self.putx([2, ['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.start_sample, self.es, self.out_ann, [0, [d]]) - self.state = None - else: - self.cmdstate += 1 - - def handle_rdsr(self, mosi, miso): - # Read status register: Master asserts CS#, sends RDSR command, - # reads status register byte. If CS# is kept asserted, the status - # register can be read continuously / multiple times in a row. - # When done, the master de-asserts CS# again. - if self.cmdstate == 1: - # Byte 1: Master sends command ID. - self.putx([3, ['Command: %s' % cmds[self.state][1]]]) - 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.cmdstate += 1 - - def handle_wrsr(self, mosi, miso): - pass # TODO - - 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]]]) - 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 - 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.cmdstate += 1 - - def handle_fast_read(self, mosi, miso): - pass # TODO - - def handle_2read(self, mosi, miso): - pass # TODO - - # 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.start_sample = self.ss - self.putx([8, ['Command: %s' % cmds[self.state][1]]]) - elif self.cmdstate in (2, 3, 4): - # Bytes 2/3/4: Master sends sectror address (24bits, MSB-first). - self.addr |= (mosi << ((4 - self.cmdstate) * 8)) - # self.putx([0, ['Sector address, byte %d: 0x%02x' % \ - # (4 - self.cmdstate, mosi)]]) - - if self.cmdstate == 4: - d = 'Erase sector %d (0x%06x)' % (self.addr, self.addr) - self.put(self.start_sample, self.es, self.out_ann, [24, [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.start_sample, self.es, self.out_ann, [101, [d]]) - self.state = None - else: - self.cmdstate += 1 - - def handle_be(self, mosi, miso): - pass # TODO - - def handle_ce(self, mosi, miso): - pass # TODO - - def handle_ce2(self, mosi, miso): - pass # TODO - - def handle_pp(self, mosi, miso): - # Page program: Master asserts CS#, sends PP command, sends 3-byte - # page address, sends >= 1 data bytes, de-asserts CS#. - if self.cmdstate == 1: - # Byte 1: Master sends command ID. - self.putx([12, ['Command: %s' % cmds[self.state][1]]]) - 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 - 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.cmdstate += 1 - - def handle_cp(self, mosi, miso): - pass # TODO - - def handle_dp(self, mosi, miso): - pass # TODO - - def handle_rdp_res(self, mosi, miso): - pass # TODO - - def handle_rems(self, mosi, miso): - if self.cmdstate == 1: - # Byte 1: Master sends command ID. - self.start_sample = self.ss - self.putx([16, ['Command: %s' % cmds[self.state][1]]]) - 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]]) - 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]]) - 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]]) - 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]]) - - if self.cmdstate == 6: - self.end_sample = self.es - id = self.ids[1] if self.manufacturer_id_first else self.ids[0] - self.putx([24, ['Device: Macronix %s' % device_name[id]]]) - self.state = None - else: - self.cmdstate += 1 - - def handle_rems2(self, mosi, miso): - pass # TODO - - def handle_enso(self, mosi, miso): - pass # TODO - - def handle_exso(self, mosi, miso): - pass # TODO - - def handle_rdscur(self, mosi, miso): - pass # TODO - - def handle_wrscur(self, mosi, miso): - pass # TODO - - def handle_esry(self, mosi, miso): - pass # TODO - - def handle_dsry(self, mosi, miso): - pass # TODO - - def decode(self, ss, es, data): - - ptype, mosi, miso = data - - # if ptype == 'DATA': - # self.putx([0, ['MOSI: 0x%02x, MISO: 0x%02x' % (mosi, miso)]]) - - # 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 != 'DATA': - return - - self.ss, self.es = ss, es - - # If we encountered a known chip command, enter the resp. state. - if self.state == 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]]) - self.state = None - diff --git a/decoders/mxc6225xu/__init__.py b/decoders/mxc6225xu/__init__.py index c4209c8..daeb89e 100644 --- a/decoders/mxc6225xu/__init__.py +++ b/decoders/mxc6225xu/__init__.py @@ -26,5 +26,4 @@ The chip's I²C interface supports standard mode and fast mode (max. 400kHz). Its I²C slave address is 0x2a. ''' -from .pd import * - +from .pd import Decoder diff --git a/decoders/mxc6225xu/pd.py b/decoders/mxc6225xu/pd.py index e0c857f..962f963 100644 --- a/decoders/mxc6225xu/pd.py +++ b/decoders/mxc6225xu/pd.py @@ -97,7 +97,7 @@ class Decoder(srd.Decoder): # Bits[7:7]: INT int_val = (b >> 7) & 1 s = 'unchanged and no' if (int_val == 0) else 'changed or' - ann = 'INT = %d: Orientation %s shake event occured\n' % (int_val, s) + ann = 'INT = %d: Orientation %s shake event occurred\n' % (int_val, s) # Bits[6:5]: SH[1:0] sh = (((b >> 6) & 1) << 1) | ((b >> 5) & 1) @@ -165,7 +165,6 @@ class Decoder(srd.Decoder): if cmd != 'START': return self.state = 'GET SLAVE ADDR' - self.block_start_sample = ss elif self.state == 'GET SLAVE ADDR': # Wait for an address write operation. # TODO: We should only handle packets to the slave(?) @@ -213,6 +212,3 @@ class Decoder(srd.Decoder): self.state = 'IDLE' else: pass # TODO? - else: - raise Exception('Invalid state: %s' % self.state) - diff --git a/decoders/nrf24l01/__init__.py b/decoders/nrf24l01/__init__.py new file mode 100644 index 0000000..7b4d748 --- /dev/null +++ b/decoders/nrf24l01/__init__.py @@ -0,0 +1,30 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 Jens Steinhauser +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, 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 protocol spoken +by the Nordic Semiconductor nRF24L01 and nRF24L01+ 2.4GHz transceiver chips. + +Details: +http://www.nordicsemi.com/eng/Products/2.4GHz-RF/nRF24L01 +http://www.nordicsemi.com/eng/Products/2.4GHz-RF/nRF24L01P +''' + +from .pd import Decoder diff --git a/decoders/nrf24l01/pd.py b/decoders/nrf24l01/pd.py new file mode 100644 index 0000000..2337d4b --- /dev/null +++ b/decoders/nrf24l01/pd.py @@ -0,0 +1,329 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 Jens Steinhauser +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + +import sigrokdecode as srd + +class ChannelError(Exception): + pass + +regs = { +# addr: ('name', size) + 0x00: ('CONFIG', 1), + 0x01: ('EN_AA', 1), + 0x02: ('EN_RXADDR', 1), + 0x03: ('SETUP_AW', 1), + 0x04: ('SETUP_RETR', 1), + 0x05: ('RF_CH', 1), + 0x06: ('RF_SETUP', 1), + 0x07: ('STATUS', 1), + 0x08: ('OBSERVE_TX', 1), + 0x09: ('RPD', 1), + 0x0a: ('RX_ADDR_P0', 5), + 0x0b: ('RX_ADDR_P1', 5), + 0x0c: ('RX_ADDR_P2', 1), + 0x0d: ('RX_ADDR_P3', 1), + 0x0e: ('RX_ADDR_P4', 1), + 0x0f: ('RX_ADDR_P5', 1), + 0x10: ('TX_ADDR', 5), + 0x11: ('RX_PW_P0', 1), + 0x12: ('RX_PW_P1', 1), + 0x13: ('RX_PW_P2', 1), + 0x14: ('RX_PW_P3', 1), + 0x15: ('RX_PW_P4', 1), + 0x16: ('RX_PW_P5', 1), + 0x17: ('FIFO_STATUS', 1), + 0x1c: ('DYNPD', 1), + 0x1d: ('FEATURE', 1), +} + +xn297_regs = { + 0x19: ('DEMOD_CAL', 5), + 0x1e: ('RF_CAL', 7), + 0x1f: ('BB_CAL', 5), +} + +class Decoder(srd.Decoder): + api_version = 2 + id = 'nrf24l01' + name = 'nRF24L01(+)' + longname = 'Nordic Semiconductor nRF24L01/nRF24L01+' + desc = '2.4GHz transceiver chip.' + license = 'gplv2+' + inputs = ['spi'] + outputs = ['nrf24l01'] + options = ( + {'id': 'chip', 'desc': 'Chip type', + 'default': 'nrf24l01', 'values': ('nrf24l01', 'xn297')}, + ) + annotations = ( + # Sent from the host to the chip. + ('cmd', 'Commands sent to the device'), + ('tx-data', 'Payload sent to the device'), + + # Returned by the chip. + ('register', 'Registers read from the device'), + ('rx-data', 'Payload read from the device'), + + ('warning', 'Warnings'), + ) + ann_cmd = 0 + ann_tx = 1 + ann_reg = 2 + ann_rx = 3 + ann_warn = 4 + annotation_rows = ( + ('commands', 'Commands', (ann_cmd, ann_tx)), + ('responses', 'Responses', (ann_reg, ann_rx)), + ('warnings', 'Warnings', (ann_warn,)), + ) + + def __init__(self, **kwargs): + self.next() + self.requirements_met = True + self.cs_was_released = False + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + if self.options['chip'] == 'xn297': + regs.update(xn297_regs) + + def warn(self, pos, msg): + '''Put a warning message 'msg' at 'pos'.''' + self.put(pos[0], pos[1], self.out_ann, [self.ann_warn, [msg]]) + + def putp(self, pos, ann, msg): + '''Put an annotation message 'msg' at 'pos'.''' + self.put(pos[0], pos[1], self.out_ann, [ann, [msg]]) + + def next(self): + '''Resets the decoder after a complete command was decoded.''' + # 'True' for the first byte after CS went low. + self.first = True + + # The current command, and the minimum and maximum number + # of data bytes to follow. + self.cmd = None + self.min = 0 + self.max = 0 + + # Used to collect the bytes after the command byte + # (and the start/end sample number). + self.mb = [] + self.mb_s = -1 + self.mb_e = -1 + + def mosi_bytes(self): + '''Returns the collected MOSI bytes of a multi byte command.''' + return [b[0] for b in self.mb] + + def miso_bytes(self): + '''Returns the collected MISO bytes of a multi byte command.''' + return [b[1] for b in self.mb] + + def decode_command(self, pos, b): + '''Decodes the command byte 'b' at position 'pos' and prepares + the decoding of the following data bytes.''' + c = self.parse_command(b) + if c is None: + self.warn(pos, 'unknown command') + return + + self.cmd, self.dat, self.min, self.max = c + + if self.cmd in ('W_REGISTER', 'ACTIVATE'): + # Don't output anything now, the command is merged with + # the data bytes following it. + self.mb_s = pos[0] + else: + self.putp(pos, self.ann_cmd, self.format_command()) + + def format_command(self): + '''Returns the label for the current command.''' + if self.cmd == 'R_REGISTER': + reg = regs[self.dat][0] if self.dat in regs else 'unknown register' + return 'Cmd R_REGISTER "{}"'.format(reg) + else: + return 'Cmd {}'.format(self.cmd) + + def parse_command(self, b): + '''Parses the command byte. + + Returns a tuple consisting of: + - the name of the command + - additional data needed to dissect the following bytes + - minimum number of following bytes + - maximum number of following bytes + ''' + + if (b & 0xe0) in (0b00000000, 0b00100000): + c = 'R_REGISTER' if not (b & 0xe0) else 'W_REGISTER' + d = b & 0x1f + m = regs[d][1] if d in regs else 1 + return (c, d, 1, m) + if b == 0b01010000: + # nRF24L01 only + return ('ACTIVATE', None, 1, 1) + if b == 0b01100001: + return ('R_RX_PAYLOAD', None, 1, 32) + if b == 0b01100000: + return ('R_RX_PL_WID', None, 1, 1) + if b == 0b10100000: + return ('W_TX_PAYLOAD', None, 1, 32) + if b == 0b10110000: + return ('W_TX_PAYLOAD_NOACK', None, 1, 32) + if (b & 0xf8) == 0b10101000: + return ('W_ACK_PAYLOAD', b & 0x07, 1, 32) + if b == 0b11100001: + return ('FLUSH_TX', None, 0, 0) + if b == 0b11100010: + return ('FLUSH_RX', None, 0, 0) + if b == 0b11100011: + return ('REUSE_TX_PL', None, 0, 0) + if b == 0b11111111: + return ('NOP', None, 0, 0) + + def decode_register(self, pos, ann, regid, data): + '''Decodes a register. + + pos -- start and end sample numbers of the register + ann -- is the annotation number that is used to output the register. + regid -- may be either an integer used as a key for the 'regs' + dictionary, or a string directly containing a register name.' + data -- is the register content. + ''' + + if type(regid) == int: + # Get the name of the register. + if regid not in regs: + self.warn(pos, 'unknown register') + return + name = regs[regid][0] + else: + name = regid + + # Multi byte register come LSByte first. + data = reversed(data) + + if self.cmd == 'W_REGISTER' and ann == self.ann_cmd: + # The 'W_REGISTER' command is merged with the following byte(s). + label = '{}: {}'.format(self.format_command(), name) + else: + label = 'Reg {}'.format(name) + + self.decode_mb_data(pos, ann, data, label, True) + + def decode_mb_data(self, pos, ann, data, label, always_hex): + '''Decodes the data bytes 'data' of a multibyte command at position + 'pos'. The decoded data is prefixed with 'label'. If 'always_hex' is + True, all bytes are decoded as hex codes, otherwise only non + printable characters are escaped.''' + + if always_hex: + def escape(b): + return '{:02X}'.format(b) + else: + def escape(b): + c = chr(b) + if not str.isprintable(c): + return '\\x{:02X}'.format(b) + return c + + data = ''.join([escape(b) for b in data]) + text = '{} = "{}"'.format(label, data) + self.putp(pos, ann, text) + + def finish_command(self, pos): + '''Decodes the remaining data bytes at position 'pos'.''' + + if self.cmd == 'R_REGISTER': + self.decode_register(pos, self.ann_reg, + self.dat, self.miso_bytes()) + elif self.cmd == 'W_REGISTER': + self.decode_register(pos, self.ann_cmd, + self.dat, self.mosi_bytes()) + elif self.cmd == 'R_RX_PAYLOAD': + self.decode_mb_data(pos, self.ann_rx, + self.miso_bytes(), 'RX payload', False) + elif (self.cmd == 'W_TX_PAYLOAD' or + self.cmd == 'W_TX_PAYLOAD_NOACK'): + self.decode_mb_data(pos, self.ann_tx, + self.mosi_bytes(), 'TX payload', False) + elif self.cmd == 'W_ACK_PAYLOAD': + lbl = 'ACK payload for pipe {}'.format(self.dat) + self.decode_mb_data(pos, self.ann_tx, + self.mosi_bytes(), lbl, False) + elif self.cmd == 'R_RX_PL_WID': + msg = 'Payload width = {}'.format(self.mb[0][1]) + self.putp(pos, self.ann_reg, msg) + elif self.cmd == 'ACTIVATE': + self.putp(pos, self.ann_cmd, self.format_command()) + if self.mosi_bytes()[0] != 0x73: + self.warn(pos, 'wrong data for "ACTIVATE" command') + + def decode(self, ss, es, data): + if not self.requirements_met: + return + + ptype, data1, data2 = data + + if ptype == 'CS-CHANGE': + if data1 is None: + if data2 is None: + self.requirements_met = False + raise ChannelError('CS# pin required.') + elif data2 == 1: + self.cs_was_released = True + + if data1 == 0 and data2 == 1: + # Rising edge, the complete command is transmitted, process + # the bytes that were send after the command byte. + if self.cmd: + # Check if we got the minimum number of data bytes + # after the command byte. + if len(self.mb) < self.min: + self.warn((ss, ss), 'missing data bytes') + elif self.mb: + self.finish_command((self.mb_s, self.mb_e)) + + self.next() + self.cs_was_released = True + elif ptype == 'DATA' and self.cs_was_released: + mosi, miso = data1, data2 + pos = (ss, es) + + if miso is None or mosi is None: + self.requirements_met = False + raise ChannelError('Both MISO and MOSI pins required.') + + if self.first: + self.first = False + # First MOSI byte is always the command. + self.decode_command(pos, mosi) + # First MISO byte is always the status register. + self.decode_register(pos, self.ann_reg, 'STATUS', [miso]) + else: + if not self.cmd or len(self.mb) >= self.max: + self.warn(pos, 'excess byte') + else: + # Collect the bytes after the command byte. + if self.mb_s == -1: + self.mb_s = ss + self.mb_e = es + self.mb.append((mosi, miso)) diff --git a/decoders/nunchuk/__init__.py b/decoders/nunchuk/__init__.py index 2fdaaeb..c579145 100644 --- a/decoders/nunchuk/__init__.py +++ b/decoders/nunchuk/__init__.py @@ -28,5 +28,4 @@ http://todbot.com/blog/2008/02/18/wiichuck-wii-nunchuck-adapter-available/ https://www.sparkfun.com/products/9281 ''' -from .pd import * - +from .pd import Decoder diff --git a/decoders/nunchuk/pd.py b/decoders/nunchuk/pd.py index 95d7bdb..afcf1de 100644 --- a/decoders/nunchuk/pd.py +++ b/decoders/nunchuk/pd.py @@ -52,7 +52,7 @@ class Decoder(srd.Decoder): self.sx = self.sy = self.ax = self.ay = self.az = self.bz = self.bc = -1 self.databytecount = 0 self.reg = 0x00 - self.ss = self.es = self.block_ss = self.block_es = 0 + self.ss = self.es = self.ss_block = self.es_block = 0 self.init_seq = [] def start(self): @@ -62,13 +62,13 @@ class Decoder(srd.Decoder): 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) + self.put(self.ss_block, self.es_block, self.out_ann, data) def putd(self, bit1, bit2, data): self.put(self.bits[bit1][1], self.bits[bit2][2], self.out_ann, data) def handle_reg_0x00(self, databyte): - self.block_ss = self.ss + self.ss_block = self.ss self.sx = databyte self.putx([0, ['Analog stick X position: 0x%02X' % self.sx, 'SX: 0x%02X' % self.sx]]) @@ -94,7 +94,7 @@ class Decoder(srd.Decoder): 'AZ[9:2]: 0x%03X' % self.az]]) def handle_reg_0x05(self, databyte): - self.block_es = self.es + self.es_block = self.es self.bz = (databyte & (1 << 0)) >> 0 # Bits[0:0] self.bc = (databyte & (1 << 1)) >> 1 # Bits[1:1] ax_rest = (databyte & (3 << 2)) >> 2 # Bits[3:2] @@ -170,7 +170,7 @@ class Decoder(srd.Decoder): if cmd != 'START': return self.state = 'GET SLAVE ADDR' - self.block_start_sample = ss + self.ss_block = ss elif self.state == 'GET SLAVE ADDR': # Wait for an address read/write operation. if cmd == 'ADDRESS READ': @@ -183,7 +183,7 @@ class Decoder(srd.Decoder): handle_reg(databyte) self.reg += 1 elif cmd == 'STOP': - self.block_end_sample = es + self.es_block = es self.output_full_block_if_possible() self.sx = self.sy = self.ax = self.ay = self.az = -1 self.bz = self.bc = -1 @@ -195,13 +195,10 @@ class Decoder(srd.Decoder): if cmd == 'DATA WRITE': self.handle_reg_write(databyte) elif cmd == 'STOP': - self.block_end_sample = es + self.es_block = es self.output_init_seq() self.init_seq = [] self.state = 'IDLE' else: # self.putx([14, ['Ignoring: %s (data=%s)' % (cmd, databyte)]]) pass - else: - raise Exception('Invalid state: %s' % self.state) - diff --git a/decoders/onewire_link/__init__.py b/decoders/onewire_link/__init__.py index d153f93..12aad25 100644 --- a/decoders/onewire_link/__init__.py +++ b/decoders/onewire_link/__init__.py @@ -65,5 +65,4 @@ These options should be configured only on very rare cases and the user should read the decoder source code to understand them correctly. ''' -from .pd import * - +from .pd import Decoder diff --git a/decoders/onewire_link/pd.py b/decoders/onewire_link/pd.py index 3641b98..2be0242 100644 --- a/decoders/onewire_link/pd.py +++ b/decoders/onewire_link/pd.py @@ -20,6 +20,9 @@ import sigrokdecode as srd +class SamplerateError(Exception): + pass + class Decoder(srd.Decoder): api_version = 2 id = 'onewire_link' @@ -106,11 +109,7 @@ class Decoder(srd.Decoder): self.out_python = self.register(srd.OUTPUT_PYTHON) self.out_ann = self.register(srd.OUTPUT_ANN) - def metadata(self, key, value): - if key != srd.SRD_CONF_SAMPLERATE: - return - self.samplerate = value - + def checks(self): # Check if samplerate is appropriate. if self.options['overdrive'] == 'yes': if self.samplerate < 2000000: @@ -123,37 +122,10 @@ class Decoder(srd.Decoder): if self.samplerate < 400000: self.putm([1, ['Sampling rate is too low. Must be above ' + '400kHz for proper normal mode decoding.']]) - elif (self.samplerate < 1000000): + elif self.samplerate < 1000000: self.putm([1, ['Sampling rate is suggested to be above ' + '1MHz for proper normal mode decoding.']]) - # The default 1-Wire time base is 30us. This is used to calculate - # sampling times. - samplerate = float(self.samplerate) - - x = float(self.options['cnt_normal_bit']) / 1000000.0 - self.cnt_normal_bit = int(samplerate * x) - 1 - x = float(self.options['cnt_normal_slot']) / 1000000.0 - self.cnt_normal_slot = int(samplerate * x) - 1 - x = float(self.options['cnt_normal_presence']) / 1000000.0 - self.cnt_normal_presence = int(samplerate * x) - 1 - x = float(self.options['cnt_normal_reset']) / 1000000.0 - self.cnt_normal_reset = int(samplerate * x) - 1 - x = float(self.options['cnt_overdrive_bit']) / 1000000.0 - self.cnt_overdrive_bit = int(samplerate * x) - 1 - x = float(self.options['cnt_overdrive_slot']) / 1000000.0 - self.cnt_overdrive_slot = int(samplerate * x) - 1 - x = float(self.options['cnt_overdrive_presence']) / 1000000.0 - self.cnt_overdrive_presence = int(samplerate * x) - 1 - x = float(self.options['cnt_overdrive_reset']) / 1000000.0 - self.cnt_overdrive_reset = int(samplerate * x) - 1 - - # Organize values into lists. - self.cnt_bit = [self.cnt_normal_bit, self.cnt_overdrive_bit] - self.cnt_presence = [self.cnt_normal_presence, self.cnt_overdrive_presence] - self.cnt_reset = [self.cnt_normal_reset, self.cnt_overdrive_reset] - self.cnt_slot = [self.cnt_normal_slot, self.cnt_overdrive_slot] - # Check if sample times are in the allowed range. time_min = float(self.cnt_normal_bit) / self.samplerate @@ -182,12 +154,47 @@ class Decoder(srd.Decoder): if (time_min < 0.0000073) or (time_max > 0.000010): self.putm([1, ['The overdrive mode presence sample time interval ' + '(%2.1fus-%2.1fus) should be inside (7.3us, 10.0us).' - % (time_min*1000000, time_max*1000000)]]) + % (time_min * 1000000, time_max * 1000000)]]) + + + def metadata(self, key, value): + if key != srd.SRD_CONF_SAMPLERATE: + return + self.samplerate = value + + # The default 1-Wire time base is 30us. This is used to calculate + # sampling times. + samplerate = float(self.samplerate) + + x = float(self.options['cnt_normal_bit']) / 1000000.0 + self.cnt_normal_bit = int(samplerate * x) - 1 + x = float(self.options['cnt_normal_slot']) / 1000000.0 + self.cnt_normal_slot = int(samplerate * x) - 1 + x = float(self.options['cnt_normal_presence']) / 1000000.0 + self.cnt_normal_presence = int(samplerate * x) - 1 + x = float(self.options['cnt_normal_reset']) / 1000000.0 + self.cnt_normal_reset = int(samplerate * x) - 1 + x = float(self.options['cnt_overdrive_bit']) / 1000000.0 + self.cnt_overdrive_bit = int(samplerate * x) - 1 + x = float(self.options['cnt_overdrive_slot']) / 1000000.0 + self.cnt_overdrive_slot = int(samplerate * x) - 1 + x = float(self.options['cnt_overdrive_presence']) / 1000000.0 + self.cnt_overdrive_presence = int(samplerate * x) - 1 + x = float(self.options['cnt_overdrive_reset']) / 1000000.0 + self.cnt_overdrive_reset = int(samplerate * x) - 1 + + # Organize values into lists. + self.cnt_bit = [self.cnt_normal_bit, self.cnt_overdrive_bit] + self.cnt_presence = [self.cnt_normal_presence, self.cnt_overdrive_presence] + self.cnt_reset = [self.cnt_normal_reset, self.cnt_overdrive_reset] + self.cnt_slot = [self.cnt_normal_slot, self.cnt_overdrive_slot] def decode(self, ss, es, data): - if self.samplerate is None: - raise Exception("Cannot decode without samplerate.") + if not self.samplerate: + raise SamplerateError('Cannot decode without samplerate.') for (self.samplenum, (owr, pwr)) in data: + if self.samplenum == 0: + self.checks() # State machine. if self.state == 'WAIT FOR FALLING EDGE': # The start of a cycle is a falling edge. @@ -251,10 +258,10 @@ class Decoder(srd.Decoder): # Save the sample number for the rising edge. self.rise = self.samplenum self.putfr([2, ['Reset', 'Rst', 'R']]) - self.state = "WAIT FOR PRESENCE DETECT" + self.state = 'WAIT FOR PRESENCE DETECT' # Otherwise this is assumed to be a data bit. else: - self.state = "WAIT FOR FALLING EDGE" + self.state = 'WAIT FOR FALLING EDGE' elif self.state == 'WAIT FOR PRESENCE DETECT': # Sample presence status. t = self.samplenum - self.rise @@ -278,5 +285,3 @@ class Decoder(srd.Decoder): # Wait for next slot. self.state = 'WAIT FOR FALLING EDGE' - else: - raise Exception('Invalid state: %s' % self.state) diff --git a/decoders/onewire_network/__init__.py b/decoders/onewire_network/__init__.py index 31e9134..44b9bcc 100644 --- a/decoders/onewire_network/__init__.py +++ b/decoders/onewire_network/__init__.py @@ -54,5 +54,4 @@ TODO: - Add reporting original/complement address values from the search algorithm. ''' -from .pd import * - +from .pd import Decoder diff --git a/decoders/onewire_network/pd.py b/decoders/onewire_network/pd.py index 23402a6..bddc4a8 100644 --- a/decoders/onewire_network/pd.py +++ b/decoders/onewire_network/pd.py @@ -46,8 +46,8 @@ class Decoder(srd.Decoder): ) def __init__(self, **kwargs): - self.beg = 0 - self.end = 0 + self.ss_block = 0 + self.es_block = 0 self.state = 'COMMAND' self.bit_cnt = 0 self.search = 'P' @@ -62,11 +62,11 @@ class Decoder(srd.Decoder): def putx(self, data): # Helper function for most annotations. - self.put(self.beg, self.end, self.out_ann, data) + self.put(self.ss_block, self.es_block, self.out_ann, data) def puty(self, data): # Helper function for most protocol packets. - self.put(self.beg, self.end, self.out_python, data) + self.put(self.ss_block, self.es_block, self.out_python, data) def decode(self, ss, es, data): code, val = data @@ -126,20 +126,18 @@ class Decoder(srd.Decoder): if self.onewire_collect(8, val, ss, es) == 0: return self.putx([0, ['ROM error data: 0x%02x' % self.data]]) - else: - raise Exception('Invalid state: %s' % self.state) # Data collector. def onewire_collect(self, length, val, ss, es): # Storing the sample this sequence begins with. if self.bit_cnt == 1: - self.beg = ss + self.ss_block = ss self.data = self.data & ~(1 << self.bit_cnt) | (val << self.bit_cnt) self.bit_cnt += 1 # Storing the sample this sequence ends with. # In case the full length of the sequence is received, return 1. if self.bit_cnt == length: - self.end = es + self.es_block = es self.data = self.data & ((1 << length) - 1) self.bit_cnt = 0 return 1 @@ -150,7 +148,7 @@ class Decoder(srd.Decoder): def onewire_search(self, length, val, ss, es): # Storing the sample this sequence begins with. if (self.bit_cnt == 0) and (self.search == 'P'): - self.beg = ss + self.ss_block = ss if self.search == 'P': # Master receives an original address bit. @@ -171,7 +169,7 @@ class Decoder(srd.Decoder): # Storing the sample this sequence ends with. # In case the full length of the sequence is received, return 1. if self.bit_cnt == length: - self.end = es + self.es_block = es self.data_p = self.data_p & ((1 << length) - 1) self.data_n = self.data_n & ((1 << length) - 1) self.data = self.data & ((1 << length) - 1) diff --git a/decoders/pan1321/__init__.py b/decoders/pan1321/__init__.py index 9bda523..c14236d 100644 --- a/decoders/pan1321/__init__.py +++ b/decoders/pan1321/__init__.py @@ -23,5 +23,4 @@ This decoder stacks on top of the 'uart' PD and decodes the Panasonic PAN1321 Bluetooth module Serial Port Profile (SPP) protocol. ''' -from .pd import * - +from .pd import Decoder diff --git a/decoders/pan1321/pd.py b/decoders/pan1321/pd.py index d173e1c..b70defc 100644 --- a/decoders/pan1321/pd.py +++ b/decoders/pan1321/pd.py @@ -136,6 +136,9 @@ class Decoder(srd.Decoder): if ptype != 'DATA': return + # We're only interested in the byte value (not individual bits). + pdata = pdata[0] + # If this is the start of a command/reply, remember the start sample. if self.cmd[rxtx] == '': self.ss_block = ss @@ -154,8 +157,5 @@ class Decoder(srd.Decoder): self.handle_device_reply(rxtx, self.cmd[rxtx][:-2]) elif rxtx == TX: self.handle_host_command(rxtx, self.cmd[rxtx][:-2]) - else: - raise Exception('Invalid rxtx value: %d' % rxtx) self.cmd[rxtx] = '' - diff --git a/decoders/parallel/__init__.py b/decoders/parallel/__init__.py index cc1f3d1..a7077bb 100644 --- a/decoders/parallel/__init__.py +++ b/decoders/parallel/__init__.py @@ -32,5 +32,4 @@ should be used. Using combinations like D7/D12/D3/D15 is not supported. For an 8-bit bus you should use D0-D7, for a 16-bit bus use D0-D15 and so on. ''' -from .pd import * - +from .pd import Decoder diff --git a/decoders/parallel/pd.py b/decoders/parallel/pd.py index 03b5e2f..cafaefc 100644 --- a/decoders/parallel/pd.py +++ b/decoders/parallel/pd.py @@ -61,6 +61,9 @@ def channel_list(num_channels): l.append(d) return tuple(l) +class ChannelError(Exception): + pass + class Decoder(srd.Decoder): api_version = 2 id = 'parallel' @@ -92,7 +95,6 @@ class Decoder(srd.Decoder): self.oldpins = None self.ss_item = self.es_item = None self.first = True - self.state = 'IDLE' def start(self): self.out_python = self.register(srd.OUTPUT_PYTHON) @@ -123,7 +125,7 @@ class Decoder(srd.Decoder): self.items.append(item) self.itemcount += 1 - if self.first == True: + if self.first: # Save the start sample and item for later (no output yet). self.ss_item = self.samplenum self.first = False @@ -181,12 +183,10 @@ class Decoder(srd.Decoder): continue self.oldpins = pins - # State machine. - if self.state == 'IDLE': - if pins[0] not in (0, 1): - self.handle_bits(pins[1:]) - else: - self.find_clk_edge(pins[0], pins[1:]) - else: - raise Exception('Invalid state: %s' % self.state) + if sum(1 for p in pins if p in (0, 1)) == 0: + raise ChannelError('At least one channel has to be supplied.') + if pins[0] not in (0, 1): + self.handle_bits(pins[1:]) + else: + self.find_clk_edge(pins[0], pins[1:]) diff --git a/decoders/pwm/__init__.py b/decoders/pwm/__init__.py new file mode 100644 index 0000000..096e077 --- /dev/null +++ b/decoders/pwm/__init__.py @@ -0,0 +1,25 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 Torsten Duwe +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + +''' +Pulse-width modulation (a.k.a pulse-duration modulation, PDM) decoder. +''' + +from .pd import Decoder diff --git a/decoders/pwm/pd.py b/decoders/pwm/pd.py new file mode 100644 index 0000000..45e96e2 --- /dev/null +++ b/decoders/pwm/pd.py @@ -0,0 +1,150 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 Torsten Duwe +## Copyright (C) 2014 Sebastien Bourdelin +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, 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 = 'pwm' + name = 'PWM' + longname = 'Pulse-width modulation' + desc = 'Analog level encoded in duty cycle percentage.' + license = 'gplv2+' + inputs = ['logic'] + outputs = ['pwm'] + channels = ( + {'id': 'data', 'name': 'Data', 'desc': 'Data line'}, + ) + options = ( + {'id': 'polarity', 'desc': 'Polarity', 'default': 'active-high', + 'values': ('active-low', 'active-high')}, + ) + annotations = ( + ('duty-cycle', 'Duty cycle'), + ('period', 'Period'), + ) + annotation_rows = ( + ('duty-cycle', 'Duty cycle', (0,)), + ('period', 'Period', (1,)), + ) + binary = ( + ('raw', 'RAW file'), + ) + + def __init__(self, **kwargs): + self.ss = self.es = None + self.first_transition = True + self.first_samplenum = None + self.start_samplenum = None + self.end_samplenum = None + self.oldpin = None + self.num_cycles = 0 + self.average = 0 + + def metadata(self, key, value): + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + + def start(self): + self.startedge = 0 if self.options['polarity'] == 'active-low' else 1 + self.out_ann = self.register(srd.OUTPUT_ANN) + self.out_bin = self.register(srd.OUTPUT_BINARY) + self.out_average = \ + self.register(srd.OUTPUT_META, + meta=(float, 'Average', 'PWM base (cycle) frequency')) + + def putx(self, data): + self.put(self.ss, self.es, self.out_ann, data) + + def putp(self, period_t): + # Adjust granularity. + if period_t == 0 or period_t >= 1: + period_s = u'%u s' % (period_t) + elif period_t <= 1e-12: + period_s = u'%.1f fs' % (period_t * 1e15) + elif period_t <= 1e-9: + period_s = u'%.1f ps' % (period_t * 1e12) + elif period_t <= 1e-6: + period_s = u'%.1f ns' % (period_t * 1e9) + elif period_t <= 1e-3: + period_s = u'%.1f μs' % (period_t * 1e6) + else: + period_s = u'%.1f ms' % (period_t * 1e3) + + self.put(self.ss, self.es, self.out_ann, [1, [period_s]]) + + def putb(self, data): + self.put(self.num_cycles, self.num_cycles, self.out_bin, data) + + def decode(self, ss, es, data): + + for (self.samplenum, pins) in data: + # Ignore identical samples early on (for performance reasons). + if self.oldpin == pins[0]: + continue + + # Initialize self.oldpins with the first sample value. + if self.oldpin is None: + self.oldpin = pins[0] + continue + + if self.first_transition: + # First rising edge + if self.oldpin != self.startedge: + self.first_samplenum = self.samplenum + self.start_samplenum = self.samplenum + self.first_transition = False + else: + if self.oldpin != self.startedge: + # Rising edge + # We are on a full cycle we can calculate + # the period, the duty cycle and its ratio. + period = self.samplenum - self.start_samplenum + duty = self.end_samplenum - self.start_samplenum + ratio = float(duty / period) + + # This interval starts at this edge. + self.ss = self.start_samplenum + # Store the new rising edge position and the ending + # edge interval. + self.start_samplenum = self.es = self.samplenum + + # Report the duty cycle in percent. + percent = float(ratio * 100) + self.putx([0, ['%f%%' % percent]]) + + # Report the duty cycle in the binary output. + self.putb((0, bytes([int(ratio * 256)]))) + + # Report the period in units of time. + period_t = float(period / self.samplerate) + self.putp(period_t) + + # Update and report the new duty cycle average. + self.num_cycles += 1 + self.average += percent + self.put(self.first_samplenum, self.es, self.out_average, + float(self.average / self.num_cycles)) + else: + # Falling edge + self.end_samplenum = self.ss = self.samplenum + + self.oldpin = pins[0] diff --git a/decoders/qi/__init__.py b/decoders/qi/__init__.py new file mode 100644 index 0000000..35ffe5b --- /dev/null +++ b/decoders/qi/__init__.py @@ -0,0 +1,26 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2015 Josef Gajdusek +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + +''' +This decoder decodes demodulated data streams used by the Qi standard +for communication from the receiver to the charging station. +''' + +from .pd import Decoder diff --git a/decoders/qi/pd.py b/decoders/qi/pd.py new file mode 100644 index 0000000..12155c9 --- /dev/null +++ b/decoders/qi/pd.py @@ -0,0 +1,244 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2015 Josef Gajdusek +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + +import sigrokdecode as srd +import operator +import collections +from functools import reduce + +end_codes = ( + 'Unknown', + 'Charge Complete', + 'Internal Fault', + 'Over Temperature', + 'Over Voltage', + 'Over Current', + 'Battery Failure', + 'Reconfigure', + 'No Response', +) + +class SamplerateError(Exception): + pass + +def calc_checksum(packet): + return reduce(operator.xor, packet[:-1]) + +def bits_to_uint(bits): + # LSB first + return reduce(lambda i, v: (i >> 1) | (v << (len(bits) - 1)), bits, 0) + +class Decoder(srd.Decoder): + api_version = 2 + id = 'qi' + name = 'Qi' + longname = 'Qi charger protocol' + desc = 'Async serial protocol for Qi charger receivers.' + license = 'gplv2+' + inputs = ['logic'] + outputs = ['qi'] + channels = ( + {'id': 'qi', 'name': 'Qi', 'desc': 'Demodulated Qi data line'}, + ) + annotations = ( + ('bits', 'Bits'), + ('bytes-errors', 'Bit errors'), + ('bytes-start', 'Start bits'), + ('bytes-info', 'Info bits'), + ('bytes-data', 'Data bytes'), + ('packets-data', 'Packet data'), + ('packets-checksum-ok', 'Packet checksum'), + ('packets-checksum-err', 'Packet checksum'), + ) + annotation_rows = ( + ('bits', 'Bits', (0,)), + ('bytes', 'Bytes', (1, 2, 3, 4)), + ('packets', 'Packets', (5, 6, 7)), + ) + + def __init__(self, **kwargs): + self.samplerate = None + self.reset_variables() + + def reset_variables(self): + self.counter = 0 + self.prev = None + self.state = 'IDLE' + self.lastbit = 0 + self.bytestart = 0 + self.deq = collections.deque(maxlen = 2) + self.bits = [] + self.bitsi = [0] + self.bytesi = [] + self.packet = [] + + def metadata(self, key, value): + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + self.bit_width = float(self.samplerate) / 2e3 + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + self.reset_variables() + + def packet_len(self, byte): + if 0x00 <= byte <= 0x1f: + return int(1 + (byte - 0) / 32) + if 0x20 <= byte <= 0x7f: + return int(2 + (byte - 32) / 16) + if 0x80 <= byte <= 0xdf: + return int(8 + (byte - 128) / 8) + if 0xe0 <= byte <= 0xff: + return int(20 + (byte - 224) / 4) + + def in_tolerance(self, l): + return (0.75 * self.bit_width) < l < (1.25 * self.bit_width) + + def putp(self, data): + self.put(self.bytesi[0], self.bytesi[-1], self.out_ann, [5, data]) + + def process_packet(self): + if self.packet[0] == 0x01: # Signal Strength + self.putp(['Signal Strength: %d' % self.packet[1], + 'SS: %d' % self.packet[1], 'SS']) + elif self.packet[0] == 0x02: # End Power Transfer + reason = end_codes[self.packet[1]] if self.packet[1] < len(end_codes) else 'Reserved' + self.putp(['End Power Transfer: %s' % reason, + 'EPT: %s' % reason, 'EPT']) + elif self.packet[0] == 0x03: # Control Error + val = self.packet[1] if self.packet[1] < 128 else (self.packet[1] & 0x7f) - 128 + self.putp(['Control Error: %d' % val, 'CE: %d' % val, 'CE']) + elif self.packet[0] == 0x04: # Received Power + self.putp(['Received Power: %d' % self.packet[1], + 'RP: %d' % self.packet[1], 'RP']) + elif self.packet[0] == 0x05: # Charge Status + self.putp(['Charge Status: %d' % self.packet[1], + 'CS: %d' % self.packet[1], 'CS']) + elif self.packet[0] == 0x06: # Power Control Hold-off + self.putp(['Power Control Hold-off: %dms' % self.packet[1], + 'PCH: %d' % self.packet[1]], 'PCH') + elif self.packet[0] == 0x51: # Configuration + powerclass = (self.packet[1] & 0xc0) >> 7 + maxpower = self.packet[1] & 0x3f + prop = (self.packet[3] & 0x80) >> 7 + count = self.packet[3] & 0x07 + winsize = (self.packet[4] & 0xf8) >> 3 + winoff = self.packet[4] & 0x07 + self.putp(['Configuration: Power Class = %d, Maximum Power = %d, Prop = %d,' + 'Count = %d, Window Size = %d, Window Offset = %d' % + (powerclass, maxpower, prop, count, winsize, winoff), + 'C: PC = %d MP = %d P = %d C = %d WS = %d WO = %d' % + (powerclass, maxpower, prop, count, winsize, winoff), + 'Configuration', 'C']) + elif self.packet[0] == 0x71: # Identification + version = '%d.%d' % ((self.packet[1] & 0xf0) >> 4, self.packet[1] & 0x0f) + mancode = '%02x%02x' % (self.packet[2], self.packet[3]) + devid = '%02x%02x%02x%02x' % (self.packet[4] & ~0x80, + self.packet[5], self.packet[6], self.packet[7]) + self.putp(['Identification: Version = %s, Manufacturer = %s, ' \ + 'Device = %s' % (version, mancode, devid), + 'ID: %s %s %s' % (version, mancode, devid), 'ID']) + elif self.packet[0] == 0x81: # Extended Identification + edevid = '%02x%02x%02x%02x%02x%02x%02x%02x' % self.packet[1:-1] + self.putp(['Extended Identification: %s' % edevid, + 'EI: %s' % edevid, 'EI']) + elif self.packet[0] in (0x18, 0x19, 0x28, 0x29, 0x38, 0x48, 0x58, 0x68, + 0x78, 0x85, 0xa4, 0xc4, 0xe2): # Proprietary + self.putp(['Proprietary', 'P']) + else: # Unknown + self.putp(['Unknown', '?']) + self.put(self.bytesi[-1], self.samplenum, self.out_ann, + [6, ['Checksum OK', 'OK']] if \ + calc_checksum(self.packet) == self.packet[-1] + else [6, ['Checksum error', 'ERR']]) + + def process_byte(self): + self.put(self.bytestart, self.bitsi[0], self.out_ann, + ([2, ['Start bit', 'Start', 'S']]) if self.bits[0] == 0 else + ([1, ['Start error', 'Start err', 'SE']])) + databits = self.bits[1:9] + data = bits_to_uint(databits) + parity = reduce(lambda i, v: (i + v) % 2, databits, 1) + self.put(self.bitsi[0], self.bitsi[8], self.out_ann, [4, ['%02x' % data]]) + self.put(self.bitsi[8], self.bitsi[9], self.out_ann, + ([3, ['Parity bit', 'Parity', 'P']]) if self.bits[9] == parity else + ([1, ['Parity error', 'Parity err', 'PE']])) + self.put(self.bitsi[9], self.bitsi[10], self.out_ann, + ([3, ['Stop bit', 'Stop', 'S']]) if self.bits[10] == 1 else + ([1, ['Stop error', 'Stop err', 'SE']])) + + self.bytesi.append(self.bytestart) + self.packet.append(data) + if self.packet_len(self.packet[0]) + 2 == len(self.packet): + self.process_packet() + self.bytesi.clear() + self.packet.clear() + + def add_bit(self, bit): + self.bits.append(bit) + self.bitsi.append(self.samplenum) + + if self.state == 'IDLE' and len(self.bits) >= 5 and \ + self.bits[-5:] == [1, 1, 1, 1, 0]: + self.state = 'DATA' + self.bytestart = self.bitsi[-2] + self.bits = [0] + self.bitsi = [self.samplenum] + self.packet.clear() + elif self.state == 'DATA' and len(self.bits) == 11: + self.process_byte() + self.bytestart = self.samplenum + self.bits.clear() + self.bitsi.clear() + if self.state != 'IDLE': + self.put(self.lastbit, self.samplenum, self.out_ann, [0, ['%d' % bit]]) + self.lastbit = self.samplenum + + def handle_transition(self, l, htl): + self.deq.append(l) + if len(self.deq) >= 2 and \ + (self.in_tolerance(self.deq[-1] + self.deq[-2]) or \ + htl and self.in_tolerance(l * 2) and \ + self.deq[-2] > 1.25 * self.bit_width): + self.add_bit(1) + self.deq.clear() + elif self.in_tolerance(l): + self.add_bit(0) + self.deq.clear() + elif l > (1.25 * self.bit_width): + self.state = 'IDLE' + self.bytesi.clear() + self.packet.clear() + self.bits.clear() + self.bitsi.clear() + + def next_sample(self, s): + if s == self.prev: + self.counter += 1 + else: + self.handle_transition(self.counter, s == 0) + self.prev = s + self.counter = 1 + + def decode(self, ss, es, data): + if not self.samplerate: + raise SamplerateError('Cannot decode without samplerate.') + for (self.samplenum, (qi,)) in data: + self.next_sample(qi) diff --git a/decoders/rfm12/__init__.py b/decoders/rfm12/__init__.py new file mode 100644 index 0000000..725d443 --- /dev/null +++ b/decoders/rfm12/__init__.py @@ -0,0 +1,26 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 Sławek Piotrowski +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, 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 HopeRF RFM12 +wireless transceiver control protocol. +''' + +from .pd import Decoder diff --git a/decoders/rfm12/pd.py b/decoders/rfm12/pd.py new file mode 100644 index 0000000..3065383 --- /dev/null +++ b/decoders/rfm12/pd.py @@ -0,0 +1,494 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 Sławek Piotrowski +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, 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 = 'rfm12' + name = 'RFM12' + longname = 'RFM12 control protocol' + desc = 'HopeRF RFM12 wireless transceiver control protocol.' + license = 'gplv2+' + inputs = ['spi'] + outputs = ['rfm12'] + annotations = ( + ('cmd', 'Command'), + ('params', 'Command parameters'), + ('disabled', 'Disabled bits'), + ('return', 'Returned values'), + ('disabled_return', 'Disabled returned values'), + ('interpretation', 'Interpretation'), + ) + annotation_rows = ( + ('commands', 'Commands', (0, 1, 2)), + ('return', 'Return', (3, 4)), + ('interpretation', 'Interpretation', (5,)), + ) + + def __init__(self, **kwargs): + self.mosi_bytes, self.miso_bytes = [], [] + self.mosi_bits, self.miso_bits = [], [] + self.row_pos = [0, 0, 0] + + self.ann_to_row = [0, 0, 0, 1, 1, 2] + + # Initialize with Power-On-Reset values. + self.last_status = [0x00, 0x00] + self.last_config = 0x08 + self.last_power = 0x08 + self.last_freq = 0x680 + self.last_data_rate = 0x23 + self.last_fifo_and_reset = 0x80 + self.last_afc = 0xF7 + self.last_transceiver = 0x00 + self.last_pll = 0x77 + + def advance_ann(self, ann, length): + row = self.ann_to_row[ann] + self.row_pos[row] += length + + def putx(self, ann, length, description): + if not isinstance(description, list): + description = [description] + row = self.ann_to_row[ann] + bit = self.row_pos[row] + self.put(self.mosi_bits[bit][1], self.mosi_bits[bit + length - 1][2], + self.out_ann, [ann, description]) + bit += length + self.row_pos[row] = bit + + def describe_bits(self, data, names): + i = 0x01 << len(names) - 1 + bit = 0 + while i != 0: + if names[bit] != '': + self.putx(1 if (data & i) else 2, 1, names[bit]) + i >>= 1 + bit += 1 + + def describe_return_bits(self, data, names): + i = 0x01 << len(names) - 1 + bit = 0 + while i != 0: + if names[bit] != '': + self.putx(3 if (data & i) else 4, 1, names[bit]) + else: + self.advance_ann(3, 1) + i >>= 1 + bit += 1 + + def describe_changed_bits(self, data, old_data, names): + changes = data ^ old_data + i = 0x01 << (len(names) - 1) + bit = 0 + while i != 0: + if names[bit] != '' and changes & i: + s = ['+', 'Turning on'] if (data & i) else ['-', 'Turning off'] + self.putx(5, 1, s) + else: + self.advance_ann(5, 1) + i >>= 1 + bit += 1 + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def handle_configuration_cmd(self, cmd, ret): + self.putx(0, 8, ['Configuration command', 'Configuration']) + NAMES = [['Internal data register', 'el'], ['FIFO mode', 'ef']] + + bits = (cmd[1] & 0xC0) >> 6 + old_bits = (self.last_config & 0xC0) >> 6 + self.describe_bits(bits, NAMES) + self.describe_changed_bits(bits, old_bits, NAMES) + + FREQUENCIES = ['315', '433', '868', '915'] + f = FREQUENCIES[(cmd[1] & 0x30) >> 4] + 'MHz' + self.putx(1, 2, ['Frequency: ' + f, f]) + if cmd[1] & 0x30 != self.last_config & 0x30: + self.putx(5, 2, ['Changed', '~']) + + c = '%.1fpF' % (8.5 + (cmd[1] & 0xF) * 0.5) + self.putx(1, 4, ['Capacitance: ' + c, c]) + if cmd[1] & 0xF != self.last_config & 0xF: + self.putx(5, 4, ['Changed', '~']) + + self.last_config = cmd[1] + + def handle_power_management_cmd(self, cmd, ret): + self.putx(0, 8, ['Power management', 'Power']) + NAMES = [['Receiver chain', 'er'], ['Baseband circuit', 'ebb'], + ['Transmission', 'et'], ['Synthesizer', 'es'], + ['Crystal oscillator', 'ex'], ['Low battery detector', 'eb'], + ['Wake-up timer', 'ew'], ['Clock output off switch', 'dc']] + + self.describe_bits(cmd[1], NAMES) + + power = cmd[1] + + # Some bits imply other, even if they are set to 0. + if power & 0x80: + power |= 0x58 + if power & 0x20: + power |= 0x18 + self.describe_changed_bits(power, self.last_power, NAMES) + + self.last_power = power + + def handle_frequency_setting_cmd(self, cmd, ret): + self.putx(0, 4, ['Frequency setting', 'Frequency']) + f = ((cmd[1] & 0xF) << 8) + cmd[2] + self.putx(0, 12, ['F = %3.4f' % f]) + self.row_pos[2] -= 4 + if self.last_freq != f: + self.putx(5, 12, ['Changing', '~']) + self.last_freq = f + + def handle_data_rate_cmd(self, cmd, ret): + self.putx(0, 8, ['Data rate command', 'Data rate']) + r = cmd[1] & 0x7F + cs = (cmd[1] & 0x80) >> 7 + rate = 10000 / 29.0 / (r + 1) / (1 + 7 * cs) + self.putx(0, 8, ['%3.1fkbps' % rate]) + if self.last_data_rate != cmd[1]: + self.putx(5, 8, ['Changing', '~']) + self.last_data_rate = cmd[1] + + def handle_receiver_control_cmd(self, cmd, ret): + self.putx(0, 5, ['Receiver control command']) + s = 'interrupt input' if (cmd[0] & 0x04) else 'VDI output' + self.putx(0, 1, ['pin16 = ' + s]) + VDI_NAMES = ['Fast', 'Medium', 'Slow', 'Always on'] + vdi_speed = VDI_NAMES[cmd[0] & 0x3] + self.putx(0, 2, ['VDI: %s' % vdi_speed]) + BANDWIDTH_NAMES = ['Reserved', '400kHz', '340kHz', '270kHz', '200kHz', + '134kHz', '67kHz', 'Reserved'] + bandwidth = BANDWIDTH_NAMES[(cmd[1] & 0xE0) >> 5] + self.putx(0, 3, ['Bandwidth: %s' % bandwidth]) + LNA_GAIN_NAMES = [0, -6, -14, -20] + lna_gain = LNA_GAIN_NAMES[(cmd[1] & 0x18) >> 3] + self.putx(0, 2, ['LNA gain: %ddB' % lna_gain]) + RSSI_THRESHOLD_NAMES = ['-103', '-97', '-91', '-85', '-79', '-73', + 'Reserved', 'Reserved'] + rssi_threshold = RSSI_THRESHOLD_NAMES[cmd[1] & 0x7] + self.putx(0, 3, ['RSSI threshold: %s' % rssi_threshold]) + + def handle_data_filter_cmd(self, cmd, ret): + self.putx(0, 8, ['Data filter command']) + if cmd[1] & 0x80: + clock_recovery = 'auto' + elif cmd[1] & 0x40: + clock_recovery = 'fast' + else: + clock_recovery = 'slow' + self.putx(0, 2, ['Clock recovery: %s mode' % clock_recovery]) + self.advance_ann(0, 1) # Should always be 1. + s = 'analog' if (cmd[1] & 0x10) else 'digital' + self.putx(0, 1, ['Data filter: ' + s]) + self.advance_ann(0, 1) # Should always be 1. + self.putx(0, 3, ['DQD threshold: %d' % (cmd[1] & 0x7)]) + + def handle_fifo_and_reset_cmd(self, cmd, ret): + self.putx(0, 8, ['FIFO and reset command']) + fifo_level = (cmd[1] & 0xF0) >> 4 + self.putx(0, 4, ['FIFO trigger level: %d' % fifo_level]) + last_fifo_level = (self.last_fifo_and_reset & 0xF0) >> 4 + if fifo_level != last_fifo_level: + self.putx(5, 4, ['Changing', '~']) + else: + self.advance_ann(5, 4) + s = 'one byte' if (cmd[1] & 0x08) else 'two bytes' + self.putx(0, 1, ['Synchron length: ' + s]) + if (cmd[1] & 0x08) != (self.last_fifo_and_reset & 0x08): + self.putx(5, 1, ['Changing', '~']) + else: + self.advance_ann(5, 1) + + if cmd[1] & 0x04: + fifo_fill = 'Always' + elif cmd[1] & 0x02: + fifo_fill = 'After synchron pattern' + else: + fifo_fill = 'Never' + self.putx(0, 2, ['FIFO fill: %s' % fifo_fill]) + if (cmd[1] & 0x06) != (self.last_fifo_and_reset & 0x06): + self.putx(5, 2, ['Changing', '~']) + else: + self.advance_ann(5, 2) + + s = 'non-sensitive' if (cmd[1] & 0x01) else 'sensitive' + self.putx(0, 1, ['Reset mode: ' + s]) + if (cmd[1] & 0x01) != (self.last_fifo_and_reset & 0x01): + self.putx(5, 1, ['Changing', '~']) + else: + self.advance_ann(5, 1) + + self.last_fifo_and_reset = cmd[1] + + def handle_synchron_pattern_cmd(self, cmd, ret): + self.putx(0, 8, ['Synchron pattern command']) + if self.last_fifo_and_reset & 0x08: + self.putx(0, 8, ['Pattern: 0x2D%02X' % pattern]) + else: + self.putx(0, 8, ['Pattern: %02X' % pattern]) + + def handle_fifo_read_cmd(self, cmd, ret): + self.putx(0, 8, ['FIFO read command', 'FIFO read']) + self.putx(3, 8, ['Data: %02X' % ret[1]]) + + def handle_afc_cmd(self, cmd, ret): + self.putx(0, 8, ['AFC command']) + MODES = ['Off', 'Once', 'During receiving', 'Always'] + mode = (cmd[1] & 0xC0) >> 6 + self.putx(0, 2, ['Mode: %s' % MODES[mode]]) + if (cmd[1] & 0xC0) != (self.last_afc & 0xC0): + self.putx(5, 2, ['Changing', '~']) + else: + self.advance_ann(5, 2) + + range_limit = (cmd[1] & 0x30) >> 4 + FREQ_TABLE = [0.0, 2.5, 5.0, 7.5] + freq_delta = FREQ_TABLE[(self.last_config & 0x30) >> 4] + + if range_limit == 0: + self.putx(0, 2, ['Range: No limit']) + elif range_limit == 1: + self.putx(0, 2, ['Range: +/-%dkHz' % (15 * freq_delta)]) + elif range_limit == 2: + self.putx(0, 2, ['Range: +/-%dkHz' % (7 * freq_delta)]) + elif range_limit == 3: + self.putx(0, 2, ['Range: +/-%dkHz' % (3 * freq_delta)]) + + if (cmd[1] & 0x30) != (self.last_afc & 0x30): + self.putx(5, 2, ['Changing', '~']) + else: + self.advance_ann(5, 2) + + NAMES = ['Strobe edge', 'High accuracy mode', 'Enable offset register', + 'Enable offset calculation'] + self.describe_bits(cmd[1] & 0xF, NAMES) + self.describe_changed_bits(cmd[1] & 0xF, self.last_afc & 0xF, NAMES) + + self.last_afc = cmd[1] + + def handle_transceiver_control_cmd(self, cmd, ret): + self.putx(0, 8, ['Transceiver control command']) + self.putx(0, 4, ['FSK frequency delta: %dkHz' % (15 * ((cmd[1] & 0xF0) >> 4))]) + if cmd[1] & 0xF0 != self.last_transceiver & 0xF0: + self.putx(5, 4, ['Changing', '~']) + else: + self.advance_ann(5, 4) + + POWERS = [0, -2.5, -5, -7.5, -10, -12.5, -15, -17.5] + self.advance_ann(0, 1) + self.advance_ann(5, 1) + self.putx(0,3, ['Relative power: %dB' % (cmd[1] & 0x07)]) + if (cmd[1] & 0x07) != (self.last_transceiver & 0x07): + self.putx(5, 3, ['Changing', '~']) + else: + self.advance_ann(5, 3) + self.last_transceiver = cmd[1] + + def handle_pll_setting_cmd(self, cmd, ret): + self.putx(0, 8, ['PLL setting command']) + self.advance_ann(0, 1) + self.putx(0, 2, ['Clock buffer rise and fall time']) + self.advance_ann(0, 1) + self.advance_ann(5, 4) + NAMES = [['Delay in phase detector', 'dly'], ['Disable dithering', 'ddit']] + self.describe_bits((cmd[1] & 0xC) >> 2, NAMES) + self.describe_changed_bits((cmd[1] & 0xC) >> 2, (self.last_pll & 0xC) >> 2, NAMES) + s = '256kbps, high' if (cmd[1] & 0x01) else '86.2kbps, low' + self.putx(0, 1, ['Max bit rate: %s noise' % s]) + + self.advance_ann(5, 1) + if (cmd[1] & 0x01) != (self.last_pll & 0x01): + self.putx(5, 1, ['Changing', '~']) + + self.last_pll = cmd[1] + + def handle_transmitter_register_cmd(self, cmd, ret): + self.putx(0, 8, ['Transmitter register command', 'Transmit']) + self.putx(0, 8, ['Data: %s' % cmd[1], '%s' % cmd[1]]) + + def handle_software_reset_cmd(self, cmd, ret): + self.putx(0, 16, ['Software reset command']) + + def handle_wake_up_timer_cmd(self, cmd, ret): + self.putx(0, 3, ['Wake-up timer command', 'Timer']) + r = cmd[0] & 0x1F + m = cmd[1] + time = 1.03 * m * pow(2, r) + 0.5 + self.putx(0, 13, ['Time: %7.2f' % time]) + + def handle_low_duty_cycle_cmd(self, cmd, ret): + self.putx(0, 16, ['Low duty cycle command']) + + def handle_low_battery_detector_cmd(self, cmd, ret): + self.putx(0, 8, ['Low battery detector command']) + NAMES = ['1', '1.25', '1.66', '2', '2.5', '3.33', '5', '10'] + clock = NAMES[(cmd[1] & 0xE0) >> 5] + self.putx(0, 3, ['Clock output: %sMHz' % clock, '%sMHz' % clock]) + self.advance_ann(0, 1) + v = 2.25 + (cmd[1] & 0x0F) * 0.1 + self.putx(0, 4, ['Low battery voltage: %1.2fV' % v, '%1.2fV' % v]) + + def handle_status_read_cmd(self, cmd, ret): + self.putx(0, 8, ['Status read command', 'Status']) + NAMES = ['RGIT/FFIT', 'POR', 'RGUR/FFOV', 'WKUP', 'EXT', 'LBD', + 'FFEM', 'RSSI/ATS', 'DQD', 'CRL', 'ATGL'] + status = (ret[0] << 3) + (ret[1] >> 5) + self.row_pos[1] -= 8 + self.row_pos[2] -= 8 + self.describe_return_bits(status, NAMES) + receiver_enabled = (self.last_power & 0x80) >> 7 + + if ret[0] & 0x80: + if receiver_enabled: + s = 'Received data in FIFO' + else: + s = 'Transmit register ready' + self.putx(5, 1, s) + else: + self.advance_ann(5, 1) + if ret[0] & 0x40: + self.putx(5, 1, 'Power on Reset') + else: + self.advance_ann(5, 1) + if ret[0] & 0x20: + if receiver_enabled: + s = 'RX FIFO overflow' + else: + s = 'Transmit register under run' + self.putx(5, 1, s) + else: + self.advance_ann(5, 1) + if ret[0] & 0x10: + self.putx(5, 1, 'Wake-up timer') + else: + self.advance_ann(5, 1) + if ret[0] & 0x08: + self.putx(5, 1, 'External interrupt') + else: + self.advance_ann(5, 1) + if ret[0] & 0x04: + self.putx(5, 1, 'Low battery') + else: + self.advance_ann(5, 1) + if ret[0] & 0x02: + self.putx(5, 1, 'FIFO is empty') + else: + self.advance_ann(5, 1) + if ret[0] & 0x01: + if receiver_enabled: + s = 'Incoming signal above limit' + else: + s = 'Antenna detected RF signal' + self.putx(5, 1, s) + else: + self.advance_ann(5, 1) + if ret[1] & 0x80: + self.putx(5, 1, 'Data quality detector') + else: + self.advance_ann(5, 1) + if ret[1] & 0x40: + self.putx(5, 1, 'Clock recovery locked') + else: + self.advance_ann(5, 1) + self.advance_ann(5, 1) + + self.putx(3, 5, ['AFC offset']) + if (self.last_status[1] & 0x1F) != (ret[1] & 0x1F): + self.putx(5, 5, ['Changed', '~']) + self.last_status = ret + + def handle_cmd(self, cmd, ret): + if cmd[0] == 0x80: + self.handle_configuration_cmd(cmd, ret) + elif cmd[0] == 0x82: + self.handle_power_management_cmd(cmd, ret) + elif cmd[0] & 0xF0 == 0xA0: + self.handle_frequency_setting_cmd(cmd, ret) + elif cmd[0] == 0xC6: + self.handle_data_rate_cmd(cmd, ret) + elif cmd[0] & 0xF8 == 0x90: + self.handle_receiver_control_cmd(cmd, ret) + elif cmd[0] == 0xC2: + self.handle_data_filter_cmd(cmd, ret) + elif cmd[0] == 0xCA: + self.handle_fifo_and_reset_cmd(cmd, ret) + elif cmd[0] == 0xCE: + self.handle_synchron_pattern_cmd(cmd, ret) + elif cmd[0] == 0xB0: + self.handle_fifo_read_cmd(cmd, ret) + elif cmd[0] == 0xC4: + self.handle_afc_cmd(cmd, ret) + elif cmd[0] & 0xFE == 0x98: + self.handle_transceiver_control_cmd(cmd, ret) + elif cmd[0] == 0xCC: + self.handle_pll_setting_cmd(cmd, ret) + elif cmd[0] == 0xB8: + self.handle_transmitter_register_cmd(cmd, ret) + elif cmd[0] == 0xFE: + self.handle_software_reset_cmd(cmd, ret) + elif cmd[0] & 0xE0 == 0xE0: + self.handle_wake_up_timer_cmd(cmd, ret) + elif cmd[0] == 0xC8: + self.handle_low_duty_cycle_cmd(cmd, ret) + elif cmd[0] == 0xC0: + self.handle_low_battery_detector_cmd(cmd, ret) + elif cmd[0] == 0x00: + self.handle_status_read_cmd(cmd, ret) + else: + c = '%02x %02x' % tuple(cmd) + r = '%02x %02x' % tuple(ret) + self.putx(0, 16, ['Uknown command: %s (reply: %s)!' % (c, r)]) + + def decode(self, ss, es, data): + ptype, mosi, miso = data + + # For now, only use DATA and BITS packets. + if ptype not in ('DATA', 'BITS'): + return + + # Store the individual bit values and ss/es numbers. The next packet + # is guaranteed to be a 'DATA' packet belonging to this 'BITS' one. + if ptype == 'BITS': + if mosi is not None: + self.mosi_bits.extend(reversed(mosi)) + if miso is not None: + self.miso_bits.extend(reversed(miso)) + return + + # Append new bytes. + self.mosi_bytes.append(mosi) + self.miso_bytes.append(miso) + + # All commands consist of 2 bytes. + if len(self.mosi_bytes) < 2: + return + + self.row_pos = [0, 8, 8] + + self.handle_cmd(self.mosi_bytes, self.miso_bytes) + + self.mosi_bytes, self.miso_bytes = [], [] + self.mosi_bits, self.miso_bits = [], [] diff --git a/decoders/rgb_led_spi/__init__.py b/decoders/rgb_led_spi/__init__.py index 3d41ea5..c0e0ea1 100644 --- a/decoders/rgb_led_spi/__init__.py +++ b/decoders/rgb_led_spi/__init__.py @@ -23,5 +23,4 @@ This decoder stacks on top of the 'spi' PD and decodes generic RGB LED string values that are clocked over SPI in RGB values. ''' -from .pd import * - +from .pd import Decoder diff --git a/decoders/rgb_led_spi/pd.py b/decoders/rgb_led_spi/pd.py index 8577928..c6e1032 100644 --- a/decoders/rgb_led_spi/pd.py +++ b/decoders/rgb_led_spi/pd.py @@ -25,7 +25,7 @@ class Decoder(srd.Decoder): id = 'rgb_led_spi' name = 'RGB LED (SPI)' longname = 'RGB LED string decoder (SPI)' - desc = 'Generic RGB LED string protocol (RGB values clocked over SPI).' + desc = 'RGB LED string protocol (RGB values clocked over SPI).' license = 'gplv2' inputs = ['spi'] outputs = ['rgb_led_spi'] @@ -34,14 +34,14 @@ class Decoder(srd.Decoder): ) def __init__(self, **kwargs): - self.cmd_ss, self.cmd_es = 0, 0 + self.ss_cmd, self.es_cmd = 0, 0 self.mosi_bytes = [] def start(self): self.out_ann = self.register(srd.OUTPUT_ANN) def putx(self, data): - self.put(self.cmd_ss, self.cmd_es, self.out_ann, data) + self.put(self.ss_cmd, self.es_cmd, self.out_ann, data) def decode(self, ss, es, data): ptype, mosi, miso = data @@ -52,7 +52,7 @@ class Decoder(srd.Decoder): self.ss, self.es = ss, es if len(self.mosi_bytes) == 0: - self.cmd_ss = ss + self.ss_cmd = ss self.mosi_bytes.append(mosi) # RGB value == 3 bytes @@ -60,10 +60,8 @@ class Decoder(srd.Decoder): return red, green, blue = self.mosi_bytes - rgb_value = int(red) << 16 - rgb_value |= int(green) << 8 - rgb_value |= int(blue) + rgb_value = int(red) << 16 | int(green) << 8 | int(blue) - self.cmd_es = es - self.putx([0, ["#%.6x" % rgb_value]]) + self.es_cmd = es + self.putx([0, ['#%.6x' % rgb_value]]) self.mosi_bytes = [] diff --git a/decoders/rtc8564/__init__.py b/decoders/rtc8564/__init__.py index 9a397b1..e2776a6 100644 --- a/decoders/rtc8564/__init__.py +++ b/decoders/rtc8564/__init__.py @@ -23,5 +23,4 @@ This decoder stacks on top of the 'i2c' PD and decodes the Epson RTC-8564 JE/NB real-time clock (RTC) protocol. ''' -from .pd import * - +from .pd import Decoder diff --git a/decoders/rtc8564/pd.py b/decoders/rtc8564/pd.py index f13af36..24a68fb 100644 --- a/decoders/rtc8564/pd.py +++ b/decoders/rtc8564/pd.py @@ -189,7 +189,7 @@ class Decoder(srd.Decoder): if cmd != 'START': return self.state = 'GET SLAVE ADDR' - self.block_start_sample = ss + self.ss_block = ss elif self.state == 'GET SLAVE ADDR': # Wait for an address write operation. # TODO: We should only handle packets to the RTC slave (0xa2/0xa3). @@ -220,7 +220,7 @@ class Decoder(srd.Decoder): # TODO: Handle read/write of only parts of these items. d = '%02d.%02d.%02d %02d:%02d:%02d' % (self.days, self.months, self.years, self.hours, self.minutes, self.seconds) - self.put(self.block_start_sample, es, self.out_ann, + self.put(self.ss_block, es, self.out_ann, [9, ['Write date/time: %s' % d, 'Write: %s' % d, 'W: %s' % d]]) self.state = 'IDLE' @@ -246,12 +246,9 @@ class Decoder(srd.Decoder): elif cmd == 'STOP': d = '%02d.%02d.%02d %02d:%02d:%02d' % (self.days, self.months, self.years, self.hours, self.minutes, self.seconds) - self.put(self.block_start_sample, es, self.out_ann, + self.put(self.ss_block, es, self.out_ann, [10, ['Read date/time: %s' % d, 'Read: %s' % d, 'R: %s' % d]]) self.state = 'IDLE' else: pass # TODO? - else: - raise Exception('Invalid state: %s' % self.state) - diff --git a/decoders/sdcard_spi/__init__.py b/decoders/sdcard_spi/__init__.py index 293b654..7ce66bf 100644 --- a/decoders/sdcard_spi/__init__.py +++ b/decoders/sdcard_spi/__init__.py @@ -66,5 +66,4 @@ SPI mode properties (differences to SD mode): * The RCA register is not accessible in SPI mode. ''' -from .pd import * - +from .pd import Decoder diff --git a/decoders/sdcard_spi/pd.py b/decoders/sdcard_spi/pd.py index be20ee3..bc761ee 100644 --- a/decoders/sdcard_spi/pd.py +++ b/decoders/sdcard_spi/pd.py @@ -98,10 +98,9 @@ class Decoder(srd.Decoder): def __init__(self, **kwargs): self.state = 'IDLE' - self.samplenum = 0 self.ss, self.es = 0, 0 - self.bit_ss, self.bit_es = 0, 0 - self.cmd_ss, self.cmd_es = 0, 0 + self.ss_bit, self.es_bit = 0, 0 + self.ss_cmd, self.es_cmd = 0, 0 self.cmd_token = [] self.cmd_token_bits = [] self.is_acmd = False # Indicates CMD vs. ACMD @@ -113,13 +112,13 @@ class Decoder(srd.Decoder): self.out_ann = self.register(srd.OUTPUT_ANN) def putx(self, data): - self.put(self.cmd_ss, self.cmd_es, self.out_ann, data) + self.put(self.ss_cmd, self.es_cmd, self.out_ann, data) def putc(self, cmd, desc): self.putx([cmd, ['%s: %s' % (self.cmd_str, desc)]]) def putb(self, data): - self.put(self.bit_ss, self.bit_es, self.out_ann, data) + self.put(self.ss_bit, self.es_bit, self.out_ann, data) def cmd_name(self, cmd): c = acmd_names if self.is_acmd else cmd_names @@ -137,7 +136,7 @@ class Decoder(srd.Decoder): # - CMD[00:00]: End bit (always 1) if len(self.cmd_token) == 0: - self.cmd_ss = self.ss + self.ss_cmd = self.ss self.cmd_token.append(mosi) self.cmd_token_bits.append(self.mosi_bits) @@ -146,7 +145,7 @@ class Decoder(srd.Decoder): if len(self.cmd_token) < 6: return - self.cmd_es = self.es + self.es_cmd = self.es t = self.cmd_token @@ -157,14 +156,14 @@ class Decoder(srd.Decoder): return self.cmd_token_bits[5 - byte][bit] # Bits[47:47]: Start bit (always 0) - bit, self.bit_ss, self.bit_es = tb(5, 7)[0], tb(5, 7)[1], tb(5, 7)[2] + bit, self.ss_bit, self.es_bit = tb(5, 7)[0], tb(5, 7)[1], tb(5, 7)[2] if bit == 0: self.putb([134, ['Start bit: %d' % bit]]) else: self.putb([135, ['Start bit: %s (Warning: Must be 0!)' % bit]]) # Bits[46:46]: Transmitter bit (1 == host) - bit, self.bit_ss, self.bit_es = tb(5, 6)[0], tb(5, 6)[1], tb(5, 6)[2] + bit, self.ss_bit, self.es_bit = tb(5, 6)[0], tb(5, 6)[1], tb(5, 6)[2] if bit == 1: self.putb([134, ['Transmitter bit: %d' % bit]]) else: @@ -172,22 +171,22 @@ class Decoder(srd.Decoder): # Bits[45:40]: Command index (BCD; valid: 0-63) cmd = self.cmd_index = t[0] & 0x3f - self.bit_ss, self.bit_es = tb(5, 5)[1], tb(5, 0)[2] + self.ss_bit, self.es_bit = tb(5, 5)[1], tb(5, 0)[2] self.putb([134, ['Command: %s%d (%s)' % (s, cmd, self.cmd_name(cmd))]]) # Bits[39:8]: Argument self.arg = (t[1] << 24) | (t[2] << 16) | (t[3] << 8) | t[4] - self.bit_ss, self.bit_es = tb(4, 7)[1], tb(1, 0)[2] + self.ss_bit, self.es_bit = tb(4, 7)[1], tb(1, 0)[2] self.putb([134, ['Argument: 0x%04x' % self.arg]]) # Bits[7:1]: CRC7 # TODO: Check CRC7. crc = t[5] >> 1 - self.bit_ss, self.bit_es = tb(0, 7)[1], tb(0, 1)[2] + self.ss_bit, self.es_bit = tb(0, 7)[1], tb(0, 1)[2] self.putb([134, ['CRC7: 0x%01x' % crc]]) # Bits[0:0]: End bit (always 1) - bit, self.bit_ss, self.bit_es = tb(0, 0)[0], tb(0, 0)[1], tb(0, 0)[2] + bit, self.ss_bit, self.es_bit = tb(0, 0)[0], tb(0, 0)[1], tb(0, 0)[2] self.putb([134, ['End bit: %d' % bit]]) if bit == 1: self.putb([134, ['End bit: %d' % bit]]) @@ -212,8 +211,8 @@ class Decoder(srd.Decoder): # CMD1: SEND_OP_COND self.putc(1, 'Send HCS info and activate the card init process') hcs = (self.arg & (1 << 30)) >> 30 - self.bit_ss = self.cmd_token_bits[5 - 4][6][1] - self.bit_es = self.cmd_token_bits[5 - 4][6][2] + self.ss_bit = self.cmd_token_bits[5 - 4][6][1] + self.es_bit = self.cmd_token_bits[5 - 4][6][2] self.putb([134, ['HCS: %d' % hcs]]) self.state = 'GET RESPONSE R1' @@ -221,14 +220,14 @@ class Decoder(srd.Decoder): # CMD9: SEND_CSD (128 bits / 16 bytes) self.putc(9, 'Ask card to send its card specific data (CSD)') if len(self.read_buf) == 0: - self.cmd_ss = self.ss + self.ss_cmd = self.ss self.read_buf.append(self.miso) # FIXME ### if len(self.read_buf) < 16: if len(self.read_buf) < 16 + 4: return - self.cmd_es = self.es - self.read_buf = self.read_buf[4:] ### TODO: Document or redo. + self.es_cmd = self.es + self.read_buf = self.read_buf[4:] # TODO: Document or redo. self.putx([9, ['CSD: %s' % self.read_buf]]) # TODO: Decode all bits. self.read_buf = [] @@ -257,11 +256,11 @@ class Decoder(srd.Decoder): # CMD17: READ_SINGLE_BLOCK self.putc(17, 'Read a block from address 0x%04x' % self.arg) if len(self.read_buf) == 0: - self.cmd_ss = self.ss + self.ss_cmd = self.ss self.read_buf.append(self.miso) if len(self.read_buf) < self.blocklen + 2: # FIXME return - self.cmd_es = self.es + self.es_cmd = self.es self.read_buf = self.read_buf[2:] # FIXME self.putx([17, ['Block data: %s' % self.read_buf]]) self.read_buf = [] @@ -332,12 +331,12 @@ class Decoder(srd.Decoder): # The R1 response token format (1 byte). # Sent by the card after every command except for SEND_STATUS. - self.cmd_ss, self.cmd_es = self.miso_bits[7][1], self.miso_bits[0][2] + self.ss_cmd, self.es_cmd = self.miso_bits[7][1], self.miso_bits[0][2] self.putx([65, ['R1: 0x%02x' % res]]) def putbit(bit, data): b = self.miso_bits[bit] - self.bit_ss, self.bit_es = b[1], b[2] + self.ss_bit, self.es_bit = b[1], b[2] self.putb([134, data]) # Bit 0: 'In idle state' bit @@ -439,6 +438,3 @@ class Decoder(srd.Decoder): handle_response(miso) self.state = 'IDLE' - else: - raise Exception('Invalid state: %s' % self.state) - diff --git a/decoders/spdif/__init__.py b/decoders/spdif/__init__.py new file mode 100644 index 0000000..38363f0 --- /dev/null +++ b/decoders/spdif/__init__.py @@ -0,0 +1,26 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 Guenther Wenninger +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + +''' +S/PDIF (Sony/Philips Digital Interface Format) is a serial bus for +transmitting audio data. +''' + +from .pd import Decoder diff --git a/decoders/spdif/pd.py b/decoders/spdif/pd.py new file mode 100644 index 0000000..e6977e2 --- /dev/null +++ b/decoders/spdif/pd.py @@ -0,0 +1,257 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 Guenther Wenninger +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, 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 = 'spdif' + name = 'S/PDIF' + longname = 'Sony/Philips Digital Interface Format' + desc = 'Serial bus for connecting digital audio devices.' + license = 'gplv2+' + inputs = ['logic'] + outputs = ['spdif'] + channels = ( + {'id': 'data', 'name': 'Data', 'desc': 'Data line'}, + ) + annotations = ( + ('bitrate', 'Bitrate / baudrate'), + ('preamble', 'Preamble'), + ('bits', 'Bits'), + ('aux', 'Auxillary-audio-databits'), + ('samples', 'Audio Samples'), + ('validity', 'Data Valid'), + ('subcode', 'Subcode data'), + ('chan_stat', 'Channnel Status'), + ('parity', 'Parity Bit'), + ) + annotation_rows = ( + ('info', 'Info', (0, 1, 3, 5, 6, 7, 8)), + ('bits', 'Bits', (2,)), + ('samples', 'Samples', (4,)), + ) + + def putx(self, ss, es, data): + self.put(ss, es, self.out_ann, data) + + def puty(self, data): + self.put(self.ss_edge, self.samplenum, self.out_ann, data) + + def __init__(self, **kwargs): + self.state = 'GET FIRST PULSE WIDTH' + self.olddata = None + self.ss_edge = None + self.first_edge = True + self.pulse_width = 0 + + self.clocks = [] + self.range1 = 0 + self.range2 = 0 + + self.preamble_state = 0 + self.preamble = [] + self.seen_preamble = False + self.last_preamble = 0 + + self.first_one = True + self.subframe = [] + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def metadata(self, key, value): + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + + def get_pulse_type(self): + if self.range1 == 0 or self.range2 == 0: + return -1 + if self.pulse_width >= self.range2: + return 2 + elif self.pulse_width >= self.range1: + return 0 + else: + return 1 + + def find_first_pulse_width(self): + if self.pulse_width != 0: + self.clocks.append(self.pulse_width) + self.state = 'GET SECOND PULSE WIDTH' + + def find_second_pulse_width(self): + if self.pulse_width > (self.clocks[0] * 1.3) or \ + self.pulse_width < (self.clocks[0] * 0.7): + self.clocks.append(self.pulse_width) + self.state = 'GET THIRD PULSE WIDTH' + + def find_third_pulse_width(self): + if not ((self.pulse_width > (self.clocks[0] * 1.3) or \ + self.pulse_width < (self.clocks[0] * 0.7)) \ + and (self.pulse_width > (self.clocks[1] * 1.3) or \ + self.pulse_width < (self.clocks[1] * 0.7))): + return + + self.clocks.append(self.pulse_width) + self.clocks.sort() + self.range1 = (self.clocks[0] + self.clocks[1]) / 2 + self.range2 = (self.clocks[1] + self.clocks[2]) / 2 + spdif_bitrate = int(self.samplerate / (self.clocks[2] / 1.5)) + self.ss_edge = 0 + + self.puty([0, ['Signal Bitrate: %d Mbit/s (=> %d kHz)' % \ + (spdif_bitrate, (spdif_bitrate/ (2 * 32)))]]) + + clock_period_nsec = 1000000000 / spdif_bitrate + + self.last_preamble = self.samplenum + + # We are done recovering the clock, now let's decode the data stream. + self.state = 'DECODE STREAM' + + def decode_stream(self): + pulse = self.get_pulse_type() + + if not self.seen_preamble: + # This is probably the start of a preamble, decode it. + if pulse == 2: + self.preamble.append(self.get_pulse_type()) + self.state = 'DECODE PREAMBLE' + self.ss_edge = self.samplenum - self.pulse_width - 1 + return + + # We've seen a preamble. + if pulse == 1 and self.first_one: + self.first_one = False + self.subframe.append([pulse, self.samplenum - \ + self.pulse_width - 1, self.samplenum]) + elif pulse == 1 and not self.first_one: + self.subframe[-1][2] = self.samplenum + self.putx(self.subframe[-1][1], self.samplenum, [2, ['1']]) + self.bitcount += 1 + self.first_one = True + else: + self.subframe.append([pulse, self.samplenum - \ + self.pulse_width - 1, self.samplenum]) + self.putx(self.samplenum - self.pulse_width - 1, + self.samplenum, [2, ['0']]) + self.bitcount += 1 + + if self.bitcount == 28: + aux_audio_data = self.subframe[0:4] + sam, sam_rot = '', '' + for a in aux_audio_data: + sam = sam + str(a[0]) + sam_rot = str(a[0]) + sam_rot + sample = self.subframe[4:24] + for s in sample: + sam = sam + str(s[0]) + sam_rot = str(s[0]) + sam_rot + validity = self.subframe[24:25] + subcode_data = self.subframe[25:26] + channel_status = self.subframe[26:27] + parity = self.subframe[27:28] + + self.putx(aux_audio_data[0][1], aux_audio_data[3][2], \ + [3, ['Aux 0x%x' % int(sam, 2), '0x%x' % int(sam, 2)]]) + self.putx(sample[0][1], sample[19][2], \ + [3, ['Sample 0x%x' % int(sam, 2), '0x%x' % int(sam, 2)]]) + self.putx(aux_audio_data[0][1], sample[19][2], \ + [4, ['Audio 0x%x' % int(sam_rot, 2), '0x%x' % int(sam_rot, 2)]]) + if validity[0][0] == 0: + self.putx(validity[0][1], validity[0][2], [5, ['V']]) + else: + self.putx(validity[0][1], validity[0][2], [5, ['E']]) + self.putx(subcode_data[0][1], subcode_data[0][2], + [6, ['S: %d' % subcode_data[0][0]]]) + self.putx(channel_status[0][1], channel_status[0][2], + [7, ['C: %d' % channel_status[0][0]]]) + self.putx(parity[0][1], parity[0][2], [8, ['P: %d' % parity[0][0]]]) + + self.subframe = [] + self.seen_preamble = False + self.bitcount = 0 + + def decode_preamble(self): + if self.preamble_state == 0: + self.preamble.append(self.get_pulse_type()) + self.preamble_state = 1 + elif self.preamble_state == 1: + self.preamble.append(self.get_pulse_type()) + self.preamble_state = 2 + elif self.preamble_state == 2: + self.preamble.append(self.get_pulse_type()) + self.preamble_state = 0 + self.state = 'DECODE STREAM' + if self.preamble == [2, 0, 1, 0]: + self.puty([1, ['Preamble W', 'W']]) + elif self.preamble == [2, 2, 1, 1]: + self.puty([1, ['Preamble M', 'M']]) + elif self.preamble == [2, 1, 1, 2]: + self.puty([1, ['Preamble B', 'B']]) + else: + self.puty([1, ['Unknown Preamble', 'Unknown Prea.', 'U']]) + self.preamble = [] + self.seen_preamble = True + self.bitcount = 0 + self.first_one = True + + self.last_preamble = self.samplenum + + def decode(self, ss, es, data): + if not self.samplerate: + raise SamplerateError('Cannot decode without samplerate.') + + for (self.samplenum, pins) in data: + data = pins[0] + + # Initialize self.olddata with the first sample value. + if self.olddata is None: + self.olddata = data + continue + + # First we need to recover the clock. + if self.olddata == data: + self.pulse_width += 1 + continue + + # Found rising or falling edge. + if self.first_edge: + # Throw away first detected edge as it might be mangled data. + self.first_edge = False + self.pulse_width = 0 + else: + if self.state == 'GET FIRST PULSE WIDTH': + self.find_first_pulse_width() + elif self.state == 'GET SECOND PULSE WIDTH': + self.find_second_pulse_width() + elif self.state == 'GET THIRD PULSE WIDTH': + self.find_third_pulse_width() + elif self.state == 'DECODE STREAM': + self.decode_stream() + elif self.state == 'DECODE PREAMBLE': + self.decode_preamble() + + self.pulse_width = 0 + + self.olddata = data diff --git a/decoders/spi/__init__.py b/decoders/spi/__init__.py index a1f0f1c..f76bb06 100644 --- a/decoders/spi/__init__.py +++ b/decoders/spi/__init__.py @@ -29,5 +29,4 @@ transitions where CS# is not asserted are ignored). If CS# is not supplied, data is decoded on every clock transition (depending on SPI mode). ''' -from .pd import * - +from .pd import Decoder diff --git a/decoders/spi/pd.py b/decoders/spi/pd.py index 5bab8f7..4a686dd 100644 --- a/decoders/spi/pd.py +++ b/decoders/spi/pd.py @@ -28,16 +28,19 @@ Packet: [, , ] : - - 'DATA': contains the MISO data, contains the MOSI data. + - 'DATA': contains the MOSI data, contains the MISO data. The data is _usually_ 8 bits (but can also be fewer or more bits). Both data items are Python numbers (not strings), or None if the respective channel was not supplied. - - 'BITS': / contain a list of bit values in this MISO/MOSI data + - 'BITS': / contain a list of bit values in this MOSI/MISO data item, and for each of those also their respective start-/endsample numbers. - 'CS CHANGE': is the old CS# pin value, is the new value. - Both data items are Python numbers (0/1), not strings. + Both data items are Python numbers (0/1), not strings. At the beginning of + the decoding a packet is generated with = None and being the + initial state of the CS# pin or None if the chip select pin is not supplied. Examples: + ['CS-CHANGE', None, 1] ['CS-CHANGE', 1, 0] ['DATA', 0xff, 0x3a] ['BITS', [[1, 80, 82], [1, 83, 84], [1, 85, 86], [1, 87, 88], @@ -60,6 +63,12 @@ spi_mode = { (1, 1): 3, # Mode 3 } +class SamplerateError(Exception): + pass + +class ChannelError(Exception): + pass + class Decoder(srd.Decoder): api_version = 2 id = 'spi' @@ -102,6 +111,10 @@ class Decoder(srd.Decoder): ('mosi-bits', 'MOSI bits', (3,)), ('other', 'Other', (4,)), ) + binary = ( + ('miso', 'MISO'), + ('mosi', 'MOSI'), + ) def __init__(self): self.samplerate = None @@ -110,13 +123,13 @@ class Decoder(srd.Decoder): self.misodata = self.mosidata = 0 self.misobits = [] self.mosibits = [] - self.startsample = -1 + self.ss_block = -1 self.samplenum = -1 self.cs_was_deasserted = False - self.oldcs = -1 + self.oldcs = None self.oldpins = None self.have_cs = self.have_miso = self.have_mosi = None - self.state = 'IDLE' + self.no_cs_notification = False def metadata(self, key, value): if key == srd.SRD_CONF_SAMPLERATE: @@ -125,11 +138,12 @@ class Decoder(srd.Decoder): def start(self): self.out_python = self.register(srd.OUTPUT_PYTHON) self.out_ann = self.register(srd.OUTPUT_ANN) + self.out_bin = self.register(srd.OUTPUT_BINARY) self.out_bitrate = self.register(srd.OUTPUT_META, meta=(int, 'Bitrate', 'Bitrate during transfers')) def putw(self, data): - self.put(self.startsample, self.samplenum, self.out_ann, data) + self.put(self.ss_block, self.samplenum, self.out_ann, data) def putdata(self): # Pass MISO and MOSI bits and then data to the next PD up the stack. @@ -140,8 +154,10 @@ class Decoder(srd.Decoder): if self.have_miso: ss, es = self.misobits[-1][1], self.misobits[0][2] + self.put(ss, es, self.out_bin, (0, bytes([so]))) if self.have_mosi: ss, es = self.mosibits[-1][1], self.mosibits[0][2] + self.put(ss, es, self.out_bin, (1, bytes([si]))) self.put(ss, es, self.out_python, ['BITS', si_bits, so_bits]) self.put(ss, es, self.out_python, ['DATA', si, so]) @@ -167,16 +183,16 @@ class Decoder(srd.Decoder): self.mosibits = [] if self.have_mosi else None self.bitcount = 0 + def cs_asserted(self, cs): + active_low = (self.options['cs_polarity'] == 'active-low') + return (cs == 0) if active_low else (cs == 1) + def handle_bit(self, miso, mosi, clk, cs): # If this is the first bit of a dataword, save its sample number. if self.bitcount == 0: - self.startsample = self.samplenum - self.cs_was_deasserted = False - if self.have_cs: - active_low = (self.options['cs_polarity'] == 'active-low') - deasserted = (cs == 1) if active_low else (cs == 0) - if deasserted: - self.cs_was_deasserted = True + self.ss_block = self.samplenum + self.cs_was_deasserted = \ + not self.cs_asserted(cs) if self.have_cs else False ws = self.options['wordsize'] @@ -222,9 +238,9 @@ class Decoder(srd.Decoder): # Meta bitrate. elapsed = 1 / float(self.samplerate) - elapsed *= (self.samplenum - self.startsample + 1) + elapsed *= (self.samplenum - self.ss_block + 1) bitrate = int(1 / elapsed * self.options['wordsize']) - self.put(self.startsample, self.samplenum, self.out_bitrate, bitrate) + self.put(self.ss_block, self.samplenum, self.out_bitrate, bitrate) if self.have_cs and self.cs_was_deasserted: self.putw([4, ['CS# was deasserted during this data word!']]) @@ -240,6 +256,10 @@ class Decoder(srd.Decoder): # Reset decoder state when CS# changes (and the CS# pin is used). self.reset_decoder_state() + # We only care about samples if CS# is asserted. + if self.have_cs and not self.cs_asserted(cs): + return + # Ignore sample if the clock pin hasn't changed. if clk == self.oldclk: return @@ -261,8 +281,8 @@ class Decoder(srd.Decoder): self.handle_bit(miso, mosi, clk, cs) def decode(self, ss, es, data): - if self.samplerate is None: - raise Exception("Cannot decode without samplerate.") + if not self.samplerate: + raise SamplerateError('Cannot decode without samplerate.') # Either MISO or MOSI can be omitted (but not both). CS# is optional. for (self.samplenum, pins) in data: @@ -276,11 +296,11 @@ class Decoder(srd.Decoder): # Either MISO or MOSI (but not both) can be omitted. if not (self.have_miso or self.have_mosi): - raise Exception('Either MISO or MOSI (or both) pins required.') + raise ChannelError('Either MISO or MOSI (or both) pins required.') - # State machine. - if self.state == 'IDLE': - self.find_clk_edge(miso, mosi, clk, cs) - else: - raise Exception('Invalid state: %s' % self.state) + # Tell stacked decoders that we don't have a CS# signal. + if not self.no_cs_notification and not self.have_cs: + self.put(0, 0, self.out_python, ['CS-CHANGE', None, None]) + self.no_cs_notification = True + self.find_clk_edge(miso, mosi, clk, cs) diff --git a/decoders/spiflash/__init__.py b/decoders/spiflash/__init__.py new file mode 100644 index 0000000..6ffb4da --- /dev/null +++ b/decoders/spiflash/__init__.py @@ -0,0 +1,31 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2012-2015 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, 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 xx25 series +SPI (NOR) flash chip protocol. + +It currently supports the MX25L1605D/MX25L3205D/MX25L6405D. + +Details: +http://www.macronix.com/QuickPlace/hq/PageLibrary4825740B00298A3B.nsf/h_Index/3F21BAC2E121E17848257639003A3146/$File/MX25L1605D-3205D-6405D-1.5.pdf +''' + +from .pd import Decoder diff --git a/decoders/spiflash/lists.py b/decoders/spiflash/lists.py new file mode 100644 index 0000000..4ed6aaf --- /dev/null +++ b/decoders/spiflash/lists.py @@ -0,0 +1,90 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2015 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## 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#'), +} + +device_name = { + 0x14: 'MX25L1605D', + 0x15: 'MX25L3205D', + 0x16: 'MX25L6405D', +} + +chips = { + # Macronix + 'macronix_mx25l1605d': { + 'vendor': 'Macronix', + 'model': 'MX25L1605D', + 'res_id': 0x14, + 'rems_id': 0xc214, + 'rems2_id': 0xc214, + 'rdid_id': 0xc22015, + 'page_size': 256, + 'sector_size': 4 * 1024, + 'block_size': 64 * 1024, + }, + 'macronix_mx25l3205d': { + 'vendor': 'Macronix', + 'model': 'MX25L3205D', + 'res_id': 0x15, + 'rems_id': 0xc215, + 'rems2_id': 0xc215, + 'rdid_id': 0xc22016, + 'page_size': 256, + 'sector_size': 4 * 1024, + 'block_size': 64 * 1024, + }, + 'macronix_mx25l6405d': { + 'vendor': 'Macronix', + 'model': 'MX25L6405D', + 'res_id': 0x16, + 'rems_id': 0xc216, + 'rems2_id': 0xc216, + 'rdid_id': 0xc22017, + 'page_size': 256, + 'sector_size': 4 * 1024, + 'block_size': 64 * 1024, + }, +} diff --git a/decoders/spiflash/pd.py b/decoders/spiflash/pd.py new file mode 100644 index 0000000..0f4dd9b --- /dev/null +++ b/decoders/spiflash/pd.py @@ -0,0 +1,379 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2011-2015 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + +import sigrokdecode as srd +from .lists import * + +def cmd_annotation_classes(): + return tuple([tuple([cmd[0].lower(), cmd[1]]) for cmd in cmds.values()]) + +def decode_status_reg(data): + # TODO: Additional per-bit(s) self.put() calls with correct start/end. + + # Bits[0:0]: WIP (write in progress) + s = 'W' if (data & (1 << 0)) else 'No w' + ret = '%srite operation in progress.\n' % s + + # Bits[1:1]: WEL (write enable latch) + s = '' if (data & (1 << 1)) else 'not ' + ret += 'Internal write enable latch is %sset.\n' % s + + # Bits[5:2]: Block protect bits + # TODO: More detailed decoding (chip-dependent). + ret += 'Block protection bits (BP3-BP0): 0x%x.\n' % ((data & 0x3c) >> 2) + + # Bits[6:6]: Continuously program mode (CP mode) + s = '' if (data & (1 << 6)) else 'not ' + ret += 'Device is %sin continuously program mode (CP mode).\n' % s + + # Bits[7:7]: SRWD (status register write disable) + s = 'not ' if (data & (1 << 7)) else '' + ret += 'Status register writes are %sallowed.\n' % s + + return ret + +class Decoder(srd.Decoder): + api_version = 2 + id = 'spiflash' + name = 'SPI flash' + longname = 'SPI flash chips' + desc = 'xx25 series SPI (NOR) flash chip protocol.' + license = 'gplv2+' + inputs = ['logic'] + outputs = ['spiflash'] + annotations = cmd_annotation_classes() + ( + ('bits', 'Bits'), + ('bits2', 'Bits2'), + ('warnings', 'Warnings'), + ) + annotation_rows = ( + ('bits', 'Bits', (24, 25)), + ('commands', 'Commands', tuple(range(23 + 1))), + ('warnings', 'Warnings', (26,)), + ) + options = ( + {'id': 'chip', 'desc': 'Chip', 'default': tuple(chips.keys())[0], + 'values': tuple(chips.keys())}, + ) + + def __init__(self, **kwargs): + self.state = None + self.cmdstate = 1 + self.addr = 0 + self.data = [] + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + self.chip = chips[self.options['chip']] + + 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 handle_wren(self, mosi, miso): + self.putx([0, ['Command: %s' % cmds[self.state][1]]]) + 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]]]) + elif self.cmdstate == 2: + # Byte 2: Slave sends the JEDEC manufacturer ID. + self.putx([2, ['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]]) + elif self.cmdstate == 4: + # Byte 4: Slave sends the device ID. + self.device_id = miso + self.putx([2, ['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.state = None + else: + self.cmdstate += 1 + + def handle_rdsr(self, mosi, miso): + # Read status register: Master asserts CS#, sends RDSR command, + # reads status register byte. If CS# is kept asserted, the status + # register can be read continuously / multiple times in a row. + # When done, the master de-asserts CS# again. + if self.cmdstate == 1: + # Byte 1: Master sends command ID. + self.putx([3, ['Command: %s' % cmds[self.state][1]]]) + 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.cmdstate += 1 + + def handle_wrsr(self, mosi, miso): + pass # TODO + + 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]]]) + 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 + 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.cmdstate += 1 + + def handle_fast_read(self, mosi, miso): + # Fast read: Master asserts CS#, sends FAST READ command, sends + # 3-byte address + 1 dummy byte, reads >= 1 data bytes, de-asserts CS#. + if self.cmdstate == 1: + # Byte 1: Master sends command ID. + self.putx([5, ['Command: %s' % cmds[self.state][1]]]) + 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)) + 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 + 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. + 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.cmdstate += 1 + + def handle_2read(self, mosi, miso): + pass # TODO + + # 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]]]) + 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)]]) + + if self.cmdstate == 4: + d = 'Erase sector %d (0x%06x)' % (self.addr, self.addr) + self.put(self.ss_block, self.es, self.out_ann, [24, [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.state = None + else: + self.cmdstate += 1 + + def handle_be(self, mosi, miso): + pass # TODO + + def handle_ce(self, mosi, miso): + pass # TODO + + def handle_ce2(self, mosi, miso): + pass # TODO + + def handle_pp(self, mosi, miso): + # Page program: Master asserts CS#, sends PP command, sends 3-byte + # page address, sends >= 1 data bytes, de-asserts CS#. + if self.cmdstate == 1: + # Byte 1: Master sends command ID. + self.putx([12, ['Command: %s' % cmds[self.state][1]]]) + 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 + 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.cmdstate += 1 + + def handle_cp(self, mosi, miso): + pass # TODO + + def handle_dp(self, mosi, miso): + pass # TODO + + def handle_rdp_res(self, mosi, miso): + pass # TODO + + 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]]]) + 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]]) + 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]]) + 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]]) + 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]]) + + 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.state = None + else: + self.cmdstate += 1 + + def handle_rems2(self, mosi, miso): + pass # TODO + + def handle_enso(self, mosi, miso): + pass # TODO + + def handle_exso(self, mosi, miso): + pass # TODO + + def handle_rdscur(self, mosi, miso): + pass # TODO + + def handle_wrscur(self, mosi, miso): + pass # TODO + + def handle_esry(self, mosi, miso): + pass # TODO + + def handle_dsry(self, mosi, miso): + pass # TODO + + def decode(self, ss, es, data): + + ptype, mosi, miso = data + + # if ptype == 'DATA': + # self.putx([0, ['MOSI: 0x%02x, MISO: 0x%02x' % (mosi, miso)]]) + + # 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 != '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]]) + self.state = None diff --git a/decoders/stepper_motor/__init__.py b/decoders/stepper_motor/__init__.py new file mode 100644 index 0000000..222d393 --- /dev/null +++ b/decoders/stepper_motor/__init__.py @@ -0,0 +1,26 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2015 Petteri Aimonen +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + +''' +This PD decodes the stepper motor controller signals (step / dir) and +shows the step speed and absolute position of the stepper motor. +''' + +from .pd import Decoder diff --git a/decoders/stepper_motor/pd.py b/decoders/stepper_motor/pd.py new file mode 100644 index 0000000..1527d48 --- /dev/null +++ b/decoders/stepper_motor/pd.py @@ -0,0 +1,97 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2015 Petteri Aimonen +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, 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 = 'stepper_motor' + name = 'Stepper motor' + longname = 'Stepper motor position / speed' + desc = 'Absolute position and movement speed from step/dir.' + license = 'gplv2+' + inputs = ['logic'] + outputs = ['stepper_motor'] + channels = ( + {'id': 'step', 'name': 'Step', 'desc': 'Step pulse'}, + {'id': 'dir', 'name': 'Direction', 'desc': 'Direction select'}, + ) + options = ( + {'id': 'unit', 'desc': 'Unit', 'default': 'steps', + 'values': ('steps', 'mm')}, + {'id': 'steps_per_mm', 'desc': 'Steps per mm', 'default': 100.0}, + ) + annotations = ( + ('speed', 'Speed'), + ('position', 'Position') + ) + annotation_rows = ( + ('speed', 'Speed', (0,)), + ('position', 'Position', (1,)), + ) + + def __init__(self, **kwargs): + self.oldstep = None + self.prev_step_ss = None + self.pos = 0 + self.prev_speed = None + self.prev_pos = None + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + if self.options['unit'] == 'steps': + self.scale = 1 + self.format = '%0.0f' + self.unit = 'steps' + else: + self.scale = self.options['steps_per_mm'] + self.format = '%0.2f' + self.unit = 'mm' + + def step(self, ss, direction): + if self.prev_step_ss is not None: + delta = ss - self.prev_step_ss + 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, + [0, [speed_txt + ' ' + self.unit + '/s', speed_txt]]) + self.put(self.prev_step_ss, ss, self.out_ann, + [1, [pos_txt + ' ' + self.unit, pos_txt]]) + + self.pos += (1 if direction else -1) + self.prev_step_ss = ss + + def metadata(self, key, value): + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + + def decode(self, ss, es, data): + if not self.samplerate: + raise SamplerateError('Cannot decode without samplerate.') + + for (self.samplenum, (step, direction)) in data: + if step == 1 and self.oldstep == 0: + self.step(self.samplenum, direction) + self.oldstep = step diff --git a/decoders/swd/__init__.py b/decoders/swd/__init__.py new file mode 100644 index 0000000..3a65143 --- /dev/null +++ b/decoders/swd/__init__.py @@ -0,0 +1,35 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 Angus Gratton +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + +''' +This PD decodes the ARM SWD (version 1) protocol, as described in the +"ARM Debug Interface v5.2" Architecture Specification. + +Details: +http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ihi0031c/index.html +(Registration required) + +Not supported: + * Turnaround periods other than the default 1, as set in DLCR.TURNROUND + (should be trivial to add) + * SWD protocol version 2 (multi-drop support, etc.) +''' + +from .pd import Decoder diff --git a/decoders/swd/pd.py b/decoders/swd/pd.py new file mode 100644 index 0000000..d53d149 --- /dev/null +++ b/decoders/swd/pd.py @@ -0,0 +1,349 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 Angus Gratton +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + +import sigrokdecode as srd +import re + +''' +OUTPUT_PYTHON format: + +Packet: +[, ] + +: + - 'AP_READ' (AP read) + - 'DP_READ' (DP read) + - 'AP_WRITE' (AP write) + - 'DP_WRITE' (DP write) + - 'LINE_RESET' (line reset sequence) + +: + - tuple of address, ack state, data for the given sequence +''' + +swd_states = [ + 'IDLE', # Idle/unknown + 'REQUEST', # Request phase (first 8 bits) + 'ACK', # Ack phase (next 3 bits) + 'READ', # Reading phase (next 32 bits for reads) + 'WRITE', # Writing phase (next 32 bits for write) + 'DPARITY', # Data parity phase +] + +# Regexes for matching SWD data out of bitstring ('1' / '0' characters) format +RE_SWDSWITCH = re.compile(bin(0xE79E)[:1:-1] + '$') +RE_SWDREQ = re.compile(r'1(?P.)(?P.)(?P..)(?P.)01$') +RE_IDLE = re.compile('0' * 50 + '$') + +# Sample edges +RISING = 1 +FALLING = 0 + +ADDR_DP_SELECT = 0x8 +ADDR_DP_CTRLSTAT = 0x4 + +BIT_SELECT_CTRLSEL = 1 +BIT_CTRLSTAT_ORUNDETECT = 1 + +ANNOTATIONS = ['reset', 'enable', 'read', 'write', 'ack', 'data', 'parity'] + +class Decoder(srd.Decoder): + api_version = 2 + id = 'swd' + name = 'SWD' + longname = 'Serial Wire Debug' + desc = 'Two-wire protocol for debug access to ARM CPUs.' + license = 'gplv2+' + inputs = ['logic'] + outputs = ['swd'] + channels = ( + {'id': 'swclk', 'name': 'SWCLK', 'desc': 'Master clock'}, + {'id': 'swdio', 'name': 'SWDIO', 'desc': 'Data input/output'}, + ) + options = ( + {'id': 'strict_start', + 'desc': 'Wait for a line reset before starting to decode', + 'default': 'no', 'values': ('yes', 'no')}, + ) + annotations = ( + ('reset', 'RESET'), + ('enable', 'ENABLE'), + ('read', 'READ'), + ('write', 'WRITE'), + ('ack', 'ACK'), + ('data', 'DATA'), + ('parity', 'PARITY'), + ) + + def __init__(self, **kwargs): + # SWD data/clock state + self.state = 'UNKNOWN' + self.oldclk = -1 + self.sample_edge = RISING + self.ack = None # Ack state of the current phase + self.ss_req = 0 # Start sample of current req + self.turnaround = 0 # Number of turnaround edges to ignore before continuing + self.bits = '' # Bits from SWDIO are accumulated here, matched against expected sequences + self.samplenums = [] # Sample numbers that correspond to the samples in self.bits + self.linereset_count = 0 + + # SWD debug port state + self.data = None + self.addr = None + self.rw = None # Are we inside an SWD read or a write? + self.ctrlsel = 0 # 'ctrlsel' is bit 0 in the SELECT register. + self.orundetect = 0 # 'orundetect' is bit 0 in the CTRLSTAT register. + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + self.out_python = self.register(srd.OUTPUT_PYTHON) + if self.options['strict_start'] == 'no': + self.state = 'REQ' # No need to wait for a LINE RESET. + + def putx(self, ann, length, data): + '''Output annotated data.''' + ann = ANNOTATIONS.index(ann) + try: + ss = self.samplenums[-length] + except IndexError: + ss = self.samplenums[0] + if self.state == 'REQ': + self.ss_req = ss + es = self.samplenum + self.put(ss, es, self.out_ann, [ann, [data]]) + + def putp(self, ptype, pdata): + self.put(self.ss_req, self.samplenum, self.out_python, [ptype, pdata]) + + def put_python_data(self): + '''Emit Python data item based on current SWD packet contents.''' + ptype = { + ('AP', 'R'): 'AP_READ', + ('AP', 'W'): 'AP_WRITE', + ('DP', 'R'): 'DP_READ', + ('DP', 'W'): 'DP_WRITE', + }[(self.apdp, self.rw)] + self.putp(ptype, (self.addr, self.data, self.ack)) + + def decode(self, ss, es, data): + for (self.samplenum, (clk, dio)) in data: + if clk == self.oldclk: + continue # Not a clock edge. + self.oldclk = clk + + # Count rising edges with DIO held high, + # as a line reset (50+ high edges) can happen from any state. + if clk == RISING: + if dio == 1: + self.linereset_count += 1 + else: + if self.linereset_count >= 50: + self.putx('reset', self.linereset_count, 'LINERESET') + self.putp('LINE_RESET', None) + self.reset_state() + self.linereset_count = 0 + + # Otherwise, we only care about either rising or falling edges + # (depending on sample_edge, set according to current state). + if clk != self.sample_edge: + continue + + # Turnaround bits get skipped. + if self.turnaround > 0: + self.turnaround -= 1 + continue + + self.bits += str(dio) + self.samplenums.append(self.samplenum) + { + 'UNKNOWN': self.handle_unknown_edge, + 'REQ': self.handle_req_edge, + 'ACK': self.handle_ack_edge, + 'DATA': self.handle_data_edge, + 'DPARITY': self.handle_dparity_edge, + }[self.state]() + + def next_state(self): + '''Step to the next SWD state, reset internal counters accordingly.''' + self.bits = '' + self.samplenums = [] + self.linereset_count = 0 + if self.state == 'UNKNOWN': + self.state = 'REQ' + self.sample_edge = RISING + self.turnaround = 0 + elif self.state == 'REQ': + self.state = 'ACK' + self.sample_edge = FALLING + self.turnaround = 1 + elif self.state == 'ACK': + self.state = 'DATA' + self.sample_edge = RISING if self.rw == 'W' else FALLING + self.turnaround = 0 if self.rw == 'R' else 2 + elif self.state == 'DATA': + self.state = 'DPARITY' + elif self.state == 'DPARITY': + self.put_python_data() + self.state = 'REQ' + self.sample_edge = RISING + self.turnaround = 1 if self.rw == 'R' else 0 + + def reset_state(self): + '''Line reset (or equivalent), wait for a new pending SWD request.''' + if self.state != 'REQ': # Emit a Python data item. + self.put_python_data() + # Clear state. + self.bits = '' + self.samplenums = [] + self.linereset_count = 0 + self.turnaround = 0 + self.sample_edge = RISING + self.data = '' + self.ack = None + self.state = 'REQ' + + def handle_unknown_edge(self): + ''' + Clock edge in the UNKNOWN state. + In the unknown state, clock edges get ignored until we see a line + reset (which is detected in the decode method, not here.) + ''' + pass + + def handle_req_edge(self): + '''Clock edge in the REQ state (waiting for SWD r/w request).''' + # Check for a JTAG->SWD enable sequence. + m = re.search(RE_SWDSWITCH, self.bits) + if m is not None: + self.putx('enable', 16, 'JTAG->SWD') + self.reset_state() + return + + # Or a valid SWD Request packet. + m = re.search(RE_SWDREQ, self.bits) + if m is not None: + calc_parity = sum([int(x) for x in m.group('rw') + m.group('apdp') + m.group('addr')]) % 2 + parity = '' if str(calc_parity) == m.group('parity') else 'E' + self.rw = 'R' if m.group('rw') == '1' else 'W' + self.apdp = 'AP' if m.group('apdp') == '1' else 'DP' + self.addr = int(m.group('addr')[::-1], 2) << 2 + self.putx('read' if self.rw == 'R' else 'write', 8, self.get_address_description()) + self.next_state() + return + + def handle_ack_edge(self): + '''Clock edge in the ACK state (waiting for complete ACK sequence).''' + if len(self.bits) < 3: + return + if self.bits == '100': + self.putx('ack', 3, 'OK') + self.ack = 'OK' + self.next_state() + elif self.bits == '001': + self.putx('ack', 3, 'FAULT') + self.ack = 'FAULT' + if self.orundetect == 1: + self.next_state() + else: + self.reset_state() + self.turnaround = 1 + elif self.bits == '010': + self.putx('ack', 3, 'WAIT') + self.ack = 'WAIT' + if self.orundetect == 1: + self.next_state() + else: + self.reset_state() + self.turnaround = 1 + elif self.bits == '111': + self.putx('ack', 3, 'NOREPLY') + self.ack = 'NOREPLY' + self.reset_state() + else: + self.putx('ack', 3, 'ERROR') + self.ack = 'ERROR' + self.reset_state() + + def handle_data_edge(self): + '''Clock edge in the DATA state (waiting for 32 bits to clock past).''' + if len(self.bits) < 32: + return + self.data = 0 + self.dparity = 0 + for x in range(32): + if self.bits[x] == '1': + self.data += (1 << x) + self.dparity += 1 + self.dparity = self.dparity % 2 + + self.putx('data', 32, '0x%08x' % self.data) + self.next_state() + + def handle_dparity_edge(self): + '''Clock edge in the DPARITY state (clocking in parity bit).''' + if str(self.dparity) != self.bits: + self.putx('parity', 1, str(self.dparity) + self.bits) # PARITY ERROR + elif self.rw == 'W': + self.handle_completed_write() + self.next_state() + + def handle_completed_write(self): + ''' + Update internal state of the debug port based on a completed + write operation. + ''' + if self.apdp != 'DP': + return + elif self.addr == ADDR_DP_SELECT: + self.ctrlsel = self.data & BIT_SELECT_CTRLSEL + elif self.addr == ADDR_DP_CTRLSTAT and self.ctrlsel == 0: + self.orundetect = self.data & BIT_CTRLSTAT_ORUNDETECT + + def get_address_description(self): + ''' + Return a human-readable description of the currently selected address, + for annotated results. + ''' + if self.apdp == 'DP': + if self.rw == 'R': + # Tables 2-4 & 2-5 in ADIv5.2 spec ARM document IHI 0031C + return { + 0: 'IDCODE', + 0x4: 'R CTRL/STAT' if self.ctrlsel == 0 else 'R DLCR', + 0x8: 'RESEND', + 0xC: 'RDBUFF' + }[self.addr] + elif self.rw == 'W': + # Tables 2-4 & 2-5 in ADIv5.2 spec ARM document IHI 0031C + return { + 0: 'W ABORT', + 0x4: 'W CTRL/STAT' if self.ctrlsel == 0 else 'W DLCR', + 0x8: 'W SELECT', + 0xC: 'W RESERVED' + }[self.addr] + elif self.apdp == 'AP': + if self.rw == 'R': + return 'R AP%x' % self.addr + elif self.rw == 'W': + return 'W AP%x' % self.addr + + # Any legitimate operations shouldn't fall through to here, probably + # a decoder bug. + return '? %s%s%x' % (self.rw, self.apdp, self.addr) diff --git a/decoders/tca6408a/__init__.py b/decoders/tca6408a/__init__.py new file mode 100644 index 0000000..f679674 --- /dev/null +++ b/decoders/tca6408a/__init__.py @@ -0,0 +1,26 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 alberink +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + +''' +This decoder stacks on top of the 'i2c' PD and decodes the Texas Instruments +TCA6408A 8-bit I²C I/O expander protocol. +''' + +from .pd import Decoder diff --git a/decoders/tca6408a/pd.py b/decoders/tca6408a/pd.py new file mode 100644 index 0000000..1f90245 --- /dev/null +++ b/decoders/tca6408a/pd.py @@ -0,0 +1,129 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2012 Uwe Hermann +## Copyright (C) 2013 Matt Ranostay +## Copyright (C) 2014 alberink +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, 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 = 'tca6408a' + name = 'TI TCA6408A' + longname = 'Texas Instruments TCA6408A' + desc = 'Texas Instruments TCA6408A 8-bit I²C I/O expander.' + license = 'gplv2+' + inputs = ['i2c'] + outputs = ['tca6408a'] + annotations = ( + ('register', 'Register type'), + ('value', 'Register value'), + ('warnings', 'Warning messages'), + ) + annotation_rows = ( + ('regs', 'Registers', (0, 1)), + ('warnings', 'Warnings', (2,)), + ) + + def __init__(self, **kwargs): + self.state = 'IDLE' + self.chip = -1 + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def putx(self, data): + self.put(self.ss, self.es, self.out_ann, data) + + def handle_reg_0x00(self, b): + self.putx([1, ['State of inputs: %02X' % b]]) + + def handle_reg_0x01(self, b): + self.putx([1, ['Outputs set: %02X' % b ]]) + + def handle_reg_0x02(self, b): + self.putx([1, ['Polarity inverted: %02X' % b]]) + + def handle_reg_0x03(self, b): + self.putx([1, ['Configuration: %02X' % b]]) + + def handle_write_reg(self, b): + if b == 0: + self.putx([0, ['Input port', 'In', 'I']]) + elif b == 1: + self.putx([0, ['Output port', 'Out', 'O']]) + elif b == 2: + self.putx([0, ['Polarity inversion register', 'Pol', 'P']]) + elif b == 3: + self.putx([0, ['Configuration register', 'Conf', 'C']]) + + def check_correct_chip(self, addr): + if addr not in (0x20, 0x21): + self.putx([2, ['Warning: I²C slave 0x%02X not a TCA6408A ' + 'compatible chip.' % addr]]) + self.state = 'IDLE' + + def decode(self, ss, es, data): + cmd, databyte = data + + # Store the start/end samples of this I²C packet. + self.ss, self.es = ss, es + + # State machine. + if self.state == 'IDLE': + # Wait for an I²C START condition. + if cmd != 'START': + return + self.state = 'GET SLAVE ADDR' + elif self.state == 'GET SLAVE ADDR': + self.chip = databyte + self.state = 'GET REG ADDR' + elif self.state == 'GET REG ADDR': + # Wait for a data write (master selects the slave register). + if cmd in ('ADDRESS READ', 'ADDRESS WRITE'): + self.check_correct_chip(databyte) + if cmd != 'DATA WRITE': + return + self.reg = databyte + self.handle_write_reg(self.reg) + self.state = 'WRITE IO REGS' + elif self.state == 'WRITE IO REGS': + # If we see a Repeated Start here, the master wants to read. + if cmd == 'START REPEAT': + self.state = 'READ IO REGS' + return + # Otherwise: Get data bytes until a STOP condition occurs. + if cmd == 'DATA WRITE': + handle_reg = getattr(self, 'handle_reg_0x%02x' % self.reg) + handle_reg(databyte) + elif cmd == 'STOP': + self.state = 'IDLE' + self.chip = -1 + elif self.state == 'READ IO REGS': + # Wait for an address read operation. + if cmd == 'ADDRESS READ': + self.state = 'READ IO REGS2' + self.chip = databyte + return + elif self.state == 'READ IO REGS2': + if cmd == 'DATA READ': + handle_reg = getattr(self, 'handle_reg_0x%02x' % self.reg) + handle_reg(databyte) + elif cmd == 'STOP': + self.state = 'IDLE' diff --git a/decoders/timing/__init__.py b/decoders/timing/__init__.py new file mode 100644 index 0000000..ee31509 --- /dev/null +++ b/decoders/timing/__init__.py @@ -0,0 +1,25 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 Torsten Duwe +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + +''' +Timing decoder, find the time between edges. +''' + +from .pd import Decoder diff --git a/decoders/timing/pd.py b/decoders/timing/pd.py new file mode 100644 index 0000000..6bca120 --- /dev/null +++ b/decoders/timing/pd.py @@ -0,0 +1,94 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 Torsten Duwe +## Copyright (C) 2014 Sebastien Bourdelin +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, 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 + +def normalize_time(t): + if t >= 1.0: + return '%.3f s' % t + elif t >= 0.001: + return '%.3f ms' % (t * 1000.0) + elif t >= 0.000001: + return '%.3f μs' % (t * 1000.0 * 1000.0) + elif t >= 0.000000001: + return '%.3f ns' % (t * 1000.0 * 1000.0 * 1000.0) + else: + return '%f' % t + +class Decoder(srd.Decoder): + api_version = 2 + id = 'timing' + name = 'Timing' + longname = 'Timing calculation' + desc = 'Calculate time between edges.' + license = 'gplv2+' + inputs = ['logic'] + outputs = ['timing'] + channels = ( + {'id': 'data', 'name': 'Data', 'desc': 'Data line'}, + ) + annotations = ( + ('time', 'Time'), + ) + annotation_rows = ( + ('time', 'Time', (0,)), + ) + + def __init__(self, **kwargs): + self.samplerate = None + self.oldpin = None + self.last_samplenum = None + + def metadata(self, key, value): + if key == srd.SRD_CONF_SAMPLERATE: + self.samplerate = value + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def decode(self, ss, es, data): + 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 + + if self.oldpin is None: + self.oldpin = pin + self.last_samplenum = samplenum + continue + + if self.oldpin != pin: + samples = samplenum - self.last_samplenum + t = samples / self.samplerate + + # Report the timing normalized. + self.put(self.last_samplenum, samplenum, self.out_ann, + [0, [normalize_time(t)]]) + + # Store data for next round. + self.last_samplenum = samplenum + self.oldpin = pin diff --git a/decoders/tlc5620/__init__.py b/decoders/tlc5620/__init__.py index c615760..cb95385 100644 --- a/decoders/tlc5620/__init__.py +++ b/decoders/tlc5620/__init__.py @@ -22,5 +22,4 @@ The Texas Instruments TLC5620 is an 8-bit quad DAC. ''' -from .pd import * - +from .pd import Decoder diff --git a/decoders/tlc5620/pd.py b/decoders/tlc5620/pd.py index f8c90d9..2a407d1 100644 --- a/decoders/tlc5620/pd.py +++ b/decoders/tlc5620/pd.py @@ -65,7 +65,7 @@ class Decoder(srd.Decoder): self.out_ann = self.register(srd.OUTPUT_ANN) def handle_11bits(self): - s = "".join(str(i) for i in self.bits[:2]) + s = ''.join(str(i) for i in self.bits[:2]) self.dac_select = s = dacs[int(s, 2)] self.put(self.ss_dac, self.es_dac, self.out_ann, [0, ['DAC select: %s' % s, 'DAC sel: %s' % s, @@ -75,7 +75,7 @@ class Decoder(srd.Decoder): self.put(self.ss_gain, self.es_gain, self.out_ann, [1, ['Gain: x%d' % g, 'G: x%d' % g, 'x%d' % g]]) - s = "".join(str(i) for i in self.bits[3:]) + s = ''.join(str(i) for i in self.bits[3:]) self.dac_value = v = int(s, 2) self.put(self.ss_value, self.es_value, self.out_ann, [2, ['DAC value: %d' % v, 'Value: %d' % v, 'Val: %d' % v, @@ -128,4 +128,3 @@ class Decoder(srd.Decoder): self.oldclk = clk self.oldload = load self.oldldac = ldac - diff --git a/decoders/uart/__init__.py b/decoders/uart/__init__.py index f3c0693..efe0e52 100644 --- a/decoders/uart/__init__.py +++ b/decoders/uart/__init__.py @@ -38,5 +38,4 @@ it though, no matter whether the source was on TTL UART levels, or RS232, or others. ''' -from .pd import * - +from .pd import Decoder diff --git a/decoders/uart/pd.py b/decoders/uart/pd.py index ae99874..7995e99 100644 --- a/decoders/uart/pd.py +++ b/decoders/uart/pd.py @@ -19,6 +19,7 @@ ## import sigrokdecode as srd +from math import floor, ceil ''' OUTPUT_PYTHON format: @@ -28,9 +29,10 @@ Packet: This is the list of s and their respective values: - 'STARTBIT': The data is the (integer) value of the start bit (0/1). - - 'DATA': The data is 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). - - 'DATABITS': List of data bits and their ss/es numbers. + - '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). + - 2nd item: the list of individual data bits and their ss/es numbers. - 'PARITYBIT': The data is the (integer) value of the parity bit (0/1). - 'STOPBIT': The data is the (integer) value of the stop bit (0 or 1). - 'INVALID STARTBIT': The data is the (integer) value of the start bit (0/1). @@ -66,8 +68,12 @@ def parity_ok(parity_type, parity_bit, data, num_data_bits): return (ones % 2) == 1 elif parity_type == 'even': return (ones % 2) == 0 - else: - raise Exception('Invalid parity type: %d' % parity_type) + +class SamplerateError(Exception): + pass + +class ChannelError(Exception): + pass class Decoder(srd.Decoder): api_version = 2 @@ -98,7 +104,10 @@ class Decoder(srd.Decoder): 'values': ('lsb-first', 'msb-first')}, {'id': 'format', 'desc': 'Data format', 'default': 'ascii', 'values': ('ascii', 'dec', 'hex', 'oct', 'bin')}, - # TODO: Options to invert the signal(s). + {'id': 'invert_rx', 'desc': 'Invert RX?', 'default': 'no', + 'values': ('yes', 'no')}, + {'id': 'invert_tx', 'desc': 'Invert TX?', 'default': 'no', + 'values': ('yes', 'no')}, ) annotations = ( ('rx-data', 'RX data'), @@ -131,24 +140,24 @@ class Decoder(srd.Decoder): ) def putx(self, rxtx, data): - s, halfbit = self.startsample[rxtx], int(self.bit_width / 2) - self.put(s - halfbit, self.samplenum + halfbit, self.out_ann, data) + s, halfbit = self.startsample[rxtx], self.bit_width / 2.0 + self.put(s - floor(halfbit), self.samplenum + ceil(halfbit), self.out_ann, data) def putpx(self, rxtx, data): - s, halfbit = self.startsample[rxtx], int(self.bit_width / 2) - self.put(s - halfbit, self.samplenum + halfbit, self.out_python, data) + s, halfbit = self.startsample[rxtx], self.bit_width / 2.0 + self.put(s - floor(halfbit), self.samplenum + ceil(halfbit), self.out_python, data) def putg(self, data): - s, halfbit = self.samplenum, int(self.bit_width / 2) - self.put(s - halfbit, s + halfbit, self.out_ann, data) + s, halfbit = self.samplenum, self.bit_width / 2.0 + self.put(s - floor(halfbit), s + ceil(halfbit), self.out_ann, data) def putp(self, data): - s, halfbit = self.samplenum, int(self.bit_width / 2) - self.put(s - halfbit, s + halfbit, self.out_python, data) + s, halfbit = self.samplenum, self.bit_width / 2.0 + self.put(s - floor(halfbit), s + ceil(halfbit), self.out_python, data) def putbin(self, rxtx, data): - s, halfbit = self.startsample[rxtx], int(self.bit_width / 2) - self.put(s - halfbit, self.samplenum + halfbit, self.out_bin, data) + s, halfbit = self.startsample[rxtx], self.bit_width / 2.0 + self.put(s - floor(halfbit), self.samplenum + ceil(halfbit), self.out_bin, data) def __init__(self, **kwargs): self.samplerate = None @@ -172,7 +181,7 @@ class Decoder(srd.Decoder): def metadata(self, key, value): if key == srd.SRD_CONF_SAMPLERATE: - self.samplerate = value; + self.samplerate = value # The width of one UART bit in number of samples. self.bit_width = float(self.samplerate) / float(self.options['baudrate']) @@ -181,7 +190,9 @@ class Decoder(srd.Decoder): # bitpos is the samplenumber which is in the middle of the # specified UART bit (0 = start bit, 1..x = data, x+1 = parity bit # (if used) or the first stop bit, and so on). - bitpos = self.frame_start[rxtx] + (self.bit_width / 2.0) + # The samples within bit are 0, 1, ..., (bit_width - 1), therefore + # index of the middle sample within bit window is (bit_width - 1) / 2. + bitpos = self.frame_start[rxtx] + (self.bit_width - 1) / 2.0 bitpos += bitnum * self.bit_width if self.samplenum >= bitpos: return True @@ -239,12 +250,9 @@ class Decoder(srd.Decoder): self.databyte[rxtx] >>= 1 self.databyte[rxtx] |= \ (signal << (self.options['num_data_bits'] - 1)) - elif self.options['bit_order'] == 'msb-first': + else: self.databyte[rxtx] <<= 1 self.databyte[rxtx] |= (signal << 0) - else: - raise Exception('Invalid bit order value: %s', - self.options['bit_order']) self.putg([rxtx + 12, ['%d' % signal]]) @@ -259,8 +267,8 @@ class Decoder(srd.Decoder): self.state[rxtx] = 'GET PARITY BIT' - self.putpx(rxtx, ['DATABITS', rxtx, self.databits[rxtx]]) - self.putpx(rxtx, ['DATA', rxtx, self.databyte[rxtx]]) + self.putpx(rxtx, ['DATA', rxtx, + (self.databyte[rxtx], self.databits[rxtx])]) b, f = self.databyte[rxtx], self.options['format'] if f == 'ascii': @@ -274,8 +282,6 @@ class Decoder(srd.Decoder): self.putx(rxtx, [rxtx, [oct(b)[2:].zfill(3)]]) elif f == 'bin': self.putx(rxtx, [rxtx, [bin(b)[2:].zfill(8)]]) - else: - raise Exception('Invalid data format option: %s' % f) self.putbin(rxtx, (rxtx, bytes([b]))) self.putbin(rxtx, (2, bytes([b]))) @@ -327,8 +333,8 @@ class Decoder(srd.Decoder): self.putg([rxtx + 4, ['Stop bit', 'Stop', 'T']]) def decode(self, ss, es, data): - if self.samplerate is None: - raise Exception("Cannot decode without samplerate.") + if not self.samplerate: + raise SamplerateError('Cannot decode without samplerate.') for (self.samplenum, pins) in data: # Note: Ignoring identical samples here for performance reasons @@ -337,10 +343,15 @@ class Decoder(srd.Decoder): # continue self.oldpins, (rx, tx) = pins, pins + if self.options['invert_rx'] == 'yes': + rx = not rx + if self.options['invert_tx'] == 'yes': + tx = not tx + # Either RX or TX (but not both) can be omitted. has_pin = [rx in (0, 1), tx in (0, 1)] if has_pin == [False, False]: - raise Exception('Either TX or RX (or both) pins required.') + raise ChannelError('Either TX or RX (or both) pins required.') # State machine. for rxtx in (RX, TX): @@ -360,9 +371,6 @@ class Decoder(srd.Decoder): self.get_parity_bit(rxtx, signal) elif self.state[rxtx] == 'GET STOP BITS': self.get_stop_bits(rxtx, signal) - else: - raise Exception('Invalid state: %s' % self.state[rxtx]) # Save current RX/TX values for the next round. self.oldbit[rxtx] = signal - diff --git a/decoders/usb_packet/__init__.py b/decoders/usb_packet/__init__.py index 06b67b9..f841d20 100644 --- a/decoders/usb_packet/__init__.py +++ b/decoders/usb_packet/__init__.py @@ -41,5 +41,4 @@ https://en.wikipedia.org/wiki/USB http://www.usb.org/developers/docs/ ''' -from .pd import * - +from .pd import Decoder diff --git a/decoders/usb_packet/pd.py b/decoders/usb_packet/pd.py index 8f86cc7..a9c8b25 100644 --- a/decoders/usb_packet/pd.py +++ b/decoders/usb_packet/pd.py @@ -192,7 +192,6 @@ class Decoder(srd.Decoder): ) def __init__(self): - self.samplenum = 0 self.bits = [] self.packet = [] self.packet_summary = '' @@ -338,6 +337,3 @@ class Decoder(srd.Decoder): self.bits, self.state = [], 'WAIT FOR SOP' else: pass # TODO: Error - else: - raise Exception('Invalid state: %s' % self.state) - diff --git a/decoders/usb_signalling/__init__.py b/decoders/usb_signalling/__init__.py index 2311b1f..b2ceb4d 100644 --- a/decoders/usb_signalling/__init__.py +++ b/decoders/usb_signalling/__init__.py @@ -48,5 +48,4 @@ https://en.wikipedia.org/wiki/USB http://www.usb.org/developers/docs/ ''' -from .pd import * - +from .pd import Decoder diff --git a/decoders/usb_signalling/pd.py b/decoders/usb_signalling/pd.py index 8c6a9c3..8ba55d9 100644 --- a/decoders/usb_signalling/pd.py +++ b/decoders/usb_signalling/pd.py @@ -73,6 +73,9 @@ sym_idx = { 'SE1': 3, } +class SamplerateError(Exception): + pass + class Decoder(srd.Decoder): api_version = 2 id = 'usb_signalling' @@ -217,8 +220,8 @@ class Decoder(srd.Decoder): self.oldsym = sym def decode(self, ss, es, data): - if self.samplerate is None: - raise Exception("Cannot decode without samplerate.") + if not self.samplerate: + raise SamplerateError('Cannot decode without samplerate.') for (self.samplenum, pins) in data: # State machine. if self.state == 'IDLE': @@ -237,6 +240,3 @@ class Decoder(srd.Decoder): self.get_bit(sym) elif self.state == 'GET EOP': self.get_eop(sym) - else: - raise Exception('Invalid state: %s' % self.state) - diff --git a/decoders/xfp/__init__.py b/decoders/xfp/__init__.py index fa4e8a2..51936e5 100644 --- a/decoders/xfp/__init__.py +++ b/decoders/xfp/__init__.py @@ -37,5 +37,4 @@ The XFP specification is available here: ftp://ftp.seagate.com/sff/INF-8077.PDF ''' -from .pd import * - +from .pd import Decoder diff --git a/decoders/xfp/pd.py b/decoders/xfp/pd.py index b8ec8ed..41aca0d 100644 --- a/decoders/xfp/pd.py +++ b/decoders/xfp/pd.py @@ -318,7 +318,7 @@ class Decoder(srd.Decoder): self.annotate("Vendor ID", chr(data[i]), cnt, cnt) # Convert 16-bit two's complement values, with each increment - # representing 1/256C, to degrees Celcius. + # representing 1/256C, to degrees Celsius. def to_temp(self, value): if value & 0x8000: value = -((value ^ 0xffff) + 1) @@ -644,4 +644,3 @@ class Decoder(srd.Decoder): self.annotate("AUX1 monitoring", aux) aux = AUX_TYPES[data[0] & 0x0f] self.annotate("AUX2 monitoring", aux) -