Skip to content

Commit a4234c3

Browse files
committed
i2c_packet: new decoder for forming packets from i2c data
i2c_packet protocol decoder stacks with i2c PD and allows to combine several data bytes from "START" i2c condition until "STOP" or "START REPEAT" and puts this data in a dedicated annotation. Possible data formats for data output are hex, ascii, dec, bin and oct. This decoder also allows logging data packets with the print_sec option enabled.
1 parent e556e11 commit a4234c3

2 files changed

Lines changed: 223 additions & 0 deletions

File tree

decoders/i2c_packet/__init__.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
##
2+
## This file is part of the libsigrokdecode project.
3+
##
4+
## Copyright (C) 2022 Sergey Spivak <sespivak@yandex.ru>
5+
##
6+
## Permission is hereby granted, free of charge, to any person obtaining a copy
7+
## of this software and associated documentation files (the "Software"), to deal
8+
## in the Software without restriction, including without limitation the rights
9+
## to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
## copies of the Software, and to permit persons to whom the Software is
11+
## furnished to do so, subject to the following conditions:
12+
##
13+
## The above copyright notice and this permission notice shall be included in all
14+
## copies or substantial portions of the Software.
15+
##
16+
## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
## IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
## FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
## AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
## LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
## OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
## SOFTWARE.
23+
24+
'''
25+
Make data packets from I²C decoder data.
26+
'''
27+
28+
from .pd import Decoder

