]> sigrok.org Git - libsigrokdecode.git/commitdiff
srd: Each PD now has its own subdirectory.
authorUwe Hermann <redacted>
Sun, 15 Jan 2012 14:36:01 +0000 (15:36 +0100)
committerUwe Hermann <redacted>
Sun, 15 Jan 2012 17:18:23 +0000 (18:18 +0100)
47 files changed:
configure.ac
decoder.c
decoders/Makefile.am
decoders/dcf77.py [deleted file]
decoders/dcf77/Makefile.am [new file with mode: 0644]
decoders/dcf77/__init__.py [new file with mode: 0644]
decoders/dcf77/dcf77.py [new file with mode: 0644]
decoders/ddc.py [deleted file]
decoders/ddc/Makefile.am [new file with mode: 0644]
decoders/ddc/__init__.py [new file with mode: 0644]
decoders/ddc/ddc.py [new file with mode: 0644]
decoders/ebr30a_i2c_demux.py [deleted file]
decoders/ebr30a_i2c_demux/Makefile.am [new file with mode: 0644]
decoders/ebr30a_i2c_demux/__init__.py [new file with mode: 0644]
decoders/ebr30a_i2c_demux/ebr30a_i2c_demux.py [new file with mode: 0644]
decoders/i2c.py [deleted file]
decoders/i2c/Makefile.am [new file with mode: 0644]
decoders/i2c/__init__.py [new file with mode: 0644]
decoders/i2c/i2c.py [new file with mode: 0644]
decoders/mx25lxx05d.py [deleted file]
decoders/mx25lxx05d/Makefile.am [new file with mode: 0644]
decoders/mx25lxx05d/__init__.py [new file with mode: 0644]
decoders/mx25lxx05d/mx25lxx05d.py [new file with mode: 0644]
decoders/nunchuk.py [deleted file]
decoders/nunchuk/Makefile.am [new file with mode: 0644]
decoders/nunchuk/__init__.py [new file with mode: 0644]
decoders/nunchuk/nunchuk.py [new file with mode: 0644]
decoders/pan1321.py [deleted file]
decoders/pan1321/Makefile.am [new file with mode: 0644]
decoders/pan1321/__init__.py [new file with mode: 0644]
decoders/pan1321/pan1321.py [new file with mode: 0644]
decoders/spi.py [deleted file]
decoders/spi/Makefile.am [new file with mode: 0644]
decoders/spi/__init__.py [new file with mode: 0644]
decoders/spi/spi.py [new file with mode: 0644]
decoders/transitioncounter.py [deleted file]
decoders/transitioncounter/Makefile.am [new file with mode: 0644]
decoders/transitioncounter/__init__.py [new file with mode: 0644]
decoders/transitioncounter/transitioncounter.py [new file with mode: 0644]
decoders/uart.py [deleted file]
decoders/uart/Makefile.am [new file with mode: 0644]
decoders/uart/__init__.py [new file with mode: 0644]
decoders/uart/uart.py [new file with mode: 0644]
decoders/usb.py [deleted file]
decoders/usb/Makefile.am [new file with mode: 0644]
decoders/usb/__init__.py [new file with mode: 0644]
decoders/usb/usb.py [new file with mode: 0644]

index a60a6a9f770ba7424d22c85912feb8673e135fd8..7acdf40ab385dd7a8e53b53bb2bfa067d16de67d 100644 (file)
@@ -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
index dde067ee45eb9520aa3f6c0a846ec8ac885d02da..957d3a2417560e2b7a13db99bef8a81e35cf8e51 100644 (file)
--- a/decoder.c
+++ b/decoder.c
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+#include <glib.h>
 #include "config.h"
 #include "sigrokdecode.h" /* First, so we avoid a _POSIX_C_SOURCE warning. */
 #include "sigrokdecode-internal.h"
-#include <dirent.h>
 
 /* 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
  */
index 7c703eec8a55aa590465992d8841e024c5c8d413..c1726cfc0fd76aa08ee96aebe007ee37e58b4a26 100644 (file)
 ## 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 (file)
