From: Uwe Hermann Date: Wed, 21 Nov 2012 21:43:02 +0000 (+0100) Subject: All PDs: Name the files pd.py consistently. X-Git-Tag: libsigrokdecode-0.1.1~21 X-Git-Url: https://sigrok.org/gitweb/?p=libsigrokdecode.git;a=commitdiff_plain;h=24c74fd30fb161837c5f8b01baf3c0fe2dfa4ed5 All PDs: Name the files pd.py consistently. The Python module name is determined by the directory name (e.g. dcf77), the *.py file names in that directory don't matter and can be kept consistent. --- diff --git a/decoders/dcf77/Makefile.am b/decoders/dcf77/Makefile.am index c55f0b7..33ea1c7 100644 --- a/decoders/dcf77/Makefile.am +++ b/decoders/dcf77/Makefile.am @@ -20,7 +20,7 @@ pkgdatadir = $(DECODERS_DIR)/dcf77 -dist_pkgdata_DATA = __init__.py dcf77.py +dist_pkgdata_DATA = __init__.py pd.py CLEANFILES = *.pyc diff --git a/decoders/dcf77/__init__.py b/decoders/dcf77/__init__.py index bef505b..e2ed26b 100644 --- a/decoders/dcf77/__init__.py +++ b/decoders/dcf77/__init__.py @@ -25,5 +25,5 @@ Details: http://en.wikipedia.org/wiki/DCF77 ''' -from .dcf77 import * +from .pd import * diff --git a/decoders/dcf77/dcf77.py b/decoders/dcf77/dcf77.py deleted file mode 100644 index c4132e9..0000000 --- a/decoders/dcf77/dcf77.py +++ /dev/null @@ -1,288 +0,0 @@ -## -## This file is part of the sigrok 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 -## - -# DCF77 protocol decoder - -import sigrokdecode as srd -import calendar - -# Return the specified BCD number (max. 8 bits) as integer. -def bcd2int(b): - return (b & 0x0f) + ((b >> 4) * 10) - -class Decoder(srd.Decoder): - api_version = 1 - id = 'dcf77' - name = 'DCF77' - longname = 'DCF77 time protocol' - desc = 'European longwave time signal (77.5kHz carrier signal).' - license = 'gplv2+' - inputs = ['logic'] - outputs = ['dcf77'] - probes = [ - {'id': 'data', 'name': 'DATA', 'desc': 'DATA line'}, - ] - optional_probes = [ - {'id': 'pon', 'name': 'PON', 'desc': 'Power on'}, - ] - options = {} - annotations = [ - ['Text', 'Human-readable text'], - ['Warnings', 'Human-readable warnings'], - ] - - def __init__(self, **kwargs): - self.state = 'WAIT FOR RISING EDGE' - self.oldpins = None - self.oldval = None - self.oldpon = None - self.samplenum = 0 - self.bit_start = 0 - self.bit_start_old = 0 - self.bitcount = 0 # Counter for the DCF77 bits (0..58) - self.dcf77_bitnumber_is_known = 0 - - def start(self, metadata): - self.samplerate = metadata['samplerate'] - # self.out_proto = self.add(srd.OUTPUT_PROTO, 'dcf77') - self.out_ann = self.add(srd.OUTPUT_ANN, 'dcf77') - - def report(self): - pass - - # TODO: Which range to use? Only the 100ms/200ms or full second? - def handle_dcf77_bit(self, bit): - c = self.bitcount - a = self.out_ann - ss = es = 0 # FIXME - - # Create one annotation for each DCF77 bit (containing the 0/1 value). - # Use 'Unknown DCF77 bit x: val' if we're not sure yet which of the - # 0..58 bits it is (because we haven't seen a 'new minute' marker yet). - # Otherwise, use 'DCF77 bit x: val'. - s = '' if self.dcf77_bitnumber_is_known else 'Unknown ' - self.put(ss, es, a, [0, ['%sDCF77 bit %d: %d' % (s, c, bit)]]) - - # If we're not sure yet which of the 0..58 DCF77 bits we have, return. - # We don't want to decode bogus data. - if not self.dcf77_bitnumber_is_known: - return - - # Output specific "decoded" annotations for the respective DCF77 bits. - if c == 0: - # Start of minute: DCF bit 0. - if bit == 0: - self.put(ss, es, a, [0, ['Start of minute (always 0)']]) - else: - self.put(ss, es, a, [0, ['ERROR: Start of minute != 0']]) - elif c in range(1, 14 + 1): - # Special bits (civil warnings, weather forecast): DCF77 bits 1-14. - if c == 1: - self.tmp = bit - else: - self.tmp |= (bit << (c - 1)) - if c == 14: - self.put(ss, es, a, [0, ['Special bits: %s' % bin(self.tmp)]]) - elif c == 15: - s = '' if (bit == 1) else 'not ' - self.put(ss, es, a, [0, ['Call bit is %sset' % s]]) - # TODO: Previously this bit indicated use of the backup antenna. - elif c == 16: - s = '' if (bit == 1) else 'not ' - self.put(ss, es, a, [0, ['Summer time announcement %sactive' % s]]) - elif c == 17: - s = '' if (bit == 1) else 'not ' - self.put(ss, es, a, [0, ['CEST is %sin effect' % s]]) - elif c == 18: - s = '' if (bit == 1) else 'not ' - self.put(ss, es, a, [0, ['CET is %sin effect' % s]]) - elif c == 19: - s = '' if (bit == 1) else 'not ' - self.put(ss, es, a, [0, ['Leap second announcement %sactive' % s]]) - elif c == 20: - # Start of encoded time: DCF bit 20. - if bit == 1: - self.put(ss, es, a, [0, ['Start of encoded time (always 1)']]) - else: - self.put(ss, es, a, - [0, ['ERROR: Start of encoded time != 1']]) - elif c in range(21, 27 + 1): - # Minutes (0-59): DCF77 bits 21-27 (BCD format). - if c == 21: - self.tmp = bit - else: - self.tmp |= (bit << (c - 21)) - if c == 27: - self.put(ss, es, a, [0, ['Minutes: %d' % bcd2int(self.tmp)]]) - elif c == 28: - # Even parity over minute bits (21-28): DCF77 bit 28. - self.tmp |= (bit << (c - 21)) - parity = bin(self.tmp).count('1') - s = 'OK' if ((parity % 2) == 0) else 'INVALID!' - self.put(ss, es, a, [0, ['Minute parity: %s' % s]]) - elif c in range(29, 34 + 1): - # Hours (0-23): DCF77 bits 29-34 (BCD format). - if c == 29: - self.tmp = bit - else: - self.tmp |= (bit << (c - 29)) - if c == 34: - self.put(ss, es, a, [0, ['Hours: %d' % bcd2int(self.tmp)]]) - elif c == 35: - # Even parity over hour bits (29-35): DCF77 bit 35. - self.tmp |= (bit << (c - 29)) - parity = bin(self.tmp).count('1') - s = 'OK' if ((parity % 2) == 0) else 'INVALID!' - self.put(ss, es, a, [0, ['Hour parity: %s' % s]]) - elif c in range(36, 41 + 1): - # Day of month (1-31): DCF77 bits 36-41 (BCD format). - if c == 36: - self.tmp = bit - else: - self.tmp |= (bit << (c - 36)) - if c == 41: - self.put(ss, es, a, [0, ['Day: %d' % bcd2int(self.tmp)]]) - elif c in range(42, 44 + 1): - # Day of week (1-7): DCF77 bits 42-44 (BCD format). - # A value of 1 means Monday, 7 means Sunday. - if c == 42: - self.tmp = bit - else: - self.tmp |= (bit << (c - 42)) - if c == 44: - d = bcd2int(self.tmp) - dn = calendar.day_name[d - 1] # day_name[0] == Monday - self.put(ss, es, a, [0, ['Day of week: %d (%s)' % (d, dn)]]) - elif c in range(45, 49 + 1): - # Month (1-12): DCF77 bits 45-49 (BCD format). - if c == 45: - self.tmp = bit - else: - self.tmp |= (bit << (c - 45)) - if c == 49: - m = bcd2int(self.tmp) - mn = calendar.month_name[m] # month_name[1] == January - self.put(ss, es, a, [0, ['Month: %d (%s)' % (m, mn)]]) - elif c in range(50, 57 + 1): - # Year (0-99): DCF77 bits 50-57 (BCD format). - if c == 50: - self.tmp = bit - else: - self.tmp |= (bit << (c - 50)) - if c == 57: - self.put(ss, es, a, [0, ['Year: %d' % bcd2int(self.tmp)]]) - elif c == 58: - # Even parity over date bits (36-58): DCF77 bit 58. - self.tmp |= (bit << (c - 50)) - parity = bin(self.tmp).count('1') - s = 'OK' if ((parity % 2) == 0) else 'INVALID!' - self.put(ss, es, a, [0, ['Date parity: %s' % s]]) - else: - raise Exception('Invalid DCF77 bit: %d' % c) - - 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, (val, pon) = pins, pins - - # Always remember the old PON state. - if self.oldpon != pon: - self.oldpon = pon - - # Warn if PON goes low. - if self.oldpon == 1 and pon == 0: - self.pon_ss = self.samplenum - self.put(self.samplenum, self.samplenum, self.out_ann, - [1, ['Warning: PON goes low, DCF77 reception ' - 'no longer possible']]) - elif self.oldpon == 0 and pon == 1: - self.put(self.samplenum, self.samplenum, self.out_ann, - [0, ['PON goes high, DCF77 reception now possible']]) - self.put(self.pon_ss, self.samplenum, self.out_ann, - [1, ['Warning: PON low, DCF77 reception disabled']]) - - # Ignore samples where PON == 0, they can't contain DCF77 signals. - if pon == 0: - continue - - if self.state == 'WAIT FOR RISING EDGE': - # Wait until the next rising edge occurs. - if not (self.oldval == 0 and val == 1): - self.oldval = val - continue - - # Save the sample number where the DCF77 bit begins. - self.bit_start = self.samplenum - - # Calculate the length (in ms) between two rising edges. - len_edges = self.bit_start - self.bit_start_old - len_edges_ms = int((len_edges / self.samplerate) * 1000) - - # The time between two rising edges is usually around 1000ms. - # For DCF77 bit 59, there is no rising edge at all, i.e. the - # time between DCF77 bit 59 and DCF77 bit 0 (of the next - # minute) is around 2000ms. Thus, if we see an edge with a - # 2000ms distance to the last one, this edge marks the - # beginning of a new minute (and DCF77 bit 0 of that minute). - if len_edges_ms in range(1600, 2400 + 1): - self.put(ss, es, self.out_ann, [0, ['New minute starts']]) - self.bitcount = 0 - self.bit_start_old = self.bit_start - self.dcf77_bitnumber_is_known = 1 - # Don't switch to 'GET BIT' state this time. - continue - - self.bit_start_old = self.bit_start - self.state = 'GET BIT' - - elif self.state == 'GET BIT': - # Wait until the next falling edge occurs. - if not (self.oldval == 1 and val == 0): - self.oldval = val - continue - - # Calculate the length (in ms) of the current high period. - len_high = self.samplenum - self.bit_start - len_high_ms = int((len_high / self.samplerate) * 1000) - - # If the high signal was 100ms long, that encodes a 0 bit. - # If it was 200ms long, that encodes a 1 bit. - if len_high_ms in range(40, 160 + 1): - bit = 0 - elif len_high_ms in range(161, 260 + 1): - bit = 1 - else: - bit = -1 # TODO: Error? - - # There's no bit 59, make sure none is decoded. - if bit in (0, 1) and self.bitcount in range(0, 58 + 1): - self.handle_dcf77_bit(bit) - self.bitcount += 1 - - self.state = 'WAIT FOR RISING EDGE' - - else: - raise Exception('Invalid state: %d' % self.state) - - self.oldval = val - diff --git a/decoders/dcf77/pd.py b/decoders/dcf77/pd.py new file mode 100644 index 0000000..c4132e9 --- /dev/null +++ b/decoders/dcf77/pd.py @@ -0,0 +1,288 @@ +## +## This file is part of the sigrok 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 +## + +# DCF77 protocol decoder + +import sigrokdecode as srd +import calendar + +# Return the specified BCD number (max. 8 bits) as integer. +def bcd2int(b): + return (b & 0x0f) + ((b >> 4) * 10) + +class Decoder(srd.Decoder): + api_version = 1 + id = 'dcf77' + name = 'DCF77' + longname = 'DCF77 time protocol' + desc = 'European longwave time signal (77.5kHz carrier signal).' + license = 'gplv2+' + inputs = ['logic'] + outputs = ['dcf77'] + probes = [ + {'id': 'data', 'name': 'DATA', 'desc': 'DATA line'}, + ] + optional_probes = [ + {'id': 'pon', 'name': 'PON', 'desc': 'Power on'}, + ] + options = {} + annotations = [ + ['Text', 'Human-readable text'], + ['Warnings', 'Human-readable warnings'], + ] + + def __init__(self, **kwargs): + self.state = 'WAIT FOR RISING EDGE' + self.oldpins = None + self.oldval = None + self.oldpon = None + self.samplenum = 0 + self.bit_start = 0 + self.bit_start_old = 0 + self.bitcount = 0 # Counter for the DCF77 bits (0..58) + self.dcf77_bitnumber_is_known = 0 + + def start(self, metadata): + self.samplerate = metadata['samplerate'] + # self.out_proto = self.add(srd.OUTPUT_PROTO, 'dcf77') + self.out_ann = self.add(srd.OUTPUT_ANN, 'dcf77') + + def report(self): + pass + + # TODO: Which range to use? Only the 100ms/200ms or full second? + def handle_dcf77_bit(self, bit): + c = self.bitcount + a = self.out_ann + ss = es = 0 # FIXME + + # Create one annotation for each DCF77 bit (containing the 0/1 value). + # Use 'Unknown DCF77 bit x: val' if we're not sure yet which of the + # 0..58 bits it is (because we haven't seen a 'new minute' marker yet). + # Otherwise, use 'DCF77 bit x: val'. + s = '' if self.dcf77_bitnumber_is_known else 'Unknown ' + self.put(ss, es, a, [0, ['%sDCF77 bit %d: %d' % (s, c, bit)]]) + + # If we're not sure yet which of the 0..58 DCF77 bits we have, return. + # We don't want to decode bogus data. + if not self.dcf77_bitnumber_is_known: + return + + # Output specific "decoded" annotations for the respective DCF77 bits. + if c == 0: + # Start of minute: DCF bit 0. + if bit == 0: + self.put(ss, es, a, [0, ['Start of minute (always 0)']]) + else: + self.put(ss, es, a, [0, ['ERROR: Start of minute != 0']]) + elif c in range(1, 14 + 1): + # Special bits (civil warnings, weather forecast): DCF77 bits 1-14. + if c == 1: + self.tmp = bit + else: + self.tmp |= (bit << (c - 1)) + if c == 14: + self.put(ss, es, a, [0, ['Special bits: %s' % bin(self.tmp)]]) + elif c == 15: + s = '' if (bit == 1) else 'not ' + self.put(ss, es, a, [0, ['Call bit is %sset' % s]]) + # TODO: Previously this bit indicated use of the backup antenna. + elif c == 16: + s = '' if (bit == 1) else 'not ' + self.put(ss, es, a, [0, ['Summer time announcement %sactive' % s]]) + elif c == 17: + s = '' if (bit == 1) else 'not ' + self.put(ss, es, a, [0, ['CEST is %sin effect' % s]]) + elif c == 18: + s = '' if (bit == 1) else 'not ' + self.put(ss, es, a, [0, ['CET is %sin effect' % s]]) + elif c == 19: + s = '' if (bit == 1) else 'not ' + self.put(ss, es, a, [0, ['Leap second announcement %sactive' % s]]) + elif c == 20: + # Start of encoded time: DCF bit 20. + if bit == 1: + self.put(ss, es, a, [0, ['Start of encoded time (always 1)']]) + else: + self.put(ss, es, a, + [0, ['ERROR: Start of encoded time != 1']]) + elif c in range(21, 27 + 1): + # Minutes (0-59): DCF77 bits 21-27 (BCD format). + if c == 21: + self.tmp = bit + else: + self.tmp |= (bit << (c - 21)) + if c == 27: + self.put(ss, es, a, [0, ['Minutes: %d' % bcd2int(self.tmp)]]) + elif c == 28: + # Even parity over minute bits (21-28): DCF77 bit 28. + self.tmp |= (bit << (c - 21)) + parity = bin(self.tmp).count('1') + s = 'OK' if ((parity % 2) == 0) else 'INVALID!' + self.put(ss, es, a, [0, ['Minute parity: %s' % s]]) + elif c in range(29, 34 + 1): + # Hours (0-23): DCF77 bits 29-34 (BCD format). + if c == 29: + self.tmp = bit + else: + self.tmp |= (bit << (c - 29)) + if c == 34: + self.put(ss, es, a, [0, ['Hours: %d' % bcd2int(self.tmp)]]) + elif c == 35: + # Even parity over hour bits (29-35): DCF77 bit 35. + self.tmp |= (bit << (c - 29)) + parity = bin(self.tmp).count('1') + s = 'OK' if ((parity % 2) == 0) else 'INVALID!' + self.put(ss, es, a, [0, ['Hour parity: %s' % s]]) + elif c in range(36, 41 + 1): + # Day of month (1-31): DCF77 bits 36-41 (BCD format). + if c == 36: + self.tmp = bit + else: + self.tmp |= (bit << (c - 36)) + if c == 41: + self.put(ss, es, a, [0, ['Day: %d' % bcd2int(self.tmp)]]) + elif c in range(42, 44 + 1): + # Day of week (1-7): DCF77 bits 42-44 (BCD format). + # A value of 1 means Monday, 7 means Sunday. + if c == 42: + self.tmp = bit + else: + self.tmp |= (bit << (c - 42)) + if c == 44: + d = bcd2int(self.tmp) + dn = calendar.day_name[d - 1] # day_name[0] == Monday + self.put(ss, es, a, [0, ['Day of week: %d (%s)' % (d, dn)]]) + elif c in range(45, 49 + 1): + # Month (1-12): DCF77 bits 45-49 (BCD format). + if c == 45: + self.tmp = bit + else: + self.tmp |= (bit << (c - 45)) + if c == 49: + m = bcd2int(self.tmp) + mn = calendar.month_name[m] # month_name[1] == January + self.put(ss, es, a, [0, ['Month: %d (%s)' % (m, mn)]]) + elif c in range(50, 57 + 1): + # Year (0-99): DCF77 bits 50-57 (BCD format). + if c == 50: + self.tmp = bit + else: + self.tmp |= (bit << (c - 50)) + if c == 57: + self.put(ss, es, a, [0, ['Year: %d' % bcd2int(self.tmp)]]) + elif c == 58: + # Even parity over date bits (36-58): DCF77 bit 58. + self.tmp |= (bit << (c - 50)) + parity = bin(self.tmp).count('1') + s = 'OK' if ((parity % 2) == 0) else 'INVALID!' + self.put(ss, es, a, [0, ['Date parity: %s' % s]]) + else: + raise Exception('Invalid DCF77 bit: %d' % c) + + 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, (val, pon) = pins, pins + + # Always remember the old PON state. + if self.oldpon != pon: + self.oldpon = pon + + # Warn if PON goes low. + if self.oldpon == 1 and pon == 0: + self.pon_ss = self.samplenum + self.put(self.samplenum, self.samplenum, self.out_ann, + [1, ['Warning: PON goes low, DCF77 reception ' + 'no longer possible']]) + elif self.oldpon == 0 and pon == 1: + self.put(self.samplenum, self.samplenum, self.out_ann, + [0, ['PON goes high, DCF77 reception now possible']]) + self.put(self.pon_ss, self.samplenum, self.out_ann, + [1, ['Warning: PON low, DCF77 reception disabled']]) + + # Ignore samples where PON == 0, they can't contain DCF77 signals. + if pon == 0: + continue + + if self.state == 'WAIT FOR RISING EDGE': + # Wait until the next rising edge occurs. + if not (self.oldval == 0 and val == 1): + self.oldval = val + continue + + # Save the sample number where the DCF77 bit begins. + self.bit_start = self.samplenum + + # Calculate the length (in ms) between two rising edges. + len_edges = self.bit_start - self.bit_start_old + len_edges_ms = int((len_edges / self.samplerate) * 1000) + + # The time between two rising edges is usually around 1000ms. + # For DCF77 bit 59, there is no rising edge at all, i.e. the + # time between DCF77 bit 59 and DCF77 bit 0 (of the next + # minute) is around 2000ms. Thus, if we see an edge with a + # 2000ms distance to the last one, this edge marks the + # beginning of a new minute (and DCF77 bit 0 of that minute). + if len_edges_ms in range(1600, 2400 + 1): + self.put(ss, es, self.out_ann, [0, ['New minute starts']]) + self.bitcount = 0 + self.bit_start_old = self.bit_start + self.dcf77_bitnumber_is_known = 1 + # Don't switch to 'GET BIT' state this time. + continue + + self.bit_start_old = self.bit_start + self.state = 'GET BIT' + + elif self.state == 'GET BIT': + # Wait until the next falling edge occurs. + if not (self.oldval == 1 and val == 0): + self.oldval = val + continue + + # Calculate the length (in ms) of the current high period. + len_high = self.samplenum - self.bit_start + len_high_ms = int((len_high / self.samplerate) * 1000) + + # If the high signal was 100ms long, that encodes a 0 bit. + # If it was 200ms long, that encodes a 1 bit. + if len_high_ms in range(40, 160 + 1): + bit = 0 + elif len_high_ms in range(161, 260 + 1): + bit = 1 + else: + bit = -1 # TODO: Error? + + # There's no bit 59, make sure none is decoded. + if bit in (0, 1) and self.bitcount in range(0, 58 + 1): + self.handle_dcf77_bit(bit) + self.bitcount += 1 + + self.state = 'WAIT FOR RISING EDGE' + + else: + raise Exception('Invalid state: %d' % self.state) + + self.oldval = val + diff --git a/decoders/edid/Makefile.am b/decoders/edid/Makefile.am index 2ec1715..8381dcc 100644 --- a/decoders/edid/Makefile.am +++ b/decoders/edid/Makefile.am @@ -20,7 +20,7 @@ pkgdatadir = $(DECODERS_DIR)/edid -dist_pkgdata_DATA = __init__.py edid.py pnpids.txt +dist_pkgdata_DATA = __init__.py pd.py pnpids.txt CLEANFILES = *.pyc diff --git a/decoders/edid/__init__.py b/decoders/edid/__init__.py index 0987d17..baa2681 100644 --- a/decoders/edid/__init__.py +++ b/decoders/edid/__init__.py @@ -36,5 +36,5 @@ More information on EDID is available here: https://en.wikipedia.org/wiki/Extended_display_identification_data ''' -from .edid import * +from .pd import * diff --git a/decoders/edid/edid.py b/decoders/edid/edid.py deleted file mode 100644 index a03fe67..0000000 --- a/decoders/edid/edid.py +++ /dev/null @@ -1,474 +0,0 @@ -## -## This file is part of the sigrok project. -## -## Copyright (C) 2012 Bert Vermeulen -## -## This program is free software; you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published by -## the Free Software Foundation; either version 3 of the License, or -## (at your option) any later version. -## -## This program is distributed in the hope that it will be useful, -## but WITHOUT ANY WARRANTY; without even the implied warranty of -## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -## GNU General Public License for more details. -## -## You should have received a copy of the GNU General Public License -## along with this program; if not, see . -## - -# EDID protocol decoder - -# TODO: -# - EDID < 1.3 -# - add short annotations -# - Signal level standard field in basic display parameters block -# - Additional color point descriptors -# - Additional standard timing descriptors -# - Extensions - -import sigrokdecode as srd -import os - -EDID_HEADER = [0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00] -OFF_VENDOR = 8 -OFF_VERSION = 18 -OFF_BASIC = 20 -OFF_CHROM = 25 -OFF_EST_TIMING = 35 -OFF_STD_TIMING = 38 -OFF_DET_TIMING = 54 -OFF_NUM_EXT = 126 -OFF_CHECKSUM = 127 - -# Pre-EDID established timing modes -est_modes = [ - '720x400@70Hz', - '720x400@88Hz', - '640x480@60Hz', - '640x480@67Hz', - '640x480@72Hz', - '640x480@75Hz', - '800x600@56Hz', - '800x600@60Hz', - '800x600@72Hz', - '800x600@75Hz', - '832x624@75Hz', - '1024x768@87Hz(i)', - '1024x768@60Hz', - '1024x768@70Hz', - '1024x768@75Hz', - '1280x1024@75Hz', - '1152x870@75Hz', -] - -# X:Y display aspect ratios, as used in standard timing modes -xy_ratio = [ - (16, 10), - (4, 3), - (5, 4), - (16, 9), -] - -# Annotation types -ANN_FIELDS = 0 -ANN_SECTIONS = 1 - -class Decoder(srd.Decoder): - api_version = 1 - id = 'edid' - name = 'EDID' - longname = 'Extended Display Identification Data' - desc = 'Data structure describing display device capabilities.' - license = 'gplv3+' - inputs = ['ddc2'] - outputs = ['edid'] - options = {} - annotations = [ - ['EDID fields', 'EDID structure fields'], - ['EDID sections', 'EDID structure sections'], - ] - - def __init__(self, **kwargs): - self.state = None - # Received data items, used as an index into samplenum/data - self.cnt = 0 - # Start/end sample numbers per data item - self.sn = [] - # Received data - self.cache = [] - - def start(self, metadata): - self.out_ann = self.add(srd.OUTPUT_ANN, 'edid') - - def report(self): - pass - - def decode(self, ss, es, data): - cmd, data = data - - # We only care about actual data bytes that are read (for now). - if cmd != 'DATA READ': - return - - self.cnt += 1 - self.sn.append([ss, es]) - self.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 - if self.cnt >= OFF_VENDOR: - if self.cache[-8:] == EDID_HEADER: - # Throw away any garbage before the header - self.sn = self.sn[-8:] - self.cache = self.cache[-8:] - self.cnt = 8 - self.state = 'edid' - self.put(ss, es, self.out_ann, [0, ['EDID header']]) - 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) - 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]]) - elif self.cnt == OFF_CHROM: - self.decode_basicdisplay(-5) - elif self.cnt == OFF_EST_TIMING: - self.decode_chromaticity(-10) - elif self.cnt == OFF_STD_TIMING: - self.decode_est_timing(-3) - elif self.cnt == OFF_DET_TIMING: - self.decode_std_timing(-16) - elif self.cnt == OFF_NUM_EXT: - self.decode_descriptors(-72) - elif self.cnt == OFF_CHECKSUM: - self.put(ss, es, self.out_ann, - [0, ['Extensions present: %d' % self.cache[self.cnt-1]]]) - elif self.cnt == OFF_CHECKSUM+1: - checksum = 0 - for i in range(128): - checksum += self.cache[i] - if checksum % 256 == 0: - csstr = 'OK' - else: - csstr = 'WRONG!' - self.put(ss, es, self.out_ann, [0, ['Checksum: %d (%s)' % ( - self.cache[self.cnt-1], csstr)]]) - self.state = 'extensions' - elif self.state == 'extensions': - pass - - def ann_field(self, start, end, annotation): - self.put(self.sn[start][0], self.sn[end][1], - self.out_ann, [ANN_FIELDS, [annotation]]) - - def lookup_pnpid(self, pnpid): - pnpid_file = os.path.join(os.path.dirname(__file__), 'pnpids.txt') - if os.path.exists(pnpid_file): - for line in open(pnpid_file).readlines(): - if line.find(pnpid + ';') == 0: - return line[4:].strip() - return '' - - def decode_vid(self, offset): - pnpid = chr(64 + ((self.cache[offset] & 0x7c) >> 2)) - pnpid += chr(64 + (((self.cache[offset] & 0x03) << 3) - | ((self.cache[offset+1] & 0xe0) >> 5))) - pnpid += chr(64 + (self.cache[offset+1] & 0x1f)) - vendor = self.lookup_pnpid(pnpid) - if vendor: - pnpid += ' (%s)' % vendor - self.ann_field(offset, offset+1, pnpid) - - def decode_pid(self, offset): - pidstr = 'Product 0x%.2x%.2x' % (self.cache[offset+1], self.cache[offset]) - self.ann_field(offset, offset+1, pidstr) - - def decode_serial(self, offset): - serialnum = (self.cache[offset+3] << 24) \ - + (self.cache[offset+2] << 16) \ - + (self.cache[offset+1] << 8) \ - + self.cache[offset] - serialstr = '' - is_alnum = True - for i in range(4): - if not chr(self.cache[offset+3-i]).isalnum(): - is_alnum = False - break - serialstr += chr(self.cache[offset+3-i]) - serial = serialstr if is_alnum else str(serialnum) - self.ann_field(offset, offset+3, 'Serial ' + serial) - - def decode_mfrdate(self, offset): - datestr = '' - if self.cache[offset]: - datestr += 'week %d, ' % self.cache[offset] - datestr += str(1990 + self.cache[offset+1]) - if datestr: - self.ann_field(offset, offset+1, 'Manufactured ' + datestr) - - def decode_basicdisplay(self, offset): - # Video input definition - vid = self.cache[offset] - if vid & 0x80: - # Digital - self.ann_field(offset, offset, 'Video input: VESA DFP 1.') - else: - # Analog - sls = (vid & 60) >> 5 - self.ann_field(offset, offset, 'Signal level standard: %.2x' % sls) - if vid & 0x10: - self.ann_field(offset, offset, 'Blank-to-black setup expected') - syncs = '' - if vid & 0x08: - syncs += 'separate syncs, ' - if vid & 0x04: - syncs += 'composite syncs, ' - if vid & 0x02: - syncs += 'sync on green, ' - if vid & 0x01: - syncs += 'Vsync serration required, ' - if syncs: - self.ann_field(offset, offset, 'Supported syncs: %s' % syncs[:-2]) - # Max horizontal/vertical image size - if self.cache[offset+1] != 0 and self.cache[offset+2] != 0: - # Projectors have this set to 0 - sizestr = '%dx%dcm' % (self.cache[offset+1], self.cache[offset+2]) - self.ann_field(offset+1, offset+2, 'Physical size: ' + sizestr) - # Display transfer characteristic (gamma) - if self.cache[offset+3] != 0xff: - gamma = (self.cache[offset+3] + 100) / 100 - self.ann_field(offset+3, offset+3, 'Gamma: %1.2f' % gamma) - # Feature support - fs = self.cache[offset+4] - dpms = '' - if fs & 0x80: - dpms += 'standby, ' - if fs & 0x40: - dpms += 'suspend, ' - if fs & 0x20: - dpms += 'active off, ' - if dpms: - self.ann_field(offset+4, offset+4, 'DPMS support: %s' % dpms[:-2]) - dt = (fs & 0x18) >> 3 - dtstr = '' - if dt == 0: - dtstr = 'Monochrome' - elif dt == 1: - dtstr = 'RGB color' - elif dt == 2: - dtstr = 'non-RGB multicolor' - if dtstr: - self.ann_field(offset+4, offset+4, 'Display type: %s' % dtstr) - if fs & 0x04: - self.ann_field(offset+4, offset+4, 'Color space: standard sRGB') - # Save this for when we decode the first detailed timing descriptor - self.have_preferred_timing = (fs & 0x02) == 0x02 - if fs & 0x01: - gft = '' - else: - gft = 'not ' - self.ann_field(offset+4, offset+4, - 'Generalized timing formula: %ssupported' % gft) - - def convert_color(self, value): - # Convert from 10-bit packet format to float - outval = 0.0 - for i in range(10): - if value & 0x01: - outval += 2 ** -(10-i) - value >>= 1 - return outval - - def decode_chromaticity(self, offset): - redx = (self.cache[offset+2] << 2) + ((self.cache[offset] & 0xc0) >> 6) - redy = (self.cache[offset+3] << 2) + ((self.cache[offset] & 0x30) >> 4) - self.ann_field(offset, offset+9, 'Chromacity red: X %1.3f, Y %1.3f' % ( - self.convert_color(redx), self.convert_color(redy))) - - greenx = (self.cache[offset+4] << 2) + ((self.cache[offset] & 0x0c) >> 6) - greeny = (self.cache[offset+5] << 2) + ((self.cache[offset] & 0x03) >> 4) - self.ann_field(offset, offset+9, 'Chromacity green: X %1.3f, Y %1.3f' % ( - self.convert_color(greenx), self.convert_color(greeny))) - - bluex = (self.cache[offset+6] << 2) + ((self.cache[offset+1] & 0xc0) >> 6) - bluey = (self.cache[offset+7] << 2) + ((self.cache[offset+1] & 0x30) >> 4) - self.ann_field(offset, offset+9, 'Chromacity blue: X %1.3f, Y %1.3f' % ( - self.convert_color(bluex), self.convert_color(bluey))) - - whitex = (self.cache[offset+8] << 2) + ((self.cache[offset+1] & 0x0c) >> 6) - whitey = (self.cache[offset+9] << 2) + ((self.cache[offset+1] & 0x03) >> 4) - self.ann_field(offset, offset+9, 'Chromacity white: X %1.3f, Y %1.3f' % ( - self.convert_color(whitex), self.convert_color(whitey))) - - def decode_est_timing(self, offset): - # Pre-EDID modes - bitmap = (self.cache[offset] << 9) \ - + (self.cache[offset+1] << 1) \ - + ((self.cache[offset+2] & 0x80) >> 7) - modestr = '' - for i in range(17): - if bitmap & (1 << (16-i)): - modestr += est_modes[i] + ', ' - if modestr: - self.ann_field(offset, offset+2, - 'Supported establised modes: %s' % modestr[:-2]) - - def decode_std_timing(self, offset): - modestr = '' - for i in range(0, 16, 2): - if self.cache[offset+i] == 0x01 and self.cache[offset+i+1] == 0x01: - # Unused field - continue - x = (self.cache[offset+i] + 31) * 8 - ratio = (self.cache[offset+i+1] & 0xc0) >> 6 - ratio_x, ratio_y = xy_ratio[ratio] - y = x / ratio_x * ratio_y - refresh = (self.cache[offset+i+1] & 0x3f) + 60 - modestr += '%dx%d@%dHz, ' % (x, y, refresh) - if modestr: - self.ann_field(offset, offset+2, - 'Supported standard modes: %s' % modestr[:-2]) - - def decode_detailed_timing(self, offset): - if offset == -72 and self.have_preferred_timing: - # Only on first detailed timing descriptor - section = 'Preferred' - else: - section = 'Detailed' - section += ' timing descriptor' - self.put(self.sn[offset][0], self.sn[offset+18][1], - self.out_ann, [ANN_SECTIONS, [section]]) - - pixclock = float((self.cache[offset+1] << 8) + self.cache[offset]) / 100 - self.ann_field(offset, offset+1, 'Pixel clock: %.2f MHz' % pixclock) - - horiz_active = ((self.cache[offset+4] & 0xf0) << 4) + self.cache[offset+2] - 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) - - 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) - - 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) - - 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) - - 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) - - 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) - vert_border = self.cache[offset+16] - if vert_border: - self.ann_field(offset+16, offset+16, 'Vertical border: %d lines' % vert_border) - - features = 'Flags: ' - if self.cache[offset+17] & 0x80: - features += 'interlaced, ' - stereo = (self.cache[offset+17] & 0x60) >> 5 - if stereo: - if self.cache[offset+17] & 0x01: - features += '2-way interleaved stereo (' - features += ['right image on even lines', - 'left image on even lines', - 'side-by-side'][stereo-1] - features += '), ' - else: - features += 'field sequential stereo (' - features += ['right image on sync=1', 'left image on sync=1', - '4-way interleaved'][stereo-1] - features += '), ' - sync = (self.cache[offset+17] & 0x18) >> 3 - sync2 = (self.cache[offset+17] & 0x06) >> 1 - posneg = ['negative', 'positive'] - features += 'sync type ' - if sync == 0x00: - features += 'analog composite (serrate on RGB)' - elif sync == 0x01: - features += 'bipolar analog composite (serrate on RGB)' - elif sync == 0x02: - features += 'digital composite (serrate on composite polarity ' \ - + (posneg[sync2 & 0x01]) + ')' - elif sync == 0x03: - features += 'digital separate (' - features += 'Vsync polarity ' + (posneg[(sync2 & 0x02) >> 1]) - features += ', Hsync polarity ' + (posneg[sync2 & 0x01]) - features += ')' - features += ', ' - self.ann_field(offset+17, offset+17, features[:-2]) - - def decode_descriptor(self, offset): - tag = self.cache[offset+3] - if tag == 0xff: - # Monitor 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()) - elif tag == 0xfe: - # Text - text = bytes(self.cache[offset+5:][:13]).decode(encoding='cp437', errors='replace') - self.ann_field(offset, offset+17, 'Info: %s' % text.strip()) - elif tag == 0xfc: - # 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()) - elif tag == 0xfd: - # Monitor range limits - self.put(self.sn[offset][0], self.sn[offset+17][1], self.out_ann, - [ANN_SECTIONS, ['Monitor range limits']]) - self.ann_field(offset+5, offset+5, 'Minimum vertical rate: %dHz' % - self.cache[offset+5]) - self.ann_field(offset+6, offset+6, 'Maximum vertical rate: %dHz' % - self.cache[offset+6]) - self.ann_field(offset+7, offset+7, 'Minimum horizontal rate: %dkHz' % - self.cache[offset+7]) - self.ann_field(offset+8, offset+8, 'Maximum horizontal rate: %dkHz' % - self.cache[offset+8]) - self.ann_field(offset+9, offset+9, 'Maximum pixel clock: %dMHz' % - (self.cache[offset+9] * 10)) - if self.cache[offset+10] == 0x02: - # Secondary GTF curve supported - self.ann_field(offset+10, offset+17, 'Secondary timing formula supported') - elif tag == 0xfb: - # Additional color point data - self.put(self.sn[offset][0], self.sn[offset+17][1], self.out_ann, - [ANN_SECTIONS, ['Additional color point data']]) - elif tag == 0xfa: - # Additional standard timing definitions - self.put(self.sn[offset][0], self.sn[offset+17][1], self.out_ann, - [ANN_SECTIONS, ['Additional standard timing definitions']]) - else: - self.put(self.sn[offset][0], self.sn[offset+17][1], self.out_ann, - [ANN_SECTIONS, ['Unknown descriptor']]) - - def decode_descriptors(self, offset): - # 4 consecutive 18-byte descriptor blocks - for i in range(offset, 0, 18): - if self.cache[i] != 0 and self.cache[i+1] != 0: - self.decode_detailed_timing(i) - else: - if self.cache[i+2] == 0 or self.cache[i+4] == 0: - self.decode_descriptor(i) - diff --git a/decoders/edid/pd.py b/decoders/edid/pd.py new file mode 100644 index 0000000..a03fe67 --- /dev/null +++ b/decoders/edid/pd.py @@ -0,0 +1,474 @@ +## +## This file is part of the sigrok project. +## +## Copyright (C) 2012 Bert Vermeulen +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 3 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +# EDID protocol decoder + +# TODO: +# - EDID < 1.3 +# - add short annotations +# - Signal level standard field in basic display parameters block +# - Additional color point descriptors +# - Additional standard timing descriptors +# - Extensions + +import sigrokdecode as srd +import os + +EDID_HEADER = [0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00] +OFF_VENDOR = 8 +OFF_VERSION = 18 +OFF_BASIC = 20 +OFF_CHROM = 25 +OFF_EST_TIMING = 35 +OFF_STD_TIMING = 38 +OFF_DET_TIMING = 54 +OFF_NUM_EXT = 126 +OFF_CHECKSUM = 127 + +# Pre-EDID established timing modes +est_modes = [ + '720x400@70Hz', + '720x400@88Hz', + '640x480@60Hz', + '640x480@67Hz', + '640x480@72Hz', + '640x480@75Hz', + '800x600@56Hz', + '800x600@60Hz', + '800x600@72Hz', + '800x600@75Hz', + '832x624@75Hz', + '1024x768@87Hz(i)', + '1024x768@60Hz', + '1024x768@70Hz', + '1024x768@75Hz', + '1280x1024@75Hz', + '1152x870@75Hz', +] + +# X:Y display aspect ratios, as used in standard timing modes +xy_ratio = [ + (16, 10), + (4, 3), + (5, 4), + (16, 9), +] + +# Annotation types +ANN_FIELDS = 0 +ANN_SECTIONS = 1 + +class Decoder(srd.Decoder): + api_version = 1 + id = 'edid' + name = 'EDID' + longname = 'Extended Display Identification Data' + desc = 'Data structure describing display device capabilities.' + license = 'gplv3+' + inputs = ['ddc2'] + outputs = ['edid'] + options = {} + annotations = [ + ['EDID fields', 'EDID structure fields'], + ['EDID sections', 'EDID structure sections'], + ] + + def __init__(self, **kwargs): + self.state = None + # Received data items, used as an index into samplenum/data + self.cnt = 0 + # Start/end sample numbers per data item + self.sn = [] + # Received data + self.cache = [] + + def start(self, metadata): + self.out_ann = self.add(srd.OUTPUT_ANN, 'edid') + + def report(self): + pass + + def decode(self, ss, es, data): + cmd, data = data + + # We only care about actual data bytes that are read (for now). + if cmd != 'DATA READ': + return + + self.cnt += 1 + self.sn.append([ss, es]) + self.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 + if self.cnt >= OFF_VENDOR: + if self.cache[-8:] == EDID_HEADER: + # Throw away any garbage before the header + self.sn = self.sn[-8:] + self.cache = self.cache[-8:] + self.cnt = 8 + self.state = 'edid' + self.put(ss, es, self.out_ann, [0, ['EDID header']]) + 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) + 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]]) + elif self.cnt == OFF_CHROM: + self.decode_basicdisplay(-5) + elif self.cnt == OFF_EST_TIMING: + self.decode_chromaticity(-10) + elif self.cnt == OFF_STD_TIMING: + self.decode_est_timing(-3) + elif self.cnt == OFF_DET_TIMING: + self.decode_std_timing(-16) + elif self.cnt == OFF_NUM_EXT: + self.decode_descriptors(-72) + elif self.cnt == OFF_CHECKSUM: + self.put(ss, es, self.out_ann, + [0, ['Extensions present: %d' % self.cache[self.cnt-1]]]) + elif self.cnt == OFF_CHECKSUM+1: + checksum = 0 + for i in range(128): + checksum += self.cache[i] + if checksum % 256 == 0: + csstr = 'OK' + else: + csstr = 'WRONG!' + self.put(ss, es, self.out_ann, [0, ['Checksum: %d (%s)' % ( + self.cache[self.cnt-1], csstr)]]) + self.state = 'extensions' + elif self.state == 'extensions': + pass + + def ann_field(self, start, end, annotation): + self.put(self.sn[start][0], self.sn[end][1], + self.out_ann, [ANN_FIELDS, [annotation]]) + + def lookup_pnpid(self, pnpid): + pnpid_file = os.path.join(os.path.dirname(__file__), 'pnpids.txt') + if os.path.exists(pnpid_file): + for line in open(pnpid_file).readlines(): + if line.find(pnpid + ';') == 0: + return line[4:].strip() + return '' + + def decode_vid(self, offset): + pnpid = chr(64 + ((self.cache[offset] & 0x7c) >> 2)) + pnpid += chr(64 + (((self.cache[offset] & 0x03) << 3) + | ((self.cache[offset+1] & 0xe0) >> 5))) + pnpid += chr(64 + (self.cache[offset+1] & 0x1f)) + vendor = self.lookup_pnpid(pnpid) + if vendor: + pnpid += ' (%s)' % vendor + self.ann_field(offset, offset+1, pnpid) + + def decode_pid(self, offset): + pidstr = 'Product 0x%.2x%.2x' % (self.cache[offset+1], self.cache[offset]) + self.ann_field(offset, offset+1, pidstr) + + def decode_serial(self, offset): + serialnum = (self.cache[offset+3] << 24) \ + + (self.cache[offset+2] << 16) \ + + (self.cache[offset+1] << 8) \ + + self.cache[offset] + serialstr = '' + is_alnum = True + for i in range(4): + if not chr(self.cache[offset+3-i]).isalnum(): + is_alnum = False + break + serialstr += chr(self.cache[offset+3-i]) + serial = serialstr if is_alnum else str(serialnum) + self.ann_field(offset, offset+3, 'Serial ' + serial) + + def decode_mfrdate(self, offset): + datestr = '' + if self.cache[offset]: + datestr += 'week %d, ' % self.cache[offset] + datestr += str(1990 + self.cache[offset+1]) + if datestr: + self.ann_field(offset, offset+1, 'Manufactured ' + datestr) + + def decode_basicdisplay(self, offset): + # Video input definition + vid = self.cache[offset] + if vid & 0x80: + # Digital + self.ann_field(offset, offset, 'Video input: VESA DFP 1.') + else: + # Analog + sls = (vid & 60) >> 5 + self.ann_field(offset, offset, 'Signal level standard: %.2x' % sls) + if vid & 0x10: + self.ann_field(offset, offset, 'Blank-to-black setup expected') + syncs = '' + if vid & 0x08: + syncs += 'separate syncs, ' + if vid & 0x04: + syncs += 'composite syncs, ' + if vid & 0x02: + syncs += 'sync on green, ' + if vid & 0x01: + syncs += 'Vsync serration required, ' + if syncs: + self.ann_field(offset, offset, 'Supported syncs: %s' % syncs[:-2]) + # Max horizontal/vertical image size + if self.cache[offset+1] != 0 and self.cache[offset+2] != 0: + # Projectors have this set to 0 + sizestr = '%dx%dcm' % (self.cache[offset+1], self.cache[offset+2]) + self.ann_field(offset+1, offset+2, 'Physical size: ' + sizestr) + # Display transfer characteristic (gamma) + if self.cache[offset+3] != 0xff: + gamma = (self.cache[offset+3] + 100) / 100 + self.ann_field(offset+3, offset+3, 'Gamma: %1.2f' % gamma) + # Feature support + fs = self.cache[offset+4] + dpms = '' + if fs & 0x80: + dpms += 'standby, ' + if fs & 0x40: + dpms += 'suspend, ' + if fs & 0x20: + dpms += 'active off, ' + if dpms: + self.ann_field(offset+4, offset+4, 'DPMS support: %s' % dpms[:-2]) + dt = (fs & 0x18) >> 3 + dtstr = '' + if dt == 0: + dtstr = 'Monochrome' + elif dt == 1: + dtstr = 'RGB color' + elif dt == 2: + dtstr = 'non-RGB multicolor' + if dtstr: + self.ann_field(offset+4, offset+4, 'Display type: %s' % dtstr) + if fs & 0x04: + self.ann_field(offset+4, offset+4, 'Color space: standard sRGB') + # Save this for when we decode the first detailed timing descriptor + self.have_preferred_timing = (fs & 0x02) == 0x02 + if fs & 0x01: + gft = '' + else: + gft = 'not ' + self.ann_field(offset+4, offset+4, + 'Generalized timing formula: %ssupported' % gft) + + def convert_color(self, value): + # Convert from 10-bit packet format to float + outval = 0.0 + for i in range(10): + if value & 0x01: + outval += 2 ** -(10-i) + value >>= 1 + return outval + + def decode_chromaticity(self, offset): + redx = (self.cache[offset+2] << 2) + ((self.cache[offset] & 0xc0) >> 6) + redy = (self.cache[offset+3] << 2) + ((self.cache[offset] & 0x30) >> 4) + self.ann_field(offset, offset+9, 'Chromacity red: X %1.3f, Y %1.3f' % ( + self.convert_color(redx), self.convert_color(redy))) + + greenx = (self.cache[offset+4] << 2) + ((self.cache[offset] & 0x0c) >> 6) + greeny = (self.cache[offset+5] << 2) + ((self.cache[offset] & 0x03) >> 4) + self.ann_field(offset, offset+9, 'Chromacity green: X %1.3f, Y %1.3f' % ( + self.convert_color(greenx), self.convert_color(greeny))) + + bluex = (self.cache[offset+6] << 2) + ((self.cache[offset+1] & 0xc0) >> 6) + bluey = (self.cache[offset+7] << 2) + ((self.cache[offset+1] & 0x30) >> 4) + self.ann_field(offset, offset+9, 'Chromacity blue: X %1.3f, Y %1.3f' % ( + self.convert_color(bluex), self.convert_color(bluey))) + + whitex = (self.cache[offset+8] << 2) + ((self.cache[offset+1] & 0x0c) >> 6) + whitey = (self.cache[offset+9] << 2) + ((self.cache[offset+1] & 0x03) >> 4) + self.ann_field(offset, offset+9, 'Chromacity white: X %1.3f, Y %1.3f' % ( + self.convert_color(whitex), self.convert_color(whitey))) + + def decode_est_timing(self, offset): + # Pre-EDID modes + bitmap = (self.cache[offset] << 9) \ + + (self.cache[offset+1] << 1) \ + + ((self.cache[offset+2] & 0x80) >> 7) + modestr = '' + for i in range(17): + if bitmap & (1 << (16-i)): + modestr += est_modes[i] + ', ' + if modestr: + self.ann_field(offset, offset+2, + 'Supported establised modes: %s' % modestr[:-2]) + + def decode_std_timing(self, offset): + modestr = '' + for i in range(0, 16, 2): + if self.cache[offset+i] == 0x01 and self.cache[offset+i+1] == 0x01: + # Unused field + continue + x = (self.cache[offset+i] + 31) * 8 + ratio = (self.cache[offset+i+1] & 0xc0) >> 6 + ratio_x, ratio_y = xy_ratio[ratio] + y = x / ratio_x * ratio_y + refresh = (self.cache[offset+i+1] & 0x3f) + 60 + modestr += '%dx%d@%dHz, ' % (x, y, refresh) + if modestr: + self.ann_field(offset, offset+2, + 'Supported standard modes: %s' % modestr[:-2]) + + def decode_detailed_timing(self, offset): + if offset == -72 and self.have_preferred_timing: + # Only on first detailed timing descriptor + section = 'Preferred' + else: + section = 'Detailed' + section += ' timing descriptor' + self.put(self.sn[offset][0], self.sn[offset+18][1], + self.out_ann, [ANN_SECTIONS, [section]]) + + pixclock = float((self.cache[offset+1] << 8) + self.cache[offset]) / 100 + self.ann_field(offset, offset+1, 'Pixel clock: %.2f MHz' % pixclock) + + horiz_active = ((self.cache[offset+4] & 0xf0) << 4) + self.cache[offset+2] + 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) + + 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) + + 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) + + 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) + + 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) + + 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) + vert_border = self.cache[offset+16] + if vert_border: + self.ann_field(offset+16, offset+16, 'Vertical border: %d lines' % vert_border) + + features = 'Flags: ' + if self.cache[offset+17] & 0x80: + features += 'interlaced, ' + stereo = (self.cache[offset+17] & 0x60) >> 5 + if stereo: + if self.cache[offset+17] & 0x01: + features += '2-way interleaved stereo (' + features += ['right image on even lines', + 'left image on even lines', + 'side-by-side'][stereo-1] + features += '), ' + else: + features += 'field sequential stereo (' + features += ['right image on sync=1', 'left image on sync=1', + '4-way interleaved'][stereo-1] + features += '), ' + sync = (self.cache[offset+17] & 0x18) >> 3 + sync2 = (self.cache[offset+17] & 0x06) >> 1 + posneg = ['negative', 'positive'] + features += 'sync type ' + if sync == 0x00: + features += 'analog composite (serrate on RGB)' + elif sync == 0x01: + features += 'bipolar analog composite (serrate on RGB)' + elif sync == 0x02: + features += 'digital composite (serrate on composite polarity ' \ + + (posneg[sync2 & 0x01]) + ')' + elif sync == 0x03: + features += 'digital separate (' + features += 'Vsync polarity ' + (posneg[(sync2 & 0x02) >> 1]) + features += ', Hsync polarity ' + (posneg[sync2 & 0x01]) + features += ')' + features += ', ' + self.ann_field(offset+17, offset+17, features[:-2]) + + def decode_descriptor(self, offset): + tag = self.cache[offset+3] + if tag == 0xff: + # Monitor 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()) + elif tag == 0xfe: + # Text + text = bytes(self.cache[offset+5:][:13]).decode(encoding='cp437', errors='replace') + self.ann_field(offset, offset+17, 'Info: %s' % text.strip()) + elif tag == 0xfc: + # 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()) + elif tag == 0xfd: + # Monitor range limits + self.put(self.sn[offset][0], self.sn[offset+17][1], self.out_ann, + [ANN_SECTIONS, ['Monitor range limits']]) + self.ann_field(offset+5, offset+5, 'Minimum vertical rate: %dHz' % + self.cache[offset+5]) + self.ann_field(offset+6, offset+6, 'Maximum vertical rate: %dHz' % + self.cache[offset+6]) + self.ann_field(offset+7, offset+7, 'Minimum horizontal rate: %dkHz' % + self.cache[offset+7]) + self.ann_field(offset+8, offset+8, 'Maximum horizontal rate: %dkHz' % + self.cache[offset+8]) + self.ann_field(offset+9, offset+9, 'Maximum pixel clock: %dMHz' % + (self.cache[offset+9] * 10)) + if self.cache[offset+10] == 0x02: + # Secondary GTF curve supported + self.ann_field(offset+10, offset+17, 'Secondary timing formula supported') + elif tag == 0xfb: + # Additional color point data + self.put(self.sn[offset][0], self.sn[offset+17][1], self.out_ann, + [ANN_SECTIONS, ['Additional color point data']]) + elif tag == 0xfa: + # Additional standard timing definitions + self.put(self.sn[offset][0], self.sn[offset+17][1], self.out_ann, + [ANN_SECTIONS, ['Additional standard timing definitions']]) + else: + self.put(self.sn[offset][0], self.sn[offset+17][1], self.out_ann, + [ANN_SECTIONS, ['Unknown descriptor']]) + + def decode_descriptors(self, offset): + # 4 consecutive 18-byte descriptor blocks + for i in range(offset, 0, 18): + if self.cache[i] != 0 and self.cache[i+1] != 0: + self.decode_detailed_timing(i) + else: + if self.cache[i+2] == 0 or self.cache[i+4] == 0: + self.decode_descriptor(i) + diff --git a/decoders/i2c/Makefile.am b/decoders/i2c/Makefile.am index 75a53eb..c25393f 100644 --- a/decoders/i2c/Makefile.am +++ b/decoders/i2c/Makefile.am @@ -20,7 +20,7 @@ pkgdatadir = $(DECODERS_DIR)/i2c -dist_pkgdata_DATA = __init__.py i2c.py +dist_pkgdata_DATA = __init__.py pd.py CLEANFILES = *.pyc diff --git a/decoders/i2c/__init__.py b/decoders/i2c/__init__.py index 14262d1..6c7dafe 100644 --- a/decoders/i2c/__init__.py +++ b/decoders/i2c/__init__.py @@ -79,5 +79,5 @@ For 'START', 'START REPEAT', 'STOP', 'ACK', and 'NACK' is None. ''' -from .i2c import * +from .pd import * diff --git a/decoders/i2c/i2c.py b/decoders/i2c/i2c.py deleted file mode 100644 index 53321eb..0000000 --- a/decoders/i2c/i2c.py +++ /dev/null @@ -1,240 +0,0 @@ -## -## This file is part of the sigrok project. -## -## Copyright (C) 2010-2011 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 -## - -# I2C protocol decoder - -# TODO: Look into arbitration, collision detection, clock synchronisation, etc. -# TODO: Handle clock stretching. -# TODO: Handle combined messages / repeated START. -# TODO: Implement support for 7bit and 10bit slave addresses. -# TODO: Implement support for inverting SDA/SCL levels (0->1 and 1->0). -# TODO: Implement support for detecting various bus errors. -# TODO: I2C address of slaves. -# TODO: Handle multiple different I2C devices on same bus -# -> we need to decode multiple protocols at the same time. - -import sigrokdecode as srd - -# Annotation feed formats -ANN_SHIFTED = 0 -ANN_SHIFTED_SHORT = 1 -ANN_RAW = 2 - -# Values are verbose and short annotation, respectively. -proto = { - 'START': ['START', 'S'], - 'START REPEAT': ['START REPEAT', 'Sr'], - 'STOP': ['STOP', 'P'], - 'ACK': ['ACK', 'A'], - 'NACK': ['NACK', 'N'], - 'ADDRESS READ': ['ADDRESS READ', 'AR'], - 'ADDRESS WRITE': ['ADDRESS WRITE', 'AW'], - 'DATA READ': ['DATA READ', 'DR'], - 'DATA WRITE': ['DATA WRITE', 'DW'], -} - -class Decoder(srd.Decoder): - api_version = 1 - id = 'i2c' - name = 'I2C' - longname = 'Inter-Integrated Circuit' - desc = 'Two-wire, multi-master, serial bus.' - license = 'gplv2+' - inputs = ['logic'] - outputs = ['i2c'] - probes = [ - {'id': 'scl', 'name': 'SCL', 'desc': 'Serial clock line'}, - {'id': 'sda', 'name': 'SDA', 'desc': 'Serial data line'}, - ] - optional_probes = [] - options = { - 'addressing': ['Slave addressing (in bits)', 7], # 7 or 10 - } - annotations = [ - # ANN_SHIFTED - ['7-bit shifted hex', - 'Read/write bit shifted out from the 8-bit I2C slave address'], - # ANN_SHIFTED_SHORT - ['7-bit shifted hex (short)', - 'Read/write bit shifted out from the 8-bit I2C slave address'], - # ANN_RAW - ['Raw hex', 'Unaltered raw data'], - ] - - def __init__(self, **kwargs): - self.startsample = -1 - self.samplenum = None - self.bitcount = 0 - self.databyte = 0 - self.wr = -1 - self.is_repeat_start = 0 - self.state = 'FIND START' - self.oldscl = None - self.oldsda = None - self.oldpins = None - - def start(self, metadata): - self.out_proto = self.add(srd.OUTPUT_PROTO, 'i2c') - self.out_ann = self.add(srd.OUTPUT_ANN, 'i2c') - - def report(self): - pass - - def is_start_condition(self, scl, sda): - # START condition (S): SDA = falling, SCL = high - if (self.oldsda == 1 and sda == 0) and scl == 1: - return True - return False - - def is_data_bit(self, scl, sda): - # Data sampling of receiver: SCL = rising - if self.oldscl == 0 and scl == 1: - return True - return False - - def is_stop_condition(self, scl, sda): - # STOP condition (P): SDA = rising, SCL = high - if (self.oldsda == 0 and sda == 1) and scl == 1: - return True - return False - - def found_start(self, scl, sda): - self.startsample = self.samplenum - - cmd = 'START REPEAT' if (self.is_repeat_start == 1) else 'START' - self.put(self.out_proto, [cmd, None]) - self.put(self.out_ann, [ANN_SHIFTED, [proto[cmd][0]]]) - self.put(self.out_ann, [ANN_SHIFTED_SHORT, [proto[cmd][1]]]) - - self.state = 'FIND ADDRESS' - self.bitcount = self.databyte = 0 - self.is_repeat_start = 1 - self.wr = -1 - - # Gather 8 bits of data plus the ACK/NACK bit. - def found_address_or_data(self, scl, sda): - # Address and data are transmitted MSB-first. - self.databyte <<= 1 - self.databyte |= sda - - if self.bitcount == 0: - self.startsample = self.samplenum - - # Return if we haven't collected all 8 + 1 bits, yet. - self.bitcount += 1 - if self.bitcount != 8: - return - - # We triggered on the ACK/NACK bit, but won't report that until later. - self.startsample -= 1 - - # Send raw output annotation before we start shifting out - # read/write and ACK/NACK bits. - self.put(self.out_ann, [ANN_RAW, ['0x%.2x' % self.databyte]]) - - if self.state == 'FIND ADDRESS': - # The READ/WRITE bit is only in address bytes, not data bytes. - self.wr = 0 if (self.databyte & 1) else 1 - d = self.databyte >> 1 - elif self.state == 'FIND DATA': - d = self.databyte - - if self.state == 'FIND ADDRESS' and self.wr == 1: - cmd = 'ADDRESS WRITE' - elif self.state == 'FIND ADDRESS' and self.wr == 0: - cmd = 'ADDRESS READ' - elif self.state == 'FIND DATA' and self.wr == 1: - cmd = 'DATA WRITE' - elif self.state == 'FIND DATA' and self.wr == 0: - cmd = 'DATA READ' - - self.put(self.out_proto, [cmd, d]) - self.put(self.out_ann, [ANN_SHIFTED, [proto[cmd][0], '0x%02x' % d]]) - self.put(self.out_ann, [ANN_SHIFTED_SHORT, [proto[cmd][1], '0x%02x' % d]]) - - # Done with this packet. - self.startsample = -1 - self.bitcount = self.databyte = 0 - self.state = 'FIND ACK' - - def get_ack(self, scl, sda): - self.startsample = self.samplenum - ack_bit = 'NACK' if (sda == 1) else 'ACK' - self.put(self.out_proto, [ack_bit, None]) - self.put(self.out_ann, [ANN_SHIFTED, [proto[ack_bit][0]]]) - self.put(self.out_ann, [ANN_SHIFTED_SHORT, [proto[ack_bit][1]]]) - # There could be multiple data bytes in a row, so either find - # another data byte or a STOP condition next. - self.state = 'FIND DATA' - - def found_stop(self, scl, sda): - self.startsample = self.samplenum - self.put(self.out_proto, ['STOP', None]) - self.put(self.out_ann, [ANN_SHIFTED, [proto['STOP'][0]]]) - self.put(self.out_ann, [ANN_SHIFTED_SHORT, [proto['STOP'][1]]]) - - self.state = 'FIND START' - self.is_repeat_start = 0 - self.wr = -1 - - def put(self, output_id, data): - # Inject sample range into the call up to sigrok. - super(Decoder, self).put(self.startsample, self.samplenum, output_id, data) - - 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, (scl, sda) = pins, pins - - # First sample: Save SCL/SDA value. - if self.oldscl == None: - self.oldscl = scl - self.oldsda = sda - continue - - # TODO: Wait until the bus is idle (SDA = SCL = 1) first? - - # State machine. - if self.state == 'FIND START': - if self.is_start_condition(scl, sda): - self.found_start(scl, sda) - elif self.state == 'FIND ADDRESS': - if self.is_data_bit(scl, sda): - self.found_address_or_data(scl, sda) - elif self.state == 'FIND DATA': - if self.is_data_bit(scl, sda): - self.found_address_or_data(scl, sda) - elif self.is_start_condition(scl, sda): - self.found_start(scl, sda) - elif self.is_stop_condition(scl, sda): - self.found_stop(scl, sda) - elif self.state == 'FIND ACK': - if self.is_data_bit(scl, sda): - self.get_ack(scl, sda) - else: - raise Exception('Invalid state %d' % self.STATE) - - # Save current SDA/SCL values for the next round. - self.oldscl = scl - self.oldsda = sda - diff --git a/decoders/i2c/pd.py b/decoders/i2c/pd.py new file mode 100644 index 0000000..53321eb --- /dev/null +++ b/decoders/i2c/pd.py @@ -0,0 +1,240 @@ +## +## This file is part of the sigrok project. +## +## Copyright (C) 2010-2011 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 +## + +# I2C protocol decoder + +# TODO: Look into arbitration, collision detection, clock synchronisation, etc. +# TODO: Handle clock stretching. +# TODO: Handle combined messages / repeated START. +# TODO: Implement support for 7bit and 10bit slave addresses. +# TODO: Implement support for inverting SDA/SCL levels (0->1 and 1->0). +# TODO: Implement support for detecting various bus errors. +# TODO: I2C address of slaves. +# TODO: Handle multiple different I2C devices on same bus +# -> we need to decode multiple protocols at the same time. + +import sigrokdecode as srd + +# Annotation feed formats +ANN_SHIFTED = 0 +ANN_SHIFTED_SHORT = 1 +ANN_RAW = 2 + +# Values are verbose and short annotation, respectively. +proto = { + 'START': ['START', 'S'], + 'START REPEAT': ['START REPEAT', 'Sr'], + 'STOP': ['STOP', 'P'], + 'ACK': ['ACK', 'A'], + 'NACK': ['NACK', 'N'], + 'ADDRESS READ': ['ADDRESS READ', 'AR'], + 'ADDRESS WRITE': ['ADDRESS WRITE', 'AW'], + 'DATA READ': ['DATA READ', 'DR'], + 'DATA WRITE': ['DATA WRITE', 'DW'], +} + +class Decoder(srd.Decoder): + api_version = 1 + id = 'i2c' + name = 'I2C' + longname = 'Inter-Integrated Circuit' + desc = 'Two-wire, multi-master, serial bus.' + license = 'gplv2+' + inputs = ['logic'] + outputs = ['i2c'] + probes = [ + {'id': 'scl', 'name': 'SCL', 'desc': 'Serial clock line'}, + {'id': 'sda', 'name': 'SDA', 'desc': 'Serial data line'}, + ] + optional_probes = [] + options = { + 'addressing': ['Slave addressing (in bits)', 7], # 7 or 10 + } + annotations = [ + # ANN_SHIFTED + ['7-bit shifted hex', + 'Read/write bit shifted out from the 8-bit I2C slave address'], + # ANN_SHIFTED_SHORT + ['7-bit shifted hex (short)', + 'Read/write bit shifted out from the 8-bit I2C slave address'], + # ANN_RAW + ['Raw hex', 'Unaltered raw data'], + ] + + def __init__(self, **kwargs): + self.startsample = -1 + self.samplenum = None + self.bitcount = 0 + self.databyte = 0 + self.wr = -1 + self.is_repeat_start = 0 + self.state = 'FIND START' + self.oldscl = None + self.oldsda = None + self.oldpins = None + + def start(self, metadata): + self.out_proto = self.add(srd.OUTPUT_PROTO, 'i2c') + self.out_ann = self.add(srd.OUTPUT_ANN, 'i2c') + + def report(self): + pass + + def is_start_condition(self, scl, sda): + # START condition (S): SDA = falling, SCL = high + if (self.oldsda == 1 and sda == 0) and scl == 1: + return True + return False + + def is_data_bit(self, scl, sda): + # Data sampling of receiver: SCL = rising + if self.oldscl == 0 and scl == 1: + return True + return False + + def is_stop_condition(self, scl, sda): + # STOP condition (P): SDA = rising, SCL = high + if (self.oldsda == 0 and sda == 1) and scl == 1: + return True + return False + + def found_start(self, scl, sda): + self.startsample = self.samplenum + + cmd = 'START REPEAT' if (self.is_repeat_start == 1) else 'START' + self.put(self.out_proto, [cmd, None]) + self.put(self.out_ann, [ANN_SHIFTED, [proto[cmd][0]]]) + self.put(self.out_ann, [ANN_SHIFTED_SHORT, [proto[cmd][1]]]) + + self.state = 'FIND ADDRESS' + self.bitcount = self.databyte = 0 + self.is_repeat_start = 1 + self.wr = -1 + + # Gather 8 bits of data plus the ACK/NACK bit. + def found_address_or_data(self, scl, sda): + # Address and data are transmitted MSB-first. + self.databyte <<= 1 + self.databyte |= sda + + if self.bitcount == 0: + self.startsample = self.samplenum + + # Return if we haven't collected all 8 + 1 bits, yet. + self.bitcount += 1 + if self.bitcount != 8: + return + + # We triggered on the ACK/NACK bit, but won't report that until later. + self.startsample -= 1 + + # Send raw output annotation before we start shifting out + # read/write and ACK/NACK bits. + self.put(self.out_ann, [ANN_RAW, ['0x%.2x' % self.databyte]]) + + if self.state == 'FIND ADDRESS': + # The READ/WRITE bit is only in address bytes, not data bytes. + self.wr = 0 if (self.databyte & 1) else 1 + d = self.databyte >> 1 + elif self.state == 'FIND DATA': + d = self.databyte + + if self.state == 'FIND ADDRESS' and self.wr == 1: + cmd = 'ADDRESS WRITE' + elif self.state == 'FIND ADDRESS' and self.wr == 0: + cmd = 'ADDRESS READ' + elif self.state == 'FIND DATA' and self.wr == 1: + cmd = 'DATA WRITE' + elif self.state == 'FIND DATA' and self.wr == 0: + cmd = 'DATA READ' + + self.put(self.out_proto, [cmd, d]) + self.put(self.out_ann, [ANN_SHIFTED, [proto[cmd][0], '0x%02x' % d]]) + self.put(self.out_ann, [ANN_SHIFTED_SHORT, [proto[cmd][1], '0x%02x' % d]]) + + # Done with this packet. + self.startsample = -1 + self.bitcount = self.databyte = 0 + self.state = 'FIND ACK' + + def get_ack(self, scl, sda): + self.startsample = self.samplenum + ack_bit = 'NACK' if (sda == 1) else 'ACK' + self.put(self.out_proto, [ack_bit, None]) + self.put(self.out_ann, [ANN_SHIFTED, [proto[ack_bit][0]]]) + self.put(self.out_ann, [ANN_SHIFTED_SHORT, [proto[ack_bit][1]]]) + # There could be multiple data bytes in a row, so either find + # another data byte or a STOP condition next. + self.state = 'FIND DATA' + + def found_stop(self, scl, sda): + self.startsample = self.samplenum + self.put(self.out_proto, ['STOP', None]) + self.put(self.out_ann, [ANN_SHIFTED, [proto['STOP'][0]]]) + self.put(self.out_ann, [ANN_SHIFTED_SHORT, [proto['STOP'][1]]]) + + self.state = 'FIND START' + self.is_repeat_start = 0 + self.wr = -1 + + def put(self, output_id, data): + # Inject sample range into the call up to sigrok. + super(Decoder, self).put(self.startsample, self.samplenum, output_id, data) + + 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, (scl, sda) = pins, pins + + # First sample: Save SCL/SDA value. + if self.oldscl == None: + self.oldscl = scl + self.oldsda = sda + continue + + # TODO: Wait until the bus is idle (SDA = SCL = 1) first? + + # State machine. + if self.state == 'FIND START': + if self.is_start_condition(scl, sda): + self.found_start(scl, sda) + elif self.state == 'FIND ADDRESS': + if self.is_data_bit(scl, sda): + self.found_address_or_data(scl, sda) + elif self.state == 'FIND DATA': + if self.is_data_bit(scl, sda): + self.found_address_or_data(scl, sda) + elif self.is_start_condition(scl, sda): + self.found_start(scl, sda) + elif self.is_stop_condition(scl, sda): + self.found_stop(scl, sda) + elif self.state == 'FIND ACK': + if self.is_data_bit(scl, sda): + self.get_ack(scl, sda) + else: + raise Exception('Invalid state %d' % self.STATE) + + # Save current SDA/SCL values for the next round. + self.oldscl = scl + self.oldsda = sda + diff --git a/decoders/i2cdemux/Makefile.am b/decoders/i2cdemux/Makefile.am index d305394..bc45d02 100644 --- a/decoders/i2cdemux/Makefile.am +++ b/decoders/i2cdemux/Makefile.am @@ -20,7 +20,7 @@ pkgdatadir = $(DECODERS_DIR)/i2cdemux -dist_pkgdata_DATA = __init__.py i2cdemux.py +dist_pkgdata_DATA = __init__.py pd.py CLEANFILES = *.pyc diff --git a/decoders/i2cdemux/__init__.py b/decoders/i2cdemux/__init__.py index 445a88e..1e5afbd 100644 --- a/decoders/i2cdemux/__init__.py +++ b/decoders/i2cdemux/__init__.py @@ -25,5 +25,5 @@ Takes an I2C stream as input and outputs multiple I2C streams, each stream containing only I2C packets for one specific I2C slave. ''' -from .i2cdemux import * +from .pd import * diff --git a/decoders/i2cdemux/i2cdemux.py b/decoders/i2cdemux/i2cdemux.py deleted file mode 100644 index bb47f23..0000000 --- a/decoders/i2cdemux/i2cdemux.py +++ /dev/null @@ -1,87 +0,0 @@ -## -## This file is part of the sigrok 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 -## - -# Generic I2C demultiplexing protocol decoder - -import sigrokdecode as srd - -class Decoder(srd.Decoder): - api_version = 1 - id = 'i2cdemux' - name = 'I2C demux' - longname = 'I2C demultiplexer' - desc = 'Demux I2C packets into per-slave-address streams.' - license = 'gplv2+' - inputs = ['i2c'] - outputs = [] # TODO: Only known at run-time. - probes = [] - optional_probes = [] - options = {} - annotations = [] - - def __init__(self, **kwargs): - self.packets = [] # Local cache of I2C packets - self.slaves = [] # List of known slave addresses - self.stream = -1 # Current output stream - self.streamcount = 0 # Number of created output streams - - def start(self, metadata): - self.out_proto = [] - - def report(self): - pass - - # Grab I2C packets into a local cache, until an I2C STOP condition - # packet comes along. At some point before that STOP condition, there - # will have been an ADDRESS READ or ADDRESS WRITE which contains the - # I2C address of the slave that the master wants to talk to. - # We use this slave address to figure out which output stream should - # get the whole chunk of packets (from START to STOP). - def decode(self, ss, es, data): - - cmd, databyte = data - - # Add the I2C packet to our local cache. - self.packets.append([ss, es, data]) - - if cmd in ('ADDRESS READ', 'ADDRESS WRITE'): - if databyte in self.slaves: - self.stream = self.slaves.index(databyte) - return - - # We're never seen this slave, add a new stream. - self.slaves.append(databyte) - self.out_proto.append(self.add(srd.OUTPUT_PROTO, - 'i2c-%s' % hex(databyte))) - self.stream = self.streamcount - self.streamcount += 1 - elif cmd == 'STOP': - if self.stream == -1: - raise Exception('Invalid stream!') # FIXME? - - # Send the whole chunk of I2C packets to the correct stream. - for p in self.packets: - self.put(p[0], p[1], self.out_proto[self.stream], p[2]) - - self.packets = [] - self.stream = -1 - else: - pass # Do nothing, only add the I2C packet to our cache. - diff --git a/decoders/i2cdemux/pd.py b/decoders/i2cdemux/pd.py new file mode 100644 index 0000000..bb47f23 --- /dev/null +++ b/decoders/i2cdemux/pd.py @@ -0,0 +1,87 @@ +## +## This file is part of the sigrok 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 +## + +# Generic I2C demultiplexing protocol decoder + +import sigrokdecode as srd + +class Decoder(srd.Decoder): + api_version = 1 + id = 'i2cdemux' + name = 'I2C demux' + longname = 'I2C demultiplexer' + desc = 'Demux I2C packets into per-slave-address streams.' + license = 'gplv2+' + inputs = ['i2c'] + outputs = [] # TODO: Only known at run-time. + probes = [] + optional_probes = [] + options = {} + annotations = [] + + def __init__(self, **kwargs): + self.packets = [] # Local cache of I2C packets + self.slaves = [] # List of known slave addresses + self.stream = -1 # Current output stream + self.streamcount = 0 # Number of created output streams + + def start(self, metadata): + self.out_proto = [] + + def report(self): + pass + + # Grab I2C packets into a local cache, until an I2C STOP condition + # packet comes along. At some point before that STOP condition, there + # will have been an ADDRESS READ or ADDRESS WRITE which contains the + # I2C address of the slave that the master wants to talk to. + # We use this slave address to figure out which output stream should + # get the whole chunk of packets (from START to STOP). + def decode(self, ss, es, data): + + cmd, databyte = data + + # Add the I2C packet to our local cache. + self.packets.append([ss, es, data]) + + if cmd in ('ADDRESS READ', 'ADDRESS WRITE'): + if databyte in self.slaves: + self.stream = self.slaves.index(databyte) + return + + # We're never seen this slave, add a new stream. + self.slaves.append(databyte) + self.out_proto.append(self.add(srd.OUTPUT_PROTO, + 'i2c-%s' % hex(databyte))) + self.stream = self.streamcount + self.streamcount += 1 + elif cmd == 'STOP': + if self.stream == -1: + raise Exception('Invalid stream!') # FIXME? + + # Send the whole chunk of I2C packets to the correct stream. + for p in self.packets: + self.put(p[0], p[1], self.out_proto[self.stream], p[2]) + + self.packets = [] + self.stream = -1 + else: + pass # Do nothing, only add the I2C packet to our cache. + diff --git a/decoders/i2cfilter/Makefile.am b/decoders/i2cfilter/Makefile.am index 5b90489..ff7abc4 100644 --- a/decoders/i2cfilter/Makefile.am +++ b/decoders/i2cfilter/Makefile.am @@ -19,7 +19,7 @@ pkgdatadir = $(DECODERS_DIR)/i2cfilter -dist_pkgdata_DATA = __init__.py i2cfilter.py +dist_pkgdata_DATA = __init__.py pd.py CLEANFILES = *.pyc diff --git a/decoders/i2cfilter/__init__.py b/decoders/i2cfilter/__init__.py index 6443f5b..9a0b256 100644 --- a/decoders/i2cfilter/__init__.py +++ b/decoders/i2cfilter/__init__.py @@ -34,5 +34,5 @@ Both of these are optional; if no options are specified the entire payload of the I2C session will be output. ''' -from .i2cfilter import * +from .pd import * diff --git a/decoders/i2cfilter/i2cfilter.py b/decoders/i2cfilter/i2cfilter.py deleted file mode 100644 index 9c9c43a..0000000 --- a/decoders/i2cfilter/i2cfilter.py +++ /dev/null @@ -1,99 +0,0 @@ -## -## This file is part of the sigrok project. -## -## Copyright (C) 2012 Bert Vermeulen -## Copyright (C) 2012 Uwe Hermann -## -## This program is free software; you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published by -## the Free Software Foundation; either version 3 of the License, or -## (at your option) any later version. -## -## This program is distributed in the hope that it will be useful, -## but WITHOUT ANY WARRANTY; without even the implied warranty of -## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -## GNU General Public License for more details. -## -## You should have received a copy of the GNU General Public License -## along with this program; if not, see . -## - -# Generic I2C filtering protocol decoder - -# TODO: Support for filtering out multiple slave/direction pairs? - -import sigrokdecode as srd - -class Decoder(srd.Decoder): - api_version = 1 - id = 'i2cfilter' - name = 'I2C filter' - longname = 'I2C filter' - desc = 'Filter out addresses/directions in an I2C stream.' - license = 'gplv3+' - inputs = ['i2c'] - outputs = ['i2c'] - probes = [] - optional_probes = [] - options = { - 'address': ['Address to filter out of the I2C stream', 0], - 'direction': ['Direction to filter (read/write/both)', 'both'] - } - annotations = [] - - def __init__(self, **kwargs): - self.state = None - self.curslave = -1 - self.curdirection = None - self.packets = [] # Local cache of I2C packets - - def start(self, metadata): - self.out_proto = self.add(srd.OUTPUT_PROTO, '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).') - - def report(self): - pass - - # Grab I2C packets into a local cache, until an I2C STOP condition - # packet comes along. At some point before that STOP condition, there - # will have been an ADDRESS READ or ADDRESS WRITE which contains the - # I2C address of the slave that the master wants to talk to. - # If that slave shall be filtered, output the cache (all packets from - # START to STOP) as proto 'i2c', otherwise drop it. - def decode(self, ss, es, data): - - cmd, databyte = data - - # Add the I2C packet to our local cache. - self.packets.append([ss, es, data]) - - if cmd in ('ADDRESS READ', 'ADDRESS WRITE'): - self.curslave = databyte - self.curdirection = cmd[8:].lower() - elif cmd in ('STOP', 'START REPEAT'): - # If this chunk was not for the correct slave, drop it. - if self.options['address'] == 0: - pass - elif self.curslave != self.options['address']: - self.packets = [] - return - - # If this chunk was not in the right direction, drop it. - if self.options['direction'] == 'both': - pass - elif self.options['direction'] != self.curdirection: - self.packets = [] - return - - # TODO: START->STOP chunks with both read and write (Repeat START) - # Otherwise, send out the whole chunk of I2C packets. - for p in self.packets: - self.put(p[0], p[1], self.out_proto, p[2]) - - self.packets = [] - else: - pass # Do nothing, only add the I2C packet to our cache. - diff --git a/decoders/i2cfilter/pd.py b/decoders/i2cfilter/pd.py new file mode 100644 index 0000000..9c9c43a --- /dev/null +++ b/decoders/i2cfilter/pd.py @@ -0,0 +1,99 @@ +## +## This file is part of the sigrok project. +## +## Copyright (C) 2012 Bert Vermeulen +## Copyright (C) 2012 Uwe Hermann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 3 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see . +## + +# Generic I2C filtering protocol decoder + +# TODO: Support for filtering out multiple slave/direction pairs? + +import sigrokdecode as srd + +class Decoder(srd.Decoder): + api_version = 1 + id = 'i2cfilter' + name = 'I2C filter' + longname = 'I2C filter' + desc = 'Filter out addresses/directions in an I2C stream.' + license = 'gplv3+' + inputs = ['i2c'] + outputs = ['i2c'] + probes = [] + optional_probes = [] + options = { + 'address': ['Address to filter out of the I2C stream', 0], + 'direction': ['Direction to filter (read/write/both)', 'both'] + } + annotations = [] + + def __init__(self, **kwargs): + self.state = None + self.curslave = -1 + self.curdirection = None + self.packets = [] # Local cache of I2C packets + + def start(self, metadata): + self.out_proto = self.add(srd.OUTPUT_PROTO, '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).') + + def report(self): + pass + + # Grab I2C packets into a local cache, until an I2C STOP condition + # packet comes along. At some point before that STOP condition, there + # will have been an ADDRESS READ or ADDRESS WRITE which contains the + # I2C address of the slave that the master wants to talk to. + # If that slave shall be filtered, output the cache (all packets from + # START to STOP) as proto 'i2c', otherwise drop it. + def decode(self, ss, es, data): + + cmd, databyte = data + + # Add the I2C packet to our local cache. + self.packets.append([ss, es, data]) + + if cmd in ('ADDRESS READ', 'ADDRESS WRITE'): + self.curslave = databyte + self.curdirection = cmd[8:].lower() + elif cmd in ('STOP', 'START REPEAT'): + # If this chunk was not for the correct slave, drop it. + if self.options['address'] == 0: + pass + elif self.curslave != self.options['address']: + self.packets = [] + return + + # If this chunk was not in the right direction, drop it. + if self.options['direction'] == 'both': + pass + elif self.options['direction'] != self.curdirection: + self.packets = [] + return + + # TODO: START->STOP chunks with both read and write (Repeat START) + # Otherwise, send out the whole chunk of I2C packets. + for p in self.packets: + self.put(p[0], p[1], self.out_proto, p[2]) + + self.packets = [] + else: + pass # Do nothing, only add the I2C packet to our cache. + diff --git a/decoders/i2s/Makefile.am b/decoders/i2s/Makefile.am index ff66902..1fcafce 100644 --- a/decoders/i2s/Makefile.am +++ b/decoders/i2s/Makefile.am @@ -20,7 +20,7 @@ pkgdatadir = $(DECODERS_DIR)/i2s -dist_pkgdata_DATA = __init__.py i2s.py +dist_pkgdata_DATA = __init__.py pd.py CLEANFILES = *.pyc diff --git a/decoders/i2s/__init__.py b/decoders/i2s/__init__.py index 81b785b..f48e94f 100644 --- a/decoders/i2s/__init__.py +++ b/decoders/i2s/__init__.py @@ -26,5 +26,5 @@ http://www.nxp.com/acrobat_download/various/I2SBUS.pdf http://en.wikipedia.org/wiki/I2s ''' -from .i2s import * +from .pd import * diff --git a/decoders/i2s/i2s.py b/decoders/i2s/i2s.py deleted file mode 100644 index b921011..0000000 --- a/decoders/i2s/i2s.py +++ /dev/null @@ -1,124 +0,0 @@ -## -## This file is part of the sigrok project. -## -## Copyright (C) 2012 Joel Holdsworth -## -## This program is free software; you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published by -## the Free Software Foundation; either version 2 of the License, or -## (at your option) any later version. -## -## This program is distributed in the hope that it will be useful, -## but WITHOUT ANY WARRANTY; without even the implied warranty of -## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -## GNU General Public License for more details. -## -## You should have received a copy of the GNU General Public License -## along with this program; if not, write to the Free Software -## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -## - -# I2S protocol decoder - -import sigrokdecode as srd - -# Annotation formats -ANN_HEX = 0 - -class Decoder(srd.Decoder): - api_version = 1 - id = 'i2s' - name = 'I2S' - longname = 'Integrated Interchip Sound' - desc = 'Serial bus for connecting digital audio devices.' - license = 'gplv2+' - inputs = ['logic'] - outputs = ['i2s'] - probes = [ - {'id': 'sck', 'name': 'SCK', 'desc': 'Bit clock line'}, - {'id': 'ws', 'name': 'WS', 'desc': 'Word select line'}, - {'id': 'sd', 'name': 'SD', 'desc': 'Serial data line'}, - ] - optional_probes = [] - options = {} - annotations = [ - ['Hex', 'Annotations in hex format'], - ] - - def __init__(self, **kwargs): - self.oldsck = 1 - self.oldws = 1 - self.bitcount = 0 - self.data = 0 - self.samplesreceived = 0 - self.first_sample = None - self.start_sample = None - self.samplenum = -1 - self.wordlength = -1 - - def start(self, metadata): - self.samplerate = metadata['samplerate'] - self.out_proto = self.add(srd.OUTPUT_PROTO, 'i2s') - self.out_ann = self.add(srd.OUTPUT_ANN, 'i2s') - - 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: - samplerate = '%d' % (self.samplesreceived * - self.samplerate / (self.start_sample - - self.first_sample)) - - return 'I2S: %d %d-bit samples received at %sHz' % \ - (self.samplesreceived, self.wordlength, samplerate) - - def decode(self, ss, es, data): - for samplenum, (sck, ws, sd) in data: - - # Ignore sample if the bit clock hasn't changed. - if sck == self.oldsck: - continue - - self.oldsck = sck - if sck == 0: # Ignore the falling clock edge. - continue - - self.data = (self.data << 1) | sd - self.bitcount += 1 - - # This was not the LSB unless WS has flipped. - if ws == self.oldws: - continue - - # Only submit the sample, if we received the beginning of it. - if self.start_sample != None: - self.samplesreceived += 1 - self.put(self.start_sample, self.samplenum, self.out_proto, - ['data', self.data]) - self.put(self.start_sample, self.samplenum, self.out_ann, - [ANN_HEX, ['%s: 0x%08x' % ('L' if self.oldws else 'R', - self.data)]]) - - # Check that the data word was the correct length. - if self.wordlength != -1 and self.wordlength != self.bitcount: - self.put(self.start_sample, self.samplenum, self.out_ann, - [ANN_HEX, ['WARNING: Received a %d-bit word, when a ' - '%d-bit word was expected' % (self.bitcount, - self.wordlength)]]) - - self.wordlength = self.bitcount - - # Reset decoder state. - self.data = 0 - self.bitcount = 0 - self.start_sample = self.samplenum - - # Save the first sample position. - if self.first_sample == None: - self.first_sample = self.samplenum - - self.oldws = ws - diff --git a/decoders/i2s/pd.py b/decoders/i2s/pd.py new file mode 100644 index 0000000..b921011 --- /dev/null +++ b/decoders/i2s/pd.py @@ -0,0 +1,124 @@ +## +## This file is part of the sigrok project. +## +## Copyright (C) 2012 Joel Holdsworth +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + +# I2S protocol decoder + +import sigrokdecode as srd + +# Annotation formats +ANN_HEX = 0 + +class Decoder(srd.Decoder): + api_version = 1 + id = 'i2s' + name = 'I2S' + longname = 'Integrated Interchip Sound' + desc = 'Serial bus for connecting digital audio devices.' + license = 'gplv2+' + inputs = ['logic'] + outputs = ['i2s'] + probes = [ + {'id': 'sck', 'name': 'SCK', 'desc': 'Bit clock line'}, + {'id': 'ws', 'name': 'WS', 'desc': 'Word select line'}, + {'id': 'sd', 'name': 'SD', 'desc': 'Serial data line'}, + ] + optional_probes = [] + options = {} + annotations = [ + ['Hex', 'Annotations in hex format'], + ] + + def __init__(self, **kwargs): + self.oldsck = 1 + self.oldws = 1 + self.bitcount = 0 + self.data = 0 + self.samplesreceived = 0 + self.first_sample = None + self.start_sample = None + self.samplenum = -1 + self.wordlength = -1 + + def start(self, metadata): + self.samplerate = metadata['samplerate'] + self.out_proto = self.add(srd.OUTPUT_PROTO, 'i2s') + self.out_ann = self.add(srd.OUTPUT_ANN, 'i2s') + + 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: + samplerate = '%d' % (self.samplesreceived * + self.samplerate / (self.start_sample - + self.first_sample)) + + return 'I2S: %d %d-bit samples received at %sHz' % \ + (self.samplesreceived, self.wordlength, samplerate) + + def decode(self, ss, es, data): + for samplenum, (sck, ws, sd) in data: + + # Ignore sample if the bit clock hasn't changed. + if sck == self.oldsck: + continue + + self.oldsck = sck + if sck == 0: # Ignore the falling clock edge. + continue + + self.data = (self.data << 1) | sd + self.bitcount += 1 + + # This was not the LSB unless WS has flipped. + if ws == self.oldws: + continue + + # Only submit the sample, if we received the beginning of it. + if self.start_sample != None: + self.samplesreceived += 1 + self.put(self.start_sample, self.samplenum, self.out_proto, + ['data', self.data]) + self.put(self.start_sample, self.samplenum, self.out_ann, + [ANN_HEX, ['%s: 0x%08x' % ('L' if self.oldws else 'R', + self.data)]]) + + # Check that the data word was the correct length. + if self.wordlength != -1 and self.wordlength != self.bitcount: + self.put(self.start_sample, self.samplenum, self.out_ann, + [ANN_HEX, ['WARNING: Received a %d-bit word, when a ' + '%d-bit word was expected' % (self.bitcount, + self.wordlength)]]) + + self.wordlength = self.bitcount + + # Reset decoder state. + self.data = 0 + self.bitcount = 0 + self.start_sample = self.samplenum + + # Save the first sample position. + if self.first_sample == None: + self.first_sample = self.samplenum + + self.oldws = ws + diff --git a/decoders/jtag/Makefile.am b/decoders/jtag/Makefile.am index ce6aede..6d96842 100644 --- a/decoders/jtag/Makefile.am +++ b/decoders/jtag/Makefile.am @@ -20,7 +20,7 @@ pkgdatadir = $(DECODERS_DIR)/jtag -dist_pkgdata_DATA = __init__.py jtag.py +dist_pkgdata_DATA = __init__.py pd.py CLEANFILES = *.pyc diff --git a/decoders/jtag/__init__.py b/decoders/jtag/__init__.py index 4a53502..4538e0e 100644 --- a/decoders/jtag/__init__.py +++ b/decoders/jtag/__init__.py @@ -52,5 +52,5 @@ https://en.wikipedia.org/wiki/Joint_Test_Action_Group http://focus.ti.com/lit/an/ssya002c/ssya002c.pdf ''' -from .jtag import * +from .pd import * diff --git a/decoders/jtag/jtag.py b/decoders/jtag/jtag.py deleted file mode 100644 index 4e27ce8..0000000 --- a/decoders/jtag/jtag.py +++ /dev/null @@ -1,180 +0,0 @@ -## -## This file is part of the sigrok 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 -## - -# JTAG protocol decoder - -import sigrokdecode as srd - -class Decoder(srd.Decoder): - api_version = 1 - id = 'jtag' - name = 'JTAG' - longname = 'Joint Test Action Group (IEEE 1149.1)' - desc = 'Protocol for testing, debugging, and flashing ICs.' - license = 'gplv2+' - inputs = ['logic'] - outputs = ['jtag'] - probes = [ - {'id': 'tdi', 'name': 'TDI', 'desc': 'Test data input'}, - {'id': 'tdo', 'name': 'TDO', 'desc': 'Test data output'}, - {'id': 'tck', 'name': 'TCK', 'desc': 'Test clock'}, - {'id': 'tms', 'name': 'TMS', 'desc': 'Test mode select'}, - ] - optional_probes = [ - {'id': 'trst', 'name': 'TRST#', 'desc': 'Test reset'}, - {'id': 'srst', 'name': 'SRST#', 'desc': 'System reset'}, - {'id': 'rtck', 'name': 'RTCK', 'desc': 'Return clock signal'}, - ] - options = {} - annotations = [ - ['Text', 'Human-readable text'], - ] - - def __init__(self, **kwargs): - # self.state = 'TEST-LOGIC-RESET' - self.state = 'RUN-TEST/IDLE' - self.oldstate = None - self.oldpins = (-1, -1, -1, -1) - self.oldtck = -1 - self.bits_tdi = [] - self.bits_tdo = [] - - def start(self, metadata): - self.out_proto = self.add(srd.OUTPUT_PROTO, 'jtag') - self.out_ann = self.add(srd.OUTPUT_ANN, 'jtag') - - def report(self): - pass - - def advance_state_machine(self, tms): - self.oldstate = self.state - - # Intro "tree" - if self.state == 'TEST-LOGIC-RESET': - self.state = 'TEST-LOGIC-RESET' if (tms) else 'RUN-TEST/IDLE' - elif self.state == 'RUN-TEST/IDLE': - self.state = 'SELECT-DR-SCAN' if (tms) else 'RUN-TEST/IDLE' - - # DR "tree" - elif self.state == 'SELECT-DR-SCAN': - self.state = 'SELECT-IR-SCAN' if (tms) else 'CAPTURE-DR' - elif self.state == 'CAPTURE-DR': - self.state = 'EXIT1-DR' if (tms) else 'SHIFT-DR' - elif self.state == 'SHIFT-DR': - self.state = 'EXIT1-DR' if (tms) else 'SHIFT-DR' - elif self.state == 'EXIT1-DR': - self.state = 'UPDATE-DR' if (tms) else 'PAUSE-DR' - elif self.state == 'PAUSE-DR': - self.state = 'EXIT2-DR' if (tms) else 'PAUSE-DR' - elif self.state == 'EXIT2-DR': - self.state = 'UPDATE-DR' if (tms) else 'SHIFT-DR' - elif self.state == 'UPDATE-DR': - self.state = 'SELECT-DR-SCAN' if (tms) else 'RUN-TEST/IDLE' - - # IR "tree" - elif self.state == 'SELECT-IR-SCAN': - self.state = 'TEST-LOGIC-RESET' if (tms) else 'CAPTURE-IR' - elif self.state == 'CAPTURE-IR': - self.state = 'EXIT1-IR' if (tms) else 'SHIFT-IR' - elif self.state == 'SHIFT-IR': - self.state = 'EXIT1-IR' if (tms) else 'SHIFT-IR' - elif self.state == 'EXIT1-IR': - self.state = 'UPDATE-IR' if (tms) else 'PAUSE-IR' - elif self.state == 'PAUSE-IR': - self.state = 'EXIT2-IR' if (tms) else 'PAUSE-IR' - elif self.state == 'EXIT2-IR': - self.state = 'UPDATE-IR' if (tms) else 'SHIFT-IR' - elif self.state == 'UPDATE-IR': - self.state = 'SELECT-DR-SCAN' if (tms) else 'RUN-TEST/IDLE' - - 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) - - # Output the state we just switched to. - self.put(self.ss, self.es, self.out_ann, - [0, ['New state: %s' % self.state]]) - self.put(self.ss, self.es, self.out_proto, - ['NEW STATE', self.state]) - - # If we went from SHIFT-IR to SHIFT-IR, or SHIFT-DR to SHIFT-DR, - # collect the current TDI/TDO values (upon rising TCK edge). - if self.state.startswith('SHIFT-') and self.oldstate == self.state: - self.bits_tdi.insert(0, tdi) - self.bits_tdo.insert(0, tdo) - # TODO: ANN/PROTO output. - # self.put(self.ss, self.es, self.out_ann, - # [0, ['TDI add: ' + str(tdi)]]) - # self.put(self.ss, self.es, self.out_ann, - # [0, ['TDO add: ' + str(tdo)]]) - - # Output all TDI/TDO bits if we just switched from SHIFT-* to EXIT1-*. - if self.oldstate.startswith('SHIFT-') and \ - self.state.startswith('EXIT1-'): - - t = self.state[-2:] + ' TDI' - b = ''.join(map(str, self.bits_tdi)) - h = ' (0x%x' % int('0b' + b, 2) + ')' - s = t + ': ' + b + h + ', ' + str(len(self.bits_tdi)) + ' bits' - self.put(self.ss, self.es, self.out_ann, [0, [s]]) - self.put(self.ss, self.es, self.out_proto, [t, b]) - self.bits_tdi = [] - - t = self.state[-2:] + ' TDO' - b = ''.join(map(str, self.bits_tdo)) - h = ' (0x%x' % int('0b' + b, 2) + ')' - s = t + ': ' + b + h + ', ' + str(len(self.bits_tdo)) + ' bits' - self.put(self.ss, self.es, self.out_ann, [0, [s]]) - self.put(self.ss, self.es, self.out_proto, [t, b]) - self.bits_tdo = [] - - def decode(self, ss, es, data): - for (samplenum, pins) in data: - - # If none of the pins changed, there's nothing to do. - if self.oldpins == pins: - continue - - # Store current pin values for the next round. - self.oldpins = pins - - # Get individual pin values into local variables. - # Unused probes will have a value of > 1. - (tdi, tdo, tck, tms, trst, srst, rtck) = pins - - # We only care about TCK edges (either rising or falling). - if (self.oldtck == tck): - continue - - # Store start/end sample for later usage. - self.ss, self.es = ss, es - - # self.put(self.ss, self.es, self.out_ann, - # [0, ['tdi:%s, tdo:%s, tck:%s, tms:%s' \ - # % (tdi, tdo, tck, tms)]]) - - if (self.oldtck == 0 and tck == 1): - self.handle_rising_tck_edge(tdi, tdo, tck, tms) - - self.oldtck = tck - diff --git a/decoders/jtag/pd.py b/decoders/jtag/pd.py new file mode 100644 index 0000000..4e27ce8 --- /dev/null +++ b/decoders/jtag/pd.py @@ -0,0 +1,180 @@ +## +## This file is part of the sigrok 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 +## + +# JTAG protocol decoder + +import sigrokdecode as srd + +class Decoder(srd.Decoder): + api_version = 1 + id = 'jtag' + name = 'JTAG' + longname = 'Joint Test Action Group (IEEE 1149.1)' + desc = 'Protocol for testing, debugging, and flashing ICs.' + license = 'gplv2+' + inputs = ['logic'] + outputs = ['jtag'] + probes = [ + {'id': 'tdi', 'name': 'TDI', 'desc': 'Test data input'}, + {'id': 'tdo', 'name': 'TDO', 'desc': 'Test data output'}, + {'id': 'tck', 'name': 'TCK', 'desc': 'Test clock'}, + {'id': 'tms', 'name': 'TMS', 'desc': 'Test mode select'}, + ] + optional_probes = [ + {'id': 'trst', 'name': 'TRST#', 'desc': 'Test reset'}, + {'id': 'srst', 'name': 'SRST#', 'desc': 'System reset'}, + {'id': 'rtck', 'name': 'RTCK', 'desc': 'Return clock signal'}, + ] + options = {} + annotations = [ + ['Text', 'Human-readable text'], + ] + + def __init__(self, **kwargs): + # self.state = 'TEST-LOGIC-RESET' + self.state = 'RUN-TEST/IDLE' + self.oldstate = None + self.oldpins = (-1, -1, -1, -1) + self.oldtck = -1 + self.bits_tdi = [] + self.bits_tdo = [] + + def start(self, metadata): + self.out_proto = self.add(srd.OUTPUT_PROTO, 'jtag') + self.out_ann = self.add(srd.OUTPUT_ANN, 'jtag') + + def report(self): + pass + + def advance_state_machine(self, tms): + self.oldstate = self.state + + # Intro "tree" + if self.state == 'TEST-LOGIC-RESET': + self.state = 'TEST-LOGIC-RESET' if (tms) else 'RUN-TEST/IDLE' + elif self.state == 'RUN-TEST/IDLE': + self.state = 'SELECT-DR-SCAN' if (tms) else 'RUN-TEST/IDLE' + + # DR "tree" + elif self.state == 'SELECT-DR-SCAN': + self.state = 'SELECT-IR-SCAN' if (tms) else 'CAPTURE-DR' + elif self.state == 'CAPTURE-DR': + self.state = 'EXIT1-DR' if (tms) else 'SHIFT-DR' + elif self.state == 'SHIFT-DR': + self.state = 'EXIT1-DR' if (tms) else 'SHIFT-DR' + elif self.state == 'EXIT1-DR': + self.state = 'UPDATE-DR' if (tms) else 'PAUSE-DR' + elif self.state == 'PAUSE-DR': + self.state = 'EXIT2-DR' if (tms) else 'PAUSE-DR' + elif self.state == 'EXIT2-DR': + self.state = 'UPDATE-DR' if (tms) else 'SHIFT-DR' + elif self.state == 'UPDATE-DR': + self.state = 'SELECT-DR-SCAN' if (tms) else 'RUN-TEST/IDLE' + + # IR "tree" + elif self.state == 'SELECT-IR-SCAN': + self.state = 'TEST-LOGIC-RESET' if (tms) else 'CAPTURE-IR' + elif self.state == 'CAPTURE-IR': + self.state = 'EXIT1-IR' if (tms) else 'SHIFT-IR' + elif self.state == 'SHIFT-IR': + self.state = 'EXIT1-IR' if (tms) else 'SHIFT-IR' + elif self.state == 'EXIT1-IR': + self.state = 'UPDATE-IR' if (tms) else 'PAUSE-IR' + elif self.state == 'PAUSE-IR': + self.state = 'EXIT2-IR' if (tms) else 'PAUSE-IR' + elif self.state == 'EXIT2-IR': + self.state = 'UPDATE-IR' if (tms) else 'SHIFT-IR' + elif self.state == 'UPDATE-IR': + self.state = 'SELECT-DR-SCAN' if (tms) else 'RUN-TEST/IDLE' + + 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) + + # Output the state we just switched to. + self.put(self.ss, self.es, self.out_ann, + [0, ['New state: %s' % self.state]]) + self.put(self.ss, self.es, self.out_proto, + ['NEW STATE', self.state]) + + # If we went from SHIFT-IR to SHIFT-IR, or SHIFT-DR to SHIFT-DR, + # collect the current TDI/TDO values (upon rising TCK edge). + if self.state.startswith('SHIFT-') and self.oldstate == self.state: + self.bits_tdi.insert(0, tdi) + self.bits_tdo.insert(0, tdo) + # TODO: ANN/PROTO output. + # self.put(self.ss, self.es, self.out_ann, + # [0, ['TDI add: ' + str(tdi)]]) + # self.put(self.ss, self.es, self.out_ann, + # [0, ['TDO add: ' + str(tdo)]]) + + # Output all TDI/TDO bits if we just switched from SHIFT-* to EXIT1-*. + if self.oldstate.startswith('SHIFT-') and \ + self.state.startswith('EXIT1-'): + + t = self.state[-2:] + ' TDI' + b = ''.join(map(str, self.bits_tdi)) + h = ' (0x%x' % int('0b' + b, 2) + ')' + s = t + ': ' + b + h + ', ' + str(len(self.bits_tdi)) + ' bits' + self.put(self.ss, self.es, self.out_ann, [0, [s]]) + self.put(self.ss, self.es, self.out_proto, [t, b]) + self.bits_tdi = [] + + t = self.state[-2:] + ' TDO' + b = ''.join(map(str, self.bits_tdo)) + h = ' (0x%x' % int('0b' + b, 2) + ')' + s = t + ': ' + b + h + ', ' + str(len(self.bits_tdo)) + ' bits' + self.put(self.ss, self.es, self.out_ann, [0, [s]]) + self.put(self.ss, self.es, self.out_proto, [t, b]) + self.bits_tdo = [] + + def decode(self, ss, es, data): + for (samplenum, pins) in data: + + # If none of the pins changed, there's nothing to do. + if self.oldpins == pins: + continue + + # Store current pin values for the next round. + self.oldpins = pins + + # Get individual pin values into local variables. + # Unused probes will have a value of > 1. + (tdi, tdo, tck, tms, trst, srst, rtck) = pins + + # We only care about TCK edges (either rising or falling). + if (self.oldtck == tck): + continue + + # Store start/end sample for later usage. + self.ss, self.es = ss, es + + # self.put(self.ss, self.es, self.out_ann, + # [0, ['tdi:%s, tdo:%s, tck:%s, tms:%s' \ + # % (tdi, tdo, tck, tms)]]) + + if (self.oldtck == 0 and tck == 1): + self.handle_rising_tck_edge(tdi, tdo, tck, tms) + + self.oldtck = tck + diff --git a/decoders/jtag_stm32/Makefile.am b/decoders/jtag_stm32/Makefile.am index 08d74d6..7cd39da 100644 --- a/decoders/jtag_stm32/Makefile.am +++ b/decoders/jtag_stm32/Makefile.am @@ -20,7 +20,7 @@ pkgdatadir = $(DECODERS_DIR)/jtag_stm32 -dist_pkgdata_DATA = __init__.py jtag_stm32.py +dist_pkgdata_DATA = __init__.py pd.py CLEANFILES = *.pyc diff --git a/decoders/jtag_stm32/__init__.py b/decoders/jtag_stm32/__init__.py index bc35c46..12e667b 100644 --- a/decoders/jtag_stm32/__init__.py +++ b/decoders/jtag_stm32/__init__.py @@ -28,5 +28,5 @@ 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 .jtag_stm32 import * +from .pd import * diff --git a/decoders/jtag_stm32/jtag_stm32.py b/decoders/jtag_stm32/jtag_stm32.py deleted file mode 100644 index e30788c..0000000 --- a/decoders/jtag_stm32/jtag_stm32.py +++ /dev/null @@ -1,232 +0,0 @@ -## -## This file is part of the sigrok 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 -## - -# ST STM32 JTAG protocol decoder - -import sigrokdecode as srd - -# JTAG debug port data registers (in IR[3:0]) and their sizes (in bits) -# Note: The ARM DAP-DP is not IEEE 1149.1 (JTAG) compliant (as per ARM docs), -# as it does not implement the EXTEST, SAMPLE, and PRELOAD instructions. -# Instead, BYPASS is decoded for any of these instructions. -ir = { - '1111': ['BYPASS', 1], # Bypass register - '1110': ['IDCODE', 32], # ID code register - '1010': ['DPACC', 35], # Debug port access register - '1011': ['APACC', 35], # Access port access register - '1000': ['ABORT', 35], # Abort register # TODO: 32 bits? Datasheet typo? -} - -# ARM Cortex-M3 r1p1-01rel0 ID code -cm3_idcode = 0x3ba00477 - -# JTAG ID code in the STM32F10xxx BSC (boundary scan) TAP -jtag_idcode = { - 0x06412041: 'Low-density device, rev. A', - 0x06410041: 'Medium-density device, rev. A', - 0x16410041: 'Medium-density device, rev. B/Z/Y', - 0x06414041: 'High-density device, rev. A/Z/Y', - 0x06430041: 'XL-density device, rev. A', - 0x06418041: 'Connectivity-line device, rev. A/Z', -} - -# ACK[2:0] in the DPACC/APACC registers (unlisted values are reserved) -ack_val = { - '001': 'WAIT', - '010': 'OK/FAULT', -} - -# 32bit debug port registers (addressed via A[3:2]) -dp_reg = { - '00': 'Reserved', # Must be kept at reset value - '01': 'DP CTRL/STAT', - '10': 'DP SELECT', - '11': 'DP RDBUFF', -} - -# APB-AP registers (each of them 32 bits wide) -apb_ap_reg = { - 0x00: ['CSW', 'Control/status word'], - 0x04: ['TAR', 'Transfer address'], - # 0x08: Reserved SBZ - 0x0c: ['DRW', 'Data read/write'], - 0x10: ['BD0', 'Banked data 0'], - 0x14: ['BD1', 'Banked data 1'], - 0x18: ['BD2', 'Banked data 2'], - 0x1c: ['BD3', 'Banked data 3'], - # 0x20-0xf4: Reserved SBZ - 0x800000000: ['ROM', 'Debug ROM address'], - 0xfc: ['IDR', 'Identification register'], -} - -# TODO: All start/end sample values in self.put() calls are bogus. -# TODO: Split off generic ARM/Cortex-M3 parts into another protocol decoder? - -# Bits[31:28]: Version (here: 0x3) -# JTAG-DP: 0x3, SW-DP: 0x2 -# Bits[27:12]: Part number (here: 0xba00) -# JTAG-DP: 0xba00, SW-DP: 0xba10 -# Bits[11:1]: JEDEC (JEP-106) manufacturer ID (here: 0x23b) -# Bits[11:8]: Continuation code ('ARM Limited': 0x04) -# Bits[7:1]: Identity code ('ARM Limited': 0x3b) -# Bits[0:0]: Reserved (here: 0x1) -def decode_device_id_code(bits): - id_hex = '0x%x' % int('0b' + bits, 2) - ver = '0x%x' % int('0b' + bits[-32:-28], 2) - part = '0x%x' % int('0b' + bits[-28:-12], 2) - manuf = '0x%x' % int('0b' + bits[-12:-1], 2) - res = '0x%x' % int('0b' + bits[-1], 2) - return (id_hex, ver, part, manuf, res) - -# DPACC is used to access debug port registers (CTRL/STAT, SELECT, RDBUFF). -# APACC is used to access all Access Port (AHB-AP) registers. - -# APACC/DPACC, when transferring data IN: -# Bits[34:3] = DATA[31:0]: 32bit data to transfer (write request) -# Bits[2:1] = A[3:2]: 2-bit address (debug/access port register) -# Bits[0:0] = RnW: Read request (1) or write request (0) -def data_in(instruction, bits): - data, a, rnw = bits[:-3], bits[-3:-1], bits[-1] - data_hex = '0x%x' % int('0b' + data, 2) - r = 'Read request' if (rnw == '1') else 'Write request' - # reg = dp_reg[a] if (instruction == 'DPACC') else apb_ap_reg[a] - reg = dp_reg[a] if (instruction == 'DPACC') else a # TODO - return 'New transaction: DATA: %s, A: %s, RnW: %s' % (data_hex, reg, r) - -# APACC/DPACC, when transferring data OUT: -# Bits[34:3] = DATA[31:0]: 32bit data which is read (read request) -# Bits[2:0] = ACK[2:0]: 3-bit acknowledge -def data_out(bits): - data, ack = bits[:-3], bits[-3:] - data_hex = '0x%x' % int('0b' + data, 2) - ack_meaning = ack_val.get(ack, 'Reserved') - return 'Previous transaction result: DATA: %s, ACK: %s' \ - % (data_hex, ack_meaning) - -class Decoder(srd.Decoder): - api_version = 1 - id = 'jtag_stm32' - name = 'JTAG / STM32' - longname = 'Joint Test Action Group / ST STM32' - desc = 'ST STM32-specific JTAG protocol.' - license = 'gplv2+' - inputs = ['jtag'] - outputs = ['jtag_stm32'] - probes = [] - optional_probes = [] - options = {} - annotations = [ - ['Text', 'Human-readable text'], - ] - - def __init__(self, **kwargs): - self.state = 'IDLE' - # self.state = 'BYPASS' - - def start(self, metadata): - # self.out_proto = self.add(srd.OUTPUT_PROTO, 'jtag_stm32') - self.out_ann = self.add(srd.OUTPUT_ANN, 'jtag_stm32') - - def report(self): - pass - - def handle_reg_bypass(self, cmd, bits): - # TODO - self.put(self.ss, self.es, self.out_ann, [0, ['BYPASS: ' + bits]]) - - def handle_reg_idcode(self, cmd, bits): - # TODO - # IDCODE is a read-only register which is always accessible. - # IR == IDCODE: The device ID code is shifted out via DR next. - self.put(self.ss, self.es, self.out_ann, - [0, ['IDCODE: %s (ver=%s, part=%s, manuf=%s, res=%s)' % \ - decode_device_id_code(bits)]]) - - def handle_reg_dpacc(self, cmd, bits): - # self.put(self.ss, self.es, self.out_ann, - # [0, ['DPACC/%s: %s' % (cmd, bits)]]) - s = data_in('DPACC', bits) if (cmd == 'DR TDI') else data_out(bits) - self.put(self.ss, self.es, self.out_ann, [0, [s]]) - - def handle_reg_apacc(self, cmd, bits): - # self.put(self.ss, self.es, self.out_ann, - # [0, ['APACC/%s: %s' % (cmd, bits)]]) - s = data_in('APACC', bits) if (cmd == 'DR TDI') else data_out(bits) - self.put(self.ss, self.es, self.out_ann, [0, [s]]) - - def handle_reg_abort(self, cmd, bits): - # Bits[31:1]: reserved. Bit[0]: DAPABORT. - a = '' if (bits[0] == '1') else 'No ' - s = 'DAPABORT = %s: %sDAP abort generated' % (bits[0], a) - self.put(self.ss, self.es, self.out_ann, [0, [s]]) - - # Warn if DAPABORT[31:1] contains non-zero bits. - if (bits[:-1] != ('0' * 31)): - self.put(self.ss, self.es, self.out_ann, - [0, ['WARNING: DAPABORT[31:1] reserved!']]) - - def handle_reg_unknown(self, cmd, bits): - self.put(self.ss, self.es, self.out_ann, - [0, ['Unknown instruction: ' % bits]]) # TODO - - def decode(self, ss, es, data): - # Assumption: The right-most char in the 'val' bitstring is the LSB. - cmd, val = data - - self.ss, self.es = ss, es - - # self.put(self.ss, self.es, self.out_ann, [0, [cmd + ' / ' + val]]) - - # State machine - if self.state == 'IDLE': - # Wait until a new instruction is shifted into the IR register. - if cmd != 'IR TDI': - return - # Switch to the state named after the instruction, or 'UNKNOWN'. - # Ignore bits other than IR[3:0]. While the IR register is only - # 4 bits in size, some programs (e.g. OpenOCD) might fill in a - # few more (dummy) bits. OpenOCD makes IR at least 8 bits long. - self.state = ir.get(val[-4:], ['UNKNOWN', 0])[0] - self.put(self.ss, self.es, self.out_ann, [0, ['IR: ' + self.state]]) - elif self.state == 'BYPASS': - # Here we're interested in incoming bits (TDI). - if cmd != 'DR TDI': - return - handle_reg = getattr(self, 'handle_reg_%s' % self.state.lower()) - handle_reg(cmd, val) - self.state = 'IDLE' - elif self.state in ('IDCODE', 'ABORT', 'UNKNOWN'): - # Here we're interested in outgoing bits (TDO). - if cmd != 'DR TDO': - return - handle_reg = getattr(self, 'handle_reg_%s' % self.state.lower()) - handle_reg(cmd, val) - self.state = 'IDLE' - elif self.state in ('DPACC', 'APACC'): - # Here we're interested in incoming and outgoing bits (TDI/TDO). - if cmd not in ('DR TDI', 'DR TDO'): - return - handle_reg = getattr(self, 'handle_reg_%s' % self.state.lower()) - handle_reg(cmd, val) - if cmd == 'DR TDO': # TODO: Assumes 'DR TDI' comes before 'DR TDO' - self.state = 'IDLE' - else: - raise Exception('Invalid state: %s' % self.state) - diff --git a/decoders/jtag_stm32/pd.py b/decoders/jtag_stm32/pd.py new file mode 100644 index 0000000..e30788c --- /dev/null +++ b/decoders/jtag_stm32/pd.py @@ -0,0 +1,232 @@ +## +## This file is part of the sigrok 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 +## + +# ST STM32 JTAG protocol decoder + +import sigrokdecode as srd + +# JTAG debug port data registers (in IR[3:0]) and their sizes (in bits) +# Note: The ARM DAP-DP is not IEEE 1149.1 (JTAG) compliant (as per ARM docs), +# as it does not implement the EXTEST, SAMPLE, and PRELOAD instructions. +# Instead, BYPASS is decoded for any of these instructions. +ir = { + '1111': ['BYPASS', 1], # Bypass register + '1110': ['IDCODE', 32], # ID code register + '1010': ['DPACC', 35], # Debug port access register + '1011': ['APACC', 35], # Access port access register + '1000': ['ABORT', 35], # Abort register # TODO: 32 bits? Datasheet typo? +} + +# ARM Cortex-M3 r1p1-01rel0 ID code +cm3_idcode = 0x3ba00477 + +# JTAG ID code in the STM32F10xxx BSC (boundary scan) TAP +jtag_idcode = { + 0x06412041: 'Low-density device, rev. A', + 0x06410041: 'Medium-density device, rev. A', + 0x16410041: 'Medium-density device, rev. B/Z/Y', + 0x06414041: 'High-density device, rev. A/Z/Y', + 0x06430041: 'XL-density device, rev. A', + 0x06418041: 'Connectivity-line device, rev. A/Z', +} + +# ACK[2:0] in the DPACC/APACC registers (unlisted values are reserved) +ack_val = { + '001': 'WAIT', + '010': 'OK/FAULT', +} + +# 32bit debug port registers (addressed via A[3:2]) +dp_reg = { + '00': 'Reserved', # Must be kept at reset value + '01': 'DP CTRL/STAT', + '10': 'DP SELECT', + '11': 'DP RDBUFF', +} + +# APB-AP registers (each of them 32 bits wide) +apb_ap_reg = { + 0x00: ['CSW', 'Control/status word'], + 0x04: ['TAR', 'Transfer address'], + # 0x08: Reserved SBZ + 0x0c: ['DRW', 'Data read/write'], + 0x10: ['BD0', 'Banked data 0'], + 0x14: ['BD1', 'Banked data 1'], + 0x18: ['BD2', 'Banked data 2'], + 0x1c: ['BD3', 'Banked data 3'], + # 0x20-0xf4: Reserved SBZ + 0x800000000: ['ROM', 'Debug ROM address'], + 0xfc: ['IDR', 'Identification register'], +} + +# TODO: All start/end sample values in self.put() calls are bogus. +# TODO: Split off generic ARM/Cortex-M3 parts into another protocol decoder? + +# Bits[31:28]: Version (here: 0x3) +# JTAG-DP: 0x3, SW-DP: 0x2 +# Bits[27:12]: Part number (here: 0xba00) +# JTAG-DP: 0xba00, SW-DP: 0xba10 +# Bits[11:1]: JEDEC (JEP-106) manufacturer ID (here: 0x23b) +# Bits[11:8]: Continuation code ('ARM Limited': 0x04) +# Bits[7:1]: Identity code ('ARM Limited': 0x3b) +# Bits[0:0]: Reserved (here: 0x1) +def decode_device_id_code(bits): + id_hex = '0x%x' % int('0b' + bits, 2) + ver = '0x%x' % int('0b' + bits[-32:-28], 2) + part = '0x%x' % int('0b' + bits[-28:-12], 2) + manuf = '0x%x' % int('0b' + bits[-12:-1], 2) + res = '0x%x' % int('0b' + bits[-1], 2) + return (id_hex, ver, part, manuf, res) + +# DPACC is used to access debug port registers (CTRL/STAT, SELECT, RDBUFF). +# APACC is used to access all Access Port (AHB-AP) registers. + +# APACC/DPACC, when transferring data IN: +# Bits[34:3] = DATA[31:0]: 32bit data to transfer (write request) +# Bits[2:1] = A[3:2]: 2-bit address (debug/access port register) +# Bits[0:0] = RnW: Read request (1) or write request (0) +def data_in(instruction, bits): + data, a, rnw = bits[:-3], bits[-3:-1], bits[-1] + data_hex = '0x%x' % int('0b' + data, 2) + r = 'Read request' if (rnw == '1') else 'Write request' + # reg = dp_reg[a] if (instruction == 'DPACC') else apb_ap_reg[a] + reg = dp_reg[a] if (instruction == 'DPACC') else a # TODO + return 'New transaction: DATA: %s, A: %s, RnW: %s' % (data_hex, reg, r) + +# APACC/DPACC, when transferring data OUT: +# Bits[34:3] = DATA[31:0]: 32bit data which is read (read request) +# Bits[2:0] = ACK[2:0]: 3-bit acknowledge +def data_out(bits): + data, ack = bits[:-3], bits[-3:] + data_hex = '0x%x' % int('0b' + data, 2) + ack_meaning = ack_val.get(ack, 'Reserved') + return 'Previous transaction result: DATA: %s, ACK: %s' \ + % (data_hex, ack_meaning) + +class Decoder(srd.Decoder): + api_version = 1 + id = 'jtag_stm32' + name = 'JTAG / STM32' + longname = 'Joint Test Action Group / ST STM32' + desc = 'ST STM32-specific JTAG protocol.' + license = 'gplv2+' + inputs = ['jtag'] + outputs = ['jtag_stm32'] + probes = [] + optional_probes = [] + options = {} + annotations = [ + ['Text', 'Human-readable text'], + ] + + def __init__(self, **kwargs): + self.state = 'IDLE' + # self.state = 'BYPASS' + + def start(self, metadata): + # self.out_proto = self.add(srd.OUTPUT_PROTO, 'jtag_stm32') + self.out_ann = self.add(srd.OUTPUT_ANN, 'jtag_stm32') + + def report(self): + pass + + def handle_reg_bypass(self, cmd, bits): + # TODO + self.put(self.ss, self.es, self.out_ann, [0, ['BYPASS: ' + bits]]) + + def handle_reg_idcode(self, cmd, bits): + # TODO + # IDCODE is a read-only register which is always accessible. + # IR == IDCODE: The device ID code is shifted out via DR next. + self.put(self.ss, self.es, self.out_ann, + [0, ['IDCODE: %s (ver=%s, part=%s, manuf=%s, res=%s)' % \ + decode_device_id_code(bits)]]) + + def handle_reg_dpacc(self, cmd, bits): + # self.put(self.ss, self.es, self.out_ann, + # [0, ['DPACC/%s: %s' % (cmd, bits)]]) + s = data_in('DPACC', bits) if (cmd == 'DR TDI') else data_out(bits) + self.put(self.ss, self.es, self.out_ann, [0, [s]]) + + def handle_reg_apacc(self, cmd, bits): + # self.put(self.ss, self.es, self.out_ann, + # [0, ['APACC/%s: %s' % (cmd, bits)]]) + s = data_in('APACC', bits) if (cmd == 'DR TDI') else data_out(bits) + self.put(self.ss, self.es, self.out_ann, [0, [s]]) + + def handle_reg_abort(self, cmd, bits): + # Bits[31:1]: reserved. Bit[0]: DAPABORT. + a = '' if (bits[0] == '1') else 'No ' + s = 'DAPABORT = %s: %sDAP abort generated' % (bits[0], a) + self.put(self.ss, self.es, self.out_ann, [0, [s]]) + + # Warn if DAPABORT[31:1] contains non-zero bits. + if (bits[:-1] != ('0' * 31)): + self.put(self.ss, self.es, self.out_ann, + [0, ['WARNING: DAPABORT[31:1] reserved!']]) + + def handle_reg_unknown(self, cmd, bits): + self.put(self.ss, self.es, self.out_ann, + [0, ['Unknown instruction: ' % bits]]) # TODO + + def decode(self, ss, es, data): + # Assumption: The right-most char in the 'val' bitstring is the LSB. + cmd, val = data + + self.ss, self.es = ss, es + + # self.put(self.ss, self.es, self.out_ann, [0, [cmd + ' / ' + val]]) + + # State machine + if self.state == 'IDLE': + # Wait until a new instruction is shifted into the IR register. + if cmd != 'IR TDI': + return + # Switch to the state named after the instruction, or 'UNKNOWN'. + # Ignore bits other than IR[3:0]. While the IR register is only + # 4 bits in size, some programs (e.g. OpenOCD) might fill in a + # few more (dummy) bits. OpenOCD makes IR at least 8 bits long. + self.state = ir.get(val[-4:], ['UNKNOWN', 0])[0] + self.put(self.ss, self.es, self.out_ann, [0, ['IR: ' + self.state]]) + elif self.state == 'BYPASS': + # Here we're interested in incoming bits (TDI). + if cmd != 'DR TDI': + return + handle_reg = getattr(self, 'handle_reg_%s' % self.state.lower()) + handle_reg(cmd, val) + self.state = 'IDLE' + elif self.state in ('IDCODE', 'ABORT', 'UNKNOWN'): + # Here we're interested in outgoing bits (TDO). + if cmd != 'DR TDO': + return + handle_reg = getattr(self, 'handle_reg_%s' % self.state.lower()) + handle_reg(cmd, val) + self.state = 'IDLE' + elif self.state in ('DPACC', 'APACC'): + # Here we're interested in incoming and outgoing bits (TDI/TDO). + if cmd not in ('DR TDI', 'DR TDO'): + return + handle_reg = getattr(self, 'handle_reg_%s' % self.state.lower()) + handle_reg(cmd, val) + if cmd == 'DR TDO': # TODO: Assumes 'DR TDI' comes before 'DR TDO' + self.state = 'IDLE' + else: + raise Exception('Invalid state: %s' % self.state) + diff --git a/decoders/lm75/Makefile.am b/decoders/lm75/Makefile.am index 6ab1818..82a2190 100644 --- a/decoders/lm75/Makefile.am +++ b/decoders/lm75/Makefile.am @@ -20,7 +20,7 @@ pkgdatadir = $(DECODERS_DIR)/lm75 -dist_pkgdata_DATA = __init__.py lm75.py +dist_pkgdata_DATA = __init__.py pd.py CLEANFILES = *.pyc diff --git a/decoders/lm75/__init__.py b/decoders/lm75/__init__.py index fd72612..548cf65 100644 --- a/decoders/lm75/__init__.py +++ b/decoders/lm75/__init__.py @@ -30,5 +30,5 @@ Details: TODO. ''' -from .lm75 import * +from .pd import * diff --git a/decoders/lm75/lm75.py b/decoders/lm75/lm75.py deleted file mode 100644 index 9d2f0ab..0000000 --- a/decoders/lm75/lm75.py +++ /dev/null @@ -1,211 +0,0 @@ -## -## This file is part of the sigrok 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 -## - -# National LM75 (and compatibles) temperature sensor protocol decoder - -# TODO: Better support for various LM75 compatible devices. - -import sigrokdecode as srd - -# LM75 only supports 9 bit resolution, compatible devices usually 9-12 bits. -resolution = { - # CONFIG[6:5]: - 0x00: 9, - 0x01: 10, - 0x02: 11, - 0x03: 12, -} - -ft = { - # CONFIG[4:3]: - 0x00: 1, - 0x01: 2, - 0x02: 4, - 0x03: 6, -} - -class Decoder(srd.Decoder): - api_version = 1 - id = 'lm75' - name = 'LM75' - longname = 'National LM75' - desc = 'National LM75 (and compatibles) temperature sensor protocol.' - license = 'gplv2+' - inputs = ['i2c'] - outputs = ['lm75'] - probes = [] - optional_probes = [ - {'id': 'os', 'name': 'OS', 'desc': 'Overtemperature shutdown'}, - {'id': 'a0', 'name': 'A0', 'desc': 'I2C slave address input 0'}, - {'id': 'a1', 'name': 'A1', 'desc': 'I2C slave address input 1'}, - {'id': 'a2', 'name': 'A2', 'desc': 'I2C slave address input 2'}, - ] - options = { - 'sensor': ['Sensor type', 'lm75'], - 'resolution': ['Resolution', 9], # 9-12 bit, sensor/config dependent - } - annotations = [ - ['Celsius', 'Temperature in degrees Celsius'], - ['Kelvin', 'Temperature in Kelvin'], - ['Text (verbose)', 'Human-readable text (verbose)'], - ['Text', 'Human-readable text'], - ['Warnings', 'Human-readable warnings'], - ] - - def __init__(self, **kwargs): - self.state = 'IDLE' - self.reg = 0x00 # Currently selected register - self.databytes = [] - self.mintemp = 0 - self.maxtemp = 0 - self.avgvalues = [] - - def start(self, metadata): - # self.out_proto = self.add(srd.OUTPUT_PROTO, 'lm75') - self.out_ann = self.add(srd.OUTPUT_ANN, 'lm75') - - def report(self): - # TODO: print() or self.put() or return xyz, or... ? - avg = sum(self.avgvalues) / len(self.avgvalues) - temperatures = (self.mintemp, self.maxtemp, avg) - # TODO: Configurable report() output, e.g. for Kelvin. - return 'Min/max/avg temperature: %f/%f/%f °C' % temperatures - - def putx(self, data): - # Helper for annotations which span exactly one I2C packet. - self.put(self.ss, self.es, self.out_ann, data) - - def putb(self, data): - # Helper for annotations which span a block of I2C packets. - self.put(self.block_start, self.block_end, self.out_ann, data) - - def warn_upon_invalid_slave(self, addr): - # LM75 and compatible devices have a 7-bit I2C slave address where - # the 4 MSBs are fixed to 1001, and the 3 LSBs are configurable. - # Thus, valid slave addresses (1001xxx) range from 0x48 to 0x4f. - if addr not in range(0x48, 0x4f + 1): - s = 'Warning: I2C slave 0x%02x not an LM75 compatible sensor.' - self.putx([4, [s % addr]]) - - def output_temperature(self, s, rw): - # TODO: Support for resolutions other than 9 bit. - before, after = self.databytes[0], (self.databytes[1] >> 7) * 5 - celsius = float('%d.%d' % (before, after)) - kelvin = celsius + 273.15 - self.putb([0, ['%s: %.1f °C' % (s, celsius)]]) - self.putb([1, ['%s: %.1f K' % (s, kelvin)]]) - - # Warn about the temperature register (0x00) being read-only. - if s == 'Temperature' and rw == 'WRITE': - s = 'Warning: The temperature register is read-only!' - self.putb([4, [s]]) - - # Keep some statistics. Can be output in report(), for example. - if celsius < self.mintemp: - self.mintemp = celsius - if celsius > self.maxtemp: - self.maxtemp = celsius - self.avgvalues.append(celsius) - - 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.databytes.append(b) - return - self.databytes.append(b) - self.block_end = self.es - self.output_temperature(s, rw) - self.databytes = [] - - def handle_reg_0x00(self, b, rw): - # Temperature register (16bits, read-only). - self.handle_temperature_reg(b, 'Temperature', rw) - - def handle_reg_0x01(self, b, rw): - # Configuration register (8 bits, read/write). - # TODO: Bit-exact annotation ranges. - - sd = b & (1 << 0) - tmp = 'normal operation' if (sd == 0) else 'shutdown mode' - s = 'SD = %d: %s\n' % (sd, tmp) - s2 = 'SD = %s, ' % tmp - - cmp_int = b & (1 << 1) - tmp = 'comparator' if (cmp_int == 0) else 'interrupt' - s += 'CMP/INT = %d: %s mode\n' % (cmp_int, tmp) - s2 += 'CMP/INT = %s, ' % tmp - - pol = b & (1 << 2) - tmp = 'low' if (pol == 0) else 'high' - s += 'POL = %d: OS polarity is active-%s\n' % (pol, tmp) - s2 += 'POL = active-%s, ' % tmp - - bits = (b & ((1 << 4) | (1 << 3))) >> 3 - s += 'Fault tolerance setting: %d bit(s)\n' % ft[bits] - s2 += 'FT = %d' % ft[bits] - - # Not supported by LM75, but by various compatible devices. - if self.options['sensor'] != 'lm75': # TODO - bits = (b & ((1 << 6) | (1 << 5))) >> 5 - s += 'Resolution: %d bits\n' % resolution[bits] - s2 += ', resolution = %d' % resolution[bits] - - self.putx([2, [s]]) - self.putx([3, [s2]]) - - def handle_reg_0x02(self, b, rw): - # T_HYST register (16 bits, read/write). - self.handle_temperature_reg(b, 'T_HYST trip temperature', rw) - - def handle_reg_0x03(self, b, rw): - # T_OS register (16 bits, read/write). - self.handle_temperature_reg(b, 'T_OS trip temperature', rw) - - def decode(self, ss, es, data): - cmd, databyte = data - - # Store the start/end samples of this I2C packet. - self.ss, self.es = ss, es - - # State machine. - if self.state == 'IDLE': - # Wait for an I2C START condition. - if cmd != 'START': - return - self.state = 'GET SLAVE ADDR' - elif self.state == 'GET SLAVE ADDR': - # Wait for an address read/write operation. - if cmd in ('ADDRESS READ', 'ADDRESS WRITE'): - self.warn_upon_invalid_slave(databyte) - self.state = cmd[8:] + ' REGS' # READ REGS / WRITE REGS - elif self.state in ('READ REGS', 'WRITE REGS'): - if cmd in ('DATA READ', 'DATA WRITE'): - handle_reg = getattr(self, 'handle_reg_0x%02x' % self.reg) - handle_reg(databyte, cmd[5:]) # READ / WRITE - elif cmd == 'STOP': - # TODO: Any output? - self.state = 'IDLE' - else: - # self.putx([0, ['Ignoring: %s (data=%s)' % (cmd, databyte)]]) - pass - else: - raise Exception('Invalid state: %s' % self.state) - diff --git a/decoders/lm75/pd.py b/decoders/lm75/pd.py new file mode 100644 index 0000000..9d2f0ab --- /dev/null +++ b/decoders/lm75/pd.py @@ -0,0 +1,211 @@ +## +## This file is part of the sigrok 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 +## + +# National LM75 (and compatibles) temperature sensor protocol decoder + +# TODO: Better support for various LM75 compatible devices. + +import sigrokdecode as srd + +# LM75 only supports 9 bit resolution, compatible devices usually 9-12 bits. +resolution = { + # CONFIG[6:5]: + 0x00: 9, + 0x01: 10, + 0x02: 11, + 0x03: 12, +} + +ft = { + # CONFIG[4:3]: + 0x00: 1, + 0x01: 2, + 0x02: 4, + 0x03: 6, +} + +class Decoder(srd.Decoder): + api_version = 1 + id = 'lm75' + name = 'LM75' + longname = 'National LM75' + desc = 'National LM75 (and compatibles) temperature sensor protocol.' + license = 'gplv2+' + inputs = ['i2c'] + outputs = ['lm75'] + probes = [] + optional_probes = [ + {'id': 'os', 'name': 'OS', 'desc': 'Overtemperature shutdown'}, + {'id': 'a0', 'name': 'A0', 'desc': 'I2C slave address input 0'}, + {'id': 'a1', 'name': 'A1', 'desc': 'I2C slave address input 1'}, + {'id': 'a2', 'name': 'A2', 'desc': 'I2C slave address input 2'}, + ] + options = { + 'sensor': ['Sensor type', 'lm75'], + 'resolution': ['Resolution', 9], # 9-12 bit, sensor/config dependent + } + annotations = [ + ['Celsius', 'Temperature in degrees Celsius'], + ['Kelvin', 'Temperature in Kelvin'], + ['Text (verbose)', 'Human-readable text (verbose)'], + ['Text', 'Human-readable text'], + ['Warnings', 'Human-readable warnings'], + ] + + def __init__(self, **kwargs): + self.state = 'IDLE' + self.reg = 0x00 # Currently selected register + self.databytes = [] + self.mintemp = 0 + self.maxtemp = 0 + self.avgvalues = [] + + def start(self, metadata): + # self.out_proto = self.add(srd.OUTPUT_PROTO, 'lm75') + self.out_ann = self.add(srd.OUTPUT_ANN, 'lm75') + + def report(self): + # TODO: print() or self.put() or return xyz, or... ? + avg = sum(self.avgvalues) / len(self.avgvalues) + temperatures = (self.mintemp, self.maxtemp, avg) + # TODO: Configurable report() output, e.g. for Kelvin. + return 'Min/max/avg temperature: %f/%f/%f °C' % temperatures + + def putx(self, data): + # Helper for annotations which span exactly one I2C packet. + self.put(self.ss, self.es, self.out_ann, data) + + def putb(self, data): + # Helper for annotations which span a block of I2C packets. + self.put(self.block_start, self.block_end, self.out_ann, data) + + def warn_upon_invalid_slave(self, addr): + # LM75 and compatible devices have a 7-bit I2C slave address where + # the 4 MSBs are fixed to 1001, and the 3 LSBs are configurable. + # Thus, valid slave addresses (1001xxx) range from 0x48 to 0x4f. + if addr not in range(0x48, 0x4f + 1): + s = 'Warning: I2C slave 0x%02x not an LM75 compatible sensor.' + self.putx([4, [s % addr]]) + + def output_temperature(self, s, rw): + # TODO: Support for resolutions other than 9 bit. + before, after = self.databytes[0], (self.databytes[1] >> 7) * 5 + celsius = float('%d.%d' % (before, after)) + kelvin = celsius + 273.15 + self.putb([0, ['%s: %.1f °C' % (s, celsius)]]) + self.putb([1, ['%s: %.1f K' % (s, kelvin)]]) + + # Warn about the temperature register (0x00) being read-only. + if s == 'Temperature' and rw == 'WRITE': + s = 'Warning: The temperature register is read-only!' + self.putb([4, [s]]) + + # Keep some statistics. Can be output in report(), for example. + if celsius < self.mintemp: + self.mintemp = celsius + if celsius > self.maxtemp: + self.maxtemp = celsius + self.avgvalues.append(celsius) + + 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.databytes.append(b) + return + self.databytes.append(b) + self.block_end = self.es + self.output_temperature(s, rw) + self.databytes = [] + + def handle_reg_0x00(self, b, rw): + # Temperature register (16bits, read-only). + self.handle_temperature_reg(b, 'Temperature', rw) + + def handle_reg_0x01(self, b, rw): + # Configuration register (8 bits, read/write). + # TODO: Bit-exact annotation ranges. + + sd = b & (1 << 0) + tmp = 'normal operation' if (sd == 0) else 'shutdown mode' + s = 'SD = %d: %s\n' % (sd, tmp) + s2 = 'SD = %s, ' % tmp + + cmp_int = b & (1 << 1) + tmp = 'comparator' if (cmp_int == 0) else 'interrupt' + s += 'CMP/INT = %d: %s mode\n' % (cmp_int, tmp) + s2 += 'CMP/INT = %s, ' % tmp + + pol = b & (1 << 2) + tmp = 'low' if (pol == 0) else 'high' + s += 'POL = %d: OS polarity is active-%s\n' % (pol, tmp) + s2 += 'POL = active-%s, ' % tmp + + bits = (b & ((1 << 4) | (1 << 3))) >> 3 + s += 'Fault tolerance setting: %d bit(s)\n' % ft[bits] + s2 += 'FT = %d' % ft[bits] + + # Not supported by LM75, but by various compatible devices. + if self.options['sensor'] != 'lm75': # TODO + bits = (b & ((1 << 6) | (1 << 5))) >> 5 + s += 'Resolution: %d bits\n' % resolution[bits] + s2 += ', resolution = %d' % resolution[bits] + + self.putx([2, [s]]) + self.putx([3, [s2]]) + + def handle_reg_0x02(self, b, rw): + # T_HYST register (16 bits, read/write). + self.handle_temperature_reg(b, 'T_HYST trip temperature', rw) + + def handle_reg_0x03(self, b, rw): + # T_OS register (16 bits, read/write). + self.handle_temperature_reg(b, 'T_OS trip temperature', rw) + + def decode(self, ss, es, data): + cmd, databyte = data + + # Store the start/end samples of this I2C packet. + self.ss, self.es = ss, es + + # State machine. + if self.state == 'IDLE': + # Wait for an I2C START condition. + if cmd != 'START': + return + self.state = 'GET SLAVE ADDR' + elif self.state == 'GET SLAVE ADDR': + # Wait for an address read/write operation. + if cmd in ('ADDRESS READ', 'ADDRESS WRITE'): + self.warn_upon_invalid_slave(databyte) + self.state = cmd[8:] + ' REGS' # READ REGS / WRITE REGS + elif self.state in ('READ REGS', 'WRITE REGS'): + if cmd in ('DATA READ', 'DATA WRITE'): + handle_reg = getattr(self, 'handle_reg_0x%02x' % self.reg) + handle_reg(databyte, cmd[5:]) # READ / WRITE + elif cmd == 'STOP': + # TODO: Any output? + self.state = 'IDLE' + else: + # self.putx([0, ['Ignoring: %s (data=%s)' % (cmd, databyte)]]) + pass + else: + raise Exception('Invalid state: %s' % self.state) + diff --git a/decoders/lpc/Makefile.am b/decoders/lpc/Makefile.am index 28de382..d9cf564 100644 --- a/decoders/lpc/Makefile.am +++ b/decoders/lpc/Makefile.am @@ -20,7 +20,7 @@ pkgdatadir = $(DECODERS_DIR)/lpc -dist_pkgdata_DATA = __init__.py lpc.py +dist_pkgdata_DATA = __init__.py pd.py CLEANFILES = *.pyc diff --git a/decoders/lpc/__init__.py b/decoders/lpc/__init__.py index ce2a140..1c79b73 100644 --- a/decoders/lpc/__init__.py +++ b/decoders/lpc/__init__.py @@ -25,5 +25,5 @@ Details: TODO ''' -from .lpc import * +from .pd import * diff --git a/decoders/lpc/lpc.py b/decoders/lpc/lpc.py deleted file mode 100644 index cd56a9b..0000000 --- a/decoders/lpc/lpc.py +++ /dev/null @@ -1,349 +0,0 @@ -## -## This file is part of the sigrok 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 -## - -# LPC protocol decoder - -import sigrokdecode as srd - -# ... -fields = { - # START field (indicates start or stop of a transaction) - 'START': { - 0b0000: 'Start of cycle for a target', - 0b0001: 'Reserved', - 0b0010: 'Grant for bus master 0', - 0b0011: 'Grant for bus master 1', - 0b0100: 'Reserved', - 0b0101: 'Reserved', - 0b0110: 'Reserved', - 0b0111: 'Reserved', - 0b1000: 'Reserved', - 0b1001: 'Reserved', - 0b1010: 'Reserved', - 0b1011: 'Reserved', - 0b1100: 'Reserved', - 0b1101: 'Start of cycle for a Firmware Memory Read cycle', - 0b1110: 'Start of cycle for a Firmware Memory Write cycle', - 0b1111: 'Stop/abort (end of a cycle for a target)', - }, - # Cycle type / direction field - # Bit 0 (LAD[0]) is unused, should always be 0. - # Neither host nor peripheral are allowed to drive 0b11x0. - 'CT_DR': { - 0b0000: 'I/O read', - 0b0010: 'I/O write', - 0b0100: 'Memory read', - 0b0110: 'Memory write', - 0b1000: 'DMA read', - 0b1010: 'DMA write', - 0b1100: 'Reserved / not allowed', - 0b1110: 'Reserved / not allowed', - }, - # SIZE field (determines how many bytes are to be transferred) - # Bits[3:2] are reserved, must be driven to 0b00. - # Neither host nor peripheral are allowed to drive 0b0010. - 'SIZE': { - 0b0000: '8 bits (1 byte)', - 0b0001: '16 bits (2 bytes)', - 0b0010: 'Reserved / not allowed', - 0b0011: '32 bits (4 bytes)', - }, - # CHANNEL field (bits[2:0] contain the DMA channel number) - 'CHANNEL': { - 0b0000: '0', - 0b0001: '1', - 0b0010: '2', - 0b0011: '3', - 0b0100: '4', - 0b0101: '5', - 0b0110: '6', - 0b0111: '7', - }, - # SYNC field (used to add wait states) - 'SYNC': { - 0b0000: 'Ready', - 0b0001: 'Reserved', - 0b0010: 'Reserved', - 0b0011: 'Reserved', - 0b0100: 'Reserved', - 0b0101: 'Short wait', - 0b0110: 'Long wait', - 0b0111: 'Reserved', - 0b1000: 'Reserved', - 0b1001: 'Ready more (DMA only)', - 0b1010: 'Error', - 0b1011: 'Reserved', - 0b1100: 'Reserved', - 0b1101: 'Reserved', - 0b1110: 'Reserved', - 0b1111: 'Reserved', - }, -} - -class Decoder(srd.Decoder): - api_version = 1 - id = 'lpc' - name = 'LPC' - longname = 'Low-Pin-Count' - desc = 'Protocol for low-bandwidth devices on PC mainboards.' - license = 'gplv2+' - inputs = ['logic'] - outputs = ['lpc'] - probes = [ - {'id': 'lframe', 'name': 'LFRAME#', 'desc': 'TODO'}, - {'id': 'lreset', 'name': 'LRESET#', 'desc': 'TODO'}, - {'id': 'lclk', 'name': 'LCLK', 'desc': 'TODO'}, - {'id': 'lad0', 'name': 'LAD[0]', 'desc': 'TODO'}, - {'id': 'lad1', 'name': 'LAD[1]', 'desc': 'TODO'}, - {'id': 'lad2', 'name': 'LAD[2]', 'desc': 'TODO'}, - {'id': 'lad3', 'name': 'LAD[3]', 'desc': 'TODO'}, - ] - optional_probes = [ - {'id': 'ldrq', 'name': 'LDRQ#', 'desc': 'TODO'}, - {'id': 'serirq', 'name': 'SERIRQ', 'desc': 'TODO'}, - {'id': 'clkrun', 'name': 'CLKRUN#', 'desc': 'TODO'}, - {'id': 'lpme', 'name': 'LPME#', 'desc': 'TODO'}, - {'id': 'lpcpd', 'name': 'LPCPD#', 'desc': 'TODO'}, - {'id': 'lsmi', 'name': 'LSMI#', 'desc': 'TODO'}, - ] - options = {} - annotations = [ - ['Text', 'Human-readable text'], - ] - - def __init__(self, **kwargs): - self.state = 'IDLE' - self.oldlclk = -1 - self.samplenum = 0 - self.clocknum = 0 - self.lad = -1 - self.addr = 0 - self.cur_nibble = 0 - self.cycle_type = -1 - self.oldpins = (-1, -1, -1, -1, -1, -1, -1) - - def start(self, metadata): - self.out_proto = self.add(srd.OUTPUT_PROTO, 'lpc') - self.out_ann = self.add(srd.OUTPUT_ANN, 'lpc') - - def report(self): - pass - - def handle_get_start(self, lad, lframe): - # LAD[3:0]: START field (1 clock cycle). - - # The last value of LAD[3:0] before LFRAME# gets de-asserted is what - # the peripherals must use. However, the host can keep LFRAME# asserted - # multiple clocks, and we output all START fields that occur, even - # though the peripherals are supposed to ignore all but the last one. - s = fields['START'][lad] - self.put(0, 0, self.out_ann, [0, [s]]) - - # Output a warning if LAD[3:0] changes while LFRAME# is low. - # TODO - if (self.lad != -1 and self.lad != lad): - self.put(0, 0, self.out_ann, - [0, ['Warning: LAD[3:0] changed while ' - 'LFRAME# was asserted']]) - - # LFRAME# is asserted (low). Wait until it gets de-asserted again - # (the host is allowed to keep it asserted multiple clocks). - if lframe != 1: - return - - self.start_field = self.lad - self.state = 'GET CT/DR' - - def handle_get_ct_dr(self, lad, lad_bits): - # LAD[3:0]: Cycle type / direction field (1 clock cycle). - - self.cycle_type = fields['CT_DR'][lad] - - # TODO: Warning/error on invalid cycle types. - if self.cycle_type == 'Reserved': - self.put(0, 0, self.out_ann, - [0, ['Warning: Invalid cycle type (%s)' % lad_bits]]) - - # ... - self.put(0, 0, self.out_ann, [0, ['Cycle type: %s' % self.cycle_type]]) - - self.state = 'GET ADDR' - self.addr = 0 - self.cur_nibble = 0 - - def handle_get_addr(self, lad, lad_bits): - # LAD[3:0]: ADDR field (4/8/0 clock cycles). - - # I/O cycles: 4 ADDR clocks. Memory cycles: 8 ADDR clocks. - # DMA cycles: no ADDR clocks at all. - if self.cycle_type in ('I/O read', 'I/O write'): - addr_nibbles = 4 # Address is 16bits. - elif self.cycle_type in ('Memory read', 'Memory write'): - addr_nibbles = 8 # Address is 32bits. - else: - addr_nibbles = 0 # TODO: How to handle later on? - - # Data is driven MSN-first. - offset = ((addr_nibbles - 1) - self.cur_nibble) * 4 - self.addr |= (lad << offset) - - # Continue if we haven't seen all ADDR cycles, yet. - # TODO: Off-by-one? - if (self.cur_nibble < addr_nibbles - 1): - self.cur_nibble += 1 - return - - self.put(0, 0, self.out_ann, [0, ['Address: %s' % hex(self.addr)]]) - - self.state = 'GET TAR' - self.tar_count = 0 - - def handle_get_tar(self, lad, lad_bits): - # LAD[3:0]: First TAR (turn-around) field (2 clock cycles). - - self.put(0, 0, self.out_ann, [0, ['TAR, cycle %d: %s' - % (self.tarcount, lad_bits)]]) - - # On the first TAR clock cycle LAD[3:0] is driven to 1111 by - # either the host or peripheral. On the second clock cycle, - # the host or peripheral tri-states LAD[3:0], but its value - # should still be 1111, due to pull-ups on the LAD lines. - if lad_bits != '1111': - self.put(0, 0, self.out_ann, - [0, ['Warning: TAR, cycle %d: %s (expected 1111)' - % (self.tarcount, lad_bits)]]) - - if (self.tarcount != 2): - self.tarcount += 1 - return - - self.state = 'GET SYNC' - - def handle_get_sync(self, lad, lad_bits): - # LAD[3:0]: SYNC field (1-n clock cycles). - - self.sync_val = lad_bits - self.cycle_type = fields['SYNC'][lad] - - # TODO: Warnings if reserved value are seen? - if self.cycle_type == 'Reserved': - self.put(0, 0, self.out_ann, [0, ['Warning: SYNC, cycle %d: %s ' - '(reserved value)' % (self.synccount, self.sync_val)]]) - - self.put(0, 0, self.out_ann, [0, ['SYNC, cycle %d: %s' - % (self.synccount, self.sync_val)]]) - - # TODO - - self.state = 'GET DATA' - self.cycle_count = 0 - - def handle_get_data(self, lad, lad_bits): - # LAD[3:0]: DATA field (2 clock cycles). - - if (self.cycle_count == 0): - self.databyte = lad - elif (self.cycle_count == 1): - self.databyte |= (lad << 4) - else: - pass # TODO: Error? - - if (self.cycle_count != 2): - self.cycle_count += 1 - return - - self.put(0, 0, self.out_ann, [0, ['DATA: %s' % hex(self.databyte)]]) - - self.state = 'GET TAR2' - - def handle_get_tar2(self, lad, lad_bits): - # LAD[3:0]: Second TAR field (2 clock cycles). - - self.put(0, 0, self.out_ann, [0, ['TAR, cycle %d: %s' - % (self.tarcount, lad_bits)]]) - - # On the first TAR clock cycle LAD[3:0] is driven to 1111 by - # either the host or peripheral. On the second clock cycle, - # the host or peripheral tri-states LAD[3:0], but its value - # should still be 1111, due to pull-ups on the LAD lines. - if lad_bits != '1111': - self.put(0, 0, self.out_ann, - [0, ['Warning: TAR, cycle %d: %s (expected 1111)' - % (self.tarcount, lad_bits)]]) - - if (self.tarcount != 2): - self.tarcount += 1 - return - - self.state = 'GET SYNC' - - # TODO: At which edge of the clock is data latched? Falling? - def decode(self, ss, es, data): - for (samplenum, pins) in data: - - # If none of the pins changed, there's nothing to do. - if self.oldpins == pins: - continue - - # Store current pin values for the next round. - self.oldpins = pins - - # Get individual pin values into local variables. - # TODO: Handle optional pins. - (lframe, lreset, lclk, lad0, lad1, lad2, lad3) = pins - - # Only look at the signals upon falling LCLK edges. - # TODO: Rising? - ## if not (self.oldlclk == 1 and lclk == 0) - ## self.oldlclk = lclk - ## continue - - # Store LAD[3:0] bit values (one nibble) in local variables. - # Most (but not all) states need this. - if self.state != 'IDLE': - lad = (lad3 << 3) | (lad2 << 2) | (lad1 << 1) | lad0 - lad_bits = bin(lad)[2:] - - # State machine - if self.state == 'IDLE': - # A valid LPC cycle starts with LFRAME# being asserted (low). - # TODO? - if lframe != 0: - continue - self.state = 'GET START' - self.lad = -1 - # self.clocknum = 0 - elif self.state == 'GET START': - handle_get_start(lad, lad_bits, lframe) - elif self.state == 'GET CT/DR': - handle_get_ct_dr(lad, lad_bits) - elif self.state == 'GET ADDR': - handle_get_addr(lad, lad_bits) - elif self.state == 'GET TAR': - handle_get_tar(lad, lad_bits) - elif self.state == 'GET SYNC': - handle_get_sync(lad, lad_bits) - elif self.state == 'GET DATA': - handle_get_data(lad, lad_bits) - elif self.state == 'GET TAR2': - handle_get_tar2(lad, lad_bits) - else: - raise Exception('Invalid state: %s' % self.state) - diff --git a/decoders/lpc/pd.py b/decoders/lpc/pd.py new file mode 100644 index 0000000..cd56a9b --- /dev/null +++ b/decoders/lpc/pd.py @@ -0,0 +1,349 @@ +## +## This file is part of the sigrok 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 +## + +# LPC protocol decoder + +import sigrokdecode as srd + +# ... +fields = { + # START field (indicates start or stop of a transaction) + 'START': { + 0b0000: 'Start of cycle for a target', + 0b0001: 'Reserved', + 0b0010: 'Grant for bus master 0', + 0b0011: 'Grant for bus master 1', + 0b0100: 'Reserved', + 0b0101: 'Reserved', + 0b0110: 'Reserved', + 0b0111: 'Reserved', + 0b1000: 'Reserved', + 0b1001: 'Reserved', + 0b1010: 'Reserved', + 0b1011: 'Reserved', + 0b1100: 'Reserved', + 0b1101: 'Start of cycle for a Firmware Memory Read cycle', + 0b1110: 'Start of cycle for a Firmware Memory Write cycle', + 0b1111: 'Stop/abort (end of a cycle for a target)', + }, + # Cycle type / direction field + # Bit 0 (LAD[0]) is unused, should always be 0. + # Neither host nor peripheral are allowed to drive 0b11x0. + 'CT_DR': { + 0b0000: 'I/O read', + 0b0010: 'I/O write', + 0b0100: 'Memory read', + 0b0110: 'Memory write', + 0b1000: 'DMA read', + 0b1010: 'DMA write', + 0b1100: 'Reserved / not allowed', + 0b1110: 'Reserved / not allowed', + }, + # SIZE field (determines how many bytes are to be transferred) + # Bits[3:2] are reserved, must be driven to 0b00. + # Neither host nor peripheral are allowed to drive 0b0010. + 'SIZE': { + 0b0000: '8 bits (1 byte)', + 0b0001: '16 bits (2 bytes)', + 0b0010: 'Reserved / not allowed', + 0b0011: '32 bits (4 bytes)', + }, + # CHANNEL field (bits[2:0] contain the DMA channel number) + 'CHANNEL': { + 0b0000: '0', + 0b0001: '1', + 0b0010: '2', + 0b0011: '3', + 0b0100: '4', + 0b0101: '5', + 0b0110: '6', + 0b0111: '7', + }, + # SYNC field (used to add wait states) + 'SYNC': { + 0b0000: 'Ready', + 0b0001: 'Reserved', + 0b0010: 'Reserved', + 0b0011: 'Reserved', + 0b0100: 'Reserved', + 0b0101: 'Short wait', + 0b0110: 'Long wait', + 0b0111: 'Reserved', + 0b1000: 'Reserved', + 0b1001: 'Ready more (DMA only)', + 0b1010: 'Error', + 0b1011: 'Reserved', + 0b1100: 'Reserved', + 0b1101: 'Reserved', + 0b1110: 'Reserved', + 0b1111: 'Reserved', + }, +} + +class Decoder(srd.Decoder): + api_version = 1 + id = 'lpc' + name = 'LPC' + longname = 'Low-Pin-Count' + desc = 'Protocol for low-bandwidth devices on PC mainboards.' + license = 'gplv2+' + inputs = ['logic'] + outputs = ['lpc'] + probes = [ + {'id': 'lframe', 'name': 'LFRAME#', 'desc': 'TODO'}, + {'id': 'lreset', 'name': 'LRESET#', 'desc': 'TODO'}, + {'id': 'lclk', 'name': 'LCLK', 'desc': 'TODO'}, + {'id': 'lad0', 'name': 'LAD[0]', 'desc': 'TODO'}, + {'id': 'lad1', 'name': 'LAD[1]', 'desc': 'TODO'}, + {'id': 'lad2', 'name': 'LAD[2]', 'desc': 'TODO'}, + {'id': 'lad3', 'name': 'LAD[3]', 'desc': 'TODO'}, + ] + optional_probes = [ + {'id': 'ldrq', 'name': 'LDRQ#', 'desc': 'TODO'}, + {'id': 'serirq', 'name': 'SERIRQ', 'desc': 'TODO'}, + {'id': 'clkrun', 'name': 'CLKRUN#', 'desc': 'TODO'}, + {'id': 'lpme', 'name': 'LPME#', 'desc': 'TODO'}, + {'id': 'lpcpd', 'name': 'LPCPD#', 'desc': 'TODO'}, + {'id': 'lsmi', 'name': 'LSMI#', 'desc': 'TODO'}, + ] + options = {} + annotations = [ + ['Text', 'Human-readable text'], + ] + + def __init__(self, **kwargs): + self.state = 'IDLE' + self.oldlclk = -1 + self.samplenum = 0 + self.clocknum = 0 + self.lad = -1 + self.addr = 0 + self.cur_nibble = 0 + self.cycle_type = -1 + self.oldpins = (-1, -1, -1, -1, -1, -1, -1) + + def start(self, metadata): + self.out_proto = self.add(srd.OUTPUT_PROTO, 'lpc') + self.out_ann = self.add(srd.OUTPUT_ANN, 'lpc') + + def report(self): + pass + + def handle_get_start(self, lad, lframe): + # LAD[3:0]: START field (1 clock cycle). + + # The last value of LAD[3:0] before LFRAME# gets de-asserted is what + # the peripherals must use. However, the host can keep LFRAME# asserted + # multiple clocks, and we output all START fields that occur, even + # though the peripherals are supposed to ignore all but the last one. + s = fields['START'][lad] + self.put(0, 0, self.out_ann, [0, [s]]) + + # Output a warning if LAD[3:0] changes while LFRAME# is low. + # TODO + if (self.lad != -1 and self.lad != lad): + self.put(0, 0, self.out_ann, + [0, ['Warning: LAD[3:0] changed while ' + 'LFRAME# was asserted']]) + + # LFRAME# is asserted (low). Wait until it gets de-asserted again + # (the host is allowed to keep it asserted multiple clocks). + if lframe != 1: + return + + self.start_field = self.lad + self.state = 'GET CT/DR' + + def handle_get_ct_dr(self, lad, lad_bits): + # LAD[3:0]: Cycle type / direction field (1 clock cycle). + + self.cycle_type = fields['CT_DR'][lad] + + # TODO: Warning/error on invalid cycle types. + if self.cycle_type == 'Reserved': + self.put(0, 0, self.out_ann, + [0, ['Warning: Invalid cycle type (%s)' % lad_bits]]) + + # ... + self.put(0, 0, self.out_ann, [0, ['Cycle type: %s' % self.cycle_type]]) + + self.state = 'GET ADDR' + self.addr = 0 + self.cur_nibble = 0 + + def handle_get_addr(self, lad, lad_bits): + # LAD[3:0]: ADDR field (4/8/0 clock cycles). + + # I/O cycles: 4 ADDR clocks. Memory cycles: 8 ADDR clocks. + # DMA cycles: no ADDR clocks at all. + if self.cycle_type in ('I/O read', 'I/O write'): + addr_nibbles = 4 # Address is 16bits. + elif self.cycle_type in ('Memory read', 'Memory write'): + addr_nibbles = 8 # Address is 32bits. + else: + addr_nibbles = 0 # TODO: How to handle later on? + + # Data is driven MSN-first. + offset = ((addr_nibbles - 1) - self.cur_nibble) * 4 + self.addr |= (lad << offset) + + # Continue if we haven't seen all ADDR cycles, yet. + # TODO: Off-by-one? + if (self.cur_nibble < addr_nibbles - 1): + self.cur_nibble += 1 + return + + self.put(0, 0, self.out_ann, [0, ['Address: %s' % hex(self.addr)]]) + + self.state = 'GET TAR' + self.tar_count = 0 + + def handle_get_tar(self, lad, lad_bits): + # LAD[3:0]: First TAR (turn-around) field (2 clock cycles). + + self.put(0, 0, self.out_ann, [0, ['TAR, cycle %d: %s' + % (self.tarcount, lad_bits)]]) + + # On the first TAR clock cycle LAD[3:0] is driven to 1111 by + # either the host or peripheral. On the second clock cycle, + # the host or peripheral tri-states LAD[3:0], but its value + # should still be 1111, due to pull-ups on the LAD lines. + if lad_bits != '1111': + self.put(0, 0, self.out_ann, + [0, ['Warning: TAR, cycle %d: %s (expected 1111)' + % (self.tarcount, lad_bits)]]) + + if (self.tarcount != 2): + self.tarcount += 1 + return + + self.state = 'GET SYNC' + + def handle_get_sync(self, lad, lad_bits): + # LAD[3:0]: SYNC field (1-n clock cycles). + + self.sync_val = lad_bits + self.cycle_type = fields['SYNC'][lad] + + # TODO: Warnings if reserved value are seen? + if self.cycle_type == 'Reserved': + self.put(0, 0, self.out_ann, [0, ['Warning: SYNC, cycle %d: %s ' + '(reserved value)' % (self.synccount, self.sync_val)]]) + + self.put(0, 0, self.out_ann, [0, ['SYNC, cycle %d: %s' + % (self.synccount, self.sync_val)]]) + + # TODO + + self.state = 'GET DATA' + self.cycle_count = 0 + + def handle_get_data(self, lad, lad_bits): + # LAD[3:0]: DATA field (2 clock cycles). + + if (self.cycle_count == 0): + self.databyte = lad + elif (self.cycle_count == 1): + self.databyte |= (lad << 4) + else: + pass # TODO: Error? + + if (self.cycle_count != 2): + self.cycle_count += 1 + return + + self.put(0, 0, self.out_ann, [0, ['DATA: %s' % hex(self.databyte)]]) + + self.state = 'GET TAR2' + + def handle_get_tar2(self, lad, lad_bits): + # LAD[3:0]: Second TAR field (2 clock cycles). + + self.put(0, 0, self.out_ann, [0, ['TAR, cycle %d: %s' + % (self.tarcount, lad_bits)]]) + + # On the first TAR clock cycle LAD[3:0] is driven to 1111 by + # either the host or peripheral. On the second clock cycle, + # the host or peripheral tri-states LAD[3:0], but its value + # should still be 1111, due to pull-ups on the LAD lines. + if lad_bits != '1111': + self.put(0, 0, self.out_ann, + [0, ['Warning: TAR, cycle %d: %s (expected 1111)' + % (self.tarcount, lad_bits)]]) + + if (self.tarcount != 2): + self.tarcount += 1 + return + + self.state = 'GET SYNC' + + # TODO: At which edge of the clock is data latched? Falling? + def decode(self, ss, es, data): + for (samplenum, pins) in data: + + # If none of the pins changed, there's nothing to do. + if self.oldpins == pins: + continue + + # Store current pin values for the next round. + self.oldpins = pins + + # Get individual pin values into local variables. + # TODO: Handle optional pins. + (lframe, lreset, lclk, lad0, lad1, lad2, lad3) = pins + + # Only look at the signals upon falling LCLK edges. + # TODO: Rising? + ## if not (self.oldlclk == 1 and lclk == 0) + ## self.oldlclk = lclk + ## continue + + # Store LAD[3:0] bit values (one nibble) in local variables. + # Most (but not all) states need this. + if self.state != 'IDLE': + lad = (lad3 << 3) | (lad2 << 2) | (lad1 << 1) | lad0 + lad_bits = bin(lad)[2:] + + # State machine + if self.state == 'IDLE': + # A valid LPC cycle starts with LFRAME# being asserted (low). + # TODO? + if lframe != 0: + continue + self.state = 'GET START' + self.lad = -1 + # self.clocknum = 0 + elif self.state == 'GET START': + handle_get_start(lad, lad_bits, lframe) + elif self.state == 'GET CT/DR': + handle_get_ct_dr(lad, lad_bits) + elif self.state == 'GET ADDR': + handle_get_addr(lad, lad_bits) + elif self.state == 'GET TAR': + handle_get_tar(lad, lad_bits) + elif self.state == 'GET SYNC': + handle_get_sync(lad, lad_bits) + elif self.state == 'GET DATA': + handle_get_data(lad, lad_bits) + elif self.state == 'GET TAR2': + handle_get_tar2(lad, lad_bits) + else: + raise Exception('Invalid state: %s' % self.state) + diff --git a/decoders/maxim_ds28ea00/Makefile.am b/decoders/maxim_ds28ea00/Makefile.am index ee53993..2441359 100644 --- a/decoders/maxim_ds28ea00/Makefile.am +++ b/decoders/maxim_ds28ea00/Makefile.am @@ -20,7 +20,7 @@ pkgdatadir = $(DECODERS_DIR)/maxim_ds28ea00 -dist_pkgdata_DATA = __init__.py maxim_ds28ea00.py +dist_pkgdata_DATA = __init__.py pd.py CLEANFILES = *.pyc diff --git a/decoders/maxim_ds28ea00/__init__.py b/decoders/maxim_ds28ea00/__init__.py index 508ae5b..cb1c778 100644 --- a/decoders/maxim_ds28ea00/__init__.py +++ b/decoders/maxim_ds28ea00/__init__.py @@ -28,5 +28,5 @@ Details: TODO ''' -from .maxim_ds28ea00 import * +from .pd import * diff --git a/decoders/maxim_ds28ea00/maxim_ds28ea00.py b/decoders/maxim_ds28ea00/maxim_ds28ea00.py deleted file mode 100644 index 026457e..0000000 --- a/decoders/maxim_ds28ea00/maxim_ds28ea00.py +++ /dev/null @@ -1,106 +0,0 @@ -## -## This file is part of the sigrok project. -## -## Copyright (C) 2012 Iztok Jeras -## -## This program is free software; you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published by -## the Free Software Foundation; either version 2 of the License, or -## (at your option) any later version. -## -## This program is distributed in the hope that it will be useful, -## but WITHOUT ANY WARRANTY; without even the implied warranty of -## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -## GNU General Public License for more details. -## -## You should have received a copy of the GNU General Public License -## along with this program; if not, write to the Free Software -## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -## - -# Maxim DS28EA00 protocol decoder - -import sigrokdecode as srd - -# Dictionary of FUNCTION commands and their names. -command = { - # Scratchpad - 0x4e: 'Write scratchpad', - 0xbe: 'Read scratchpad', - 0x48: 'Copy scratchpad', - # Thermometer - 0x44: 'Convert temperature', - 0xb4: 'Read power mode', - 0xb8: 'Recall EEPROM', - 0xf5: 'PIO access read', - 0xA5: 'PIO access write', - 0x99: 'Chain', -} - -class Decoder(srd.Decoder): - api_version = 1 - id = 'maxim_ds28ea00' - name = 'Maxim DS28EA00' - longname = 'Maxim DS28EA00 1-Wire digital thermometer' - desc = '1-Wire digital thermometer with Sequence Detect and PIO' - license = 'gplv2+' - inputs = ['onewire_network'] - outputs = ['maxim_ds28ea00'] - probes = [] - optional_probes = [ - {'id': 'pioa', 'name': 'PIOA/DONE#', - 'desc': 'PIOA channel and chain output'}, - {'id': 'piob', 'name': 'PIOB/EN#', - 'desc': 'PIOB channel and chain output'}, - ] - options = {} - annotations = [ - ['Text', 'Human-readable text'], - ] - - def __init__(self, **kwargs): - self.trn_beg = 0 - self.trn_end = 0 - self.state = 'ROM' - self.rom = 0x0000000000000000 - - def start(self, metadata): - self.out_ann = self.add(srd.OUTPUT_ANN, 'maxim_ds28ea00') - - def report(self): - pass - - def putx(self, data): - self.put(self.ss, self.es, self.out_ann, data) - - def decode(self, ss, es, data): - code, val = data - - self.ss, self.es = ss, es - - # State machine. - if code == 'RESET/PRESENCE': - self.putx([0, ['Reset/presence: %s' - % ('true' if val else 'false')]]) - self.state = 'ROM' - elif code == 'ROM': - self.rom = val - self.putx([0, ['ROM: 0x%016x' % (val)]]) - self.state = 'COMMAND' - elif code == 'DATA': - if self.state == 'COMMAND': - if val not in command: - self.putx([0, ['Unrecognized command: 0x%02x' % val]]) - return - self.putx([0, ['Function command: 0x%02x \'%s\'' - % (val, command[val])]]) - self.state = command[val].upper() - elif self.state == 'READ SCRATCHPAD': - self.putx([0, ['Scratchpad data: 0x%02x' % val]]) - elif self.state == 'CONVERT TEMPERATURE': - self.putx([0, ['Temperature conversion status: 0x%02x' % val]]) - elif self.state in [s.upper() for s in command.values()]: - self.putx([0, ['TODO \'%s\': 0x%02x' % (self.state, val)]]) - else: - raise Exception('Invalid state: %s' % self.state) - diff --git a/decoders/maxim_ds28ea00/pd.py b/decoders/maxim_ds28ea00/pd.py new file mode 100644 index 0000000..026457e --- /dev/null +++ b/decoders/maxim_ds28ea00/pd.py @@ -0,0 +1,106 @@ +## +## This file is part of the sigrok project. +## +## Copyright (C) 2012 Iztok Jeras +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + +# Maxim DS28EA00 protocol decoder + +import sigrokdecode as srd + +# Dictionary of FUNCTION commands and their names. +command = { + # Scratchpad + 0x4e: 'Write scratchpad', + 0xbe: 'Read scratchpad', + 0x48: 'Copy scratchpad', + # Thermometer + 0x44: 'Convert temperature', + 0xb4: 'Read power mode', + 0xb8: 'Recall EEPROM', + 0xf5: 'PIO access read', + 0xA5: 'PIO access write', + 0x99: 'Chain', +} + +class Decoder(srd.Decoder): + api_version = 1 + id = 'maxim_ds28ea00' + name = 'Maxim DS28EA00' + longname = 'Maxim DS28EA00 1-Wire digital thermometer' + desc = '1-Wire digital thermometer with Sequence Detect and PIO' + license = 'gplv2+' + inputs = ['onewire_network'] + outputs = ['maxim_ds28ea00'] + probes = [] + optional_probes = [ + {'id': 'pioa', 'name': 'PIOA/DONE#', + 'desc': 'PIOA channel and chain output'}, + {'id': 'piob', 'name': 'PIOB/EN#', + 'desc': 'PIOB channel and chain output'}, + ] + options = {} + annotations = [ + ['Text', 'Human-readable text'], + ] + + def __init__(self, **kwargs): + self.trn_beg = 0 + self.trn_end = 0 + self.state = 'ROM' + self.rom = 0x0000000000000000 + + def start(self, metadata): + self.out_ann = self.add(srd.OUTPUT_ANN, 'maxim_ds28ea00') + + def report(self): + pass + + def putx(self, data): + self.put(self.ss, self.es, self.out_ann, data) + + def decode(self, ss, es, data): + code, val = data + + self.ss, self.es = ss, es + + # State machine. + if code == 'RESET/PRESENCE': + self.putx([0, ['Reset/presence: %s' + % ('true' if val else 'false')]]) + self.state = 'ROM' + elif code == 'ROM': + self.rom = val + self.putx([0, ['ROM: 0x%016x' % (val)]]) + self.state = 'COMMAND' + elif code == 'DATA': + if self.state == 'COMMAND': + if val not in command: + self.putx([0, ['Unrecognized command: 0x%02x' % val]]) + return + self.putx([0, ['Function command: 0x%02x \'%s\'' + % (val, command[val])]]) + self.state = command[val].upper() + elif self.state == 'READ SCRATCHPAD': + self.putx([0, ['Scratchpad data: 0x%02x' % val]]) + elif self.state == 'CONVERT TEMPERATURE': + self.putx([0, ['Temperature conversion status: 0x%02x' % val]]) + elif self.state in [s.upper() for s in command.values()]: + self.putx([0, ['TODO \'%s\': 0x%02x' % (self.state, val)]]) + else: + raise Exception('Invalid state: %s' % self.state) + diff --git a/decoders/mlx90614/Makefile.am b/decoders/mlx90614/Makefile.am index f14874f..c9d0d4b 100644 --- a/decoders/mlx90614/Makefile.am +++ b/decoders/mlx90614/Makefile.am @@ -20,7 +20,7 @@ pkgdatadir = $(DECODERS_DIR)/mlx90614 -dist_pkgdata_DATA = __init__.py mlx90614.py +dist_pkgdata_DATA = __init__.py pd.py CLEANFILES = *.pyc diff --git a/decoders/mlx90614/__init__.py b/decoders/mlx90614/__init__.py index 0ceabc2..c14e67f 100644 --- a/decoders/mlx90614/__init__.py +++ b/decoders/mlx90614/__init__.py @@ -25,5 +25,5 @@ Details: TODO ''' -from .mlx90614 import * +from .pd import * diff --git a/decoders/mlx90614/mlx90614.py b/decoders/mlx90614/mlx90614.py deleted file mode 100644 index 1ee88d7..0000000 --- a/decoders/mlx90614/mlx90614.py +++ /dev/null @@ -1,87 +0,0 @@ -## -## This file is part of the sigrok 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 -## - -# Melexis MLX90614 Infrared Thermometer protocol decoder - -import sigrokdecode as srd - -class Decoder(srd.Decoder): - api_version = 1 - id = 'mlx90614' - name = 'MLX90614' - longname = 'Melexis MLX90614' - desc = 'Infrared Thermometer protocol.' - license = 'gplv2+' - inputs = ['i2c'] - outputs = ['mlx90614'] - probes = [] - optional_probes = [] - options = {} - annotations = [ - ['Celsius', 'Temperature in degrees Celsius'], - ['Kelvin', 'Temperature in degrees Kelvin'], - ] - - def __init__(self, **kwargs): - self.state = 'IGNORE START REPEAT' - self.data = [] - - def start(self, metadata): - # self.out_proto = self.add(srd.OUTPUT_PROTO, 'mlx90614') - self.out_ann = self.add(srd.OUTPUT_ANN, 'mlx90614') - - def report(self): - pass - - def putx(self, data): - self.put(self.ss, self.es, self.out_ann, data) - - # Quick hack implementation! This needs to be improved a lot! - def decode(self, ss, es, data): - cmd, databyte = data - - # State machine. - if self.state == 'IGNORE START REPEAT': - if cmd != 'START REPEAT': - return - self.state = 'IGNORE ADDRESS WRITE' - elif self.state == 'IGNORE ADDRESS WRITE': - if cmd != 'ADDRESS WRITE': - return - self.state = 'GET TEMPERATURE' - elif self.state == 'GET TEMPERATURE': - if cmd != 'DATA WRITE': - return - if len(self.data) == 0: - self.data.append(databyte) - self.ss = ss - elif len(self.data) == 1: - self.data.append(databyte) - self.es = es - else: - kelvin = (self.data[0] | (self.data[1] << 8)) * 0.02 - celsius = kelvin - 273.15 - self.putx([0, ['Temperature: %3.2f °C' % celsius]]) - self.putx([1, ['Temperature: %3.2f °K' % kelvin]]) - self.state = 'IGNORE START REPEAT' - self.data = [] - else: - raise Exception('Invalid state: %d' % self.state) - diff --git a/decoders/mlx90614/pd.py b/decoders/mlx90614/pd.py new file mode 100644 index 0000000..1ee88d7 --- /dev/null +++ b/decoders/mlx90614/pd.py @@ -0,0 +1,87 @@ +## +## This file is part of the sigrok 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 +## + +# Melexis MLX90614 Infrared Thermometer protocol decoder + +import sigrokdecode as srd + +class Decoder(srd.Decoder): + api_version = 1 + id = 'mlx90614' + name = 'MLX90614' + longname = 'Melexis MLX90614' + desc = 'Infrared Thermometer protocol.' + license = 'gplv2+' + inputs = ['i2c'] + outputs = ['mlx90614'] + probes = [] + optional_probes = [] + options = {} + annotations = [ + ['Celsius', 'Temperature in degrees Celsius'], + ['Kelvin', 'Temperature in degrees Kelvin'], + ] + + def __init__(self, **kwargs): + self.state = 'IGNORE START REPEAT' + self.data = [] + + def start(self, metadata): + # self.out_proto = self.add(srd.OUTPUT_PROTO, 'mlx90614') + self.out_ann = self.add(srd.OUTPUT_ANN, 'mlx90614') + + def report(self): + pass + + def putx(self, data): + self.put(self.ss, self.es, self.out_ann, data) + + # Quick hack implementation! This needs to be improved a lot! + def decode(self, ss, es, data): + cmd, databyte = data + + # State machine. + if self.state == 'IGNORE START REPEAT': + if cmd != 'START REPEAT': + return + self.state = 'IGNORE ADDRESS WRITE' + elif self.state == 'IGNORE ADDRESS WRITE': + if cmd != 'ADDRESS WRITE': + return + self.state = 'GET TEMPERATURE' + elif self.state == 'GET TEMPERATURE': + if cmd != 'DATA WRITE': + return + if len(self.data) == 0: + self.data.append(databyte) + self.ss = ss + elif len(self.data) == 1: + self.data.append(databyte) + self.es = es + else: + kelvin = (self.data[0] | (self.data[1] << 8)) * 0.02 + celsius = kelvin - 273.15 + self.putx([0, ['Temperature: %3.2f °C' % celsius]]) + self.putx([1, ['Temperature: %3.2f °K' % kelvin]]) + self.state = 'IGNORE START REPEAT' + self.data = [] + else: + raise Exception('Invalid state: %d' % self.state) + diff --git a/decoders/mx25lxx05d/Makefile.am b/decoders/mx25lxx05d/Makefile.am index 2d571f3..6949796 100644 --- a/decoders/mx25lxx05d/Makefile.am +++ b/decoders/mx25lxx05d/Makefile.am @@ -20,7 +20,7 @@ pkgdatadir = $(DECODERS_DIR)/mx25lxx05d -dist_pkgdata_DATA = __init__.py mx25lxx05d.py +dist_pkgdata_DATA = __init__.py pd.py CLEANFILES = *.pyc diff --git a/decoders/mx25lxx05d/__init__.py b/decoders/mx25lxx05d/__init__.py index 3f3c1c6..74b70e8 100644 --- a/decoders/mx25lxx05d/__init__.py +++ b/decoders/mx25lxx05d/__init__.py @@ -29,5 +29,5 @@ Details: http://www.macronix.com/QuickPlace/hq/PageLibrary4825740B00298A3B.nsf/h_Index/3F21BAC2E121E17848257639003A3146/$File/MX25L1605D-3205D-6405D-1.5.pdf ''' -from .mx25lxx05d import * +from .pd import * diff --git a/decoders/mx25lxx05d/mx25lxx05d.py b/decoders/mx25lxx05d/mx25lxx05d.py deleted file mode 100644 index 182ae26..0000000 --- a/decoders/mx25lxx05d/mx25lxx05d.py +++ /dev/null @@ -1,381 +0,0 @@ -## -## This file is part of the sigrok project. -## -## Copyright (C) 2011-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 -## - -# Macronix MX25Lxx05D SPI (NOR) flash chip protocol decoder - -# Note: Works for MX25L1605D/MX25L3205D/MX25L6405D. - -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 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 = 1 - id = 'mx25lxx05d' - name = 'MX25Lxx05D' - longname = 'Macronix MX25Lxx05D' - desc = 'SPI (NOR) flash chip protocol.' - license = 'gplv2+' - inputs = ['spi', 'logic'] - outputs = ['mx25lxx05d'] - probes = [] - optional_probes = [ - {'id': 'hold', 'name': 'HOLD#', 'desc': 'TODO.'}, - {'id': 'wp_acc', 'name': 'WP#/ACC', 'desc': 'TODO.'}, - ] - options = {} - annotations = [ - ['Text', 'Human-readable text'], - ['Verbose decode', 'Decoded register bits, read/write data'], - ['Warnings', 'Human-readable warnings'], - ] - - def __init__(self, **kwargs): - self.state = None - self.cmdstate = 1 - self.addr = 0 - self.data = [] - - def start(self, metadata): - # self.out_proto = self.add(srd.OUTPUT_PROTO, 'mx25lxx05d') - self.out_ann = self.add(srd.OUTPUT_ANN, 'mx25lxx05d') - - def report(self): - pass - - 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([0, ['Command: %s' % cmds[self.state][1]]]) - elif self.cmdstate == 2: - # Byte 2: Slave sends the JEDEC manufacturer ID. - self.putx([0, ['Manufacturer ID: 0x%02x' % miso]]) - elif self.cmdstate == 3: - # Byte 3: Slave sends the memory type (0x20 for this chip). - self.putx([0, ['Memory type: 0x%02x' % miso]]) - elif self.cmdstate == 4: - # Byte 4: Slave sends the device ID. - self.device_id = miso - self.putx([0, ['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([0, ['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([0, ['Status register: 0x%02x' % miso]]) - self.putx([1, [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([0, ['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([0, ['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([0, ['Read data']]) - self.putx([1, ['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([0, ['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, [0, [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, [2, [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([0, ['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([0, ['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([0, ['Page data']]) - self.putx([1, ['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([0, ['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([0, ['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([0, ['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([0, ['%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([0, ['%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([0, ['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([0, ['Unknown command: 0x%02x' % mosi]]) - self.state = None - diff --git a/decoders/mx25lxx05d/pd.py b/decoders/mx25lxx05d/pd.py new file mode 100644 index 0000000..182ae26 --- /dev/null +++ b/decoders/mx25lxx05d/pd.py @@ -0,0 +1,381 @@ +## +## This file is part of the sigrok project. +## +## Copyright (C) 2011-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 +## + +# Macronix MX25Lxx05D SPI (NOR) flash chip protocol decoder + +# Note: Works for MX25L1605D/MX25L3205D/MX25L6405D. + +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 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 = 1 + id = 'mx25lxx05d' + name = 'MX25Lxx05D' + longname = 'Macronix MX25Lxx05D' + desc = 'SPI (NOR) flash chip protocol.' + license = 'gplv2+' + inputs = ['spi', 'logic'] + outputs = ['mx25lxx05d'] + probes = [] + optional_probes = [ + {'id': 'hold', 'name': 'HOLD#', 'desc': 'TODO.'}, + {'id': 'wp_acc', 'name': 'WP#/ACC', 'desc': 'TODO.'}, + ] + options = {} + annotations = [ + ['Text', 'Human-readable text'], + ['Verbose decode', 'Decoded register bits, read/write data'], + ['Warnings', 'Human-readable warnings'], + ] + + def __init__(self, **kwargs): + self.state = None + self.cmdstate = 1 + self.addr = 0 + self.data = [] + + def start(self, metadata): + # self.out_proto = self.add(srd.OUTPUT_PROTO, 'mx25lxx05d') + self.out_ann = self.add(srd.OUTPUT_ANN, 'mx25lxx05d') + + def report(self): + pass + + 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([0, ['Command: %s' % cmds[self.state][1]]]) + elif self.cmdstate == 2: + # Byte 2: Slave sends the JEDEC manufacturer ID. + self.putx([0, ['Manufacturer ID: 0x%02x' % miso]]) + elif self.cmdstate == 3: + # Byte 3: Slave sends the memory type (0x20 for this chip). + self.putx([0, ['Memory type: 0x%02x' % miso]]) + elif self.cmdstate == 4: + # Byte 4: Slave sends the device ID. + self.device_id = miso + self.putx([0, ['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([0, ['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([0, ['Status register: 0x%02x' % miso]]) + self.putx([1, [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([0, ['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([0, ['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([0, ['Read data']]) + self.putx([1, ['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([0, ['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, [0, [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, [2, [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([0, ['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([0, ['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([0, ['Page data']]) + self.putx([1, ['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([0, ['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([0, ['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([0, ['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([0, ['%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([0, ['%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([0, ['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([0, ['Unknown command: 0x%02x' % mosi]]) + self.state = None + diff --git a/decoders/mxc6225xu/Makefile.am b/decoders/mxc6225xu/Makefile.am index 891dbc1..45cd50c 100644 --- a/decoders/mxc6225xu/Makefile.am +++ b/decoders/mxc6225xu/Makefile.am @@ -20,7 +20,7 @@ pkgdatadir = $(DECODERS_DIR)/mxc6225xu -dist_pkgdata_DATA = __init__.py mxc6225xu.py +dist_pkgdata_DATA = __init__.py pd.py CLEANFILES = *.pyc diff --git a/decoders/mxc6225xu/__init__.py b/decoders/mxc6225xu/__init__.py index 02b63b2..3bca9e4 100644 --- a/decoders/mxc6225xu/__init__.py +++ b/decoders/mxc6225xu/__init__.py @@ -30,5 +30,5 @@ Details: TODO ''' -from .mxc6225xu import * +from .pd import * diff --git a/decoders/mxc6225xu/mxc6225xu.py b/decoders/mxc6225xu/mxc6225xu.py deleted file mode 100644 index 39564a9..0000000 --- a/decoders/mxc6225xu/mxc6225xu.py +++ /dev/null @@ -1,229 +0,0 @@ -## -## This file is part of the sigrok 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 -## - -# MEMSIC MXC6225XU protocol decoder - -import sigrokdecode as srd - -# Definitions of various bits in MXC6225XU registers. -status = { - # SH[1:0] - 'sh': { - 0b00: 'none', - 0b01: 'shake left', - 0b10: 'shake right', - 0b11: 'undefined', - }, - # ORI[1:0] and OR[1:0] (same format) - 'ori': { - 0b00: 'vertical in upright orientation', - 0b01: 'rotated 90 degrees clockwise', - 0b10: 'vertical in inverted orientation', - 0b11: 'rotated 90 degrees counterclockwise', - }, - # SHTH[1:0] - 'shth': { - 0b00: '0.5g', - 0b01: '1.0g', - 0b10: '1.5g', - 0b11: '2.0g', - }, - # SHC[1:0] - 'shc': { - 0b00: '16', - 0b01: '32', - 0b10: '64', - 0b11: '128', - }, - # ORC[1:0] - 'orc': { - 0b00: '16', - 0b01: '32', - 0b10: '64', - 0b11: '128', - }, -} - -class Decoder(srd.Decoder): - api_version = 1 - id = 'mxc6225xu' - name = 'MXC6225XU' - longname = 'MEMSIC MXC6225XU' - desc = 'Digital Thermal Orientation Sensor (DTOS) protocol.' - license = 'gplv2+' - inputs = ['i2c'] - outputs = ['mxc6225xu'] - probes = [] - optional_probes = [ - {'id': 'int', 'name': 'INT', 'desc': 'DTOS interrupt output pin'}, - ] - options = {} - annotations = [ - ['Text', 'Human-readable text'], - ] - - def __init__(self, **kwargs): - self.state = 'IDLE' - - def start(self, metadata): - # self.out_proto = self.add(srd.OUTPUT_PROTO, 'mxc6225xu') - self.out_ann = self.add(srd.OUTPUT_ANN, 'mxc6225xu') - - def report(self): - pass - - def putx(self, data): - self.put(self.ss, self.es, self.out_ann, data) - - def handle_reg_0x00(self, b): - # XOUT: 8-bit x-axis acceleration output. - # Data is in 2's complement, values range from -128 to 127. - self.putx([0, ['XOUT: %d' % b]]) - - def handle_reg_0x01(self, b): - # YOUT: 8-bit y-axis acceleration output. - # Data is in 2's complement, values range from -128 to 127. - self.putx([0, ['YOUT: %d' % b]]) - - def handle_reg_0x02(self, b): - # STATUS: Orientation and shake status. - - # Bits[7:7]: INT - int_val = (b >> 7) & 1 - s = 'unchanged and no' if (int_val == 0) else 'changed or' - ann = 'INT = %d: Orientation %s shake event occured\n' % (int_val, s) - - # Bits[6:5]: SH[1:0] - sh = (((b >> 6) & 1) << 1) | ((b >> 5) & 1) - ann += 'SH[1:0] = %s: Shake event: %s\n' % \ - (bin(sh)[2:], status['sh'][sh]) - - # Bits[4:4]: TILT - tilt = (b >> 4) & 1 - s = '' if (tilt == 0) else 'not ' - ann += 'TILT = %d: Orientation measurement is %svalid\n' % (tilt, s) - - # Bits[3:2]: ORI[1:0] - ori = (((b >> 3) & 1) << 1) | ((b >> 2) & 1) - ann += 'ORI[1:0] = %s: %s\n' % (bin(ori)[2:], status['ori'][ori]) - - # Bits[1:0]: OR[1:0] - or_val = (((b >> 1) & 1) << 1) | ((b >> 0) & 1) - ann += 'OR[1:0] = %s: %s\n' % (bin(or_val)[2:], status['ori'][or_val]) - - # ann += 'b = %s\n' % (bin(b)) - - self.putx([0, [ann]]) - - def handle_reg_0x03(self, b): - # DETECTION: Powerdown, orientation and shake detection parameters. - # Note: This is a write-only register. - - # Bits[7:7]: PD - pd = (b >> 7) & 1 - s = 'Do not power down' if (pd == 0) else 'Power down' - ann = 'PD = %d: %s the device (into a low-power state)\n' % (pd, s) - - # Bits[6:6]: SHM - shm = (b >> 6) & 1 - ann = 'SHM = %d: Set shake mode to %d\n' % (shm, shm) - - # Bits[5:4]: SHTH[1:0] - shth = (((b >> 5) & 1) << 1) | ((b >> 4) & 1) - ann += 'SHTH[1:0] = %s: Set shake threshold to %s\n' \ - % (bin(shth)[2:], status['shth'][shth]) - - # Bits[3:2]: SHC[1:0] - shc = (((b >> 3) & 1) << 1) | ((b >> 2) & 1) - ann += 'SHC[1:0] = %s: Set shake count to %s readings\n' \ - % (bin(shc)[2:], status['shc'][shc]) - - # Bits[1:0]: ORC[1:0] - orc = (((b >> 1) & 1) << 1) | ((b >> 0) & 1) - ann += 'ORC[1:0] = %s: Set orientation count to %s readings\n' \ - % (bin(orc)[2:], status['orc'][orc]) - - self.putx([0, [ann]]) - - # TODO: Fixup, this is copy-pasted from another PD. - # TODO: Handle/check the ACKs/NACKs. - def decode(self, ss, es, data): - cmd, databyte = data - - # Store the start/end samples of this I2C packet. - self.ss, self.es = ss, es - - # State machine. - if self.state == 'IDLE': - # Wait for an I2C START condition. - 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(?) - if cmd != 'ADDRESS WRITE': - return - self.state = 'GET REG ADDR' - elif self.state == 'GET REG ADDR': - # Wait for a data write (master selects the slave register). - if cmd != 'DATA WRITE': - return - self.reg = databyte - self.state = 'WRITE REGS' - elif self.state == 'WRITE REGS': - # If we see a Repeated Start here, it's a multi-byte read. - if cmd == 'START REPEAT': - self.state = 'READ REGS' - return - # Otherwise: Get data bytes until a STOP condition occurs. - if cmd == 'DATA WRITE': - handle_reg = getattr(self, 'handle_reg_0x%02x' % self.reg) - handle_reg(databyte) - self.reg += 1 - # TODO: Check for NACK! - elif cmd == 'STOP': - # TODO - self.state = 'IDLE' - else: - pass # TODO - elif self.state == 'READ REGS': - # Wait for an address read operation. - # TODO: We should only handle packets to the slave(?) - if cmd == 'ADDRESS READ': - self.state = 'READ REGS2' - return - else: - pass # TODO - elif self.state == 'READ REGS2': - if cmd == 'DATA READ': - handle_reg = getattr(self, 'handle_reg_0x%02x' % self.reg) - handle_reg(databyte) - self.reg += 1 - # TODO: Check for NACK! - elif cmd == 'STOP': - # TODO - self.state = 'IDLE' - else: - pass # TODO? - else: - raise Exception('Invalid state: %d' % self.state) - diff --git a/decoders/mxc6225xu/pd.py b/decoders/mxc6225xu/pd.py new file mode 100644 index 0000000..39564a9 --- /dev/null +++ b/decoders/mxc6225xu/pd.py @@ -0,0 +1,229 @@ +## +## This file is part of the sigrok 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 +## + +# MEMSIC MXC6225XU protocol decoder + +import sigrokdecode as srd + +# Definitions of various bits in MXC6225XU registers. +status = { + # SH[1:0] + 'sh': { + 0b00: 'none', + 0b01: 'shake left', + 0b10: 'shake right', + 0b11: 'undefined', + }, + # ORI[1:0] and OR[1:0] (same format) + 'ori': { + 0b00: 'vertical in upright orientation', + 0b01: 'rotated 90 degrees clockwise', + 0b10: 'vertical in inverted orientation', + 0b11: 'rotated 90 degrees counterclockwise', + }, + # SHTH[1:0] + 'shth': { + 0b00: '0.5g', + 0b01: '1.0g', + 0b10: '1.5g', + 0b11: '2.0g', + }, + # SHC[1:0] + 'shc': { + 0b00: '16', + 0b01: '32', + 0b10: '64', + 0b11: '128', + }, + # ORC[1:0] + 'orc': { + 0b00: '16', + 0b01: '32', + 0b10: '64', + 0b11: '128', + }, +} + +class Decoder(srd.Decoder): + api_version = 1 + id = 'mxc6225xu' + name = 'MXC6225XU' + longname = 'MEMSIC MXC6225XU' + desc = 'Digital Thermal Orientation Sensor (DTOS) protocol.' + license = 'gplv2+' + inputs = ['i2c'] + outputs = ['mxc6225xu'] + probes = [] + optional_probes = [ + {'id': 'int', 'name': 'INT', 'desc': 'DTOS interrupt output pin'}, + ] + options = {} + annotations = [ + ['Text', 'Human-readable text'], + ] + + def __init__(self, **kwargs): + self.state = 'IDLE' + + def start(self, metadata): + # self.out_proto = self.add(srd.OUTPUT_PROTO, 'mxc6225xu') + self.out_ann = self.add(srd.OUTPUT_ANN, 'mxc6225xu') + + def report(self): + pass + + def putx(self, data): + self.put(self.ss, self.es, self.out_ann, data) + + def handle_reg_0x00(self, b): + # XOUT: 8-bit x-axis acceleration output. + # Data is in 2's complement, values range from -128 to 127. + self.putx([0, ['XOUT: %d' % b]]) + + def handle_reg_0x01(self, b): + # YOUT: 8-bit y-axis acceleration output. + # Data is in 2's complement, values range from -128 to 127. + self.putx([0, ['YOUT: %d' % b]]) + + def handle_reg_0x02(self, b): + # STATUS: Orientation and shake status. + + # Bits[7:7]: INT + int_val = (b >> 7) & 1 + s = 'unchanged and no' if (int_val == 0) else 'changed or' + ann = 'INT = %d: Orientation %s shake event occured\n' % (int_val, s) + + # Bits[6:5]: SH[1:0] + sh = (((b >> 6) & 1) << 1) | ((b >> 5) & 1) + ann += 'SH[1:0] = %s: Shake event: %s\n' % \ + (bin(sh)[2:], status['sh'][sh]) + + # Bits[4:4]: TILT + tilt = (b >> 4) & 1 + s = '' if (tilt == 0) else 'not ' + ann += 'TILT = %d: Orientation measurement is %svalid\n' % (tilt, s) + + # Bits[3:2]: ORI[1:0] + ori = (((b >> 3) & 1) << 1) | ((b >> 2) & 1) + ann += 'ORI[1:0] = %s: %s\n' % (bin(ori)[2:], status['ori'][ori]) + + # Bits[1:0]: OR[1:0] + or_val = (((b >> 1) & 1) << 1) | ((b >> 0) & 1) + ann += 'OR[1:0] = %s: %s\n' % (bin(or_val)[2:], status['ori'][or_val]) + + # ann += 'b = %s\n' % (bin(b)) + + self.putx([0, [ann]]) + + def handle_reg_0x03(self, b): + # DETECTION: Powerdown, orientation and shake detection parameters. + # Note: This is a write-only register. + + # Bits[7:7]: PD + pd = (b >> 7) & 1 + s = 'Do not power down' if (pd == 0) else 'Power down' + ann = 'PD = %d: %s the device (into a low-power state)\n' % (pd, s) + + # Bits[6:6]: SHM + shm = (b >> 6) & 1 + ann = 'SHM = %d: Set shake mode to %d\n' % (shm, shm) + + # Bits[5:4]: SHTH[1:0] + shth = (((b >> 5) & 1) << 1) | ((b >> 4) & 1) + ann += 'SHTH[1:0] = %s: Set shake threshold to %s\n' \ + % (bin(shth)[2:], status['shth'][shth]) + + # Bits[3:2]: SHC[1:0] + shc = (((b >> 3) & 1) << 1) | ((b >> 2) & 1) + ann += 'SHC[1:0] = %s: Set shake count to %s readings\n' \ + % (bin(shc)[2:], status['shc'][shc]) + + # Bits[1:0]: ORC[1:0] + orc = (((b >> 1) & 1) << 1) | ((b >> 0) & 1) + ann += 'ORC[1:0] = %s: Set orientation count to %s readings\n' \ + % (bin(orc)[2:], status['orc'][orc]) + + self.putx([0, [ann]]) + + # TODO: Fixup, this is copy-pasted from another PD. + # TODO: Handle/check the ACKs/NACKs. + def decode(self, ss, es, data): + cmd, databyte = data + + # Store the start/end samples of this I2C packet. + self.ss, self.es = ss, es + + # State machine. + if self.state == 'IDLE': + # Wait for an I2C START condition. + 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(?) + if cmd != 'ADDRESS WRITE': + return + self.state = 'GET REG ADDR' + elif self.state == 'GET REG ADDR': + # Wait for a data write (master selects the slave register). + if cmd != 'DATA WRITE': + return + self.reg = databyte + self.state = 'WRITE REGS' + elif self.state == 'WRITE REGS': + # If we see a Repeated Start here, it's a multi-byte read. + if cmd == 'START REPEAT': + self.state = 'READ REGS' + return + # Otherwise: Get data bytes until a STOP condition occurs. + if cmd == 'DATA WRITE': + handle_reg = getattr(self, 'handle_reg_0x%02x' % self.reg) + handle_reg(databyte) + self.reg += 1 + # TODO: Check for NACK! + elif cmd == 'STOP': + # TODO + self.state = 'IDLE' + else: + pass # TODO + elif self.state == 'READ REGS': + # Wait for an address read operation. + # TODO: We should only handle packets to the slave(?) + if cmd == 'ADDRESS READ': + self.state = 'READ REGS2' + return + else: + pass # TODO + elif self.state == 'READ REGS2': + if cmd == 'DATA READ': + handle_reg = getattr(self, 'handle_reg_0x%02x' % self.reg) + handle_reg(databyte) + self.reg += 1 + # TODO: Check for NACK! + elif cmd == 'STOP': + # TODO + self.state = 'IDLE' + else: + pass # TODO? + else: + raise Exception('Invalid state: %d' % self.state) + diff --git a/decoders/nunchuk/Makefile.am b/decoders/nunchuk/Makefile.am index 5a4917b..7e37522 100644 --- a/decoders/nunchuk/Makefile.am +++ b/decoders/nunchuk/Makefile.am @@ -20,7 +20,7 @@ pkgdatadir = $(DECODERS_DIR)/nunchuk -dist_pkgdata_DATA = __init__.py nunchuk.py +dist_pkgdata_DATA = __init__.py pd.py CLEANFILES = *.pyc diff --git a/decoders/nunchuk/__init__.py b/decoders/nunchuk/__init__.py index 9b85374..ac8e64b 100644 --- a/decoders/nunchuk/__init__.py +++ b/decoders/nunchuk/__init__.py @@ -29,5 +29,5 @@ http://todbot.com/blog/2008/02/18/wiichuck-wii-nunchuck-adapter-available/ https://www.sparkfun.com/products/9281 ''' -from .nunchuk import * +from .pd import * diff --git a/decoders/nunchuk/nunchuk.py b/decoders/nunchuk/nunchuk.py deleted file mode 100644 index 0dae39c..0000000 --- a/decoders/nunchuk/nunchuk.py +++ /dev/null @@ -1,206 +0,0 @@ -## -## This file is part of the sigrok project. -## -## Copyright (C) 2010-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 -## - -# Nintendo Wii Nunchuk protocol decoder - -import sigrokdecode as srd - -class Decoder(srd.Decoder): - api_version = 1 - id = 'nunchuk' - name = 'Nunchuk' - longname = 'Nintendo Wii Nunchuk' - desc = 'Nintendo Wii Nunchuk controller protocol.' - license = 'gplv2+' - inputs = ['i2c'] - outputs = ['nunchuck'] - probes = [] - optional_probes = [] - options = {} - annotations = [ - ['Text (verbose)', 'Human-readable text (verbose)'], - ['Text', 'Human-readable text'], - ['Warnings', 'Human-readable warnings'], - ] - - def __init__(self, **kwargs): - self.state = 'IDLE' - self.sx = self.sy = self.ax = self.ay = self.az = self.bz = self.bc = -1 - self.databytecount = 0 - self.reg = 0x00 - self.init_seq = [] - - def start(self, metadata): - # self.out_proto = self.add(srd.OUTPUT_PROTO, 'nunchuk') - self.out_ann = self.add(srd.OUTPUT_ANN, 'nunchuk') - - def report(self): - pass - - def putx(self, data): - # Helper for annotations which span exactly one I2C packet. - self.put(self.ss, self.es, self.out_ann, data) - - def putb(self, data): - # Helper for annotations which span a block of I2C packets. - self.put(self.block_start_sample, self.block_end_sample, - self.out_ann, data) - - def handle_reg_0x00(self, databyte): - self.sx = databyte - self.putx([0, ['Analog stick X position: 0x%02x' % self.sx]]) - self.putx([1, ['SX: 0x%02x' % self.sx]]) - - def handle_reg_0x01(self, databyte): - self.sy = databyte - self.putx([0, ['Analog stick Y position: 0x%02x' % self.sy]]) - self.putx([1, ['SY: 0x%02x' % self.sy]]) - - def handle_reg_0x02(self, databyte): - self.ax = databyte << 2 - self.putx([0, ['Accelerometer X value bits[9:2]: 0x%03x' % self.ax]]) - self.putx([1, ['AX[9:2]: 0x%03x' % self.ax]]) - - def handle_reg_0x03(self, databyte): - self.ay = databyte << 2 - self.putx([0, ['Accelerometer Y value bits[9:2]: 0x%03x' % self.ay]]) - self.putx([1, ['AY[9:2]: 0x%x' % self.ay]]) - - def handle_reg_0x04(self, databyte): - self.az = databyte << 2 - self.putx([0, ['Accelerometer Z value bits[9:2]: 0x%03x' % self.az]]) - self.putx([1, ['AZ[9:2]: 0x%x' % self.az]]) - - # TODO: Bit-exact annotations. - def handle_reg_0x05(self, databyte): - self.bz = (databyte & (1 << 0)) >> 0 # Bits[0:0] - self.bc = (databyte & (1 << 1)) >> 1 # Bits[1:1] - ax_rest = (databyte & (3 << 2)) >> 2 # Bits[3:2] - ay_rest = (databyte & (3 << 4)) >> 4 # Bits[5:4] - az_rest = (databyte & (3 << 6)) >> 6 # Bits[7:6] - self.ax |= ax_rest - self.ay |= ay_rest - self.az |= az_rest - - s = '' if (self.bz == 0) else 'not ' - self.putx([0, ['Z button: %spressed' % s]]) - self.putx([1, ['BZ: %d' % self.bz]]) - - s = '' if (self.bc == 0) else 'not ' - self.putx([0, ['C button: %spressed' % s]]) - self.putx([1, ['BC: %d' % self.bc]]) - - self.putx([0, ['Accelerometer X value bits[1:0]: 0x%x' % ax_rest]]) - self.putx([1, ['AX[1:0]: 0x%x' % ax_rest]]) - - self.putx([0, ['Accelerometer Y value bits[1:0]: 0x%x' % ay_rest]]) - self.putx([1, ['AY[1:0]: 0x%x' % ay_rest]]) - - self.putx([0, ['Accelerometer Z value bits[1:0]: 0x%x' % az_rest]]) - self.putx([1, ['AZ[1:0]: 0x%x' % az_rest]]) - - def output_full_block_if_possible(self): - # For now, only output summary annotation if all values are available. - t = (self.sx, self.sy, self.ax, self.ay, self.az, self.bz, self.bc) - if -1 in t: - return - - s = 'Analog stick X position: 0x%02x\n' % self.sx - s += 'Analog stick Y position: 0x%02x\n' % self.sy - s += 'Z button: %spressed\n' % ('' if (self.bz == 0) else 'not ') - s += 'C button: %spressed\n' % ('' if (self.bc == 0) else 'not ') - s += 'Accelerometer X value: 0x%03x\n' % self.ax - s += 'Accelerometer Y value: 0x%03x\n' % self.ay - s += 'Accelerometer Z value: 0x%03x\n' % self.az - self.put(self.block_start_sample, self.block_end_sample, - self.out_ann, [0, [s]]) - - s = 'SX = 0x%02x, SY = 0x%02x, AX = 0x%02x, AY = 0x%02x, ' \ - 'AZ = 0x%02x, BZ = 0x%02x, BC = 0x%02x' % (self.sx, \ - self.sy, self.ax, self.ay, self.az, self.bz, self.bc) - self.put(self.block_start_sample, self.block_end_sample, - self.out_ann, [1, [s]]) - - def handle_reg_write(self, databyte): - self.putx([0, ['Nunchuk write: 0x%02x' % databyte]]) - if len(self.init_seq) < 2: - self.init_seq.append(databyte) - - def output_init_seq(self): - if len(self.init_seq) != 2: - self.putb([2, ['Init sequence was %d bytes long (2 expected)' % \ - len(self.init_seq)]]) - - if self.init_seq != (0x40, 0x00): - self.putb([2, ['Unknown init sequence (expected: 0x40 0x00)']]) - - # TODO: Detect Nunchuk clones (they have different init sequences). - s = 'Initialized Nintendo Wii Nunchuk' - - self.putb([0, [s]]) - self.putb([1, ['INIT']]) - - def decode(self, ss, es, data): - cmd, databyte = data - - # Store the start/end samples of this I2C packet. - self.ss, self.es = ss, es - - # State machine. - if self.state == 'IDLE': - # Wait for an I2C START condition. - if cmd != 'START': - return - self.state = 'GET SLAVE ADDR' - self.block_start_sample = ss - elif self.state == 'GET SLAVE ADDR': - # Wait for an address read/write operation. - if cmd == 'ADDRESS READ': - self.state = 'READ REGS' - elif cmd == 'ADDRESS WRITE': - self.state = 'WRITE REGS' - elif self.state == 'READ REGS': - if cmd == 'DATA READ': - handle_reg = getattr(self, 'handle_reg_0x%02x' % self.reg) - handle_reg(databyte) - self.reg += 1 - elif cmd == 'STOP': - self.block_end_sample = es - self.output_full_block_if_possible() - self.sx = self.sy = self.ax = self.ay = self.az = -1 - self.bz = self.bc = -1 - self.state = 'IDLE' - else: - # self.putx([0, ['Ignoring: %s (data=%s)' % (cmd, databyte)]]) - pass - elif self.state == 'WRITE REGS': - if cmd == 'DATA WRITE': - self.handle_reg_write(databyte) - elif cmd == 'STOP': - self.block_end_sample = es - self.output_init_seq() - self.init_seq = [] - self.state = 'IDLE' - else: - # self.putx([0, ['Ignoring: %s (data=%s)' % (cmd, databyte)]]) - pass - else: - raise Exception('Invalid state: %s' % self.state) - diff --git a/decoders/nunchuk/pd.py b/decoders/nunchuk/pd.py new file mode 100644 index 0000000..0dae39c --- /dev/null +++ b/decoders/nunchuk/pd.py @@ -0,0 +1,206 @@ +## +## This file is part of the sigrok project. +## +## Copyright (C) 2010-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 +## + +# Nintendo Wii Nunchuk protocol decoder + +import sigrokdecode as srd + +class Decoder(srd.Decoder): + api_version = 1 + id = 'nunchuk' + name = 'Nunchuk' + longname = 'Nintendo Wii Nunchuk' + desc = 'Nintendo Wii Nunchuk controller protocol.' + license = 'gplv2+' + inputs = ['i2c'] + outputs = ['nunchuck'] + probes = [] + optional_probes = [] + options = {} + annotations = [ + ['Text (verbose)', 'Human-readable text (verbose)'], + ['Text', 'Human-readable text'], + ['Warnings', 'Human-readable warnings'], + ] + + def __init__(self, **kwargs): + self.state = 'IDLE' + self.sx = self.sy = self.ax = self.ay = self.az = self.bz = self.bc = -1 + self.databytecount = 0 + self.reg = 0x00 + self.init_seq = [] + + def start(self, metadata): + # self.out_proto = self.add(srd.OUTPUT_PROTO, 'nunchuk') + self.out_ann = self.add(srd.OUTPUT_ANN, 'nunchuk') + + def report(self): + pass + + def putx(self, data): + # Helper for annotations which span exactly one I2C packet. + self.put(self.ss, self.es, self.out_ann, data) + + def putb(self, data): + # Helper for annotations which span a block of I2C packets. + self.put(self.block_start_sample, self.block_end_sample, + self.out_ann, data) + + def handle_reg_0x00(self, databyte): + self.sx = databyte + self.putx([0, ['Analog stick X position: 0x%02x' % self.sx]]) + self.putx([1, ['SX: 0x%02x' % self.sx]]) + + def handle_reg_0x01(self, databyte): + self.sy = databyte + self.putx([0, ['Analog stick Y position: 0x%02x' % self.sy]]) + self.putx([1, ['SY: 0x%02x' % self.sy]]) + + def handle_reg_0x02(self, databyte): + self.ax = databyte << 2 + self.putx([0, ['Accelerometer X value bits[9:2]: 0x%03x' % self.ax]]) + self.putx([1, ['AX[9:2]: 0x%03x' % self.ax]]) + + def handle_reg_0x03(self, databyte): + self.ay = databyte << 2 + self.putx([0, ['Accelerometer Y value bits[9:2]: 0x%03x' % self.ay]]) + self.putx([1, ['AY[9:2]: 0x%x' % self.ay]]) + + def handle_reg_0x04(self, databyte): + self.az = databyte << 2 + self.putx([0, ['Accelerometer Z value bits[9:2]: 0x%03x' % self.az]]) + self.putx([1, ['AZ[9:2]: 0x%x' % self.az]]) + + # TODO: Bit-exact annotations. + def handle_reg_0x05(self, databyte): + self.bz = (databyte & (1 << 0)) >> 0 # Bits[0:0] + self.bc = (databyte & (1 << 1)) >> 1 # Bits[1:1] + ax_rest = (databyte & (3 << 2)) >> 2 # Bits[3:2] + ay_rest = (databyte & (3 << 4)) >> 4 # Bits[5:4] + az_rest = (databyte & (3 << 6)) >> 6 # Bits[7:6] + self.ax |= ax_rest + self.ay |= ay_rest + self.az |= az_rest + + s = '' if (self.bz == 0) else 'not ' + self.putx([0, ['Z button: %spressed' % s]]) + self.putx([1, ['BZ: %d' % self.bz]]) + + s = '' if (self.bc == 0) else 'not ' + self.putx([0, ['C button: %spressed' % s]]) + self.putx([1, ['BC: %d' % self.bc]]) + + self.putx([0, ['Accelerometer X value bits[1:0]: 0x%x' % ax_rest]]) + self.putx([1, ['AX[1:0]: 0x%x' % ax_rest]]) + + self.putx([0, ['Accelerometer Y value bits[1:0]: 0x%x' % ay_rest]]) + self.putx([1, ['AY[1:0]: 0x%x' % ay_rest]]) + + self.putx([0, ['Accelerometer Z value bits[1:0]: 0x%x' % az_rest]]) + self.putx([1, ['AZ[1:0]: 0x%x' % az_rest]]) + + def output_full_block_if_possible(self): + # For now, only output summary annotation if all values are available. + t = (self.sx, self.sy, self.ax, self.ay, self.az, self.bz, self.bc) + if -1 in t: + return + + s = 'Analog stick X position: 0x%02x\n' % self.sx + s += 'Analog stick Y position: 0x%02x\n' % self.sy + s += 'Z button: %spressed\n' % ('' if (self.bz == 0) else 'not ') + s += 'C button: %spressed\n' % ('' if (self.bc == 0) else 'not ') + s += 'Accelerometer X value: 0x%03x\n' % self.ax + s += 'Accelerometer Y value: 0x%03x\n' % self.ay + s += 'Accelerometer Z value: 0x%03x\n' % self.az + self.put(self.block_start_sample, self.block_end_sample, + self.out_ann, [0, [s]]) + + s = 'SX = 0x%02x, SY = 0x%02x, AX = 0x%02x, AY = 0x%02x, ' \ + 'AZ = 0x%02x, BZ = 0x%02x, BC = 0x%02x' % (self.sx, \ + self.sy, self.ax, self.ay, self.az, self.bz, self.bc) + self.put(self.block_start_sample, self.block_end_sample, + self.out_ann, [1, [s]]) + + def handle_reg_write(self, databyte): + self.putx([0, ['Nunchuk write: 0x%02x' % databyte]]) + if len(self.init_seq) < 2: + self.init_seq.append(databyte) + + def output_init_seq(self): + if len(self.init_seq) != 2: + self.putb([2, ['Init sequence was %d bytes long (2 expected)' % \ + len(self.init_seq)]]) + + if self.init_seq != (0x40, 0x00): + self.putb([2, ['Unknown init sequence (expected: 0x40 0x00)']]) + + # TODO: Detect Nunchuk clones (they have different init sequences). + s = 'Initialized Nintendo Wii Nunchuk' + + self.putb([0, [s]]) + self.putb([1, ['INIT']]) + + def decode(self, ss, es, data): + cmd, databyte = data + + # Store the start/end samples of this I2C packet. + self.ss, self.es = ss, es + + # State machine. + if self.state == 'IDLE': + # Wait for an I2C START condition. + if cmd != 'START': + return + self.state = 'GET SLAVE ADDR' + self.block_start_sample = ss + elif self.state == 'GET SLAVE ADDR': + # Wait for an address read/write operation. + if cmd == 'ADDRESS READ': + self.state = 'READ REGS' + elif cmd == 'ADDRESS WRITE': + self.state = 'WRITE REGS' + elif self.state == 'READ REGS': + if cmd == 'DATA READ': + handle_reg = getattr(self, 'handle_reg_0x%02x' % self.reg) + handle_reg(databyte) + self.reg += 1 + elif cmd == 'STOP': + self.block_end_sample = es + self.output_full_block_if_possible() + self.sx = self.sy = self.ax = self.ay = self.az = -1 + self.bz = self.bc = -1 + self.state = 'IDLE' + else: + # self.putx([0, ['Ignoring: %s (data=%s)' % (cmd, databyte)]]) + pass + elif self.state == 'WRITE REGS': + if cmd == 'DATA WRITE': + self.handle_reg_write(databyte) + elif cmd == 'STOP': + self.block_end_sample = es + self.output_init_seq() + self.init_seq = [] + self.state = 'IDLE' + else: + # self.putx([0, ['Ignoring: %s (data=%s)' % (cmd, databyte)]]) + pass + else: + raise Exception('Invalid state: %s' % self.state) + diff --git a/decoders/onewire_link/Makefile.am b/decoders/onewire_link/Makefile.am index b043eca..f949f4c 100644 --- a/decoders/onewire_link/Makefile.am +++ b/decoders/onewire_link/Makefile.am @@ -20,7 +20,7 @@ pkgdatadir = $(DECODERS_DIR)/onewire_link -dist_pkgdata_DATA = __init__.py onewire_link.py +dist_pkgdata_DATA = __init__.py pd.py CLEANFILES = *.pyc diff --git a/decoders/onewire_link/__init__.py b/decoders/onewire_link/__init__.py index cb03471..c1331c6 100644 --- a/decoders/onewire_link/__init__.py +++ b/decoders/onewire_link/__init__.py @@ -87,5 +87,5 @@ TODO: - Maybe add support for interrupts, check if this feature is deprecated. ''' -from .onewire_link import * +from .pd import * diff --git a/decoders/onewire_link/onewire_link.py b/decoders/onewire_link/onewire_link.py deleted file mode 100644 index c8bda56..0000000 --- a/decoders/onewire_link/onewire_link.py +++ /dev/null @@ -1,269 +0,0 @@ -## -## This file is part of the sigrok project. -## -## Copyright (C) 2012 Iztok Jeras -## -## This program is free software; you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published by -## the Free Software Foundation; either version 2 of the License, or -## (at your option) any later version. -## -## This program is distributed in the hope that it will be useful, -## but WITHOUT ANY WARRANTY; without even the implied warranty of -## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -## GNU General Public License for more details. -## -## You should have received a copy of the GNU General Public License -## along with this program; if not, write to the Free Software -## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -## - -# 1-Wire protocol decoder (link layer) - -import sigrokdecode as srd - -class Decoder(srd.Decoder): - api_version = 1 - id = 'onewire_link' - name = '1-Wire link layer' - longname = '1-Wire serial communication bus (link layer)' - desc = 'Bidirectional, half-duplex, asynchronous serial bus.' - license = 'gplv2+' - inputs = ['logic'] - outputs = ['onewire_link'] - probes = [ - {'id': 'owr', 'name': 'OWR', 'desc': '1-Wire signal line'}, - ] - optional_probes = [ - {'id': 'pwr', 'name': 'PWR', 'desc': '1-Wire power supply pin'}, - ] - options = { - 'overdrive': ['Overdrive', 1], - # Time options (specified in number of samplerate periods): - 'cnt_normal_bit': ['Normal mode sample bit time', 0], - 'cnt_normal_slot': ['Normal mode data slot time', 0], - 'cnt_normal_presence': ['Normal mode sample presence time', 0], - 'cnt_normal_reset': ['Normal mode reset time', 0], - 'cnt_overdrive_bit': ['Overdrive mode sample bit time', 0], - 'cnt_overdrive_slot': ['Overdrive mode data slot time', 0], - 'cnt_overdrive_presence': ['Overdrive mode sample presence time', 0], - 'cnt_overdrive_reset': ['Overdrive mode reset time', 0], - } - annotations = [ - ['Text', 'Human-readable text'], - ['Warnings', 'Human-readable warnings'], - ] - - def __init__(self, **kwargs): - self.samplenum = 0 - self.state = 'WAIT FOR FALLING EDGE' - self.present = 0 - self.bit = 0 - self.bit_cnt = 0 - self.command = 0 - self.overdrive = 0 - self.fall = 0 - self.rise = 0 - - def start(self, metadata): - self.out_proto = self.add(srd.OUTPUT_PROTO, 'onewire_link') - self.out_ann = self.add(srd.OUTPUT_ANN, 'onewire_link') - - self.samplerate = metadata['samplerate'] - - # Check if samplerate is appropriate. - if self.options['overdrive']: - if self.samplerate < 2000000: - self.put(0, 0, self.out_ann, [1, - ['ERROR: Sampling rate is too low. Must be above 2MHz ' + - 'for proper overdrive mode decoding.']]) - elif self.samplerate < 5000000: - self.put(0, 0, self.out_ann, [1, - ['WARNING: Sampling rate is suggested to be above 5MHz ' + - 'for proper overdrive mode decoding.']]) - else: - if self.samplerate < 400000: - self.put(0, 0, self.out_ann, [1, - ['ERROR: Sampling rate is too low. Must be above ' + - '400kHz for proper normal mode decoding.']]) - elif (self.samplerate < 1000000): - self.put(0, 0, self.out_ann, [1, - ['WARNING: 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) - if self.options['cnt_normal_bit']: - self.cnt_normal_bit = self.options['cnt_normal_bit'] - else: - self.cnt_normal_bit = int(samplerate * 0.000015) - 1 # 15ns - if self.options['cnt_normal_slot']: - self.cnt_normal_slot = self.options['cnt_normal_slot'] - else: - self.cnt_normal_slot = int(samplerate * 0.000060) - 1 # 60ns - if self.options['cnt_normal_presence']: - self.cnt_normal_presence = self.options['cnt_normal_presence'] - else: - self.cnt_normal_presence = int(samplerate * 0.000075) - 1 # 75ns - if self.options['cnt_normal_reset']: - self.cnt_normal_reset = self.options['cnt_normal_reset'] - else: - self.cnt_normal_reset = int(samplerate * 0.000480) - 1 # 480ns - if self.options['cnt_overdrive_bit']: - self.cnt_overdrive_bit = self.options['cnt_overdrive_bit'] - else: - self.cnt_overdrive_bit = int(samplerate * 0.000002) - 1 # 2ns - if self.options['cnt_overdrive_slot']: - self.cnt_overdrive_slot = self.options['cnt_overdrive_slot'] - else: - self.cnt_overdrive_slot = int(samplerate * 0.0000073) - 1 # 6ns+1.3ns - if self.options['cnt_overdrive_presence']: - self.cnt_overdrive_presence = self.options['cnt_overdrive_presence'] - else: - self.cnt_overdrive_presence = int(samplerate * 0.000010) - 1 # 10ns - if self.options['cnt_overdrive_reset']: - self.cnt_overdrive_reset = self.options['cnt_overdrive_reset'] - else: - self.cnt_overdrive_reset = int(samplerate * 0.000048) - 1 # 48ns - - # 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 - time_max = float(self.cnt_normal_bit + 1) / self.samplerate - if (time_min < 0.000005) or (time_max > 0.000015): - self.put(0, 0, self.out_ann, [1, - ['WARNING: The normal mode data sample time interval ' + - '(%2.1fus-%2.1fus) should be inside (5.0us, 15.0us).' - % (time_min * 1000000, time_max * 1000000)]]) - - time_min = float(self.cnt_normal_presence) / self.samplerate - time_max = float(self.cnt_normal_presence + 1) / self.samplerate - if (time_min < 0.0000681) or (time_max > 0.000075): - self.put(0, 0, self.out_ann, [1, - ['WARNING: The normal mode presence sample time interval ' + - '(%2.1fus-%2.1fus) should be inside (68.1us, 75.0us).' - % (time_min * 1000000, time_max * 1000000)]]) - - time_min = float(self.cnt_overdrive_bit) / self.samplerate - time_max = float(self.cnt_overdrive_bit + 1) / self.samplerate - if (time_min < 0.000001) or (time_max > 0.000002): - self.put(0, 0, self.out_ann, [1, - ['WARNING: The overdrive mode data sample time interval ' + - '(%2.1fus-%2.1fus) should be inside (1.0us, 2.0us).' - % (time_min * 1000000, time_max * 1000000)]]) - - time_min = float(self.cnt_overdrive_presence) / self.samplerate - time_max = float(self.cnt_overdrive_presence + 1) / self.samplerate - if (time_min < 0.0000073) or (time_max > 0.000010): - self.put(0, 0, self.out_ann, [1, - ['WARNING: The overdrive mode presence sample time interval ' + - '(%2.1fus-%2.1fus) should be inside (7.3us, 10.0us).' - % (time_min*1000000, time_max*1000000)]]) - - def report(self): - pass - - def decode(self, ss, es, data): - for (self.samplenum, (owr, pwr)) in data: - # State machine. - if self.state == 'WAIT FOR FALLING EDGE': - # The start of a cycle is a falling edge. - if owr != 0: - continue - # Save the sample number for the falling edge. - self.fall = self.samplenum - # Go to waiting for sample time. - self.state = 'WAIT FOR DATA SAMPLE' - elif self.state == 'WAIT FOR DATA SAMPLE': - # Sample data bit. - t = self.samplenum - self.fall - if t == self.cnt_bit[self.overdrive]: - self.bit = owr - self.state = 'WAIT FOR DATA SLOT END' - elif self.state == 'WAIT FOR DATA SLOT END': - # A data slot ends in a recovery period, otherwise, this is - # probably a reset. - t = self.samplenum - self.fall - if t != self.cnt_slot[self.overdrive]: - continue - - if owr == 0: - # This seems to be a reset slot, wait for its end. - self.state = 'WAIT FOR RISING EDGE' - continue - - self.put(self.fall, self.samplenum, self.out_ann, - [0, ['Bit: %d' % self.bit]]) - self.put(self.fall, self.samplenum, self.out_proto, - ['BIT', self.bit]) - - # Checking the first command to see if overdrive mode - # should be entered. - if self.bit_cnt <= 8: - self.command |= (self.bit << self.bit_cnt) - elif self.bit_cnt == 8 and self.command in [0x3c, 0x69]: - self.put(self.fall, self.cnt_bit[self.overdrive], - self.out_ann, [0, ['Entering overdrive mode']]) - # Increment the bit counter. - self.bit_cnt += 1 - # Wait for next slot. - self.state = 'WAIT FOR FALLING EDGE' - elif self.state == 'WAIT FOR RISING EDGE': - # The end of a cycle is a rising edge. - if owr != 1: - continue - - # Check if this was a reset cycle. - t = self.samplenum - self.fall - if t > self.cnt_normal_reset: - # Save the sample number for the falling edge. - self.rise = self.samplenum - self.state = 'WAIT FOR PRESENCE DETECT' - # Exit overdrive mode. - if self.overdrive: - self.put(self.fall, self.cnt_bit[self.overdrive], - self.out_ann, [0, ['Exiting overdrive mode']]) - self.overdrive = 0 - # Clear command bit counter and data register. - self.bit_cnt = 0 - self.command = 0 - elif (t > self.cnt_overdrive_reset) and self.overdrive: - # Save the sample number for the falling edge. - self.rise = self.samplenum - self.state = "WAIT FOR PRESENCE DETECT" - # Otherwise this is assumed to be a data bit. - else: - self.state = "WAIT FOR FALLING EDGE" - elif self.state == 'WAIT FOR PRESENCE DETECT': - # Sample presence status. - t = self.samplenum - self.rise - if t == self.cnt_presence[self.overdrive]: - self.present = owr - self.state = 'WAIT FOR RESET SLOT END' - elif self.state == 'WAIT FOR RESET SLOT END': - # A reset slot ends in a long recovery period. - t = self.samplenum - self.rise - if t != self.cnt_reset[self.overdrive]: - continue - - if owr == 0: - # This seems to be a reset slot, wait for its end. - self.state = 'WAIT FOR RISING EDGE' - continue - - self.put(self.fall, self.samplenum, self.out_ann, - [0, ['Reset/presence: %s' - % ('false' if self.present else 'true')]]) - self.put(self.fall, self.samplenum, self.out_proto, - ['RESET/PRESENCE', not self.present]) - # Wait for next slot. - self.state = 'WAIT FOR FALLING EDGE' - else: - raise Exception('Invalid state: %s' % self.state) diff --git a/decoders/onewire_link/pd.py b/decoders/onewire_link/pd.py new file mode 100644 index 0000000..c8bda56 --- /dev/null +++ b/decoders/onewire_link/pd.py @@ -0,0 +1,269 @@ +## +## This file is part of the sigrok project. +## +## Copyright (C) 2012 Iztok Jeras +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + +# 1-Wire protocol decoder (link layer) + +import sigrokdecode as srd + +class Decoder(srd.Decoder): + api_version = 1 + id = 'onewire_link' + name = '1-Wire link layer' + longname = '1-Wire serial communication bus (link layer)' + desc = 'Bidirectional, half-duplex, asynchronous serial bus.' + license = 'gplv2+' + inputs = ['logic'] + outputs = ['onewire_link'] + probes = [ + {'id': 'owr', 'name': 'OWR', 'desc': '1-Wire signal line'}, + ] + optional_probes = [ + {'id': 'pwr', 'name': 'PWR', 'desc': '1-Wire power supply pin'}, + ] + options = { + 'overdrive': ['Overdrive', 1], + # Time options (specified in number of samplerate periods): + 'cnt_normal_bit': ['Normal mode sample bit time', 0], + 'cnt_normal_slot': ['Normal mode data slot time', 0], + 'cnt_normal_presence': ['Normal mode sample presence time', 0], + 'cnt_normal_reset': ['Normal mode reset time', 0], + 'cnt_overdrive_bit': ['Overdrive mode sample bit time', 0], + 'cnt_overdrive_slot': ['Overdrive mode data slot time', 0], + 'cnt_overdrive_presence': ['Overdrive mode sample presence time', 0], + 'cnt_overdrive_reset': ['Overdrive mode reset time', 0], + } + annotations = [ + ['Text', 'Human-readable text'], + ['Warnings', 'Human-readable warnings'], + ] + + def __init__(self, **kwargs): + self.samplenum = 0 + self.state = 'WAIT FOR FALLING EDGE' + self.present = 0 + self.bit = 0 + self.bit_cnt = 0 + self.command = 0 + self.overdrive = 0 + self.fall = 0 + self.rise = 0 + + def start(self, metadata): + self.out_proto = self.add(srd.OUTPUT_PROTO, 'onewire_link') + self.out_ann = self.add(srd.OUTPUT_ANN, 'onewire_link') + + self.samplerate = metadata['samplerate'] + + # Check if samplerate is appropriate. + if self.options['overdrive']: + if self.samplerate < 2000000: + self.put(0, 0, self.out_ann, [1, + ['ERROR: Sampling rate is too low. Must be above 2MHz ' + + 'for proper overdrive mode decoding.']]) + elif self.samplerate < 5000000: + self.put(0, 0, self.out_ann, [1, + ['WARNING: Sampling rate is suggested to be above 5MHz ' + + 'for proper overdrive mode decoding.']]) + else: + if self.samplerate < 400000: + self.put(0, 0, self.out_ann, [1, + ['ERROR: Sampling rate is too low. Must be above ' + + '400kHz for proper normal mode decoding.']]) + elif (self.samplerate < 1000000): + self.put(0, 0, self.out_ann, [1, + ['WARNING: 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) + if self.options['cnt_normal_bit']: + self.cnt_normal_bit = self.options['cnt_normal_bit'] + else: + self.cnt_normal_bit = int(samplerate * 0.000015) - 1 # 15ns + if self.options['cnt_normal_slot']: + self.cnt_normal_slot = self.options['cnt_normal_slot'] + else: + self.cnt_normal_slot = int(samplerate * 0.000060) - 1 # 60ns + if self.options['cnt_normal_presence']: + self.cnt_normal_presence = self.options['cnt_normal_presence'] + else: + self.cnt_normal_presence = int(samplerate * 0.000075) - 1 # 75ns + if self.options['cnt_normal_reset']: + self.cnt_normal_reset = self.options['cnt_normal_reset'] + else: + self.cnt_normal_reset = int(samplerate * 0.000480) - 1 # 480ns + if self.options['cnt_overdrive_bit']: + self.cnt_overdrive_bit = self.options['cnt_overdrive_bit'] + else: + self.cnt_overdrive_bit = int(samplerate * 0.000002) - 1 # 2ns + if self.options['cnt_overdrive_slot']: + self.cnt_overdrive_slot = self.options['cnt_overdrive_slot'] + else: + self.cnt_overdrive_slot = int(samplerate * 0.0000073) - 1 # 6ns+1.3ns + if self.options['cnt_overdrive_presence']: + self.cnt_overdrive_presence = self.options['cnt_overdrive_presence'] + else: + self.cnt_overdrive_presence = int(samplerate * 0.000010) - 1 # 10ns + if self.options['cnt_overdrive_reset']: + self.cnt_overdrive_reset = self.options['cnt_overdrive_reset'] + else: + self.cnt_overdrive_reset = int(samplerate * 0.000048) - 1 # 48ns + + # 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 + time_max = float(self.cnt_normal_bit + 1) / self.samplerate + if (time_min < 0.000005) or (time_max > 0.000015): + self.put(0, 0, self.out_ann, [1, + ['WARNING: The normal mode data sample time interval ' + + '(%2.1fus-%2.1fus) should be inside (5.0us, 15.0us).' + % (time_min * 1000000, time_max * 1000000)]]) + + time_min = float(self.cnt_normal_presence) / self.samplerate + time_max = float(self.cnt_normal_presence + 1) / self.samplerate + if (time_min < 0.0000681) or (time_max > 0.000075): + self.put(0, 0, self.out_ann, [1, + ['WARNING: The normal mode presence sample time interval ' + + '(%2.1fus-%2.1fus) should be inside (68.1us, 75.0us).' + % (time_min * 1000000, time_max * 1000000)]]) + + time_min = float(self.cnt_overdrive_bit) / self.samplerate + time_max = float(self.cnt_overdrive_bit + 1) / self.samplerate + if (time_min < 0.000001) or (time_max > 0.000002): + self.put(0, 0, self.out_ann, [1, + ['WARNING: The overdrive mode data sample time interval ' + + '(%2.1fus-%2.1fus) should be inside (1.0us, 2.0us).' + % (time_min * 1000000, time_max * 1000000)]]) + + time_min = float(self.cnt_overdrive_presence) / self.samplerate + time_max = float(self.cnt_overdrive_presence + 1) / self.samplerate + if (time_min < 0.0000073) or (time_max > 0.000010): + self.put(0, 0, self.out_ann, [1, + ['WARNING: The overdrive mode presence sample time interval ' + + '(%2.1fus-%2.1fus) should be inside (7.3us, 10.0us).' + % (time_min*1000000, time_max*1000000)]]) + + def report(self): + pass + + def decode(self, ss, es, data): + for (self.samplenum, (owr, pwr)) in data: + # State machine. + if self.state == 'WAIT FOR FALLING EDGE': + # The start of a cycle is a falling edge. + if owr != 0: + continue + # Save the sample number for the falling edge. + self.fall = self.samplenum + # Go to waiting for sample time. + self.state = 'WAIT FOR DATA SAMPLE' + elif self.state == 'WAIT FOR DATA SAMPLE': + # Sample data bit. + t = self.samplenum - self.fall + if t == self.cnt_bit[self.overdrive]: + self.bit = owr + self.state = 'WAIT FOR DATA SLOT END' + elif self.state == 'WAIT FOR DATA SLOT END': + # A data slot ends in a recovery period, otherwise, this is + # probably a reset. + t = self.samplenum - self.fall + if t != self.cnt_slot[self.overdrive]: + continue + + if owr == 0: + # This seems to be a reset slot, wait for its end. + self.state = 'WAIT FOR RISING EDGE' + continue + + self.put(self.fall, self.samplenum, self.out_ann, + [0, ['Bit: %d' % self.bit]]) + self.put(self.fall, self.samplenum, self.out_proto, + ['BIT', self.bit]) + + # Checking the first command to see if overdrive mode + # should be entered. + if self.bit_cnt <= 8: + self.command |= (self.bit << self.bit_cnt) + elif self.bit_cnt == 8 and self.command in [0x3c, 0x69]: + self.put(self.fall, self.cnt_bit[self.overdrive], + self.out_ann, [0, ['Entering overdrive mode']]) + # Increment the bit counter. + self.bit_cnt += 1 + # Wait for next slot. + self.state = 'WAIT FOR FALLING EDGE' + elif self.state == 'WAIT FOR RISING EDGE': + # The end of a cycle is a rising edge. + if owr != 1: + continue + + # Check if this was a reset cycle. + t = self.samplenum - self.fall + if t > self.cnt_normal_reset: + # Save the sample number for the falling edge. + self.rise = self.samplenum + self.state = 'WAIT FOR PRESENCE DETECT' + # Exit overdrive mode. + if self.overdrive: + self.put(self.fall, self.cnt_bit[self.overdrive], + self.out_ann, [0, ['Exiting overdrive mode']]) + self.overdrive = 0 + # Clear command bit counter and data register. + self.bit_cnt = 0 + self.command = 0 + elif (t > self.cnt_overdrive_reset) and self.overdrive: + # Save the sample number for the falling edge. + self.rise = self.samplenum + self.state = "WAIT FOR PRESENCE DETECT" + # Otherwise this is assumed to be a data bit. + else: + self.state = "WAIT FOR FALLING EDGE" + elif self.state == 'WAIT FOR PRESENCE DETECT': + # Sample presence status. + t = self.samplenum - self.rise + if t == self.cnt_presence[self.overdrive]: + self.present = owr + self.state = 'WAIT FOR RESET SLOT END' + elif self.state == 'WAIT FOR RESET SLOT END': + # A reset slot ends in a long recovery period. + t = self.samplenum - self.rise + if t != self.cnt_reset[self.overdrive]: + continue + + if owr == 0: + # This seems to be a reset slot, wait for its end. + self.state = 'WAIT FOR RISING EDGE' + continue + + self.put(self.fall, self.samplenum, self.out_ann, + [0, ['Reset/presence: %s' + % ('false' if self.present else 'true')]]) + self.put(self.fall, self.samplenum, self.out_proto, + ['RESET/PRESENCE', not self.present]) + # Wait for next slot. + self.state = 'WAIT FOR FALLING EDGE' + else: + raise Exception('Invalid state: %s' % self.state) diff --git a/decoders/onewire_network/Makefile.am b/decoders/onewire_network/Makefile.am index 2484987..d0a84a8 100644 --- a/decoders/onewire_network/Makefile.am +++ b/decoders/onewire_network/Makefile.am @@ -20,7 +20,7 @@ pkgdatadir = $(DECODERS_DIR)/onewire_network -dist_pkgdata_DATA = __init__.py onewire_network.py +dist_pkgdata_DATA = __init__.py pd.py CLEANFILES = *.pyc diff --git a/decoders/onewire_network/__init__.py b/decoders/onewire_network/__init__.py index 25b3f5d..74b9f4b 100644 --- a/decoders/onewire_network/__init__.py +++ b/decoders/onewire_network/__init__.py @@ -58,5 +58,5 @@ TODO: - Add reporting original/complement address values from the search algorithm. ''' -from .onewire_network import * +from .pd import * diff --git a/decoders/onewire_network/onewire_network.py b/decoders/onewire_network/onewire_network.py deleted file mode 100644 index ab11ea4..0000000 --- a/decoders/onewire_network/onewire_network.py +++ /dev/null @@ -1,190 +0,0 @@ -## -## This file is part of the sigrok project. -## -## Copyright (C) 2012 Iztok Jeras -## -## This program is free software; you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published by -## the Free Software Foundation; either version 2 of the License, or -## (at your option) any later version. -## -## This program is distributed in the hope that it will be useful, -## but WITHOUT ANY WARRANTY; without even the implied warranty of -## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -## GNU General Public License for more details. -## -## You should have received a copy of the GNU General Public License -## along with this program; if not, write to the Free Software -## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -## - -# 1-Wire protocol decoder (network layer) - -import sigrokdecode as srd - -# Dictionary of ROM commands and their names, next state. -command = { - 0x33: ['Read ROM' , 'GET ROM' ], - 0x0f: ['Conditional read ROM' , 'GET ROM' ], - 0xcc: ['Skip ROM' , 'TRANSPORT' ], - 0x55: ['Match ROM' , 'GET ROM' ], - 0xf0: ['Search ROM' , 'SEARCH ROM'], - 0xec: ['Conditional search ROM', 'SEARCH ROM'], - 0x3c: ['Overdrive skip ROM' , 'TRANSPORT' ], - 0x69: ['Overdrive match ROM' , 'GET ROM' ], -} - -class Decoder(srd.Decoder): - api_version = 1 - id = 'onewire_network' - name = '1-Wire network layer' - longname = '1-Wire serial communication bus (network layer)' - desc = 'Bidirectional, half-duplex, asynchronous serial bus.' - license = 'gplv2+' - inputs = ['onewire_link'] - outputs = ['onewire_network'] - probes = [] - optional_probes = [] - options = {} - annotations = [ - ['Text', 'Human-readable text'], - ] - - def __init__(self, **kwargs): - self.beg = 0 - self.end = 0 - self.state = 'COMMAND' - self.bit_cnt = 0 - self.search = 'P' - self.data_p = 0x0 - self.data_n = 0x0 - self.data = 0x0 - self.rom = 0x0000000000000000 - - def start(self, metadata): - self.out_proto = self.add(srd.OUTPUT_PROTO, 'onewire_network') - self.out_ann = self.add(srd.OUTPUT_ANN, 'onewire_network') - - def report(self): - pass - - def putx(self, data): - # Helper function for most annotations. - self.put(self.beg, self.end, self.out_ann, data) - - def puty(self, data): - # Helper function for most protocol packets. - self.put(self.beg, self.end, self.out_proto, data) - - def decode(self, ss, es, data): - code, val = data - - # State machine. - if code == 'RESET/PRESENCE': - self.search = 'P' - self.bit_cnt = 0 - self.put(ss, es, self.out_ann, - [0, ['Reset/presence: %s' % ('true' if val else 'false')]]) - self.put(ss, es, self.out_proto, ['RESET/PRESENCE', val]) - self.state = 'COMMAND' - return - - # For now we're only interested in 'RESET/PRESENCE' and 'BIT' packets. - if code != 'BIT': - return - - if self.state == 'COMMAND': - # Receiving and decoding a ROM command. - if self.onewire_collect(8, val, ss, es) == 0: - return - if self.data in command: - self.putx([0, ['ROM command: 0x%02x \'%s\'' - % (self.data, command[self.data][0])]]) - self.state = command[self.data][1] - else: - self.putx([0, ['ROM command: 0x%02x \'%s\'' - % (self.data, 'unrecognized')]]) - self.state = 'COMMAND ERROR' - elif self.state == 'GET ROM': - # A 64 bit device address is selected. - # Family code (1 byte) + serial number (6 bytes) + CRC (1 byte) - if self.onewire_collect(64, val, ss, es) == 0: - return - self.rom = self.data & 0xffffffffffffffff - self.putx([0, ['ROM: 0x%016x' % self.rom]]) - self.puty(['ROM', self.rom]) - self.state = 'TRANSPORT' - elif self.state == 'SEARCH ROM': - # A 64 bit device address is searched for. - # Family code (1 byte) + serial number (6 bytes) + CRC (1 byte) - if self.onewire_search(64, val, ss, es) == 0: - return - self.rom = self.data & 0xffffffffffffffff - self.putx([0, ['ROM: 0x%016x' % self.rom]]) - self.puty(['ROM', self.rom]) - self.state = 'TRANSPORT' - elif self.state == 'TRANSPORT': - # The transport layer is handled in byte sized units. - if self.onewire_collect(8, val, ss, es) == 0: - return - self.putx([0, ['Data: 0x%02x' % self.data]]) - self.puty(['DATA', self.data]) - elif self.state == 'COMMAND ERROR': - # Since the command is not recognized, print raw data. - if self.onewire_collect(8, val, ss, es) == 0: - return - self.putx([0, ['ROM error data: 0x%02x' % self.data]]) - 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.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.data = self.data & ((1 << length) - 1) - self.bit_cnt = 0 - return 1 - else: - return 0 - - # Search collector. - def onewire_search(self, length, val, ss, es): - # Storing the sample this sequence begins with. - if (self.bit_cnt == 0) and (self.search == 'P'): - self.beg = ss - - if self.search == 'P': - # Master receives an original address bit. - self.data_p = self.data_p & ~(1 << self.bit_cnt) | \ - (val << self.bit_cnt) - self.search = 'N' - elif self.search == 'N': - # Master receives a complemented address bit. - self.data_n = self.data_n & ~(1 << self.bit_cnt) | \ - (val << self.bit_cnt) - self.search = 'D' - elif self.search == 'D': - # Master transmits an address bit. - self.data = self.data & ~(1 << self.bit_cnt) | (val << self.bit_cnt) - self.search = 'P' - self.bit_cnt += 1 - - # Storing the sample this sequence ends with. - # In case the full length of the sequence is received, return 1. - if self.bit_cnt == length: - self.end = es - self.data_p = self.data_p & ((1 << length) - 1) - self.data_n = self.data_n & ((1 << length) - 1) - self.data = self.data & ((1 << length) - 1) - self.search = 'P' - self.bit_cnt = 0 - return 1 - else: - return 0 diff --git a/decoders/onewire_network/pd.py b/decoders/onewire_network/pd.py new file mode 100644 index 0000000..ab11ea4 --- /dev/null +++ b/decoders/onewire_network/pd.py @@ -0,0 +1,190 @@ +## +## This file is part of the sigrok project. +## +## Copyright (C) 2012 Iztok Jeras +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + +# 1-Wire protocol decoder (network layer) + +import sigrokdecode as srd + +# Dictionary of ROM commands and their names, next state. +command = { + 0x33: ['Read ROM' , 'GET ROM' ], + 0x0f: ['Conditional read ROM' , 'GET ROM' ], + 0xcc: ['Skip ROM' , 'TRANSPORT' ], + 0x55: ['Match ROM' , 'GET ROM' ], + 0xf0: ['Search ROM' , 'SEARCH ROM'], + 0xec: ['Conditional search ROM', 'SEARCH ROM'], + 0x3c: ['Overdrive skip ROM' , 'TRANSPORT' ], + 0x69: ['Overdrive match ROM' , 'GET ROM' ], +} + +class Decoder(srd.Decoder): + api_version = 1 + id = 'onewire_network' + name = '1-Wire network layer' + longname = '1-Wire serial communication bus (network layer)' + desc = 'Bidirectional, half-duplex, asynchronous serial bus.' + license = 'gplv2+' + inputs = ['onewire_link'] + outputs = ['onewire_network'] + probes = [] + optional_probes = [] + options = {} + annotations = [ + ['Text', 'Human-readable text'], + ] + + def __init__(self, **kwargs): + self.beg = 0 + self.end = 0 + self.state = 'COMMAND' + self.bit_cnt = 0 + self.search = 'P' + self.data_p = 0x0 + self.data_n = 0x0 + self.data = 0x0 + self.rom = 0x0000000000000000 + + def start(self, metadata): + self.out_proto = self.add(srd.OUTPUT_PROTO, 'onewire_network') + self.out_ann = self.add(srd.OUTPUT_ANN, 'onewire_network') + + def report(self): + pass + + def putx(self, data): + # Helper function for most annotations. + self.put(self.beg, self.end, self.out_ann, data) + + def puty(self, data): + # Helper function for most protocol packets. + self.put(self.beg, self.end, self.out_proto, data) + + def decode(self, ss, es, data): + code, val = data + + # State machine. + if code == 'RESET/PRESENCE': + self.search = 'P' + self.bit_cnt = 0 + self.put(ss, es, self.out_ann, + [0, ['Reset/presence: %s' % ('true' if val else 'false')]]) + self.put(ss, es, self.out_proto, ['RESET/PRESENCE', val]) + self.state = 'COMMAND' + return + + # For now we're only interested in 'RESET/PRESENCE' and 'BIT' packets. + if code != 'BIT': + return + + if self.state == 'COMMAND': + # Receiving and decoding a ROM command. + if self.onewire_collect(8, val, ss, es) == 0: + return + if self.data in command: + self.putx([0, ['ROM command: 0x%02x \'%s\'' + % (self.data, command[self.data][0])]]) + self.state = command[self.data][1] + else: + self.putx([0, ['ROM command: 0x%02x \'%s\'' + % (self.data, 'unrecognized')]]) + self.state = 'COMMAND ERROR' + elif self.state == 'GET ROM': + # A 64 bit device address is selected. + # Family code (1 byte) + serial number (6 bytes) + CRC (1 byte) + if self.onewire_collect(64, val, ss, es) == 0: + return + self.rom = self.data & 0xffffffffffffffff + self.putx([0, ['ROM: 0x%016x' % self.rom]]) + self.puty(['ROM', self.rom]) + self.state = 'TRANSPORT' + elif self.state == 'SEARCH ROM': + # A 64 bit device address is searched for. + # Family code (1 byte) + serial number (6 bytes) + CRC (1 byte) + if self.onewire_search(64, val, ss, es) == 0: + return + self.rom = self.data & 0xffffffffffffffff + self.putx([0, ['ROM: 0x%016x' % self.rom]]) + self.puty(['ROM', self.rom]) + self.state = 'TRANSPORT' + elif self.state == 'TRANSPORT': + # The transport layer is handled in byte sized units. + if self.onewire_collect(8, val, ss, es) == 0: + return + self.putx([0, ['Data: 0x%02x' % self.data]]) + self.puty(['DATA', self.data]) + elif self.state == 'COMMAND ERROR': + # Since the command is not recognized, print raw data. + if self.onewire_collect(8, val, ss, es) == 0: + return + self.putx([0, ['ROM error data: 0x%02x' % self.data]]) + 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.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.data = self.data & ((1 << length) - 1) + self.bit_cnt = 0 + return 1 + else: + return 0 + + # Search collector. + def onewire_search(self, length, val, ss, es): + # Storing the sample this sequence begins with. + if (self.bit_cnt == 0) and (self.search == 'P'): + self.beg = ss + + if self.search == 'P': + # Master receives an original address bit. + self.data_p = self.data_p & ~(1 << self.bit_cnt) | \ + (val << self.bit_cnt) + self.search = 'N' + elif self.search == 'N': + # Master receives a complemented address bit. + self.data_n = self.data_n & ~(1 << self.bit_cnt) | \ + (val << self.bit_cnt) + self.search = 'D' + elif self.search == 'D': + # Master transmits an address bit. + self.data = self.data & ~(1 << self.bit_cnt) | (val << self.bit_cnt) + self.search = 'P' + self.bit_cnt += 1 + + # Storing the sample this sequence ends with. + # In case the full length of the sequence is received, return 1. + if self.bit_cnt == length: + self.end = es + self.data_p = self.data_p & ((1 << length) - 1) + self.data_n = self.data_n & ((1 << length) - 1) + self.data = self.data & ((1 << length) - 1) + self.search = 'P' + self.bit_cnt = 0 + return 1 + else: + return 0 diff --git a/decoders/pan1321/Makefile.am b/decoders/pan1321/Makefile.am index 6997ed5..5c5913e 100644 --- a/decoders/pan1321/Makefile.am +++ b/decoders/pan1321/Makefile.am @@ -20,7 +20,7 @@ pkgdatadir = $(DECODERS_DIR)/pan1321 -dist_pkgdata_DATA = __init__.py pan1321.py +dist_pkgdata_DATA = __init__.py pd.py CLEANFILES = *.pyc diff --git a/decoders/pan1321/__init__.py b/decoders/pan1321/__init__.py index 155d961..df48767 100644 --- a/decoders/pan1321/__init__.py +++ b/decoders/pan1321/__init__.py @@ -25,5 +25,5 @@ Details: TODO ''' -from .pan1321 import * +from .pd import * diff --git a/decoders/pan1321/pan1321.py b/decoders/pan1321/pan1321.py deleted file mode 100644 index 827269d..0000000 --- a/decoders/pan1321/pan1321.py +++ /dev/null @@ -1,118 +0,0 @@ -## -## This file is part of the sigrok 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 -## - -# Panasonic PAN1321 Bluetooth module protocol decoder - -import sigrokdecode as srd - -# ... -RX = 0 -TX = 1 - -class Decoder(srd.Decoder): - api_version = 1 - id = 'pan1321' - name = 'PAN1321' - longname = 'Panasonic PAN1321' - desc = 'Bluetooth RF module with Serial Port Profile (SPP).' - license = 'gplv2+' - inputs = ['uart'] - outputs = ['pan1321'] - probes = [] - optional_probes = [] - options = {} - annotations = [ - ['Text (verbose)', 'Human-readable text (verbose)'], - ['Text', 'Human-readable text'], - ] - - def __init__(self, **kwargs): - self.cmd = ['', ''] - self.ss_block = None - - def start(self, metadata): - # self.out_proto = self.add(srd.OUTPUT_PROTO, 'pan1321') - self.out_ann = self.add(srd.OUTPUT_ANN, 'pan1321') - - def report(self): - pass - - def putx(self, data): - self.put(self.ss_block, self.es_block, self.out_ann, data) - - def handle_host_command(self, rxtx, s): - if s.startswith('AT+JSEC'): - pin = s[-4:] - self.putx([0, ['Host set the Bluetooth PIN to ' + pin]]) - self.putx([1, ['PIN = ' + pin]]) - elif s.startswith('AT+JSLN'): - name = s[s.find(',') + 1:] - self.putx([0, ['Host set the Bluetooth name to ' + name]]) - self.putx([1, ['BT name = ' + name]]) - else: - self.putx([0, ['Host sent unsupported command: %s' % s]]) - self.putx([1, ['Unsupported command: %s' % s]]) - self.cmd[rxtx] = '' - - def handle_device_reply(self, rxtx, s): - if s == 'ROK': - self.putx([0, ['Device initialized correctly']]) - self.putx([1, ['Init']]) - elif s == 'OK': - self.putx([0, ['Device acknowledged last command']]) - self.putx([1, ['ACK']]) - elif s.startswith('ERR'): - error = s[s.find('=') + 1:] - self.putx([0, ['Device sent error code ' + error]]) - self.putx([1, ['ERR = ' + error]]) - else: - self.putx([0, ['Device sent an unknown reply: %s' % s]]) - self.putx([1, ['Unknown reply: %s' % s]]) - self.cmd[rxtx] = '' - - 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 - - # If this is the start of a command/reply, remember the start sample. - if self.cmd[rxtx] == '': - self.ss_block = ss - - # Append a new (ASCII) byte to the currently built/parsed command. - self.cmd[rxtx] += chr(pdata) - - # Get packets/bytes until an \r\n sequence is found (end of command). - if self.cmd[rxtx][-1:] != '\n': - return - - # Handle host commands and device replies. - # We remove trailing \r\n from the strings before handling them. - if rxtx == RX: - self.es_block = es - self.handle_device_reply(rxtx, self.cmd[rxtx][:-2]) - elif rxtx == TX: - self.es_block = es - self.handle_host_command(rxtx, self.cmd[rxtx][:-2]) - else: - raise Exception('Invalid rxtx value: %d' % rxtx) - diff --git a/decoders/pan1321/pd.py b/decoders/pan1321/pd.py new file mode 100644 index 0000000..827269d --- /dev/null +++ b/decoders/pan1321/pd.py @@ -0,0 +1,118 @@ +## +## This file is part of the sigrok 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 +## + +# Panasonic PAN1321 Bluetooth module protocol decoder + +import sigrokdecode as srd + +# ... +RX = 0 +TX = 1 + +class Decoder(srd.Decoder): + api_version = 1 + id = 'pan1321' + name = 'PAN1321' + longname = 'Panasonic PAN1321' + desc = 'Bluetooth RF module with Serial Port Profile (SPP).' + license = 'gplv2+' + inputs = ['uart'] + outputs = ['pan1321'] + probes = [] + optional_probes = [] + options = {} + annotations = [ + ['Text (verbose)', 'Human-readable text (verbose)'], + ['Text', 'Human-readable text'], + ] + + def __init__(self, **kwargs): + self.cmd = ['', ''] + self.ss_block = None + + def start(self, metadata): + # self.out_proto = self.add(srd.OUTPUT_PROTO, 'pan1321') + self.out_ann = self.add(srd.OUTPUT_ANN, 'pan1321') + + def report(self): + pass + + def putx(self, data): + self.put(self.ss_block, self.es_block, self.out_ann, data) + + def handle_host_command(self, rxtx, s): + if s.startswith('AT+JSEC'): + pin = s[-4:] + self.putx([0, ['Host set the Bluetooth PIN to ' + pin]]) + self.putx([1, ['PIN = ' + pin]]) + elif s.startswith('AT+JSLN'): + name = s[s.find(',') + 1:] + self.putx([0, ['Host set the Bluetooth name to ' + name]]) + self.putx([1, ['BT name = ' + name]]) + else: + self.putx([0, ['Host sent unsupported command: %s' % s]]) + self.putx([1, ['Unsupported command: %s' % s]]) + self.cmd[rxtx] = '' + + def handle_device_reply(self, rxtx, s): + if s == 'ROK': + self.putx([0, ['Device initialized correctly']]) + self.putx([1, ['Init']]) + elif s == 'OK': + self.putx([0, ['Device acknowledged last command']]) + self.putx([1, ['ACK']]) + elif s.startswith('ERR'): + error = s[s.find('=') + 1:] + self.putx([0, ['Device sent error code ' + error]]) + self.putx([1, ['ERR = ' + error]]) + else: + self.putx([0, ['Device sent an unknown reply: %s' % s]]) + self.putx([1, ['Unknown reply: %s' % s]]) + self.cmd[rxtx] = '' + + 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 + + # If this is the start of a command/reply, remember the start sample. + if self.cmd[rxtx] == '': + self.ss_block = ss + + # Append a new (ASCII) byte to the currently built/parsed command. + self.cmd[rxtx] += chr(pdata) + + # Get packets/bytes until an \r\n sequence is found (end of command). + if self.cmd[rxtx][-1:] != '\n': + return + + # Handle host commands and device replies. + # We remove trailing \r\n from the strings before handling them. + if rxtx == RX: + self.es_block = es + self.handle_device_reply(rxtx, self.cmd[rxtx][:-2]) + elif rxtx == TX: + self.es_block = es + self.handle_host_command(rxtx, self.cmd[rxtx][:-2]) + else: + raise Exception('Invalid rxtx value: %d' % rxtx) + diff --git a/decoders/rtc8564/Makefile.am b/decoders/rtc8564/Makefile.am index 581eb5f..8757b9c 100644 --- a/decoders/rtc8564/Makefile.am +++ b/decoders/rtc8564/Makefile.am @@ -20,7 +20,7 @@ pkgdatadir = $(DECODERS_DIR)/rtc8564 -dist_pkgdata_DATA = __init__.py rtc8564.py +dist_pkgdata_DATA = __init__.py pd.py CLEANFILES = *.pyc diff --git a/decoders/rtc8564/__init__.py b/decoders/rtc8564/__init__.py index 5653453..94fdaea 100644 --- a/decoders/rtc8564/__init__.py +++ b/decoders/rtc8564/__init__.py @@ -25,5 +25,5 @@ Details: TODO ''' -from .rtc8564 import * +from .pd import * diff --git a/decoders/rtc8564/pd.py b/decoders/rtc8564/pd.py new file mode 100644 index 0000000..bf5d4b6 --- /dev/null +++ b/decoders/rtc8564/pd.py @@ -0,0 +1,216 @@ +## +## This file is part of the sigrok 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 +## + +# Epson RTC-8564 JE/NB protocol decoder + +import sigrokdecode as srd + +# Return the specified BCD number (max. 8 bits) as integer. +def bcd2int(b): + return (b & 0x0f) + ((b >> 4) * 10) + +class Decoder(srd.Decoder): + api_version = 1 + id = 'rtc8564' + name = 'RTC-8564' + longname = 'Epson RTC-8564 JE/NB' + desc = 'Realtime clock module protocol.' + license = 'gplv2+' + inputs = ['i2c'] + outputs = ['rtc8564'] + probes = [] + optional_probes = [ + {'id': 'clkout', 'name': 'CLKOUT', 'desc': 'TODO.'}, + {'id': 'clkoe', 'name': 'CLKOE', 'desc': 'TODO.'}, + {'id': 'int', 'name': 'INT#', 'desc': 'TODO.'}, + ] + options = {} + annotations = [ + ['Text', 'Human-readable text'], + ] + + def __init__(self, **kwargs): + self.state = 'IDLE' + self.hours = -1 + self.minutes = -1 + self.seconds = -1 + self.days = -1 + self.months = -1 + self.years = -1 + + def start(self, metadata): + # self.out_proto = self.add(srd.OUTPUT_PROTO, 'rtc8564') + self.out_ann = self.add(srd.OUTPUT_ANN, 'rtc8564') + + def report(self): + pass + + def putx(self, data): + self.put(self.ss, self.es, self.out_ann, data) + + def handle_reg_0x00(self, b): # Control register 1 + pass + + def handle_reg_0x01(self, b): # Control register 2 + ti_tp = 1 if (b & (1 << 4)) else 0 + af = 1 if (b & (1 << 3)) else 0 + tf = 1 if (b & (1 << 2)) else 0 + aie = 1 if (b & (1 << 1)) else 0 + tie = 1 if (b & (1 << 0)) else 0 + + ann = '' + + s = 'repeated' if ti_tp else 'single-shot' + ann += 'TI/TP = %d: %s operation upon fixed-cycle timer interrupt '\ + 'events\n' % (ti_tp, s) + s = '' if af else 'no ' + ann += 'AF = %d: %salarm interrupt detected\n' % (af, s) + s = '' if tf else 'no ' + ann += 'TF = %d: %sfixed-cycle timer interrupt detected\n' % (tf, s) + s = 'enabled' if aie else 'prohibited' + ann += 'AIE = %d: INT# pin output %s when an alarm interrupt '\ + 'occurs\n' % (aie, s) + s = 'enabled' if tie else 'prohibited' + ann += 'TIE = %d: INT# pin output %s when a fixed-cycle interrupt '\ + 'event occurs\n' % (tie, s) + + self.putx([0, [ann]]) + + def handle_reg_0x02(self, b): # Seconds / Voltage-low flag + self.seconds = bcd2int(b & 0x7f) + self.putx([0, ['Seconds: %d' % self.seconds]]) + vl = 1 if (b & (1 << 7)) else 0 + self.putx([0, ['Voltage low (VL) bit: %d' % vl]]) + + def handle_reg_0x03(self, b): # Minutes + self.minutes = bcd2int(b & 0x7f) + self.putx([0, ['Minutes: %d' % self.minutes]]) + + def handle_reg_0x04(self, b): # Hours + self.hours = bcd2int(b & 0x3f) + self.putx([0, ['Hours: %d' % self.hours]]) + + def handle_reg_0x05(self, b): # Days + self.days = bcd2int(b & 0x3f) + self.putx([0, ['Days: %d' % self.days]]) + + def handle_reg_0x06(self, b): # Day counter + pass + + def handle_reg_0x07(self, b): # Months / century + # TODO: Handle century bit. + self.months = bcd2int(b & 0x1f) + self.putx([0, ['Months: %d' % self.months]]) + + def handle_reg_0x08(self, b): # Years + self.years = bcd2int(b & 0xff) + self.putx([0, ['Years: %d' % self.years]]) + + def handle_reg_0x09(self, b): # Alarm, minute + pass + + def handle_reg_0x0a(self, b): # Alarm, hour + pass + + def handle_reg_0x0b(self, b): # Alarm, day + pass + + def handle_reg_0x0c(self, b): # Alarm, weekday + pass + + def handle_reg_0x0d(self, b): # CLKOUT output + pass + + def handle_reg_0x0e(self, b): # Timer setting + pass + + def handle_reg_0x0f(self, b): # Down counter for fixed-cycle timer + pass + + def decode(self, ss, es, data): + cmd, databyte = data + + # Store the start/end samples of this I2C packet. + self.ss, self.es = ss, es + + # State machine. + if self.state == 'IDLE': + # Wait for an I2C START condition. + 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 RTC slave (0xa2/0xa3). + if cmd != 'ADDRESS WRITE': + return + self.state = 'GET REG ADDR' + elif self.state == 'GET REG ADDR': + # Wait for a data write (master selects the slave register). + if cmd != 'DATA WRITE': + return + self.reg = databyte + self.state = 'WRITE RTC REGS' + elif self.state == 'WRITE RTC REGS': + # If we see a Repeated Start here, it's probably an RTC read. + if cmd == 'START REPEAT': + self.state = 'READ RTC REGS' + return + # Otherwise: Get data bytes until a STOP condition occurs. + if cmd == 'DATA WRITE': + handle_reg = getattr(self, 'handle_reg_0x%02x' % self.reg) + handle_reg(databyte) + self.reg += 1 + # TODO: Check for NACK! + elif cmd == 'STOP': + # TODO: Handle read/write of only parts of these items. + d = '%02d.%02d.%02d %02d:%02d:%02d' % (self.days, self.months, + self.years, self.hours, self.minutes, self.seconds) + self.put(self.block_start_sample, es, self.out_ann, + [0, ['Written date/time: %s' % d]]) + self.state = 'IDLE' + else: + pass # TODO + elif self.state == 'READ RTC REGS': + # Wait for an address read operation. + # TODO: We should only handle packets to the RTC slave (0xa2/0xa3). + if cmd == 'ADDRESS READ': + self.state = 'READ RTC REGS2' + return + else: + pass # TODO + elif self.state == 'READ RTC REGS2': + if cmd == 'DATA READ': + handle_reg = getattr(self, 'handle_reg_0x%02x' % self.reg) + handle_reg(databyte) + self.reg += 1 + # TODO: Check for NACK! + elif cmd == 'STOP': + d = '%02d.%02d.%02d %02d:%02d:%02d' % (self.days, self.months, + self.years, self.hours, self.minutes, self.seconds) + self.put(self.block_start_sample, es, self.out_ann, + [0, ['Read date/time: %s' % d]]) + self.state = 'IDLE' + else: + pass # TODO? + else: + raise Exception('Invalid state: %d' % self.state) + diff --git a/decoders/rtc8564/rtc8564.py b/decoders/rtc8564/rtc8564.py deleted file mode 100644 index bf5d4b6..0000000 --- a/decoders/rtc8564/rtc8564.py +++ /dev/null @@ -1,216 +0,0 @@ -## -## This file is part of the sigrok 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 -## - -# Epson RTC-8564 JE/NB protocol decoder - -import sigrokdecode as srd - -# Return the specified BCD number (max. 8 bits) as integer. -def bcd2int(b): - return (b & 0x0f) + ((b >> 4) * 10) - -class Decoder(srd.Decoder): - api_version = 1 - id = 'rtc8564' - name = 'RTC-8564' - longname = 'Epson RTC-8564 JE/NB' - desc = 'Realtime clock module protocol.' - license = 'gplv2+' - inputs = ['i2c'] - outputs = ['rtc8564'] - probes = [] - optional_probes = [ - {'id': 'clkout', 'name': 'CLKOUT', 'desc': 'TODO.'}, - {'id': 'clkoe', 'name': 'CLKOE', 'desc': 'TODO.'}, - {'id': 'int', 'name': 'INT#', 'desc': 'TODO.'}, - ] - options = {} - annotations = [ - ['Text', 'Human-readable text'], - ] - - def __init__(self, **kwargs): - self.state = 'IDLE' - self.hours = -1 - self.minutes = -1 - self.seconds = -1 - self.days = -1 - self.months = -1 - self.years = -1 - - def start(self, metadata): - # self.out_proto = self.add(srd.OUTPUT_PROTO, 'rtc8564') - self.out_ann = self.add(srd.OUTPUT_ANN, 'rtc8564') - - def report(self): - pass - - def putx(self, data): - self.put(self.ss, self.es, self.out_ann, data) - - def handle_reg_0x00(self, b): # Control register 1 - pass - - def handle_reg_0x01(self, b): # Control register 2 - ti_tp = 1 if (b & (1 << 4)) else 0 - af = 1 if (b & (1 << 3)) else 0 - tf = 1 if (b & (1 << 2)) else 0 - aie = 1 if (b & (1 << 1)) else 0 - tie = 1 if (b & (1 << 0)) else 0 - - ann = '' - - s = 'repeated' if ti_tp else 'single-shot' - ann += 'TI/TP = %d: %s operation upon fixed-cycle timer interrupt '\ - 'events\n' % (ti_tp, s) - s = '' if af else 'no ' - ann += 'AF = %d: %salarm interrupt detected\n' % (af, s) - s = '' if tf else 'no ' - ann += 'TF = %d: %sfixed-cycle timer interrupt detected\n' % (tf, s) - s = 'enabled' if aie else 'prohibited' - ann += 'AIE = %d: INT# pin output %s when an alarm interrupt '\ - 'occurs\n' % (aie, s) - s = 'enabled' if tie else 'prohibited' - ann += 'TIE = %d: INT# pin output %s when a fixed-cycle interrupt '\ - 'event occurs\n' % (tie, s) - - self.putx([0, [ann]]) - - def handle_reg_0x02(self, b): # Seconds / Voltage-low flag - self.seconds = bcd2int(b & 0x7f) - self.putx([0, ['Seconds: %d' % self.seconds]]) - vl = 1 if (b & (1 << 7)) else 0 - self.putx([0, ['Voltage low (VL) bit: %d' % vl]]) - - def handle_reg_0x03(self, b): # Minutes - self.minutes = bcd2int(b & 0x7f) - self.putx([0, ['Minutes: %d' % self.minutes]]) - - def handle_reg_0x04(self, b): # Hours - self.hours = bcd2int(b & 0x3f) - self.putx([0, ['Hours: %d' % self.hours]]) - - def handle_reg_0x05(self, b): # Days - self.days = bcd2int(b & 0x3f) - self.putx([0, ['Days: %d' % self.days]]) - - def handle_reg_0x06(self, b): # Day counter - pass - - def handle_reg_0x07(self, b): # Months / century - # TODO: Handle century bit. - self.months = bcd2int(b & 0x1f) - self.putx([0, ['Months: %d' % self.months]]) - - def handle_reg_0x08(self, b): # Years - self.years = bcd2int(b & 0xff) - self.putx([0, ['Years: %d' % self.years]]) - - def handle_reg_0x09(self, b): # Alarm, minute - pass - - def handle_reg_0x0a(self, b): # Alarm, hour - pass - - def handle_reg_0x0b(self, b): # Alarm, day - pass - - def handle_reg_0x0c(self, b): # Alarm, weekday - pass - - def handle_reg_0x0d(self, b): # CLKOUT output - pass - - def handle_reg_0x0e(self, b): # Timer setting - pass - - def handle_reg_0x0f(self, b): # Down counter for fixed-cycle timer - pass - - def decode(self, ss, es, data): - cmd, databyte = data - - # Store the start/end samples of this I2C packet. - self.ss, self.es = ss, es - - # State machine. - if self.state == 'IDLE': - # Wait for an I2C START condition. - 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 RTC slave (0xa2/0xa3). - if cmd != 'ADDRESS WRITE': - return - self.state = 'GET REG ADDR' - elif self.state == 'GET REG ADDR': - # Wait for a data write (master selects the slave register). - if cmd != 'DATA WRITE': - return - self.reg = databyte - self.state = 'WRITE RTC REGS' - elif self.state == 'WRITE RTC REGS': - # If we see a Repeated Start here, it's probably an RTC read. - if cmd == 'START REPEAT': - self.state = 'READ RTC REGS' - return - # Otherwise: Get data bytes until a STOP condition occurs. - if cmd == 'DATA WRITE': - handle_reg = getattr(self, 'handle_reg_0x%02x' % self.reg) - handle_reg(databyte) - self.reg += 1 - # TODO: Check for NACK! - elif cmd == 'STOP': - # TODO: Handle read/write of only parts of these items. - d = '%02d.%02d.%02d %02d:%02d:%02d' % (self.days, self.months, - self.years, self.hours, self.minutes, self.seconds) - self.put(self.block_start_sample, es, self.out_ann, - [0, ['Written date/time: %s' % d]]) - self.state = 'IDLE' - else: - pass # TODO - elif self.state == 'READ RTC REGS': - # Wait for an address read operation. - # TODO: We should only handle packets to the RTC slave (0xa2/0xa3). - if cmd == 'ADDRESS READ': - self.state = 'READ RTC REGS2' - return - else: - pass # TODO - elif self.state == 'READ RTC REGS2': - if cmd == 'DATA READ': - handle_reg = getattr(self, 'handle_reg_0x%02x' % self.reg) - handle_reg(databyte) - self.reg += 1 - # TODO: Check for NACK! - elif cmd == 'STOP': - d = '%02d.%02d.%02d %02d:%02d:%02d' % (self.days, self.months, - self.years, self.hours, self.minutes, self.seconds) - self.put(self.block_start_sample, es, self.out_ann, - [0, ['Read date/time: %s' % d]]) - self.state = 'IDLE' - else: - pass # TODO? - else: - raise Exception('Invalid state: %d' % self.state) - diff --git a/decoders/spi/Makefile.am b/decoders/spi/Makefile.am index 89bf640..0ab762e 100644 --- a/decoders/spi/Makefile.am +++ b/decoders/spi/Makefile.am @@ -20,7 +20,7 @@ pkgdatadir = $(DECODERS_DIR)/spi -dist_pkgdata_DATA = __init__.py spi.py +dist_pkgdata_DATA = __init__.py pd.py CLEANFILES = *.pyc diff --git a/decoders/spi/__init__.py b/decoders/spi/__init__.py index fedcc5f..1fccbe7 100644 --- a/decoders/spi/__init__.py +++ b/decoders/spi/__init__.py @@ -44,5 +44,5 @@ Examples: ''' -from .spi import * +from .pd import * diff --git a/decoders/spi/pd.py b/decoders/spi/pd.py new file mode 100644 index 0000000..1085786 --- /dev/null +++ b/decoders/spi/pd.py @@ -0,0 +1,166 @@ +## +## This file is part of the sigrok project. +## +## Copyright (C) 2011 Gareth McMullin +## 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 +## + +# SPI protocol decoder + +import sigrokdecode as srd + +# Key: (CPOL, CPHA). Value: SPI mode. +# Clock polarity (CPOL) = 0/1: Clock is low/high when inactive. +# Clock phase (CPHA) = 0/1: Data is valid on the leading/trailing clock edge. +spi_mode = { + (0, 0): 0, # Mode 0 + (0, 1): 1, # Mode 1 + (1, 0): 2, # Mode 2 + (1, 1): 3, # Mode 3 +} + +# Annotation formats +ANN_HEX = 0 + +class Decoder(srd.Decoder): + api_version = 1 + id = 'spi' + name = 'SPI' + longname = 'Serial Peripheral Interface' + desc = 'Full-duplex, synchronous, serial bus.' + license = 'gplv2+' + inputs = ['logic'] + outputs = ['spi'] + probes = [ + {'id': 'miso', 'name': 'MISO', + 'desc': 'SPI MISO line (Master in, slave out)'}, + {'id': 'mosi', 'name': 'MOSI', + 'desc': 'SPI MOSI line (Master out, slave in)'}, + {'id': 'sck', 'name': 'CLK', 'desc': 'SPI clock line'}, + {'id': 'cs', 'name': 'CS#', 'desc': 'SPI CS (chip select) line'}, + ] + optional_probes = [] # TODO + options = { + 'cs_polarity': ['CS# polarity', 'active-low'], + 'cpol': ['Clock polarity', 0], + 'cpha': ['Clock phase', 0], + 'bitorder': ['Bit order within the SPI data', 'msb-first'], + 'wordsize': ['Word size of SPI data', 8], # 1-64? + } + annotations = [ + ['Hex', 'SPI data bytes in hex format'], + ] + + def __init__(self): + self.oldsck = 1 + self.bitcount = 0 + self.mosidata = 0 + self.misodata = 0 + self.bytesreceived = 0 + self.samplenum = -1 + self.cs_was_deasserted_during_data_word = 0 + self.oldcs = -1 + self.oldpins = None + + def start(self, metadata): + self.out_proto = self.add(srd.OUTPUT_PROTO, 'spi') + self.out_ann = self.add(srd.OUTPUT_ANN, 'spi') + + def report(self): + return 'SPI: %d bytes received' % self.bytesreceived + + def decode(self, ss, es, data): + # TODO: Either MISO or MOSI could be optional. CS# is optional. + for (self.samplenum, pins) in data: + + # Ignore identical samples early on (for performance reasons). + if self.oldpins == pins: + continue + self.oldpins, (miso, mosi, sck, cs) = pins, pins + + if self.oldcs != cs: + # Send all CS# pin value changes. + self.put(self.samplenum, self.samplenum, self.out_proto, + ['CS-CHANGE', self.oldcs, cs]) + self.put(self.samplenum, self.samplenum, self.out_ann, + [0, ['CS-CHANGE: %d->%d' % (self.oldcs, cs)]]) + self.oldcs = cs + + # Ignore sample if the clock pin hasn't changed. + if sck == self.oldsck: + continue + + self.oldsck = sck + + # Sample data on rising/falling clock edge (depends on mode). + mode = spi_mode[self.options['cpol'], self.options['cpha']] + if mode == 0 and sck == 0: # Sample on rising clock edge + continue + elif mode == 1 and sck == 1: # Sample on falling clock edge + continue + elif mode == 2 and sck == 1: # Sample on falling clock edge + continue + elif mode == 3 and sck == 0: # Sample on rising clock edge + continue + + # If this is the first bit, save its sample number. + if self.bitcount == 0: + self.start_sample = self.samplenum + active_low = (self.options['cs_polarity'] == 'active-low') + deasserted = cs if active_low else not cs + if deasserted: + self.cs_was_deasserted_during_data_word = 1 + + ws = self.options['wordsize'] + + # Receive MOSI bit into our shift register. + if self.options['bitorder'] == 'msb-first': + self.mosidata |= mosi << (ws - 1 - self.bitcount) + else: + self.mosidata |= mosi << self.bitcount + + # Receive MISO bit into our shift register. + if self.options['bitorder'] == 'msb-first': + self.misodata |= miso << (ws - 1 - self.bitcount) + else: + self.misodata |= miso << self.bitcount + + self.bitcount += 1 + + # Continue to receive if not enough bits were received, yet. + if self.bitcount != ws: + continue + + self.put(self.start_sample, self.samplenum, self.out_proto, + ['DATA', self.mosidata, self.misodata]) + self.put(self.start_sample, self.samplenum, self.out_ann, + [ANN_HEX, ['MOSI: 0x%02x, MISO: 0x%02x' % (self.mosidata, + self.misodata)]]) + + if self.cs_was_deasserted_during_data_word: + self.put(self.start_sample, self.samplenum, self.out_ann, + [ANN_HEX, ['WARNING: CS# was deasserted during this ' + 'SPI data byte!']]) + + # Reset decoder state. + self.mosidata = 0 + self.misodata = 0 + self.bitcount = 0 + + # Keep stats for summary. + self.bytesreceived += 1 + diff --git a/decoders/spi/spi.py b/decoders/spi/spi.py deleted file mode 100644 index 1085786..0000000 --- a/decoders/spi/spi.py +++ /dev/null @@ -1,166 +0,0 @@ -## -## This file is part of the sigrok project. -## -## Copyright (C) 2011 Gareth McMullin -## 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 -## - -# SPI protocol decoder - -import sigrokdecode as srd - -# Key: (CPOL, CPHA). Value: SPI mode. -# Clock polarity (CPOL) = 0/1: Clock is low/high when inactive. -# Clock phase (CPHA) = 0/1: Data is valid on the leading/trailing clock edge. -spi_mode = { - (0, 0): 0, # Mode 0 - (0, 1): 1, # Mode 1 - (1, 0): 2, # Mode 2 - (1, 1): 3, # Mode 3 -} - -# Annotation formats -ANN_HEX = 0 - -class Decoder(srd.Decoder): - api_version = 1 - id = 'spi' - name = 'SPI' - longname = 'Serial Peripheral Interface' - desc = 'Full-duplex, synchronous, serial bus.' - license = 'gplv2+' - inputs = ['logic'] - outputs = ['spi'] - probes = [ - {'id': 'miso', 'name': 'MISO', - 'desc': 'SPI MISO line (Master in, slave out)'}, - {'id': 'mosi', 'name': 'MOSI', - 'desc': 'SPI MOSI line (Master out, slave in)'}, - {'id': 'sck', 'name': 'CLK', 'desc': 'SPI clock line'}, - {'id': 'cs', 'name': 'CS#', 'desc': 'SPI CS (chip select) line'}, - ] - optional_probes = [] # TODO - options = { - 'cs_polarity': ['CS# polarity', 'active-low'], - 'cpol': ['Clock polarity', 0], - 'cpha': ['Clock phase', 0], - 'bitorder': ['Bit order within the SPI data', 'msb-first'], - 'wordsize': ['Word size of SPI data', 8], # 1-64? - } - annotations = [ - ['Hex', 'SPI data bytes in hex format'], - ] - - def __init__(self): - self.oldsck = 1 - self.bitcount = 0 - self.mosidata = 0 - self.misodata = 0 - self.bytesreceived = 0 - self.samplenum = -1 - self.cs_was_deasserted_during_data_word = 0 - self.oldcs = -1 - self.oldpins = None - - def start(self, metadata): - self.out_proto = self.add(srd.OUTPUT_PROTO, 'spi') - self.out_ann = self.add(srd.OUTPUT_ANN, 'spi') - - def report(self): - return 'SPI: %d bytes received' % self.bytesreceived - - def decode(self, ss, es, data): - # TODO: Either MISO or MOSI could be optional. CS# is optional. - for (self.samplenum, pins) in data: - - # Ignore identical samples early on (for performance reasons). - if self.oldpins == pins: - continue - self.oldpins, (miso, mosi, sck, cs) = pins, pins - - if self.oldcs != cs: - # Send all CS# pin value changes. - self.put(self.samplenum, self.samplenum, self.out_proto, - ['CS-CHANGE', self.oldcs, cs]) - self.put(self.samplenum, self.samplenum, self.out_ann, - [0, ['CS-CHANGE: %d->%d' % (self.oldcs, cs)]]) - self.oldcs = cs - - # Ignore sample if the clock pin hasn't changed. - if sck == self.oldsck: - continue - - self.oldsck = sck - - # Sample data on rising/falling clock edge (depends on mode). - mode = spi_mode[self.options['cpol'], self.options['cpha']] - if mode == 0 and sck == 0: # Sample on rising clock edge - continue - elif mode == 1 and sck == 1: # Sample on falling clock edge - continue - elif mode == 2 and sck == 1: # Sample on falling clock edge - continue - elif mode == 3 and sck == 0: # Sample on rising clock edge - continue - - # If this is the first bit, save its sample number. - if self.bitcount == 0: - self.start_sample = self.samplenum - active_low = (self.options['cs_polarity'] == 'active-low') - deasserted = cs if active_low else not cs - if deasserted: - self.cs_was_deasserted_during_data_word = 1 - - ws = self.options['wordsize'] - - # Receive MOSI bit into our shift register. - if self.options['bitorder'] == 'msb-first': - self.mosidata |= mosi << (ws - 1 - self.bitcount) - else: - self.mosidata |= mosi << self.bitcount - - # Receive MISO bit into our shift register. - if self.options['bitorder'] == 'msb-first': - self.misodata |= miso << (ws - 1 - self.bitcount) - else: - self.misodata |= miso << self.bitcount - - self.bitcount += 1 - - # Continue to receive if not enough bits were received, yet. - if self.bitcount != ws: - continue - - self.put(self.start_sample, self.samplenum, self.out_proto, - ['DATA', self.mosidata, self.misodata]) - self.put(self.start_sample, self.samplenum, self.out_ann, - [ANN_HEX, ['MOSI: 0x%02x, MISO: 0x%02x' % (self.mosidata, - self.misodata)]]) - - if self.cs_was_deasserted_during_data_word: - self.put(self.start_sample, self.samplenum, self.out_ann, - [ANN_HEX, ['WARNING: CS# was deasserted during this ' - 'SPI data byte!']]) - - # Reset decoder state. - self.mosidata = 0 - self.misodata = 0 - self.bitcount = 0 - - # Keep stats for summary. - self.bytesreceived += 1 - diff --git a/decoders/transitioncounter/Makefile.am b/decoders/transitioncounter/Makefile.am index 74688a3..b7ee1f2 100644 --- a/decoders/transitioncounter/Makefile.am +++ b/decoders/transitioncounter/Makefile.am @@ -20,7 +20,7 @@ pkgdatadir = $(DECODERS_DIR)/transitioncounter -dist_pkgdata_DATA = __init__.py transitioncounter.py +dist_pkgdata_DATA = __init__.py pd.py CLEANFILES = *.pyc diff --git a/decoders/transitioncounter/__init__.py b/decoders/transitioncounter/__init__.py index f2c3b80..3345da6 100644 --- a/decoders/transitioncounter/__init__.py +++ b/decoders/transitioncounter/__init__.py @@ -25,5 +25,5 @@ Details: TODO ''' -from .transitioncounter import * +from .pd import * diff --git a/decoders/transitioncounter/pd.py b/decoders/transitioncounter/pd.py new file mode 100644 index 0000000..9506cee --- /dev/null +++ b/decoders/transitioncounter/pd.py @@ -0,0 +1,104 @@ +## +## This file is part of the sigrok project. +## +## Copyright (C) 2010 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 +## + +# Transition counter protocol decoder + +import sigrokdecode as srd + +class Decoder(srd.Decoder): + api_version = 1 + id = 'transitioncounter' + name = 'Transition counter' + longname = 'Pin transition counter' + desc = 'Counts rising/falling edges in the signal.' + license = 'gplv2+' + inputs = ['logic'] + outputs = ['transitioncounts'] + probes = [] + optional_probes = [] + options = {} + annotations = [ + ['TODO', 'TODO'], + ] + + def __init__(self, **kwargs): + self.channels = -1 + self.lastsample = None + + def start(self, metadata): + # self.out_proto = self.add(srd.OUTPUT_PROTO, 'transitioncounter') + self.out_ann = self.add(srd.OUTPUT_ANN, 'transitioncounter') + + def report(self): + pass + + def decode(self, ss, es, data): + + for (samplenum, s) in data: + + # ... + if self.channels == -1: + self.channels = len(s) + self.oldbit = [0] * self.channels + self.transitions = [0] * self.channels + self.rising = [0] * self.channels + self.falling = [0] * self.channels + + # Optimization: Skip identical samples (no transitions). + if self.lastsample == s: + continue + + # Upon the first sample, store the initial values. + if self.lastsample == None: + self.lastsample = s + for i in range(self.channels): + self.oldbit[i] = self.lastsample[i] + + # Iterate over all channels/probes in this sample. + # Count rising and falling edges for each channel. + for i in range(self.channels): + curbit = s[i] + # Optimization: Skip identical bits (no transitions). + if self.oldbit[i] == curbit: + continue + elif (self.oldbit[i] == 0 and curbit == 1): + self.rising[i] += 1 + elif (self.oldbit[i] == 1 and curbit == 0): + self.falling[i] += 1 + self.oldbit[i] = curbit + + # Save the current sample as 'lastsample' for the next round. + self.lastsample = s + + # Total number of transitions = rising + falling edges. + for i in range(self.channels): + self.transitions[i] = self.rising[i] + self.falling[i] + + # TODO: Which output format? + # TODO: How to only output something after the last chunk of data? + outdata = [] + for i in range(self.channels): + outdata.append([self.transitions[i], self.rising[i], + self.falling[i]]) + + if outdata != []: + # self.put(0, 0, self.out_proto, out_proto) + self.put(0, 0, self.out_ann, [0, [str(outdata)]]) + diff --git a/decoders/transitioncounter/transitioncounter.py b/decoders/transitioncounter/transitioncounter.py deleted file mode 100644 index 9506cee..0000000 --- a/decoders/transitioncounter/transitioncounter.py +++ /dev/null @@ -1,104 +0,0 @@ -## -## This file is part of the sigrok project. -## -## Copyright (C) 2010 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 -## - -# Transition counter protocol decoder - -import sigrokdecode as srd - -class Decoder(srd.Decoder): - api_version = 1 - id = 'transitioncounter' - name = 'Transition counter' - longname = 'Pin transition counter' - desc = 'Counts rising/falling edges in the signal.' - license = 'gplv2+' - inputs = ['logic'] - outputs = ['transitioncounts'] - probes = [] - optional_probes = [] - options = {} - annotations = [ - ['TODO', 'TODO'], - ] - - def __init__(self, **kwargs): - self.channels = -1 - self.lastsample = None - - def start(self, metadata): - # self.out_proto = self.add(srd.OUTPUT_PROTO, 'transitioncounter') - self.out_ann = self.add(srd.OUTPUT_ANN, 'transitioncounter') - - def report(self): - pass - - def decode(self, ss, es, data): - - for (samplenum, s) in data: - - # ... - if self.channels == -1: - self.channels = len(s) - self.oldbit = [0] * self.channels - self.transitions = [0] * self.channels - self.rising = [0] * self.channels - self.falling = [0] * self.channels - - # Optimization: Skip identical samples (no transitions). - if self.lastsample == s: - continue - - # Upon the first sample, store the initial values. - if self.lastsample == None: - self.lastsample = s - for i in range(self.channels): - self.oldbit[i] = self.lastsample[i] - - # Iterate over all channels/probes in this sample. - # Count rising and falling edges for each channel. - for i in range(self.channels): - curbit = s[i] - # Optimization: Skip identical bits (no transitions). - if self.oldbit[i] == curbit: - continue - elif (self.oldbit[i] == 0 and curbit == 1): - self.rising[i] += 1 - elif (self.oldbit[i] == 1 and curbit == 0): - self.falling[i] += 1 - self.oldbit[i] = curbit - - # Save the current sample as 'lastsample' for the next round. - self.lastsample = s - - # Total number of transitions = rising + falling edges. - for i in range(self.channels): - self.transitions[i] = self.rising[i] + self.falling[i] - - # TODO: Which output format? - # TODO: How to only output something after the last chunk of data? - outdata = [] - for i in range(self.channels): - outdata.append([self.transitions[i], self.rising[i], - self.falling[i]]) - - if outdata != []: - # self.put(0, 0, self.out_proto, out_proto) - self.put(0, 0, self.out_ann, [0, [str(outdata)]]) - diff --git a/decoders/uart/Makefile.am b/decoders/uart/Makefile.am index 155bfc9..61efbd0 100644 --- a/decoders/uart/Makefile.am +++ b/decoders/uart/Makefile.am @@ -20,7 +20,7 @@ pkgdatadir = $(DECODERS_DIR)/uart -dist_pkgdata_DATA = __init__.py uart.py +dist_pkgdata_DATA = __init__.py pd.py CLEANFILES = *.pyc diff --git a/decoders/uart/__init__.py b/decoders/uart/__init__.py index ecba2e1..23e2dd0 100644 --- a/decoders/uart/__init__.py +++ b/decoders/uart/__init__.py @@ -111,5 +111,5 @@ This is the list of s and their respective : The field is 0 for RX packets, 1 for TX packets. ''' -from .uart import * +from .pd import * diff --git a/decoders/uart/pd.py b/decoders/uart/pd.py new file mode 100644 index 0000000..24551da --- /dev/null +++ b/decoders/uart/pd.py @@ -0,0 +1,304 @@ +## +## This file is part of the sigrok project. +## +## Copyright (C) 2011-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 +## + +# UART protocol decoder + +import sigrokdecode as srd + +# Used for differentiating between the two data directions. +RX = 0 +TX = 1 + +# Annotation feed formats +ANN_ASCII = 0 +ANN_DEC = 1 +ANN_HEX = 2 +ANN_OCT = 3 +ANN_BITS = 4 + +# Given a parity type to check (odd, even, zero, one), the value of the +# parity bit, the value of the data, and the length of the data (5-9 bits, +# usually 8 bits) return True if the parity is correct, False otherwise. +# 'none' is _not_ allowed as value for 'parity_type'. +def parity_ok(parity_type, parity_bit, data, num_data_bits): + + # Handle easy cases first (parity bit is always 1 or 0). + if parity_type == 'zero': + return parity_bit == 0 + elif parity_type == 'one': + return parity_bit == 1 + + # Count number of 1 (high) bits in the data (and the parity bit itself!). + ones = bin(data).count('1') + parity_bit + + # Check for odd/even parity. + if parity_type == 'odd': + return (ones % 2) == 1 + elif parity_type == 'even': + return (ones % 2) == 0 + else: + raise Exception('Invalid parity type: %d' % parity_type) + +class Decoder(srd.Decoder): + api_version = 1 + id = 'uart' + name = 'UART' + longname = 'Universal Asynchronous Receiver/Transmitter' + desc = 'Asynchronous, serial bus.' + license = 'gplv2+' + inputs = ['logic'] + outputs = ['uart'] + probes = [ + # Allow specifying only one of the signals, e.g. if only one data + # direction exists (or is relevant). + {'id': 'rx', 'name': 'RX', 'desc': 'UART receive line'}, + {'id': 'tx', 'name': 'TX', 'desc': 'UART transmit line'}, + ] + optional_probes = [] + options = { + 'baudrate': ['Baud rate', 115200], + 'num_data_bits': ['Data bits', 8], # Valid: 5-9. + 'parity_type': ['Parity type', 'none'], + 'parity_check': ['Check parity?', 'yes'], # TODO: Bool supported? + 'num_stop_bits': ['Stop bit(s)', '1'], # String! 0, 0.5, 1, 1.5. + 'bit_order': ['Bit order', 'lsb-first'], + # TODO: Options to invert the signal(s). + } + annotations = [ + ['ASCII', 'Data bytes as ASCII characters'], + ['Decimal', 'Databytes as decimal, integer values'], + ['Hex', 'Data bytes in hex format'], + ['Octal', 'Data bytes as octal numbers'], + ['Bits', 'Data bytes in bit notation (sequence of 0/1 digits)'], + ] + + def putx(self, rxtx, data): + self.put(self.startsample[rxtx], self.samplenum - 1, self.out_ann, data) + + def __init__(self, **kwargs): + self.samplenum = 0 + self.frame_start = [-1, -1] + self.startbit = [-1, -1] + self.cur_data_bit = [0, 0] + self.databyte = [0, 0] + self.paritybit = [-1, -1] + self.stopbit1 = [-1, -1] + self.startsample = [-1, -1] + self.state = ['WAIT FOR START BIT', 'WAIT FOR START BIT'] + self.oldbit = [None, None] + self.oldpins = None + + def start(self, metadata): + self.samplerate = metadata['samplerate'] + self.out_proto = self.add(srd.OUTPUT_PROTO, 'uart') + self.out_ann = self.add(srd.OUTPUT_ANN, 'uart') + + # The width of one UART bit in number of samples. + self.bit_width = \ + float(self.samplerate) / float(self.options['baudrate']) + + def report(self): + pass + + # Return true if we reached the middle of the desired bit, false otherwise. + def reached_bit(self, rxtx, bitnum): + # 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) + bitpos += bitnum * self.bit_width + if self.samplenum >= bitpos: + return True + return False + + def reached_bit_last(self, rxtx, bitnum): + bitpos = self.frame_start[rxtx] + ((bitnum + 1) * self.bit_width) + if self.samplenum >= bitpos: + return True + return False + + def wait_for_start_bit(self, rxtx, old_signal, signal): + # The start bit is always 0 (low). As the idle UART (and the stop bit) + # level is 1 (high), the beginning of a start bit is a falling edge. + if not (old_signal == 1 and signal == 0): + return + + # Save the sample number where the start bit begins. + self.frame_start[rxtx] = self.samplenum + + self.state[rxtx] = 'GET START BIT' + + def get_start_bit(self, rxtx, signal): + # Skip samples until we're in the middle of the start bit. + if not self.reached_bit(rxtx, 0): + return + + self.startbit[rxtx] = signal + + # The startbit must be 0. If not, we report an error. + if self.startbit[rxtx] != 0: + self.put(self.frame_start[rxtx], self.samplenum, self.out_proto, + ['INVALID STARTBIT', rxtx, self.startbit[rxtx]]) + # TODO: Abort? Ignore rest of the frame? + + self.cur_data_bit[rxtx] = 0 + self.databyte[rxtx] = 0 + self.startsample[rxtx] = -1 + + self.state[rxtx] = 'GET DATA BITS' + + self.put(self.frame_start[rxtx], self.samplenum, self.out_proto, + ['STARTBIT', rxtx, self.startbit[rxtx]]) + self.put(self.frame_start[rxtx], self.samplenum, self.out_ann, + [ANN_ASCII, ['Start bit', 'Start', 'S']]) + + def get_data_bits(self, rxtx, signal): + # Skip samples until we're in the middle of the desired data bit. + if not self.reached_bit(rxtx, self.cur_data_bit[rxtx] + 1): + return + + # Save the sample number where the data byte starts. + if self.startsample[rxtx] == -1: + self.startsample[rxtx] = self.samplenum + + # Get the next data bit in LSB-first or MSB-first fashion. + if self.options['bit_order'] == 'lsb-first': + self.databyte[rxtx] >>= 1 + self.databyte[rxtx] |= \ + (signal << (self.options['num_data_bits'] - 1)) + elif self.options['bit_order'] == 'msb-first': + self.databyte[rxtx] <<= 1 + self.databyte[rxtx] |= (signal << 0) + else: + raise Exception('Invalid bit order value: %s', + self.options['bit_order']) + + # Return here, unless we already received all data bits. + # TODO? Off-by-one? + if self.cur_data_bit[rxtx] < self.options['num_data_bits'] - 1: + self.cur_data_bit[rxtx] += 1 + return + + self.state[rxtx] = 'GET PARITY BIT' + + self.put(self.startsample[rxtx], self.samplenum - 1, self.out_proto, + ['DATA', rxtx, self.databyte[rxtx]]) + + s = 'RX: ' if (rxtx == RX) else 'TX: ' + self.putx(rxtx, [ANN_ASCII, [s + chr(self.databyte[rxtx])]]) + self.putx(rxtx, [ANN_DEC, [s + str(self.databyte[rxtx])]]) + self.putx(rxtx, [ANN_HEX, [s + hex(self.databyte[rxtx]), + s + hex(self.databyte[rxtx])[2:]]]) + self.putx(rxtx, [ANN_OCT, [s + oct(self.databyte[rxtx]), + s + oct(self.databyte[rxtx])[2:]]]) + self.putx(rxtx, [ANN_BITS, [s + bin(self.databyte[rxtx]), + s + bin(self.databyte[rxtx])[2:]]]) + + def get_parity_bit(self, rxtx, signal): + # If no parity is used/configured, skip to the next state immediately. + if self.options['parity_type'] == 'none': + self.state[rxtx] = 'GET STOP BITS' + return + + # Skip samples until we're in the middle of the parity bit. + if not self.reached_bit(rxtx, self.options['num_data_bits'] + 1): + return + + self.paritybit[rxtx] = signal + + self.state[rxtx] = 'GET STOP BITS' + + if parity_ok(self.options['parity_type'], self.paritybit[rxtx], + self.databyte[rxtx], self.options['num_data_bits']): + # TODO: Fix range. + self.put(self.samplenum, self.samplenum, self.out_proto, + ['PARITYBIT', rxtx, self.paritybit[rxtx]]) + self.put(self.samplenum, self.samplenum, self.out_ann, + [ANN_ASCII, ['Parity bit', 'Parity', 'P']]) + else: + # TODO: Fix range. + # TODO: Return expected/actual parity values. + self.put(self.samplenum, self.samplenum, self.out_proto, + ['PARITY ERROR', rxtx, (0, 1)]) # FIXME: Dummy tuple... + self.put(self.samplenum, self.samplenum, self.out_ann, + [ANN_ASCII, ['Parity error', 'Parity err', 'PE']]) + + # TODO: Currently only supports 1 stop bit. + def get_stop_bits(self, rxtx, signal): + # Skip samples until we're in the middle of the stop bit(s). + skip_parity = 0 if self.options['parity_type'] == 'none' else 1 + b = self.options['num_data_bits'] + 1 + skip_parity + if not self.reached_bit(rxtx, b): + return + + self.stopbit1[rxtx] = signal + + # Stop bits must be 1. If not, we report an error. + if self.stopbit1[rxtx] != 1: + self.put(self.frame_start[rxtx], self.samplenum, self.out_proto, + ['INVALID STOPBIT', rxtx, self.stopbit1[rxtx]]) + # TODO: Abort? Ignore the frame? Other? + + self.state[rxtx] = 'WAIT FOR START BIT' + + # TODO: Fix range. + self.put(self.samplenum, self.samplenum, self.out_proto, + ['STOPBIT', rxtx, self.stopbit1[rxtx]]) + self.put(self.samplenum, self.samplenum, self.out_ann, + [ANN_ASCII, ['Stop bit', 'Stop', 'P']]) + + def decode(self, ss, es, data): + # TODO: Either RX or TX could be omitted (optional probe). + for (self.samplenum, pins) in data: + + # Note: Ignoring identical samples here for performance reasons + # is not possible for this PD, at least not in the current state. + # if self.oldpins == pins: + # continue + self.oldpins, (rx, tx) = pins, pins + + # First sample: Save RX/TX value. + if self.oldbit[RX] == None: + self.oldbit[RX] = rx + continue + if self.oldbit[TX] == None: + self.oldbit[TX] = tx + continue + + # State machine. + for rxtx in (RX, TX): + signal = rx if (rxtx == RX) else tx + + if self.state[rxtx] == 'WAIT FOR START BIT': + self.wait_for_start_bit(rxtx, self.oldbit[rxtx], signal) + elif self.state[rxtx] == 'GET START BIT': + self.get_start_bit(rxtx, signal) + elif self.state[rxtx] == 'GET DATA BITS': + self.get_data_bits(rxtx, signal) + elif self.state[rxtx] == 'GET PARITY BIT': + self.get_parity_bit(rxtx, signal) + elif self.state[rxtx] == 'GET STOP BITS': + self.get_stop_bits(rxtx, signal) + else: + raise Exception('Invalid state: %d' % self.state[rxtx]) + + # Save current RX/TX values for the next round. + self.oldbit[rxtx] = signal + diff --git a/decoders/uart/uart.py b/decoders/uart/uart.py deleted file mode 100644 index 24551da..0000000 --- a/decoders/uart/uart.py +++ /dev/null @@ -1,304 +0,0 @@ -## -## This file is part of the sigrok project. -## -## Copyright (C) 2011-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 -## - -# UART protocol decoder - -import sigrokdecode as srd - -# Used for differentiating between the two data directions. -RX = 0 -TX = 1 - -# Annotation feed formats -ANN_ASCII = 0 -ANN_DEC = 1 -ANN_HEX = 2 -ANN_OCT = 3 -ANN_BITS = 4 - -# Given a parity type to check (odd, even, zero, one), the value of the -# parity bit, the value of the data, and the length of the data (5-9 bits, -# usually 8 bits) return True if the parity is correct, False otherwise. -# 'none' is _not_ allowed as value for 'parity_type'. -def parity_ok(parity_type, parity_bit, data, num_data_bits): - - # Handle easy cases first (parity bit is always 1 or 0). - if parity_type == 'zero': - return parity_bit == 0 - elif parity_type == 'one': - return parity_bit == 1 - - # Count number of 1 (high) bits in the data (and the parity bit itself!). - ones = bin(data).count('1') + parity_bit - - # Check for odd/even parity. - if parity_type == 'odd': - return (ones % 2) == 1 - elif parity_type == 'even': - return (ones % 2) == 0 - else: - raise Exception('Invalid parity type: %d' % parity_type) - -class Decoder(srd.Decoder): - api_version = 1 - id = 'uart' - name = 'UART' - longname = 'Universal Asynchronous Receiver/Transmitter' - desc = 'Asynchronous, serial bus.' - license = 'gplv2+' - inputs = ['logic'] - outputs = ['uart'] - probes = [ - # Allow specifying only one of the signals, e.g. if only one data - # direction exists (or is relevant). - {'id': 'rx', 'name': 'RX', 'desc': 'UART receive line'}, - {'id': 'tx', 'name': 'TX', 'desc': 'UART transmit line'}, - ] - optional_probes = [] - options = { - 'baudrate': ['Baud rate', 115200], - 'num_data_bits': ['Data bits', 8], # Valid: 5-9. - 'parity_type': ['Parity type', 'none'], - 'parity_check': ['Check parity?', 'yes'], # TODO: Bool supported? - 'num_stop_bits': ['Stop bit(s)', '1'], # String! 0, 0.5, 1, 1.5. - 'bit_order': ['Bit order', 'lsb-first'], - # TODO: Options to invert the signal(s). - } - annotations = [ - ['ASCII', 'Data bytes as ASCII characters'], - ['Decimal', 'Databytes as decimal, integer values'], - ['Hex', 'Data bytes in hex format'], - ['Octal', 'Data bytes as octal numbers'], - ['Bits', 'Data bytes in bit notation (sequence of 0/1 digits)'], - ] - - def putx(self, rxtx, data): - self.put(self.startsample[rxtx], self.samplenum - 1, self.out_ann, data) - - def __init__(self, **kwargs): - self.samplenum = 0 - self.frame_start = [-1, -1] - self.startbit = [-1, -1] - self.cur_data_bit = [0, 0] - self.databyte = [0, 0] - self.paritybit = [-1, -1] - self.stopbit1 = [-1, -1] - self.startsample = [-1, -1] - self.state = ['WAIT FOR START BIT', 'WAIT FOR START BIT'] - self.oldbit = [None, None] - self.oldpins = None - - def start(self, metadata): - self.samplerate = metadata['samplerate'] - self.out_proto = self.add(srd.OUTPUT_PROTO, 'uart') - self.out_ann = self.add(srd.OUTPUT_ANN, 'uart') - - # The width of one UART bit in number of samples. - self.bit_width = \ - float(self.samplerate) / float(self.options['baudrate']) - - def report(self): - pass - - # Return true if we reached the middle of the desired bit, false otherwise. - def reached_bit(self, rxtx, bitnum): - # 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) - bitpos += bitnum * self.bit_width - if self.samplenum >= bitpos: - return True - return False - - def reached_bit_last(self, rxtx, bitnum): - bitpos = self.frame_start[rxtx] + ((bitnum + 1) * self.bit_width) - if self.samplenum >= bitpos: - return True - return False - - def wait_for_start_bit(self, rxtx, old_signal, signal): - # The start bit is always 0 (low). As the idle UART (and the stop bit) - # level is 1 (high), the beginning of a start bit is a falling edge. - if not (old_signal == 1 and signal == 0): - return - - # Save the sample number where the start bit begins. - self.frame_start[rxtx] = self.samplenum - - self.state[rxtx] = 'GET START BIT' - - def get_start_bit(self, rxtx, signal): - # Skip samples until we're in the middle of the start bit. - if not self.reached_bit(rxtx, 0): - return - - self.startbit[rxtx] = signal - - # The startbit must be 0. If not, we report an error. - if self.startbit[rxtx] != 0: - self.put(self.frame_start[rxtx], self.samplenum, self.out_proto, - ['INVALID STARTBIT', rxtx, self.startbit[rxtx]]) - # TODO: Abort? Ignore rest of the frame? - - self.cur_data_bit[rxtx] = 0 - self.databyte[rxtx] = 0 - self.startsample[rxtx] = -1 - - self.state[rxtx] = 'GET DATA BITS' - - self.put(self.frame_start[rxtx], self.samplenum, self.out_proto, - ['STARTBIT', rxtx, self.startbit[rxtx]]) - self.put(self.frame_start[rxtx], self.samplenum, self.out_ann, - [ANN_ASCII, ['Start bit', 'Start', 'S']]) - - def get_data_bits(self, rxtx, signal): - # Skip samples until we're in the middle of the desired data bit. - if not self.reached_bit(rxtx, self.cur_data_bit[rxtx] + 1): - return - - # Save the sample number where the data byte starts. - if self.startsample[rxtx] == -1: - self.startsample[rxtx] = self.samplenum - - # Get the next data bit in LSB-first or MSB-first fashion. - if self.options['bit_order'] == 'lsb-first': - self.databyte[rxtx] >>= 1 - self.databyte[rxtx] |= \ - (signal << (self.options['num_data_bits'] - 1)) - elif self.options['bit_order'] == 'msb-first': - self.databyte[rxtx] <<= 1 - self.databyte[rxtx] |= (signal << 0) - else: - raise Exception('Invalid bit order value: %s', - self.options['bit_order']) - - # Return here, unless we already received all data bits. - # TODO? Off-by-one? - if self.cur_data_bit[rxtx] < self.options['num_data_bits'] - 1: - self.cur_data_bit[rxtx] += 1 - return - - self.state[rxtx] = 'GET PARITY BIT' - - self.put(self.startsample[rxtx], self.samplenum - 1, self.out_proto, - ['DATA', rxtx, self.databyte[rxtx]]) - - s = 'RX: ' if (rxtx == RX) else 'TX: ' - self.putx(rxtx, [ANN_ASCII, [s + chr(self.databyte[rxtx])]]) - self.putx(rxtx, [ANN_DEC, [s + str(self.databyte[rxtx])]]) - self.putx(rxtx, [ANN_HEX, [s + hex(self.databyte[rxtx]), - s + hex(self.databyte[rxtx])[2:]]]) - self.putx(rxtx, [ANN_OCT, [s + oct(self.databyte[rxtx]), - s + oct(self.databyte[rxtx])[2:]]]) - self.putx(rxtx, [ANN_BITS, [s + bin(self.databyte[rxtx]), - s + bin(self.databyte[rxtx])[2:]]]) - - def get_parity_bit(self, rxtx, signal): - # If no parity is used/configured, skip to the next state immediately. - if self.options['parity_type'] == 'none': - self.state[rxtx] = 'GET STOP BITS' - return - - # Skip samples until we're in the middle of the parity bit. - if not self.reached_bit(rxtx, self.options['num_data_bits'] + 1): - return - - self.paritybit[rxtx] = signal - - self.state[rxtx] = 'GET STOP BITS' - - if parity_ok(self.options['parity_type'], self.paritybit[rxtx], - self.databyte[rxtx], self.options['num_data_bits']): - # TODO: Fix range. - self.put(self.samplenum, self.samplenum, self.out_proto, - ['PARITYBIT', rxtx, self.paritybit[rxtx]]) - self.put(self.samplenum, self.samplenum, self.out_ann, - [ANN_ASCII, ['Parity bit', 'Parity', 'P']]) - else: - # TODO: Fix range. - # TODO: Return expected/actual parity values. - self.put(self.samplenum, self.samplenum, self.out_proto, - ['PARITY ERROR', rxtx, (0, 1)]) # FIXME: Dummy tuple... - self.put(self.samplenum, self.samplenum, self.out_ann, - [ANN_ASCII, ['Parity error', 'Parity err', 'PE']]) - - # TODO: Currently only supports 1 stop bit. - def get_stop_bits(self, rxtx, signal): - # Skip samples until we're in the middle of the stop bit(s). - skip_parity = 0 if self.options['parity_type'] == 'none' else 1 - b = self.options['num_data_bits'] + 1 + skip_parity - if not self.reached_bit(rxtx, b): - return - - self.stopbit1[rxtx] = signal - - # Stop bits must be 1. If not, we report an error. - if self.stopbit1[rxtx] != 1: - self.put(self.frame_start[rxtx], self.samplenum, self.out_proto, - ['INVALID STOPBIT', rxtx, self.stopbit1[rxtx]]) - # TODO: Abort? Ignore the frame? Other? - - self.state[rxtx] = 'WAIT FOR START BIT' - - # TODO: Fix range. - self.put(self.samplenum, self.samplenum, self.out_proto, - ['STOPBIT', rxtx, self.stopbit1[rxtx]]) - self.put(self.samplenum, self.samplenum, self.out_ann, - [ANN_ASCII, ['Stop bit', 'Stop', 'P']]) - - def decode(self, ss, es, data): - # TODO: Either RX or TX could be omitted (optional probe). - for (self.samplenum, pins) in data: - - # Note: Ignoring identical samples here for performance reasons - # is not possible for this PD, at least not in the current state. - # if self.oldpins == pins: - # continue - self.oldpins, (rx, tx) = pins, pins - - # First sample: Save RX/TX value. - if self.oldbit[RX] == None: - self.oldbit[RX] = rx - continue - if self.oldbit[TX] == None: - self.oldbit[TX] = tx - continue - - # State machine. - for rxtx in (RX, TX): - signal = rx if (rxtx == RX) else tx - - if self.state[rxtx] == 'WAIT FOR START BIT': - self.wait_for_start_bit(rxtx, self.oldbit[rxtx], signal) - elif self.state[rxtx] == 'GET START BIT': - self.get_start_bit(rxtx, signal) - elif self.state[rxtx] == 'GET DATA BITS': - self.get_data_bits(rxtx, signal) - elif self.state[rxtx] == 'GET PARITY BIT': - self.get_parity_bit(rxtx, signal) - elif self.state[rxtx] == 'GET STOP BITS': - self.get_stop_bits(rxtx, signal) - else: - raise Exception('Invalid state: %d' % self.state[rxtx]) - - # Save current RX/TX values for the next round. - self.oldbit[rxtx] = signal - diff --git a/decoders/uart_dump/Makefile.am b/decoders/uart_dump/Makefile.am index d95618c..ca9f532 100644 --- a/decoders/uart_dump/Makefile.am +++ b/decoders/uart_dump/Makefile.am @@ -20,7 +20,7 @@ pkgdatadir = $(DECODERS_DIR)/uart_dump -dist_pkgdata_DATA = __init__.py uart_dump.py +dist_pkgdata_DATA = __init__.py pd.py CLEANFILES = *.pyc diff --git a/decoders/uart_dump/__init__.py b/decoders/uart_dump/__init__.py index 06a6d95..ea351ab 100644 --- a/decoders/uart_dump/__init__.py +++ b/decoders/uart_dump/__init__.py @@ -25,5 +25,5 @@ Details: TODO ''' -from .uart_dump import * +from .pd import * diff --git a/decoders/uart_dump/pd.py b/decoders/uart_dump/pd.py new file mode 100644 index 0000000..1174832 --- /dev/null +++ b/decoders/uart_dump/pd.py @@ -0,0 +1,110 @@ +## +## This file is part of the sigrok 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 +## + +import sigrokdecode as srd +import os +import sys + +RX = 0 +TX = 1 + +class Decoder(srd.Decoder): + api_version = 1 + id = 'uart_dump' + name = 'UART dump' + longname = 'UART dump' + desc = 'Output decoded UART data to a file.' + license = 'gplv2+' + inputs = ['uart'] + outputs = [] # TODO? + probes = [] + optional_probes = [] + options = { + 'rx': ['Output RX data?', 'yes'], + 'tx': ['Output TX data?', 'yes'], + 'filename': ['File name for RX and TX data', '-'], + 'filename_rx': ['File name for RX data', 'none'], + 'filename_tx': ['File name for TX data', 'none'], + } + annotations = [] + + def __init__(self, **kwargs): + self.f = None + self.f_rx = None + self.f_tx = None + + def file_open(self, filename): + if filename == 'none': + return None + elif filename == '-': + return sys.stdout + else: + return open(filename, 'w') + + def start(self, metadata): + # The user can specify 'filename' (gets both RX and TX data), and/or + # 'filename_rx' (for RX data only), and/or 'filename_tx', respectively. + + # Default is to output RX and TX to 'filename', with 'filename_rx' and + # 'filename_tx' being unused. + + # If multiple 'filename*' options are specified, the user must NOT + # use the same file for any of them. + + # A filename of 'none' is not allowed (has special meaning). A filename + # of '-' means 'stdout'. + + self.f = self.file_open(self.options['filename']) + self.f_rx = self.file_open(self.options['filename_rx']) + self.f_tx = self.file_open(self.options['filename_tx']) + + def report(self): + pass + + def decode(self, ss, es, data): + ptype, rxtx, pdata = data + + # Ignore all UART packets except the actual data packets (i.e., we + # do not print start bits, parity bits, stop bits, errors, and so on). + if ptype != 'DATA': + return + + # TODO: Configurable format. + c = chr(pdata) + + # TODO: Error handling. + + # Output RX and/or TX to 'filename'. + if self.f != None: + self.f.write(c) + self.f.flush() + + # Output RX data to 'filename_rx'. + if self.f_rx != None: + if self.options['rx'] == 'yes' and rxtx == RX: + self.f_rx.write(c) + self.f_rx.flush() + + # Output TX data to 'filename_tx'. + if self.f_tx != None: + if self.options['tx'] == 'yes' and rxtx == TX: + self.f_tx.write(c) + self.f_tx.flush() + diff --git a/decoders/uart_dump/uart_dump.py b/decoders/uart_dump/uart_dump.py deleted file mode 100644 index 1174832..0000000 --- a/decoders/uart_dump/uart_dump.py +++ /dev/null @@ -1,110 +0,0 @@ -## -## This file is part of the sigrok 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 -## - -import sigrokdecode as srd -import os -import sys - -RX = 0 -TX = 1 - -class Decoder(srd.Decoder): - api_version = 1 - id = 'uart_dump' - name = 'UART dump' - longname = 'UART dump' - desc = 'Output decoded UART data to a file.' - license = 'gplv2+' - inputs = ['uart'] - outputs = [] # TODO? - probes = [] - optional_probes = [] - options = { - 'rx': ['Output RX data?', 'yes'], - 'tx': ['Output TX data?', 'yes'], - 'filename': ['File name for RX and TX data', '-'], - 'filename_rx': ['File name for RX data', 'none'], - 'filename_tx': ['File name for TX data', 'none'], - } - annotations = [] - - def __init__(self, **kwargs): - self.f = None - self.f_rx = None - self.f_tx = None - - def file_open(self, filename): - if filename == 'none': - return None - elif filename == '-': - return sys.stdout - else: - return open(filename, 'w') - - def start(self, metadata): - # The user can specify 'filename' (gets both RX and TX data), and/or - # 'filename_rx' (for RX data only), and/or 'filename_tx', respectively. - - # Default is to output RX and TX to 'filename', with 'filename_rx' and - # 'filename_tx' being unused. - - # If multiple 'filename*' options are specified, the user must NOT - # use the same file for any of them. - - # A filename of 'none' is not allowed (has special meaning). A filename - # of '-' means 'stdout'. - - self.f = self.file_open(self.options['filename']) - self.f_rx = self.file_open(self.options['filename_rx']) - self.f_tx = self.file_open(self.options['filename_tx']) - - def report(self): - pass - - def decode(self, ss, es, data): - ptype, rxtx, pdata = data - - # Ignore all UART packets except the actual data packets (i.e., we - # do not print start bits, parity bits, stop bits, errors, and so on). - if ptype != 'DATA': - return - - # TODO: Configurable format. - c = chr(pdata) - - # TODO: Error handling. - - # Output RX and/or TX to 'filename'. - if self.f != None: - self.f.write(c) - self.f.flush() - - # Output RX data to 'filename_rx'. - if self.f_rx != None: - if self.options['rx'] == 'yes' and rxtx == RX: - self.f_rx.write(c) - self.f_rx.flush() - - # Output TX data to 'filename_tx'. - if self.f_tx != None: - if self.options['tx'] == 'yes' and rxtx == TX: - self.f_tx.write(c) - self.f_tx.flush() - diff --git a/decoders/usb_protocol/Makefile.am b/decoders/usb_protocol/Makefile.am index 599092b..d76c1a4 100644 --- a/decoders/usb_protocol/Makefile.am +++ b/decoders/usb_protocol/Makefile.am @@ -20,7 +20,7 @@ pkgdatadir = $(DECODERS_DIR)/usb_protocol -dist_pkgdata_DATA = __init__.py usb_protocol.py +dist_pkgdata_DATA = __init__.py pd.py CLEANFILES = *.pyc diff --git a/decoders/usb_protocol/__init__.py b/decoders/usb_protocol/__init__.py index a19304e..735e3f8 100644 --- a/decoders/usb_protocol/__init__.py +++ b/decoders/usb_protocol/__init__.py @@ -43,5 +43,5 @@ https://en.wikipedia.org/wiki/USB http://www.usb.org/developers/docs/ ''' -from .usb_protocol import * +from .pd import * diff --git a/decoders/usb_protocol/pd.py b/decoders/usb_protocol/pd.py new file mode 100644 index 0000000..b1cd33b --- /dev/null +++ b/decoders/usb_protocol/pd.py @@ -0,0 +1,137 @@ +## +## This file is part of the sigrok project. +## +## Copyright (C) 2011 Gareth McMullin +## 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 +## + +# USB (low-speed and full-speed) protocol decoder + +import sigrokdecode as srd + +# Packet IDs (PIDs). +# The first 4 bits are the 'packet type' field, the last 4 bits are the +# 'check field' (each bit in the check field must be the inverse of the resp. +# bit in the 'packet type' field; if not, that's a 'PID error'). +# For the 4-bit strings, the left-most '1' or '0' is the LSB, i.e. it's sent +# to the bus first. +pids = { + # Tokens + '10000111': ['OUT', 'Address & EP number in host-to-function transaction'], + '10010110': ['IN', 'Address & EP number in function-to-host transaction'], + '10100101': ['SOF', 'Start-Of-Frame marker & frame number'], + '10110100': ['SETUP', 'Address & EP number in host-to-function transaction for SETUP to a control pipe'], + + # Data + # Note: DATA2 and MDATA are HS-only. + '11000011': ['DATA0', 'Data packet PID even'], + '11010010': ['DATA1', 'Data packet PID odd'], + '11100001': ['DATA2', 'Data packet PID HS, high bandwidth isosynchronous transaction in a microframe'], + '11110000': ['MDATA', 'Data packet PID HS for split and high-bandwidth isosynchronous transactions'], + + # Handshake + '01001011': ['ACK', 'Receiver accepts error-free packet'], + '01011010': ['NAK', 'Receiver cannot accept or transmitter cannot send'], + '01111000': ['STALL', 'EP halted or control pipe request unsupported'], + '01101001': ['NYET', 'No response yet from receiver'], + + # Special + '00111100': ['PRE', 'Host-issued preamble; enables downstream bus traffic to low-speed devices'], + '00111100': ['ERR', 'Split transaction error handshake'], + '00011110': ['SPLIT', 'HS split transaction token'], + '00101101': ['PING', 'HS flow control probe for a bulk/control EP'], + '00001111': ['Reserved', 'Reserved PID'], +} + +def bitstr_to_num(bitstr): + if not bitstr: + return 0 + l = list(bitstr) + l.reverse() + return int(''.join(l), 2) + +def packet_decode(packet): + sync = packet[:8] + pid = packet[8:16] + pid = pids.get(pid, (pid, ''))[0] + + # Remove CRC. + if pid in ('OUT', 'IN', 'SOF', 'SETUP'): + data = packet[16:-5] + if pid == 'SOF': + data = str(bitstr_to_num(data)) + else: + dev = bitstr_to_num(data[:7]) + ep = bitstr_to_num(data[7:]) + data = 'DEV %d EP %d' % (dev, ep) + elif pid in ('DATA0', 'DATA1'): + data = packet[16:-16] + tmp = '' + while data: + tmp += '%02x ' % bitstr_to_num(data[:8]) + data = data[8:] + data = tmp + else: + data = packet[16:] + + # The SYNC pattern for low-speed/full-speed is KJKJKJKK (0001). + if sync != '00000001': + return 'SYNC INVALID: %s' % sync + + return pid + ' ' + data + +class Decoder(srd.Decoder): + api_version = 1 + id = 'usb_protocol' + name = 'USB protocol' + longname = 'Universal Serial Bus (LS/FS) protocol' + desc = 'USB 1.x (low-speed and full-speed) serial protocol.' + license = 'gplv2+' + inputs = ['usb_signalling'] + outputs = ['usb_protocol'] + probes = [] + optional_probes = [] + options = { + 'signalling': ['Signalling', 'full-speed'], + } + annotations = [ + ['Text', 'Human-readable text'] + ] + + def __init__(self): + self.sym = 'J' + self.samplenum = 0 + self.scount = 0 + self.packet = '' + self.state = 'IDLE' + + def start(self, metadata): + self.samplerate = metadata['samplerate'] + self.out_proto = self.add(srd.OUTPUT_PROTO, 'usb_protocol') + self.out_ann = self.add(srd.OUTPUT_ANN, 'usb_protocol') + + def report(self): + pass + + def decode(self, ss, es, data): + (ptype, pdata) = data + + if ptype == 'PACKET': + self.put(0, 0, self.out_ann, [0, [packet_decode(pdata)]]) + + # TODO. + diff --git a/decoders/usb_protocol/usb_protocol.py b/decoders/usb_protocol/usb_protocol.py deleted file mode 100644 index b1cd33b..0000000 --- a/decoders/usb_protocol/usb_protocol.py +++ /dev/null @@ -1,137 +0,0 @@ -## -## This file is part of the sigrok project. -## -## Copyright (C) 2011 Gareth McMullin -## 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 -## - -# USB (low-speed and full-speed) protocol decoder - -import sigrokdecode as srd - -# Packet IDs (PIDs). -# The first 4 bits are the 'packet type' field, the last 4 bits are the -# 'check field' (each bit in the check field must be the inverse of the resp. -# bit in the 'packet type' field; if not, that's a 'PID error'). -# For the 4-bit strings, the left-most '1' or '0' is the LSB, i.e. it's sent -# to the bus first. -pids = { - # Tokens - '10000111': ['OUT', 'Address & EP number in host-to-function transaction'], - '10010110': ['IN', 'Address & EP number in function-to-host transaction'], - '10100101': ['SOF', 'Start-Of-Frame marker & frame number'], - '10110100': ['SETUP', 'Address & EP number in host-to-function transaction for SETUP to a control pipe'], - - # Data - # Note: DATA2 and MDATA are HS-only. - '11000011': ['DATA0', 'Data packet PID even'], - '11010010': ['DATA1', 'Data packet PID odd'], - '11100001': ['DATA2', 'Data packet PID HS, high bandwidth isosynchronous transaction in a microframe'], - '11110000': ['MDATA', 'Data packet PID HS for split and high-bandwidth isosynchronous transactions'], - - # Handshake - '01001011': ['ACK', 'Receiver accepts error-free packet'], - '01011010': ['NAK', 'Receiver cannot accept or transmitter cannot send'], - '01111000': ['STALL', 'EP halted or control pipe request unsupported'], - '01101001': ['NYET', 'No response yet from receiver'], - - # Special - '00111100': ['PRE', 'Host-issued preamble; enables downstream bus traffic to low-speed devices'], - '00111100': ['ERR', 'Split transaction error handshake'], - '00011110': ['SPLIT', 'HS split transaction token'], - '00101101': ['PING', 'HS flow control probe for a bulk/control EP'], - '00001111': ['Reserved', 'Reserved PID'], -} - -def bitstr_to_num(bitstr): - if not bitstr: - return 0 - l = list(bitstr) - l.reverse() - return int(''.join(l), 2) - -def packet_decode(packet): - sync = packet[:8] - pid = packet[8:16] - pid = pids.get(pid, (pid, ''))[0] - - # Remove CRC. - if pid in ('OUT', 'IN', 'SOF', 'SETUP'): - data = packet[16:-5] - if pid == 'SOF': - data = str(bitstr_to_num(data)) - else: - dev = bitstr_to_num(data[:7]) - ep = bitstr_to_num(data[7:]) - data = 'DEV %d EP %d' % (dev, ep) - elif pid in ('DATA0', 'DATA1'): - data = packet[16:-16] - tmp = '' - while data: - tmp += '%02x ' % bitstr_to_num(data[:8]) - data = data[8:] - data = tmp - else: - data = packet[16:] - - # The SYNC pattern for low-speed/full-speed is KJKJKJKK (0001). - if sync != '00000001': - return 'SYNC INVALID: %s' % sync - - return pid + ' ' + data - -class Decoder(srd.Decoder): - api_version = 1 - id = 'usb_protocol' - name = 'USB protocol' - longname = 'Universal Serial Bus (LS/FS) protocol' - desc = 'USB 1.x (low-speed and full-speed) serial protocol.' - license = 'gplv2+' - inputs = ['usb_signalling'] - outputs = ['usb_protocol'] - probes = [] - optional_probes = [] - options = { - 'signalling': ['Signalling', 'full-speed'], - } - annotations = [ - ['Text', 'Human-readable text'] - ] - - def __init__(self): - self.sym = 'J' - self.samplenum = 0 - self.scount = 0 - self.packet = '' - self.state = 'IDLE' - - def start(self, metadata): - self.samplerate = metadata['samplerate'] - self.out_proto = self.add(srd.OUTPUT_PROTO, 'usb_protocol') - self.out_ann = self.add(srd.OUTPUT_ANN, 'usb_protocol') - - def report(self): - pass - - def decode(self, ss, es, data): - (ptype, pdata) = data - - if ptype == 'PACKET': - self.put(0, 0, self.out_ann, [0, [packet_decode(pdata)]]) - - # TODO. - diff --git a/decoders/usb_signalling/Makefile.am b/decoders/usb_signalling/Makefile.am index 316c62c..6a54eb3 100644 --- a/decoders/usb_signalling/Makefile.am +++ b/decoders/usb_signalling/Makefile.am @@ -20,7 +20,7 @@ pkgdatadir = $(DECODERS_DIR)/usb_signalling -dist_pkgdata_DATA = __init__.py usb_signalling.py +dist_pkgdata_DATA = __init__.py pd.py CLEANFILES = *.pyc diff --git a/decoders/usb_signalling/__init__.py b/decoders/usb_signalling/__init__.py index 026875c..98ae872 100644 --- a/decoders/usb_signalling/__init__.py +++ b/decoders/usb_signalling/__init__.py @@ -51,5 +51,5 @@ https://en.wikipedia.org/wiki/USB http://www.usb.org/developers/docs/ ''' -from .usb_signalling import * +from .pd import * diff --git a/decoders/usb_signalling/pd.py b/decoders/usb_signalling/pd.py new file mode 100644 index 0000000..3272cbf --- /dev/null +++ b/decoders/usb_signalling/pd.py @@ -0,0 +1,152 @@ +## +## This file is part of the sigrok project. +## +## Copyright (C) 2011 Gareth McMullin +## 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 +## + +# USB signalling (low-speed and full-speed) protocol decoder + +import sigrokdecode as srd + +# Low-/full-speed symbols (used as states of our state machine, too). +# Note: Low-speed J and K are inverted compared to the full-speed J and K! +symbols_ls = { + # (, ): + (0, 0): 'SE0', + (1, 0): 'K', + (0, 1): 'J', + (1, 1): 'SE1', +} +symbols_fs = { + # (, ): + (0, 0): 'SE0', + (1, 0): 'J', + (0, 1): 'K', + (1, 1): 'SE1', +} + +class Decoder(srd.Decoder): + api_version = 1 + id = 'usb_signalling' + name = 'USB signalling' + longname = 'Universal Serial Bus (LS/FS) signalling' + desc = 'USB 1.x (low-speed and full-speed) signalling protocol.' + license = 'gplv2+' + inputs = ['logic'] + outputs = ['usb_signalling'] + probes = [ + {'id': 'dp', 'name': 'D+', 'desc': 'USB D+ signal'}, + {'id': 'dm', 'name': 'D-', 'desc': 'USB D- signal'}, + ] + optional_probes = [] + options = { + 'signalling': ['Signalling', 'full-speed'], + } + annotations = [ + ['Text', 'Human-readable text'] + ] + + def __init__(self): + self.sym = 'J' # The "idle" state is J. + self.samplenum = 0 + self.scount = 0 + self.packet = '' + self.syms = [] + self.oldpins = None + + def start(self, metadata): + self.samplerate = metadata['samplerate'] + self.out_proto = self.add(srd.OUTPUT_PROTO, 'usb_signalling') + self.out_ann = self.add(srd.OUTPUT_ANN, 'usb_signalling') + + def report(self): + pass + + def decode(self, ss, es, data): + for (self.samplenum, pins) in data: + + # Note: self.samplenum is the absolute sample number, whereas + # self.scount only counts the number of samples since the + # last change in the D+/D- lines. + self.scount += 1 + + # Ignore identical samples early on (for performance reasons). + if self.oldpins == pins: + continue + self.oldpins, (dp, dm) = pins, pins + + if self.options['signalling'] == 'low-speed': + sym = symbols_ls[dp, dm] + elif self.options['signalling'] == 'full-speed': + sym = symbols_fs[dp, dm] + + self.put(0, 0, self.out_ann, [0, [sym]]) + self.put(0, 0, self.out_proto, ['SYM', sym]) + + # Wait for a symbol change (i.e., change in D+/D- lines). + if sym == self.sym: + continue + + ## # Debug code: + ## self.syms.append(sym + ' ') + ## if len(self.syms) == 16: + ## self.put(0, 0, self.out_ann, [0, [''.join(self.syms)]]) + ## self.syms = [] + # continue + + # How many bits since the last transition? + if self.packet != '' or self.sym != 'J': + if self.options['signalling'] == 'low-speed': + bitrate = 1500000 # 1.5Mb/s (+/- 1.5%) + elif self.options['signalling'] == 'full-speed': + bitrate = 12000000 # 12Mb/s (+/- 0.25%) + bitcount = int((self.scount - 1) * bitrate / self.samplerate) + else: + bitcount = 0 + + if self.sym == 'SE0': + if bitcount == 1: + # End-Of-Packet (EOP) + # self.put(0, 0, self.out_ann, + # [0, [packet_decode(self.packet), self.packet]]) + if self.packet != '': # FIXME? + self.put(0, 0, self.out_ann, [0, ['PACKET: %s' % self.packet]]) + self.put(0, 0, self.out_proto, ['PACKET', self.packet]) + else: + # Longer than EOP, assume reset. + self.put(0, 0, self.out_ann, [0, ['RESET']]) + self.put(0, 0, self.out_proto, ['RESET', None]) + # self.put(0, 0, self.out_ann, [0, [self.packet]]) + self.scount = 0 + self.sym = sym + self.packet = '' + continue + + # Add bits to the packet string. + self.packet += '1' * bitcount + + # Handle bit stuffing. + if bitcount < 6 and sym != 'SE0': + self.packet += '0' + elif bitcount > 6: + self.put(0, 0, self.out_ann, [0, ['BIT STUFF ERROR']]) + self.put(0, 0, self.out_proto, ['BIT STUFF ERROR', None]) + + self.scount = 0 + self.sym = sym + diff --git a/decoders/usb_signalling/usb_signalling.py b/decoders/usb_signalling/usb_signalling.py deleted file mode 100644 index 3272cbf..0000000 --- a/decoders/usb_signalling/usb_signalling.py +++ /dev/null @@ -1,152 +0,0 @@ -## -## This file is part of the sigrok project. -## -## Copyright (C) 2011 Gareth McMullin -## 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 -## - -# USB signalling (low-speed and full-speed) protocol decoder - -import sigrokdecode as srd - -# Low-/full-speed symbols (used as states of our state machine, too). -# Note: Low-speed J and K are inverted compared to the full-speed J and K! -symbols_ls = { - # (, ): - (0, 0): 'SE0', - (1, 0): 'K', - (0, 1): 'J', - (1, 1): 'SE1', -} -symbols_fs = { - # (, ): - (0, 0): 'SE0', - (1, 0): 'J', - (0, 1): 'K', - (1, 1): 'SE1', -} - -class Decoder(srd.Decoder): - api_version = 1 - id = 'usb_signalling' - name = 'USB signalling' - longname = 'Universal Serial Bus (LS/FS) signalling' - desc = 'USB 1.x (low-speed and full-speed) signalling protocol.' - license = 'gplv2+' - inputs = ['logic'] - outputs = ['usb_signalling'] - probes = [ - {'id': 'dp', 'name': 'D+', 'desc': 'USB D+ signal'}, - {'id': 'dm', 'name': 'D-', 'desc': 'USB D- signal'}, - ] - optional_probes = [] - options = { - 'signalling': ['Signalling', 'full-speed'], - } - annotations = [ - ['Text', 'Human-readable text'] - ] - - def __init__(self): - self.sym = 'J' # The "idle" state is J. - self.samplenum = 0 - self.scount = 0 - self.packet = '' - self.syms = [] - self.oldpins = None - - def start(self, metadata): - self.samplerate = metadata['samplerate'] - self.out_proto = self.add(srd.OUTPUT_PROTO, 'usb_signalling') - self.out_ann = self.add(srd.OUTPUT_ANN, 'usb_signalling') - - def report(self): - pass - - def decode(self, ss, es, data): - for (self.samplenum, pins) in data: - - # Note: self.samplenum is the absolute sample number, whereas - # self.scount only counts the number of samples since the - # last change in the D+/D- lines. - self.scount += 1 - - # Ignore identical samples early on (for performance reasons). - if self.oldpins == pins: - continue - self.oldpins, (dp, dm) = pins, pins - - if self.options['signalling'] == 'low-speed': - sym = symbols_ls[dp, dm] - elif self.options['signalling'] == 'full-speed': - sym = symbols_fs[dp, dm] - - self.put(0, 0, self.out_ann, [0, [sym]]) - self.put(0, 0, self.out_proto, ['SYM', sym]) - - # Wait for a symbol change (i.e., change in D+/D- lines). - if sym == self.sym: - continue - - ## # Debug code: - ## self.syms.append(sym + ' ') - ## if len(self.syms) == 16: - ## self.put(0, 0, self.out_ann, [0, [''.join(self.syms)]]) - ## self.syms = [] - # continue - - # How many bits since the last transition? - if self.packet != '' or self.sym != 'J': - if self.options['signalling'] == 'low-speed': - bitrate = 1500000 # 1.5Mb/s (+/- 1.5%) - elif self.options['signalling'] == 'full-speed': - bitrate = 12000000 # 12Mb/s (+/- 0.25%) - bitcount = int((self.scount - 1) * bitrate / self.samplerate) - else: - bitcount = 0 - - if self.sym == 'SE0': - if bitcount == 1: - # End-Of-Packet (EOP) - # self.put(0, 0, self.out_ann, - # [0, [packet_decode(self.packet), self.packet]]) - if self.packet != '': # FIXME? - self.put(0, 0, self.out_ann, [0, ['PACKET: %s' % self.packet]]) - self.put(0, 0, self.out_proto, ['PACKET', self.packet]) - else: - # Longer than EOP, assume reset. - self.put(0, 0, self.out_ann, [0, ['RESET']]) - self.put(0, 0, self.out_proto, ['RESET', None]) - # self.put(0, 0, self.out_ann, [0, [self.packet]]) - self.scount = 0 - self.sym = sym - self.packet = '' - continue - - # Add bits to the packet string. - self.packet += '1' * bitcount - - # Handle bit stuffing. - if bitcount < 6 and sym != 'SE0': - self.packet += '0' - elif bitcount > 6: - self.put(0, 0, self.out_ann, [0, ['BIT STUFF ERROR']]) - self.put(0, 0, self.out_proto, ['BIT STUFF ERROR', None]) - - self.scount = 0 - self.sym = sym -