Difference between revisions of "Protocol decoder API"

From sigrok
Jump to navigation Jump to search
Line 18: Line 18:
== API ==
== API ==


A PD must contain at least two definitions
A PD must contain at least a populated dictionary '''register''' which defines the name, capabilities etc of the PD. The following keys can be used:


* a populated dictionary '''register''' which defines the name, capabilities etc of the PD.
{| border="0" style="font-size: smaller"
This is an example:
|- bgcolor="#6699ff"
!Key
!Type
!Description
 
|- bgcolor="#6699ff"
| colspan="3" | '''Protocol Decoder information'''
 
|- bgcolor="#eeeeee"
| id
| required
| A short unique identifier for this protocol. It should be all-lowercase, and only contains a-z, 0-9 and underscores. The sigrok CLI uses this to specify PDs.
 
|- bgcolor="#dddddd"
| description
| required
| A freeform one-line description of the decoder. Used when listing available PDs.
 
|- bgcolor="#eeeeee"
| in
| required
| The type of input this decoder needs. If the decoder takes input from a logic analyzer driver, this should be set to 'logic', which maps to DF_LOGIC the datafeed type. If it takes input from another PD, it should be set to the value of the 'out' key of that PD. It should conform to the same rules as the '''id''' key.
 
|- bgcolor="#dddddd"
| out
| optional
| If this decoder can feed decoded data back into the datafeed stream, its output will be identified with this key's value. It should conform to the same rules as the `id` key. If not specified, this decoder cannot feed data back into the stream. This typically means it only does analysis on the whole stream, producing a report at the end of acquisition.
 
|- bgcolor="#eeeeee"
| author
| optional
| Name and optionally email address of the author of this PD.
 
|- bgcolor="#dddddd"
| version
| optional
| Version of this PD.
 
|- bgcolor="#6699ff"
| colspan="3" | '''Available functions'''
 
|- bgcolor="#eeeeee"
| start
| required
| Reference to a python function that will be called to start the decoder. The PD controller will call this function when the first packet with real data in it comes in on the stream. See below for arguments to this function.
 
|- bgcolor="#dddddd"
| report
| optional
| If given, this is a reference to a function which returns a report in freeform text, describing some analysis done on the stream. For example, PDs for counting logic level transitioons, bit rate analysis and so on would use this to report back. This function is typically only called at the end of the stream, so the whole stream is analyzed. However, the function may be called at any time -- possibly giving an analysis of data received up until that point.
 
|- bgcolor="#6699ff"
| colspan="3" | '''Protocol Decoder configuration'''
 
|- bgcolor="#eeeeee"
| probes
| optional
| For the 'logic' input type this is a required key. it lists the probes (pins) the logic analyzer should feed to this PD, in order for it to make sense of the data. For example, an SPI decoder has to know which probe has the clock, which has the chip select, and so on. This key contains a list of probe entries, where each entry can be either a string with the probe name (`"SCLK"`) or a list with the probe name and description, such as `["SCLK", "Clock"]`.
 
|- bgcolor="#dddddd"
| options
| optional
| A dictionary with options for this decoder. The keys should follow the same rules as for the `id` key above, and each value is a list consisting of a short freeform description of the option, and the default value for that option. For example, an SPI decoder might have an entry with key `cpol`, and value `'Clock polarity', 0]`.
 
<!--
|- bgcolor="#eeeeee"
|
|
|
 
|- bgcolor="#dddddd"
|
|
|
-->
 
|}
 
 
 
