DSlogic协议开发笔记

参考资料:smiley:

Protocol decoder HOWTO(新手指导)

Protocol decoder API

Protocol decoder API/Queries

名词解析

  • Protocol Decoders (PDs)

协议解码器

  • libsigrokdecode

是一个采用C语言编写的共享库,提供了数据流协议解码功能。该协议解码器采用Python(= 3.0)编写。

Decoder class functions

必须的函数

  • start(self)

这个函数在解码开始之前被调用。这里可以 [register()](#Decoder registration) 输出类型,检查用户提供的PD选项的有效性,等等。

  • decode(self) (解码)

non-stacked 解码器中,这个函数由libsigrokdecode后端调用以开始解码。它不接受任何参数,而是进入一个无限循环,并通过调用更通用的wait()方法获取样本。这使得特定的协议解码器从繁琐但常见的任务中解放出来,比如检测边缘,或者在相对于当前位置的特定时间点采样信号。

注意:这个decode(self)方法的签名已经在协议解码器API的第三版中引入,在以前的版本中只有decode(self、startsample、endsample、data)是可用的。

  • decode(self, startsample, endsample, data)

可选的函数

  • metadata(self, key, value)( 元数据 )

用于传递关于数据流的解码器元数据。目前 key 的唯一值是sigrokdecode.SRD_CONF_SAMPLERATE, value 则为数据流的采样率(samplerate),单位为Hz。

Decoder registration

解码器类必须包含几个指定PD元数据的属性。可以使用以下关键字:

原文查考 Decoder registration 章节

KeyDescription
api_version这个模块使用的libsigrokdecode API版本。现在不是2就是3。
id此协议解码器的简短唯一标识符。它应该是全小写的,只包含a-z, 0-9和下划线。这必须匹配PD的Python模块名(decoders目录中的子目录名)。siglock -cli工具使用它在命令行上指定PDs。例如:’jtag’, ‘sdcard_spi’, ‘uart’。
name解码器的名称。当列出可用的PDs时使用。例如:“JTAG”,“SD卡(SPI模式)”,“UART”。
longname译码器的(长)名。当列出可用的PDs时使用。Example: ‘Joint Test Action Group (IEEE 1149.1)’, ‘Secure Digital card (SPI mode)’, ‘Universal Asynchronous Receiver/Transmitter(UART)’
desc解码器的自由的一行描述。当列出可用的PDs时使用。应该以句号结束。Example: ‘Protocol for testing, debugging, and flashing ICs.’, ‘Secure Digital card (SPI mode) low-level protocol.’, ‘Asynchronous, serial bus.’.
license提供模块的许可证。这必须是gplv2+(即GNU通用公共许可证2或更高版本),或者gplv3+ (GNU通用公共许可证3或更高版本)。libsigrokdecode中不允许为模块提供其他许可证。
inputs此解码器需要的输入类型列表。如果解码器从逻辑分析器驱动程序获取输入,则应将其设置为logic,该逻辑将映射到数据籽类型SR_DF_LOGIC。如果它从另一个PD获取输入,则应该将其设置为该PD的输出键的值。它应该符合与id键相同的规则(小写,没有空格,等等)。
outputs此解码器产生的输出类型列表。如果这个解码器能够将解码后的数据反馈到数据流中,那么它的输出将被标识为该密钥的值。它应该与id键遵循相同的规则。
channels该密钥包含有关必须提供给该PD的通道(pin)的信息;没有他们,PD将无法工作。例如,SPI解码器必须知道哪个信道有时钟信号。该键包含一个通道条目的元组,其中每个条目都是一个Python dict,其键为id、name和desc。Example: {'id': 'rx', 'name': 'RX', 'desc': 'UART receive line'}.
optional_channelsPD可选的通道,但不是必须的。该键的格式与上面的通道键相同(dicts的元组)。如果相应的协议解码器没有可选通道,则允许该元组为空。
options描述此解码器的选项的元组。每个元组条目都是一个Python dict,其键id、desc、缺省值和值。如果PD没有可选的通道,则此元组可以为空。Example: {'id': 'bitorder', 'desc': 'Bit order', 'default': 'msb-first', 'values': ('msb-first', 'lsb-first')}.
annotations此协议解码器可以输出的注释类的列表。此列表的元素是由标识符字符串和可读的描述字符串组成的元组。标识符字符串可以在siglock -cli选项中用于选择特定的注释类型,因此不应该包含空白或特殊字符。
annotation_rows注释行用于将多个注释类型分组在一起。这个列表的元素是三个元素元组,包括: 1.注释行ID(与其他ID的命名规则相同)。2.注释行的人类可读的名称/描述字符串。3. 包含注释元组中注释类的索引的元组。示例参考
binary此协议解码器可以输出的二进制输出类型列表,格式与**annotations**列表相同。

self.put( )

原型

put(startsample, endsample, output_id, data)

参数

startsample:开始的序号

endsample:结束的序号

output_id:取值看下表

data:根据output_id的不同数据也不同

output_idfuncation
OUTPUT_ANN注释信息
OUTPUT_PYTHON
OUTPUT_BINARY
OUTPUT_META
  1. OUTPUT_ANN
  • 这个东西在显示bit数据时会在信号上有小点点指示。

  • 不同缩放级别显示不同内容
    Example: self.put(10, 20, self.out_ann, [4, ['Start', 'St', 'S']])

  • data参数是一个包含两个项的Python列表。第一项是注释索引(由解码器中项的顺序决定)。第二个是注释字符串列表。字符串应该是相同注释文本的长版本和短版本(按长度排序,最长优先),可以由前端根据缩放级别显示不同的注释文本。

  1. OUTPUT_PYTHON
  • 输出特定格式的数据么?( 数据内容本身完全依赖于各自的解码器,应该在其pd.py文件中记录。 )

    Example: self.put(10, 20, self.out_python, ['PACKET', ['Foo', 19.7, [1, 2, 3], ('bar', 'baz')]])

  • 数据参数是将传递给堆叠解码器的任意Python对象。格式和内容完全依赖于解码器。通常,包含各种内容的Python列表被传递给堆叠的PDs。

  1. OUTPUT_BINARY

    • 输出数据0x55 0xaa等等( 二进制格式的索引为4,发出的字节分别为0xfe、0x55、0xaa)

      Example: self.put(10, 20, self.out_binary, [4, b'\xfe\x55\xaa'])

    • data参数是一个包含两个项的Python列表。第一项是二进制格式的索引(由解码器中项的顺序决定)。第二个是Python bytes对象。

  2. OUTPUT_META

  • 输出解码出来的数字( 在本例中数据本身是一个浮点数)

    Example: self.put(10, 20, self.out_meta, 15.7)

  • 数据参数是特定类型的Python对象,在各自的register()函数中定义

self.wait()

这是协议解码器用来将查询发送到libsigrokdecode后端的API调用。
从PD的角度来看,这是一个阻塞呼叫。它将阻塞,直到在样本数据中找到指定的条件,然后才将控制权返回给PD。

原型

1
2
3
4
5
def wait(self, conds):
# 1. 等待,直到conds中的一个或多个条件匹配.
# 2. 设置 self.samplenum ,匹配样本的绝对样本数.
# 3. 根据 self.matched 条件匹配.
# 4. 返回一个包含匹配样本的pin值的元组.

参数

空参

如果完全不提供conds,或者它是一个空列表[],或者它只是一个“空”条件{},那么后端将直接跳到下一个示例。 (相当于直接进入下一个样本)

Examples:

1
2
3
4
5
# Don't wait for any condition, just skip to the next sample.
pins = self.wait()
pins = self.wait([])
pins = self.wait({})
pins = self.wait({'skip': 1}) # Skip one sample, see below.

conds 引脚状态条件(上下沿)

ValueDescribe
l’Low pin value (logical 0)
‘h’High pin value (logical 1)
‘r’Rising edge
‘f’Falling edge
‘e’Either edge (rising or falling)
‘s’稳定状态 Stable state, the opposite of ‘e’. That is, there was no edge and the current and previous pin value were both low (or both high).
other任何其他值都会产生错误。

Examples:

1
2
3
4
5
6
7
8
# Wait until pin 7 has a falling edge.
pins = self.wait({7: 'f'})

# Wait until pin 3 has a rising edge “and” pin 4 is high at the same time.
pins = self.wait({3: 'r', 4: 'h'})

# Wait until pins 2-4 are low and pin 16 has any edge.
pins = self.wait({2: 'l', 3: 'l', 4: 'l', 16: 'e'})

conds 样品跳过条(样本数量)

后端另一个常见的查询是,当解码器想要跳过一定数量的样本时,而不管样本值是什么(因为它们与当前的协议无关)。
这可以通过条件dict中的一个特殊键来实现— ‘skip’ 。 ‘skip’ 键的值是要跳过的样本的整数。

解码器也可以跳过一定的时间,通过使用采样来计算 ‘skip’ 键的正确值。

Examples:

1
2
3
4
5
6
7
8
9
# Skip over the next 100 samples.
pins = self.wait({'skip': 100})

# Skip over the next 20ms of samples.
pins = self.wait({'skip': 20 * (1000 / self.samplerate)})

# Skip half a bitwidth of samples (e.g. for UART).
self.halfbitwidth = int((self.samplerate / self.options['baudrate']) / 2.0)
pins = self.wait({'skip': self.halfbitwidth})

注意

1
2
pins = self.wait({'skip': 100})  	#这里的pins是元组,
(pins,) = self.wait({'skip': 100}) #可以写成这样

self.skip()

这个函数不知道怎么回事,用了会报错

注意这下面的skip不是wait

在相同的条件下混合通道索引键和“跳过”键通常没有多大意义,然而,在不同的情况下,混合索引键和“跳过”键是非常合理的:

(下面的语句应该是 ((pin7 e)和(pin12 l))或(skip 10000)

1
2
3
4
5
6
7
# Wait until there's
# a) an edge on pin 7 and a low state on pin 12, and/or
# b) 1000 samples passed by,
# whichever occurs first (both conditions could occur at the same time too).
# This is basically "wait for an edge on pin 7 and a low state on pin 12,
# with a timeout of 1000 samples".
pins = self.skip(({7: 'e', 12: 'l'}, {'skip': 1000}))

self.matched

当解码器通过self.wait()请求前端等待多个条件时,当该调用返回时,PD仅知道至少一个条件已匹配。但是,在大多数情况下,它还需要知道哪些条件匹配(或不匹配)。

这是self.matched提供的信息。它是布尔值(TrueFalse)的元组,始终包含与上次self.wait()调用中存在的条件一样多的条目。对于每个条件,各自的布尔值表示此特定条件是否匹配。

Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 等待,直到引脚9的上升边 或 引脚27的高状态(逻辑1), 或 经过一定的“时间”(这里:跳过1000个样本)。这意味着有一个1000个样本的“超时”之后self.wait()将返回(不管其他条件如何)。
pins = self.wait([{9: 'r'}, {27: 'h'}, {'skip': 1000}])

if self.matched == (True, True, False):
# 前两个条件是同时匹配的。
# Pin 9 contains a rising edge and pin 27 is high.
elif self.matched == (True, False, False):
# Rising edge on pin 9, pin 27 is guaranteed to not be high.
elif self.matched == (False, True, False):
# Pin 27 is high, pin 9 is guaranteed to not be a rising edge.
elif self.matched == (False, False, True):
# Pin 9 is not a rising edge, pin 27 is not high, but 1000 samples were skipped.
elif self.matched == (False, True, True):
# Pin 9 is not a rising edge, pin 27 is high, and it just so happens that
# exactly 1000 samples were skipped.
elif self.matched == (False, False, False):
# Bug, this cannot happen. self.wait() only returns upon >= 1 matches.

对于’skip’键/值对self。如果达到指定数目的样本,则匹配的tuple将包含一个真值。

Example:

1
2
3
4
5
6
7
# Wait for a falling edge on channel 18, or until 25000 samples passed by.
pins = self.wait([{18: 'f'}, {'skip': 25000}])

if self.matched[0]:
# Pin 18 has a falling edge.
if self.matched[1]:
# 25000 samples were skipped.

self.samplenum

self.samplenum 相当于一个指针,指示当前解码器解码的样本的位置

self.samplenum是一个特殊属性,对于协议解码器来说是只读的,并且只能由libsigrokdecode后端设置。

self.samplenum 总是在最后一次self.wait()调用返回后的当前绝对样本号(从0开始)。

self.samplerate

采样率

self.has_channel

1
2
3
4
5
6
7
8
9
10
#code1 
...
self.have_cs = self.has_channel(3)
...

#code2
...
if self.have_cs and (first or (self.matched & (0b1 << self.have_cs))):
....
...

self.has_channel返回的值似乎是对应通道的值,比如这里返回的应该是3.

一些奇怪的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def xxxxx(self):
logging.info('----- xxx start -----')
data = 0
bit_count = 5

while True:
pin_state = self.wait( [{0: 'r'}, {'skip': self.freq_half_samp }] )
if( self.matched & (0b1 << 0) ): #r
self.LS_2 |= 0b0 << bit_count
self.wait( [{'skip': (self.bit_samp-self.freq_quarter_samp) }] )
else:
self.LS_2 |= 0b1 << bit_count
self.wait( [{'skip': (self.bit_samp-self.freq_half_samp) }] )
if( bit_count == 0 ):
break
bit_count -= 1
logging.info('----- xxx end -----')
return data


注意到pin_state = self.wait( [{0: 'r'}, {'skip': self.freq_half_samp }] )语句,把pin_state删掉就可以正常运行,若没删if后面的self.wait就会一直出问题。

  • 原因应该是pin_state没打括号,pin_state变成了元组,所以出现这种情况

附录

一些快速处理数据的方法

  • 快速计算奇偶校验
1
ones = bin(0x55).count('1')
  • BCD转成整数
1
2
def bcd2int(b):
return (b & 0x0f) + ((b >> 4) * 10)
  • 一种将总线引脚序列转换为数字值的好方法:
1
2
3
4
5
6
7
from functools import reduce

def reduce_bus(bus):
if 0xFF in bus:
return None # unassigned bus channels
else:
return reduce(lambda a, b: (a << 1) | b, reversed(bus))
  • 根据协议命令构造方法名的一个好方法是(假设cmd是8,这将调用self.handle_cmd_0x08函数):
1
2
fn = getattr(self, 'handle_cmd_0x%02x' % cmd);
fn(arg1, arg2, ...)
  • 一种处理Python缺少枚举类型(对状态、pin索引、注释索引等有用)的方法
1
2
class Cycle:
NONE, MEMRD, MEMWR, IORD, IOWR, FETCH, INTACK = range(7)

DSlogic协议开发笔记
https://www.oikiou.top/2020/5ced0506/
作者
Oikiou
发布于
2020年5月21日
许可协议