From 8f1dd70dd15916813186135bae71aa0683851da9 Mon Sep 17 00:00:00 2001 From: Gerhard Sittig Date: Fri, 28 Jul 2023 08:08:38 +0200 Subject: [PATCH 01/16] nes_gamepad: adjust Python style of decoder implementation Rephrase the NES gamepad decoder to become more idiomatic Python. Address nits to better match the sigrok project's coding style. Eliminate hidden coupling by means of instance variables. --- decoders/nes_gamepad/pd.py | 49 ++++++++++++++------------------------ 1 file changed, 18 insertions(+), 31 deletions(-) diff --git a/decoders/nes_gamepad/pd.py b/decoders/nes_gamepad/pd.py index b276e5d..a393abf 100644 --- a/decoders/nes_gamepad/pd.py +++ b/decoders/nes_gamepad/pd.py @@ -51,24 +51,22 @@ class Decoder(srd.Decoder): def reset(self): self.variant = None - self.ss_block = None - self.es_block = None def start(self): self.out_ann = self.register(srd.OUTPUT_ANN) self.variant = self.options['variant'] - def putx(self, data): - self.put(self.ss_block, self.es_block, self.out_ann, data) + def putg(self, ss, es, cls, text): + self.put(ss, es, self.out_ann, [cls, [text]]) - def handle_data(self, value): - if value == 0xFF: - self.putx([1, ['No button is pressed']]) - return + def handle_data(self, ss, es, value): + if value == 0xff: + self.putg(ss, es, 1, 'No button is pressed') + return if value == 0x00: - self.putx([2, ['Gamepad is not connected']]) - return + self.putg(ss, es, 2, 'Gamepad is not connected') + return buttons = [ 'A', @@ -78,28 +76,17 @@ class Decoder(srd.Decoder): 'North', 'South', 'West', - 'East' + 'East', ] - bits = format(value, '08b') - button_str = '' - - for b in enumerate(bits): - button_index = b[0] - button_is_pressed = b[1] == '0' - - if button_is_pressed: - if button_str != '': - button_str += ' + ' - button_str += buttons[button_index] - - self.putx([0, ['%s' % button_str]]) + bits = '{:08b}'.format(value) + text = [buttons[i] for i, b in enumerate(bits) if b == '0'] + text = ' + '.join(text) + self.putg(ss, es, 0, text) def decode(self, ss, es, data): - ptype, mosi, miso = data - self.ss_block, self.es_block = ss, es - - if ptype != 'DATA': - return - - self.handle_data(miso) + ptype, _, _ = data + if ptype == 'DATA': + _, _, miso = data + self.handle_data(ss, es, miso) + return -- 2.30.2 From 0e3c34984c1251da0a1cb90afac1ed000d72e5e0 Mon Sep 17 00:00:00 2001 From: Gerhard Sittig Date: Fri, 28 Jul 2023 07:27:37 +0200 Subject: [PATCH 02/16] rgb_led_ws281x: use symbolic names for annotation classes --- decoders/rgb_led_ws281x/pd.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/decoders/rgb_led_ws281x/pd.py b/decoders/rgb_led_ws281x/pd.py index 43fbce4..0591e95 100644 --- a/decoders/rgb_led_ws281x/pd.py +++ b/decoders/rgb_led_ws281x/pd.py @@ -23,6 +23,8 @@ from functools import reduce class SamplerateError(Exception): pass +( ANN_BIT, ANN_RESET, ANN_RGB, ) = range(3) + class Decoder(srd.Decoder): api_version = 3 id = 'rgb_led_ws281x' @@ -42,8 +44,8 @@ class Decoder(srd.Decoder): ('rgb', 'RGB'), ) annotation_rows = ( - ('bits', 'Bits', (0, 1)), - ('rgb-vals', 'RGB values', (2,)), + ('bits', 'Bits', (ANN_BIT, ANN_RESET,)), + ('rgb-vals', 'RGB values', (ANN_RGB,)), ) options = ( {'id': 'type', 'desc': 'RGB or RGBW', 'default': 'RGB', @@ -75,7 +77,7 @@ class Decoder(srd.Decoder): grb = reduce(lambda a, b: (a << 1) | b, self.bits) rgb = (grb & 0xff0000) >> 8 | (grb & 0x00ff00) << 8 | (grb & 0x0000ff) self.put(self.ss_packet, samplenum, self.out_ann, - [2, ['#%06x' % rgb]]) + [ANN_RGB, ['#%06x' % rgb]]) self.bits = [] self.ss_packet = None else: @@ -83,7 +85,7 @@ class Decoder(srd.Decoder): grb = reduce(lambda a, b: (a << 1) | b, self.bits) rgb = (grb & 0xff0000) >> 8 | (grb & 0x00ff00) << 8 | (grb & 0xff0000ff) self.put(self.ss_packet, samplenum, self.out_ann, - [2, ['#%08x' % rgb]]) + [ANN_RGB, ['#%08x' % rgb]]) self.bits = [] self.ss_packet = None @@ -112,9 +114,9 @@ class Decoder(srd.Decoder): self.bits.append(bit_) self.handle_bits(self.es) - self.put(self.ss, self.es, self.out_ann, [0, ['%d' % bit_]]) + self.put(self.ss, self.es, self.out_ann, [ANN_BIT, ['%d' % bit_]]) self.put(self.es, self.samplenum, self.out_ann, - [1, ['RESET', 'RST', 'R']]) + [ANN_RESET, ['RESET', 'RST', 'R']]) self.inreset = True self.bits = [] @@ -130,7 +132,7 @@ class Decoder(srd.Decoder): bit_ = (duty / period) > 0.5 self.put(self.ss, self.samplenum, self.out_ann, - [0, ['%d' % bit_]]) + [ANN_BIT, ['%d' % bit_]]) self.bits.append(bit_) self.handle_bits(self.samplenum) -- 2.30.2 From 192a9e78f97ea82f7ee69a683b4639847cbf4e5a Mon Sep 17 00:00:00 2001 From: Gerhard Sittig Date: Fri, 28 Jul 2023 07:40:14 +0200 Subject: [PATCH 03/16] rgb_led_ws281x: rephrase .put() calls for readability Separate the construction of the list of texts for different zoom levels from the emission of annotations. Use a .putg() helpers to match other decoder implementations. Prefer .format() calls over "modulo" operations on strings. --- decoders/rgb_led_ws281x/pd.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/decoders/rgb_led_ws281x/pd.py b/decoders/rgb_led_ws281x/pd.py index 0591e95..dbd41df 100644 --- a/decoders/rgb_led_ws281x/pd.py +++ b/decoders/rgb_led_ws281x/pd.py @@ -71,21 +71,24 @@ class Decoder(srd.Decoder): if key == srd.SRD_CONF_SAMPLERATE: self.samplerate = value + def putg(self, ss, es, cls, text): + self.put(ss, es, self.out_ann, [cls, text]) + def handle_bits(self, samplenum): if self.options['type'] == 'RGB': if len(self.bits) == 24: grb = reduce(lambda a, b: (a << 1) | b, self.bits) rgb = (grb & 0xff0000) >> 8 | (grb & 0x00ff00) << 8 | (grb & 0x0000ff) - self.put(self.ss_packet, samplenum, self.out_ann, - [ANN_RGB, ['#%06x' % rgb]]) + text = ['#{:06x}'.format(rgb)] + self.putg(self.ss_packet, samplenum, ANN_RGB, text) self.bits = [] self.ss_packet = None else: if len(self.bits) == 32: grb = reduce(lambda a, b: (a << 1) | b, self.bits) rgb = (grb & 0xff0000) >> 8 | (grb & 0x00ff00) << 8 | (grb & 0xff0000ff) - self.put(self.ss_packet, samplenum, self.out_ann, - [ANN_RGB, ['#%08x' % rgb]]) + text = ['#{:08x}'.format(rgb)] + self.putg(self.ss_packet, samplenum, ANN_RGB, text) self.bits = [] self.ss_packet = None @@ -114,9 +117,10 @@ class Decoder(srd.Decoder): self.bits.append(bit_) self.handle_bits(self.es) - self.put(self.ss, self.es, self.out_ann, [ANN_BIT, ['%d' % bit_]]) - self.put(self.es, self.samplenum, self.out_ann, - [ANN_RESET, ['RESET', 'RST', 'R']]) + text = ['{:d}'.format(bit_)] + self.putg(self.ss, self.es, ANN_BIT, text) + text = ['RESET', 'RST', 'R'] + self.putg(self.es, self.samplenum, ANN_RESET, text) self.inreset = True self.bits = [] @@ -131,8 +135,8 @@ class Decoder(srd.Decoder): # Ideal duty for T0H: 33%, T1H: 66%. bit_ = (duty / period) > 0.5 - self.put(self.ss, self.samplenum, self.out_ann, - [ANN_BIT, ['%d' % bit_]]) + text = ['{:d}'.format(bit_)] + self.putg(self.ss, self.samplenum, ANN_BIT, text) self.bits.append(bit_) self.handle_bits(self.samplenum) -- 2.30.2 From 6d1cde1daeea93b36c1c2c09e160e5b8042ef22b Mon Sep 17 00:00:00 2001 From: Gerhard Sittig Date: Fri, 28 Jul 2023 07:56:46 +0200 Subject: [PATCH 04/16] rgb_led_ws281x: refactor bit/bits handling, use more common code Pass all .decode() routine's bit handling through a common bit handler. Accumulate the bit values as well as their ss/es timestamps. Reduce code duplication in the bits handler. Use common support code to get the 24/32 bit wide integer from the list of bits. Prepare to handle streams of different per-pixel length or layout. This commit remains backwards compatible, and keeps all warts of the previous implementation including inconsistent annotation order. Just eliminates unnecessary instance members and hidden coupling, to keep timestamp handling in the .decode() method. --- decoders/rgb_led_ws281x/pd.py | 60 ++++++++++++++++------------------- 1 file changed, 27 insertions(+), 33 deletions(-) diff --git a/decoders/rgb_led_ws281x/pd.py b/decoders/rgb_led_ws281x/pd.py index dbd41df..2957405 100644 --- a/decoders/rgb_led_ws281x/pd.py +++ b/decoders/rgb_led_ws281x/pd.py @@ -18,7 +18,7 @@ ## import sigrokdecode as srd -from functools import reduce +from common.srdhelper import bitpack_msb class SamplerateError(Exception): pass @@ -58,7 +58,6 @@ class Decoder(srd.Decoder): def reset(self): self.samplerate = None self.oldpin = None - self.ss_packet = None self.ss = None self.es = None self.bits = [] @@ -74,27 +73,35 @@ class Decoder(srd.Decoder): def putg(self, ss, es, cls, text): self.put(ss, es, self.out_ann, [cls, text]) - def handle_bits(self, samplenum): + def handle_bits(self): + if len(self.bits) < self.need_bits: + return + grb = bitpack_msb(self.bits, 0) if self.options['type'] == 'RGB': - if len(self.bits) == 24: - grb = reduce(lambda a, b: (a << 1) | b, self.bits) - rgb = (grb & 0xff0000) >> 8 | (grb & 0x00ff00) << 8 | (grb & 0x0000ff) - text = ['#{:06x}'.format(rgb)] - self.putg(self.ss_packet, samplenum, ANN_RGB, text) - self.bits = [] - self.ss_packet = None + rgb = (grb & 0xff0000) >> 8 | (grb & 0x00ff00) << 8 | (grb & 0x0000ff) + text = '#{:06x}'.format(rgb) else: - if len(self.bits) == 32: - grb = reduce(lambda a, b: (a << 1) | b, self.bits) - rgb = (grb & 0xff0000) >> 8 | (grb & 0x00ff00) << 8 | (grb & 0xff0000ff) - text = ['#{:08x}'.format(rgb)] - self.putg(self.ss_packet, samplenum, ANN_RGB, text) - self.bits = [] - self.ss_packet = None + rgb = (grb & 0xff0000) >> 8 | (grb & 0x00ff00) << 8 | (grb & 0xff0000ff) + text = '#{:08x}'.format(rgb) + ss_packet, es_packet = self.bits[0][1], self.bits[-1][2] + self.putg(ss_packet, es_packet, ANN_RGB, [text]) + self.bits.clear() + + def handle_bit(self, ss, es, value, ann_late = False): + if not ann_late: + text = ['{:d}'.format(value)] + self.putg(ss, es, ANN_BIT, text) + item = (value, ss, es) + self.bits.append(item) + self.handle_bits() + if ann_late: + text = ['{:d}'.format(value)] + self.putg(ss, es, ANN_BIT, text) def decode(self): if not self.samplerate: raise SamplerateError('Cannot decode without samplerate.') + self.need_bits = len(self.options['type']) * 8 while True: # TODO: Come up with more appropriate self.wait() conditions. @@ -114,17 +121,13 @@ class Decoder(srd.Decoder): tH = (self.es - self.ss) / self.samplerate bit_ = True if tH >= 625e-9 else False - self.bits.append(bit_) - self.handle_bits(self.es) + self.handle_bit(self.ss, self.es, bit_, True) - text = ['{:d}'.format(bit_)] - self.putg(self.ss, self.es, ANN_BIT, text) text = ['RESET', 'RST', 'R'] self.putg(self.es, self.samplenum, ANN_RESET, text) self.inreset = True - self.bits = [] - self.ss_packet = None + self.bits.clear() self.ss = None if not self.oldpin and pin: @@ -134,16 +137,7 @@ class Decoder(srd.Decoder): duty = self.es - self.ss # Ideal duty for T0H: 33%, T1H: 66%. bit_ = (duty / period) > 0.5 - - text = ['{:d}'.format(bit_)] - self.putg(self.ss, self.samplenum, ANN_BIT, text) - - self.bits.append(bit_) - self.handle_bits(self.samplenum) - - if self.ss_packet is None: - self.ss_packet = self.samplenum - + self.handle_bit(self.ss, self.samplenum, bit_) self.ss = self.samplenum elif self.oldpin and not pin: -- 2.30.2 From 5e8090d7d30fb0915fc1b54f3ec098c69a417745 Mon Sep 17 00:00:00 2001 From: Gerhard Sittig Date: Sat, 29 Jul 2023 16:53:07 +0200 Subject: [PATCH 05/16] rgb_led_ws281x: rework the .decode() main loop, improve robustness 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 | 113 +++++++++++++++++++++------------- 1 file changed, 70 insertions(+), 43 deletions(-) diff --git a/decoders/rgb_led_ws281x/pd.py b/decoders/rgb_led_ws281x/pd.py index 2957405..5f35594 100644 --- a/decoders/rgb_led_ws281x/pd.py +++ b/decoders/rgb_led_ws281x/pd.py @@ -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 -- 2.30.2 From 17b2579a517489ce06756943529986921e194ef9 Mon Sep 17 00:00:00 2001 From: Gerhard Sittig Date: Sat, 29 Jul 2023 16:59:43 +0200 Subject: [PATCH 06/16] rgb_led_ws281x: support more colour component orders (wire, and text) The 'type' option was not good enough. Replace it by 'wireorder' (order of colour components on the wire, depends on the RGB LED chip type), and 'textorder' (presentation to users in annotations). Support many more layouts of colour components on the wire. Cover all permutations of R, G, and B. Support a few RGB plus W layouts that are known to be in use. Adding more is just a matter of adding more choices in the option, the implementation transparently follows. Support a few text orders: Reflect the very order of bits on the wire. Automatic support for RGB with optional White, or fixed RGB or RGB-W variants (all are users' choices, default remains "RGB" for backwards compatibility). Support arbitrary combinations of wire order and text order in emitted annotations. Keep support for the weird RGWB text format, which the previous decoder implementation used for "all" RGBW types, and which is referenced by existing test cases. It is uncertain which chip type is supposed to generate this specific RGBW traffic. It is as uncertain why this text order was chosen, which neither is the human readable RGBW format nor matches the wire order. The previous implementation was introduced in commit 47ff9910f7e1, but neither commented nor referenced literature or external sources nor did the commit message contain any clues. This current implementation needs more tests and reviews, but lends itself better to maintenance, fixes and enhancements. --- decoders/rgb_led_ws281x/pd.py | 72 +++++++++++++++++++++++++++++------ 1 file changed, 61 insertions(+), 11 deletions(-) diff --git a/decoders/rgb_led_ws281x/pd.py b/decoders/rgb_led_ws281x/pd.py index 5f35594..7c8ead4 100644 --- a/decoders/rgb_led_ws281x/pd.py +++ b/decoders/rgb_led_ws281x/pd.py @@ -23,6 +23,9 @@ from common.srdhelper import bitpack_msb class SamplerateError(Exception): pass +class DecoderError(Exception): + pass + ( ANN_BIT, ANN_RESET, ANN_RGB, ) = range(3) class Decoder(srd.Decoder): @@ -48,8 +51,11 @@ class Decoder(srd.Decoder): ('rgb-vals', 'RGB values', (ANN_RGB,)), ) options = ( - {'id': 'type', 'desc': 'RGB or RGBW', 'default': 'RGB', - 'values': ('RGB', 'RGBW')}, + {'id': 'wireorder', 'desc': 'colour components order (wire)', + 'default': 'GRB', + 'values': ('BGR', 'BRG', 'GBR', 'GRB', 'RBG', 'RGB', 'RWBG', 'RGBW')}, + {'id': 'textorder', 'desc': 'components output order (text)', + 'default': 'RGB', 'values': ('wire', 'RGB[W]', 'RGB', 'RGBW', 'RGWB')}, ) def __init__(self): @@ -72,15 +78,32 @@ class Decoder(srd.Decoder): def handle_bits(self): if len(self.bits) < self.need_bits: return - grb = bitpack_msb(self.bits, 0) - if self.options['type'] == 'RGB': - rgb = (grb & 0xff0000) >> 8 | (grb & 0x00ff00) << 8 | (grb & 0x0000ff) - text = '#{:06x}'.format(rgb) - else: - rgb = (grb & 0xff0000) >> 8 | (grb & 0x00ff00) << 8 | (grb & 0xff0000ff) - text = '#{:08x}'.format(rgb) ss_packet, es_packet = self.bits[0][1], self.bits[-1][2] - self.putg(ss_packet, es_packet, ANN_RGB, [text]) + r, g, b, w = 0, 0, 0, None + comps = [] + for i, c in enumerate(self.wireformat): + first_idx, after_idx = 8 * i, 8 * i + 8 + comp_bits = self.bits[first_idx:after_idx] + comp_ss, comp_es = comp_bits[0][1], comp_bits[-1][2] + comp_value = bitpack_msb(comp_bits, 0) + comp_item = (comp_ss, comp_es, comp_value) + comps.append(comp_item) + if c.lower() == 'r': + r = comp_value + elif c.lower() == 'g': + g = comp_value + elif c.lower() == 'b': + b = comp_value + elif c.lower() == 'w': + w = comp_value + wt = '' if w is None else '{:02x}'.format(w) + if self.textformat == 'wire': + rgb_text = ['{:02x}'.format(c[-1]) for c in comps] + rgb_text = '#' + ''.join(rgb_text) + else: + rgb_text = self.textformat.format(r = r, g = g, b = b, w = w, wt = wt) + if rgb_text: + self.putg(ss_packet, es_packet, ANN_RGB, [rgb_text]) self.bits.clear() def handle_bit(self, ss, es, value, ann_late = False): @@ -97,7 +120,34 @@ class Decoder(srd.Decoder): def decode(self): if not self.samplerate: raise SamplerateError('Cannot decode without samplerate.') - self.need_bits = len(self.options['type']) * 8 + + # Preprocess options here, to simplify logic which executes + # much later in loops while settings have the same values. + wireorder = self.options['wireorder'].lower() + self.wireformat = [c for c in wireorder if c in 'rgbw'] + self.need_bits = len(self.wireformat) * 8 + textorder = self.options['textorder'].lower() + if textorder == 'wire': + self.textformat = 'wire' + elif textorder == 'rgb[w]': + self.textformat = '#{r:02x}{g:02x}{b:02x}{wt:s}' + else: + self.textformat = { + # "Obvious" permutations of R/G/B. + 'bgr': '#{b:02x}{g:02x}{r:02x}', + 'brg': '#{b:02x}{r:02x}{g:02x}', + 'gbr': '#{g:02x}{b:02x}{r:02x}', + 'grb': '#{g:02x}{r:02x}{b:02x}', + 'rbg': '#{r:02x}{b:02x}{g:02x}', + 'rgb': '#{r:02x}{g:02x}{b:02x}', + # RGB plus White. Only one of them useful? + 'rgbw': '#{r:02x}{g:02x}{b:02x}{w:02x}', + # Weird RGBW permutation for compatibility to test case. + # Neither used RGBW nor the 'wire' order. Obsolete now? + 'rgwb': '#{r:02x}{g:02x}{w:02x}{b:02x}', + }.get(textorder, None) + if self.textformat is None: + raise DecoderError('Unsupported text output format.') # Either check for edges which communicate bit values, or for # long periods of idle level which represent a reset pulse. -- 2.30.2 From 6300d97ea67e10ecb645e43f80d445543e05ce00 Mon Sep 17 00:00:00 2001 From: Gerhard Sittig Date: Sat, 29 Jul 2023 17:09:40 +0200 Subject: [PATCH 07/16] rgb_led_ws281x: add developer comments, link to chip datasheets Add developer comments at the top of the decoder source. Reference datasheets, discuss vendor's numbers and what's used in the field. Discuss variants (sets of involved colour components, alternative timings, reset detection). --- decoders/rgb_led_ws281x/pd.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/decoders/rgb_led_ws281x/pd.py b/decoders/rgb_led_ws281x/pd.py index 7c8ead4..3e66dac 100644 --- a/decoders/rgb_led_ws281x/pd.py +++ b/decoders/rgb_led_ws281x/pd.py @@ -17,6 +17,34 @@ ## along with this program; if not, see . ## +# Implementor's notes on the wire format: +# - World Semi vendor, (Adafruit copy of the) datasheet +# https://cdn-shop.adafruit.com/datasheets/WS2812.pdf +# - reset pulse is 50us (or more) of low pin level +# - 24bits per WS281x item, 3x 8bits, MSB first, GRB sequence, +# cascaded WS281x items, all "excess bits" are passed through +# - bit time starts with high period, continues with low period, +# high to low periods' ratio determines bit value, datasheet +# mentions 0.35us/0.8us for value 0, 0.7us/0.6us for value 1 +# (huge 150ns tolerances, un-even 0/1 value length, hmm) +# - experience suggests the timing "is variable", rough estimation +# often is good enough, microcontroller firmware got away with +# four quanta per bit time, or even with three quanta (30%/60%), +# Adafruit learn article suggests 1.2us total and 0.4/0.8 or +# 0.8/0.4 high/low parts, four quanta are easier to handle when +# the bit stream is sent via SPI to avoid MCU bit banging and its +# inaccurate timing (when interrupts are used in the firmware) +# - RGBW datasheet (Adafruit copy) for SK6812 +# https://cdn-shop.adafruit.com/product-files/2757/p2757_SK6812RGBW_REV01.pdf +# also 1.2us total, shared across 0.3/0.9 for 0, 0.6/0.6 for 1, +# 80us reset pulse, R8/G8/B8/W8 format per 32bits +# - WS2815, RGB LED, uses GRB wire format, 280us RESET pulse width +# - more vendors and models available and in popular use, +# suggests "one third" or "two thirds" ratio would be most robust, +# sample "a little before" the bit half? reset pulse width may need +# to become an option? matrices and/or fast refresh environments +# may want to experiment with back to back pixel streams + import sigrokdecode as srd from common.srdhelper import bitpack_msb -- 2.30.2 From 3378f524a1b720cdcedb10089ad4f9bacc33cb4f Mon Sep 17 00:00:00 2001 From: Gerhard Sittig Date: Sat, 29 Jul 2023 17:23:02 +0200 Subject: [PATCH 08/16] rgb_led_ws281x: emit annotations for individual colour components The previous implementation presented bits and per-LED RGB-values (in a from similar to HTML colours). This commit introduces annotations for individual colour components (R/G/B/W) between these two levels of abstraction. --- decoders/rgb_led_ws281x/pd.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/decoders/rgb_led_ws281x/pd.py b/decoders/rgb_led_ws281x/pd.py index 3e66dac..eda762a 100644 --- a/decoders/rgb_led_ws281x/pd.py +++ b/decoders/rgb_led_ws281x/pd.py @@ -54,7 +54,10 @@ class SamplerateError(Exception): class DecoderError(Exception): pass -( ANN_BIT, ANN_RESET, ANN_RGB, ) = range(3) +( + ANN_BIT, ANN_RESET, ANN_RGB, + ANN_COMP_R, ANN_COMP_G, ANN_COMP_B, ANN_COMP_W, +) = range(7) class Decoder(srd.Decoder): api_version = 3 @@ -73,9 +76,14 @@ class Decoder(srd.Decoder): ('bit', 'Bit'), ('reset', 'RESET'), ('rgb', 'RGB'), + ('r', 'R'), + ('g', 'G'), + ('b', 'B'), + ('w', 'W'), ) annotation_rows = ( ('bits', 'Bits', (ANN_BIT, ANN_RESET,)), + ('rgb-comps', 'RGB components', (ANN_COMP_R, ANN_COMP_G, ANN_COMP_B, ANN_COMP_W,)), ('rgb-vals', 'RGB values', (ANN_RGB,)), ) options = ( @@ -114,7 +122,12 @@ class Decoder(srd.Decoder): comp_bits = self.bits[first_idx:after_idx] comp_ss, comp_es = comp_bits[0][1], comp_bits[-1][2] comp_value = bitpack_msb(comp_bits, 0) - comp_item = (comp_ss, comp_es, comp_value) + comp_text = '{:02x}'.format(comp_value) + comp_ann = { + 'r': ANN_COMP_R, 'g': ANN_COMP_G, + 'b': ANN_COMP_B, 'w': ANN_COMP_W, + }.get(c.lower(), None) + comp_item = (comp_ss, comp_es, comp_ann, comp_value, comp_text) comps.append(comp_item) if c.lower() == 'r': r = comp_value @@ -126,10 +139,11 @@ class Decoder(srd.Decoder): w = comp_value wt = '' if w is None else '{:02x}'.format(w) if self.textformat == 'wire': - rgb_text = ['{:02x}'.format(c[-1]) for c in comps] - rgb_text = '#' + ''.join(rgb_text) + rgb_text = '#' + ''.join([c[-1] for c in comps]) else: rgb_text = self.textformat.format(r = r, g = g, b = b, w = w, wt = wt) + for ss_comp, es_comp, cls_comp, value_comp, text_comp in comps: + self.putg(ss_comp, es_comp, cls_comp, [text_comp]) if rgb_text: self.putg(ss_packet, es_packet, ANN_RGB, [rgb_text]) self.bits.clear() -- 2.30.2 From e6962b3fe8260382bb9932a1cfdd7ee7090ce267 Mon Sep 17 00:00:00 2001 From: Gerhard Sittig Date: Sat, 29 Jul 2023 17:26:48 +0200 Subject: [PATCH 09/16] rgb_led_ws281x: default to RGB[W] annotation text order The earlier default was "RGB" (exactly three components), and users had to manually pick "RGBW" when white was involved. Change the default to the automatic "RGB[W]" instead which transparently presents white when applicable, and doesn't fail when white is not involved. --- decoders/rgb_led_ws281x/pd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/decoders/rgb_led_ws281x/pd.py b/decoders/rgb_led_ws281x/pd.py index eda762a..099a2ce 100644 --- a/decoders/rgb_led_ws281x/pd.py +++ b/decoders/rgb_led_ws281x/pd.py @@ -91,7 +91,7 @@ class Decoder(srd.Decoder): 'default': 'GRB', 'values': ('BGR', 'BRG', 'GBR', 'GRB', 'RBG', 'RGB', 'RWBG', 'RGBW')}, {'id': 'textorder', 'desc': 'components output order (text)', - 'default': 'RGB', 'values': ('wire', 'RGB[W]', 'RGB', 'RGBW', 'RGWB')}, + 'default': 'RGB[W]', 'values': ('wire', 'RGB[W]', 'RGB', 'RGBW', 'RGWB')}, ) def __init__(self): -- 2.30.2 From bb5af7af06b8fb1b37a88a03a94db3e35a7d15de Mon Sep 17 00:00:00 2001 From: Gerhard Sittig Date: Sun, 30 Jul 2023 17:03:39 +0200 Subject: [PATCH 10/16] rgb_led_spi: adjust decoder implementation's Python style Eliminate unneeded instance variables. Start from None when values are unknown. Only access data after checking its availability. Invalidate previously accumulated details after processing. Prefer .format() over string modulo. Use list .clear() where applicable. Use symbolic names for annotation classes. --- decoders/rgb_led_spi/pd.py | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/decoders/rgb_led_spi/pd.py b/decoders/rgb_led_spi/pd.py index 82877b3..899a64a 100644 --- a/decoders/rgb_led_spi/pd.py +++ b/decoders/rgb_led_spi/pd.py @@ -19,6 +19,8 @@ import sigrokdecode as srd +( ANN_RGB, ) = range(1) + class Decoder(srd.Decoder): api_version = 3 id = 'rgb_led_spi' @@ -37,34 +39,34 @@ class Decoder(srd.Decoder): self.reset() def reset(self): - self.ss_cmd, self.es_cmd = 0, 0 + self.ss_cmd = None self.mosi_bytes = [] def start(self): self.out_ann = self.register(srd.OUTPUT_ANN) - def putx(self, data): - self.put(self.ss_cmd, self.es_cmd, self.out_ann, data) + def putg(self, ss, es, cls, text): + self.put(ss, es, self.out_ann, [cls, text]) def decode(self, ss, es, data): - ptype, mosi, miso = data + ptype = data[0] - # Only care about data packets. + # Grab the payload of three DATA packets. These hold the + # RGB values (in this very order). if ptype != 'DATA': return - self.ss, self.es = ss, es - - if len(self.mosi_bytes) == 0: + _, mosi, _ = data + if not self.mosi_bytes: self.ss_cmd = ss self.mosi_bytes.append(mosi) - - # RGB value == 3 bytes - if len(self.mosi_bytes) != 3: + if len(self.mosi_bytes) < 3: return - red, green, blue = self.mosi_bytes + # Emit annotations. Invalidate accumulated details as soon as + # they were processed, to prepare the next iteration. + ss_cmd, es_cmd = self.ss_cmd, es + self.ss_cmd = None + red, green, blue = self.mosi_bytes[:3] + self.mosi_bytes.clear() rgb_value = int(red) << 16 | int(green) << 8 | int(blue) - - self.es_cmd = es - self.putx([0, ['#%.6x' % rgb_value]]) - self.mosi_bytes = [] + self.putg(ss_cmd, es_cmd, ANN_RGB, ['#{:06x}'.format(rgb_value)]) -- 2.30.2 From df3a4a3bd1763324765f53932d878525a4a20102 Mon Sep 17 00:00:00 2001 From: Gerhard Sittig Date: Sun, 30 Jul 2023 18:01:29 +0200 Subject: [PATCH 11/16] sae_j1850_vpw: rewrite decoder to improve usability and maintenance The previous implementation of the SAE J1850 (VPW) decoder dumped bytes on one row and added another row with textual labels what those upper row bytes would mean. That allowed manual inspection at a rather low level of abstraction, but is not idiomatic and reduces usability of the decoder. Rewrite the decoder to become more idiomatic, and to prepare future inspection of more upper layers. Concentrate all timestamp gathering in .decode(), use PD API v3 for the IFS timeout as well, run handlers for the lowest level symbols. Pass accumulated protocol bytes to a fields handler, flush accumulated details when EOF is seen. Prepare validity checks (checksum verification), and prepare to handle data bytes when all header fields were seen and validity was checked. Emit annotations in proper classes to improve usability, start emitting warnings. Force re-synchronization when input stream conditions are not handled. --- decoders/sae_j1850_vpw/pd.py | 327 ++++++++++++++++++++++++----------- 1 file changed, 229 insertions(+), 98 deletions(-) diff --git a/decoders/sae_j1850_vpw/pd.py b/decoders/sae_j1850_vpw/pd.py index fd2389e..3655f96 100644 --- a/decoders/sae_j1850_vpw/pd.py +++ b/decoders/sae_j1850_vpw/pd.py @@ -2,6 +2,7 @@ ## This file is part of the libsigrokdecode project. ## ## Copyright (C) 2016 Anthony Symons +## Copyright (C) 2023 Gerhard Sittig ## ## 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 @@ -18,16 +19,30 @@ ## import sigrokdecode as srd +from common.srdhelper import bitpack_msb + +# VPW Timings. From the SAE J1850 1995 rev section 23.406 documentation. +# Ideal, minimum and maximum tolerances. +VPW_SOF = 200 +VPW_SOFL = 164 +VPW_SOFH = 245 # 240 by the spec, 245 so a 60us 4x sample will pass +VPW_LONG = 128 +VPW_LONGL = 97 +VPW_LONGH = 170 # 164 by the spec but 170 for low sample rate tolerance. +VPW_SHORT = 64 +VPW_SHORTL = 24 # 35 by the spec, 24 to allow down to 6us as measured in practice for 4x @ 1mhz sampling +VPW_SHORTH = 97 +VPW_IFS = 240 class SamplerateError(Exception): pass -def timeuf(t): - return int (t * 1000.0 * 1000.0) - -class Ann: - ANN_RAW, ANN_SOF, ANN_IFS, ANN_DATA, \ - ANN_PACKET = range(5) +( + ANN_SOF, ANN_BIT, ANN_IFS, ANN_BYTE, + ANN_PRIO, ANN_DEST, ANN_SRC, ANN_MODE, ANN_DATA, ANN_CSUM, + ANN_M1_PID, + ANN_WARN, +) = range(12) class Decoder(srd.Decoder): api_version = 3 @@ -43,123 +58,239 @@ class Decoder(srd.Decoder): {'id': 'data', 'name': 'Data', 'desc': 'Data line'}, ) annotations = ( - ('raw', 'Raw'), ('sof', 'SOF'), + ('bit', 'Bit'), ('ifs', 'EOF/IFS'), + ('byte', 'Byte'), + ('prio', 'Priority'), + ('dest', 'Destination'), + ('src', 'Source'), + ('mode', 'Mode'), ('data', 'Data'), - ('packet', 'Packet'), + ('csum', 'Checksum'), + ('m1_pid', 'Pid'), + ('warn', 'Warning'), ) annotation_rows = ( - ('raws', 'Raws', (Ann.ANN_RAW, Ann.ANN_SOF, Ann.ANN_IFS,)), - ('bytes', 'Bytes', (Ann.ANN_DATA,)), - ('packets', 'Packets', (Ann.ANN_PACKET,)), + ('bits', 'Bits', (ANN_SOF, ANN_BIT, ANN_IFS,)), + ('bytes', 'Bytes', (ANN_BYTE,)), + ('fields', 'Fields', (ANN_PRIO, ANN_DEST, ANN_SRC, ANN_MODE, ANN_DATA, ANN_CSUM,)), + ('values', 'Values', (ANN_M1_PID,)), + ('warns', 'Warnings', (ANN_WARN,)), ) + # TODO Add support for options? Polarity. Glitch length. def __init__(self): self.reset() def reset(self): - self.state = 'IDLE' self.samplerate = None - self.byte = 0 # the byte offset in the packet - self.mode = 0 # for by packet decode - self.data = 0 # the current byte - self.datastart = 0 # sample number this byte started at - self.csa = 0 # track the last byte seperately to retrospectively add the CS marker - self.csb = 0 - self.count = 0 # which bit number we are up to - self.active = 0 # which logic level is considered active - - # vpw timings. ideal, min and max tollerances. - # From SAE J1850 1995 rev section 23.406 - - self.sof = 200 - self.sofl = 164 - self.sofh = 245 # 240 by the spec, 245 so a 60us 4x sample will pass - self.long = 128 - self.longl = 97 - self.longh = 170 # 164 by the spec but 170 for low sample rate tolerance. - self.short = 64 - self.shortl = 24 # 35 by the spec, 24 to allow down to 6us as measured in practice for 4x @ 1mhz sampling - self.shorth = 97 - self.ifs = 240 - self.spd = 1 # set to 4 when a 4x SOF is detected (VPW high speed frame) + self.active = 0 # Signal polarity. Needs to become an option? + self.bits = [] + self.fields = {} - def handle_bit(self, ss, es, b): - self.data |= (b << 7-self.count) # MSB-first - self.put(ss, es, self.out_ann, [Ann.ANN_RAW, ["%d" % b]]) - if self.count == 0: - self.datastart = ss - if self.count == 7: - self.csa = self.datastart # for CS - self.csb = self.samplenum # for CS - self.put(self.datastart, self.samplenum, self.out_ann, [Ann.ANN_DATA, ["%02X" % self.data]]) - # add protocol parsing here - if self.byte == 0: - self.put(self.datastart, self.samplenum, self.out_ann, [Ann.ANN_PACKET, ['Priority','Prio','P']]) - elif self.byte == 1: - self.put(self.datastart, self.samplenum, self.out_ann, [Ann.ANN_PACKET, ['Destination','Dest','D']]) - elif self.byte == 2: - self.put(self.datastart, self.samplenum, self.out_ann, [Ann.ANN_PACKET, ['Source','Src','S']]) - elif self.byte == 3: - self.put(self.datastart, self.samplenum, self.out_ann, [Ann.ANN_PACKET, ['Mode','M']]) - self.mode = self.data - elif self.mode == 1 and self.byte == 4: # mode 1 payload - self.put(self.datastart, self.samplenum, self.out_ann, [Ann.ANN_PACKET, ['Pid','P']]) - - # prepare for next byte - self.count = -1 - self.data = 0 - self.byte = self.byte + 1 # track packet offset - self.count = self.count + 1 + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) def metadata(self, key, value): if key == srd.SRD_CONF_SAMPLERATE: self.samplerate = value - def start(self): - self.out_ann = self.register(srd.OUTPUT_ANN) + def putg(self, ss, es, cls, texts): + self.put(ss, es, self.out_ann, [cls, texts]) + + def invalidate_frame_details(self): + self.bits.clear() + self.fields.clear() + + def handle_databytes(self, fields, data): + # TODO Deep inspection of header fields and data values, including + # checksum verification results. + mode = fields.get('mode', None) + if mode is None: + return + if mode == 1: + # An earlier implementation commented that for mode 1 the + # first data byte would be the PID. But example captures + # have no data bytes in packets for that mode. This position + # is taken by the checksum. Is this correct? + pid = data[0] if data else fields.get('csum', None) + if pid is None: + text = ['PID missing'] + self.putg(ss, es, ANN_WARN, text) + else: + byte_text = '{:02x}'.format(pid) + self.putg(ss, es, ANN_M1_PID, [byte_text]) + + def handle_byte(self, ss, es, b): + # Annotate all raw byte values. Inspect and process the first + # bytes in a frame already. Cease inspection and only accumulate + # all other bytes after the mode. The checksum's position and + # thus the data bytes' span will only be known when EOF or IFS + # were seen. Implementor's note: This method just identifies + # header fields. Processing is left to the .handle_databytes() + # method. Until then validity will have been checked, too (CS). + byte_text = '{:02x}'.format(b) + self.putg(ss, es, ANN_BYTE, [byte_text]) + + if not 'prio' in self.fields: + self.fields.update({'prio': b}) + self.putg(ss, es, ANN_PRIO, [byte_text]) + return + if not 'dest' in self.fields: + self.fields.update({'dest': b}) + self.putg(ss, es, ANN_DEST, [byte_text]) + return + if not 'src' in self.fields: + self.fields.update({'src': b}) + self.putg(ss, es, ANN_SRC, [byte_text]) + return + if not 'mode' in self.fields: + self.fields.update({'mode': b}) + self.putg(ss, es, ANN_MODE, [byte_text]) + return + if not 'data' in self.fields: + self.fields.update({'data': [], 'csum': None}) + self.fields['data'].append((b, ss, es)) + + def handle_sof(self, ss, es, speed): + text = ['{speed:d}x SOF', 'S{speed:d}', 'S'] + text = [f.format(speed = speed) for f in text] + self.putg(ss, es, ANN_SOF, text) + self.invalidate_frame_details() + self.fields.update({'speed': speed}) + + def handle_bit(self, ss, es, b): + self.bits.append((b, ss, es)) + self.putg(ss, es, ANN_BIT, ['{:d}'.format(b)]) + if len(self.bits) < 8: + return + ss, es = self.bits[0][1], self.bits[-1][2] + b = bitpack_msb(self.bits, 0) + self.bits.clear() + self.handle_byte(ss, es, b) + + def handle_eof(self, ss, es, is_ifs = False): + # EOF or IFS were seen. Post process the data bytes sequence. + # Separate the checksum from the data bytes. Emit annotations. + # Pass data bytes and header fields to deeper inspection. + data = self.fields.get('data', {}) + if not data: + text = ['Short data phase', 'Data'] + self.putg(ss, es, ANN_WARN, text) + csum = None + if len(data) >= 1: + csum, ss_csum, es_csum = data.pop() + self.fields.update({'csum': csum}) + # TODO Verify checksum's correctness? + if data: + ss_data, es_data = data[0][1], data[-1][2] + text = ' '.join(['{:02x}'.format(b[0]) for b in data]) + self.putg(ss_data, es_data, ANN_DATA, [text]) + if csum is not None: + text = '{:02x}'.format(csum) + self.putg(ss_csum, es_csum, ANN_CSUM, [text]) + text = ['IFS', 'I'] if is_ifs else ['EOF', 'E'] + self.putg(ss, es, ANN_IFS, text) + self.handle_databytes(self.fields, data); + self.invalidate_frame_details() + + def handle_unknown(self, ss, es): + text = ['Unknown condition', 'Unknown', 'UNK'] + self.putg(ss, es, ANN_WARN, text) + self.invalidate_frame_details() + + def usecs_to_samples(self, us): + us *= 1e-6 + us *= self.samplerate + return int(us) + + def samples_to_usecs(self, n): + n /= self.samplerate + n *= 1000.0 * 1000.0 + return int(n) def decode(self): if not self.samplerate: raise SamplerateError('Cannot decode without samplerate.') - self.wait({0: 'e'}) + # Get the distance between edges. Classify the distance + # to derive symbols and data bit values. Prepare waiting + # for an interframe gap as well, while this part of the + # condition is optional (switches in and out at runtime). + conds_edge = {0: 'e'} + conds_edge_only = [conds_edge] + conds_edge_idle = [conds_edge, {'skip': 0}] + conds = conds_edge_only + self.wait(conds) es = self.samplenum + spd = None while True: ss = es - pin, = self.wait({0: 'e'}) + pin, = self.wait(conds) es = self.samplenum + count = es - ss + t = self.samples_to_usecs(count) + + # Synchronization to the next frame. Wait for SOF. + # Silently keep synchronizing until SOF was seen. + if spd is None: + if not self.matched[0]: + continue + if pin != self.active: + continue + + # Detect the frame's speed from the SOF length. Adjust + # the expected BIT lengths to the SOF derived speed. + # Arrange for the additional supervision of EOF/IFS. + if t in range(VPW_SOFL // 1, VPW_SOFH // 1): + spd = 1 + elif t in range(VPW_SOFL // 4, VPW_SOFH // 4): + spd = 4 + else: + continue + short_lower, short_upper = VPW_SHORTL // spd, VPW_SHORTH // spd + long_lower, long_upper = VPW_LONGL // spd, VPW_LONGH // spd + samples = self.usecs_to_samples(VPW_IFS // spd) + conds_edge_idle[-1]['skip'] = samples + conds = conds_edge_idle + + # Emit the SOF annotation. Start collecting DATA. + self.handle_sof(ss, es, spd) + continue + + # Inside the DATA phase. Get data bits. Handle EOF/IFS. + if len(conds) > 1 and self.matched[1]: + # TODO The current implementation gets here after a + # pre-determined minimum wait time. Does not differ + # between EOF and IFS. An earlier implementation had + # this developer note: EOF=239-280 IFS=281+ + self.handle_eof(ss, es) + # Enter the IDLE phase. Wait for the next SOF. + spd = None + conds = conds_edge_only + continue + if t in range(short_lower, short_upper): + value = 1 if pin == self.active else 0 + self.handle_bit(ss, es, value) + continue + if t in range(long_lower, long_upper): + value = 0 if pin == self.active else 1 + self.handle_bit(ss, es, value) + continue - samples = es - ss - t = timeuf(samples / self.samplerate) - if self.state == 'IDLE': # detect and set speed from the size of sof - if pin == self.active and t in range(self.sofl , self.sofh): - self.put(ss, es, self.out_ann, [Ann.ANN_RAW, ['1X SOF', 'S1', 'S']]) - self.spd = 1 - self.data = 0 - self.count = 0 - self.state = 'DATA' - elif pin == self.active and t in range(int(self.sofl / 4) , int(self.sofh / 4)): - self.put(ss, es, self.out_ann, [Ann.ANN_RAW, ['4X SOF', 'S4', '4']]) - self.spd = 4 - self.data = 0 - self.count = 0 - self.state = 'DATA' - - elif self.state == 'DATA': - if t >= int(self.ifs / self.spd): - self.state = 'IDLE' - self.put(ss, es, self.out_ann, [Ann.ANN_RAW, ["EOF/IFS", "E"]]) # EOF=239-280 IFS=281+ - self.put(self.csa, self.csb, self.out_ann, [Ann.ANN_PACKET, ['Checksum','CS','C']]) # retrospective print of CS - self.byte = 0 # reset packet offset - elif t in range(int(self.shortl / self.spd), int(self.shorth / self.spd)): - if pin == self.active: - self.handle_bit(ss, es, 1) - else: - self.handle_bit(ss, es, 0) - elif t in range(int(self.longl / self.spd), int(self.longh / self.spd)): - if pin == self.active: - self.handle_bit(ss, es, 0) - else: - self.handle_bit(ss, es, 1) + # Implementation detail: An earlier implementation used to + # ignore everything that was not handled above. This would + # be motivated by the noisy environment the protocol is + # typically used in. This more recent implementation accepts + # short glitches, but by design falls back to synchronization + # to the input stream for other unhandled conditions. This + # wants to improve usability of the decoder, by presenting + # potential issues to the user. The threshold (microseconds + # between edges that are not valid symbols that are handled + # above) is an arbitrary choice. + if t <= 2: + continue + self.handle_unknown(ss, es) + spd = None + conds = conds_edge_only -- 2.30.2 From ca0312bfee5761a68e8d9b14020b12cd2e689c84 Mon Sep 17 00:00:00 2001 From: fenugrec Date: Wed, 26 Apr 2023 13:28:40 -0400 Subject: [PATCH 12/16] spiflash: add MX25L8006 device type --- decoders/spiflash/lists.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/decoders/spiflash/lists.py b/decoders/spiflash/lists.py index 80ca27d..e31daf7 100644 --- a/decoders/spiflash/lists.py +++ b/decoders/spiflash/lists.py @@ -60,6 +60,7 @@ device_name = { 0x15: 'FM25Q32', }, 'macronix': { + 0x13: 'MX25L8006', 0x14: 'MX25L1605D', 0x15: 'MX25L3205D', 0x16: 'MX25L6405D', @@ -151,6 +152,17 @@ chips = { 'sector_size': 4 * 1024, 'block_size': 64 * 1024, }, + 'macronix_mx25l8006': { + 'vendor': 'Macronix', + 'model': 'MX25L8006', + 'res_id': 0x13, + 'rems_id': 0xc213, + 'rems2_id': 0xc213, + 'rdid_id': 0xc22013, + 'page_size': 256, + 'sector_size': 4 * 1024, + 'block_size': 64 * 1024, + }, # Winbond 'winbond_w25q80dv': { 'vendor': 'Winbond', -- 2.30.2 From 0c35c5c5845d05e5f624c99d58af992d2f004446 Mon Sep 17 00:00:00 2001 From: Sascha Silbe Date: Mon, 23 Oct 2023 22:21:38 +0200 Subject: [PATCH 13/16] srd: drop deprecated PyEval_InitThreads() on Python 3.9+ `PyEval_InitThreads()` is called implicitly during `Py_InitializeEx()` since Python 3.7. It has been deprecated since 3.9 and dropped in 3.13. [ gsi: touch up comment style ] --- srd.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/srd.c b/srd.c index 35ec5f2..10dfaf6 100644 --- a/srd.c +++ b/srd.c @@ -302,8 +302,14 @@ SRD_API int srd_init(const char *path) g_strfreev(dir_list); } - /* Initialize the Python GIL (this also happens to acquire it). */ +#if PY_VERSION_HEX < 0x03090000 + /* + * Initialize and acquire the Python GIL. In Python 3.7+ this + * will be done implicitly as part of the Py_InitializeEx() + * call above. PyEval_InitThreads() was deprecated in 3.9. + */ PyEval_InitThreads(); +#endif /* Release the GIL (ignore return value, we don't need it here). */ (void)PyEval_SaveThread(); -- 2.30.2 From aff61f55c5544623c2ba0dd971b37cf844e3124f Mon Sep 17 00:00:00 2001 From: bvernoux Date: Tue, 10 Aug 2021 20:01:18 +0200 Subject: [PATCH 14/16] st25r39xx_spi: Fix FIFOR/FIFOW issues with PV's Tabular Output View The issue exists because of FIFO Read/FIFO Write commands not returning the annotations short name FIFOR/FIFOW --- decoders/st25r39xx_spi/pd.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/decoders/st25r39xx_spi/pd.py b/decoders/st25r39xx_spi/pd.py index 1b0df07..a6f55b9 100644 --- a/decoders/st25r39xx_spi/pd.py +++ b/decoders/st25r39xx_spi/pd.py @@ -1,7 +1,7 @@ ## ## This file is part of the libsigrokdecode project. ## -## Copyright (C) 2019-2020 Benjamin Vernoux +## Copyright (C) 2019-2021 Benjamin Vernoux ## ## 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 @@ -16,6 +16,16 @@ ## You should have received a copy of the GNU General Public License ## along with this program; if not, see . ## +## v0.1 - 17 September 2019 B.VERNOUX +### Use ST25R3916 Datasheet DS12484 Rev 1 (January 2019) +## v0.2 - 28 April 2020 B.VERNOUX +### Use ST25R3916 Datasheet DS12484 Rev 2 (December 2019) +## v0.3 - 17 June 2020 B.VERNOUX +### Use ST25R3916 Datasheet DS12484 Rev 3 (04 June 2020) +## v0.4 - 10 Aug 2021 B.VERNOUX +### Fix FIFOR/FIFOW issues with Pulseview (with "Tabular Output View") +### because of FIFO Read/FIFO Write commands, was not returning the +### annotations short name FIFOR/FIFOW import sigrokdecode as srd from collections import namedtuple @@ -127,7 +137,7 @@ class Decoder(srd.Decoder): def format_command(self): '''Returns the label for the current command.''' - if self.cmd in ('Write', 'Read', 'WriteB', 'ReadB', 'WriteT', 'ReadT', 'FIFO Write', 'FIFO Read'): + if self.cmd in ('Write', 'Read', 'WriteB', 'ReadB', 'WriteT', 'ReadT', 'FIFOW', 'FIFOR'): return self.cmd if self.cmd == 'Cmd': reg = dir_cmd.get(self.dat, 'Unknown direct command') @@ -182,7 +192,7 @@ class Decoder(srd.Decoder): # Register Space-B Access 0b11111011 0xFB => 'Space B' # Register Test Access 0b11111100 0xFC => 'TestAccess' if b == 0x80: - return ('FIFO Write', b, 1, 99999) + return ('FIFOW', b, 1, 99999) if b == 0xA0: return ('Write', b, 1, 99999) if b == 0xA8: @@ -192,7 +202,7 @@ class Decoder(srd.Decoder): if b == 0xBF: return ('Read', b, 1, 99999) if b == 0x9F: - return ('FIFO Read', b, 1, 99999) + return ('FIFOR', b, 1, 99999) if (b >= 0x0C and b <= 0xE8) : return ('Cmd', b, 0, 0) if b == 0xFB: @@ -273,9 +283,9 @@ class Decoder(srd.Decoder): self.decode_reg(pos, Ann.BURST_WRITET, self.dat, self.mosi_bytes()) elif self.cmd == 'ReadT': self.decode_reg(pos, Ann.BURST_READT, self.dat, self.miso_bytes()) - elif self.cmd == 'FIFO Write': + elif self.cmd == 'FIFOW': self.decode_reg(pos, Ann.FIFO_WRITE, self.dat, self.mosi_bytes()) - elif self.cmd == 'FIFO Read': + elif self.cmd == 'FIFOR': self.decode_reg(pos, Ann.FIFO_READ, self.dat, self.miso_bytes()) elif self.cmd == 'Cmd': self.decode_reg(pos, Ann.DIRECTCMD, self.dat, self.mosi_bytes()) -- 2.30.2 From c600ee71ea021fd581cf48419e6a051da5b44c06 Mon Sep 17 00:00:00 2001 From: Simon Ruderich Date: Fri, 9 Feb 2024 00:21:23 +0100 Subject: [PATCH 15/16] avr_isp: fix crash if part name is not known Decoding an unknown part causes a KeyError exception. For example for an ATmega328/P: srd: KeyError: Calling avr_isp-1 decode() failed: (149, 15) --- decoders/avr_isp/pd.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/decoders/avr_isp/pd.py b/decoders/avr_isp/pd.py index e3af4d6..9e3c5df 100644 --- a/decoders/avr_isp/pd.py +++ b/decoders/avr_isp/pd.py @@ -123,9 +123,12 @@ class Decoder(srd.Decoder): self.part_number = ret[3] self.putx([Ann.RSB2, ['Part number: 0x%02x' % ret[3]]]) - p = part[(self.part_fam_flash_size, self.part_number)] - data = [Ann.DEV, ['Device: Atmel %s' % p]] - self.put(self.ss_device, self.es_cmd, self.out_ann, data) + # Part name if known + key = (self.part_fam_flash_size, self.part_number) + if key in part: + p = part[key] + data = [Ann.DEV, ['Device: Atmel %s' % p]] + self.put(self.ss_device, self.es_cmd, self.out_ann, data) # Sanity check on reply. if ret[1] != 0x30 or ret[2] != self.xx or ret[0] != self.mm: -- 2.30.2 From 0235970293590f673a253950e6c61017cefa97df Mon Sep 17 00:00:00 2001 From: atoomnetmarc Date: Mon, 4 Mar 2024 20:36:46 +0100 Subject: [PATCH 16/16] avr_isp: Add more parts --- decoders/avr_isp/parts.py | 68 +++++++++++++++++++++++++++++++++++---- 1 file changed, 62 insertions(+), 6 deletions(-) diff --git a/decoders/avr_isp/parts.py b/decoders/avr_isp/parts.py index 0767789..fee4d9b 100644 --- a/decoders/avr_isp/parts.py +++ b/decoders/avr_isp/parts.py @@ -22,20 +22,76 @@ # Vendor code vendor_code = { - 0x1e: 'Atmel', + 0x1E: 'Atmel', 0x00: 'Device locked', } # (Part family + flash size, part number) part = { (0x90, 0x01): 'AT90S1200', + (0x90, 0x05): 'ATtiny12', + (0x90, 0x06): 'ATtiny15', + (0x90, 0x07): 'ATtiny13', (0x91, 0x01): 'AT90S2313', + (0x91, 0x02): 'AT90S2323', + (0x91, 0x03): 'AT90S2343', + (0x91, 0x05): 'AT90S2333', + (0x91, 0x06): 'ATtiny22', + (0x91, 0x07): 'ATtiny28', + (0x91, 0x08): 'ATtiny25', + (0x91, 0x09): 'ATtiny26', + (0x91, 0x0A): 'ATtiny2313', + (0x91, 0x0B): 'ATtiny24', + (0x91, 0x0C): 'ATtiny261', (0x92, 0x01): 'AT90S4414', - (0x92, 0x05): 'ATmega48', # 4kB flash + (0x92, 0x03): 'AT90S4433', + (0x92, 0x05): 'ATmega48(A)', + (0x92, 0x06): 'ATtiny45', + (0x92, 0x08): 'ATtiny461', + (0x92, 0x09): 'ATtiny48', + (0x92, 0x0A): 'ATmega48PA', + (0x92, 0x0D): 'ATtiny4313', + (0x92, 0x10): 'ATmega48PB', (0x93, 0x01): 'AT90S8515', - (0x93, 0x0a): 'ATmega88', # 8kB flash - (0x94, 0x06): 'ATmega168', # 16kB flash - (0xff, 0xff): 'Device code erased, or target missing', + (0x93, 0x03): 'AT90S8535', + (0x93, 0x07): 'ATmega8', + (0x93, 0x0A): 'ATmega88(A)', + (0x93, 0x0B): 'ATtiny85', + (0x93, 0x0D): 'ATtiny861', + (0x93, 0x0F): 'ATmega88PA', + (0x93, 0x11): 'ATtiny88', + (0x93, 0x16): 'ATmega88PB', + (0x93, 0x89): 'ATmega8U2', + (0x94, 0x01): 'ATmega161', + (0x94, 0x02): 'ATmega163', + (0x94, 0x03): 'ATmega16', + (0x94, 0x04): 'ATmega162', + (0x94, 0x06): 'ATmega168(A)', + (0x94, 0x0A): 'ATmega164PA', + (0x94, 0x0B): 'ATmega168PA', + (0x94, 0x0F): 'ATmega164A', + (0x94, 0x12): 'ATtiny1634', + (0x94, 0x15): 'ATmega168PB', + (0x94, 0x88): 'ATmega16U4', + (0x94, 0x89): 'ATmega16U2', + (0x95, 0x01): 'ATmega32', + (0x95, 0x01): 'ATmega323', + (0x95, 0x0F): 'ATmega328P', + (0x95, 0x11): 'ATmega324PA', + (0x95, 0x14): 'ATmega328', + (0x95, 0x15): 'ATmega324A', + (0x95, 0x87): 'ATmega32U4', + (0x95, 0x8A): 'ATmega32U2', + (0x96, 0x08): 'ATmega640', + (0x96, 0x09): 'ATmega644(A)', + (0x96, 0x0A): 'ATmega644PA', + (0x97, 0x01): 'ATmega103', + (0x97, 0x03): 'ATmega1280', + (0x97, 0x04): 'ATmega1281', + (0x97, 0x05): 'ATmega1284P', + (0x97, 0x06): 'ATmega1284', + (0x98, 0x01): 'ATmega2560', + (0x98, 0x02): 'ATmega2561', + (0xFF, 0xFF): 'Device code erased, or target missing', (0x01, 0x02): 'Device locked', - # TODO: Lots more entries. } -- 2.30.2