]> sigrok.org Git - libsigrokdecode.git/commitdiff
rgb_led_ws281x: rework the .decode() main loop, improve robustness
authorGerhard Sittig <redacted>
Sat, 29 Jul 2023 14:53:07 +0000 (16:53 +0200)
committerGerhard Sittig <redacted>
Sat, 29 Jul 2023 19:29:10 +0000 (21:29 +0200)
Concentrate timestamp gathering in the .decode() method, eliminate
instance members by using variables that are local to the method.
Finally use appropriate PD API v3 invocations. Use edge conditions plus
a counted 'skip' to detect the RESET pulse. Use a positive "check the
reset condition" logic, simplify the conditions which support the reset
pulse tracking, and which flush previously accumulated data when "the
bit time doesn't end" (the next edge is missing).

Improve robustness in those cases where captures use low oversampling
and similar length high and low pulses. The fixed (rather arbitrary?)
625us threshold resulted in several false last-bit values after the
API v3 conversion.

Heavily comment on this edge/pulse detection and timestamps logic,
since it's non-trivial and non-obvious. Keep all behaviour backwards
compatible before extending the feature set in future commits.

decoders/rgb_led_ws281x/pd.py

index 295740515d41ef6286c1937c102a47d7a4fe30ba..5f355942b392d3b751115ab76bf345bdf91fe125 100644 (file)
@@ -57,11 +57,7 @@ class Decoder(srd.Decoder):
 
     def reset(self):
         self.samplerate = None
-        self.oldpin = None
-        self.ss = None
-        self.es = None
         self.bits = []
-        self.inreset = False
 
     def start(self):
         self.out_ann = self.register(srd.OUTPUT_ANN)
@@ -103,46 +99,77 @@ class Decoder(srd.Decoder):
             raise SamplerateError('Cannot decode without samplerate.')
         self.need_bits = len(self.options['type']) * 8
 
+        # Either check for edges which communicate bit values, or for
+        # long periods of idle level which represent a reset pulse.
+        # Track the left-most, right-most, and inner edge positions of
+        # a bit. The positive period's width determines the bit's value.
+        # Initially synchronize to the input stream by searching for a
+        # low period, which preceeds a data bit or starts a reset pulse.
+        # Don't annotate the very first reset pulse, but process it. We
+        # may not see the right-most edge of a data bit when reset is
+        # adjacent to that bit time.
+        cond_bit_starts = {0: 'r'}
+        cond_inbit_edge = {0: 'f'}
+        samples_625ns = int(self.samplerate * 625e-9)
+        samples_50us = round(self.samplerate * 50e-6)
+        cond_reset_pulse = {'skip': samples_50us + 1}
+        conds = [cond_bit_starts, cond_inbit_edge, cond_reset_pulse]
+        ss_bit, inv_bit, es_bit = None, None, None
+        pin, = self.wait({0: 'l'})
+        inv_bit = self.samplenum
+        check_reset = False
         while True:
-            # TODO: Come up with more appropriate self.wait() conditions.
-            (pin,) = self.wait()
+            pin, = self.wait(conds)
+
+            # Check RESET condition. Manufacturers may disagree on the
+            # minimal pulse width. 50us are recommended in datasheets,
+            # experiments suggest the limit is around 10us.
+            # When the RESET pulse is adjacent to the low phase of the
+            # last bit time, we have no appropriate condition for the
+            # bit time's end location. That's why this BIT's annotation
+            # is shorter (only spans the high phase), and the RESET
+            # annotation immediately follows (spans from the falling edge
+            # to the end of the minimum RESET pulse width).
+            if check_reset and self.matched[2]:
+                es_bit = inv_bit
+                ss_rst, es_rst = inv_bit, self.samplenum
+
+                if ss_bit and inv_bit and es_bit:
+                    # Decode last bit value. Use the last processed bit's
+                    # width for comparison when available. Fallback to an
+                    # arbitrary threshold otherwise (which can result in
+                    # false detection of value 1 for those captures where
+                    # high and low pulses are of similar width).
+                    duty = inv_bit - ss_bit
+                    thres = samples_625ns
+                    if self.bits:
+                        period = self.bits[-1][2] - self.bits[-1][1]
+                        thres = period * 0.5
+                    bit_value = 1 if duty >= thres else 0
+                    self.handle_bit(ss_bit, inv_bit, bit_value, True)
+
+                if ss_rst and es_rst:
+                    text = ['RESET', 'RST', 'R']
+                    self.putg(ss_rst, es_rst, ANN_RESET, text)
+                check_reset = False
 
-            if self.oldpin is None:
-                self.oldpin = pin
-                continue
-
-            # Check RESET condition (manufacturer recommends 50 usec minimal,
-            # but real minimum is ~10 usec).
-            if not self.inreset and not pin and self.es is not None and \
-                    self.ss is not None and \
-                    (self.samplenum - self.es) / self.samplerate > 50e-6:
-
-                # Decode last bit value.
-                tH = (self.es - self.ss) / self.samplerate
-                bit_ = True if tH >= 625e-9 else False
-
-                self.handle_bit(self.ss, self.es, bit_, True)
-
-                text = ['RESET', 'RST', 'R']
-                self.putg(self.es, self.samplenum, ANN_RESET, text)
-
-                self.inreset = True
                 self.bits.clear()
-                self.ss = None
-
-            if not self.oldpin and pin:
-                # Rising edge.
-                if self.ss and self.es:
-                    period = self.samplenum - self.ss
-                    duty = self.es - self.ss
+                ss_bit, inv_bit, es_bit = None, None, None
+
+            # Rising edge starts a bit time. Falling edge ends its high
+            # period. Get the previous bit's duty cycle and thus its
+            # bit value when the next bit starts.
+            if self.matched[0]: # and pin:
+                check_reset = False
+                if ss_bit and inv_bit:
+                    # Got a previous bit? Handle it.
+                    es_bit = self.samplenum
+                    period = es_bit - ss_bit
+                    duty = inv_bit - ss_bit
                     # Ideal duty for T0H: 33%, T1H: 66%.
-                    bit_ = (duty / period) > 0.5
-                    self.handle_bit(self.ss, self.samplenum, bit_)
-                self.ss = self.samplenum
-
-            elif self.oldpin and not pin:
-                # Falling edge.
-                self.inreset = False
-                self.es = self.samplenum
-
-            self.oldpin = pin
+                    bit_value = 1 if (duty / period) > 0.5 else 0
+                    self.handle_bit(ss_bit, es_bit, bit_value)
+                ss_bit, inv_bit, es_bit = self.samplenum, None, None
+            if self.matched[1]: # and not pin:
+                check_reset = True
+                inv_bit = self.samplenum