Here's an example for an SPI decoder:


  register = {
  register = {
   'id': 'spi',
   'id': 'spi',
   'description': 'SPI',
   'description': 'SPI',
  'author': 'Sigrok project',
   'in': 'logic',
   'in': 'logic',
   'out': 'spi',
   'out': 'spi',
   'probes': [
   'probes': [
     ['sclk', 'sck', 'clk'],
     ['sclk', 'Clock, also known as sck or clk'],
     ['mosi', 'simo', 'sdi", 'di', 'si'],
     'mosi',
     ['miso', 'somi', 'sdo", 'do', 'so'],
     'miso',
     ['ncs', 'cs', 'csb', 'nss', 'ste'],
     ['ncs', 'Chip Select, also known as cs'],
   ],
   ],
   'options': {
   'options': {
Line 39: Line 120:
     'wordsize': ['Word size (in bits)', 8],
     'wordsize': ['Word size (in bits)', 8],
   },
   },
  'start': start,
  }
  }


* a function called '''run()''' which is called by libsigrokdecode. It is defined like this:
The '''start''' function should look like this:


  def run(num_probes, samplerate=None)
  def spi_start(probes, samplerate)


This function is called by libsigrokdecode at the start of a session which has this PD in its pipeline. It should implement a loop calling get_sample(), and only return when an EOF is detected in that loop.
This function is called by libsigrokdecode at the start of a session which has this PD in its pipeline. It should implement a loop calling get_sample(), and only return when an EOF is detected in that loop.


== Protocol decoders ==
The probes argument is a list containing probe names, in the order in which they will be returned from get_sample().
 
The samplerate argument is specified in Hz.


See [[Protocol decoders]] for a list of planned or already supported protocol decoders in sigrok.


== Ideas ==
== Ideas ==


* Plugin system for protocol decoding:
* Sending analysis results back from the plugin, e.g. timestamps or sample IDs with structured results:
** Should support SPI, I2C, RS232/UART and many many more protocols, see above.
** Protocol overhead e.g. start bits
** Should be easy to add support for additional/custom protocols, e.g. AT93C46:
** Commands e.g. opcodes
*** CS (chip select)
** Command parameters e.g. address following a READ command
*** SK (clock)
** Extracted data
*** DI (data from chip to outside)
** String to display over the data ("opcode READ")
*** DO (data from outside into chip)
** Data (e.g. 10) + length in bits (e.g. 2)
*** Has start bit, opcodes, data following transmission
** Results can overlap, as in e.g. "READ address 0x08" and "opcode 10"
** A protocol as simple as this should be doable to implement without code, only the description of the meaning of the various pins.
* PD plugins '''must''' be GUI-independent: they must always work on any [[GUI]] present in sigrok. The plugin interface must therefore provide hooks for:
** Providing data from core to plugin - stream or dump.
** Sending analysis results back from the plugin, e.g. timestamps or sample IDs with structured results:
*** Type:
**** Protocol overhead e.g. start bits
**** Commands e.g. opcodes
**** Command parameters e.g. address following a READ command
**** Extracted data
**** String to display over the data ("opcode READ")
**** Data (e.g. 10) + length in bits (e.g. 2)
**** Results can overlap, as in e.g. "READ address 0x08" and "opcode 10"
* This also opens up the possibility of producing e.g. a protocol analysis report from the main code.
* All PD plugins are written in Python.
** We embed a scripting language (Python) for very simple transforms and/or additional display; e.g. 4-bit interface to HD44780 LCD, take two nibbles in sequence, based on clock line, and assemble them into a whole byte. Scripted plugin could do this, then pass the data back to the UI for display.

Revision as of 17:05, 9 January 2011

This page describes how Protocol Decoders (PDs) work in sigrok.

Architecture

The frontend gets input from the user on which PDs to use in an acquisition session. It then configures these into the session with session_pd_add(). As the first PD is added, the session sets up an additional datafeed callback to itself, which it uses as input to the first PD in the stack. The output of that is sent to the frontend, along with its original datafeed, as well as fed into the next PD in the stack.

The frontend thus gets the raw datafeed as well as a feed from every PD in the stack. Which of these different feeds is actually displayed to the user is a matter of configuration or selection by the user; it should be possible, for example, to have sigrok-cli print only the top of the PD stack's output on stdout.

  • All PDs are written in Python. Only source code will be used (i.e. no .pyc or .pyo files).
  • Every PD registers its name, description, capabilities, etc by populating a hash (dictionary) in its own main namespace called "register".
  • PDs will be stacked together, so the user can construct a decoding pipeline. The control of communication to/from PDs is done by the PD controller code in libsigrokdecode.
  • The data feed into the PDs will be streamed, so they will run in real time as the data comes in from the hardware.
  • In order to keep PDs simple, they don't have to deal with the intricacies of the datafeed packets. Instead, the PD controller will hide the details from the python code:
    • when receiving a DF_HEADER packet going to a PD, the controller intercepts the packet and instead generates a DF_HEADER "coming from" that PD across the session bus, with that PD's output characteristics (which it derived from the PD's register).
    • data packets get translated into a bytestream, which the PDs access through an API: the function get_sample() is a blocking call into the controller, which only returns when a datafeed packet has arrived, and its payload queued up for the PD.
    • DF_END packets are translated into an EOF on the get_sample() call.

API

A PD must contain at least a populated dictionary register which defines the name, capabilities etc of the PD. The following keys can be used:

Key Type Description
Protocol Decoder information
id required A short unique identifier for this protocol. It should be all-lowercase, and only contains a-z, 0-9 and underscores. The sigrok CLI uses this to specify PDs.
description required A freeform one-line description of the decoder. Used when listing available PDs.
in required The type of input this decoder needs. If the decoder takes input from a logic analyzer driver, this should be set to 'logic', which maps to DF_LOGIC the datafeed type. If it takes input from another PD, it should be set to the value of the 'out' key of that PD. It should conform to the same rules as the id key.
out optional If this decoder can feed decoded data back into the datafeed stream, its output will be identified with this key's value. It should conform to the same rules as the `id` key. If not specified, this decoder cannot feed data back into the stream. This typically means it only does analysis on the whole stream, producing a report at the end of acquisition.
author optional Name and optionally email address of the author of this PD.
version optional Version of this PD.
Available functions
start required Reference to a python function that will be called to start the decoder. The PD controller will call this function when the first packet with real data in it comes in on the stream. See below for arguments to this function.
report optional If given, this is a reference to a function which returns a report in freeform text, describing some analysis done on the stream. For example, PDs for counting logic level transitioons, bit rate analysis and so on would use this to report back. This function is typically only called at the end of the stream, so the whole stream is analyzed. However, the function may be called at any time -- possibly giving an analysis of data received up until that point.
Protocol Decoder configuration
probes optional For the 'logic' input type this is a required key. it lists the probes (pins) the logic analyzer should feed to this PD, in order for it to make sense of the data. For example, an SPI decoder has to know which probe has the clock, which has the chip select, and so on. This key contains a list of probe entries, where each entry can be either a string with the probe name (`"SCLK"`) or a list with the probe name and description, such as `["SCLK", "Clock"]`.
options optional A dictionary with options for this decoder. The keys should follow the same rules as for the `id` key above, and each value is a list consisting of a short freeform description of the option, and the default value for that option. For example, an SPI decoder might have an entry with key `cpol`, and value `'Clock polarity', 0]`.



Here's an example for an SPI decoder:

register = {
 'id': 'spi',
 'description': 'SPI',
 'author': 'Sigrok project',
 'in': 'logic',
 'out': 'spi',
 'probes': [
   ['sclk', 'Clock, also known as sck or clk'],
   'mosi',
   'miso',
   ['ncs', 'Chip Select, also known as cs'],
 ],
 'options': {
   'cpol': ['Clock polarity', 0],
   'cpha': ['Clock phase', 0],
   'wordsize': ['Word size (in bits)', 8],
 },
 'start': start,
}

The start function should look like this:

def spi_start(probes, samplerate)

This function is called by libsigrokdecode at the start of a session which has this PD in its pipeline. It should implement a loop calling get_sample(), and only return when an EOF is detected in that loop.

The probes argument is a list containing probe names, in the order in which they will be returned from get_sample().

The samplerate argument is specified in Hz.


Ideas

  • Sending analysis results back from the plugin, e.g. timestamps or sample IDs with structured results:
    • Protocol overhead e.g. start bits
    • Commands e.g. opcodes
    • Command parameters e.g. address following a READ command
    • Extracted data
    • String to display over the data ("opcode READ")
    • Data (e.g. 10) + length in bits (e.g. 2)
    • Results can overlap, as in e.g. "READ address 0x08" and "opcode 10"