decoders/i2c_packet/pd.py

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
##
2+
## This file is part of the libsigrokdecode project.
3+
##
4+
## Copyright (C) 2022 Sergey Spivak <sespivak@yandex.ru>
5+
##
6+
## Permission is hereby granted, free of charge, to any person obtaining a copy
7+
## of this software and associated documentation files (the "Software"), to deal
8+
## in the Software without restriction, including without limitation the rights
9+
## to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
## copies of the Software, and to permit persons to whom the Software is
11+
## furnished to do so, subject to the following conditions:
12+
##
13+
## The above copyright notice and this permission notice shall be included in all
14+
## copies or substantial portions of the Software.
15+
##
16+
## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
## IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
## FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
## AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
## LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
## OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
## SOFTWARE.
23+
24+
from collections import deque
25+
import sigrokdecode as srd
26+
27+
'''
28+
OUTPUT_PYTHON format:
29+
30+
Packet:
31+
[<ptype>, <pdata>]
32+
33+
<ptype>:
34+
- 'PACKET READ' (ADDRESS READ followed by one or several
35+
DATA bytes until STOP or START REPEAT)
36+
- 'PACKET WRITE' (ADDRESS WRITE followed by one or several
37+
DATA bytes until STOP or START REPEAT)
38+
39+
<pdata> is the tuple with slave address byte and tuple with data bytes.
40+
Slave addresses do not include bit 0 (the READ/WRITE indication bit).
41+
'''
42+
43+
class Ann:
44+
PACKET_READ, \
45+
PACKET_WRITE, \
46+
= range(2)
47+
48+
RX = 0
49+
TX = 1
50+
51+
class Decoder(srd.Decoder):
52+
api_version = 3
53+
id = 'i2c_packet'
54+
name = 'I²C packet'
55+
longname = 'I²C packet builder'
56+
desc = 'Combine I²C data to packets'
57+
license = 'mit'
58+
inputs = ['i2c']
59+
outputs = []
60+
tags = ['Embedded/industrial']
61+
options = (
62+
{'id': 'format', 'desc': 'Data format', 'default': 'hex',
63+
'values': ('ascii', 'dec', 'hex', 'oct', 'bin')},
64+
{'id': 'print_sec', 'desc': 'Print start time (sec)', 'default': 'no', 'values': ('yes', 'no')},
65+
)
66+
annotations = (
67+
('packet-read', 'READ'),
68+
('packet-write', 'WRITE'),
69+
)
70+
annotation_rows = (
71+
('packet-read', 'RD', (Ann.PACKET_READ, )),
72+
('packet-write', 'WR', (Ann.PACKET_WRITE, )),
73+
)
74+
75+
def __init__(self):
76+
self.out_py = None
77+
self.out_ann = None
78+
self.packet_data = deque()
79+
self.packet_ss = 0
80+
self.packet_es = 0
81+
self.read_sign = False
82+
self.address = 0
83+
self.print_sec = False
84+
self.fmt = None
85+
self.samplerate = 0
86+
self.sampletime = 0
87+
self.reset()
88+
89+
def metadata(self, key, value):
90+
if key == srd.SRD_CONF_SAMPLERATE:
91+
self.samplerate = value
92+
if self.samplerate:
93+
self.sampletime = 1.0 / self.samplerate
94+
95+
def start(self):
96+
self.out_ann = self.register(srd.OUTPUT_ANN)
97+
self.out_py = self.register(srd.OUTPUT_PYTHON)
98+
99+
format_name = self.options['format']
100+
if format_name == 'hex':
101+
self.fmt = '{:02X}'
102+
elif format_name == 'dec':
103+
self.fmt = '{:d}'
104+
elif format_name == 'bin':
105+
self.fmt = '{:08b}'
106+
elif format_name == 'oct':
107+
self.fmt = '{:03o}'
108+
else:
109+
self.fmt = None
110+
111+
self.print_sec = self.options['print_sec'] == 'yes'
112+
self.packet_data = deque()
113+
114+
def reset(self):
115+
self.packet_data.clear()
116+
self.packet_ss = 0
117+
self.packet_es = 0
118+
self.address = 0
119+
120+
def putg(self, ss, es, data):
121+
"""Put a graphical annotation."""
122+
self.put(ss, es, self.out_ann, data)
123+
124+
def putp(self, ss, es, data):
125+
"""Put a python annotation."""
126+
self.put(ss, es, self.out_py, data)
127+
128+
def format_data_value(self, v):
129+
# Assume "is printable" for values from 32 to including 126,
130+
# below 32 is "control" and thus not printable, above 127 is
131+
# "not ASCII" in its strict sense, 127 (DEL) is not printable,
132+
# fall back to hex representation for non-printables.
133+
if self.fmt is None:
134+
if 32 <= v <= 126:
135+
return chr(v)
136+
return "[{:02X}]".format(v)
137+
else:
138+
return self.fmt.format(v)
139+
140+
def data_array_to_str(self, data_array):
141+
if self.fmt:
142+
str_array = [self.fmt.format(value) for value in data_array]
143+
return ' '.join(str_array)
144+
else:
145+
str_array = [self.format_data_value(value) for value in data_array]
146+
return ''.join(str_array)
147+
148+
def handle_packet(self):
149+
if len(self.packet_data):
150+
packet_str = self.data_array_to_str(self.packet_data)
151+
if self.read_sign:
152+
ann = Ann.PACKET_READ
153+
else:
154+
ann = Ann.PACKET_WRITE
155+
156+
packet_str = "ADDR 0x{:02X} {:}: ".format(
157+
self.address,
158+
'RD' if self.read_sign else 'WR',
159+
) + packet_str
160+
161+
# TODO: This option should be in the sigrok-cli to avoid boilerplate code in each PD
162+
if self.print_sec and self.sampletime:
163+
packet_str = "{:8.3f} ".format(self.packet_ss * self.sampletime,) + packet_str
164+
165+
self.putg(self.packet_ss, self.packet_es, [ann,
166+
[
167+
# Full version of packet annotation
168+
packet_str,
169+
# Short version without timestamp and "ADDR 0x" prefix
170+
packet_str[7+9:] if self.print_sec else packet_str[7:],
171+
]])
172+
ptype = 'PACKET READ' if self.read_sign else 'PACKET WRITE'
173+
self.putp(self.packet_ss, self.packet_es, (ptype, (self.address, tuple(self.packet_data))))
174+
self.reset()
175+
176+
def decode(self, ss, es, data):
177+
ptype = data[0]
178+
if 'DATA' in ptype:
179+
self.packet_data.append(data[1])
180+
self.packet_es = es
181+
elif ptype.startswith('START'):
182+
if 'REPEAT' in ptype:
183+
self.packet_es = es
184+
# If something goes wrong, there is still data in the receive buffer self.packet_data
185+
self.handle_packet()
186+
self.packet_ss = ss
187+
elif 'ADDRESS' in ptype:
188+
self.address = data[1]
189+
self.read_sign = 'READ' in ptype
190+
self.packet_es = es
191+
elif ptype.endswith('ACK'):
192+
self.packet_es = es
193+
elif ptype == 'STOP':
194+
self.packet_es = es
195+
self.handle_packet()

0 commit comments

Comments
 (0)