Difference between revisions of "Protocol decoder HOWTO"

From sigrok
Jump to navigation Jump to search
(Updates.)
Line 14: Line 14:


'''Note''': Do not start new protocol decoders by copying code from here. Instead, it's recommended to select an already existing decoder in the source code which is similar to the one you plan to write, and copy that as a starting point.
'''Note''': Do not start new protocol decoders by copying code from here. Instead, it's recommended to select an already existing decoder in the source code which is similar to the one you plan to write, and copy that as a starting point.
=== Hooking up to the build system ===
In order to hookup the new protocol decoder you wrote ('''i2c''' in this example), just add the respective entries for it in the following files.
* Add an '''i2c''' line to the '''SUBDIRS''' variable in '''decoders/Makefile.am'''.
* Add an '''decoders/i2c/Makefile''' line to the '''AC_CONFIG_FILES''' in '''configure.ac'''.
=== Makefile.am ===
<small>
<pre>
pkgdatadir = $(DECODERS_DIR)/i2c
dist_pkgdata_DATA = __init__.py pd.py
CLEANFILES = *.pyc
</pre>
</small>
This file is used to tell the build system which files belong to the decoder.


=== __init__.py ===
=== __init__.py ===
Line 39: Line 20:
<pre>
<pre>
  '''
  '''
  I2C protocol decoder.
  I²C (Inter-Integrated Circuit) is a bidirectional, multi-master
The Inter-Integrated Circuit (I2C) bus is a bidirectional, multi-master
  bus using two signals (SCL = serial clock line, SDA = serial data line).
  bus using two signals (SCL = serial clock line, SDA = serial data line).
 
  <Insert notes and hints for the user here>
  <Insert notes and hints for the user here>
  '''
  '''
Line 51: Line 30:
</small>
</small>


This is a standard Python file, required in every Python module. It contains a module-level docstring, which is accessible by frontends via the libsigrokdecode API. It should contain a (very) short description of the protocol (in this case I2C), and some notes and hints for the user of this protocol decoder (which can be shown in GUIs when the user selects/browses different PDs).
This is a standard Python file, required in every Python module. It contains a module-level docstring, which is accessible by frontends via the libsigrokdecode API. It should contain a (very) short description of what the protocol (in this case I²C) is about, and some notes and hints for the user of this protocol decoder (which can be shown in GUIs when the user selects/browses different PDs).


This docstring should '''not''' contain the full, extensive protocol description. Instead, the per-PD wiki page should be used for protocol description, photos of devices or photos of example acquisition setups, and so on. Each decoder has one unique wiki page at the URL '''<nowiki>http://sigrok.org/wiki/Protocol_decoder:<pd></nowiki>''', where '''<pd>''' is the Python module name of the decoder ('''i2c''' in this case). Some examples for such per-PD wiki pages: [[Protocol_decoder:Uart|UART]], [[Protocol_decoder:Pan1321|PAN1321]], [[Protocol_decoder:Mx25lxx05d|MX25LXX05D]], [[Protocol_decoder:Dcf77|DCF77]].
This docstring should '''not''' contain the full, extensive protocol description. Instead, the per-PD wiki page should be used for protocol description, photos of devices or photos of example acquisition setups, and so on. Each decoder has one unique wiki page at the URL '''<nowiki>http://sigrok.org/wiki/Protocol_decoder:<pd></nowiki>''', where '''<pd>''' is the Python module name of the decoder ('''i2c''' in this case). Some examples for such per-PD wiki pages: [[Protocol_decoder:Uart|UART]], [[Protocol_decoder:Pan1321|PAN1321]], [[Protocol_decoder:Mx25lxx05d|MX25Lxx05D]], [[Protocol_decoder:Dcf77|DCF77]].


The "'''from .pd import *'''" line will make sure the code from '''pd.py''' gets properly imported when this module is used.
The "'''from .pd import *'''" line will make sure the code from '''pd.py''' gets properly imported when this module is used.
Line 66: Line 45:
     api_version = 1
     api_version = 1
     id = 'i2c'
     id = 'i2c'
     name = 'I2C'
     name = 'I²C'
     longname = 'Inter-Integrated Circuit'
     longname = 'Inter-Integrated Circuit'
     desc = 'Two-wire, multi-master, serial bus.'
     desc = 'Two-wire, multi-master, serial bus.'
Line 81: Line 60:
     }
     }
     annotations = [
     annotations = [
         ['Start', 'Start condition'],
         ['start', 'Start condition'],
         ['Repeat start', 'Repeat start condition'],
         ['repeat-start', 'Repeat start condition'],
         ['Stop', 'Stop condition'],
         ['stop', 'Stop condition'],
         ['ACK', 'ACK'],
         ['ack', 'ACK'],
         ['NACK', 'NACK'],
         ['nack', 'NACK'],
         ['Address read', 'Address read'],
        ['bit', 'Data/address bit'],
         ['Address write', 'Address write'],
         ['address-read', 'Address read'],
         ['Data read', 'Data read'],
         ['address-write', 'Address write'],
         ['Data write', 'Data write'],
         ['data-read', 'Data read'],
         ['Warnings', 'Human-readable warnings'],
         ['data-write', 'Data write'],
         ['warnings', 'Human-readable warnings'],
     ]
     ]
    annotation_rows = (
        ('bits', 'Bits', (5,)),
        ('addr-data', 'Address/Data', (0, 1, 2, 3, 4, 6, 7, 8, 9)),
        ('warnings', 'Warnings', (10,)),
    )
   
   
     def __init__(self, **kwargs):
     def __init__(self, **kwargs):
