From: Uwe Hermann Date: Sun, 15 Jan 2012 14:36:01 +0000 (+0100) Subject: srd: Each PD now has its own subdirectory. X-Git-Tag: libsigrokdecode-0.1.0~132 X-Git-Url: https://sigrok.org/gitaction?a=commitdiff_plain;h=64c29e28e0efa184319f7831b3eca18c7f73f7d0;p=libsigrokdecode.git srd: Each PD now has its own subdirectory. --- diff --git a/configure.ac b/configure.ac index a60a6a9..7acdf40 100644 --- a/configure.ac +++ b/configure.ac @@ -150,6 +150,17 @@ AC_DEFINE(LIBSIGROKDECODE_VERSION, [libsigrokdecode_version], AC_CONFIG_FILES([Makefile libsigrokdecode.pc decoders/Makefile + decoders/dcf77/Makefile + decoders/ddc/Makefile + decoders/ebr30a_i2c_demux/Makefile + decoders/i2c/Makefile + decoders/mx25lxx05d/Makefile + decoders/nunchuk/Makefile + decoders/pan1321/Makefile + decoders/spi/Makefile + decoders/transitioncounter/Makefile + decoders/uart/Makefile + decoders/usb/Makefile ]) AC_OUTPUT diff --git a/decoder.c b/decoder.c index dde067e..957d3a2 100644 --- a/decoder.c +++ b/decoder.c @@ -18,10 +18,10 @@ * along with this program. If not, see . */ +#include #include "config.h" #include "sigrokdecode.h" /* First, so we avoid a _POSIX_C_SOURCE warning. */ #include "sigrokdecode-internal.h" -#include /* The list of protocol decoders. */ GSList *pd_list = NULL; @@ -66,7 +66,7 @@ struct srd_decoder *srd_get_decoder_by_id(const char *id) /** - * Load a protocol decoder module into the embedded python interpreter. + * Load a protocol decoder module into the embedded Python interpreter. * * @param name The module name to be loaded. * @param dec Pointer to the struct srd_decoder filled with the loaded module. @@ -82,17 +82,20 @@ int srd_load_decoder(const char *name, struct srd_decoder **dec) py_basedec = NULL; ret = SRD_ERR; - srd_dbg("loading module %s", name); + + srd_dbg("decoder: %s: loading module '%s'", __func__, name); if (!(d = g_try_malloc0(sizeof(struct srd_decoder)))) { + srd_err("decoder: %s: d malloc failed", __func__); ret = SRD_ERR_MALLOC; goto err_out; } /* Import the Python module. */ if (!(d->py_mod = PyImport_ImportModule(name))) { - /* TODO: report exception message/traceback to err/dbg */ - srd_dbg("import failed"); + /* TODO: Report exception message/traceback to err/dbg. */ + srd_warn("decoder: %s: import of '%s' failed", __func__, name); + PyErr_Print(); PyErr_Clear(); ret = SRD_ERR_PYTHON; goto err_out; @@ -101,6 +104,7 @@ int srd_load_decoder(const char *name, struct srd_decoder **dec) /* Get the 'Decoder' class as Python object. */ if (!(d->py_dec = PyObject_GetAttrString(d->py_mod, "Decoder"))) { /* This generated an AttributeError exception. */ + PyErr_Print(); PyErr_Clear(); srd_err("Decoder class not found in protocol decoder module %s", name); ret = SRD_ERR_PYTHON; @@ -243,27 +247,117 @@ int srd_unload_decoder(struct srd_decoder *dec) return SRD_OK; } +/** + * Check if the directory in the specified search path is a valid PD dir. + * + * A valid sigrok protocol decoder consists of a directory, which contains + * at least two .py files (the special __init__.py and at least one additional + * .py file which contains the actual PD code). + * + * TODO: We should also check that this is not a random other Python module, + * but really a sigrok PD module by some means. + * + * @param search_path A string containing the (absolute) path to the directory + * where 'entry' resides in. + * @param entry A string containing the (relative) directory name of the + * sigrok PD module. E.g. "i2c" for the I2C protocol decoder. + * @return SRD_OK, if the directory is a valid sigrok PD, a negative error + * code, such as SRD_ERR, otherwise. + */ +int srd_is_valid_pd_dir(const gchar *search_path, const gchar *entry) +{ + GDir *dir; + int py_files = 0, has_init_py = 0; + gchar *path1, *path2, *file; + GError *error; + + /* TODO: Error handling. */ + path1 = g_build_filename(search_path, entry, NULL); + + /* Check that it is a directory (and exists). */ + if (!g_file_test(path1, G_FILE_TEST_IS_DIR)) { + srd_dbg("decoder: %s: '%s' not a directory or doesn't exist", + __func__, entry); + return SRD_ERR; + } + + if (!(dir = g_dir_open(path1, 0, &error))) { /* TODO: flags? */ + srd_dbg("decoder: %s: '%s' failed to open directory", + __func__, entry); + return SRD_ERR; + } + + /* Check the contents of the directory. */ + while ((file = g_dir_read_name(dir)) != NULL) { + /* TODO: Error handling. */ + path2 = g_build_filename(path1, file, NULL); + + /* Ignore non-files. */ + if (!g_file_test(path2, G_FILE_TEST_IS_REGULAR)) { + srd_spew("decoder: %s: '%s' not a file, ignoring", + __func__, file); + continue; + } + + /* Count number of .py files. */ + if (g_str_has_suffix(path2, ".py")) { + srd_spew("decoder: %s: found .py file: '%s'", + __func__, file); + py_files++; + } + + /* Check if it's an __init__.py file. */ + if (g_str_has_suffix(path2, "__init__.py")) { + srd_spew("decoder: %s: found __init__.py file: '%s'", + __func__, path2); + has_init_py = 1; + } + } + g_dir_close(dir); + + /* Check if the directory contains >= 2 *.py files. */ + if (py_files < 2) { + srd_dbg("decoder: %s: '%s' is not a valid PD dir, it doesn't " + "contain >= 2 .py files", __func__, entry); + return SRD_ERR; + } + + /* Check if the directory contains an __init__.py file. */ + if (!has_init_py) { + srd_dbg("decoder: %s: '%s' is not a valid PD dir, it doesn't " + "contain an __init__.py file", __func__, entry); + return SRD_ERR; + } + + /* TODO: Check if it's a PD, not a random other Python module. */ + + return SRD_OK; +} int srd_load_all_decoders(void) { - DIR *dir; - struct dirent *dp; + GDir *dir; + gchar *direntry; int ret; char *decodername; struct srd_decoder *dec; + GError *error; - if (!(dir = opendir(DECODERS_DIR))) { + if (!(dir = g_dir_open(DECODERS_DIR, 0, &error))) { /* TODO: flags? */ Py_Finalize(); /* Returns void. */ return SRD_ERR_DECODERS_DIR; } - while ((dp = readdir(dir)) != NULL) { - /* Ignore filenames which don't end with ".py". */ - if (!g_str_has_suffix(dp->d_name, ".py")) + while ((direntry = g_dir_read_name(dir)) != NULL) { + /* Ignore directory entries which are not valid PDs. */ + if (srd_is_valid_pd_dir(DECODERS_DIR, direntry) != SRD_OK) { + srd_dbg("decoder: %s: '%s' not a valid PD dir, " + "ignoring it", __func__, direntry); continue; + } - /* Decoder name == filename (without .py suffix). */ - decodername = g_strndup(dp->d_name, strlen(dp->d_name) - 3); + /* The decoder name is the PD directory name (e.g. "i2c"). */ + decodername = g_strdup(direntry); /* TODO: Error handling. Use g_try_malloc(). */ if (!(dec = malloc(sizeof(struct srd_decoder)))) { @@ -278,12 +372,11 @@ int srd_load_all_decoders(void) pd_list = g_slist_append(pd_list, dec); } } - closedir(dir); + g_dir_close(dir); return SRD_OK; } - /** * TODO */ diff --git a/decoders/Makefile.am b/decoders/Makefile.am index 7c703ee..c1726cf 100644 --- a/decoders/Makefile.am +++ b/decoders/Makefile.am @@ -18,21 +18,17 @@ ## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA ## -pkgdatadir = $(DECODERS_DIR) - # Please keep this list in alphabetical order. -dist_pkgdata_DATA = \ - dcf77.py \ - ddc.py \ - i2c.py \ - mx25lxx05d.py \ - nunchuk.py \ - pan1321.py \ - spi.py \ - usb.py \ - transitioncounter.py \ - ebr30a_i2c_demux.py \ - uart.py - -CLEANFILES = *.pyc +SUBDIRS = \ + dcf77 \ + ddc \ + ebr30a_i2c_demux \ + i2c \ + mx25lxx05d \ + nunchuk \ + pan1321 \ + spi \ + transitioncounter \ + uart \ + usb diff --git a/decoders/dcf77.py b/decoders/dcf77.py deleted file mode 100644 index 48b3bba..0000000 --- a/decoders/dcf77.py +++ /dev/null @@ -1,277 +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 -# -# More information: -# http://en.wikipedia.org/wiki/DCF77 -# - -# -# Protocol output format: -# TODO -# - -import sigrokdecode as srd -import calendar - -# States -WAIT_FOR_RISING_EDGE = 0 -GET_BIT = 1 - -# Annotation feed formats -ANN_ASCII = 0 - -# Return the specified BCD number (max. 8 bits) as integer. -def bcd2int(b): - return (b & 0x0f) + ((b >> 4) * 10) - -class Decoder(srd.Decoder): - id = 'dcf77' - name = 'DCF77' - longname = 'DCF77 time protocol' - desc = 'TODO.' - longdesc = 'TODO.' - license = 'gplv2+' - inputs = ['logic'] - outputs = ['dcf77'] - probes = [ - {'id': 'data', 'name': 'DATA', 'desc': 'DATA line'}, - ] - options = {} - annotations = [ - # ANN_ASCII - ['ASCII', 'TODO: description'], - ] - - def __init__(self, **kwargs): - self.state = WAIT_FOR_RISING_EDGE - self.oldval = 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 samplenum, (pon, val) in data: # FIXME - - self.samplenum += 1 # FIXME. Use samplenum. Off-by-one? - - 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? - - # TODO: 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: %s' % self.state) - - self.oldval = val - diff --git a/decoders/dcf77/Makefile.am b/decoders/dcf77/Makefile.am new file mode 100644 index 0000000..c55f0b7 --- /dev/null +++ b/decoders/dcf77/Makefile.am @@ -0,0 +1,26 @@ +## +## 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 +## + +pkgdatadir = $(DECODERS_DIR)/dcf77 + +dist_pkgdata_DATA = __init__.py dcf77.py + +CLEANFILES = *.pyc + diff --git a/decoders/dcf77/__init__.py b/decoders/dcf77/__init__.py new file mode 100644 index 0000000..db7d2de --- /dev/null +++ b/decoders/dcf77/__init__.py @@ -0,0 +1,22 @@ +## +## 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 +## + +from .dcf77 import * + diff --git a/decoders/dcf77/dcf77.py b/decoders/dcf77/dcf77.py new file mode 100644 index 0000000..48b3bba --- /dev/null +++ b/decoders/dcf77/dcf77.py @@ -0,0 +1,277 @@ +## +## 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 +# +# More information: +# http://en.wikipedia.org/wiki/DCF77 +# + +# +# Protocol output format: +# TODO +# + +import sigrokdecode as srd +import calendar + +# States +WAIT_FOR_RISING_EDGE = 0 +GET_BIT = 1 + +# Annotation feed formats +ANN_ASCII = 0 + +# Return the specified BCD number (max. 8 bits) as integer. +def bcd2int(b): + return (b & 0x0f) + ((b >> 4) * 10) + +class Decoder(srd.Decoder): + id = 'dcf77' + name = 'DCF77' + longname = 'DCF77 time protocol' + desc = 'TODO.' + longdesc = 'TODO.' + license = 'gplv2+' + inputs = ['logic'] + outputs = ['dcf77'] + probes = [ + {'id': 'data', 'name': 'DATA', 'desc': 'DATA line'}, + ] + options = {} + annotations = [ + # ANN_ASCII + ['ASCII', 'TODO: description'], + ] + + def __init__(self, **kwargs): + self.state = WAIT_FOR_RISING_EDGE + self.oldval = 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 samplenum, (pon, val) in data: # FIXME + + self.samplenum += 1 # FIXME. Use samplenum. Off-by-one? + + 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? + + # TODO: 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: %s' % self.state) + + self.oldval = val + diff --git a/decoders/ddc.py b/decoders/ddc.py deleted file mode 100644 index f839093..0000000 --- a/decoders/ddc.py +++ /dev/null @@ -1,74 +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, If not, see . -## - -''' -This decoder extracts a DDC stream from an I2C session between a computer -and a display device. The stream is output as plain bytes. - -Details: -https://en.wikipedia.org/wiki/Display_Data_Channel -''' - -import sigrokdecode as srd - -class Decoder(srd.Decoder): - id = 'ddc' - name = 'DDC' - longname = 'Display Data Channel' - desc = 'A protocol for communication between computers and displays.' - longdesc = '' - license = 'gplv3+' - inputs = ['i2c'] - outputs = ['ddc'] - probes = [] - options = {} - annotations = [ - ['Byte stream', 'DDC byte stream as read from display.'], - ] - - def __init__(self, **kwargs): - self.state = None - - def start(self, metadata): - self.out_ann = self.add(srd.OUTPUT_ANN, 'ddc') - - def decode(self, ss, es, data): - try: - cmd, data, ack_bit = data - except Exception as e: - raise Exception('malformed I2C input: %s' % str(e)) from e - - if self.state is None: - # Wait for the DDC session to start. - if cmd in ('START', 'START REPEAT'): - self.state = 'start' - elif self.state == 'start': - if cmd == 'ADDRESS READ' and data == 80: - # 80 is the I2C slave address of a connected display, - # so this marks the start of the DDC data transfer. - self.state = 'transfer' - elif cmd == 'STOP': - # Got back to the idle state. - self.state = None - elif self.state == 'transfer': - if cmd == 'DATA READ': - # There shouldn't be anything but data reads on this - # address, so ignore everything else. - self.put(ss, es, self.out_ann, [0, ['0x%.2x' % data]]) - diff --git a/decoders/ddc/Makefile.am b/decoders/ddc/Makefile.am new file mode 100644 index 0000000..59c2c69 --- /dev/null +++ b/decoders/ddc/Makefile.am @@ -0,0 +1,26 @@ +## +## 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 +## + +pkgdatadir = $(DECODERS_DIR)/ddc + +dist_pkgdata_DATA = __init__.py ddc.py + +CLEANFILES = *.pyc + diff --git a/decoders/ddc/__init__.py b/decoders/ddc/__init__.py new file mode 100644 index 0000000..95cf31e --- /dev/null +++ b/decoders/ddc/__init__.py @@ -0,0 +1,22 @@ +## +## 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 +## + +from .ddc import * + diff --git a/decoders/ddc/ddc.py b/decoders/ddc/ddc.py new file mode 100644 index 0000000..f839093 --- /dev/null +++ b/decoders/ddc/ddc.py @@ -0,0 +1,74 @@ +## +## 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, If not, see . +## + +''' +This decoder extracts a DDC stream from an I2C session between a computer +and a display device. The stream is output as plain bytes. + +Details: +https://en.wikipedia.org/wiki/Display_Data_Channel +''' + +import sigrokdecode as srd + +class Decoder(srd.Decoder): + id = 'ddc' + name = 'DDC' + longname = 'Display Data Channel' + desc = 'A protocol for communication between computers and displays.' + longdesc = '' + license = 'gplv3+' + inputs = ['i2c'] + outputs = ['ddc'] + probes = [] + options = {} + annotations = [ + ['Byte stream', 'DDC byte stream as read from display.'], + ] + + def __init__(self, **kwargs): + self.state = None + + def start(self, metadata): + self.out_ann = self.add(srd.OUTPUT_ANN, 'ddc') + + def decode(self, ss, es, data): + try: + cmd, data, ack_bit = data + except Exception as e: + raise Exception('malformed I2C input: %s' % str(e)) from e + + if self.state is None: + # Wait for the DDC session to start. + if cmd in ('START', 'START REPEAT'): + self.state = 'start' + elif self.state == 'start': + if cmd == 'ADDRESS READ' and data == 80: + # 80 is the I2C slave address of a connected display, + # so this marks the start of the DDC data transfer. + self.state = 'transfer' + elif cmd == 'STOP': + # Got back to the idle state. + self.state = None + elif self.state == 'transfer': + if cmd == 'DATA READ': + # There shouldn't be anything but data reads on this + # address, so ignore everything else. + self.put(ss, es, self.out_ann, [0, ['0x%.2x' % data]]) + diff --git a/decoders/ebr30a_i2c_demux.py b/decoders/ebr30a_i2c_demux.py deleted file mode 100644 index 353fea8..0000000 --- a/decoders/ebr30a_i2c_demux.py +++ /dev/null @@ -1,104 +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 -## - -# -# TrekStor EBR30-a I2C demux protocol decoder -# -# Takes an I2C stream as input and outputs 3 different I2C streams, for the -# 3 different I2C devices on the TrekStor EBR30-a eBook reader (which are all -# physically connected to the same SCL/SDA lines). -# -# I2C slave addresses: -# -# - AXP199 battery management chip: 0x69/0x68 (8bit R/W), 0x34 (7bit) -# - H8563S RTC chip: 0xa3/0xa2 (8bit R/W), 0x51 (7bit) -# - Unknown accelerometer chip: 0x2b/0x2a (8bit R/W), 0x15 (7bit) -# - -import sigrokdecode as srd - -# I2C devices -AXP199 = 0 -H8563S = 1 -ACCEL = 2 - -class Decoder(srd.Decoder): - id = 'ebr30a_i2c_demux' - name = 'EBR30-a I2C demux' - longname = 'TrekStor EBR30-a I2C demux' - desc = 'TODO.' - longdesc = 'TODO.' - license = 'gplv2+' - inputs = ['i2c'] - outputs = ['i2c-axp199', 'i2c-h8563s', 'i2c-accel'] - probes = [] - options = {} - annotations = [] - - def __init__(self, **kwargs): - self.packets = [] - self.stream = -1 - - def start(self, metadata): - self.out_proto = [] - self.out_proto.append(self.add(srd.OUTPUT_PROTO, 'i2c-axp199')) - self.out_proto.append(self.add(srd.OUTPUT_PROTO, 'i2c-h8563s')) - self.out_proto.append(self.add(srd.OUTPUT_PROTO, 'i2c-accel')) - # TODO: Annotations? - - 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, ack_bit = data - - # Add the I2C packet to our local cache. - self.packets += [[ss, es, data]] - - if cmd in ('ADDRESS READ', 'ADDRESS WRITE'): - # print(hex(databyte)) - if databyte == 0x34: - self.stream = AXP199 - elif databyte == 0x51: - self.stream = H8563S - elif databyte == 0x15: - self.stream = ACCEL - else: - pass # TODO: Error? - # TODO: Can there be two ADDRESS READ/WRITE with two different - # slave addresses before any STOP occurs? - elif cmd == 'STOP': - if self.stream != -1: - # Send the whole chunk of I2C packets to the correct stream. - for p in self.packets: - # print(self.out_proto[self.stream], p) - self.put(p[0], p[1], self.out_proto[self.stream], p[2]) - else: - print('Error: Could not determine correct stream!') # FIXME - self.packets = [] - self.stream = -1 - diff --git a/decoders/ebr30a_i2c_demux/Makefile.am b/decoders/ebr30a_i2c_demux/Makefile.am new file mode 100644 index 0000000..02e3421 --- /dev/null +++ b/decoders/ebr30a_i2c_demux/Makefile.am @@ -0,0 +1,26 @@ +## +## 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 +## + +pkgdatadir = $(DECODERS_DIR)/ebr30a_i2c_demux + +dist_pkgdata_DATA = __init__.py ebr30a_i2c_demux.py + +CLEANFILES = *.pyc + diff --git a/decoders/ebr30a_i2c_demux/__init__.py b/decoders/ebr30a_i2c_demux/__init__.py new file mode 100644 index 0000000..dddc8b7 --- /dev/null +++ b/decoders/ebr30a_i2c_demux/__init__.py @@ -0,0 +1,22 @@ +## +## 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 +## + +from .ebr30a_i2c_demux import * + diff --git a/decoders/ebr30a_i2c_demux/ebr30a_i2c_demux.py b/decoders/ebr30a_i2c_demux/ebr30a_i2c_demux.py new file mode 100644 index 0000000..353fea8 --- /dev/null +++ b/decoders/ebr30a_i2c_demux/ebr30a_i2c_demux.py @@ -0,0 +1,104 @@ +## +## 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 +## + +# +# TrekStor EBR30-a I2C demux protocol decoder +# +# Takes an I2C stream as input and outputs 3 different I2C streams, for the +# 3 different I2C devices on the TrekStor EBR30-a eBook reader (which are all +# physically connected to the same SCL/SDA lines). +# +# I2C slave addresses: +# +# - AXP199 battery management chip: 0x69/0x68 (8bit R/W), 0x34 (7bit) +# - H8563S RTC chip: 0xa3/0xa2 (8bit R/W), 0x51 (7bit) +# - Unknown accelerometer chip: 0x2b/0x2a (8bit R/W), 0x15 (7bit) +# + +import sigrokdecode as srd + +# I2C devices +AXP199 = 0 +H8563S = 1 +ACCEL = 2 + +class Decoder(srd.Decoder): + id = 'ebr30a_i2c_demux' + name = 'EBR30-a I2C demux' + longname = 'TrekStor EBR30-a I2C demux' + desc = 'TODO.' + longdesc = 'TODO.' + license = 'gplv2+' + inputs = ['i2c'] + outputs = ['i2c-axp199', 'i2c-h8563s', 'i2c-accel'] + probes = [] + options = {} + annotations = [] + + def __init__(self, **kwargs): + self.packets = [] + self.stream = -1 + + def start(self, metadata): + self.out_proto = [] + self.out_proto.append(self.add(srd.OUTPUT_PROTO, 'i2c-axp199')) + self.out_proto.append(self.add(srd.OUTPUT_PROTO, 'i2c-h8563s')) + self.out_proto.append(self.add(srd.OUTPUT_PROTO, 'i2c-accel')) + # TODO: Annotations? + + 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, ack_bit = data + + # Add the I2C packet to our local cache. + self.packets += [[ss, es, data]] + + if cmd in ('ADDRESS READ', 'ADDRESS WRITE'): + # print(hex(databyte)) + if databyte == 0x34: + self.stream = AXP199 + elif databyte == 0x51: + self.stream = H8563S + elif databyte == 0x15: + self.stream = ACCEL + else: + pass # TODO: Error? + # TODO: Can there be two ADDRESS READ/WRITE with two different + # slave addresses before any STOP occurs? + elif cmd == 'STOP': + if self.stream != -1: + # Send the whole chunk of I2C packets to the correct stream. + for p in self.packets: + # print(self.out_proto[self.stream], p) + self.put(p[0], p[1], self.out_proto[self.stream], p[2]) + else: + print('Error: Could not determine correct stream!') # FIXME + self.packets = [] + self.stream = -1 + diff --git a/decoders/i2c.py b/decoders/i2c.py deleted file mode 100644 index f0a95b7..0000000 --- a/decoders/i2c.py +++ /dev/null @@ -1,304 +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 -# - -# -# The Inter-Integrated Circuit (I2C) bus is a bidirectional, multi-master -# bus using two signals (SCL = serial clock line, SDA = serial data line). -# -# There can be many devices on the same bus. Each device can potentially be -# master or slave (and that can change during runtime). Both slave and master -# can potentially play the transmitter or receiver role (this can also -# change at runtime). -# -# Possible maximum data rates: -# - Standard mode: 100 kbit/s -# - Fast mode: 400 kbit/s -# - Fast-mode Plus: 1 Mbit/s -# - High-speed mode: 3.4 Mbit/s -# -# START condition (S): SDA = falling, SCL = high -# Repeated START condition (Sr): same as S -# Data bit sampling: SCL = rising -# STOP condition (P): SDA = rising, SCL = high -# -# All data bytes on SDA are exactly 8 bits long (transmitted MSB-first). -# Each byte has to be followed by a 9th ACK/NACK bit. If that bit is low, -# that indicates an ACK, if it's high that indicates a NACK. -# -# After the first START condition, a master sends the device address of the -# slave it wants to talk to. Slave addresses are 7 bits long (MSB-first). -# After those 7 bits, a data direction bit is sent. If the bit is low that -# indicates a WRITE operation, if it's high that indicates a READ operation. -# -# Later an optional 10bit slave addressing scheme was added. -# -# Documentation: -# http://www.nxp.com/acrobat/literature/9398/39340011.pdf (v2.1 spec) -# http://www.nxp.com/acrobat/usermanuals/UM10204_3.pdf (v3 spec) -# http://en.wikipedia.org/wiki/I2C -# - -# 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. - -''' -Protocol output format: - -I2C packet: -[, , ] - - is one of: - - 'START' (START condition) - - 'START REPEAT' (Repeated START) - - 'ADDRESS READ' (Address, read) - - 'ADDRESS WRITE' (Address, write) - - 'DATA READ' (Data, read) - - 'DATA WRITE' (Data, write) - - 'STOP' (STOP condition) - - is the data or address byte associated with the 'ADDRESS*' and 'DATA*' -command. For 'START', 'START REPEAT' and 'STOP', this is None. - - is either 'ACK' or 'NACK', but may also be None. -''' - -import sigrokdecode as srd - -# Annotation feed formats -ANN_SHIFTED = 0 -ANN_SHIFTED_SHORT = 1 -ANN_RAW = 2 - -# Values are verbose and short annotation, respectively. -protocol = { - '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'], -} - -# States -FIND_START = 0 -FIND_ADDRESS = 1 -FIND_DATA = 2 - -class Decoder(srd.Decoder): - id = 'i2c' - name = 'I2C' - longname = 'Inter-Integrated Circuit' - desc = 'I2C is a two-wire, multi-master, serial bus.' - longdesc = '...' - license = 'gplv2+' - inputs = ['logic'] - outputs = ['i2c'] - probes = [ - {'id': 'scl', 'name': 'SCL', 'desc': 'Serial clock line'}, - {'id': 'sda', 'name': 'SDA', 'desc': 'Serial data line'}, - ] - 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.samplecnt = 0 - self.bitcount = 0 - self.databyte = 0 - self.wr = -1 - self.startsample = -1 - self.is_repeat_start = 0 - self.state = FIND_START - self.oldscl = None - self.oldsda = None - - # Set protocol decoder option defaults. - self.addressing = Decoder.options['addressing'][1] - - 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): - cmd = 'START REPEAT' if (self.is_repeat_start == 1) else 'START' - - self.put(self.out_proto, [cmd, None, None]) - self.put(self.out_ann, [ANN_SHIFTED, [protocol[cmd][0]]]) - self.put(self.out_ann, [ANN_SHIFTED_SHORT, [protocol[cmd][1]]]) - - self.state = FIND_ADDRESS - self.bitcount = self.databyte = 0 - self.is_repeat_start = 1 - self.wr = -1 - - def found_address_or_data(self, scl, sda): - # Gather 8 bits of data plus the ACK/NACK bit. - - if self.startsample == -1: - # TODO: Should be samplenum, as received from the feed. - self.startsample = self.samplecnt - self.bitcount += 1 - - # Address and data are transmitted MSB-first. - self.databyte <<= 1 - self.databyte |= sda - - # Return if we haven't collected all 8 + 1 bits, yet. - if self.bitcount != 9: - return - - # 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]]) - - # We received 8 address/data bits and the ACK/NACK bit. - self.databyte >>= 1 # Shift out unwanted ACK/NACK bit here. - - 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 - else: - # TODO: Error? - pass - - # Last bit that came in was the ACK/NACK bit (1 = NACK). - ack_bit = 'NACK' if (sda == 1) else 'ACK' - - 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, ack_bit]) - self.put(self.out_ann, [ANN_SHIFTED, - [protocol[cmd][0], '0x%02x' % d, protocol[ack_bit][0]]]) - self.put(self.out_ann, [ANN_SHIFTED_SHORT, - [protocol[cmd][1], '0x%02x' % d, protocol[ack_bit][1]]]) - - self.bitcount = self.databyte = 0 - self.startsample = -1 - - if self.state == FIND_ADDRESS: - self.state = FIND_DATA - elif self.state == FIND_DATA: - # There could be multiple data bytes in a row. - # So, either find a STOP condition or another data byte next. - pass - - def found_stop(self, scl, sda): - self.put(self.out_proto, ['STOP', None, None]) - self.put(self.out_ann, [ANN_SHIFTED, [protocol['STOP'][0]]]) - self.put(self.out_ann, [ANN_SHIFTED_SHORT, [protocol['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. - # TODO: 0-0 sample range for now. - super(Decoder, self).put(0, 0, output_id, data) - - def decode(self, ss, es, data): - for samplenum, (scl, sda) in data: - self.samplecnt += 1 - - # 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) - else: - # TODO: Error? - pass - - # Save current SDA/SCL values for the next round. - self.oldscl = scl - self.oldsda = sda - diff --git a/decoders/i2c/Makefile.am b/decoders/i2c/Makefile.am new file mode 100644 index 0000000..75a53eb --- /dev/null +++ b/decoders/i2c/Makefile.am @@ -0,0 +1,26 @@ +## +## 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 +## + +pkgdatadir = $(DECODERS_DIR)/i2c + +dist_pkgdata_DATA = __init__.py i2c.py + +CLEANFILES = *.pyc + diff --git a/decoders/i2c/__init__.py b/decoders/i2c/__init__.py new file mode 100644 index 0000000..9e7856e --- /dev/null +++ b/decoders/i2c/__init__.py @@ -0,0 +1,22 @@ +## +## 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 +## + +from .i2c import * + diff --git a/decoders/i2c/i2c.py b/decoders/i2c/i2c.py new file mode 100644 index 0000000..f0a95b7 --- /dev/null +++ b/decoders/i2c/i2c.py @@ -0,0 +1,304 @@ +## +## 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 +# + +# +# The Inter-Integrated Circuit (I2C) bus is a bidirectional, multi-master +# bus using two signals (SCL = serial clock line, SDA = serial data line). +# +# There can be many devices on the same bus. Each device can potentially be +# master or slave (and that can change during runtime). Both slave and master +# can potentially play the transmitter or receiver role (this can also +# change at runtime). +# +# Possible maximum data rates: +# - Standard mode: 100 kbit/s +# - Fast mode: 400 kbit/s +# - Fast-mode Plus: 1 Mbit/s +# - High-speed mode: 3.4 Mbit/s +# +# START condition (S): SDA = falling, SCL = high +# Repeated START condition (Sr): same as S +# Data bit sampling: SCL = rising +# STOP condition (P): SDA = rising, SCL = high +# +# All data bytes on SDA are exactly 8 bits long (transmitted MSB-first). +# Each byte has to be followed by a 9th ACK/NACK bit. If that bit is low, +# that indicates an ACK, if it's high that indicates a NACK. +# +# After the first START condition, a master sends the device address of the +# slave it wants to talk to. Slave addresses are 7 bits long (MSB-first). +# After those 7 bits, a data direction bit is sent. If the bit is low that +# indicates a WRITE operation, if it's high that indicates a READ operation. +# +# Later an optional 10bit slave addressing scheme was added. +# +# Documentation: +# http://www.nxp.com/acrobat/literature/9398/39340011.pdf (v2.1 spec) +# http://www.nxp.com/acrobat/usermanuals/UM10204_3.pdf (v3 spec) +# http://en.wikipedia.org/wiki/I2C +# + +# 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. + +''' +Protocol output format: + +I2C packet: +[, , ] + + is one of: + - 'START' (START condition) + - 'START REPEAT' (Repeated START) + - 'ADDRESS READ' (Address, read) + - 'ADDRESS WRITE' (Address, write) + - 'DATA READ' (Data, read) + - 'DATA WRITE' (Data, write) + - 'STOP' (STOP condition) + + is the data or address byte associated with the 'ADDRESS*' and 'DATA*' +command. For 'START', 'START REPEAT' and 'STOP', this is None. + + is either 'ACK' or 'NACK', but may also be None. +''' + +import sigrokdecode as srd + +# Annotation feed formats +ANN_SHIFTED = 0 +ANN_SHIFTED_SHORT = 1 +ANN_RAW = 2 + +# Values are verbose and short annotation, respectively. +protocol = { + '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'], +} + +# States +FIND_START = 0 +FIND_ADDRESS = 1 +FIND_DATA = 2 + +class Decoder(srd.Decoder): + id = 'i2c' + name = 'I2C' + longname = 'Inter-Integrated Circuit' + desc = 'I2C is a two-wire, multi-master, serial bus.' + longdesc = '...' + license = 'gplv2+' + inputs = ['logic'] + outputs = ['i2c'] + probes = [ + {'id': 'scl', 'name': 'SCL', 'desc': 'Serial clock line'}, + {'id': 'sda', 'name': 'SDA', 'desc': 'Serial data line'}, + ] + 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.samplecnt = 0 + self.bitcount = 0 + self.databyte = 0 + self.wr = -1 + self.startsample = -1 + self.is_repeat_start = 0 + self.state = FIND_START + self.oldscl = None + self.oldsda = None + + # Set protocol decoder option defaults. + self.addressing = Decoder.options['addressing'][1] + + 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): + cmd = 'START REPEAT' if (self.is_repeat_start == 1) else 'START' + + self.put(self.out_proto, [cmd, None, None]) + self.put(self.out_ann, [ANN_SHIFTED, [protocol[cmd][0]]]) + self.put(self.out_ann, [ANN_SHIFTED_SHORT, [protocol[cmd][1]]]) + + self.state = FIND_ADDRESS + self.bitcount = self.databyte = 0 + self.is_repeat_start = 1 + self.wr = -1 + + def found_address_or_data(self, scl, sda): + # Gather 8 bits of data plus the ACK/NACK bit. + + if self.startsample == -1: + # TODO: Should be samplenum, as received from the feed. + self.startsample = self.samplecnt + self.bitcount += 1 + + # Address and data are transmitted MSB-first. + self.databyte <<= 1 + self.databyte |= sda + + # Return if we haven't collected all 8 + 1 bits, yet. + if self.bitcount != 9: + return + + # 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]]) + + # We received 8 address/data bits and the ACK/NACK bit. + self.databyte >>= 1 # Shift out unwanted ACK/NACK bit here. + + 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 + else: + # TODO: Error? + pass + + # Last bit that came in was the ACK/NACK bit (1 = NACK). + ack_bit = 'NACK' if (sda == 1) else 'ACK' + + 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, ack_bit]) + self.put(self.out_ann, [ANN_SHIFTED, + [protocol[cmd][0], '0x%02x' % d, protocol[ack_bit][0]]]) + self.put(self.out_ann, [ANN_SHIFTED_SHORT, + [protocol[cmd][1], '0x%02x' % d, protocol[ack_bit][1]]]) + + self.bitcount = self.databyte = 0 + self.startsample = -1 + + if self.state == FIND_ADDRESS: + self.state = FIND_DATA + elif self.state == FIND_DATA: + # There could be multiple data bytes in a row. + # So, either find a STOP condition or another data byte next. + pass + + def found_stop(self, scl, sda): + self.put(self.out_proto, ['STOP', None, None]) + self.put(self.out_ann, [ANN_SHIFTED, [protocol['STOP'][0]]]) + self.put(self.out_ann, [ANN_SHIFTED_SHORT, [protocol['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. + # TODO: 0-0 sample range for now. + super(Decoder, self).put(0, 0, output_id, data) + + def decode(self, ss, es, data): + for samplenum, (scl, sda) in data: + self.samplecnt += 1 + + # 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) + else: + # TODO: Error? + pass + + # Save current SDA/SCL values for the next round. + self.oldscl = scl + self.oldsda = sda + diff --git a/decoders/mx25lxx05d.py b/decoders/mx25lxx05d.py deleted file mode 100644 index cd97dee..0000000 --- a/decoders/mx25lxx05d.py +++ /dev/null @@ -1,272 +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 decoder. -# Works for MX25L1605D/MX25L3205D/MX25L6405D. -# - -# -# TODO: Description -# -# Details: -# http://www.macronix.com/QuickPlace/hq/PageLibrary4825740B00298A3B.nsf/h_Index/3F21BAC2E121E17848257639003A3146/$File/MX25L1605D-3205D-6405D-1.5.pdf -# - -import sigrokdecode as srd - -# States -IDLE = -1 - -# Chip commands (also used as additional decoder states). -CMD_WREN = 0x06 -CMD_WRDI = 0x04 -CMD_RDID = 0x9f -CMD_RDSR = 0x05 -CMD_WRSR = 0x01 -CMD_READ = 0x03 -CMD_FAST_READ = 0x0b -CMD_2READ = 0xbb -CMD_SE = 0x20 -CMD_BE = 0xd8 -CMD_CE = 0x60 -CMD_CE2 = 0xc7 -CMD_PP = 0x02 -CMD_CP = 0xad -CMD_DP = 0xb9 -# CMD_RDP = 0xab -# CMD_RES = 0xab -CMD_RDP_RES = 0xab # Note: RDP/RES have the same ID. -CMD_REMS = 0x90 -CMD_REMS2 = 0xef -CMD_ENSO = 0xb1 -CMD_EXSO = 0xc1 -CMD_RDSCUR = 0x2b -CMD_WRSCUR = 0x2f -CMD_ESRY = 0x70 -CMD_DSRY = 0x80 - -# TODO: (Short) command names as strings in a dict, too? - -# Dict which maps command IDs to their description. -cmds = { - CMD_WREN: 'Write enable', - CMD_WRDI: 'Write disable', - CMD_RDID: 'Read identification', - CMD_RDSR: 'Read status register', - CMD_WRSR: 'Write status register', - CMD_READ: 'Read data', - CMD_FAST_READ: 'Fast read data', - CMD_2READ: '2x I/O read', - CMD_SE: 'Sector erase', - CMD_BE: 'Block erase', - CMD_CE: 'Chip erase', - CMD_CE2: 'Chip erase', # Alternative command ID - CMD_PP: 'Page program', - CMD_CP: 'Continuously program mode', - CMD_DP: 'Deep power down', - # CMD_RDP: 'Release from deep powerdown', - # CMD_RES: 'Read electronic ID', - CMD_RDP_RES: 'Release from deep powerdown / Read electronic ID', - CMD_REMS: 'Read electronic manufacturer & device ID', - CMD_REMS2: 'Read ID for 2x I/O mode', - CMD_ENSO: 'Enter secured OTP', - CMD_EXSO: 'Exit secured OTP', - CMD_RDSCUR: 'Read security register', - CMD_WRSCUR: 'Write security register', - CMD_ESRY: 'Enable SO to output RY/BY#', - CMD_DSRY: 'Disable SO to output RY/BY#', -} - -device_name = { - 0x14: 'MX25L1605D', - 0x15: 'MX25L3205D', - 0x16: 'MX25L6405D', -} - -class Decoder(srd.Decoder): - id = 'mx25lxx05d' - name = 'MX25Lxx05D' - longname = 'Macronix MX25Lxx05D' - desc = 'Macronix MX25Lxx05D SPI flash chip decoder' - longdesc = 'TODO' - license = 'gplv2+' - inputs = ['spi', 'spi', 'logic'] - outputs = ['mx25lxx05d'] - probes = [] # TODO: HOLD#, WP#/ACC - options = {} # TODO - annotations = [ - ['TODO', 'TODO'], - ] - - def __init__(self, **kwargs): - self.state = IDLE - self.cmdstate = 1 # TODO - - 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 putann(self, data): - # Simplification, most annotations span extactly one SPI byte/packet. - self.put(self.ss, self.es, self.out_ann, data) - - def handle_wren(self, mosi, miso): - self.putann([0, ['Command: %s' % cmds[self.cmd]]]) - self.state = IDLE - - # 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.putann([0, ['Command: %s' % cmds[self.cmd]]]) - elif self.cmdstate == 2: - # Byte 2: Slave sends the JEDEC manufacturer ID. - self.putann([0, ['Manufacturer ID: 0x%02x' % miso]]) - elif self.cmdstate == 3: - # Byte 3: Slave sends the memory type (0x20 for this chip). - self.putann([0, ['Memory type: 0x%02x' % miso]]) - elif self.cmdstate == 4: - # Byte 4: Slave sends the device ID. - self.device_id = miso - self.putann([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 = IDLE - else: - self.cmdstate += 1 - - # TODO: Warn/abort if we don't see the necessary amount of bytes. - # TODO: Warn if WREN was not seen before. - def handle_se(self, mosi, miso): - if self.cmdstate == 1: - # Byte 1: Master sends command ID. - self.addr = 0 - self.start_sample = self.ss - self.putann([0, ['Command: %s' % cmds[self.cmd]]]) - elif self.cmdstate in (2, 3, 4): - # Bytes 2/3/4: Master sends address of the sector to erase. - # Note: Assumes SPI data is 8 bits wide (it is for MX25Lxx05D). - # TODO: LSB-first of MSB-first? - self.addr <<= 8 - self.addr |= mosi - self.putann([0, ['Address byte %d: 0x%02x' % (self.cmdstate - 1, - miso)]]) # TODO: Count from 0 or 1? - - if self.cmdstate == 4: - d = 'Erase sector %d' % 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!' # TODO: type == WARN? - self.put(self.start_sample, self.es, self.out_ann, [0, [d]]) - self.state = IDLE - else: - self.cmdstate += 1 - - def handle_rems(self, mosi, miso): - if self.cmdstate == 1: - # Byte 1: Master sends command ID. - self.start_sample = self.ss - self.putann([0, ['Command: %s' % cmds[self.cmd]]]) - elif self.cmdstate in (2, 3): - # Bytes 2/3: Master sends two dummy bytes. - # TODO: Check dummy bytes? Check reply from device? - self.putann([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.putann([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.putann([0, ['%s ID' % d]]) - elif self.cmdstate == 6: - # Byte 6: Slave sends device ID (or manufacturer ID). - self.ids += [miso] - d = 'Manufacturer' if self.manufacturer_id_first else 'Device' - self.putann([0, ['%s ID' % d]]) - else: - # TODO: Error? - pass - - if self.cmdstate == 6: - self.end_sample = self.es - id = self.ids[1] if self.manufacturer_id_first else self.ids[0] - self.putann([0, ['Device: Macronix %s' % device_name[id]]]) - self.state = IDLE - else: - self.cmdstate += 1 - - def handle_rdsr(self, mosi, miso): - self.putann([0, ['Command: %s (0x%02x)' % (cmds[self.cmd], miso)]]) - self.state = IDLE - - def decode(self, ss, es, data): - - ptype, mosi, miso = data - - if ptype != 'data': - return - - cmd = mosi - self.ss = ss - self.es = es - - # If we encountered a known chip command, enter the resp. state. - if self.state == IDLE: - if cmd in cmds: - self.state = cmd - self.cmd = cmd # TODO: Eliminate? - self.cmdstate = 1 - else: - pass # TODO - else: - pass - - # Handle commands. - # TODO: Use some generic way to invoke the resp. method. - if self.state == CMD_WREN: - self.handle_wren(mosi, miso) - elif self.state == CMD_SE: - self.handle_se(mosi, miso) - elif self.state == CMD_RDID: - self.handle_rdid(mosi, miso) - if self.state == CMD_REMS: - self.handle_rems(mosi, miso) - if self.state == CMD_RDSR: - self.handle_rdsr(mosi, miso) - else: - self.put(0, 0, self.out_ann, [0, ['Unknown command: 0x%02x' % cmd]]) - self.state = IDLE - diff --git a/decoders/mx25lxx05d/Makefile.am b/decoders/mx25lxx05d/Makefile.am new file mode 100644 index 0000000..2d571f3 --- /dev/null +++ b/decoders/mx25lxx05d/Makefile.am @@ -0,0 +1,26 @@ +## +## 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 +## + +pkgdatadir = $(DECODERS_DIR)/mx25lxx05d + +dist_pkgdata_DATA = __init__.py mx25lxx05d.py + +CLEANFILES = *.pyc + diff --git a/decoders/mx25lxx05d/__init__.py b/decoders/mx25lxx05d/__init__.py new file mode 100644 index 0000000..0affc99 --- /dev/null +++ b/decoders/mx25lxx05d/__init__.py @@ -0,0 +1,22 @@ +## +## 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 +## + +from .mx25lxx05d import * + diff --git a/decoders/mx25lxx05d/mx25lxx05d.py b/decoders/mx25lxx05d/mx25lxx05d.py new file mode 100644 index 0000000..cd97dee --- /dev/null +++ b/decoders/mx25lxx05d/mx25lxx05d.py @@ -0,0 +1,272 @@ +## +## 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 decoder. +# Works for MX25L1605D/MX25L3205D/MX25L6405D. +# + +# +# TODO: Description +# +# Details: +# http://www.macronix.com/QuickPlace/hq/PageLibrary4825740B00298A3B.nsf/h_Index/3F21BAC2E121E17848257639003A3146/$File/MX25L1605D-3205D-6405D-1.5.pdf +# + +import sigrokdecode as srd + +# States +IDLE = -1 + +# Chip commands (also used as additional decoder states). +CMD_WREN = 0x06 +CMD_WRDI = 0x04 +CMD_RDID = 0x9f +CMD_RDSR = 0x05 +CMD_WRSR = 0x01 +CMD_READ = 0x03 +CMD_FAST_READ = 0x0b +CMD_2READ = 0xbb +CMD_SE = 0x20 +CMD_BE = 0xd8 +CMD_CE = 0x60 +CMD_CE2 = 0xc7 +CMD_PP = 0x02 +CMD_CP = 0xad +CMD_DP = 0xb9 +# CMD_RDP = 0xab +# CMD_RES = 0xab +CMD_RDP_RES = 0xab # Note: RDP/RES have the same ID. +CMD_REMS = 0x90 +CMD_REMS2 = 0xef +CMD_ENSO = 0xb1 +CMD_EXSO = 0xc1 +CMD_RDSCUR = 0x2b +CMD_WRSCUR = 0x2f +CMD_ESRY = 0x70 +CMD_DSRY = 0x80 + +# TODO: (Short) command names as strings in a dict, too? + +# Dict which maps command IDs to their description. +cmds = { + CMD_WREN: 'Write enable', + CMD_WRDI: 'Write disable', + CMD_RDID: 'Read identification', + CMD_RDSR: 'Read status register', + CMD_WRSR: 'Write status register', + CMD_READ: 'Read data', + CMD_FAST_READ: 'Fast read data', + CMD_2READ: '2x I/O read', + CMD_SE: 'Sector erase', + CMD_BE: 'Block erase', + CMD_CE: 'Chip erase', + CMD_CE2: 'Chip erase', # Alternative command ID + CMD_PP: 'Page program', + CMD_CP: 'Continuously program mode', + CMD_DP: 'Deep power down', + # CMD_RDP: 'Release from deep powerdown', + # CMD_RES: 'Read electronic ID', + CMD_RDP_RES: 'Release from deep powerdown / Read electronic ID', + CMD_REMS: 'Read electronic manufacturer & device ID', + CMD_REMS2: 'Read ID for 2x I/O mode', + CMD_ENSO: 'Enter secured OTP', + CMD_EXSO: 'Exit secured OTP', + CMD_RDSCUR: 'Read security register', + CMD_WRSCUR: 'Write security register', + CMD_ESRY: 'Enable SO to output RY/BY#', + CMD_DSRY: 'Disable SO to output RY/BY#', +} + +device_name = { + 0x14: 'MX25L1605D', + 0x15: 'MX25L3205D', + 0x16: 'MX25L6405D', +} + +class Decoder(srd.Decoder): + id = 'mx25lxx05d' + name = 'MX25Lxx05D' + longname = 'Macronix MX25Lxx05D' + desc = 'Macronix MX25Lxx05D SPI flash chip decoder' + longdesc = 'TODO' + license = 'gplv2+' + inputs = ['spi', 'spi', 'logic'] + outputs = ['mx25lxx05d'] + probes = [] # TODO: HOLD#, WP#/ACC + options = {} # TODO + annotations = [ + ['TODO', 'TODO'], + ] + + def __init__(self, **kwargs): + self.state = IDLE + self.cmdstate = 1 # TODO + + 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 putann(self, data): + # Simplification, most annotations span extactly one SPI byte/packet. + self.put(self.ss, self.es, self.out_ann, data) + + def handle_wren(self, mosi, miso): + self.putann([0, ['Command: %s' % cmds[self.cmd]]]) + self.state = IDLE + + # 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.putann([0, ['Command: %s' % cmds[self.cmd]]]) + elif self.cmdstate == 2: + # Byte 2: Slave sends the JEDEC manufacturer ID. + self.putann([0, ['Manufacturer ID: 0x%02x' % miso]]) + elif self.cmdstate == 3: + # Byte 3: Slave sends the memory type (0x20 for this chip). + self.putann([0, ['Memory type: 0x%02x' % miso]]) + elif self.cmdstate == 4: + # Byte 4: Slave sends the device ID. + self.device_id = miso + self.putann([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 = IDLE + else: + self.cmdstate += 1 + + # TODO: Warn/abort if we don't see the necessary amount of bytes. + # TODO: Warn if WREN was not seen before. + def handle_se(self, mosi, miso): + if self.cmdstate == 1: + # Byte 1: Master sends command ID. + self.addr = 0 + self.start_sample = self.ss + self.putann([0, ['Command: %s' % cmds[self.cmd]]]) + elif self.cmdstate in (2, 3, 4): + # Bytes 2/3/4: Master sends address of the sector to erase. + # Note: Assumes SPI data is 8 bits wide (it is for MX25Lxx05D). + # TODO: LSB-first of MSB-first? + self.addr <<= 8 + self.addr |= mosi + self.putann([0, ['Address byte %d: 0x%02x' % (self.cmdstate - 1, + miso)]]) # TODO: Count from 0 or 1? + + if self.cmdstate == 4: + d = 'Erase sector %d' % 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!' # TODO: type == WARN? + self.put(self.start_sample, self.es, self.out_ann, [0, [d]]) + self.state = IDLE + else: + self.cmdstate += 1 + + def handle_rems(self, mosi, miso): + if self.cmdstate == 1: + # Byte 1: Master sends command ID. + self.start_sample = self.ss + self.putann([0, ['Command: %s' % cmds[self.cmd]]]) + elif self.cmdstate in (2, 3): + # Bytes 2/3: Master sends two dummy bytes. + # TODO: Check dummy bytes? Check reply from device? + self.putann([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.putann([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.putann([0, ['%s ID' % d]]) + elif self.cmdstate == 6: + # Byte 6: Slave sends device ID (or manufacturer ID). + self.ids += [miso] + d = 'Manufacturer' if self.manufacturer_id_first else 'Device' + self.putann([0, ['%s ID' % d]]) + else: + # TODO: Error? + pass + + if self.cmdstate == 6: + self.end_sample = self.es + id = self.ids[1] if self.manufacturer_id_first else self.ids[0] + self.putann([0, ['Device: Macronix %s' % device_name[id]]]) + self.state = IDLE + else: + self.cmdstate += 1 + + def handle_rdsr(self, mosi, miso): + self.putann([0, ['Command: %s (0x%02x)' % (cmds[self.cmd], miso)]]) + self.state = IDLE + + def decode(self, ss, es, data): + + ptype, mosi, miso = data + + if ptype != 'data': + return + + cmd = mosi + self.ss = ss + self.es = es + + # If we encountered a known chip command, enter the resp. state. + if self.state == IDLE: + if cmd in cmds: + self.state = cmd + self.cmd = cmd # TODO: Eliminate? + self.cmdstate = 1 + else: + pass # TODO + else: + pass + + # Handle commands. + # TODO: Use some generic way to invoke the resp. method. + if self.state == CMD_WREN: + self.handle_wren(mosi, miso) + elif self.state == CMD_SE: + self.handle_se(mosi, miso) + elif self.state == CMD_RDID: + self.handle_rdid(mosi, miso) + if self.state == CMD_REMS: + self.handle_rems(mosi, miso) + if self.state == CMD_RDSR: + self.handle_rdsr(mosi, miso) + else: + self.put(0, 0, self.out_ann, [0, ['Unknown command: 0x%02x' % cmd]]) + self.state = IDLE + diff --git a/decoders/nunchuk.py b/decoders/nunchuk.py deleted file mode 100644 index 46bf53c..0000000 --- a/decoders/nunchuk.py +++ /dev/null @@ -1,144 +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 decoder -# - -# -# TODO: Description -# -# http://wiibrew.org/wiki/Wiimote/Extension_Controllers/Nunchuck -# http://todbot.com/blog/2008/02/18/wiichuck-wii-nunchuck-adapter-available/ -# https://www.sparkfun.com/products/9281 -# - -import sigrokdecode as srd - -# States -IDLE = 0 -START = 1 -NUNCHUK_SLAVE = 2 -INIT = 3 -INITIALIZED = 4 - -class Decoder(srd.Decoder): - id = 'nunchuk' - name = 'Nunchuk' - longname = 'Nintendo Wii Nunchuk' - desc = 'Decodes the Nintendo Wii Nunchuk I2C-based protocol.' - longdesc = '...' - license = 'gplv2+' - inputs = ['i2c'] - outputs = ['nunchuck'] - probes = [] # TODO - options = {} - annotations = [ - ['TODO', 'TODO'], - ] - - def __init__(self, **kwargs): - self.state = IDLE # TODO: Can we assume a certain initial state? - self.sx = self.sy = self.ax = self.ay = self.az = self.bz = self.bc = 0 - self.databytecount = 0 - - 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 decode(self, ss, es, data): - - cmd, databyte, ack_bit = data - - if cmd == 'START': # TODO: Handle 'Sr' here, too? - self.state = START - - elif cmd == 'START REPEAT': - pass # FIXME - - elif cmd == 'ADDRESS READ': - # TODO: Error/Warning, not supported, I think. - pass - - elif cmd == 'ADDRESS WRITE': - # The Wii Nunchuk always has slave address 0x54. - # TODO: Handle this stuff more correctly. - if databyte == 0x54: - pass # TODO - else: - pass # TODO: What to do here? Ignore? Error? - - elif cmd == 'DATA READ' and self.state == INITIALIZED: - if self.databytecount == 0: - self.sx = databyte - elif self.databytecount == 1: - self.sy = databyte - elif self.databytecount == 2: - self.ax = databyte << 2 - elif self.databytecount == 3: - self.ay = databyte << 2 - elif self.databytecount == 4: - self.az = databyte << 2 - elif self.databytecount == 5: - self.bz = (databyte & (1 << 0)) >> 0 - self.bc = (databyte & (1 << 1)) >> 1 - self.ax |= (databyte & (3 << 2)) >> 2 - self.ay |= (databyte & (3 << 4)) >> 4 - self.az |= (databyte & (3 << 6)) >> 6 - - d = '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(ss, es, self.out_ann, [0, [d]]) - - self.sx = self.sy = self.ax = self.ay = self.az = 0 - self.bz = self.bc = 0 - else: - pass # TODO - - if 0 <= self.databytecount <= 5: - self.databytecount += 1 - - # TODO: If 6 bytes read -> save and reset - - # TODO - elif cmd == 'DATA READ' and self.state != INITIALIZED: - pass - - elif cmd == 'DATA WRITE': - if self.state == IDLE: - self.state = INITIALIZED - return - - if databyte == 0x40 and self.state == START: - self.state = INIT - elif databyte == 0x00 and self.state == INIT: - self.put(ss, es, self.out_ann, [0, ['Initialize nunchuk']]) - self.state = INITIALIZED - else: - pass # TODO - - elif cmd == 'STOP': - self.state = INITIALIZED - self.databytecount = 0 - diff --git a/decoders/nunchuk/Makefile.am b/decoders/nunchuk/Makefile.am new file mode 100644 index 0000000..5a4917b --- /dev/null +++ b/decoders/nunchuk/Makefile.am @@ -0,0 +1,26 @@ +## +## 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 +## + +pkgdatadir = $(DECODERS_DIR)/nunchuk + +dist_pkgdata_DATA = __init__.py nunchuk.py + +CLEANFILES = *.pyc + diff --git a/decoders/nunchuk/__init__.py b/decoders/nunchuk/__init__.py new file mode 100644 index 0000000..6772ba7 --- /dev/null +++ b/decoders/nunchuk/__init__.py @@ -0,0 +1,22 @@ +## +## 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 +## + +from .nunchuk import * + diff --git a/decoders/nunchuk/nunchuk.py b/decoders/nunchuk/nunchuk.py new file mode 100644 index 0000000..46bf53c --- /dev/null +++ b/decoders/nunchuk/nunchuk.py @@ -0,0 +1,144 @@ +## +## 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 decoder +# + +# +# TODO: Description +# +# http://wiibrew.org/wiki/Wiimote/Extension_Controllers/Nunchuck +# http://todbot.com/blog/2008/02/18/wiichuck-wii-nunchuck-adapter-available/ +# https://www.sparkfun.com/products/9281 +# + +import sigrokdecode as srd + +# States +IDLE = 0 +START = 1 +NUNCHUK_SLAVE = 2 +INIT = 3 +INITIALIZED = 4 + +class Decoder(srd.Decoder): + id = 'nunchuk' + name = 'Nunchuk' + longname = 'Nintendo Wii Nunchuk' + desc = 'Decodes the Nintendo Wii Nunchuk I2C-based protocol.' + longdesc = '...' + license = 'gplv2+' + inputs = ['i2c'] + outputs = ['nunchuck'] + probes = [] # TODO + options = {} + annotations = [ + ['TODO', 'TODO'], + ] + + def __init__(self, **kwargs): + self.state = IDLE # TODO: Can we assume a certain initial state? + self.sx = self.sy = self.ax = self.ay = self.az = self.bz = self.bc = 0 + self.databytecount = 0 + + 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 decode(self, ss, es, data): + + cmd, databyte, ack_bit = data + + if cmd == 'START': # TODO: Handle 'Sr' here, too? + self.state = START + + elif cmd == 'START REPEAT': + pass # FIXME + + elif cmd == 'ADDRESS READ': + # TODO: Error/Warning, not supported, I think. + pass + + elif cmd == 'ADDRESS WRITE': + # The Wii Nunchuk always has slave address 0x54. + # TODO: Handle this stuff more correctly. + if databyte == 0x54: + pass # TODO + else: + pass # TODO: What to do here? Ignore? Error? + + elif cmd == 'DATA READ' and self.state == INITIALIZED: + if self.databytecount == 0: + self.sx = databyte + elif self.databytecount == 1: + self.sy = databyte + elif self.databytecount == 2: + self.ax = databyte << 2 + elif self.databytecount == 3: + self.ay = databyte << 2 + elif self.databytecount == 4: + self.az = databyte << 2 + elif self.databytecount == 5: + self.bz = (databyte & (1 << 0)) >> 0 + self.bc = (databyte & (1 << 1)) >> 1 + self.ax |= (databyte & (3 << 2)) >> 2 + self.ay |= (databyte & (3 << 4)) >> 4 + self.az |= (databyte & (3 << 6)) >> 6 + + d = '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(ss, es, self.out_ann, [0, [d]]) + + self.sx = self.sy = self.ax = self.ay = self.az = 0 + self.bz = self.bc = 0 + else: + pass # TODO + + if 0 <= self.databytecount <= 5: + self.databytecount += 1 + + # TODO: If 6 bytes read -> save and reset + + # TODO + elif cmd == 'DATA READ' and self.state != INITIALIZED: + pass + + elif cmd == 'DATA WRITE': + if self.state == IDLE: + self.state = INITIALIZED + return + + if databyte == 0x40 and self.state == START: + self.state = INIT + elif databyte == 0x00 and self.state == INIT: + self.put(ss, es, self.out_ann, [0, ['Initialize nunchuk']]) + self.state = INITIALIZED + else: + pass # TODO + + elif cmd == 'STOP': + self.state = INITIALIZED + self.databytecount = 0 + diff --git a/decoders/pan1321.py b/decoders/pan1321.py deleted file mode 100644 index 55676ce..0000000 --- a/decoders/pan1321.py +++ /dev/null @@ -1,115 +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 -# -# TODO -# - -import sigrokdecode as srd - -# Annotation feed formats -ANN_ASCII = 0 - -# UART 'data' packet type. -T_DATA = 1 - -# ... -RX = 0 -TX = 1 - -class Decoder(srd.Decoder): - id = 'pan1321' - name = 'PAN1321' - longname = 'Panasonic PAN1321' - desc = 'TODO.' - longdesc = 'TODO.' - license = 'gplv2+' - inputs = ['uart'] - outputs = ['pan1321'] - probes = [] - options = {} - annotations = [ - ['ASCII', 'TODO: description'], - ] - - def __init__(self, **kwargs): - self.cmd = ['', ''] - - 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 handle_host_command(self, ss, es, rxtx, s): - if s.startswith('AT+JSEC'): - pin = s[s.find('\r\n') - 4:len(s) - 2] - self.put(ss, es, self.out_ann, - [ANN_ASCII, ['Host set the Bluetooth PIN to ' + pin]]) - elif s.startswith('AT+JSLN'): - name = s[s.find(',') + 1:-2] - self.put(ss, es, self.out_ann, - [ANN_ASCII, ['Host set the Bluetooth name to ' + name]]) - else: - self.put(ss, es, self.out_ann, - [ANN_ASCII, ['Host sent unsupported command']]) - self.cmd[rxtx] = '' - - def handle_device_reply(self, ss, es, rxtx, s): - if s == 'ROK\r\n': - self.put(ss, es, self.out_ann, - [ANN_ASCII, ['Device initialized correctly']]) - elif s == 'OK\r\n': - self.put(ss, es, self.out_ann, - [ANN_ASCII, ['Device acknowledged last command']]) - elif s.startswith('ERR'): - error = s[s.find('=') + 1:] - self.put(ss, es, self.out_ann, - [ANN_ASCII, ['Device sent error code ' + error]]) - else: - self.put(ss, es, self.out_ann, - [ANN_ASCII, ['Device sent an unknown reply']]) - 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 != T_DATA: - return - - # 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. - if rxtx == RX: - self.handle_device_reply(ss, es, rxtx, self.cmd[rxtx]) - elif rxtx == TX: - self.handle_host_command(ss, es, rxtx, self.cmd[rxtx]) - else: - pass # TODO: Error. - diff --git a/decoders/pan1321/Makefile.am b/decoders/pan1321/Makefile.am new file mode 100644 index 0000000..6997ed5 --- /dev/null +++ b/decoders/pan1321/Makefile.am @@ -0,0 +1,26 @@ +## +## 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 +## + +pkgdatadir = $(DECODERS_DIR)/pan1321 + +dist_pkgdata_DATA = __init__.py pan1321.py + +CLEANFILES = *.pyc + diff --git a/decoders/pan1321/__init__.py b/decoders/pan1321/__init__.py new file mode 100644 index 0000000..abbcf5d --- /dev/null +++ b/decoders/pan1321/__init__.py @@ -0,0 +1,22 @@ +## +## 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 +## + +from .pan1321 import * + diff --git a/decoders/pan1321/pan1321.py b/decoders/pan1321/pan1321.py new file mode 100644 index 0000000..55676ce --- /dev/null +++ b/decoders/pan1321/pan1321.py @@ -0,0 +1,115 @@ +## +## 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 +# +# TODO +# + +import sigrokdecode as srd + +# Annotation feed formats +ANN_ASCII = 0 + +# UART 'data' packet type. +T_DATA = 1 + +# ... +RX = 0 +TX = 1 + +class Decoder(srd.Decoder): + id = 'pan1321' + name = 'PAN1321' + longname = 'Panasonic PAN1321' + desc = 'TODO.' + longdesc = 'TODO.' + license = 'gplv2+' + inputs = ['uart'] + outputs = ['pan1321'] + probes = [] + options = {} + annotations = [ + ['ASCII', 'TODO: description'], + ] + + def __init__(self, **kwargs): + self.cmd = ['', ''] + + 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 handle_host_command(self, ss, es, rxtx, s): + if s.startswith('AT+JSEC'): + pin = s[s.find('\r\n') - 4:len(s) - 2] + self.put(ss, es, self.out_ann, + [ANN_ASCII, ['Host set the Bluetooth PIN to ' + pin]]) + elif s.startswith('AT+JSLN'): + name = s[s.find(',') + 1:-2] + self.put(ss, es, self.out_ann, + [ANN_ASCII, ['Host set the Bluetooth name to ' + name]]) + else: + self.put(ss, es, self.out_ann, + [ANN_ASCII, ['Host sent unsupported command']]) + self.cmd[rxtx] = '' + + def handle_device_reply(self, ss, es, rxtx, s): + if s == 'ROK\r\n': + self.put(ss, es, self.out_ann, + [ANN_ASCII, ['Device initialized correctly']]) + elif s == 'OK\r\n': + self.put(ss, es, self.out_ann, + [ANN_ASCII, ['Device acknowledged last command']]) + elif s.startswith('ERR'): + error = s[s.find('=') + 1:] + self.put(ss, es, self.out_ann, + [ANN_ASCII, ['Device sent error code ' + error]]) + else: + self.put(ss, es, self.out_ann, + [ANN_ASCII, ['Device sent an unknown reply']]) + 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 != T_DATA: + return + + # 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. + if rxtx == RX: + self.handle_device_reply(ss, es, rxtx, self.cmd[rxtx]) + elif rxtx == TX: + self.handle_host_command(ss, es, rxtx, self.cmd[rxtx]) + else: + pass # TODO: Error. + diff --git a/decoders/spi.py b/decoders/spi.py deleted file mode 100644 index 07b8da2..0000000 --- a/decoders/spi.py +++ /dev/null @@ -1,170 +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 -## - -import sigrokdecode as srd - -# Chip-select options -ACTIVE_LOW = 0 -ACTIVE_HIGH = 1 - -# Clock polarity options -CPOL_0 = 0 # Clock is low when inactive -CPOL_1 = 1 # Clock is high when inactive - -# Clock phase options -CPHA_0 = 0 # Data is valid on the leading clock edge -CPHA_1 = 1 # Data is valid on the trailing clock edge - -# Bit order options -MSB_FIRST = 0 -LSB_FIRST = 1 - -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): - id = 'spi' - name = 'SPI' - longname = 'Serial Peripheral Interface' - desc = '...desc...' - longdesc = '...longdesc...' - license = 'gplv2+' - inputs = ['logic'] - outputs = ['spi'] - probes = [ - {'id': 'mosi', 'name': 'MOSI', - 'desc': 'SPI MOSI line (Master out, slave in)'}, - {'id': 'miso', 'name': 'MISO', - 'desc': 'SPI MISO line (Master in, slave out)'}, - {'id': 'sck', 'name': 'CLK', 'desc': 'SPI clock line'}, - {'id': 'cs', 'name': 'CS#', 'desc': 'SPI CS (chip select) line'}, - ] - options = { - 'cs_polarity': ['CS# polarity', ACTIVE_LOW], - 'cpol': ['Clock polarity', CPOL_0], - 'cpha': ['Clock phase', CPHA_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 - - # Set protocol decoder option defaults. - self.cs_polarity = Decoder.options['cs_polarity'][1] - self.cpol = Decoder.options['cpol'][1] - self.cpha = Decoder.options['cpha'][1] - self.bitorder = Decoder.options['bitorder'][1] - self.wordsize = Decoder.options['wordsize'][1] - - 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): - # HACK! At the moment the number of probes is not handled correctly. - # E.g. if an input file (-i foo.sr) has more than two probes enabled. - # for (samplenum, (mosi, sck, x, y, z, a)) in data: - # for (samplenum, (cs, miso, sck, mosi, wp, hold)) in data: - for (samplenum, (cs, miso, sck, mosi, wp, hold)) in data: - - self.samplenum += 1 # FIXME - - # 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.cpol, self.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 = samplenum - deasserted = cs if (self.cs_polarity == ACTIVE_LOW) else not c - if deasserted: - self.cs_was_deasserted_during_data_word = 1 - - # Receive MOSI bit into our shift register. - if self.bitorder == MSB_FIRST: - self.mosidata |= mosi << (self.wordsize - 1 - self.bitcount) - else: - self.mosidata |= mosi << self.bitcount - - # Receive MISO bit into our shift register. - if self.bitorder == MSB_FIRST: - self.misodata |= miso << (self.wordsize - 1 - self.bitcount) - else: - self.misodata |= miso << self.bitcount - - self.bitcount += 1 - - # Continue to receive if not a byte yet. - if self.bitcount != self.wordsize: - 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/Makefile.am b/decoders/spi/Makefile.am new file mode 100644 index 0000000..89bf640 --- /dev/null +++ b/decoders/spi/Makefile.am @@ -0,0 +1,26 @@ +## +## 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 +## + +pkgdatadir = $(DECODERS_DIR)/spi + +dist_pkgdata_DATA = __init__.py spi.py + +CLEANFILES = *.pyc + diff --git a/decoders/spi/__init__.py b/decoders/spi/__init__.py new file mode 100644 index 0000000..3276fed --- /dev/null +++ b/decoders/spi/__init__.py @@ -0,0 +1,22 @@ +## +## 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 +## + +from .spi import * + diff --git a/decoders/spi/spi.py b/decoders/spi/spi.py new file mode 100644 index 0000000..07b8da2 --- /dev/null +++ b/decoders/spi/spi.py @@ -0,0 +1,170 @@ +## +## 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 +## + +import sigrokdecode as srd + +# Chip-select options +ACTIVE_LOW = 0 +ACTIVE_HIGH = 1 + +# Clock polarity options +CPOL_0 = 0 # Clock is low when inactive +CPOL_1 = 1 # Clock is high when inactive + +# Clock phase options +CPHA_0 = 0 # Data is valid on the leading clock edge +CPHA_1 = 1 # Data is valid on the trailing clock edge + +# Bit order options +MSB_FIRST = 0 +LSB_FIRST = 1 + +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): + id = 'spi' + name = 'SPI' + longname = 'Serial Peripheral Interface' + desc = '...desc...' + longdesc = '...longdesc...' + license = 'gplv2+' + inputs = ['logic'] + outputs = ['spi'] + probes = [ + {'id': 'mosi', 'name': 'MOSI', + 'desc': 'SPI MOSI line (Master out, slave in)'}, + {'id': 'miso', 'name': 'MISO', + 'desc': 'SPI MISO line (Master in, slave out)'}, + {'id': 'sck', 'name': 'CLK', 'desc': 'SPI clock line'}, + {'id': 'cs', 'name': 'CS#', 'desc': 'SPI CS (chip select) line'}, + ] + options = { + 'cs_polarity': ['CS# polarity', ACTIVE_LOW], + 'cpol': ['Clock polarity', CPOL_0], + 'cpha': ['Clock phase', CPHA_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 + + # Set protocol decoder option defaults. + self.cs_polarity = Decoder.options['cs_polarity'][1] + self.cpol = Decoder.options['cpol'][1] + self.cpha = Decoder.options['cpha'][1] + self.bitorder = Decoder.options['bitorder'][1] + self.wordsize = Decoder.options['wordsize'][1] + + 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): + # HACK! At the moment the number of probes is not handled correctly. + # E.g. if an input file (-i foo.sr) has more than two probes enabled. + # for (samplenum, (mosi, sck, x, y, z, a)) in data: + # for (samplenum, (cs, miso, sck, mosi, wp, hold)) in data: + for (samplenum, (cs, miso, sck, mosi, wp, hold)) in data: + + self.samplenum += 1 # FIXME + + # 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.cpol, self.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 = samplenum + deasserted = cs if (self.cs_polarity == ACTIVE_LOW) else not c + if deasserted: + self.cs_was_deasserted_during_data_word = 1 + + # Receive MOSI bit into our shift register. + if self.bitorder == MSB_FIRST: + self.mosidata |= mosi << (self.wordsize - 1 - self.bitcount) + else: + self.mosidata |= mosi << self.bitcount + + # Receive MISO bit into our shift register. + if self.bitorder == MSB_FIRST: + self.misodata |= miso << (self.wordsize - 1 - self.bitcount) + else: + self.misodata |= miso << self.bitcount + + self.bitcount += 1 + + # Continue to receive if not a byte yet. + if self.bitcount != self.wordsize: + 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.py b/decoders/transitioncounter.py deleted file mode 100644 index 4c832b0..0000000 --- a/decoders/transitioncounter.py +++ /dev/null @@ -1,100 +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 -## - -import sigrokdecode as srd - -class Decoder(srd.Decoder): - id = 'transitioncounter' - name = 'Transition counter' - longname = 'Pin transition counter' - desc = 'Counts rising/falling edges in the signal.' - longdesc = '...' - license = 'gplv2+' - inputs = ['logic'] - outputs = ['transitioncounts'] - 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 += [[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/Makefile.am b/decoders/transitioncounter/Makefile.am new file mode 100644 index 0000000..74688a3 --- /dev/null +++ b/decoders/transitioncounter/Makefile.am @@ -0,0 +1,26 @@ +## +## 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 +## + +pkgdatadir = $(DECODERS_DIR)/transitioncounter + +dist_pkgdata_DATA = __init__.py transitioncounter.py + +CLEANFILES = *.pyc + diff --git a/decoders/transitioncounter/__init__.py b/decoders/transitioncounter/__init__.py new file mode 100644 index 0000000..bdf296b --- /dev/null +++ b/decoders/transitioncounter/__init__.py @@ -0,0 +1,22 @@ +## +## 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 +## + +from .transitioncounter import * + diff --git a/decoders/transitioncounter/transitioncounter.py b/decoders/transitioncounter/transitioncounter.py new file mode 100644 index 0000000..4c832b0 --- /dev/null +++ b/decoders/transitioncounter/transitioncounter.py @@ -0,0 +1,100 @@ +## +## 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 +## + +import sigrokdecode as srd + +class Decoder(srd.Decoder): + id = 'transitioncounter' + name = 'Transition counter' + longname = 'Pin transition counter' + desc = 'Counts rising/falling edges in the signal.' + longdesc = '...' + license = 'gplv2+' + inputs = ['logic'] + outputs = ['transitioncounts'] + 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 += [[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.py b/decoders/uart.py deleted file mode 100644 index 4752c7d..0000000 --- a/decoders/uart.py +++ /dev/null @@ -1,432 +0,0 @@ -## -## This file is part of the sigrok project. -## -## Copyright (C) 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 -## - -# -# UART protocol decoder -# - -# -# Universal Asynchronous Receiver Transmitter (UART) is a simple serial -# communication protocol which allows two devices to talk to each other. -# -# It uses just two data signals and a ground (GND) signal: -# - RX/RXD: Receive signal -# - TX/TXD: Transmit signal -# -# The protocol is asynchronous, i.e., there is no dedicated clock signal. -# Rather, both devices have to agree on a baudrate (number of bits to be -# transmitted per second) beforehand. Baudrates can be arbitrary in theory, -# but usually the choice is limited by the hardware UARTs that are used. -# Common values are 9600 or 115200. -# -# The protocol allows full-duplex transmission, i.e. both devices can send -# data at the same time. However, unlike SPI (which is always full-duplex, -# i.e., each send operation is automatically also a receive operation), UART -# allows one-way communication, too. In such a case only one signal (and GND) -# is required. -# -# The data is sent over the TX line in so-called 'frames', which consist of: -# - Exactly one start bit (always 0/low). -# - Between 5 and 9 data bits. -# - An (optional) parity bit. -# - One or more stop bit(s). -# -# The idle state of the RX/TX line is 1/high. As the start bit is 0/low, the -# receiver can continually monitor its RX line for a falling edge, in order -# to detect the start bit. -# -# Once detected, it can (due to the agreed-upon baudrate and thus the known -# width/duration of one UART bit) sample the state of the RX line "in the -# middle" of each (start/data/parity/stop) bit it wants to analyze. -# -# It is configurable whether there is a parity bit in a frame, and if yes, -# which type of parity is used: -# - None: No parity bit is included. -# - Odd: The number of 1 bits in the data (and parity bit itself) is odd. -# - Even: The number of 1 bits in the data (and parity bit itself) is even. -# - Mark/one: The parity bit is always 1/high (also called 'mark state'). -# - Space/zero: The parity bit is always 0/low (also called 'space state'). -# -# It is also configurable how many stop bits are to be used: -# - 1 stop bit (most common case) -# - 2 stop bits -# - 1.5 stop bits (i.e., one stop bit, but 1.5 times the UART bit width) -# - 0.5 stop bits (i.e., one stop bit, but 0.5 times the UART bit width) -# -# The bit order of the 5-9 data bits is LSB-first. -# -# Possible special cases: -# - One or both data lines could be inverted, which also means that the idle -# state of the signal line(s) is low instead of high. -# - Only the data bits on one or both data lines (and the parity bit) could -# be inverted (but the start/stop bits remain non-inverted). -# - The bit order could be MSB-first instead of LSB-first. -# - The baudrate could change in the middle of the communication. This only -# happens in very special cases, and can only work if both devices know -# to which baudrate they are to switch, and when. -# - Theoretically, the baudrate on RX and the one on TX could also be -# different, but that's a very obscure case and probably doesn't happen -# very often in practice. -# -# Error conditions: -# - If there is a parity bit, but it doesn't match the expected parity, -# this is called a 'parity error'. -# - If there are no stop bit(s), that's called a 'frame error'. -# -# More information: -# TODO: URLs -# - -# -# Protocol output format: -# -# UART packet: -# [, , ] -# -# This is the list of s and their respective : -# - T_START: The data is the (integer) value of the start bit (0 or 1). -# - T_DATA: The data is the (integer) value of the UART data. Valid values -# range from 0 to 512 (as the data can be up to 9 bits in size). -# - T_PARITY: The data is the (integer) value of the parity bit (0 or 1). -# - T_STOP: The data is the (integer) value of the stop bit (0 or 1). -# - T_INVALID_START: The data is the (integer) value of the start bit (0 or 1). -# - T_INVALID_STOP: The data is the (integer) value of the stop bit (0 or 1). -# - T_PARITY_ERROR: The data is a tuple with two entries. The first one is -# the expected parity value, the second is the actual parity value. -# -# The field is 0 for RX packets, 1 for TX packets. -# - -import sigrokdecode as srd - -# States -WAIT_FOR_START_BIT = 0 -GET_START_BIT = 1 -GET_DATA_BITS = 2 -GET_PARITY_BIT = 3 -GET_STOP_BITS = 4 - -# Used for differentiating between the two data directions. -RX = 0 -TX = 1 - -# Parity options -PARITY_NONE = 0 -PARITY_ODD = 1 -PARITY_EVEN = 2 -PARITY_ZERO = 3 -PARITY_ONE = 4 - -# Stop bit options -STOP_BITS_0_5 = 0 -STOP_BITS_1 = 1 -STOP_BITS_1_5 = 2 -STOP_BITS_2 = 3 - -# Bit order options -LSB_FIRST = 0 -MSB_FIRST = 1 - -# Annotation feed formats -ANN_ASCII = 0 -ANN_DEC = 1 -ANN_HEX = 2 -ANN_OCT = 3 -ANN_BITS = 4 - -# Protocol output packet types -T_START = 0 -T_DATA = 1 -T_PARITY = 2 -T_STOP = 3 -T_INVALID_START = 4 -T_INVALID_STOP = 5 -T_PARITY_ERROR = 6 - -# 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. -# PARITY_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 == PARITY_ZERO: - return parity_bit == 0 - elif parity_type == PARITY_ONE: - return parity_bit == 1 - - # Count number of 1 (high) bits in the data (and the parity bit itself!). - parity = bin(data).count('1') + parity_bit - - # Check for odd/even parity. - if parity_type == PARITY_ODD: - return (parity % 2) == 1 - elif parity_type == PARITY_EVEN: - return (parity % 2) == 0 - else: - raise Exception('Invalid parity type: %d' % parity_type) - -class Decoder(srd.Decoder): - id = 'uart' - name = 'UART' - longname = 'Universal Asynchronous Receiver/Transmitter' - desc = 'Universal Asynchronous Receiver/Transmitter (UART)' - longdesc = 'TODO.' - 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'}, - ] - options = { - 'baudrate': ['Baud rate', 115200], - 'num_data_bits': ['Data bits', 8], # Valid: 5-9. - 'parity': ['Parity', PARITY_NONE], - 'parity_check': ['Check parity', True], - 'num_stop_bits': ['Stop bit(s)', STOP_BITS_1], - '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.stopbit1 = [-1, -1] - self.startsample = [-1, -1] - - # Initial state. - self.state = [WAIT_FOR_START_BIT, WAIT_FOR_START_BIT] - - self.oldbit = [None, None] - - # Set protocol decoder option defaults. - self.baudrate = Decoder.options['baudrate'][1] - self.num_data_bits = Decoder.options['num_data_bits'][1] - self.parity = Decoder.options['parity'][1] - self.check_parity = Decoder.options['parity_check'][1] - self.num_stop_bits = Decoder.options['num_stop_bits'][1] - self.bit_order = Decoder.options['bit_order'][1] - - 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') - - # TODO: Override PD options, if user wants that. - - # The width of one UART bit in number of samples. - self.bit_width = float(self.samplerate) / float(self.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, - [T_INVALID_START, 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, - [T_START, 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.bit_order == LSB_FIRST: - self.databyte[rxtx] >>= 1 - self.databyte[rxtx] |= (signal << (self.num_data_bits - 1)) - elif self.bit_order == MSB_FIRST: - self.databyte[rxtx] <<= 1 - self.databyte[rxtx] |= (signal << 0) - else: - raise Exception('Invalid bit order value: %d', self.bit_order) - - # Return here, unless we already received all data bits. - if self.cur_data_bit[rxtx] < self.num_data_bits - 1: # TODO? Off-by-one? - self.cur_data_bit[rxtx] += 1 - return - - self.state[rxtx] = GET_PARITY_BIT - - self.put(self.startsample[rxtx], self.samplenum - 1, self.out_proto, - [T_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.parity == PARITY_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.num_data_bits + 1): - return - - self.paritybit[rxtx] = signal - - self.state[rxtx] = GET_STOP_BITS - - if parity_ok(self.parity[rxtx], self.paritybit[rxtx], - self.databyte[rxtx], self.num_data_bits): - # TODO: Fix range. - self.put(self.samplenum, self.samplenum, self.out_proto, - [T_PARITY_BIT, 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, - [T_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.parity == PARITY_NONE else 1 - if not self.reached_bit(rxtx, self.num_data_bits + 1 + skip_parity): - 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, - [T_INVALID_STOP, 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, - [T_STOP, 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 - for (samplenum, (rx, tx)) in data: - - # TODO: Start counting at 0 or 1? Increase before or after? - self.samplenum += 1 - - # 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: %s' % self.state[rxtx]) - - # Save current RX/TX values for the next round. - self.oldbit[rxtx] = signal - diff --git a/decoders/uart/Makefile.am b/decoders/uart/Makefile.am new file mode 100644 index 0000000..155bfc9 --- /dev/null +++ b/decoders/uart/Makefile.am @@ -0,0 +1,26 @@ +## +## 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 +## + +pkgdatadir = $(DECODERS_DIR)/uart + +dist_pkgdata_DATA = __init__.py uart.py + +CLEANFILES = *.pyc + diff --git a/decoders/uart/__init__.py b/decoders/uart/__init__.py new file mode 100644 index 0000000..4267d83 --- /dev/null +++ b/decoders/uart/__init__.py @@ -0,0 +1,22 @@ +## +## 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 +## + +from .uart import * + diff --git a/decoders/uart/uart.py b/decoders/uart/uart.py new file mode 100644 index 0000000..4752c7d --- /dev/null +++ b/decoders/uart/uart.py @@ -0,0 +1,432 @@ +## +## This file is part of the sigrok project. +## +## Copyright (C) 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 +## + +# +# UART protocol decoder +# + +# +# Universal Asynchronous Receiver Transmitter (UART) is a simple serial +# communication protocol which allows two devices to talk to each other. +# +# It uses just two data signals and a ground (GND) signal: +# - RX/RXD: Receive signal +# - TX/TXD: Transmit signal +# +# The protocol is asynchronous, i.e., there is no dedicated clock signal. +# Rather, both devices have to agree on a baudrate (number of bits to be +# transmitted per second) beforehand. Baudrates can be arbitrary in theory, +# but usually the choice is limited by the hardware UARTs that are used. +# Common values are 9600 or 115200. +# +# The protocol allows full-duplex transmission, i.e. both devices can send +# data at the same time. However, unlike SPI (which is always full-duplex, +# i.e., each send operation is automatically also a receive operation), UART +# allows one-way communication, too. In such a case only one signal (and GND) +# is required. +# +# The data is sent over the TX line in so-called 'frames', which consist of: +# - Exactly one start bit (always 0/low). +# - Between 5 and 9 data bits. +# - An (optional) parity bit. +# - One or more stop bit(s). +# +# The idle state of the RX/TX line is 1/high. As the start bit is 0/low, the +# receiver can continually monitor its RX line for a falling edge, in order +# to detect the start bit. +# +# Once detected, it can (due to the agreed-upon baudrate and thus the known +# width/duration of one UART bit) sample the state of the RX line "in the +# middle" of each (start/data/parity/stop) bit it wants to analyze. +# +# It is configurable whether there is a parity bit in a frame, and if yes, +# which type of parity is used: +# - None: No parity bit is included. +# - Odd: The number of 1 bits in the data (and parity bit itself) is odd. +# - Even: The number of 1 bits in the data (and parity bit itself) is even. +# - Mark/one: The parity bit is always 1/high (also called 'mark state'). +# - Space/zero: The parity bit is always 0/low (also called 'space state'). +# +# It is also configurable how many stop bits are to be used: +# - 1 stop bit (most common case) +# - 2 stop bits +# - 1.5 stop bits (i.e., one stop bit, but 1.5 times the UART bit width) +# - 0.5 stop bits (i.e., one stop bit, but 0.5 times the UART bit width) +# +# The bit order of the 5-9 data bits is LSB-first. +# +# Possible special cases: +# - One or both data lines could be inverted, which also means that the idle +# state of the signal line(s) is low instead of high. +# - Only the data bits on one or both data lines (and the parity bit) could +# be inverted (but the start/stop bits remain non-inverted). +# - The bit order could be MSB-first instead of LSB-first. +# - The baudrate could change in the middle of the communication. This only +# happens in very special cases, and can only work if both devices know +# to which baudrate they are to switch, and when. +# - Theoretically, the baudrate on RX and the one on TX could also be +# different, but that's a very obscure case and probably doesn't happen +# very often in practice. +# +# Error conditions: +# - If there is a parity bit, but it doesn't match the expected parity, +# this is called a 'parity error'. +# - If there are no stop bit(s), that's called a 'frame error'. +# +# More information: +# TODO: URLs +# + +# +# Protocol output format: +# +# UART packet: +# [, , ] +# +# This is the list of s and their respective : +# - T_START: The data is the (integer) value of the start bit (0 or 1). +# - T_DATA: The data is the (integer) value of the UART data. Valid values +# range from 0 to 512 (as the data can be up to 9 bits in size). +# - T_PARITY: The data is the (integer) value of the parity bit (0 or 1). +# - T_STOP: The data is the (integer) value of the stop bit (0 or 1). +# - T_INVALID_START: The data is the (integer) value of the start bit (0 or 1). +# - T_INVALID_STOP: The data is the (integer) value of the stop bit (0 or 1). +# - T_PARITY_ERROR: The data is a tuple with two entries. The first one is +# the expected parity value, the second is the actual parity value. +# +# The field is 0 for RX packets, 1 for TX packets. +# + +import sigrokdecode as srd + +# States +WAIT_FOR_START_BIT = 0 +GET_START_BIT = 1 +GET_DATA_BITS = 2 +GET_PARITY_BIT = 3 +GET_STOP_BITS = 4 + +# Used for differentiating between the two data directions. +RX = 0 +TX = 1 + +# Parity options +PARITY_NONE = 0 +PARITY_ODD = 1 +PARITY_EVEN = 2 +PARITY_ZERO = 3 +PARITY_ONE = 4 + +# Stop bit options +STOP_BITS_0_5 = 0 +STOP_BITS_1 = 1 +STOP_BITS_1_5 = 2 +STOP_BITS_2 = 3 + +# Bit order options +LSB_FIRST = 0 +MSB_FIRST = 1 + +# Annotation feed formats +ANN_ASCII = 0 +ANN_DEC = 1 +ANN_HEX = 2 +ANN_OCT = 3 +ANN_BITS = 4 + +# Protocol output packet types +T_START = 0 +T_DATA = 1 +T_PARITY = 2 +T_STOP = 3 +T_INVALID_START = 4 +T_INVALID_STOP = 5 +T_PARITY_ERROR = 6 + +# 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. +# PARITY_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 == PARITY_ZERO: + return parity_bit == 0 + elif parity_type == PARITY_ONE: + return parity_bit == 1 + + # Count number of 1 (high) bits in the data (and the parity bit itself!). + parity = bin(data).count('1') + parity_bit + + # Check for odd/even parity. + if parity_type == PARITY_ODD: + return (parity % 2) == 1 + elif parity_type == PARITY_EVEN: + return (parity % 2) == 0 + else: + raise Exception('Invalid parity type: %d' % parity_type) + +class Decoder(srd.Decoder): + id = 'uart' + name = 'UART' + longname = 'Universal Asynchronous Receiver/Transmitter' + desc = 'Universal Asynchronous Receiver/Transmitter (UART)' + longdesc = 'TODO.' + 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'}, + ] + options = { + 'baudrate': ['Baud rate', 115200], + 'num_data_bits': ['Data bits', 8], # Valid: 5-9. + 'parity': ['Parity', PARITY_NONE], + 'parity_check': ['Check parity', True], + 'num_stop_bits': ['Stop bit(s)', STOP_BITS_1], + '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.stopbit1 = [-1, -1] + self.startsample = [-1, -1] + + # Initial state. + self.state = [WAIT_FOR_START_BIT, WAIT_FOR_START_BIT] + + self.oldbit = [None, None] + + # Set protocol decoder option defaults. + self.baudrate = Decoder.options['baudrate'][1] + self.num_data_bits = Decoder.options['num_data_bits'][1] + self.parity = Decoder.options['parity'][1] + self.check_parity = Decoder.options['parity_check'][1] + self.num_stop_bits = Decoder.options['num_stop_bits'][1] + self.bit_order = Decoder.options['bit_order'][1] + + 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') + + # TODO: Override PD options, if user wants that. + + # The width of one UART bit in number of samples. + self.bit_width = float(self.samplerate) / float(self.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, + [T_INVALID_START, 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, + [T_START, 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.bit_order == LSB_FIRST: + self.databyte[rxtx] >>= 1 + self.databyte[rxtx] |= (signal << (self.num_data_bits - 1)) + elif self.bit_order == MSB_FIRST: + self.databyte[rxtx] <<= 1 + self.databyte[rxtx] |= (signal << 0) + else: + raise Exception('Invalid bit order value: %d', self.bit_order) + + # Return here, unless we already received all data bits. + if self.cur_data_bit[rxtx] < self.num_data_bits - 1: # TODO? Off-by-one? + self.cur_data_bit[rxtx] += 1 + return + + self.state[rxtx] = GET_PARITY_BIT + + self.put(self.startsample[rxtx], self.samplenum - 1, self.out_proto, + [T_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.parity == PARITY_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.num_data_bits + 1): + return + + self.paritybit[rxtx] = signal + + self.state[rxtx] = GET_STOP_BITS + + if parity_ok(self.parity[rxtx], self.paritybit[rxtx], + self.databyte[rxtx], self.num_data_bits): + # TODO: Fix range. + self.put(self.samplenum, self.samplenum, self.out_proto, + [T_PARITY_BIT, 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, + [T_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.parity == PARITY_NONE else 1 + if not self.reached_bit(rxtx, self.num_data_bits + 1 + skip_parity): + 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, + [T_INVALID_STOP, 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, + [T_STOP, 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 + for (samplenum, (rx, tx)) in data: + + # TODO: Start counting at 0 or 1? Increase before or after? + self.samplenum += 1 + + # 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: %s' % self.state[rxtx]) + + # Save current RX/TX values for the next round. + self.oldbit[rxtx] = signal + diff --git a/decoders/usb.py b/decoders/usb.py deleted file mode 100644 index 073fe01..0000000 --- a/decoders/usb.py +++ /dev/null @@ -1,193 +0,0 @@ -## -## This file is part of the sigrok project. -## -## Copyright (C) 2011 Gareth McMullin -## -## This program is free software; you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published by -## the Free Software Foundation; either version 2 of the License, or -## (at your option) any later version. -## -## This program is distributed in the hope that it will be useful, -## but WITHOUT ANY WARRANTY; without even the implied warranty of -## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -## GNU 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 Full-speed protocol decoder -# -# Full-speed USB signalling consists of two signal lines, both driven at 3.3V -# logic levels. The signals are DP (D+) and DM (D-), and normally operate in -# differential mode. -# The state where DP=1,DM=0 is J, the state DP=0,DM=1 is K. -# A state SE0 is defined where DP=DM=0. This common mode signal is used to -# signal a reset or end of packet. -# -# Data transmitted on the USB is encoded with NRZI. A transition from J to K -# or vice-versa indicates a logic 0, while no transition indicates a logic 1. -# If 6 ones are transmitted consecutively, a zero is inserted to force a -# transition. This is known as bit stuffing. Data is transferred at a rate -# of 12Mbit/s. The SE0 transmitted to signal an end-of-packet is two bit -# intervals long. -# -# Details: -# https://en.wikipedia.org/wiki/USB -# http://www.usb.org/developers/docs/ -# - -import sigrokdecode as srd - -# States -SE0, J, K, SE1 = 0, 1, 2, 3 - -# ... -syms = { - (0, 0): SE0, - (1, 0): J, - (0, 1): K, - (1, 1): SE1, -} - -# ... -pids = { - '10000111': 'OUT', # Tokens - '10010110': 'IN', - '10100101': 'SOF', - '10110100': 'SETUP', - '11000011': 'DATA0', # Data - '11010010': 'DATA1', - '01001011': 'ACK', # Handshake - '01011010': 'NAK', - '01111000': 'STALL', - '01101001': 'NYET', -} - -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) - - # 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:] - - if sync != '00000001': - return 'SYNC INVALID!' - - return pid + ' ' + data - -class Decoder(srd.Decoder): - id = 'usb' - name = 'USB' - longname = 'Universal Serial Bus' - desc = 'Universal Serial Bus' - longdesc = '...longdesc...' - license = 'gplv2+' - inputs = ['logic'] - outputs = ['usb'] - probes = [ - {'id': 'dp', 'name': 'D+', 'desc': 'USB D+ signal'}, - {'id': 'dm', 'name': 'D-', 'desc': 'USB D- signal'}, - ] - options = {} - annotations = [ - ['TODO', 'TODO'] - ] - - def __init__(self): - pass - - def start(self, metadata): - self.rate = metadata['samplerate'] - - # self.out_proto = self.add(srd.OUTPUT_PROTO, 'usb') - self.out_ann = self.add(srd.OUTPUT_ANN, 'usb') - - if self.rate < 48000000: - raise Exception('Sample rate not sufficient for USB decoding') - - # Initialise decoder state. - self.sym = J - self.scount = 0 - self.packet = '' - - def decode(self, ss, es, data): - - # FIXME - # for (samplenum, (dp, dm, x, y, z, a)) in data: - for (samplenum, (dm, dp)) in data: - - self.scount += 1 - - sym = syms[dp, dm] - - # ... - if sym == self.sym: - continue - - if self.scount == 1: - # We ignore single sample width pulses. - # I sometimes get these with the OLS. - self.sym = sym - self.scount = 0 - continue - - # How many bits since the last transition? - if self.packet != '' or self.sym != J: - bitcount = int((self.scount - 1) * 12000000 / self.rate) - 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]]) - else: - # Longer than EOP, assume reset. - self.put(0, 0, self.out_ann, [0, ['RESET']]) - 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.scount = 0 - self.sym = sym - diff --git a/decoders/usb/Makefile.am b/decoders/usb/Makefile.am new file mode 100644 index 0000000..f758e06 --- /dev/null +++ b/decoders/usb/Makefile.am @@ -0,0 +1,26 @@ +## +## 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 +## + +pkgdatadir = $(DECODERS_DIR)/usb + +dist_pkgdata_DATA = __init__.py usb.py + +CLEANFILES = *.pyc + diff --git a/decoders/usb/__init__.py b/decoders/usb/__init__.py new file mode 100644 index 0000000..068cadf --- /dev/null +++ b/decoders/usb/__init__.py @@ -0,0 +1,22 @@ +## +## 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 +## + +from .usb import * + diff --git a/decoders/usb/usb.py b/decoders/usb/usb.py new file mode 100644 index 0000000..073fe01 --- /dev/null +++ b/decoders/usb/usb.py @@ -0,0 +1,193 @@ +## +## This file is part of the sigrok project. +## +## Copyright (C) 2011 Gareth McMullin +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU 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 Full-speed protocol decoder +# +# Full-speed USB signalling consists of two signal lines, both driven at 3.3V +# logic levels. The signals are DP (D+) and DM (D-), and normally operate in +# differential mode. +# The state where DP=1,DM=0 is J, the state DP=0,DM=1 is K. +# A state SE0 is defined where DP=DM=0. This common mode signal is used to +# signal a reset or end of packet. +# +# Data transmitted on the USB is encoded with NRZI. A transition from J to K +# or vice-versa indicates a logic 0, while no transition indicates a logic 1. +# If 6 ones are transmitted consecutively, a zero is inserted to force a +# transition. This is known as bit stuffing. Data is transferred at a rate +# of 12Mbit/s. The SE0 transmitted to signal an end-of-packet is two bit +# intervals long. +# +# Details: +# https://en.wikipedia.org/wiki/USB +# http://www.usb.org/developers/docs/ +# + +import sigrokdecode as srd + +# States +SE0, J, K, SE1 = 0, 1, 2, 3 + +# ... +syms = { + (0, 0): SE0, + (1, 0): J, + (0, 1): K, + (1, 1): SE1, +} + +# ... +pids = { + '10000111': 'OUT', # Tokens + '10010110': 'IN', + '10100101': 'SOF', + '10110100': 'SETUP', + '11000011': 'DATA0', # Data + '11010010': 'DATA1', + '01001011': 'ACK', # Handshake + '01011010': 'NAK', + '01111000': 'STALL', + '01101001': 'NYET', +} + +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) + + # 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:] + + if sync != '00000001': + return 'SYNC INVALID!' + + return pid + ' ' + data + +class Decoder(srd.Decoder): + id = 'usb' + name = 'USB' + longname = 'Universal Serial Bus' + desc = 'Universal Serial Bus' + longdesc = '...longdesc...' + license = 'gplv2+' + inputs = ['logic'] + outputs = ['usb'] + probes = [ + {'id': 'dp', 'name': 'D+', 'desc': 'USB D+ signal'}, + {'id': 'dm', 'name': 'D-', 'desc': 'USB D- signal'}, + ] + options = {} + annotations = [ + ['TODO', 'TODO'] + ] + + def __init__(self): + pass + + def start(self, metadata): + self.rate = metadata['samplerate'] + + # self.out_proto = self.add(srd.OUTPUT_PROTO, 'usb') + self.out_ann = self.add(srd.OUTPUT_ANN, 'usb') + + if self.rate < 48000000: + raise Exception('Sample rate not sufficient for USB decoding') + + # Initialise decoder state. + self.sym = J + self.scount = 0 + self.packet = '' + + def decode(self, ss, es, data): + + # FIXME + # for (samplenum, (dp, dm, x, y, z, a)) in data: + for (samplenum, (dm, dp)) in data: + + self.scount += 1 + + sym = syms[dp, dm] + + # ... + if sym == self.sym: + continue + + if self.scount == 1: + # We ignore single sample width pulses. + # I sometimes get these with the OLS. + self.sym = sym + self.scount = 0 + continue + + # How many bits since the last transition? + if self.packet != '' or self.sym != J: + bitcount = int((self.scount - 1) * 12000000 / self.rate) + 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]]) + else: + # Longer than EOP, assume reset. + self.put(0, 0, self.out_ann, [0, ['RESET']]) + 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.scount = 0 + self.sym = sym +