index 48b3bba..0000000
+++ /dev/null
@@ -1,277 +0,0 @@
-##
-## This file is part of the sigrok project.
-##
-## Copyright (C) 2012 Uwe Hermann <uwe@hermann-uwe.de>
-##
-## This program is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published by
-## the Free Software Foundation; either version 2 of the License, or
-## (at your option) any later version.
-##
-## This program is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-## GNU 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 (file)
index 0000000..c55f0b7
--- /dev/null
@@ -0,0 +1,26 @@
+##
+## This file is part of the sigrok project.
+##
+## Copyright (C) 2012 Uwe Hermann <uwe@hermann-uwe.de>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU 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 (file)
index 0000000..db7d2de
--- /dev/null
@@ -0,0 +1,22 @@
+##
+## This file is part of the sigrok project.
+##
+## Copyright (C) 2012 Uwe Hermann <uwe@hermann-uwe.de>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU 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 (file)
index 0000000..48b3bba
--- /dev/null
@@ -0,0 +1,277 @@
+##
+## This file is part of the sigrok project.
+##
+## Copyright (C) 2012 Uwe Hermann <uwe@hermann-uwe.de>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU 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 (file)
index f839093..0000000
+++ /dev/null
@@ -1,74 +0,0 @@
-##
-## This file is part of the sigrok project.
-##
-## Copyright (C) 2012 Bert Vermeulen <bert@biot.com>
-##
-## This program is free software; you can 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 <http://www.gnu.org/licenses/>.
-##
-
-'''
-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 (file)
index 0000000..59c2c69
--- /dev/null
@@ -0,0 +1,26 @@
+##
+## This file is part of the sigrok project.
+##
+## Copyright (C) 2012 Uwe Hermann <uwe@hermann-uwe.de>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU 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 (file)
index 0000000..95cf31e
--- /dev/null
@@ -0,0 +1,22 @@
+##
+## This file is part of the sigrok project.
+##
+## Copyright (C) 2012 Uwe Hermann <uwe@hermann-uwe.de>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU 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 (file)
index 0000000..f839093
--- /dev/null
@@ -0,0 +1,74 @@
+##
+## This file is part of the sigrok project.
+##
+## Copyright (C) 2012 Bert Vermeulen <bert@biot.com>
+##
+## This program is free software; you can 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 <http://www.gnu.org/licenses/>.
+##
+
+'''
+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 (file)
index 353fea8..0000000
+++ /dev/null
@@ -1,104 +0,0 @@
-##
-## This file is part of the sigrok project.
-##
-## Copyright (C) 2012 Uwe Hermann <uwe@hermann-uwe.de>
-##
-## This program is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published by
-## the Free Software Foundation; either version 2 of the License, or
-## (at your option) any later version.
-##
-## This program is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-## GNU 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 (file)
index 0000000..02e3421
--- /dev/null
@@ -0,0 +1,26 @@
+##
+## This file is part of the sigrok project.
+##
+## Copyright (C) 2012 Uwe Hermann <uwe@hermann-uwe.de>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU 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 (file)
index 0000000..dddc8b7
--- /dev/null
@@ -0,0 +1,22 @@
+##
+## This file is part of the sigrok project.
+##
+## Copyright (C) 2012 Uwe Hermann <uwe@hermann-uwe.de>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU 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 (file)
index 0000000..353fea8
--- /dev/null
@@ -0,0 +1,104 @@
+##
+## This file is part of the sigrok project.
+##
+## Copyright (C) 2012 Uwe Hermann <uwe@hermann-uwe.de>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU 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 (file)
index f0a95b7..0000000
+++ /dev/null
@@ -1,304 +0,0 @@
-##
-## This file is part of the sigrok project.
-##
-## Copyright (C) 2010-2011 Uwe Hermann <uwe@hermann-uwe.de>
-##
-## This program is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published by
-## the Free Software Foundation; either version 2 of the License, or
-## (at your option) any later version.
-##
-## This program is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-## GNU 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:
-[<i2c_command>, <data>, <ack_bit>]
-
-<i2c_command> 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)
-
-<data> is the data or address byte associated with the 'ADDRESS*' and 'DATA*'
-command. For 'START', 'START REPEAT' and 'STOP', this is None.
-
-<ack_bit> 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 (file)
index 0000000..75a53eb
--- /dev/null
@@ -0,0 +1,26 @@
+##
+## This file is part of the sigrok project.
+##
+## Copyright (C) 2012 Uwe Hermann <uwe@hermann-uwe.de>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU 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 (file)
index 0000000..9e7856e
--- /dev/null
@@ -0,0 +1,22 @@
+##
+## This file is part of the sigrok project.
+##
+## Copyright (C) 2012 Uwe Hermann <uwe@hermann-uwe.de>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU 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 (file)
index 0000000..f0a95b7
--- /dev/null
@@ -0,0 +1,304 @@
+##
+## This file is part of the sigrok project.
+##
+## Copyright (C) 2010-2011 Uwe Hermann <uwe@hermann-uwe.de>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU 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:
+[<i2c_command>, <data>, <ack_bit>]
+
+<i2c_command> 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)
+
+<data> is the data or address byte associated with the 'ADDRESS*' and 'DATA*'
+command. For 'START', 'START REPEAT' and 'STOP', this is None.
+
+<ack_bit> 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 (file)
index cd97dee..0000000
+++ /dev/null
@@ -1,272 +0,0 @@
-##
-## This file is part of the sigrok project.
-##
-## Copyright (C) 2011-2012 Uwe Hermann <uwe@hermann-uwe.de>
-##
-## This program is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published by
-## the Free Software Foundation; either version 2 of the License, or
-## (at your option) any later version.
-##
-## This program is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-## GNU 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 (file)
index 0000000..2d571f3
--- /dev/null
@@ -0,0 +1,26 @@
+##
+## This file is part of the sigrok project.
+##
+## Copyright (C) 2012 Uwe Hermann <uwe@hermann-uwe.de>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU 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 (file)
index 0000000..0affc99
--- /dev/null
@@ -0,0 +1,22 @@
+##
+## This file is part of the sigrok project.
+##
+## Copyright (C) 2012 Uwe Hermann <uwe@hermann-uwe.de>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU 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 (file)
index 0000000..cd97dee
--- /dev/null
@@ -0,0 +1,272 @@
+##
+## This file is part of the sigrok project.
+##
+## Copyright (C) 2011-2012 Uwe Hermann <uwe@hermann-uwe.de>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU 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 (file)
index 46bf53c..0000000
+++ /dev/null
@@ -1,144 +0,0 @@
-##
-## This file is part of the sigrok project.
-##
-## Copyright (C) 2010-2012 Uwe Hermann <uwe@hermann-uwe.de>
-##
-## This program is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published by
-## the Free Software Foundation; either version 2 of the License, or
-## (at your option) any later version.
-##
-## This program is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-## GNU 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 (file)
index 0000000..5a4917b
--- /dev/null
@@ -0,0 +1,26 @@
+##
+## This file is part of the sigrok project.
+##
+## Copyright (C) 2012 Uwe Hermann <uwe@hermann-uwe.de>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU 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 (file)
index 0000000..6772ba7
--- /dev/null
@@ -0,0 +1,22 @@
+##
+## This file is part of the sigrok project.
+##
+## Copyright (C) 2012 Uwe Hermann <uwe@hermann-uwe.de>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU 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 (file)
index 0000000..46bf53c
--- /dev/null
@@ -0,0 +1,144 @@
+##
+## This file is part of the sigrok project.
+##
+## Copyright (C) 2010-2012 Uwe Hermann <uwe@hermann-uwe.de>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU 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 (file)
index 55676ce..0000000
+++ /dev/null
@@ -1,115 +0,0 @@
-##
-## This file is part of the sigrok project.
-##
-## Copyright (C) 2012 Uwe Hermann <uwe@hermann-uwe.de>
-##
-## This program is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published by
-## the Free Software Foundation; either version 2 of the License, or
-## (at your option) any later version.
-##
-## This program is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-## GNU 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 (file)
index 0000000..6997ed5
--- /dev/null
@@ -0,0 +1,26 @@
+##
+## This file is part of the sigrok project.
+##
+## Copyright (C) 2012 Uwe Hermann <uwe@hermann-uwe.de>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU 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 (file)
index 0000000..abbcf5d
--- /dev/null
@@ -0,0 +1,22 @@
+##
+## This file is part of the sigrok project.
+##
+## Copyright (C) 2012 Uwe Hermann <uwe@hermann-uwe.de>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU 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 (file)
index 0000000..55676ce
--- /dev/null
@@ -0,0 +1,115 @@
+##
+## This file is part of the sigrok project.
+##
+## Copyright (C) 2012 Uwe Hermann <uwe@hermann-uwe.de>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU 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 (file)
index 07b8da2..0000000
+++ /dev/null
@@ -1,170 +0,0 @@
-##
-## This file is part of the sigrok project.
-##
-## Copyright (C) 2011 Gareth McMullin <gareth@blacksphere.co.nz>
-## Copyright (C) 2012 Uwe Hermann <uwe@hermann-uwe.de>
-##
-## This program is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published by
-## the Free Software Foundation; either version 2 of the License, or
-## (at your option) any later version.
-##
-## This program is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-## GNU 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 (file)
index 0000000..89bf640
--- /dev/null
@@ -0,0 +1,26 @@
+##
+## This file is part of the sigrok project.
+##
+## Copyright (C) 2012 Uwe Hermann <uwe@hermann-uwe.de>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU 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 (file)
index 0000000..3276fed
--- /dev/null
@@ -0,0 +1,22 @@
+##
+## This file is part of the sigrok project.
+##
+## Copyright (C) 2012 Uwe Hermann <uwe@hermann-uwe.de>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU 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 (file)
index 0000000..07b8da2
--- /dev/null
@@ -0,0 +1,170 @@
+##
+## This file is part of the sigrok project.
+##
+## Copyright (C) 2011 Gareth McMullin <gareth@blacksphere.co.nz>
+## Copyright (C) 2012 Uwe Hermann <uwe@hermann-uwe.de>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU 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 (file)
index 4c832b0..0000000
+++ /dev/null
@@ -1,100 +0,0 @@
-##
-## This file is part of the sigrok project.
-##
-## Copyright (C) 2010 Uwe Hermann <uwe@hermann-uwe.de>
-##
-## This program is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published by
-## the Free Software Foundation; either version 2 of the License, or
-## (at your option) any later version.
-##
-## This program is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-## GNU 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 (file)
index 0000000..74688a3
--- /dev/null
@@ -0,0 +1,26 @@
+##
+## This file is part of the sigrok project.
+##
+## Copyright (C) 2012 Uwe Hermann <uwe@hermann-uwe.de>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU 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 (file)
index 0000000..bdf296b
--- /dev/null
@@ -0,0 +1,22 @@
+##
+## This file is part of the sigrok project.
+##
+## Copyright (C) 2012 Uwe Hermann <uwe@hermann-uwe.de>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU 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 (file)
index 0000000..4c832b0
--- /dev/null
@@ -0,0 +1,100 @@
+##
+## This file is part of the sigrok project.
+##
+## Copyright (C) 2010 Uwe Hermann <uwe@hermann-uwe.de>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU 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 (file)
index 4752c7d..0000000
+++ /dev/null
@@ -1,432 +0,0 @@
-##
-## This file is part of the sigrok project.
-##
-## Copyright (C) 2011 Uwe Hermann <uwe@hermann-uwe.de>
-##
-## This program is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published by
-## the Free Software Foundation; either version 2 of the License, or
-## (at your option) any later version.
-##
-## This program is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-## GNU 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:
-# [<packet-type>, <rxtx>, <packet-data>]
-#
-# This is the list of <packet-types>s and their respective <packet-data>:
-#  - 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 <rxtx> 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 (file)
index 0000000..155bfc9
--- /dev/null
@@ -0,0 +1,26 @@
+##
+## This file is part of the sigrok project.
+##
+## Copyright (C) 2012 Uwe Hermann <uwe@hermann-uwe.de>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU 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 (file)
index 0000000..4267d83
--- /dev/null
@@ -0,0 +1,22 @@
+##
+## This file is part of the sigrok project.
+##
+## Copyright (C) 2012 Uwe Hermann <uwe@hermann-uwe.de>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU 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 (file)
index 0000000..4752c7d
--- /dev/null
@@ -0,0 +1,432 @@
+##
+## This file is part of the sigrok project.
+##
+## Copyright (C) 2011 Uwe Hermann <uwe@hermann-uwe.de>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU 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:
+# [<packet-type>, <rxtx>, <packet-data>]
+#
+# This is the list of <packet-types>s and their respective <packet-data>:
+#  - 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 <rxtx> 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 (file)
index 073fe01..0000000
+++ /dev/null
@@ -1,193 +0,0 @@
-##
-## This file is part of the sigrok project.
-##
-## Copyright (C) 2011 Gareth McMullin <gareth@blacksphere.co.nz>
-##
-## This program is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published by
-## the Free Software Foundation; either version 2 of the License, or
-## (at your option) any later version.
-##
-## This program is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-## GNU 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 (file)
index 0000000..f758e06
--- /dev/null
@@ -0,0 +1,26 @@
+##
+## This file is part of the sigrok project.
+##
+## Copyright (C) 2012 Uwe Hermann <uwe@hermann-uwe.de>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU 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 (file)
index 0000000..068cadf
--- /dev/null
@@ -0,0 +1,22 @@
+##
+## This file is part of the sigrok project.
+##
+## Copyright (C) 2012 Uwe Hermann <uwe@hermann-uwe.de>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU 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 (file)
index 0000000..073fe01
--- /dev/null
@@ -0,0 +1,193 @@
+##
+## This file is part of the sigrok project.
+##
+## Copyright (C) 2011 Gareth McMullin <gareth@blacksphere.co.nz>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU 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
+