Line 97: Line 82:
         # And various other variable initializations...
         # And various other variable initializations...
   
   
     def start(self, metadata):
     def metadata(self, key, value):
         self.out_proto = self.add(srd.OUTPUT_PROTO, 'i2c')
         if key == srd.SRD_CONF_SAMPLERATE:
        self.out_ann = self.add(srd.OUTPUT_ANN, 'i2c')
            self.samplerate = value
   
   
     def report(self):
     def start(self):
         pass # Unused so far.
         self.out_ann = self.register(srd.OUTPUT_ANN)
   
   
     def decode(self, ss, es, data):
     def decode(self, ss, es, data):

Revision as of 19:06, 19 February 2014

This page serves as a quick-start guide for people who want to write their own libsigrokdecode protocol decoders (PDs).

It is not intended to replace the Protocol decoder API page, but rather to give a short overview/tutorial and some tips.

Introduction

Protocol decoders are written entirely in Python (>= 3.0).

Files

Every protocol decoder is a Python module and has its own subdirectory in libsigrokdecode's decoders directory.

This is a minimalistic example of how a protocol decoder looks like, in this case the i2c decoder (license header, comments, and some other parts omitted).

Note: Do not start new protocol decoders by copying code from here. Instead, it's recommended to select an already existing decoder in the source code which is similar to the one you plan to write, and copy that as a starting point.

__init__.py

 '''
 I²C (Inter-Integrated Circuit) is a bidirectional, multi-master
 bus using two signals (SCL = serial clock line, SDA = serial data line).

 <Insert notes and hints for the user here>
 '''
 
 from .pd import *

This is a standard Python file, required in every Python module. It contains a module-level docstring, which is accessible by frontends via the libsigrokdecode API. It should contain a (very) short description of what the protocol (in this case I²C) is about, and some notes and hints for the user of this protocol decoder (which can be shown in GUIs when the user selects/browses different PDs).

This docstring should not contain the full, extensive protocol description. Instead, the per-PD wiki page should be used for protocol description, photos of devices or photos of example acquisition setups, and so on. Each decoder has one unique wiki page at the URL http://sigrok.org/wiki/Protocol_decoder:<pd>, where <pd> is the Python module name of the decoder (i2c in this case). Some examples for such per-PD wiki pages: UART, PAN1321, MX25Lxx05D, DCF77.

The "from .pd import *" line will make sure the code from pd.py gets properly imported when this module is used.

pd.py

 import sigrokdecode as srd
 
 class Decoder(srd.Decoder):
     api_version = 1
     id = 'i2c'
     name = 'I²C'
     longname = 'Inter-Integrated Circuit'
     desc = 'Two-wire, multi-master, serial bus.'
     license = 'gplv2+'
     inputs = ['logic']
     outputs = ['i2c']
     probes = [
         {'id': 'scl', 'name': 'SCL', 'desc': 'Serial clock line'},
         {'id': 'sda', 'name': 'SDA', 'desc': 'Serial data line'},
     ]
     optional_probes = []
     options = {
         'address_format': ['Displayed slave address format', 'shifted'],
     }
     annotations = [
         ['start', 'Start condition'],
         ['repeat-start', 'Repeat start condition'],
         ['stop', 'Stop condition'],
         ['ack', 'ACK'],
         ['nack', 'NACK'],
         ['bit', 'Data/address bit'],
         ['address-read', 'Address read'],
         ['address-write', 'Address write'],
         ['data-read', 'Data read'],
         ['data-write', 'Data write'],
         ['warnings', 'Human-readable warnings'],
     ]
     annotation_rows = (
         ('bits', 'Bits', (5,)),
         ('addr-data', 'Address/Data', (0, 1, 2, 3, 4, 6, 7, 8, 9)),
         ('warnings', 'Warnings', (10,)),
     )
 
     def __init__(self, **kwargs):
         self.state = 'FIND START'
         # And various other variable initializations...
 
     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 decode(self, ss, es, data):
         for self.samplenum, (scl, sda) in data:
             # ...

The recommended name for the actual decoder file is pd.py.

This file contains some meta information about the decoder, and the actual code itself, mostly in the decode() method.

Random notes, tips and tricks

  • You should only use raise in a protocol decoder to raise exceptions in cases which are a clear bug in the protocol decoder.
  • A simple and fast way to calculate a parity (i.e., count the number of 1 bits) over a number (0x55 in this example) is: ones = bin(0x55).count('1').
  • A simple function to convert a BCD number (max. 8 bits) to an integer is: def bcd2int(b): return (b & 0x0f) + ((b >> 4) * 10).
  • A nice way to construct method names according to (e.g.) protocol commands is: fn = getattr(self, 'handle_cmd_0x%02x' % cmd); fn(arg1, arg2, ...).