forked from Ivasoft/DSView
Merge pull request #377 from abougouffa/add_new_sigrok_decoders
Add new sigrok decoders, fix typos in README
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -7,6 +7,7 @@
|
||||
*.lo
|
||||
CMakeFiles
|
||||
CMakeCache.txt
|
||||
CMakeLists.txt.user
|
||||
*.cxx_parameters
|
||||
autom4te.cache
|
||||
!cmake_modules
|
||||
|
||||
16
README.md
16
README.md
@@ -1,18 +1,20 @@
|
||||
# DSView
|
||||

|
||||
|
||||
DSView is an GUI programe for supporting various instruments from [DreamSourceLab](http://www.dreamsourcelab.com), including logic analyzer, oscilloscope, etc. DSView is based on sigrok project.
|
||||
|
||||
# DSView
|
||||
DSView is a GUI program for supporting various instruments from [DreamSourceLab](http://www.dreamsourcelab.com), including logic analyzers, oscilloscopes, etc. DSView is based on the [sigrok project](https://sigrok.org).
|
||||
|
||||
The sigrok project aims at creating a portable, cross-platform, Free/Libre/Open-Source signal analysis software suite that supports various device types (such as logic analyzers, oscilloscopes, multimeters, and more).
|
||||
|
||||
# Status
|
||||
|
||||
DSView software is in a usable state and has had official tarball releases. However, it is still work in progress. Some basic functionality is available and working, but other things are still on the TODO list.
|
||||
The DSView software is in a usable state and has official tarball releases. However, it is still a work in progress. Some basic functionality is available and working, but other things are always on the TODO list.
|
||||
|
||||
# Usefull links
|
||||
# Useful links
|
||||
|
||||
- [dreamsourcelab.com](http://www.dreamsourcelab.com)
|
||||
- [kickstarter.com](www.kickstarter.com/projects/dreamsourcelab/dslogic-multifunction-instruments-for-everyone)
|
||||
- [sigrok.org](http://sigrok.org)
|
||||
- [dreamsourcelab.com](https://www.dreamsourcelab.com)
|
||||
- [kickstarter.com](https://www.kickstarter.com/projects/dreamsourcelab/dslogic-multifunction-instruments-for-everyone)
|
||||
- [sigrok.org](https://sigrok.org)
|
||||
|
||||
# Copyright and license
|
||||
|
||||
|
||||
25
libsigrokdecode4DSL/decoders/ad5626/__init__.py
Normal file
25
libsigrokdecode4DSL/decoders/ad5626/__init__.py
Normal file
@@ -0,0 +1,25 @@
|
||||
##
|
||||
## This file is part of the libsigrokdecode project.
|
||||
##
|
||||
## Copyright (C) 2020 Analog Devices Inc.
|
||||
##
|
||||
## 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
|
||||
## the Free Software Foundation; either version 3 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
'''
|
||||
This decoder stacks on top of the 'spi' PD and decodes the
|
||||
Analog Devices AD5626 protocol.
|
||||
'''
|
||||
|
||||
from .pd import Decoder
|
||||
62
libsigrokdecode4DSL/decoders/ad5626/pd.py
Normal file
62
libsigrokdecode4DSL/decoders/ad5626/pd.py
Normal file
@@ -0,0 +1,62 @@
|
||||
##
|
||||
## This file is part of the libsigrokdecode project.
|
||||
##
|
||||
## Copyright (C) 2020 Analog Devices Inc.
|
||||
##
|
||||
## 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
|
||||
## the Free Software Foundation; either version 3 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
import sigrokdecode as srd
|
||||
|
||||
class Decoder(srd.Decoder):
|
||||
api_version = 3
|
||||
id = 'ad5626'
|
||||
name = 'AD5626'
|
||||
longname = 'Analog Devices AD5626'
|
||||
desc = 'Analog Devices AD5626 12-bit nanoDAC.'
|
||||
license = 'gplv2+'
|
||||
inputs = ['spi']
|
||||
outputs = []
|
||||
tags = ['IC', 'Analog/digital']
|
||||
annotations = (
|
||||
('voltage', 'Voltage'),
|
||||
)
|
||||
|
||||
def __init__(self,):
|
||||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
self.data = 0
|
||||
self.ss = 0
|
||||
|
||||
def start(self):
|
||||
self.out_ann = self.register(srd.OUTPUT_ANN)
|
||||
|
||||
def decode(self, ss, es, data):
|
||||
ptype = data[0]
|
||||
|
||||
if ptype == 'CS-CHANGE':
|
||||
cs_old, cs_new = data[1:]
|
||||
if cs_old is not None and cs_old == 0 and cs_new == 1:
|
||||
self.data >>= 1
|
||||
self.data /= 1000
|
||||
self.put(self.ss, es, self.out_ann, [0, ['%.3fV' % self.data]])
|
||||
self.data = 0
|
||||
elif cs_old is not None and cs_old == 1 and cs_new == 0:
|
||||
self.ss = ss
|
||||
elif ptype == 'BITS':
|
||||
mosi = data[1]
|
||||
for bit in reversed(mosi):
|
||||
self.data = self.data | bit[0]
|
||||
self.data <<= 1
|
||||
25
libsigrokdecode4DSL/decoders/ad79x0/__init__.py
Normal file
25
libsigrokdecode4DSL/decoders/ad79x0/__init__.py
Normal file
@@ -0,0 +1,25 @@
|
||||
##
|
||||
## This file is part of the libsigrokdecode project.
|
||||
##
|
||||
## Copyright (C) 2020 Analog Devices Inc.
|
||||
##
|
||||
## 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
|
||||
## the Free Software Foundation; either version 3 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
'''
|
||||
This decoder stacks on top of the 'spi' PD and decodes the
|
||||
Analog Devices AD7910/AD7920 protocol.
|
||||
'''
|
||||
|
||||
from .pd import Decoder
|
||||
137
libsigrokdecode4DSL/decoders/ad79x0/pd.py
Normal file
137
libsigrokdecode4DSL/decoders/ad79x0/pd.py
Normal file
@@ -0,0 +1,137 @@
|
||||
##
|
||||
## This file is part of the libsigrokdecode project.
|
||||
##
|
||||
## Copyright (C) 2020 Analog Devices Inc.
|
||||
##
|
||||
## 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
|
||||
## the Free Software Foundation; either version 3 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
import sigrokdecode as srd
|
||||
|
||||
modes = {
|
||||
0: ['Normal Mode', 'Normal', 'Norm', 'N'],
|
||||
1: ['Power Down Mode', 'Power Down', 'PD'],
|
||||
2: ['Power Up Mode', 'Power Up', 'PU'],
|
||||
}
|
||||
|
||||
input_voltage_format = ['%.6fV', '%.2fV']
|
||||
|
||||
validation = {
|
||||
'invalid': ['Invalid data', 'Invalid', 'N/A'],
|
||||
'incomplete': ['Incomplete conversion', 'Incomplete', 'I'],
|
||||
'complete': ['Complete conversion', 'Complete', 'C'],
|
||||
}
|
||||
|
||||
class Decoder(srd.Decoder):
|
||||
api_version = 3
|
||||
id = 'ad79x0'
|
||||
name = 'AD79x0'
|
||||
longname = 'Analog Devices AD79x0'
|
||||
desc = 'Analog Devices AD7910/AD7920 12-bit ADC.'
|
||||
license = 'gplv2+'
|
||||
inputs = ['spi']
|
||||
outputs = []
|
||||
tags = ['IC', 'Analog/digital']
|
||||
annotations = (
|
||||
('mode', 'Mode'),
|
||||
('voltage', 'Voltage'),
|
||||
('validation', 'Validation'),
|
||||
)
|
||||
annotation_rows = (
|
||||
('modes', 'Modes', (0,)),
|
||||
('voltages', 'Voltages', (1,)),
|
||||
('data_validation', 'Data validation', (2,)),
|
||||
)
|
||||
options = (
|
||||
{'id': 'vref', 'desc': 'Reference voltage (V)', 'default': 1.5},
|
||||
)
|
||||
|
||||
def __init__(self,):
|
||||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
self.samplerate = 0
|
||||
self.samples_bit = -1
|
||||
self.ss = -1
|
||||
self.start_sample = 0
|
||||
self.previous_state = 0
|
||||
self.data = 0
|
||||
|
||||
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 put_validation(self, pos, msg):
|
||||
self.put(pos[0], pos[1], self.out_ann, [2, validation[msg]])
|
||||
|
||||
def put_data(self, pos, input_voltage):
|
||||
ann = []
|
||||
for format in input_voltage_format:
|
||||
ann.append(format % input_voltage)
|
||||
self.put(pos[0], pos[1], self.out_ann, [1, ann])
|
||||
|
||||
def put_mode(self, pos, msg):
|
||||
self.put(pos[0], pos[1], self.out_ann, [0, modes[msg]])
|
||||
|
||||
def decode(self, ss, es, data):
|
||||
ptype = data[0]
|
||||
|
||||
if ptype == 'CS-CHANGE':
|
||||
cs_old, cs_new = data[1:]
|
||||
if cs_old is not None and cs_old == 0 and cs_new == 1:
|
||||
if self.samples_bit == -1:
|
||||
return
|
||||
self.data >>= 1
|
||||
nb_bits = (ss - self.ss) // self.samples_bit
|
||||
if nb_bits >= 10:
|
||||
if self.data == 0xFFF:
|
||||
self.put_mode([self.start_sample, es], 2)
|
||||
self.previous_state = 0
|
||||
self.put_validation([self.start_sample, es], 'invalid')
|
||||
else:
|
||||
self.put_mode([self.start_sample, es], 0)
|
||||
if nb_bits == 16:
|
||||
self.put_validation([self.start_sample, es], 'complete')
|
||||
elif nb_bits < 16:
|
||||
self.put_validation([self.start_sample, es], 'incomplete')
|
||||
vin = (self.data / ((2**12) - 1)) * self.options['vref']
|
||||
self.put_data([self.start_sample, es], vin)
|
||||
elif nb_bits < 10:
|
||||
self.put_mode([self.start_sample, es], 1)
|
||||
self.previous_state = 1
|
||||
self.put_validation([self.start_sample, es], 'invalid')
|
||||
|
||||
self.ss = -1
|
||||
self.samples_bit = -1
|
||||
self.data = 0
|
||||
elif cs_old is not None and cs_old == 1 and cs_new == 0:
|
||||
self.start_sample = ss
|
||||
self.samples_bit = -1
|
||||
|
||||
elif ptype == 'BITS':
|
||||
if data[2] is None:
|
||||
return
|
||||
miso = data[2]
|
||||
if self.samples_bit == -1:
|
||||
self.samples_bit = miso[0][2] - miso[0][1]
|
||||
|
||||
if self.ss == -1:
|
||||
self.ss = ss
|
||||
|
||||
for bit in reversed(miso):
|
||||
self.data = self.data | bit[0]
|
||||
self.data <<= 1
|
||||
26
libsigrokdecode4DSL/decoders/adxl345/__init__.py
Normal file
26
libsigrokdecode4DSL/decoders/adxl345/__init__.py
Normal file
@@ -0,0 +1,26 @@
|
||||
##
|
||||
## This file is part of the libsigrokdecode project.
|
||||
##
|
||||
## Copyright (C) 2020 Analog Devices Inc.
|
||||
##
|
||||
## 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
|
||||
## the Free Software Foundation; either version 2 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program; if not, write to the Free Software
|
||||
## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
##
|
||||
|
||||
'''
|
||||
This decoder stacks on top of the 'spi' PD and decodes the
|
||||
Analog Devices ADXL345 protocol.
|
||||
'''
|
||||
|
||||
from .pd import Decoder
|
||||
96
libsigrokdecode4DSL/decoders/adxl345/lists.py
Normal file
96
libsigrokdecode4DSL/decoders/adxl345/lists.py
Normal file
@@ -0,0 +1,96 @@
|
||||
##
|
||||
## This file is part of the libsigrokdecode project.
|
||||
##
|
||||
## Copyright (C) 2020 Analog Devices Inc.
|
||||
##
|
||||
## 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
|
||||
## the Free Software Foundation; either version 2 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program; if not, write to the Free Software
|
||||
## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
##
|
||||
|
||||
error_messages = {
|
||||
'interrupt': ['Interrupt'],
|
||||
'undesirable': ['Undesirable behavior'],
|
||||
'dis_single': ['Disable single tap'],
|
||||
'dis_double': ['Disable double tap'],
|
||||
'dis_single_double': ['Disable single/double tap'],
|
||||
}
|
||||
|
||||
rate_code = {
|
||||
0x00: 0.1,
|
||||
0x01: 0.2,
|
||||
0x02: 0.39,
|
||||
0x03: 0.78,
|
||||
0x04: 1.56,
|
||||
0x05: 3.13,
|
||||
0x06: 6.25,
|
||||
0x07: 12.5,
|
||||
0x08: 25,
|
||||
0x09: 50,
|
||||
0x0A: 100,
|
||||
0x0B: 200,
|
||||
0x0C: 400,
|
||||
0x0D: 800,
|
||||
0x0E: 1600,
|
||||
0x0F: 3200,
|
||||
}
|
||||
|
||||
fifo_modes = {
|
||||
0x00: 'Bypass',
|
||||
0x01: 'FIFO',
|
||||
0x02: 'Stream',
|
||||
0x03: 'Trigger',
|
||||
}
|
||||
|
||||
operations = {
|
||||
0x00: ['WRITE REG', 'WRITE', 'W'],
|
||||
0x01: ['READ REG', 'READ', 'R'],
|
||||
}
|
||||
|
||||
number_bytes = {
|
||||
0x00: ['SINGLE BYTE', 'SING BYTE', '1 BYTE', '1B'],
|
||||
0x01: ['MULTIPLE BYTES', 'MULTI BYTES', 'n*BYTES', 'n*B'],
|
||||
}
|
||||
|
||||
registers = {
|
||||
0x00: ['DEVID', 'DID', 'ID'],
|
||||
0x1D: ['THRESH_TAP', 'TH_TAP', 'TH_T'],
|
||||
0x1E: ['OFSX', 'OFX'],
|
||||
0x1F: ['OFSY', 'OFY'],
|
||||
0x20: ['OFSZ', 'OFZ'],
|
||||
0x21: ['DUR'],
|
||||
0x22: ['Latent', 'Lat'],
|
||||
0x23: ['Window', 'Win'],
|
||||
0x24: ['THRESH_ACT', 'TH_ACT', 'TH_A'],
|
||||
0x25: ['THRESH_INACT', 'TH_INACT', 'TH_I'],
|
||||
0x26: ['TIME_INACT', 'TI_INACT', 'TI_I'],
|
||||
0x27: ['ACT_INACT_CTL', 'ACT_I_CTL', 'A_I_C'],
|
||||
0x28: ['THRESH_FF', 'TH_FF'],
|
||||
0x29: ['TIME_FF', 'TI_FF'],
|
||||
0x2A: ['TAP_AXES', 'TAP_AX', 'TP_AX'],
|
||||
0x2B: ['ACT_TAP_STATUS', 'ACT_TAP_STAT', 'ACT_TP_ST', 'A_T_S'],
|
||||
0x2C: ['BW_RATE', 'BW_R'],
|
||||
0x2D: ['POWER_CTL', 'PW_CTL', 'PW_C'],
|
||||
0x2E: ['INT_ENABLE', 'INT_EN', 'I_EN'],
|
||||
0x2F: ['INT_MAP', 'I_M'],
|
||||
0x30: ['INT_SOURCE', 'INT_SRC', 'I_SRC', 'I_S'],
|
||||
0x31: ['DATA_FORMAT', 'DATA_FRM', 'D_FRM', 'D_F'],
|
||||
0x32: ['DATAX0', 'DX0', 'X0'],
|
||||
0x33: ['DATAX1', 'DX1', 'X1'],
|
||||
0x34: ['DATAY0', 'DY0', 'Y0'],
|
||||
0x35: ['DATAY1', 'DY1', 'Y1'],
|
||||
0x36: ['DATAZ0', 'DZ0', 'Z0'],
|
||||
0x37: ['DATAZ1', 'DZ1', 'Z1'],
|
||||
0x38: ['FIFO_CTL', 'FIF_CT', 'F_C'],
|
||||
0x39: ['FIFO_STATUS', 'FIFO_STAT', 'FIF_ST', 'F_S'],
|
||||
}
|
||||
453
libsigrokdecode4DSL/decoders/adxl345/pd.py
Normal file
453
libsigrokdecode4DSL/decoders/adxl345/pd.py
Normal file
@@ -0,0 +1,453 @@
|
||||
##
|
||||
## This file is part of the libsigrokdecode project.
|
||||
##
|
||||
## Copyright (C) 2020 Analog Devices Inc.
|
||||
##
|
||||
## 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
|
||||
## the Free Software Foundation; either version 3 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
import sigrokdecode as srd
|
||||
from common.srdhelper import SrdIntEnum
|
||||
from .lists import *
|
||||
|
||||
WORD_SIZE = 8
|
||||
|
||||
class Channel():
|
||||
MISO, MOSI = range(2)
|
||||
|
||||
class Operation():
|
||||
READ, WRITE = range(2)
|
||||
|
||||
class BitType():
|
||||
ENABLE = {1: ['Enable %s', 'En %s', '%s '], 0: ['Disable %s', 'Dis %s', '!%s '],}
|
||||
SOURCE = {1: ['Involve %s', 'Inv %s', '%s'], 0: ['Not involve %s', 'Not inv %s', '!%s'],}
|
||||
INTERRUPT = {1: ['INT2 %s', 'I2: %s '], 0: ['INT1 %s', 'I1:%s '],}
|
||||
AC_DC = {1: ['%s ac', 'ac'], 0: ['%s dc', 'dc'],}
|
||||
UNUSED = {1: ['N/A'], 0: ['N/A'],}
|
||||
OTHER = 0
|
||||
|
||||
class Bit():
|
||||
def __init__(self, name, type, values=None):
|
||||
self.value = 0
|
||||
self.name = name
|
||||
self.type = type
|
||||
self.values = values
|
||||
|
||||
def set_value(self, value):
|
||||
self.value = value
|
||||
|
||||
def get_bit_annotation(self):
|
||||
if self.type == BitType.OTHER:
|
||||
annotation = self.values[self.value].copy()
|
||||
else:
|
||||
annotation = self.type[self.value].copy()
|
||||
|
||||
for index in range(len(annotation)):
|
||||
if '%s' in annotation[index]:
|
||||
annotation[index] = str(annotation[index] % self.name)
|
||||
return annotation
|
||||
|
||||
Ann = SrdIntEnum.from_str('Ann', 'READ WRITE MB REG_ADDRESS REG_DATA WARNING')
|
||||
|
||||
St = SrdIntEnum.from_str('St', 'IDLE ADDRESS_BYTE DATA')
|
||||
|
||||
class Decoder(srd.Decoder):
|
||||
api_version = 3
|
||||
id = 'adxl345'
|
||||
name = 'ADXL345'
|
||||
longname = 'Analog Devices ADXL345'
|
||||
desc = 'Analog Devices ADXL345 3-axis accelerometer.'
|
||||
license = 'gplv2+'
|
||||
inputs = ['spi']
|
||||
outputs = []
|
||||
tags = ['IC', 'Sensor']
|
||||
annotations = (
|
||||
('read', 'Read'),
|
||||
('write', 'Write'),
|
||||
('mb', 'Multiple bytes'),
|
||||
('reg-address', 'Register address'),
|
||||
('reg-data', 'Register data'),
|
||||
('warning', 'Warning'),
|
||||
)
|
||||
annotation_rows = (
|
||||
('reg', 'Registers', (Ann.READ, Ann.WRITE, Ann.MB, Ann.REG_ADDRESS)),
|
||||
('data', 'Data', (Ann.REG_DATA, Ann.WARNING)),
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
self.mosi, self.miso = [], []
|
||||
self.reg = []
|
||||
self.operation = None
|
||||
self.address = 0
|
||||
self.data = -1
|
||||
self.state = St.IDLE
|
||||
self.ss, self.es = -1, -1
|
||||
self.samples_per_bit = 0
|
||||
|
||||
def start(self):
|
||||
self.out_ann = self.register(srd.OUTPUT_ANN)
|
||||
|
||||
def putx(self, data):
|
||||
self.put(self.ss, self.es, self.out_ann, data)
|
||||
|
||||
def putb(self, data, index):
|
||||
start = self.ss + (self.samples_per_bit * index)
|
||||
self.put(start, start + self.samples_per_bit, self.out_ann, data)
|
||||
|
||||
def putbs(self, data, start_index, stop_index):
|
||||
start_index = self.reverse_bit_index(start_index, WORD_SIZE)
|
||||
stop_index = self.reverse_bit_index(stop_index, WORD_SIZE)
|
||||
start = self.ss + (self.samples_per_bit * start_index)
|
||||
stop = start + (self.samples_per_bit * (stop_index - start_index + 1))
|
||||
self.put(start, stop, self.out_ann, data)
|
||||
|
||||
def handle_reg_with_scaling_factor(self, data, factor, name, unit, error_msg):
|
||||
if data == 0 and error_msg is not None:
|
||||
self.putx([Ann.WARNING, error_msg])
|
||||
else:
|
||||
result = (data * factor) / 1000
|
||||
self.putx([Ann.REG_DATA, ['%s: %f %s' % (name, result, unit), '%f %s' % (result, unit)]])
|
||||
|
||||
def handle_reg_bit_msg(self, bit, index, en_msg, dis_msg):
|
||||
self.putb([Ann.REG_DATA, [en_msg if bit else dis_msg]], index)
|
||||
|
||||
def interpret_bits(self, data, bits):
|
||||
bits_values = []
|
||||
for offset in range(8):
|
||||
bits_values.insert(0, (data & (1 << offset)) >> offset)
|
||||
|
||||
for index in range(len(bits)):
|
||||
if bits[index] is None:
|
||||
continue
|
||||
bit = bits[index]
|
||||
bit.set_value(bits_values[index])
|
||||
self.putb([Ann.REG_DATA, bit.get_bit_annotation()], index)
|
||||
|
||||
return list(reversed(bits_values))
|
||||
|
||||
def reverse_bit_index(self, index, word_size):
|
||||
return word_size - index - 1
|
||||
|
||||
def get_decimal_number(self, bits, start_index, stop_index):
|
||||
number = 0
|
||||
interval = range(start_index, stop_index + 1, 1)
|
||||
for index, offset in zip(interval, range(len(interval))):
|
||||
bit = bits[index]
|
||||
number = number | (bit << offset)
|
||||
return number
|
||||
|
||||
def get_axis_value(self, data, axis):
|
||||
if self.data != - 1:
|
||||
data <<= 8
|
||||
self.data |= data
|
||||
self.put(self.start_index, self.es, self.out_ann,
|
||||
[Ann.REG_DATA, ['%s: 0x%04X' % (axis, self.data), str(data)]])
|
||||
self.data = -1
|
||||
else:
|
||||
self.putx([Ann.REG_DATA, [str(data)]])
|
||||
|
||||
def handle_reg_0x1d(self, data):
|
||||
self.handle_reg_with_scaling_factor(data, 62.5, 'Threshold', 'g',
|
||||
error_messages['undesirable'])
|
||||
|
||||
def handle_reg_0x1e(self, data):
|
||||
self.handle_reg_with_scaling_factor(data, 15.6, 'OFSX', 'g', None)
|
||||
|
||||
def handle_reg_0x1f(self, data):
|
||||
self.handle_reg_with_scaling_factor(data, 15.6, 'OFSY', 'g', None)
|
||||
|
||||
def handle_reg_0x20(self, data):
|
||||
self.handle_reg_with_scaling_factor(data, 15.6, 'OFSZ', 'g', None)
|
||||
|
||||
def handle_reg_0x21(self, data):
|
||||
self.handle_reg_with_scaling_factor(data, 0.625, 'Duration', 's',
|
||||
error_messages['dis_single_double'])
|
||||
|
||||
def handle_reg_0x22(self, data):
|
||||
self.handle_reg_with_scaling_factor(data, 1.25, 'Latency', 's',
|
||||
error_messages['dis_double'])
|
||||
|
||||
def handle_reg_0x23(self, data):
|
||||
self.handle_reg_with_scaling_factor(data, 1.25, 'Window', 's',
|
||||
error_messages['dis_double'])
|
||||
|
||||
def handle_reg_0x24(self, data):
|
||||
self.handle_reg_0x1d(data)
|
||||
|
||||
def handle_reg_0x25(self, data):
|
||||
self.handle_reg_0x1d(data)
|
||||
|
||||
def handle_reg_0x26(self, data):
|
||||
self.handle_reg_with_scaling_factor(data, 1000, 'Time', 's',
|
||||
error_messages['interrupt'])
|
||||
|
||||
def handle_reg_0x27(self, data):
|
||||
bits = [Bit('ACT', BitType.AC_DC),
|
||||
Bit('ACT_X', BitType.ENABLE),
|
||||
Bit('ACT_Y', BitType.ENABLE),
|
||||
Bit('ACT_Z', BitType.ENABLE),
|
||||
Bit('INACT', BitType.AC_DC),
|
||||
Bit('INACT_X', BitType.ENABLE),
|
||||
Bit('INACT_Y', BitType.ENABLE),
|
||||
Bit('INACT_Z', BitType.ENABLE)]
|
||||
self.interpret_bits(data, bits)
|
||||
|
||||
def handle_reg_0x28(self, data):
|
||||
self.handle_reg_0x1d(data)
|
||||
|
||||
def handle_reg_0x29(self, data):
|
||||
self.handle_reg_with_scaling_factor(data, 5, 'Time', 's',
|
||||
error_messages['undesirable'])
|
||||
|
||||
def handle_reg_0x2a(self, data):
|
||||
bits = [Bit('', BitType.UNUSED),
|
||||
Bit('', BitType.UNUSED),
|
||||
Bit('', BitType.UNUSED),
|
||||
Bit('', BitType.UNUSED),
|
||||
Bit('', BitType.OTHER, {1: ['Suppressed', 'Suppr', 'S'],
|
||||
0: ['Unsuppressed', 'Unsuppr', 'Uns'],}),
|
||||
Bit('TAP_X', BitType.ENABLE),
|
||||
Bit('TAP_Y', BitType.ENABLE),
|
||||
Bit('TAP_Z', BitType.ENABLE)]
|
||||
self.interpret_bits(data, bits)
|
||||
|
||||
def handle_reg_0x2b(self, data):
|
||||
bits = [Bit('', BitType.UNUSED),
|
||||
Bit('ACT_X', BitType.SOURCE),
|
||||
Bit('ACT_Y', BitType.SOURCE),
|
||||
Bit('ACT_Z', BitType.SOURCE),
|
||||
Bit('', BitType.OTHER, {1: ['Asleep', 'Asl'],
|
||||
0: ['Not asleep', 'Not asl', '!Asl'],}),
|
||||
Bit('TAP_X', BitType.SOURCE),
|
||||
Bit('TAP_Y', BitType.SOURCE),
|
||||
Bit('TAP_Z', BitType.SOURCE)]
|
||||
self.interpret_bits(data, bits)
|
||||
|
||||
def handle_reg_0x2c(self, data):
|
||||
bits = [Bit('', BitType.UNUSED),
|
||||
Bit('', BitType.UNUSED),
|
||||
Bit('', BitType.UNUSED),
|
||||
Bit('', BitType.OTHER, {1: ['Reduce power', 'Reduce pw', 'Red pw'], 0: ['Normal operation', 'Normal op', 'Norm op'],})]
|
||||
bits_values = self.interpret_bits(data, bits)
|
||||
|
||||
start_index, stop_index = 0, 3
|
||||
rate = self.get_decimal_number(bits_values, start_index, stop_index)
|
||||
self.putbs([Ann.REG_DATA, ['%f' % rate_code[rate]]], stop_index, start_index)
|
||||
|
||||
def handle_reg_0x2d(self, data):
|
||||
bits = [Bit('', BitType.UNUSED),
|
||||
Bit('', BitType.UNUSED),
|
||||
Bit('', BitType.OTHER, {1: ['Link'], 0: ['Unlink'], }),
|
||||
Bit('AUTO_SLEEP', BitType.ENABLE),
|
||||
Bit('', BitType.OTHER, {1: ['Measurement mode', 'Measurement', 'Meas'], 0: ['Standby mode', 'Standby'], }),
|
||||
Bit('', BitType.OTHER, {1: ['Sleep mode', 'Sleep', 'Slp'], 0: ['Normal mode', 'Normal', 'Nrm'],})]
|
||||
bits_values = self.interpret_bits(data, bits)
|
||||
|
||||
start_index, stop_index = 0, 1
|
||||
wakeup = self.get_decimal_number(bits_values, start_index, stop_index)
|
||||
frequency = 2 ** (~wakeup & 0x03)
|
||||
self.putbs([Ann.REG_DATA, ['%d Hz' % frequency]], stop_index, start_index)
|
||||
|
||||
def handle_reg_0x2e(self, data):
|
||||
bits = [Bit('DATA_READY', BitType.ENABLE),
|
||||
Bit('SINGLE_TAP', BitType.ENABLE),
|
||||
Bit('DOUBLE_TAP', BitType.ENABLE),
|
||||
Bit('Activity', BitType.ENABLE),
|
||||
Bit('Inactivity', BitType.ENABLE),
|
||||
Bit('FREE_FALL', BitType.ENABLE),
|
||||
Bit('Watermark', BitType.ENABLE),
|
||||
Bit('Overrun', BitType.ENABLE)]
|
||||
self.interpret_bits(data, bits)
|
||||
|
||||
def handle_reg_0x2f(self, data):
|
||||
bits = [Bit('DATA_READY', BitType.INTERRUPT),
|
||||
Bit('SINGLE_TAP', BitType.INTERRUPT),
|
||||
Bit('DOUBLE_TAP', BitType.INTERRUPT),
|
||||
Bit('Activity', BitType.INTERRUPT),
|
||||
Bit('Inactivity', BitType.INTERRUPT),
|
||||
Bit('FREE_FALL', BitType.INTERRUPT),
|
||||
Bit('Watermark', BitType.INTERRUPT),
|
||||
Bit('Overrun', BitType.INTERRUPT)]
|
||||
self.interpret_bits(data, bits)
|
||||
|
||||
def handle_reg_0x30(self, data):
|
||||
bits = [Bit('DATA_READY', BitType.SOURCE),
|
||||
Bit('SINGLE_TAP', BitType.SOURCE),
|
||||
Bit('DOUBLE_TAP', BitType.SOURCE),
|
||||
Bit('Activity', BitType.SOURCE),
|
||||
Bit('Inactivity', BitType.SOURCE),
|
||||
Bit('FREE_FALL', BitType.SOURCE),
|
||||
Bit('Watermark', BitType.SOURCE),
|
||||
Bit('Overrun', BitType.SOURCE)]
|
||||
self.interpret_bits(data, bits)
|
||||
|
||||
def handle_reg_0x31(self, data):
|
||||
bits = [Bit('SELF_TEST', BitType.ENABLE),
|
||||
Bit('', BitType.OTHER, {1: ['3-wire SPI', '3-SPI'], 0: ['4-wire SPI', '4-SPI'],}),
|
||||
Bit('', BitType.OTHER, {1: ['INT ACT LOW', 'INT LOW'], 0: ['INT ACT HIGH', 'INT HIGH'],}),
|
||||
Bit('', BitType.UNUSED),
|
||||
Bit('', BitType.OTHER, {1: ['Full resolution', 'Full res'], 0: ['10-bit mode', '10-bit'],}),
|
||||
Bit('', BitType.OTHER, {1: ['MSB mode', 'MSB'], 0: ['LSB mode', 'LSB'],})]
|
||||
bits_values = self.interpret_bits(data, bits)
|
||||
|
||||
start_index, stop_index = 0, 1
|
||||
range_g = self.get_decimal_number(bits_values, start_index, stop_index)
|
||||
result = 2 ** (range_g + 1)
|
||||
self.putbs([Ann.REG_DATA, ['+/-%d g' % result]], stop_index, start_index)
|
||||
|
||||
def handle_reg_0x32(self, data):
|
||||
self.data = data
|
||||
self.putx([Ann.REG_DATA, [str(data)]])
|
||||
|
||||
def handle_reg_0x33(self, data):
|
||||
self.get_axis_value(data, 'X')
|
||||
|
||||
def handle_reg_0x34(self, data):
|
||||
self.handle_reg_0x32(data)
|
||||
|
||||
def handle_reg_0x35(self, data):
|
||||
self.get_axis_value(data, 'Y')
|
||||
|
||||
def handle_reg_0x36(self, data):
|
||||
self.handle_reg_0x32(data)
|
||||
|
||||
def handle_reg_0x37(self, data):
|
||||
self.get_axis_value(data, 'Z')
|
||||
|
||||
def handle_reg_0x38(self, data):
|
||||
bits = [None,
|
||||
None,
|
||||
Bit('', BitType.OTHER, {1: ['Trig-INT2', 'INT2'], 0: ['Trig-INT1', 'INT1'], })]
|
||||
bits_values = self.interpret_bits(data, bits)
|
||||
|
||||
start_index, stop_index = 6, 7
|
||||
fifo = self.get_decimal_number(bits_values, start_index, stop_index)
|
||||
self.putbs([Ann.REG_DATA, [fifo_modes[fifo]]], stop_index, start_index)
|
||||
|
||||
start_index, stop_index = 0, 4
|
||||
samples = self.get_decimal_number(bits_values, start_index, stop_index)
|
||||
self.putbs([Ann.REG_DATA, ['Samples: %d' % samples, '%d' % samples]], stop_index, start_index)
|
||||
|
||||
def handle_reg_0x39(self, data):
|
||||
bits = [Bit('', BitType.OTHER, {1: ['Triggered', 'Trigg'], 0: ['Not triggered', 'Not trigg'],}),
|
||||
Bit('', BitType.UNUSED)]
|
||||
bits_values = self.interpret_bits(data, bits)
|
||||
|
||||
start_index, stop_index = 0, 5
|
||||
entries = self.get_decimal_number(bits_values, start_index, stop_index)
|
||||
self.putbs([Ann.REG_DATA, ['Entries: %d' % entries, '%d' % entries]], stop_index, start_index)
|
||||
|
||||
def get_bit(self, channel):
|
||||
if (channel == Channel.MOSI and self.mosi is None) or \
|
||||
(channel == Channel.MISO and self.miso is None):
|
||||
raise Exception('No available data')
|
||||
|
||||
mosi_bit, miso_bit = 0, 0
|
||||
if self.miso is not None:
|
||||
if len(self.mosi) < 0:
|
||||
raise Exception('No available data')
|
||||
miso_bit = self.miso.pop(0)
|
||||
if self.miso is not None:
|
||||
if len(self.miso) < 0:
|
||||
raise Exception('No available data')
|
||||
mosi_bit = self.mosi.pop(0)
|
||||
|
||||
if channel == Channel.MOSI:
|
||||
return mosi_bit
|
||||
return miso_bit
|
||||
|
||||
def decode(self, ss, es, data):
|
||||
ptype = data[0]
|
||||
|
||||
if ptype == 'CS-CHANGE':
|
||||
cs_old, cs_new = data[1:]
|
||||
if cs_old is not None and cs_old == 1 and cs_new == 0:
|
||||
self.ss, self.es = ss, es
|
||||
self.state = St.ADDRESS_BYTE
|
||||
else:
|
||||
self.state = St.IDLE
|
||||
|
||||
elif ptype == 'BITS':
|
||||
if data[1] is not None:
|
||||
self.mosi = list(reversed(data[1]))
|
||||
if data[2] is not None:
|
||||
self.miso = list(reversed(data[2]))
|
||||
|
||||
if self.mosi is None and self.miso is None:
|
||||
return
|
||||
|
||||
if self.state == St.ADDRESS_BYTE:
|
||||
# OPERATION BIT
|
||||
op_bit = self.get_bit(Channel.MOSI)
|
||||
self.put(op_bit[1], op_bit[2], self.out_ann,
|
||||
[Ann.READ if op_bit[0] else Ann.WRITE, operations[op_bit[0]]])
|
||||
self.operation = Operation.READ if op_bit[0] else Operation.WRITE
|
||||
# MULTIPLE-BYTE BIT
|
||||
mb_bit = self.get_bit(Channel.MOSI)
|
||||
self.put(mb_bit[1], mb_bit[2], self.out_ann, [Ann.MB, number_bytes[mb_bit[0]]])
|
||||
|
||||
# REGISTER 6-BIT ADDRESS
|
||||
self.address = 0
|
||||
start_sample = self.mosi[0][1]
|
||||
addr_bit = []
|
||||
for i in range(6):
|
||||
addr_bit = self.get_bit(Channel.MOSI)
|
||||
self.address |= addr_bit[0]
|
||||
self.address <<= 1
|
||||
self.address >>= 1
|
||||
self.put(start_sample, addr_bit[2], self.out_ann,
|
||||
[Ann.REG_ADDRESS, ['ADDRESS: 0x%02X' % self.address, 'ADDR: 0x%02X'
|
||||
% self.address, '0x%02X' % self.address]])
|
||||
self.ss = -1
|
||||
self.state = St.DATA
|
||||
|
||||
elif self.state == St.DATA:
|
||||
self.reg.extend(self.mosi if self.operation == Operation.WRITE else self.miso)
|
||||
|
||||
self.mosi, self.miso = [], []
|
||||
if self.ss == -1:
|
||||
self.ss, self.es = self.reg[0][1], es
|
||||
self.samples_per_bit = self.reg[0][2] - self.ss
|
||||
|
||||
if len(self.reg) < 8:
|
||||
return
|
||||
else:
|
||||
reg_value = 0
|
||||
reg_bit = []
|
||||
for offset in range(7, -1, -1):
|
||||
reg_bit = self.reg.pop(0)
|
||||
|
||||
mask = reg_bit[0] << offset
|
||||
reg_value |= mask
|
||||
|
||||
if self.address < 0x00 or self.address > 0x39:
|
||||
return
|
||||
|
||||
if self.address in [0x32, 0x34, 0x36]:
|
||||
self.start_index = self.ss
|
||||
|
||||
if 0x1D > self.address >= 0x00:
|
||||
self.put(self.ss, reg_bit[2], self.out_ann, [Ann.REG_ADDRESS, [str(self.address)]])
|
||||
self.put(self.ss, reg_bit[2], self.out_ann, [Ann.REG_DATA, [str(reg_value)]])
|
||||
else:
|
||||
self.put(self.ss, reg_bit[2], self.out_ann, [Ann.REG_ADDRESS, registers[self.address]])
|
||||
handle_reg = getattr(self, 'handle_reg_0x%02x' % self.address)
|
||||
handle_reg(reg_value)
|
||||
|
||||
self.reg = []
|
||||
self.address += 1
|
||||
self.ss = -1
|
||||
28
libsigrokdecode4DSL/decoders/amulet_ascii/__init__.py
Normal file
28
libsigrokdecode4DSL/decoders/amulet_ascii/__init__.py
Normal file
@@ -0,0 +1,28 @@
|
||||
##
|
||||
## This file is part of the libsigrokdecode project.
|
||||
##
|
||||
## Copyright (C) 2019 Vesa-Pekka Palmu <vpalmu@depili.fi>
|
||||
##
|
||||
## 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
|
||||
## the Free Software Foundation; either version 2 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
'''
|
||||
This decoder stacks on top of the 'uart' PD and decodes the ASCII protocol
|
||||
for Amulet LCD display controllers.
|
||||
|
||||
Currently the decoder treats both RX and TX the same way, decoding all
|
||||
message types.
|
||||
'''
|
||||
|
||||
from .pd import Decoder
|
||||
73
libsigrokdecode4DSL/decoders/amulet_ascii/lists.py
Normal file
73
libsigrokdecode4DSL/decoders/amulet_ascii/lists.py
Normal file
@@ -0,0 +1,73 @@
|
||||
##
|
||||
## This file is part of the libsigrokdecode project.
|
||||
##
|
||||
## Copyright (C) 2019 Vesa-Pekka Palmu <vpalmu@depili.fi>
|
||||
##
|
||||
## 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
|
||||
## the Free Software Foundation; either version 2 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
from collections import OrderedDict
|
||||
|
||||
# OrderedDict which maps command IDs to their names and descriptions.
|
||||
cmds = OrderedDict([
|
||||
(0xA0, ('PAGE', 'Jump to page')),
|
||||
(0xD0, ('GBV', 'Get byte variable')),
|
||||
(0xD1, ('GWV', 'Get word variable')),
|
||||
(0xD2, ('GSV', 'Get string variable')),
|
||||
(0xD3, ('GLV', 'Get label variable')),
|
||||
(0xD4, ('GRPC', 'Get RPC buffer')),
|
||||
(0xD5, ('SBV', 'Set byte variable')),
|
||||
(0xD6, ('SWV', 'Set word variable')),
|
||||
(0xD7, ('SSV', 'Set string variable')),
|
||||
(0xD8, ('RPC', 'Invoke RPC')),
|
||||
(0xD9, ('LINE', 'Draw line')),
|
||||
(0xDA, ('RECT', 'Draw rectangle')),
|
||||
(0xDB, ('FRECT', 'Draw filled rectangle')),
|
||||
(0xDC, ('PIXEL', 'Draw pixel')),
|
||||
(0xDD, ('GBVA', 'Get byte variable array')),
|
||||
(0xDE, ('GWVA', 'Get word variable array')),
|
||||
(0xDF, ('SBVA', 'Set byte variable array')),
|
||||
(0xE0, ('GBVR', 'Get byte variable reply')),
|
||||
(0xE1, ('GWVR', 'Get word variable reply')),
|
||||
(0xE2, ('GSVR', 'Get string variable reply')),
|
||||
(0xE3, ('GLVR', 'Get label variable reply')),
|
||||
(0xE4, ('GRPCR', 'Get RPC buffer reply')),
|
||||
(0xE5, ('SBVR', 'Set byte variable reply')),
|
||||
(0xE6, ('SWVR', 'Set word variable reply')),
|
||||
(0xE7, ('SSVR', 'Set string variable reply')),
|
||||
(0xE8, ('RPCR', 'Invoke RPC reply')),
|
||||
(0xE9, ('LINER', 'Draw line reply')),
|
||||
(0xEA, ('RECTR', 'Draw rectangle')),
|
||||
(0xEB, ('FRECTR', 'Draw filled rectangle reply')),
|
||||
(0xEC, ('PIXELR', 'Draw pixel reply')),
|
||||
(0xED, ('GBVAR', 'Get byte variable array reply')),
|
||||
(0xEE, ('GWVAR', 'Get word variable array reply')),
|
||||
(0xEF, ('SBVAR', 'Set byte variable array reply')),
|
||||
(0xF0, ('ACK', 'Acknowledgment')),
|
||||
(0xF1, ('NACK', 'Negative acknowledgment')),
|
||||
(0xF2, ('SWVA', 'Set word variable array')),
|
||||
(0xF3, ('SWVAR', 'Set word variable array reply')),
|
||||
(0xF4, ('GCV', 'Get color variable')),
|
||||
(0xF5, ('GCVR', 'Get color variable reply')),
|
||||
(0xF6, ('SCV', 'Set color variable')),
|
||||
(0xF7, ('SCVR', 'Set color variable reply')),
|
||||
])
|
||||
|
||||
cmds_with_high_bytes = [
|
||||
0xA0, # PAGE - Page change
|
||||
0xD7, # SVV - Set string variable
|
||||
0xE7, # SVVR - Set string variable reply
|
||||
0xE2, # GSVR - Get string variable reply
|
||||
0xE3, # GLVR - Get label variable reply
|
||||
]
|
||||
699
libsigrokdecode4DSL/decoders/amulet_ascii/pd.py
Normal file
699
libsigrokdecode4DSL/decoders/amulet_ascii/pd.py
Normal file
@@ -0,0 +1,699 @@
|
||||
##
|
||||
## This file is part of the libsigrokdecode project.
|
||||
##
|
||||
## Copyright (C) 2019 Vesa-Pekka Palmu <vpalmu@depili.fi>
|
||||
##
|
||||
## 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
|
||||
## the Free Software Foundation; either version 3 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
import sigrokdecode as srd
|
||||
from math import ceil
|
||||
from common.srdhelper import SrdIntEnum
|
||||
from .lists import *
|
||||
|
||||
L = len(cmds)
|
||||
RX = 0
|
||||
TX = 1
|
||||
|
||||
Ann = SrdIntEnum.from_list('Ann',
|
||||
[c[0] for c in cmds.values()] + ['BIT', 'FIELD', 'WARN'])
|
||||
|
||||
def cmd_annotation_classes():
|
||||
return tuple([tuple([cmd[0].lower(), cmd[1]]) for cmd in cmds.values()])
|
||||
|
||||
class Decoder(srd.Decoder):
|
||||
api_version = 3
|
||||
id = 'amulet_ascii'
|
||||
name = 'Amulet ASCII'
|
||||
longname = 'Amulet LCD ASCII'
|
||||
desc = 'Amulet Technologies LCD controller ASCII protocol.'
|
||||
license = 'gplv3+'
|
||||
inputs = ['uart']
|
||||
outputs = []
|
||||
tags = ['Display']
|
||||
annotations = cmd_annotation_classes() + (
|
||||
('bit', 'Bit'),
|
||||
('field', 'Field'),
|
||||
('warning', 'Warning'),
|
||||
)
|
||||
annotation_rows = (
|
||||
('bits', 'Bits', (L + 0,)),
|
||||
('fields', 'Fields', (L + 1,)),
|
||||
('commands', 'Commands', tuple(range(L))),
|
||||
('warnings', 'Warnings', (L + 2,)),
|
||||
)
|
||||
options = (
|
||||
{'id': 'ms_chan', 'desc': 'Master -> slave channel',
|
||||
'default': 'RX', 'values': ('RX', 'TX')},
|
||||
{'id': 'sm_chan', 'desc': 'Slave -> master channel',
|
||||
'default': 'TX', 'values': ('RX', 'TX')},
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
self.state = None
|
||||
self.cmdstate = None
|
||||
|
||||
# Build dict mapping command keys to handler functions. Each
|
||||
# command in 'cmds' (defined in lists.py) has a matching
|
||||
# handler self.handle_<shortname>.
|
||||
def get_handler(cmd):
|
||||
s = 'handle_%s' % cmds[cmd][0].lower().replace('/', '_')
|
||||
return getattr(self, s)
|
||||
self.cmd_handlers = dict((cmd, get_handler(cmd)) for cmd in cmds.keys())
|
||||
|
||||
def start(self):
|
||||
self.out_ann = self.register(srd.OUTPUT_ANN)
|
||||
|
||||
def putx(self, data):
|
||||
# Simplification, most annotations span exactly one SPI byte/packet.
|
||||
self.put(self.ss, self.es, self.out_ann, data)
|
||||
|
||||
def putf(self, data):
|
||||
self.put(self.ss_field, self.es_field, self.out_ann, data)
|
||||
|
||||
def putc(self, data):
|
||||
self.put(self.ss_cmd, self.es_cmd, self.out_ann, data)
|
||||
|
||||
def cmd_ann_list(self):
|
||||
x, s = cmds[self.state][0], cmds[self.state][1]
|
||||
return ['Command: %s (%s)' % (s, x), 'Command: %s' % s,
|
||||
'Cmd: %s' % s, 'Cmd: %s' % x, x]
|
||||
|
||||
def emit_cmd_byte(self):
|
||||
self.ss_cmd = self.ss
|
||||
self.putx([Ann.FIELD, self.cmd_ann_list()])
|
||||
|
||||
def emit_addr_bytes(self, pdata):
|
||||
if self.cmdstate == 2:
|
||||
self.ss_field = self.ss
|
||||
self.addr = chr(pdata)
|
||||
self.putx([Ann.BIT, ['Address high nibble: %c' % pdata,
|
||||
'Addr high 0x%c' % pdata, 'Addr h 0x%c' % pdata]])
|
||||
elif self.cmdstate == 3:
|
||||
self.es_field = self.es
|
||||
self.addr += chr(pdata)
|
||||
self.addr = int(self.addr, 16)
|
||||
self.putx([Ann.BIT, ['Address low nibble: %c' % pdata,
|
||||
'Addr low 0x%c' % pdata, 'Addr l 0x%c' % pdata]])
|
||||
self.putf([Ann.FIELD, ['Address: 0x%02X' % self.addr,
|
||||
'Addr: 0x%02X' % self.addr, '0x%02X' % self.addr]])
|
||||
|
||||
def emit_cmd_end(self, data):
|
||||
self.es_cmd = self.es
|
||||
self.putc(data)
|
||||
self.state = None
|
||||
|
||||
def handle_read(self, data):
|
||||
if self.cmdstate == 1:
|
||||
self.emit_cmd_byte()
|
||||
self.addr = 0
|
||||
elif self.cmdstate == 2:
|
||||
self.emit_addr_bytes(pdata)
|
||||
elif self.cmdstate == 3:
|
||||
self.emit_addr_bytes(pdata)
|
||||
self.cmdstate += 1
|
||||
|
||||
def handle_set_common(self, pdata):
|
||||
if self.cmdstate == 1:
|
||||
self.addr = 0
|
||||
self.emit_addr_bytes(pdata)
|
||||
|
||||
def emit_not_implemented(self, data):
|
||||
self.es_cmd = self.es
|
||||
self.putc([Ann.WARN, ['Command not decoded', 'Not decoded']])
|
||||
self.emit_cmd_end(data)
|
||||
|
||||
def handle_string(self, pdata, ann_class):
|
||||
# TODO: unicode / string modifiers...
|
||||
self.handle_set_common(pdata)
|
||||
if self.cmdstate == 4:
|
||||
self.ss_field = self.ss
|
||||
self.value = ''
|
||||
if pdata == 0x00:
|
||||
# Null terminated string ends.
|
||||
self.es_field = self.es
|
||||
self.putx([Ann.BIT, ['NULL']])
|
||||
self.putf([Ann.FIELD, ['Value: %s' % self.value,
|
||||
'Val: %s' % self.value, '%s' % self.value]])
|
||||
self.emit_cmd_end([ann_class, self.cmd_ann_list()])
|
||||
return
|
||||
if self.cmdstate > 3:
|
||||
self.value += chr(pdata)
|
||||
self.putx([Ann.BIT, ['%c' % pdata]])
|
||||
self.cmdstate += 1
|
||||
|
||||
# Command handlers
|
||||
|
||||
# Page change 0xA0, 0x02, index_high, index_low, checksum
|
||||
def handle_page(self, pdata):
|
||||
if self.cmdstate == 2:
|
||||
if pdata == 0x02:
|
||||
self.ss_field = self.ss_cmd
|
||||
self.es_field = self.es
|
||||
self.putf([Ann.FIELD, self.cmd_ann_list()])
|
||||
self.checksum = 0xA0 + 0x02
|
||||
else:
|
||||
self.putx([Ann.WARN, ['Illegal second byte for page change',
|
||||
'Illegal byte']])
|
||||
self.state = None
|
||||
elif self.cmdstate == 3:
|
||||
self.ss_field = self.ss
|
||||
self.checksum += pdata
|
||||
self.page[0] = pdata
|
||||
elif self.cmdstate == 4:
|
||||
self.checksum += pdata
|
||||
self.page[1] = pdata
|
||||
self.es_field = self.es
|
||||
if self.page[0] == self.page [1] == 0xFF:
|
||||
# Soft reset trigger
|
||||
self.putf(Ann.WARN, ['Soft reset', 'Reset'])
|
||||
else:
|
||||
page = chr(self.page[0]) + chr(self.page[1])
|
||||
self.putf(Ann.FIELD, ['Page index: 0x%s' % page,
|
||||
'Page: 0x%s' % page, '0x%s' % page])
|
||||
elif self.cmdstate == 5:
|
||||
self.checksum += pdata
|
||||
if (self.checksum & 0xFF) != 0:
|
||||
self.putx([Ann.WARN, ['Checksum error', 'Error', 'ERR']])
|
||||
else:
|
||||
self.putx([Ann.FIELD, ['Checksum OK', 'OK']])
|
||||
self.emit_cmd_end(Ann.PAGE)
|
||||
self.cmdstate += 1
|
||||
|
||||
# Value reads: command byte, address high nibble, address low nibble
|
||||
|
||||
# Get byte value
|
||||
def handle_gbv(self, pdata):
|
||||
self.handle_read(pdata)
|
||||
self.emit_cmd_end([Ann.GBV, self.cmd_ann_list()])
|
||||
|
||||
# Get word value
|
||||
def handle_gwv(self, pdata):
|
||||
self.handle_read(pdata)
|
||||
self.emit_cmd_end([Ann.GWV, self.cmd_ann_list()])
|
||||
|
||||
# Get string value
|
||||
def handle_gsv(self, pdata):
|
||||
self.handle_read(pdata)
|
||||
self.emit_cmd_end([Ann.GSV, self.cmd_ann_list()])
|
||||
|
||||
# Get label value
|
||||
def handle_glv(self, pdata):
|
||||
self.handle_read(pdata)
|
||||
self.emit_cmd_end([Ann.GLV, self.cmd_ann_list()])
|
||||
|
||||
# Get RPC buffer
|
||||
def handle_grpc(self, pdata):
|
||||
if self.cmdstate == 2:
|
||||
self.ss_field = self.ss
|
||||
self.flags = int(chr(pdata), 16) << 4
|
||||
elif self.cmdstate == 3:
|
||||
self.flags += int(chr(pdata), 16)
|
||||
self.es_field = self.es
|
||||
self.putf([Ann.FIELD, ['RPC flag: 0x%02X' % self.flags]])
|
||||
self.emit_cmd_end([Ann.GRPC, self.cmd_ann_list()])
|
||||
|
||||
# Get byte value array
|
||||
def handle_gbva(self, pdata):
|
||||
self.handle_read(pdata)
|
||||
self.emit_cmd_end([Ann.GBVA, self.cmd_ann_list()])
|
||||
|
||||
# Get word value array
|
||||
def handle_gwva(self, pdata):
|
||||
self.handle_read(pdata)
|
||||
self.emit_cmd_end([Ann.GWVA, self.cmd_ann_list()])
|
||||
|
||||
# Get color variable
|
||||
def handle_gcv(self, pdata):
|
||||
self.handle_read(pdata)
|
||||
self.emit_cmd_end([Ann.GCV, self.cmd_ann_list()])
|
||||
|
||||
# Value setters: command byte, address high nibble, address low nibble, data bytes
|
||||
|
||||
# Set byte value data = high nibble, low nibble
|
||||
def handle_sbv(self, pdata):
|
||||
self.handle_set_common(pdata)
|
||||
if self.cmdstate == 4:
|
||||
self.ss_field = self.ss
|
||||
self.value = chr(pdata)
|
||||
elif self.cmdstate == 5:
|
||||
self.value += chr(pdata)
|
||||
self.es_field = self.es
|
||||
self.putf([Ann.FIELD, ['Value: 0x%s' % self.value,
|
||||
'Val: 0x%s' % self.value, '0x%s' % self.value]])
|
||||
self.emit_cmd_end([Ann.SBV, self.cmd_ann_list()])
|
||||
self.cmdstate += 1
|
||||
|
||||
# Set word value, msb high, msb low, lsb high, lsb low
|
||||
def handle_swv(self, pdata):
|
||||
self.handle_set_common(pdata)
|
||||
if self.cmdstate > 3:
|
||||
nibble = self.cmdstate - 4
|
||||
if nibble == 0:
|
||||
self.ss_field = self.ss
|
||||
self.value = 0
|
||||
self.value += int(chr(pdata), 16) << 12 - (4 * nibble)
|
||||
if nibble == 3:
|
||||
self.es_field = self.es
|
||||
self.putf([Ann.FIELD, ['Value: 0x%04x' % self.value,
|
||||
'Val: 0x%04x' % self.value, '0x%04x' % self.value]])
|
||||
self.emit_cmd_end([Ann.SWV, self.cmd_ann_list()])
|
||||
return
|
||||
self.cmdstate += 1
|
||||
|
||||
# Set string value, null terminated utf8 strings
|
||||
def handle_ssv(self, pdata):
|
||||
self.handle_string(pdata, Ann.SSV)
|
||||
|
||||
# Set byte value array
|
||||
def handle_sbva(self, pdata):
|
||||
nibble = (self.cmdstate - 3) % 2
|
||||
if self.cmdstate == 2:
|
||||
self.addr = int(chr(pdata), 16) << 4
|
||||
self.ss_field = self.ss
|
||||
self.putx([Ann.BIT, ['Address high nibble: %c' % pdata,
|
||||
'Addr high 0x%c' % pdata, '0x%c' % pdata]])
|
||||
elif self.cmdstate == 3:
|
||||
self.addr += int(chr(pdata), 16)
|
||||
self.es_field = self.ss
|
||||
self.putx([Ann.BIT, ['Address low nibble: %c' % pdata,
|
||||
'Addr low 0x%c' % pdata, '0x%c' % pdata]])
|
||||
self.putf([Ann.FIELD, ['Address: 0x%02X' % self.addr,
|
||||
'Addr: 0x%02X' % self.addr, '0x%02X' % self.addr]])
|
||||
elif stage == 2:
|
||||
if pdata == 0x00:
|
||||
# Null terminated list
|
||||
self.emit_cmd_end([Ann.SBVA, self.cmd_ann_list()])
|
||||
return
|
||||
self.value = int(chr(pdata), 16) << 4
|
||||
else:
|
||||
self.value += int(chr(pdata), 16)
|
||||
self.es_field = self.es
|
||||
self.putf([Ann.FIELD, ['Value 0x%02X' % self.value,
|
||||
'0x%02X' % self.value]])
|
||||
self.cmdstate += 1
|
||||
|
||||
# Set word value array
|
||||
def handle_swva(self, pdata):
|
||||
nibble = (self.cmdstate - 3) % 4
|
||||
if self.cmdstate == 2:
|
||||
self.addr = int(chr(pdata), 16) << 4
|
||||
self.ss_field = self.ss
|
||||
self.putx([Ann.BIT, ['Address high nibble: %c' % pdata,
|
||||
'Addr high 0x%c' % pdata, '0x%c' % pdata]])
|
||||
elif self.cmdstate == 3:
|
||||
self.addr += int(chr(pdata), 16)
|
||||
self.es_field = self.ss
|
||||
self.putx([Ann.BIT, ['Address low nibble: %c' % pdata,
|
||||
'Addr low 0x%c' % pdata, '0x%c' % pdata]])
|
||||
self.putf([Ann.FIELD, ['Address: 0x%02X' % self.addr,
|
||||
'Addr: 0x%02X' % self.addr, '0x%02X' % self.addr]])
|
||||
self.value = 0
|
||||
else:
|
||||
self.value += int(chr(pdata), 16) << 12 - (4 * nibble)
|
||||
if nibble == 0:
|
||||
if pdata == 0x00:
|
||||
# Null terminated list
|
||||
self.emit_cmd_end([Ann.SWVA, self.cmd_ann_list()])
|
||||
return
|
||||
self.ss_field = self.ss
|
||||
if nibble == 3:
|
||||
self.es_field = self.es
|
||||
self.putf([Ann.FIELD, ['Value 0x%04X' % self.value,
|
||||
'0x%04X' % self.value]])
|
||||
self.cmdstate += 1
|
||||
|
||||
# Set color variable
|
||||
def handle_scv(self, pdata):
|
||||
if self.cmdstate == 8:
|
||||
self.emit_not_implemented([Ann.SCV, self.cmd_ann_list()])
|
||||
self.cmdstate += 1
|
||||
|
||||
# RPC trigger
|
||||
def handle_rpc(self, pdata):
|
||||
self.handle_read(pdata)
|
||||
self.emit_cmd_end([Ann.RPC, self.cmd_ann_list()])
|
||||
|
||||
# Drawing
|
||||
|
||||
# Decode pair of (x,y) 16bit coordinates
|
||||
def decode_coords(self, pdata):
|
||||
if self.cmdstate == 1:
|
||||
self.coords[0] = 0
|
||||
self.coords[1] = 0
|
||||
self.coords[2] = 0
|
||||
self.coords[3] = 0
|
||||
if self.cmdstate < 18:
|
||||
# Coordinates
|
||||
nibble = (self.cmdstate - 1) % 4
|
||||
i = (self.cmdstate - 1) / 4
|
||||
self.coords[i] += int(chr(pdata), 16) << 12 - (4 * nibble)
|
||||
if nibble == 0:
|
||||
self.ss_field = self.ss
|
||||
elif nibble == 3:
|
||||
self.es_field = self.es
|
||||
self.putf([Ann.FIELD, ['Coordinate 0x%04X' % self.coords[i]],
|
||||
['0x%04X' % self.coords[i]]])
|
||||
|
||||
# TODO: There are actually two protocol revisions for drawing.
|
||||
# Both use 4 bytes for 16bit x and y pairs for start and end.
|
||||
# The older follows this by a pattern selector and then line weight.
|
||||
# Newer version has 6 bytes for 8bit RGB color...
|
||||
|
||||
# Draw line
|
||||
def handle_line(self, pdata):
|
||||
decode_coords(pdata)
|
||||
if self.cmdstate == 18:
|
||||
self.es_cmd = self.es
|
||||
self.putc([Ann.LINE, self.cmd_ann_list()])
|
||||
self.putc([Ann.WARN, ['Line pattern / Color not implemented']])
|
||||
self.state = None
|
||||
self.cmdstate += 1
|
||||
|
||||
# Draw rectange
|
||||
def handle_rect(self, pdata):
|
||||
decode_coords(pdata)
|
||||
if self.cmdstate == 18:
|
||||
self.es_cmd = self.es
|
||||
self.putc([Ann.RECT, self.cmd_ann_list()])
|
||||
self.putc([Ann.WARN, ['Line pattern / Color not implemented']])
|
||||
self.state = None
|
||||
self.cmdstate += 1
|
||||
|
||||
# Draw filled rectangle
|
||||
def handle_frect(self, pdata):
|
||||
decode_coords(pdata)
|
||||
if self.cmdstate == 18:
|
||||
self.es_cmd = self.es
|
||||
self.putc([Ann.FRECT, self.cmd_ann_list()])
|
||||
self.putc([Ann.WARN, ['Fill pattern / Color not implemented']])
|
||||
self.state = None
|
||||
self.cmdstate += 1
|
||||
|
||||
# Draw pixel
|
||||
def handle_pixel(self, pdata):
|
||||
self.es_cmd = self.es
|
||||
self.putc([Ann.WARN, ['Draw pixel documentation is missing.', 'Undocumented']])
|
||||
self.state = None
|
||||
|
||||
# Replies
|
||||
def handle_gbvr(self, pdata):
|
||||
self.emit_add_bytes(pdata)
|
||||
if self.cmdstate == 4:
|
||||
self.ss_field = self.ss
|
||||
self.value = int(chr(pdata), 16) << 4
|
||||
self.putx([Ann.BIT, ['High nibble 0x%s' % pdata, '0x%s' % pdata]])
|
||||
elif self.cmdstate == 5:
|
||||
self.value += int(chr(pdata), 16)
|
||||
self.putx([Ann.BIT, ['Low nibble 0x%s' % pdata, '0x%s' % pdata]])
|
||||
self.es_field = self.es
|
||||
self.putf([Ann.FIELD, ['Value: 0x%02X' % self.value,
|
||||
'0x%02X' % self.value]])
|
||||
self.emit_cmd_end([Ann.GBVR, self.cmd_ann_list()])
|
||||
self.cmdstate += 1
|
||||
|
||||
def handle_gwvr(self, pdata):
|
||||
self.emit_add_bytes(pdata)
|
||||
if self.cmdstate > 3:
|
||||
nibble = self.cmdstate - 3
|
||||
if nibble == 0:
|
||||
self.value = 0
|
||||
self.ss_field = self.ss
|
||||
self.value += int(chr(pdata), 16) << 12 - (4 * nibble)
|
||||
self.putx([Ann.BIT, ['0x%s' % pdata]])
|
||||
if nibble == 3:
|
||||
self.putf([Ann.FIELD, ['Value: 0x%04x' % self.value,
|
||||
'0x%04X' % self.value]])
|
||||
self.es_cmd = self.ss
|
||||
self.emit_cmd_end([Ann.GWVR, self.cmd_ann_list()])
|
||||
self.cmdstate += 1
|
||||
|
||||
def handle_gsvr(self, pdata):
|
||||
self.handle_string(pdata, Ann.GSVR)
|
||||
|
||||
def handle_glvr(self, pdata):
|
||||
self.handle_string(pdata, Ann.GLVR)
|
||||
|
||||
def handle_grpcr(self, pdata):
|
||||
self.handle_addr(pdata)
|
||||
if self.cmdstate > 3:
|
||||
nibble = (self.cmdstate - 3) % 2
|
||||
if nibble == 0:
|
||||
if pdata == 0x00:
|
||||
self.emit_cmd_end([Ann.GRPCR, self.cmd_ann_list()])
|
||||
return
|
||||
self.value = int(chr(pdata), 16) << 4
|
||||
self.ss_field = self.ss
|
||||
self.putx([Ann.BIT, ['0x%s' % pdata]])
|
||||
if nibble == 2:
|
||||
self.value += int(chr(pdata), 16)
|
||||
self.es_field = self.es
|
||||
self.putx([Ann.BIT, ['0x%s' % pdata]])
|
||||
self.putf([Ann.FIELD, ['0x%02X' % self.value]])
|
||||
self.cmdstate += 1
|
||||
|
||||
def handle_sbvr(self, pdata):
|
||||
self.handle_set_common(pdata)
|
||||
if self.cmdstate == 4:
|
||||
self.ss_field = self.ss
|
||||
self.value = chr(pdata)
|
||||
elif self.cmdstate == 5:
|
||||
self.value += chr(pdata)
|
||||
self.es_field = self.es
|
||||
self.putf([Ann.FIELD, ['Value: 0x%s' % self.value,
|
||||
'Val: 0x%s' % self.value, '0x%s' % self.value]])
|
||||
self.emit_cmd_end([Ann.SBVR, self.cmd_ann_list()])
|
||||
self.cmdstate += 1
|
||||
|
||||
def handle_swvr(self, pdata):
|
||||
self.handle_set_common(pdata)
|
||||
if self.cmdstate == 4:
|
||||
self.ss_field = self.ss
|
||||
self.value = (pdata - 0x30) << 4
|
||||
elif self.cmdstate == 5:
|
||||
self.value += (pdata - 0x30)
|
||||
self.value = self.value << 8
|
||||
elif self.cmdstate == 6:
|
||||
self.value += (pdata - 0x30) << 4
|
||||
elif self.cmdstate == 7:
|
||||
self.value += (pdata - 0x30)
|
||||
self.es_field = self.es
|
||||
self.putf([Ann.FIELD, ['Value: 0x%04x' % self.value,
|
||||
'Val: 0x%04x' % self.value, '0x%04x' % self.value]])
|
||||
self.emit_cmd_end([Ann.SWVR, self.cmd_ann_list()])
|
||||
self.state = None
|
||||
self.cmdstate += 1
|
||||
|
||||
def handle_ssvr(self, pdata):
|
||||
self.handle_string(pdata, Ann.SSVR)
|
||||
|
||||
def handle_rpcr(self, pdata):
|
||||
self.handle_read(pdata)
|
||||
self.emit_cmd_end([Ann.RPCR, self.cmd_ann_list()])
|
||||
|
||||
def handle_liner(self, pdata):
|
||||
decode_coords(pdata)
|
||||
if self.cmdstate == 18:
|
||||
self.es_cmd = self.es
|
||||
self.putc([Ann.LINER, self.cmd_ann_list()])
|
||||
self.putc([Ann.WARN, ['Line pattern / Color not implemented']])
|
||||
self.state = None
|
||||
self.cmdstate += 1
|
||||
|
||||
def handle_rectr(self, pdata):
|
||||
decode_coords(pdata)
|
||||
if self.cmdstate == 18:
|
||||
self.es_cmd = self.es
|
||||
self.putc([Ann.RECTR, self.cmd_ann_list()])
|
||||
self.putc([Ann.WARN, ['Line pattern / Color not implemented']])
|
||||
self.state = None
|
||||
self.cmdstate += 1
|
||||
|
||||
def handle_frectr(self, pdata):
|
||||
decode_coords(pdata)
|
||||
if self.cmdstate == 18:
|
||||
self.es_cmd = self.es
|
||||
self.putc([Ann.FRECTR, self.cmd_ann_list()])
|
||||
self.putc([Ann.WARN, ['Line pattern / Color not implemented']])
|
||||
self.state = None
|
||||
self.cmdstate += 1
|
||||
|
||||
def handle_pixelr(self, pdata):
|
||||
self.es_cmd = self.es
|
||||
self.putc([Ann.WARN,['Draw pixel documentation is missing.', 'Undocumented']])
|
||||
self.state = None
|
||||
|
||||
def handle_gbvar(self, pdata):
|
||||
nibble = (self.cmdstate - 3) % 2
|
||||
if self.cmdstate == 2:
|
||||
self.addr = int(chr(pdata), 16) << 4
|
||||
self.ss_field = self.ss
|
||||
self.putx([Ann.BIT, ['Address high nibble: %c' % pdata,
|
||||
'Addr high 0x%c' % pdata, '0x%c' % pdata]])
|
||||
elif self.cmdstate == 3:
|
||||
self.addr += int(chr(pdata), 16)
|
||||
self.es_field = self.ss
|
||||
self.putx([Ann.BIT, ['Address low nibble: %c' % pdata,
|
||||
'Addr low 0x%c' % pdata, '0x%c' % pdata]])
|
||||
self.putf([Ann.FIELD, ['Address: 0x%02X' % self.addr,
|
||||
'Addr: 0x%02X' % self.addr, '0x%02X' % self.addr]])
|
||||
elif stage == 2:
|
||||
if pdata == 0x00:
|
||||
# Null terminated list
|
||||
self.emit_cmd_end([Ann.GBVAR, self.cmd_ann_list()])
|
||||
return
|
||||
self.value = int(chr(pdata), 16) << 4
|
||||
else:
|
||||
self.value += int(chr(pdata), 16)
|
||||
self.es_field = self.es
|
||||
self.putf([Ann.FIELD, ['Value 0x%02X' % self.value,
|
||||
'0x%02X' % self.value]])
|
||||
self.cmdstate += 1
|
||||
|
||||
def handle_gwvar(self, pdata):
|
||||
nibble = (self.cmdstate - 3) % 4
|
||||
if self.cmdstate == 2:
|
||||
self.addr = int(chr(pdata), 16) << 4
|
||||
self.ss_field = self.ss
|
||||
self.putx([Ann.BIT, ['Address high nibble: %c' % pdata,
|
||||
'Addr high 0x%c' % pdata, '0x%c' % pdata]])
|
||||
elif self.cmdstate == 3:
|
||||
self.addr += int(chr(pdata), 16)
|
||||
self.es_field = self.ss
|
||||
self.putx([Ann.BIT, ['Address low nibble: %c' % pdata,
|
||||
'Addr low 0x%c' % pdata, '0x%c' % pdata]])
|
||||
self.putf([Ann.FIELD, ['Address: 0x%02X' % self.addr,
|
||||
'Addr: 0x%02X' % self.addr, '0x%02X' % self.addr]])
|
||||
self.value = 0
|
||||
else:
|
||||
self.value += int(chr(pdata), 16) << 12 - (4 * nibble)
|
||||
if nibble == 0:
|
||||
if pdata == 0x00:
|
||||
# Null terminated list
|
||||
self.emit_cmd_end([Ann.GWVAR, self.cmd_ann_list()])
|
||||
return
|
||||
self.ss_field = self.ss
|
||||
if nibble == 3:
|
||||
self.es_field = self.es
|
||||
self.putf([Ann.FIELD, ['Value 0x%04X' % self.value,
|
||||
'0x%04X' % self.value]])
|
||||
self.cmdstate += 1
|
||||
|
||||
# Get byte variable array reply
|
||||
def handle_sbvar(self, pdata):
|
||||
nibble = (self.cmdstate - 3) % 2
|
||||
if self.cmdstate == 2:
|
||||
self.addr = int(chr(pdata), 16) << 4
|
||||
self.ss_field = self.ss
|
||||
self.putx([Ann.BIT, ['Address high nibble: %c' % pdata,
|
||||
'Addr high 0x%c' % pdata, '0x%c' % pdata]])
|
||||
elif self.cmdstate == 3:
|
||||
self.addr += int(chr(pdata), 16)
|
||||
self.es_field = self.ss
|
||||
self.putx([Ann.BIT, ['Address low nibble: %c' % pdata,
|
||||
'Addr low 0x%c' % pdata, '0x%c' % pdata]])
|
||||
self.putf([Ann.FIELD, ['Address: 0x%02X' % self.addr,
|
||||
'Addr: 0x%02X' % self.addr, '0x%02X' % self.addr]])
|
||||
elif stage == 2:
|
||||
if pdata == 0x00:
|
||||
# Null terminated list
|
||||
self.emit_cmd_end([Ann.SBVAR, self.cmd_ann_list()])
|
||||
return
|
||||
self.value = int(chr(pdata), 16) << 4
|
||||
else:
|
||||
self.value += int(chr(pdata), 16)
|
||||
self.es_field = self.es
|
||||
self.putf([Ann.FIELD, ['Value 0x%02X' % self.value,
|
||||
'0x%02X' % self.value]])
|
||||
self.cmdstate += 1
|
||||
|
||||
# Set word variable array reply
|
||||
def handle_swvar(self, pdata):
|
||||
nibble = (self.cmdstate - 3) % 4
|
||||
if self.cmdstate == 2:
|
||||
self.addr = int(chr(pdata), 16) << 4
|
||||
self.ss_field = self.ss
|
||||
self.putx([Ann.BIT, ['Address high nibble: %c' % pdata,
|
||||
'Addr high 0x%c' % pdata, '0x%c' % pdata]])
|
||||
elif self.cmdstate == 3:
|
||||
self.addr += int(chr(pdata), 16)
|
||||
self.es_field = self.ss
|
||||
self.putx([Ann.BIT, ['Address low nibble: %c' % pdata,
|
||||
'Addr low 0x%c' % pdata, '0x%c' % pdata]])
|
||||
self.putf([Ann.FIELD, ['Address: 0x%02X' % self.addr,
|
||||
'Addr: 0x%02X' % self.addr, '0x%02X' % self.addr]])
|
||||
self.value = 0
|
||||
else:
|
||||
self.value += int(chr(pdata), 16) << 12 - (4 * nibble)
|
||||
if nibble == 0:
|
||||
if pdata == 0x00:
|
||||
# Null terminated list
|
||||
self.emit_cmd_end([Ann.SWVAR, self.cmd_ann_list()])
|
||||
return
|
||||
self.ss_field = self.ss
|
||||
if nibble == 3:
|
||||
self.es_field = self.es
|
||||
self.putf([Ann.FIELD, ['Value 0x%04X' % self.value,
|
||||
'0x%04X' % self.value]])
|
||||
self.cmdstate += 1
|
||||
|
||||
def handle_gcvr(self, pdata):
|
||||
if self.cmdstate == 8:
|
||||
self.emit_not_implemented([Ann.SCV, self.cmd_ann_list()])
|
||||
self.cmdstate += 1
|
||||
|
||||
def handle_scvr(self, pdata):
|
||||
if self.cmdstate == 8:
|
||||
self.emit_not_implemented([Ann.SCV, self.cmd_ann_list()])
|
||||
self.cmdstate += 1
|
||||
|
||||
# ACK & NACK
|
||||
|
||||
def handle_ack(self, pdata):
|
||||
self.putx([Ann.ACK, self.cmd_ann_list()])
|
||||
self.state = None
|
||||
|
||||
def handle_nack(self, pdata):
|
||||
self.putx([Ann.NACK, self.cmd_ann_list()])
|
||||
self.state = None
|
||||
|
||||
def decode(self, ss, es, data):
|
||||
ptype, rxtx, pdata = data
|
||||
|
||||
self.ss, self.es = ss, es
|
||||
|
||||
if ptype != 'DATA':
|
||||
return
|
||||
|
||||
# Handle commands.
|
||||
try:
|
||||
abort_current = (0xD0 <= pdata[0] <= 0xF7) and \
|
||||
(not (self.state in cmds_with_high_bytes)) and \
|
||||
self.state != None
|
||||
if abort_current:
|
||||
self.putx([Ann.WARN, ['Command aborted by invalid byte', 'Abort']])
|
||||
self.state = pdata[0]
|
||||
self.emit_cmd_byte()
|
||||
self.cmdstate = 1
|
||||
if self.state is None:
|
||||
self.state = pdata[0]
|
||||
self.emit_cmd_byte()
|
||||
self.cmdstate = 1
|
||||
self.cmd_handlers[self.state](pdata[0])
|
||||
except KeyError:
|
||||
self.putx([Ann.WARN, ['Unknown command: 0x%02x' % pdata[0]]])
|
||||
self.state = None
|
||||
36
libsigrokdecode4DSL/decoders/caliper/__init__.py
Normal file
36
libsigrokdecode4DSL/decoders/caliper/__init__.py
Normal file
@@ -0,0 +1,36 @@
|
||||
##
|
||||
## This file is part of the libsigrokdecode project.
|
||||
##
|
||||
## Copyright (C) 2020 Tomas Mudrunka <harvie@github>
|
||||
##
|
||||
## 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
|
||||
## the Free Software Foundation; either version 2 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
'''
|
||||
This decoder interprets the digital output of cheap generic calipers
|
||||
(usually made in China), and shows the measured value in millimeters
|
||||
or inches.
|
||||
|
||||
Notice that these devices often communicate on voltage levels below
|
||||
3.3V and may require additional circuitry to capture the signal.
|
||||
|
||||
This decoder does not work for calipers using the Digimatic protocol
|
||||
(eg. Mitutoyo and similar brands).
|
||||
|
||||
For more information see:
|
||||
http://www.shumatech.com/support/chinese_scales.htm
|
||||
https://www.instructables.com/id/Reading-Digital-Callipers-with-an-Arduino-USB/
|
||||
'''
|
||||
|
||||
from .pd import Decoder
|
||||
146
libsigrokdecode4DSL/decoders/caliper/pd.py
Normal file
146
libsigrokdecode4DSL/decoders/caliper/pd.py
Normal file
@@ -0,0 +1,146 @@
|
||||
##
|
||||
## This file is part of the libsigrokdecode project.
|
||||
##
|
||||
## Copyright (C) 2020 Tomas Mudrunka <harvie@github>
|
||||
##
|
||||
## Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
## of this software and associated documentation files (the "Software"), to deal
|
||||
## in the Software without restriction, including without limitation the rights
|
||||
## to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
## copies of the Software, and to permit persons to whom the Software is
|
||||
## furnished to do so, subject to the following conditions:
|
||||
##
|
||||
## The above copyright notice and this permission notice shall be included in all
|
||||
## copies or substantial portions of the Software.
|
||||
##
|
||||
## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
## IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
## FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
## AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
## LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
## OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
## SOFTWARE.
|
||||
|
||||
import sigrokdecode as srd
|
||||
from common.srdhelper import bitpack
|
||||
|
||||
# Millimeters per inch.
|
||||
mm_per_inch = 25.4
|
||||
|
||||
class Decoder(srd.Decoder):
|
||||
api_version = 3
|
||||
id = 'caliper'
|
||||
name = 'Caliper'
|
||||
longname = 'Digital calipers'
|
||||
desc = 'Protocol of cheap generic digital calipers.'
|
||||
license = 'mit'
|
||||
inputs = ['logic']
|
||||
outputs = []
|
||||
channels = (
|
||||
{'id': 'clk', 'name': 'CLK', 'desc': 'Serial clock line'},
|
||||
{'id': 'data', 'name': 'DATA', 'desc': 'Serial data line'},
|
||||
)
|
||||
options = (
|
||||
{'id': 'timeout_ms', 'desc': 'Packet timeout in ms, 0 to disable',
|
||||
'default': 10},
|
||||
{'id': 'unit', 'desc': 'Convert units', 'default': 'keep',
|
||||
'values': ('keep', 'mm', 'inch')},
|
||||
{'id': 'changes', 'desc': 'Changes only', 'default': 'no',
|
||||
'values': ('no', 'yes')},
|
||||
)
|
||||
tags = ['Analog/digital', 'Sensor']
|
||||
annotations = (
|
||||
('measurement', 'Measurement'),
|
||||
('warning', 'Warning'),
|
||||
)
|
||||
annotation_rows = (
|
||||
('measurements', 'Measurements', (0,)),
|
||||
('warnings', 'Warnings', (1,)),
|
||||
)
|
||||
|
||||
def metadata(self, key, value):
|
||||
if key == srd.SRD_CONF_SAMPLERATE:
|
||||
self.samplerate = value
|
||||
|
||||
def __init__(self):
|
||||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
self.ss, self.es = 0, 0
|
||||
self.number_bits = []
|
||||
self.flags_bits = []
|
||||
|
||||
def start(self):
|
||||
self.out_ann = self.register(srd.OUTPUT_ANN)
|
||||
|
||||
def putg(self, ss, es, cls, data):
|
||||
self.put(ss, es, self.out_ann, [cls, data])
|
||||
|
||||
def decode(self):
|
||||
last_sent = None
|
||||
timeout_ms = self.options['timeout_ms']
|
||||
want_unit = self.options['unit']
|
||||
show_all = self.options['changes'] == 'no'
|
||||
wait_cond = [{0: 'r'}]
|
||||
if timeout_ms:
|
||||
snum_per_ms = self.samplerate / 1000
|
||||
timeout_snum = timeout_ms * snum_per_ms
|
||||
wait_cond.append({'skip': round(timeout_snum)})
|
||||
while True:
|
||||
# Sample data at the rising clock edge. Optionally timeout
|
||||
# after inactivity for a user specified period. Present the
|
||||
# number of unprocessed bits to the user for diagnostics.
|
||||
clk, data = self.wait(wait_cond)
|
||||
if timeout_ms and not self.matched[0]:
|
||||
if self.number_bits or self.flags_bits:
|
||||
count = len(self.number_bits) + len(self.flags_bits)
|
||||
self.putg(self.ss, self.samplenum, 1, [
|
||||
'timeout with {} bits in buffer'.format(count),
|
||||
'timeout ({} bits)'.format(count),
|
||||
'timeout',
|
||||
])
|
||||
self.reset()
|
||||
continue
|
||||
|
||||
# Store position of first bit and last activity.
|
||||
# Shift in measured number and flag bits.
|
||||
if not self.ss:
|
||||
self.ss = self.samplenum
|
||||
self.es = self.samplenum
|
||||
if len(self.number_bits) < 16:
|
||||
self.number_bits.append(data)
|
||||
continue
|
||||
if len(self.flags_bits) < 8:
|
||||
self.flags_bits.append(data)
|
||||
if len(self.flags_bits) < 8:
|
||||
continue
|
||||
|
||||
# Get raw values from received data bits. Run the number
|
||||
# conversion, controlled by flags and/or user specs.
|
||||
negative = bool(self.flags_bits[4])
|
||||
is_inch = bool(self.flags_bits[7])
|
||||
number = bitpack(self.number_bits)
|
||||
if negative:
|
||||
number = -number
|
||||
if is_inch:
|
||||
number /= 2000
|
||||
if want_unit == 'mm':
|
||||
number *= mm_per_inch
|
||||
is_inch = False
|
||||
else:
|
||||
number /= 100
|
||||
if want_unit == 'inch':
|
||||
number = round(number / mm_per_inch, 4)
|
||||
is_inch = True
|
||||
unit = 'in' if is_inch else 'mm'
|
||||
|
||||
# Construct and emit an annotation.
|
||||
if show_all or (number, unit) != last_sent:
|
||||
self.putg(self.ss, self.es, 0, [
|
||||
'{number}{unit}'.format(**locals()),
|
||||
'{number}'.format(**locals()),
|
||||
])
|
||||
last_sent = (number, unit)
|
||||
|
||||
# Reset internal state for the start of the next packet.
|
||||
self.reset()
|
||||
32
libsigrokdecode4DSL/decoders/flexray/__init__.py
Normal file
32
libsigrokdecode4DSL/decoders/flexray/__init__.py
Normal file
@@ -0,0 +1,32 @@
|
||||
##
|
||||
## This file is part of the libsigrokdecode project.
|
||||
##
|
||||
## Copyright (C) 2019 Stephan Thiele <stephan.thiele@mailbox.org>
|
||||
##
|
||||
## 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
|
||||
## the Free Software Foundation; either version 2 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
'''
|
||||
FlexRay is a fast, deterministic and fault-tolerant fieldbus system
|
||||
which is used in cars in high security related areas like X-by-Wire.
|
||||
|
||||
It is the result of the FlexRay consortium which consisted of BMW,
|
||||
Daimler, Motorola (today Freescale) and Philips, with the goal of
|
||||
working out a common standard automotive bus system.
|
||||
|
||||
This decoder assumes that at least one channel of a logic level RX line
|
||||
of a transceiver is sampled (e.g. NXP TJA1080).
|
||||
'''
|
||||
|
||||
from .pd import Decoder
|
||||
413
libsigrokdecode4DSL/decoders/flexray/pd.py
Normal file
413
libsigrokdecode4DSL/decoders/flexray/pd.py
Normal file
@@ -0,0 +1,413 @@
|
||||
##
|
||||
## This file is part of the libsigrokdecode project.
|
||||
##
|
||||
## Copyright (C) 2019 Stephan Thiele <stephan.thiele@mailbox.org>
|
||||
##
|
||||
## 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
|
||||
## the Free Software Foundation; either version 2 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
import sigrokdecode as srd
|
||||
|
||||
# Selection of constants as defined in FlexRay specification 3.0.1 Chapter A.1:
|
||||
class Const:
|
||||
cChannelIdleDelimiter = 11
|
||||
cCrcInitA = 0xFEDCBA
|
||||
cCrcInitB = 0xABCDEF
|
||||
cCrcPolynomial = 0x5D6DCB
|
||||
cCrcSize = 24
|
||||
cCycleCountMax = 63
|
||||
cdBSS = 2
|
||||
cdCAS = 30
|
||||
cdFES = 2
|
||||
cdFSS = 1
|
||||
cHCrcInit = 0x01A
|
||||
cHCrcPolynomial = 0x385
|
||||
cHCrcSize = 11
|
||||
cSamplesPerBit = 8
|
||||
cSlotIDMax = 2047
|
||||
cStaticSlotIDMax = 1023
|
||||
cVotingSamples = 5
|
||||
|
||||
class SamplerateError(Exception):
|
||||
pass
|
||||
|
||||
class Decoder(srd.Decoder):
|
||||
api_version = 3
|
||||
id = 'flexray'
|
||||
name = 'FlexRay'
|
||||
longname = 'FlexRay'
|
||||
desc = 'Automotive network communications protocol.'
|
||||
license = 'gplv2+'
|
||||
inputs = ['logic']
|
||||
outputs = []
|
||||
tags = ['Automotive']
|
||||
channels = (
|
||||
{'id': 'channel', 'name': 'Channel', 'desc': 'FlexRay bus channel'},
|
||||
)
|
||||
options = (
|
||||
{'id': 'channel_type', 'desc': 'Channel type', 'default': 'A',
|
||||
'values': ('A', 'B')},
|
||||
{'id': 'bitrate', 'desc': 'Bitrate (bit/s)', 'default': 10000000,
|
||||
'values': (10000000, 5000000, 2500000)},
|
||||
)
|
||||
annotations = (
|
||||
('data', 'FlexRay payload data'),
|
||||
('tss', 'Transmission start sequence'),
|
||||
('fss', 'Frame start sequence'),
|
||||
('reserved-bit', 'Reserved bit'),
|
||||
('ppi', 'Payload preamble indicator'),
|
||||
('null-frame', 'Nullframe indicator'),
|
||||
('sync-frame', 'Full identifier'),
|
||||
('startup-frame', 'Startup frame indicator'),
|
||||
('id', 'Frame ID'),
|
||||
('length', 'Data length'),
|
||||
('header-crc', 'Header CRC'),
|
||||
('cycle', 'Cycle code'),
|
||||
('data-byte', 'Data byte'),
|
||||
('frame-crc', 'Frame CRC'),
|
||||
('fes', 'Frame end sequence'),
|
||||
('bss', 'Byte start sequence'),
|
||||
('warning', 'Warning'),
|
||||
('bit', 'Bit'),
|
||||
('cid', 'Channel idle delimiter'),
|
||||
('dts', 'Dynamic trailing sequence'),
|
||||
('cas', 'Collision avoidance symbol'),
|
||||
)
|
||||
annotation_rows = (
|
||||
('bits', 'Bits', (15, 17)),
|
||||
('fields', 'Fields', tuple(range(15)) + (18, 19, 20)),
|
||||
('warnings', 'Warnings', (16,)),
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
self.samplerate = None
|
||||
self.reset_variables()
|
||||
|
||||
def start(self):
|
||||
self.out_ann = self.register(srd.OUTPUT_ANN)
|
||||
|
||||
def metadata(self, key, value):
|
||||
if key == srd.SRD_CONF_SAMPLERATE:
|
||||
bitrate = float(self.options['bitrate'])
|
||||
self.samplerate = value
|
||||
self.bit_width = float(self.samplerate) / bitrate
|
||||
self.sample_point = (self.bit_width / 100.0) * self.sample_point_percent
|
||||
|
||||
# Generic helper for FlexRay bit annotations.
|
||||
def putg(self, ss, es, data):
|
||||
left, right = int(self.sample_point), int(self.bit_width - self.sample_point)
|
||||
self.put(ss - left, es + right, self.out_ann, data)
|
||||
|
||||
# Single-FlexRay-bit annotation using the current samplenum.
|
||||
def putx(self, data):
|
||||
self.putg(self.samplenum, self.samplenum, data)
|
||||
|
||||
# Multi-FlexRay-bit annotation from self.ss_block to current samplenum.
|
||||
def putb(self, data):
|
||||
self.putg(self.ss_block, self.samplenum, data)
|
||||
|
||||
# Generic CRC algorithm for any bit size and any data length. Used for
|
||||
# 11-bit header and 24-bit trailer. Not very efficient but at least it
|
||||
# works for now.
|
||||
#
|
||||
# TODO:
|
||||
# - use precalculated tables to increase performance.
|
||||
# - Add support for reverse CRC calculations.
|
||||
|
||||
@staticmethod
|
||||
def crc(data, data_len_bits, polynom, crc_len_bits, iv=0, xor=0):
|
||||
reg = iv ^ xor
|
||||
|
||||
for i in range(data_len_bits - 1, -1, -1):
|
||||
bit = ((reg >> (crc_len_bits - 1)) & 0x1) ^ ((data >> i) & 0x1)
|
||||
reg <<= 1
|
||||
if bit:
|
||||
reg ^= polynom
|
||||
|
||||
mask = (1 << crc_len_bits) - 1
|
||||
crc = reg & mask
|
||||
|
||||
return crc ^ xor
|
||||
|
||||
def reset_variables(self):
|
||||
self.sample_point_percent = 50 # TODO: use vote based sampling
|
||||
self.state = 'IDLE'
|
||||
self.tss_start = self.tss_end = self.frame_type = self.dlc = None
|
||||
self.rawbits = [] # All bits, including byte start sequence bits
|
||||
self.bits = [] # Only actual FlexRay frame bits (no byte start sequence bits)
|
||||
self.curbit = 0 # Current bit of FlexRay frame (bit 0 == FSS)
|
||||
self.last_databit = 999 # Positive value that bitnum+x will never match
|
||||
self.last_xmit_bit = 999 # Positive value that bitnum+x will never match
|
||||
self.ss_block = None
|
||||
self.ss_databytebits = []
|
||||
self.end_of_frame = False
|
||||
self.dynamic_frame = False
|
||||
self.ss_bit0 = None
|
||||
self.ss_bit1 = None
|
||||
self.ss_bit2 = None
|
||||
|
||||
# Poor man's clock synchronization. Use signal edges which change to
|
||||
# dominant state in rather simple ways. This naive approach is neither
|
||||
# aware of the SYNC phase's width nor the specific location of the edge,
|
||||
# but improves the decoder's reliability when the input signal's bitrate
|
||||
# does not exactly match the nominal rate.
|
||||
def dom_edge_seen(self, force=False):
|
||||
self.dom_edge_snum = self.samplenum
|
||||
self.dom_edge_bcount = self.curbit
|
||||
|
||||
# Determine the position of the next desired bit's sample point.
|
||||
def get_sample_point(self, bitnum):
|
||||
samplenum = self.dom_edge_snum
|
||||
samplenum += self.bit_width * (bitnum - self.dom_edge_bcount)
|
||||
samplenum += self.sample_point
|
||||
return int(samplenum)
|
||||
|
||||
def is_bss_sequence(self):
|
||||
# FlexRay uses NRZ encoding and adds a binary 10 sequence before each
|
||||
# byte. After each 8 data bits, a BSS sequence is added but not after
|
||||
# frame CRC.
|
||||
|
||||
if self.end_of_frame:
|
||||
return False
|
||||
|
||||
if (len(self.rawbits) - 2) % 10 == 0:
|
||||
return True
|
||||
elif (len(self.rawbits) - 3) % 10 == 0:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def handle_bit(self, fr_rx):
|
||||
self.rawbits.append(fr_rx)
|
||||
self.bits.append(fr_rx)
|
||||
|
||||
# Get the index of the current FlexRay frame bit.
|
||||
bitnum = len(self.bits) - 1
|
||||
|
||||
# If this is a byte start sequence remove it from self.bits and ignore it.
|
||||
if self.is_bss_sequence():
|
||||
self.bits.pop()
|
||||
|
||||
if bitnum > 1:
|
||||
self.putx([15, [str(fr_rx)]])
|
||||
else:
|
||||
if len(self.rawbits) == 2:
|
||||
self.ss_bit1 = self.samplenum
|
||||
elif len(self.rawbits) == 3:
|
||||
self.ss_bit2 = self.samplenum
|
||||
|
||||
self.curbit += 1 # Increase self.curbit (bitnum is not affected).
|
||||
return
|
||||
else:
|
||||
if bitnum > 1:
|
||||
self.putx([17, [str(fr_rx)]])
|
||||
|
||||
# Bit 0: Frame start sequence (FSS) bit
|
||||
if bitnum == 0:
|
||||
self.ss_bit0 = self.samplenum
|
||||
|
||||
# Bit 1: Start of header
|
||||
elif bitnum == 1:
|
||||
if self.rawbits[:3] == [1, 1, 0]:
|
||||
self.put(self.tss_start, self.tss_end, self.out_ann,
|
||||
[1, ['Transmission start sequence', 'TSS']])
|
||||
|
||||
self.putg(self.ss_bit0, self.ss_bit0, [17, [str(self.rawbits[:3][0])]])
|
||||
self.putg(self.ss_bit0, self.ss_bit0, [2, ['FSS', 'Frame start sequence']])
|
||||
self.putg(self.ss_bit1, self.ss_bit1, [15, [str(self.rawbits[:3][1])]])
|
||||
self.putg(self.ss_bit2, self.ss_bit2, [15, [str(self.rawbits[:3][2])]])
|
||||
self.putx([17, [str(fr_rx)]])
|
||||
self.putx([3, ['Reserved bit: %d' % fr_rx, 'RB: %d' % fr_rx, 'RB']])
|
||||
else:
|
||||
self.put(self.tss_start, self.tss_end, self.out_ann,
|
||||
[20, ['Collision avoidance symbol', 'CAS']])
|
||||
self.reset_variables()
|
||||
|
||||
# TODO: warning, if sequence is neither [1, 1, 0] nor [1, 1, 1]
|
||||
|
||||
# Bit 2: Payload preamble indicator. Must be 0 if null frame indicator is 0.
|
||||
elif bitnum == 2:
|
||||
self.putx([4, ['Payload preamble indicator: %d' % fr_rx,
|
||||
'PPI: %d' % fr_rx]])
|
||||
|
||||
# Bit 3: Null frame indicator (inversed)
|
||||
elif bitnum == 3:
|
||||
data_type = 'data frame' if fr_rx else 'null frame'
|
||||
self.putx([5, ['Null frame indicator: %s' % data_type,
|
||||
'NF: %d' % fr_rx, 'NF']])
|
||||
|
||||
# Bit 4: Sync frame indicator
|
||||
# Must be 1 if startup frame indicator is 1.
|
||||
elif bitnum == 4:
|
||||
self.putx([6, ['Sync frame indicator: %d' % fr_rx,
|
||||
'Sync: %d' % fr_rx, 'Sync']])
|
||||
|
||||
# Bit 5: Startup frame indicator
|
||||
elif bitnum == 5:
|
||||
self.putx([7, ['Startup frame indicator: %d' % fr_rx,
|
||||
'Startup: %d' % fr_rx, 'Startup']])
|
||||
|
||||
# Remember start of ID (see below).
|
||||
elif bitnum == 6:
|
||||
self.ss_block = self.samplenum
|
||||
|
||||
# Bits 6-16: Frame identifier (ID[10..0])
|
||||
# ID must NOT be 0.
|
||||
elif bitnum == 16:
|
||||
self.id = int(''.join(str(d) for d in self.bits[6:]), 2)
|
||||
self.putb([8, ['Frame ID: %d' % self.id, 'ID: %d' % self.id,
|
||||
'%d' % self.id]])
|
||||
|
||||
# Remember start of payload length (see below).
|
||||
elif bitnum == 17:
|
||||
self.ss_block = self.samplenum
|
||||
|
||||
# Bits 17-23: Payload length (Length[7..0])
|
||||
# Payload length in header is the half of the real payload size.
|
||||
elif bitnum == 23:
|
||||
self.payload_length = int(''.join(str(d) for d in self.bits[17:]), 2)
|
||||
self.putb([9, ['Payload length: %d' % self.payload_length,
|
||||
'Length: %d' % self.payload_length,
|
||||
'%d' % self.payload_length]])
|
||||
|
||||
# Remember start of header CRC (see below).
|
||||
elif bitnum == 24:
|
||||
self.ss_block = self.samplenum
|
||||
|
||||
# Bits 24-34: Header CRC (11-bit) (HCRC[11..0])
|
||||
# Calculation of header CRC is equal on both channels.
|
||||
elif bitnum == 34:
|
||||
bits = ''.join([str(b) for b in self.bits[4:24]])
|
||||
header_to_check = int(bits, 2)
|
||||
expected_crc = self.crc(header_to_check, len(bits),
|
||||
Const.cHCrcPolynomial, Const.cHCrcSize, Const.cHCrcInit)
|
||||
self.header_crc = int(''.join(str(d) for d in self.bits[24:]), 2)
|
||||
|
||||
crc_ok = self.header_crc == expected_crc
|
||||
crc_ann = "OK" if crc_ok else "bad"
|
||||
|
||||
self.putb([10, ['Header CRC: 0x%X (%s)' % (self.header_crc, crc_ann),
|
||||
'0x%X (%s)' % (self.header_crc, crc_ann),
|
||||
'0x%X' % self.header_crc]])
|
||||
|
||||
# Remember start of cycle code (see below).
|
||||
elif bitnum == 35:
|
||||
self.ss_block = self.samplenum
|
||||
|
||||
# Bits 35-40: Cycle code (Cyc[6..0])
|
||||
# Cycle code. Must be between 0 and 63.
|
||||
elif bitnum == 40:
|
||||
self.cycle = int(''.join(str(d) for d in self.bits[35:]), 2)
|
||||
self.putb([11, ['Cycle: %d' % self.cycle, 'Cyc: %d' % self.cycle,
|
||||
'%d' % self.cycle]])
|
||||
self.last_databit = 41 + 2 * self.payload_length * 8
|
||||
|
||||
# Remember all databyte bits, except the very last one.
|
||||
elif bitnum in range(41, self.last_databit):
|
||||
self.ss_databytebits.append(self.samplenum)
|
||||
|
||||
# Bits 41-X: Data field (0-254 bytes, depending on length)
|
||||
# The bits within a data byte are transferred MSB-first.
|
||||
elif bitnum == self.last_databit:
|
||||
self.ss_databytebits.append(self.samplenum) # Last databyte bit.
|
||||
for i in range(2 * self.payload_length):
|
||||
x = 40 + (8 * i) + 1
|
||||
b = int(''.join(str(d) for d in self.bits[x:x + 8]), 2)
|
||||
ss = self.ss_databytebits[i * 8]
|
||||
es = self.ss_databytebits[((i + 1) * 8) - 1]
|
||||
self.putg(ss, es, [12, ['Data byte %d: 0x%02x' % (i, b),
|
||||
'DB%d: 0x%02x' % (i, b), '%02X' % b]])
|
||||
self.ss_databytebits = []
|
||||
self.ss_block = self.samplenum # Remember start of trailer CRC.
|
||||
|
||||
# Trailer CRC (24-bit) (CRC[11..0])
|
||||
# Initialization vector of channel A and B are different, so CRCs are
|
||||
# different for same data.
|
||||
elif bitnum == self.last_databit + 23:
|
||||
bits = ''.join([str(b) for b in self.bits[1:-24]])
|
||||
frame_to_check = int(bits, 2)
|
||||
iv = Const.cCrcInitA if self.options['channel_type'] == 'A' else Const.cCrcInitB
|
||||
expected_crc = self.crc(frame_to_check, len(bits),
|
||||
Const.cCrcPolynomial, Const.cCrcSize, iv=iv)
|
||||
self.frame_crc = int(''.join(str(d) for d in self.bits[self.last_databit:]), 2)
|
||||
|
||||
crc_ok = self.frame_crc == expected_crc
|
||||
crc_ann = "OK" if crc_ok else "bad"
|
||||
|
||||
self.putb([13, ['Frame CRC: 0x%X (%s)' % (self.frame_crc, crc_ann),
|
||||
'0x%X (%s)' % (self.frame_crc, crc_ann),
|
||||
'0x%X' % self.frame_crc]])
|
||||
self.end_of_frame = True
|
||||
|
||||
# Remember start of frame end sequence (see below).
|
||||
elif bitnum == self.last_databit + 24:
|
||||
self.ss_block = self.samplenum
|
||||
|
||||
# Frame end sequence, must be 1 followed by 0.
|
||||
elif bitnum == self.last_databit + 25:
|
||||
self.putb([14, ['Frame end sequence', 'FES']])
|
||||
|
||||
# Check for DTS
|
||||
elif bitnum == self.last_databit + 26:
|
||||
if not fr_rx:
|
||||
self.dynamic_frame = True
|
||||
else:
|
||||
self.last_xmit_bit = bitnum
|
||||
self.ss_block = self.samplenum
|
||||
|
||||
# Remember start of channel idle delimiter (see below).
|
||||
elif bitnum == self.last_xmit_bit:
|
||||
self.ss_block = self.samplenum
|
||||
|
||||
# Channel idle limiter (CID[11..0])
|
||||
elif bitnum == self.last_xmit_bit + Const.cChannelIdleDelimiter - 1:
|
||||
self.putb([18, ['Channel idle delimiter', 'CID']])
|
||||
self.reset_variables()
|
||||
|
||||
# DTS if dynamic frame
|
||||
elif bitnum > self.last_databit + 27:
|
||||
if self.dynamic_frame:
|
||||
if fr_rx:
|
||||
if self.last_xmit_bit == 999:
|
||||
self.putb([19, ['Dynamic trailing sequence', 'DTS']])
|
||||
self.last_xmit_bit = bitnum + 1
|
||||
self.ss_block = self.samplenum
|
||||
|
||||
self.curbit += 1
|
||||
|
||||
def decode(self):
|
||||
if not self.samplerate:
|
||||
raise SamplerateError('Cannot decode without samplerate.')
|
||||
|
||||
while True:
|
||||
# State machine.
|
||||
if self.state == 'IDLE':
|
||||
# Wait for a dominant state (logic 0) on the bus.
|
||||
(fr_rx,) = self.wait({0: 'l'})
|
||||
self.tss_start = self.samplenum
|
||||
(fr_rx,) = self.wait({0: 'h'})
|
||||
self.tss_end = self.samplenum
|
||||
self.dom_edge_seen(force = True)
|
||||
self.state = 'GET BITS'
|
||||
elif self.state == 'GET BITS':
|
||||
# Wait until we're in the correct bit/sampling position.
|
||||
pos = self.get_sample_point(self.curbit)
|
||||
(fr_rx,) = self.wait([{'skip': pos - self.samplenum}, {0: 'f'}])
|
||||
if self.matched[1]:
|
||||
self.dom_edge_seen()
|
||||
if self.matched[0]:
|
||||
self.handle_bit(fr_rx)
|
||||
27
libsigrokdecode4DSL/decoders/hdcp/__init__.py
Normal file
27
libsigrokdecode4DSL/decoders/hdcp/__init__.py
Normal file
@@ -0,0 +1,27 @@
|
||||
##
|
||||
## This file is part of the libsigrokdecode project.
|
||||
##
|
||||
## Copyright (C) 2018 Dave Craig <dcraig@brightsign.biz>
|
||||
##
|
||||
## 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
|
||||
## the Free Software Foundation; either version 2 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
'''
|
||||
This decoder stacks on top of the 'i2c' PD and decodes HDCP messages.
|
||||
|
||||
Details:
|
||||
https://www.digital-cp.com/sites/default/files/specifications/HDCP%20on%20HDMI%20Specification%20Rev2_2_Final1.pdf
|
||||
'''
|
||||
|
||||
from .pd import Decoder
|
||||
191
libsigrokdecode4DSL/decoders/hdcp/pd.py
Normal file
191
libsigrokdecode4DSL/decoders/hdcp/pd.py
Normal file
@@ -0,0 +1,191 @@
|
||||
##
|
||||
## This file is part of the libsigrokdecode project.
|
||||
##
|
||||
## Copyright (C) 2018 Dave Craig <dcraig@brightsign.biz>
|
||||
##
|
||||
## 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
|
||||
## the Free Software Foundation; either version 2 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
import sigrokdecode as srd
|
||||
|
||||
msg_ids = {
|
||||
2: 'AKE_Init',
|
||||
3: 'AKE_Send_Cert',
|
||||
4: 'AKE_No_stored_km',
|
||||
5: 'AKE_Stored_km',
|
||||
|
||||
7: 'AKE_Send_H_prime',
|
||||
8: 'AKE_Send_Pairing_Info',
|
||||
|
||||
9: 'LC_Init',
|
||||
10: 'LC_Send_L_prime',
|
||||
|
||||
11: 'SKE_Send_Eks',
|
||||
12: 'RepeaterAuth_Send_ReceiverID_List',
|
||||
|
||||
15: 'RepeaterAuth_Send_Ack',
|
||||
16: 'RepeaterAuth_Stream_Manage',
|
||||
17: 'RepeaterAuth_Stream_Ready',
|
||||
}
|
||||
|
||||
write_items = {
|
||||
0x00: '1.4 Bksv - Receiver KSV',
|
||||
0x08: '1.4 Ri\' - Link Verification',
|
||||
0x0a: '1.4 Pj\' - Enhanced Link Verification',
|
||||
0x10: '1.4 Aksv - Transmitter KSV',
|
||||
0x15: '1.4 Ainfo - Transmitter KSV',
|
||||
0x18: '1.4 An - Session random number',
|
||||
0x20: '1.4 V\'H0',
|
||||
0x24: '1.4 V\'H1',
|
||||
0x28: '1.4 V\'H2',
|
||||
0x2c: '1.4 V\'H3',
|
||||
0x30: '1.4 V\'H4',
|
||||
0x40: '1.4 Bcaps',
|
||||
0x41: '1.4 Bstatus',
|
||||
0x43: '1.4 KSV FIFO',
|
||||
0x50: 'HDCP2Version',
|
||||
0x60: 'Write_Message',
|
||||
0x70: 'RxStatus',
|
||||
0x80: 'Read_Message',
|
||||
}
|
||||
|
||||
class Decoder(srd.Decoder):
|
||||
api_version = 3
|
||||
id = 'hdcp'
|
||||
name = 'HDCP'
|
||||
longname = 'HDCP over HDMI'
|
||||
desc = 'HDCP protocol over HDMI.'
|
||||
license = 'gplv2+'
|
||||
inputs = ['i2c']
|
||||
outputs = ['hdcp']
|
||||
tags = ['PC', 'Security/crypto']
|
||||
annotations = \
|
||||
tuple(('message-0x%02X' % i, 'Message 0x%02X' % i) for i in range(18)) + (
|
||||
('summary', 'Summary'),
|
||||
('warning', 'Warning'),
|
||||
)
|
||||
annotation_rows = (
|
||||
('messages', 'Messages', tuple(range(18))),
|
||||
('summaries', 'Summaries', (18,)),
|
||||
('warnings', 'Warnings', (19,)),
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
self.state = 'IDLE'
|
||||
self.stack = []
|
||||
self.msg = -1
|
||||
self.ss = self.es = self.ss_block = self.es_block = 0
|
||||
self.init_seq = []
|
||||
self.valid = 0
|
||||
self.type = ''
|
||||
|
||||
def start(self):
|
||||
self.out_ann = self.register(srd.OUTPUT_ANN)
|
||||
|
||||
def putb(self, data):
|
||||
self.put(self.ss_block, self.es_block, self.out_ann, data)
|
||||
|
||||
def decode(self, ss, es, data):
|
||||
cmd, databyte = data
|
||||
|
||||
# Collect the 'BITS' packet, then return. The next packet is
|
||||
# guaranteed to belong to these bits we just stored.
|
||||
if cmd == 'BITS':
|
||||
self.bits = databyte
|
||||
return
|
||||
|
||||
self.ss, self.es = ss, es
|
||||
|
||||
# State machine.
|
||||
if self.state == 'IDLE':
|
||||
# Wait for an I2C START condition.
|
||||
if cmd == 'START':
|
||||
self.reset()
|
||||
self.ss_block = ss
|
||||
elif cmd != 'START REPEAT':
|
||||
return
|
||||
self.state = 'GET SLAVE ADDR'
|
||||
elif self.state == 'GET SLAVE ADDR':
|
||||
if cmd == 'ADDRESS READ':
|
||||
self.state = 'BUFFER DATA'
|
||||
if databyte != 0x3a:
|
||||
self.state = 'IDLE'
|
||||
elif cmd == 'ADDRESS WRITE':
|
||||
self.state = 'WRITE OFFSET'
|
||||
if databyte != 0x3a:
|
||||
self.state = 'IDLE'
|
||||
elif self.state == 'WRITE OFFSET':
|
||||
if cmd == 'DATA WRITE':
|
||||
if databyte in write_items:
|
||||
self.type = write_items[databyte]
|
||||
if databyte in (0x10, 0x15, 0x18, 0x60):
|
||||
self.state = 'BUFFER DATA'
|
||||
# If we are reading, then jump back to IDLE for a start repeat.
|
||||
# If we are writing, then just continue onwards.
|
||||
if self.state == 'BUFFER DATA':
|
||||
pass
|
||||
elif self.type != '':
|
||||
self.state = 'IDLE'
|
||||
elif self.state == 'BUFFER DATA':
|
||||
if cmd in ('STOP', 'NACK'):
|
||||
self.es_block = es
|
||||
self.state = 'IDLE'
|
||||
if self.type == '':
|
||||
return
|
||||
if not self.stack:
|
||||
self.putb([18, ['%s' % (self.type)]])
|
||||
return
|
||||
if self.type == 'RxStatus':
|
||||
rxstatus = (self.stack.pop() << 8) | self.stack.pop()
|
||||
reauth_req = (rxstatus & 0x800) != 0
|
||||
ready = (rxstatus & 0x400) != 0
|
||||
length = rxstatus & 0x3ff
|
||||
text = '%s, reauth %s, ready %s, length %s' % \
|
||||
(self.type, reauth_req, ready, length)
|
||||
self.putb([18, [text]])
|
||||
elif self.type == '1.4 Bstatus':
|
||||
bstatus = (self.stack.pop() << 8) | self.stack.pop()
|
||||
device_count = bstatus & 0x7f
|
||||
max_devs_exceeded = (bstatus & 0x80) != 0
|
||||
depth = ((bstatus & 0x700) >> 8)
|
||||
max_cascase_exceeded = bstatus & 0x800
|
||||
hdmi_mode = (bstatus & 0x1000) != 0
|
||||
text = '%s, %s devices, depth %s, hdmi mode %s' % \
|
||||
(self.type, device_count, depth, hdmi_mode)
|
||||
self.putb([18, [text]])
|
||||
elif self.type == 'Read_Message':
|
||||
msg = self.stack.pop(0)
|
||||
self.putb([msg, ['%s, %s' % (self.type,
|
||||
msg_ids.get(msg, 'Invalid'))]])
|
||||
elif self.type == 'Write_Message':
|
||||
msg = self.stack.pop(0)
|
||||
self.putb([msg, ['%s, %s' % (self.type,
|
||||
msg_ids.get(msg, 'Invalid'))]])
|
||||
elif self.type == 'HDCP2Version':
|
||||
version = self.stack.pop(0)
|
||||
if (version & 0x4):
|
||||
self.putb([18, ['HDCP2']])
|
||||
else:
|
||||
self.putb([18, ['NOT HDCP2']])
|
||||
else:
|
||||
self.putb([18, ['%s' % (self.type)]])
|
||||
elif cmd == 'DATA READ':
|
||||
# Stack up our data bytes.
|
||||
self.stack.append(databyte)
|
||||
elif cmd == 'DATA WRITE':
|
||||
# Stack up our data bytes.
|
||||
self.stack.append(databyte)
|
||||
26
libsigrokdecode4DSL/decoders/ieee488/__init__.py
Normal file
26
libsigrokdecode4DSL/decoders/ieee488/__init__.py
Normal file
@@ -0,0 +1,26 @@
|
||||
##
|
||||
## This file is part of the libsigrokdecode project.
|
||||
##
|
||||
## Copyright (C) 2019 Gerhard Sittig <gerhard.sittig@gmx.net>
|
||||
##
|
||||
## 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
|
||||
## the Free Software Foundation; either version 2 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
'''
|
||||
This protocol decoder can decode the GPIB (IEEE-488) protocol. Both variants
|
||||
of (up to) 16 parallel lines as well as serial communication (IEC bus) are
|
||||
supported in this implementation.
|
||||
'''
|
||||
|
||||
from .pd import Decoder
|
||||
748
libsigrokdecode4DSL/decoders/ieee488/pd.py
Normal file
748
libsigrokdecode4DSL/decoders/ieee488/pd.py
Normal file
@@ -0,0 +1,748 @@
|
||||
##
|
||||
## This file is part of the libsigrokdecode project.
|
||||
##
|
||||
## Copyright (C) 2016 Rudolf Reuter <reuterru@arcor.de>
|
||||
## Copyright (C) 2017 Marcus Comstedt <marcus@mc.pp.se>
|
||||
## Copyright (C) 2019 Gerhard Sittig <gerhard.sittig@gmx.net>
|
||||
##
|
||||
## 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
|
||||
## the Free Software Foundation; either version 2 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
# This file was created from earlier implementations of the 'gpib' and
|
||||
# the 'iec' protocol decoders. It combines the parallel and the serial
|
||||
# transmission variants in a single instance with optional inputs for
|
||||
# maximum code re-use.
|
||||
|
||||
# TODO
|
||||
# - Extend annotations for improved usability.
|
||||
# - Keep talkers' data streams on separate annotation rows? Is this useful
|
||||
# here at the GPIB level, or shall stacked decoders dispatch these? May
|
||||
# depend on how often captures get inspected which involve multiple peers.
|
||||
# - Make serial bit annotations optional? Could slow down interactive
|
||||
# exploration for long captures (see USB).
|
||||
# - Move the inlined Commodore IEC peripherals support to a stacked decoder
|
||||
# when more peripherals get added.
|
||||
# - SCPI over GPIB may "represent somewhat naturally" here already when
|
||||
# text lines are a single run of data at the GPIB layer (each line between
|
||||
# the address spec and either EOI or ATN). So a stacked SCPI decoder may
|
||||
# only become necessary when the text lines' content shall get inspected.
|
||||
|
||||
import sigrokdecode as srd
|
||||
from common.srdhelper import bitpack
|
||||
|
||||
'''
|
||||
OUTPUT_PYTHON format for stacked decoders:
|
||||
|
||||
General packet format:
|
||||
[<ptype>, <addr>, <pdata>]
|
||||
|
||||
This is the list of <ptype>s and their respective <pdata> values:
|
||||
|
||||
Raw bits and bytes at the physical transport level:
|
||||
- 'IEC_BIT': <addr> is not applicable, <pdata> is the transport's bit value.
|
||||
- 'GPIB_RAW': <addr> is not applicable, <pdata> is the transport's
|
||||
byte value. Data bytes are in the 0x00-0xff range, command/address
|
||||
bytes are in the 0x100-0x1ff range.
|
||||
|
||||
GPIB level byte fields (commands, addresses, pieces of data):
|
||||
- 'COMMAND': <addr> is not applicable, <pdata> is the command's byte value.
|
||||
- 'LISTEN': <addr> is the listener address (0-30), <pdata> is the raw
|
||||
byte value (including the 0x20 offset).
|
||||
- 'TALK': <addr> is the talker address (0-30), <pdata> is the raw byte
|
||||
value (including the 0x40 offset).
|
||||
- 'SECONDARY': <addr> is the secondary address (0-31), <pdata> is the
|
||||
raw byte value (including the 0x60 offset).
|
||||
- 'MSB_SET': <addr> as well as <pdata> are the raw byte value (including
|
||||
the 0x80 offset). This usually does not happen for GPIB bytes with ATN
|
||||
active, but was observed with the IEC bus and Commodore floppy drives,
|
||||
when addressing channels within the device.
|
||||
- 'DATA_BYTE': <addr> is the talker address (when available), <pdata>
|
||||
is the raw data byte (transport layer, ATN inactive).
|
||||
|
||||
Extracted payload information (peers and their communicated data):
|
||||
- 'TALK_LISTEN': <addr> is the current talker, <pdata> is the list of
|
||||
current listeners. These updates for the current "connected peers"
|
||||
are sent when the set of peers changes, i.e. after talkers/listeners
|
||||
got selected or deselected. Of course the data only covers what could
|
||||
be gathered from the input data. Some controllers may not explicitly
|
||||
address themselves, or captures may not include an early setup phase.
|
||||
- 'TALKER_BYTES': <addr> is the talker address (when available), <pdata>
|
||||
is the accumulated byte sequence between addressing a talker and EOI,
|
||||
or the next command/address.
|
||||
- 'TALKER_TEXT': <addr> is the talker address (when available), <pdata>
|
||||
is the accumulated text sequence between addressing a talker and EOI,
|
||||
or the next command/address.
|
||||
'''
|
||||
|
||||
class ChannelError(Exception):
|
||||
pass
|
||||
|
||||
def _format_ann_texts(fmts, **args):
|
||||
if not fmts:
|
||||
return None
|
||||
return [fmt.format(**args) for fmt in fmts]
|
||||
|
||||
_cmd_table = {
|
||||
# Command codes in the 0x00-0x1f range.
|
||||
0x01: ['Go To Local', 'GTL'],
|
||||
0x04: ['Selected Device Clear', 'SDC'],
|
||||
0x05: ['Parallel Poll Configure', 'PPC'],
|
||||
0x08: ['Global Execute Trigger', 'GET'],
|
||||
0x09: ['Take Control', 'TCT'],
|
||||
0x11: ['Local Lock Out', 'LLO'],
|
||||
0x14: ['Device Clear', 'DCL'],
|
||||
0x15: ['Parallel Poll Unconfigure', 'PPU'],
|
||||
0x18: ['Serial Poll Enable', 'SPE'],
|
||||
0x19: ['Serial Poll Disable', 'SPD'],
|
||||
# Unknown type of command.
|
||||
None: ['Unknown command 0x{cmd:02x}', 'command 0x{cmd:02x}', 'cmd {cmd:02x}', 'C{cmd_ord:c}'],
|
||||
# Special listener/talker "addresses" (deselecting previous peers).
|
||||
0x3f: ['Unlisten', 'UNL'],
|
||||
0x5f: ['Untalk', 'UNT'],
|
||||
}
|
||||
|
||||
def _is_command(b):
|
||||
# Returns a tuple of booleans (or None when not applicable) whether
|
||||
# the raw GPIB byte is: a command, an un-listen, an un-talk command.
|
||||
if b in range(0x00, 0x20):
|
||||
return True, None, None
|
||||
if b in range(0x20, 0x40) and (b & 0x1f) == 31:
|
||||
return True, True, False
|
||||
if b in range(0x40, 0x60) and (b & 0x1f) == 31:
|
||||
return True, False, True
|
||||
return False, None, None
|
||||
|
||||
def _is_listen_addr(b):
|
||||
if b in range(0x20, 0x40):
|
||||
return b & 0x1f
|
||||
return None
|
||||
|
||||
def _is_talk_addr(b):
|
||||
if b in range(0x40, 0x60):
|
||||
return b & 0x1f
|
||||
return None
|
||||
|
||||
def _is_secondary_addr(b):
|
||||
if b in range(0x60, 0x80):
|
||||
return b & 0x1f
|
||||
return None
|
||||
|
||||
def _is_msb_set(b):
|
||||
if b & 0x80:
|
||||
return b
|
||||
return None
|
||||
|
||||
def _get_raw_byte(b, atn):
|
||||
# "Decorate" raw byte values for stacked decoders.
|
||||
return b | 0x100 if atn else b
|
||||
|
||||
def _get_raw_text(b, atn):
|
||||
return ['{leader}{data:02x}'.format(leader = '/' if atn else '', data = b)]
|
||||
|
||||
def _get_command_texts(b):
|
||||
fmts = _cmd_table.get(b, None)
|
||||
known = fmts is not None
|
||||
if not fmts:
|
||||
fmts = _cmd_table.get(None, None)
|
||||
if not fmts:
|
||||
return known, None
|
||||
return known, _format_ann_texts(fmts, cmd = b, cmd_ord = ord('0') + b)
|
||||
|
||||
def _get_address_texts(b):
|
||||
laddr = _is_listen_addr(b)
|
||||
taddr = _is_talk_addr(b)
|
||||
saddr = _is_secondary_addr(b)
|
||||
msb = _is_msb_set(b)
|
||||
fmts = None
|
||||
if laddr is not None:
|
||||
fmts = ['Listen {addr:d}', 'L {addr:d}', 'L{addr_ord:c}']
|
||||
addr = laddr
|
||||
elif taddr is not None:
|
||||
fmts = ['Talk {addr:d}', 'T {addr:d}', 'T{addr_ord:c}']
|
||||
addr = taddr
|
||||
elif saddr is not None:
|
||||
fmts = ['Secondary {addr:d}', 'S {addr:d}', 'S{addr_ord:c}']
|
||||
addr = saddr
|
||||
elif msb is not None: # For IEC bus compat.
|
||||
fmts = ['Secondary {addr:d}', 'S {addr:d}', 'S{addr_ord:c}']
|
||||
addr = msb
|
||||
return _format_ann_texts(fmts, addr = addr, addr_ord = ord('0') + addr)
|
||||
|
||||
def _get_data_text(b):
|
||||
# TODO Move the table of ASCII control characters to a common location?
|
||||
# TODO Move the "printable with escapes" logic to a common helper?
|
||||
_control_codes = {
|
||||
0x00: 'NUL',
|
||||
0x01: 'SOH',
|
||||
0x02: 'STX',
|
||||
0x03: 'ETX',
|
||||
0x04: 'EOT',
|
||||
0x05: 'ENQ',
|
||||
0x06: 'ACK',
|
||||
0x07: 'BEL',
|
||||
0x08: 'BS',
|
||||
0x09: 'TAB',
|
||||
0x0a: 'LF',
|
||||
0x0b: 'VT',
|
||||
0x0c: 'FF',
|
||||
0x0d: 'CR',
|
||||
0x0e: 'SO',
|
||||
0x0f: 'SI',
|
||||
0x10: 'DLE',
|
||||
0x11: 'DC1',
|
||||
0x12: 'DC2',
|
||||
0x13: 'DC3',
|
||||
0x14: 'DC4',
|
||||
0x15: 'NAK',
|
||||
0x16: 'SYN',
|
||||
0x17: 'ETB',
|
||||
0x18: 'CAN',
|
||||
0x19: 'EM',
|
||||
0x1a: 'SUB',
|
||||
0x1b: 'ESC',
|
||||
0x1c: 'FS',
|
||||
0x1d: 'GS',
|
||||
0x1e: 'RS',
|
||||
0x1f: 'US',
|
||||
}
|
||||
# Yes, exclude 0x7f (DEL) here. It's considered non-printable.
|
||||
if b in range(0x20, 0x7f) and b not in ('[', ']'):
|
||||
return '{:s}'.format(chr(b))
|
||||
elif b in _control_codes:
|
||||
return '[{:s}]'.format(_control_codes[b])
|
||||
# Use a compact yet readable and unambigous presentation for bytes
|
||||
# which contain non-printables. The format that is used here is
|
||||
# compatible with 93xx EEPROM and UART decoders.
|
||||
return '[{:02x}]'.format(b)
|
||||
|
||||
(
|
||||
PIN_DIO1, PIN_DIO2, PIN_DIO3, PIN_DIO4,
|
||||
PIN_DIO5, PIN_DIO6, PIN_DIO7, PIN_DIO8,
|
||||
PIN_EOI, PIN_DAV, PIN_NRFD, PIN_NDAC,
|
||||
PIN_IFC, PIN_SRQ, PIN_ATN, PIN_REN,
|
||||
PIN_CLK,
|
||||
) = range(17)
|
||||
PIN_DATA = PIN_DIO1
|
||||
|
||||
(
|
||||
ANN_RAW_BIT, ANN_RAW_BYTE,
|
||||
ANN_CMD, ANN_LADDR, ANN_TADDR, ANN_SADDR, ANN_DATA,
|
||||
ANN_EOI,
|
||||
ANN_TEXT,
|
||||
# TODO Want to provide one annotation class per talker address (0-30)?
|
||||
ANN_IEC_PERIPH,
|
||||
ANN_WARN,
|
||||
) = range(11)
|
||||
|
||||
(
|
||||
BIN_RAW,
|
||||
BIN_DATA,
|
||||
# TODO Want to provide one binary annotation class per talker address (0-30)?
|
||||
) = range(2)
|
||||
|
||||
class Decoder(srd.Decoder):
|
||||
api_version = 3
|
||||
id = 'ieee488'
|
||||
name = 'IEEE-488'
|
||||
longname = 'IEEE-488 GPIB/HPIB/IEC'
|
||||
desc = 'IEEE-488 General Purpose Interface Bus (GPIB/HPIB or IEC).'
|
||||
license = 'gplv2+'
|
||||
inputs = ['logic']
|
||||
outputs = ['ieee488']
|
||||
tags = ['PC', 'Retro computing']
|
||||
channels = (
|
||||
{'id': 'dio1' , 'name': 'DIO1/DATA',
|
||||
'desc': 'Data I/O bit 1, or serial data'},
|
||||
)
|
||||
optional_channels = (
|
||||
{'id': 'dio2' , 'name': 'DIO2', 'desc': 'Data I/O bit 2'},
|
||||
{'id': 'dio3' , 'name': 'DIO3', 'desc': 'Data I/O bit 3'},
|
||||
{'id': 'dio4' , 'name': 'DIO4', 'desc': 'Data I/O bit 4'},
|
||||
{'id': 'dio5' , 'name': 'DIO5', 'desc': 'Data I/O bit 5'},
|
||||
{'id': 'dio6' , 'name': 'DIO6', 'desc': 'Data I/O bit 6'},
|
||||
{'id': 'dio7' , 'name': 'DIO7', 'desc': 'Data I/O bit 7'},
|
||||
{'id': 'dio8' , 'name': 'DIO8', 'desc': 'Data I/O bit 8'},
|
||||
{'id': 'eoi', 'name': 'EOI', 'desc': 'End or identify'},
|
||||
{'id': 'dav', 'name': 'DAV', 'desc': 'Data valid'},
|
||||
{'id': 'nrfd', 'name': 'NRFD', 'desc': 'Not ready for data'},
|
||||
{'id': 'ndac', 'name': 'NDAC', 'desc': 'Not data accepted'},
|
||||
{'id': 'ifc', 'name': 'IFC', 'desc': 'Interface clear'},
|
||||
{'id': 'srq', 'name': 'SRQ', 'desc': 'Service request'},
|
||||
{'id': 'atn', 'name': 'ATN', 'desc': 'Attention'},
|
||||
{'id': 'ren', 'name': 'REN', 'desc': 'Remote enable'},
|
||||
{'id': 'clk', 'name': 'CLK', 'desc': 'Serial clock'},
|
||||
)
|
||||
options = (
|
||||
{'id': 'iec_periph', 'desc': 'Decode Commodore IEC bus peripherals details',
|
||||
'default': 'no', 'values': ('no', 'yes')},
|
||||
{'id': 'delim', 'desc': 'Payload data delimiter',
|
||||
'default': 'eol', 'values': ('none', 'eol')},
|
||||
)
|
||||
annotations = (
|
||||
('bit', 'IEC bit'),
|
||||
('raw', 'Raw byte'),
|
||||
('cmd', 'Command'),
|
||||
('laddr', 'Listener address'),
|
||||
('taddr', 'Talker address'),
|
||||
('saddr', 'Secondary address'),
|
||||
('data', 'Data byte'),
|
||||
('eoi', 'EOI'),
|
||||
('text', 'Talker text'),
|
||||
('periph', 'IEC bus peripherals'),
|
||||
('warning', 'Warning'),
|
||||
)
|
||||
annotation_rows = (
|
||||
('bits', 'IEC bits', (ANN_RAW_BIT,)),
|
||||
('raws', 'Raw bytes', (ANN_RAW_BYTE,)),
|
||||
('gpib', 'Commands/data', (ANN_CMD, ANN_LADDR, ANN_TADDR, ANN_SADDR, ANN_DATA,)),
|
||||
('eois', 'EOI', (ANN_EOI,)),
|
||||
('texts', 'Talker texts', (ANN_TEXT,)),
|
||||
('periphs', 'IEC peripherals', (ANN_IEC_PERIPH,)),
|
||||
('warnings', 'Warnings', (ANN_WARN,)),
|
||||
)
|
||||
binary = (
|
||||
('raw', 'Raw bytes'),
|
||||
('data', 'Talker bytes'),
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
self.curr_raw = None
|
||||
self.curr_atn = None
|
||||
self.curr_eoi = None
|
||||
self.latch_atn = None
|
||||
self.latch_eoi = None
|
||||
self.accu_bytes = []
|
||||
self.accu_text = []
|
||||
self.ss_raw = None
|
||||
self.es_raw = None
|
||||
self.ss_eoi = None
|
||||
self.es_eoi = None
|
||||
self.ss_text = None
|
||||
self.es_text = None
|
||||
self.last_talker = None
|
||||
self.last_listener = []
|
||||
self.last_iec_addr = None
|
||||
self.last_iec_sec = None
|
||||
|
||||
def start(self):
|
||||
self.out_ann = self.register(srd.OUTPUT_ANN)
|
||||
self.out_bin = self.register(srd.OUTPUT_BINARY)
|
||||
self.out_python = self.register(srd.OUTPUT_PYTHON)
|
||||
|
||||
def putg(self, ss, es, data):
|
||||
self.put(ss, es, self.out_ann, data)
|
||||
|
||||
def putbin(self, ss, es, data):
|
||||
self.put(ss, es, self.out_bin, data)
|
||||
|
||||
def putpy(self, ss, es, ptype, addr, pdata):
|
||||
self.put(ss, es, self.out_python, [ptype, addr, pdata])
|
||||
|
||||
def emit_eoi_ann(self, ss, es):
|
||||
self.putg(ss, es, [ANN_EOI, ['EOI']])
|
||||
|
||||
def emit_bin_ann(self, ss, es, ann_cls, data):
|
||||
self.putbin(ss, es, [ann_cls, bytes(data)])
|
||||
|
||||
def emit_data_ann(self, ss, es, ann_cls, data):
|
||||
self.putg(ss, es, [ann_cls, data])
|
||||
|
||||
def emit_warn_ann(self, ss, es, data):
|
||||
self.putg(ss, es, [ANN_WARN, data])
|
||||
|
||||
def flush_bytes_text_accu(self):
|
||||
if self.accu_bytes and self.ss_text is not None and self.es_text is not None:
|
||||
self.emit_bin_ann(self.ss_text, self.es_text, BIN_DATA, bytearray(self.accu_bytes))
|
||||
self.putpy(self.ss_text, self.es_text, 'TALKER_BYTES', self.last_talker, bytearray(self.accu_bytes))
|
||||
self.accu_bytes = []
|
||||
if self.accu_text and self.ss_text is not None and self.es_text is not None:
|
||||
text = ''.join(self.accu_text)
|
||||
self.emit_data_ann(self.ss_text, self.es_text, ANN_TEXT, [text])
|
||||
self.putpy(self.ss_text, self.es_text, 'TALKER_TEXT', self.last_talker, text)
|
||||
self.accu_text = []
|
||||
self.ss_text = self.es_text = None
|
||||
|
||||
def check_extra_flush(self, b):
|
||||
# Optionally flush previously accumulated runs of payload data
|
||||
# according to user specified conditions.
|
||||
if self.options['delim'] == 'none':
|
||||
return
|
||||
if not self.accu_bytes:
|
||||
return
|
||||
|
||||
# This implementation exlusively handles "text lines", but adding
|
||||
# support for more variants here is straight forward.
|
||||
#
|
||||
# Search for the first data byte _after_ a user specified text
|
||||
# line termination sequence was seen. The termination sequence's
|
||||
# alphabet may be variable, and the sequence may span multiple
|
||||
# data bytes. We accept either CR or LF, and combine the CR+LF
|
||||
# sequence to strive for maximum length annotations for improved
|
||||
# readability at different zoom levels. It's acceptable that this
|
||||
# implementation would also combine multiple line terminations
|
||||
# like LF+LF.
|
||||
term_chars = (10, 13)
|
||||
is_eol = b in term_chars
|
||||
had_eol = self.accu_bytes[-1] in term_chars
|
||||
if had_eol and not is_eol:
|
||||
self.flush_bytes_text_accu()
|
||||
|
||||
def handle_ifc_change(self, ifc):
|
||||
# Track IFC line for parallel input.
|
||||
# Assertion of IFC de-selects all talkers and listeners.
|
||||
if ifc:
|
||||
self.last_talker = None
|
||||
self.last_listener = []
|
||||
self.flush_bytes_text_accu()
|
||||
|
||||
def handle_eoi_change(self, eoi):
|
||||
# Track EOI line for parallel and serial input.
|
||||
if eoi:
|
||||
self.ss_eoi = self.samplenum
|
||||
self.curr_eoi = eoi
|
||||
else:
|
||||
self.es_eoi = self.samplenum
|
||||
if self.ss_eoi and self.latch_eoi:
|
||||
self.emit_eoi_ann(self.ss_eoi, self.es_eoi)
|
||||
self.es_text = self.es_eoi
|
||||
self.flush_bytes_text_accu()
|
||||
self.ss_eoi = self.es_eoi = None
|
||||
self.curr_eoi = None
|
||||
|
||||
def handle_atn_change(self, atn):
|
||||
# Track ATN line for parallel and serial input.
|
||||
self.curr_atn = atn
|
||||
if atn:
|
||||
self.flush_bytes_text_accu()
|
||||
|
||||
def handle_iec_periph(self, ss, es, addr, sec, data):
|
||||
# The annotation is optional.
|
||||
if self.options['iec_periph'] != 'yes':
|
||||
return
|
||||
# Void internal state.
|
||||
if addr is None and sec is None and data is None:
|
||||
self.last_iec_addr = None
|
||||
self.last_iec_sec = None
|
||||
return
|
||||
# Grab and evaluate new input.
|
||||
_iec_addr_names = {
|
||||
# TODO Add more items here. See the "Device numbering" section
|
||||
# of the https://en.wikipedia.org/wiki/Commodore_bus page.
|
||||
8: 'Disk 0',
|
||||
9: 'Disk 1',
|
||||
}
|
||||
_iec_disk_range = range(8, 16)
|
||||
if addr is not None:
|
||||
self.last_iec_addr = addr
|
||||
name = _iec_addr_names.get(addr, None)
|
||||
if name:
|
||||
self.emit_data_ann(ss, es, ANN_IEC_PERIPH, [name])
|
||||
addr = self.last_iec_addr # Simplify subsequent logic.
|
||||
if sec is not None:
|
||||
# BEWARE! The secondary address is a full byte and includes
|
||||
# the 0x60 offset, to also work when the MSB was set.
|
||||
self.last_iec_sec = sec
|
||||
subcmd, channel = sec & 0xf0, sec & 0x0f
|
||||
channel_ord = ord('0') + channel
|
||||
if addr is not None and addr in _iec_disk_range:
|
||||
subcmd_fmts = {
|
||||
0x60: ['Reopen {ch:d}', 'Re {ch:d}', 'R{ch_ord:c}'],
|
||||
0xe0: ['Close {ch:d}', 'Cl {ch:d}', 'C{ch_ord:c}'],
|
||||
0xf0: ['Open {ch:d}', 'Op {ch:d}', 'O{ch_ord:c}'],
|
||||
}.get(subcmd, None)
|
||||
if subcmd_fmts:
|
||||
texts = _format_ann_texts(subcmd_fmts, ch = channel, ch_ord = channel_ord)
|
||||
self.emit_data_ann(ss, es, ANN_IEC_PERIPH, texts)
|
||||
sec = self.last_iec_sec # Simplify subsequent logic.
|
||||
if data is not None:
|
||||
if addr is None or sec is None:
|
||||
return
|
||||
# TODO Process data depending on peripheral type and channel?
|
||||
|
||||
def handle_data_byte(self):
|
||||
if not self.curr_atn:
|
||||
self.check_extra_flush(self.curr_raw)
|
||||
b = self.curr_raw
|
||||
texts = _get_raw_text(b, self.curr_atn)
|
||||
self.emit_data_ann(self.ss_raw, self.es_raw, ANN_RAW_BYTE, texts)
|
||||
self.emit_bin_ann(self.ss_raw, self.es_raw, BIN_RAW, b.to_bytes(1, byteorder='big'))
|
||||
self.putpy(self.ss_raw, self.es_raw, 'GPIB_RAW', None, _get_raw_byte(b, self.curr_atn))
|
||||
if self.curr_atn:
|
||||
ann_cls = None
|
||||
upd_iec = False,
|
||||
py_type = None
|
||||
py_peers = False
|
||||
is_cmd, is_unl, is_unt = _is_command(b)
|
||||
laddr = _is_listen_addr(b)
|
||||
taddr = _is_talk_addr(b)
|
||||
saddr = _is_secondary_addr(b)
|
||||
msb = _is_msb_set(b)
|
||||
if is_cmd:
|
||||
known, texts = _get_command_texts(b)
|
||||
if not known:
|
||||
warn_texts = ['Unknown GPIB command', 'unknown', 'UNK']
|
||||
self.emit_warn_ann(self.ss_raw, self.es_raw, warn_texts)
|
||||
ann_cls = ANN_CMD
|
||||
py_type, py_addr = 'COMMAND', None
|
||||
if is_unl:
|
||||
self.last_listener = []
|
||||
py_peers = True
|
||||
if is_unt:
|
||||
self.last_talker = None
|
||||
py_peers = True
|
||||
if is_unl or is_unt:
|
||||
upd_iec = True, None, None, None
|
||||
elif laddr is not None:
|
||||
addr = laddr
|
||||
texts = _get_address_texts(b)
|
||||
ann_cls = ANN_LADDR
|
||||
py_type, py_addr = 'LISTEN', addr
|
||||
if addr == self.last_talker:
|
||||
self.last_talker = None
|
||||
self.last_listener.append(addr)
|
||||
upd_iec = True, addr, None, None
|
||||
py_peers = True
|
||||
elif taddr is not None:
|
||||
addr = taddr
|
||||
texts = _get_address_texts(b)
|
||||
ann_cls = ANN_TADDR
|
||||
py_type, py_addr = 'TALK', addr
|
||||
if addr in self.last_listener:
|
||||
self.last_listener.remove(addr)
|
||||
self.last_talker = addr
|
||||
upd_iec = True, addr, None, None
|
||||
py_peers = True
|
||||
elif saddr is not None:
|
||||
addr = saddr
|
||||
texts = _get_address_texts(b)
|
||||
ann_cls = ANN_SADDR
|
||||
upd_iec = True, None, b, None
|
||||
py_type, py_addr = 'SECONDARY', addr
|
||||
elif msb is not None:
|
||||
# These are not really "secondary addresses", but they
|
||||
# are used by the Commodore IEC bus (floppy channels).
|
||||
texts = _get_address_texts(b)
|
||||
ann_cls = ANN_SADDR
|
||||
upd_iec = True, None, b, None
|
||||
py_type, py_addr = 'MSB_SET', b
|
||||
if ann_cls is not None and texts is not None:
|
||||
self.emit_data_ann(self.ss_raw, self.es_raw, ann_cls, texts)
|
||||
if upd_iec[0]:
|
||||
self.handle_iec_periph(self.ss_raw, self.es_raw, upd_iec[1], upd_iec[2], upd_iec[3])
|
||||
if py_type:
|
||||
self.putpy(self.ss_raw, self.es_raw, py_type, py_addr, b)
|
||||
if py_peers:
|
||||
self.last_listener.sort()
|
||||
self.putpy(self.ss_raw, self.es_raw, 'TALK_LISTEN', self.last_talker, self.last_listener)
|
||||
else:
|
||||
self.accu_bytes.append(b)
|
||||
text = _get_data_text(b)
|
||||
if not self.accu_text:
|
||||
self.ss_text = self.ss_raw
|
||||
self.accu_text.append(text)
|
||||
self.es_text = self.es_raw
|
||||
self.emit_data_ann(self.ss_raw, self.es_raw, ANN_DATA, [text])
|
||||
self.handle_iec_periph(self.ss_raw, self.es_raw, None, None, b)
|
||||
self.putpy(self.ss_raw, self.es_raw, 'DATA_BYTE', self.last_talker, b)
|
||||
|
||||
def handle_dav_change(self, dav, data):
|
||||
if dav:
|
||||
# Data availability starts when the flag goes active.
|
||||
self.ss_raw = self.samplenum
|
||||
self.curr_raw = bitpack(data)
|
||||
self.latch_atn = self.curr_atn
|
||||
self.latch_eoi = self.curr_eoi
|
||||
return
|
||||
# Data availability ends when the flag goes inactive. Handle the
|
||||
# previously captured data byte according to associated flags.
|
||||
self.es_raw = self.samplenum
|
||||
self.handle_data_byte()
|
||||
self.ss_raw = self.es_raw = None
|
||||
self.curr_raw = None
|
||||
|
||||
def inject_dav_phase(self, ss, es, data):
|
||||
# Inspection of serial input has resulted in one raw byte which
|
||||
# spans a given period of time. Pretend we had seen a DAV active
|
||||
# phase, to re-use code for the parallel transmission.
|
||||
self.ss_raw = ss
|
||||
self.curr_raw = bitpack(data)
|
||||
self.latch_atn = self.curr_atn
|
||||
self.latch_eoi = self.curr_eoi
|
||||
self.es_raw = es
|
||||
self.handle_data_byte()
|
||||
self.ss_raw = self.es_raw = None
|
||||
self.curr_raw = None
|
||||
|
||||
def invert_pins(self, pins):
|
||||
# All lines (including data bits!) are low active and thus need
|
||||
# to get inverted to receive their logical state (high active,
|
||||
# regular data bit values). Cope with inputs being optional.
|
||||
return [1 - p if p in (0, 1) else p for p in pins]
|
||||
|
||||
def decode_serial(self, has_clk, has_data_1, has_atn, has_srq):
|
||||
if not has_clk or not has_data_1 or not has_atn:
|
||||
raise ChannelError('IEC bus needs at least ATN and serial CLK and DATA.')
|
||||
|
||||
# This is a rephrased version of decoders/iec/pd.py:decode().
|
||||
# SRQ was not used there either. Magic numbers were eliminated.
|
||||
(
|
||||
STEP_WAIT_READY_TO_SEND,
|
||||
STEP_WAIT_READY_FOR_DATA,
|
||||
STEP_PREP_DATA_TEST_EOI,
|
||||
STEP_CLOCK_DATA_BITS,
|
||||
) = range(4)
|
||||
step_wait_conds = (
|
||||
[{PIN_ATN: 'f'}, {PIN_DATA: 'l', PIN_CLK: 'h'}],
|
||||
[{PIN_ATN: 'f'}, {PIN_DATA: 'h', PIN_CLK: 'h'}, {PIN_CLK: 'l'}],
|
||||
[{PIN_ATN: 'f'}, {PIN_DATA: 'f'}, {PIN_CLK: 'l'}],
|
||||
[{PIN_ATN: 'f'}, {PIN_CLK: 'e'}],
|
||||
)
|
||||
step = STEP_WAIT_READY_TO_SEND
|
||||
bits = []
|
||||
|
||||
while True:
|
||||
|
||||
# Sample input pin values. Keep DATA/CLK in verbatim form to
|
||||
# re-use 'iec' decoder logic. Turn ATN to positive logic for
|
||||
# easier processing. The data bits get handled during byte
|
||||
# accumulation.
|
||||
pins = self.wait(step_wait_conds[step])
|
||||
data, clk = pins[PIN_DATA], pins[PIN_CLK]
|
||||
atn, = self.invert_pins([pins[PIN_ATN]])
|
||||
|
||||
if self.matched[0]:
|
||||
# Falling edge on ATN, reset step.
|
||||
step = STEP_WAIT_READY_TO_SEND
|
||||
|
||||
if step == STEP_WAIT_READY_TO_SEND:
|
||||
# Don't use self.matched[1] here since we might come from
|
||||
# a step with different conds due to the code above.
|
||||
if data == 0 and clk == 1:
|
||||
# Rising edge on CLK while DATA is low: Ready to send.
|
||||
step = STEP_WAIT_READY_FOR_DATA
|
||||
elif step == STEP_WAIT_READY_FOR_DATA:
|
||||
if data == 1 and clk == 1:
|
||||
# Rising edge on DATA while CLK is high: Ready for data.
|
||||
ss_byte = self.samplenum
|
||||
self.handle_atn_change(atn)
|
||||
if self.curr_eoi:
|
||||
self.handle_eoi_change(False)
|
||||
bits = []
|
||||
step = STEP_PREP_DATA_TEST_EOI
|
||||
elif clk == 0:
|
||||
# CLK low again, transfer aborted.
|
||||
step = STEP_WAIT_READY_TO_SEND
|
||||
elif step == STEP_PREP_DATA_TEST_EOI:
|
||||
if data == 0 and clk == 1:
|
||||
# DATA goes low while CLK is still high, EOI confirmed.
|
||||
self.handle_eoi_change(True)
|
||||
elif clk == 0:
|
||||
step = STEP_CLOCK_DATA_BITS
|
||||
ss_bit = self.samplenum
|
||||
elif step == STEP_CLOCK_DATA_BITS:
|
||||
if self.matched[1]:
|
||||
if clk == 1:
|
||||
# Rising edge on CLK; latch DATA.
|
||||
bits.append(data)
|
||||
elif clk == 0:
|
||||
# Falling edge on CLK; end of bit.
|
||||
es_bit = self.samplenum
|
||||
self.emit_data_ann(ss_bit, es_bit, ANN_RAW_BIT, ['{:d}'.format(bits[-1])])
|
||||
self.putpy(ss_bit, es_bit, 'IEC_BIT', None, bits[-1])
|
||||
ss_bit = self.samplenum
|
||||
if len(bits) == 8:
|
||||
es_byte = self.samplenum
|
||||
self.inject_dav_phase(ss_byte, es_byte, bits)
|
||||
if self.curr_eoi:
|
||||
self.handle_eoi_change(False)
|
||||
step = STEP_WAIT_READY_TO_SEND
|
||||
|
||||
def decode_parallel(self, has_data_n, has_dav, has_atn, has_eoi, has_srq):
|
||||
|
||||
if False in has_data_n or not has_dav or not has_atn:
|
||||
raise ChannelError('IEEE-488 needs at least ATN and DAV and eight DIO lines.')
|
||||
has_ifc = self.has_channel(PIN_IFC)
|
||||
|
||||
# Capture data lines at the falling edge of DAV, process their
|
||||
# values at rising DAV edge (when data validity ends). Also make
|
||||
# sure to start inspection when the capture happens to start with
|
||||
# low signal levels, i.e. won't include the initial falling edge.
|
||||
# Scan for ATN/EOI edges as well (including the trick which works
|
||||
# around initial pin state).
|
||||
# Map low-active physical transport lines to positive logic here,
|
||||
# to simplify logical inspection/decoding of communicated data,
|
||||
# and to avoid redundancy and inconsistency in later code paths.
|
||||
waitcond = []
|
||||
idx_dav = len(waitcond)
|
||||
waitcond.append({PIN_DAV: 'l'})
|
||||
idx_atn = len(waitcond)
|
||||
waitcond.append({PIN_ATN: 'l'})
|
||||
idx_eoi = None
|
||||
if has_eoi:
|
||||
idx_eoi = len(waitcond)
|
||||
waitcond.append({PIN_EOI: 'l'})
|
||||
idx_ifc = None
|
||||
if has_ifc:
|
||||
idx_ifc = len(waitcond)
|
||||
waitcond.append({PIN_IFC: 'l'})
|
||||
while True:
|
||||
pins = self.wait(waitcond)
|
||||
pins = self.invert_pins(pins)
|
||||
|
||||
# BEWARE! Order of evaluation does matter. For low samplerate
|
||||
# captures, many edges fall onto the same sample number. So
|
||||
# we process active edges of flags early (before processing
|
||||
# data bits), and inactive edges late (after data got processed).
|
||||
if idx_ifc is not None and self.matched[idx_ifc] and pins[PIN_IFC] == 1:
|
||||
self.handle_ifc_change(pins[PIN_IFC])
|
||||
if idx_eoi is not None and self.matched[idx_eoi] and pins[PIN_EOI] == 1:
|
||||
self.handle_eoi_change(pins[PIN_EOI])
|
||||
if self.matched[idx_atn] and pins[PIN_ATN] == 1:
|
||||
self.handle_atn_change(pins[PIN_ATN])
|
||||
if self.matched[idx_dav]:
|
||||
self.handle_dav_change(pins[PIN_DAV], pins[PIN_DIO1:PIN_DIO8 + 1])
|
||||
if self.matched[idx_atn] and pins[PIN_ATN] == 0:
|
||||
self.handle_atn_change(pins[PIN_ATN])
|
||||
if idx_eoi is not None and self.matched[idx_eoi] and pins[PIN_EOI] == 0:
|
||||
self.handle_eoi_change(pins[PIN_EOI])
|
||||
if idx_ifc is not None and self.matched[idx_ifc] and pins[PIN_IFC] == 0:
|
||||
self.handle_ifc_change(pins[PIN_IFC])
|
||||
|
||||
waitcond[idx_dav][PIN_DAV] = 'e'
|
||||
waitcond[idx_atn][PIN_ATN] = 'e'
|
||||
if has_eoi:
|
||||
waitcond[idx_eoi][PIN_EOI] = 'e'
|
||||
if has_ifc:
|
||||
waitcond[idx_ifc][PIN_IFC] = 'e'
|
||||
|
||||
def decode(self):
|
||||
# The decoder's boilerplate declares some of the input signals as
|
||||
# optional, but only to support both serial and parallel variants.
|
||||
# The CLK signal discriminates the two. For either variant some
|
||||
# of the "optional" signals are not really optional for proper
|
||||
# operation of the decoder. Check these conditions here.
|
||||
has_clk = self.has_channel(PIN_CLK)
|
||||
has_data_1 = self.has_channel(PIN_DIO1)
|
||||
has_data_n = [bool(self.has_channel(pin) for pin in range(PIN_DIO1, PIN_DIO8 + 1))]
|
||||
has_dav = self.has_channel(PIN_DAV)
|
||||
has_atn = self.has_channel(PIN_ATN)
|
||||
has_eoi = self.has_channel(PIN_EOI)
|
||||
has_srq = self.has_channel(PIN_SRQ)
|
||||
if has_clk:
|
||||
self.decode_serial(has_clk, has_data_1, has_atn, has_srq)
|
||||
else:
|
||||
self.decode_parallel(has_data_n, has_dav, has_atn, has_eoi, has_srq)
|
||||
25
libsigrokdecode4DSL/decoders/ir_irmp/__init__.py
Normal file
25
libsigrokdecode4DSL/decoders/ir_irmp/__init__.py
Normal file
@@ -0,0 +1,25 @@
|
||||
##
|
||||
## This file is part of the libsigrokdecode project.
|
||||
##
|
||||
## Copyright (C) 2019 Rene Staffen
|
||||
##
|
||||
## 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
|
||||
## the Free Software Foundation; either version 2 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
'''
|
||||
IRMP is a multi protocol infrared remote protocol decoder. See
|
||||
https://www.mikrocontroller.net/articles/IRMP for details.
|
||||
'''
|
||||
|
||||
from .pd import Decoder
|
||||
137
libsigrokdecode4DSL/decoders/ir_irmp/irmp_library.py
Normal file
137
libsigrokdecode4DSL/decoders/ir_irmp/irmp_library.py
Normal file
@@ -0,0 +1,137 @@
|
||||
##
|
||||
## This file is part of the libsigrokdecode project.
|
||||
##
|
||||
## Copyright (C) 2019 Rene Staffen
|
||||
##
|
||||
## 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
|
||||
## the Free Software Foundation; either version 2 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
'''
|
||||
Python binding for the IRMP library.
|
||||
'''
|
||||
|
||||
import ctypes
|
||||
import platform
|
||||
|
||||
class IrmpLibrary:
|
||||
'''
|
||||
Library instance for an infrared protocol detector.
|
||||
'''
|
||||
|
||||
__usable_instance = None
|
||||
|
||||
class ResultData(ctypes.Structure):
|
||||
_fields_ = [
|
||||
( 'protocol', ctypes.c_uint32, ),
|
||||
( 'protocol_name', ctypes.c_char_p, ),
|
||||
( 'address', ctypes.c_uint32, ),
|
||||
( 'command', ctypes.c_uint32, ),
|
||||
( 'flags', ctypes.c_uint32, ),
|
||||
( 'start_sample', ctypes.c_uint32, ),
|
||||
( 'end_sample', ctypes.c_uint32, ),
|
||||
]
|
||||
|
||||
FLAG_REPETITION = 1 << 0
|
||||
FLAG_RELEASE = 1 << 1
|
||||
|
||||
def _library_filename(self):
|
||||
'''
|
||||
Determine the library filename depending on the platform.
|
||||
'''
|
||||
|
||||
if platform.uname()[0] == 'Linux':
|
||||
return 'libirmp.so'
|
||||
if platform.uname()[0] == 'Darwin':
|
||||
return 'libirmp.dylib'
|
||||
return 'irmp.dll'
|
||||
|
||||
def _library_setup_api(self):
|
||||
'''
|
||||
Lookup the C library's API routines. Declare their prototypes.
|
||||
'''
|
||||
|
||||
if not self._lib:
|
||||
return False
|
||||
|
||||
self._lib.irmp_get_sample_rate.restype = ctypes.c_uint32
|
||||
self._lib.irmp_get_sample_rate.argtypes = []
|
||||
|
||||
self._lib.irmp_reset_state.restype = None
|
||||
self._lib.irmp_reset_state.argtypes = []
|
||||
|
||||
self._lib.irmp_add_one_sample.restype = ctypes.c_int
|
||||
self._lib.irmp_add_one_sample.argtypes = [ ctypes.c_int, ]
|
||||
|
||||
if False:
|
||||
self._lib.irmp_detect_buffer.restype = self.ResultData
|
||||
self._lib.irmp_detect_buffer.argtypes = [ ctypes.POINTER(ctypes.c_uint8), ctypes.c_size_t, ]
|
||||
|
||||
self._lib.irmp_get_result_data.restype = ctypes.c_int
|
||||
self._lib.irmp_get_result_data.argtypes = [ ctypes.POINTER(self.ResultData), ]
|
||||
|
||||
self._lib.irmp_get_protocol_name.restype = ctypes.c_char_p
|
||||
self._lib.irmp_get_protocol_name.argtypes = [ ctypes.c_uint32, ]
|
||||
|
||||
# Create a result buffer that's local to the library instance.
|
||||
self._data = self.ResultData()
|
||||
|
||||
return True
|
||||
|
||||
def __init__(self):
|
||||
'''
|
||||
Create a library instance.
|
||||
'''
|
||||
|
||||
# Only create a working instance for the first invocation.
|
||||
# Degrade all other instances, make them fail "late" during
|
||||
# execution, so that users will see the errors.
|
||||
self._lib = None
|
||||
self._data = None
|
||||
if IrmpLibrary.__usable_instance is None:
|
||||
filename = self._library_filename()
|
||||
self._lib = ctypes.cdll.LoadLibrary(filename)
|
||||
self._library_setup_api()
|
||||
IrmpLibrary.__usable_instance = self
|
||||
|
||||
def get_sample_rate(self):
|
||||
if not self._lib:
|
||||
return None
|
||||
return self._lib.irmp_get_sample_rate()
|
||||
|
||||
def reset_state(self):
|
||||
if not self._lib:
|
||||
return None
|
||||
self._lib.irmp_reset_state()
|
||||
|
||||
def add_one_sample(self, level):
|
||||
if not self._lib:
|
||||
raise Exception("IRMP library limited to a single instance.")
|
||||
if not self._lib.irmp_add_one_sample(int(level)):
|
||||
return False
|
||||
self._lib.irmp_get_result_data(ctypes.byref(self._data))
|
||||
return True
|
||||
|
||||
def get_result_data(self):
|
||||
if not self._data:
|
||||
return None
|
||||
return {
|
||||
'proto_nr': self._data.protocol,
|
||||
'proto_name': self._data.protocol_name.decode('UTF-8', 'ignore'),
|
||||
'address': self._data.address,
|
||||
'command': self._data.command,
|
||||
'repeat': bool(self._data.flags & self.FLAG_REPETITION),
|
||||
'release': bool(self._data.flags & self.FLAG_RELEASE),
|
||||
'start': self._data.start_sample,
|
||||
'end': self._data.end_sample,
|
||||
}
|
||||
137
libsigrokdecode4DSL/decoders/ir_irmp/pd.py
Normal file
137
libsigrokdecode4DSL/decoders/ir_irmp/pd.py
Normal file
@@ -0,0 +1,137 @@
|
||||
##
|
||||
## This file is part of the libsigrokdecode project.
|
||||
##
|
||||
## Copyright (C) 2014 Gump Yang <gump.yang@gmail.com>
|
||||
## Copyright (C) 2019 Rene Staffen
|
||||
##
|
||||
## 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
|
||||
## the Free Software Foundation; either version 2 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
from . import irmp_library
|
||||
import sigrokdecode as srd
|
||||
|
||||
class SamplerateError(Exception):
|
||||
pass
|
||||
|
||||
class LibraryError(Exception):
|
||||
pass
|
||||
|
||||
class Decoder(srd.Decoder):
|
||||
api_version = 3
|
||||
id = 'ir_irmp'
|
||||
name = 'IR IRMP'
|
||||
longname = 'IR IRMP'
|
||||
desc = 'IRMP infrared remote control multi protocol.'
|
||||
license = 'gplv2+'
|
||||
inputs = ['logic']
|
||||
outputs = []
|
||||
tags = ['IR']
|
||||
channels = (
|
||||
{'id': 'ir', 'name': 'IR', 'desc': 'Data line'},
|
||||
)
|
||||
options = (
|
||||
{'id': 'polarity', 'desc': 'Polarity', 'default': 'active-low',
|
||||
'values': ('active-low', 'active-high')},
|
||||
)
|
||||
annotations = (
|
||||
('packet', 'Packet'),
|
||||
)
|
||||
annotation_rows = (
|
||||
('packets', 'IR Packets', (0,)),
|
||||
)
|
||||
|
||||
def putframe(self, data):
|
||||
'''Emit annotation for an IR frame.'''
|
||||
|
||||
# Cache result data fields in local variables. Get the ss/es
|
||||
# timestamps, scaled to sample numbers.
|
||||
nr = data['proto_nr']
|
||||
name = data['proto_name']
|
||||
addr = data['address']
|
||||
cmd = data['command']
|
||||
repeat = data['repeat']
|
||||
release = data['release']
|
||||
ss = data['start'] * self.rate_factor
|
||||
es = data['end'] * self.rate_factor
|
||||
|
||||
# Prepare display texts for several zoom levels.
|
||||
# Implementor's note: Keep list lengths for flags aligned during
|
||||
# maintenance. Make sure there are as many flags text variants
|
||||
# as are referenced by annotation text variants. Differing list
|
||||
# lengths or dynamic refs will severely complicate the logic.
|
||||
rep_txts = ['repeat', 'rep', 'r']
|
||||
rel_txts = ['release', 'rel', 'R']
|
||||
flag_txts = [None,] * len(rep_txts)
|
||||
for zoom in range(len(flag_txts)):
|
||||
flag_txts[zoom] = []
|
||||
if repeat:
|
||||
flag_txts[zoom].append(rep_txts[zoom])
|
||||
if release:
|
||||
flag_txts[zoom].append(rel_txts[zoom])
|
||||
flag_txts = [' '.join(t) or '-' for t in flag_txts]
|
||||
flg = flag_txts # Short name for .format() references.
|
||||
txts = [
|
||||
'Protocol: {name} ({nr}), Address 0x{addr:04x}, Command: 0x{cmd:04x}, Flags: {flg[0]}'.format(**locals()),
|
||||
'P: {name} ({nr}), Addr: 0x{addr:x}, Cmd: 0x{cmd:x}, Flg: {flg[1]}'.format(**locals()),
|
||||
'P: {nr} A: 0x{addr:x} C: 0x{cmd:x} F: {flg[1]}'.format(**locals()),
|
||||
'C:{cmd:x} A:{addr:x} {flg[2]}'.format(**locals()),
|
||||
'C:{cmd:x}'.format(**locals()),
|
||||
]
|
||||
|
||||
# Emit the annotation from details which were constructed above.
|
||||
self.put(ss, es, self.out_ann, [0, txts])
|
||||
|
||||
def __init__(self):
|
||||
self.irmp = None
|
||||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
self.want_reset = True
|
||||
|
||||
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 decode(self):
|
||||
if not self.irmp:
|
||||
try:
|
||||
self.irmp = irmp_library.IrmpLibrary()
|
||||
except Exception as e:
|
||||
txt = e.args[0]
|
||||
raise LibraryError(txt)
|
||||
if self.irmp:
|
||||
self.lib_rate = self.irmp.get_sample_rate()
|
||||
if not self.irmp or not self.lib_rate:
|
||||
raise LibraryError('Cannot access IRMP library. One instance limit exceeded?')
|
||||
if not self.samplerate:
|
||||
raise SamplerateError('Cannot decode without samplerate.')
|
||||
if self.samplerate % self.lib_rate:
|
||||
raise SamplerateError('Capture samplerate must be multiple of library samplerate ({})'.format(self.lib_rate))
|
||||
self.rate_factor = int(self.samplerate / self.lib_rate)
|
||||
if self.want_reset:
|
||||
self.irmp.reset_state()
|
||||
self.want_reset = False
|
||||
|
||||
self.active = 0 if self.options['polarity'] == 'active-low' else 1
|
||||
ir, = self.wait()
|
||||
while True:
|
||||
if self.active == 1:
|
||||
ir = 1 - ir
|
||||
if self.irmp.add_one_sample(ir):
|
||||
data = self.irmp.get_result_data()
|
||||
self.putframe(data)
|
||||
ir, = self.wait([{'skip': self.rate_factor}])
|
||||
24
libsigrokdecode4DSL/decoders/ir_rc6/__init__.py
Normal file
24
libsigrokdecode4DSL/decoders/ir_rc6/__init__.py
Normal file
@@ -0,0 +1,24 @@
|
||||
##
|
||||
## This file is part of the libsigrokdecode project.
|
||||
##
|
||||
## Copyright (C) 2019 Benedikt Otto <benedikt_o@web.de>
|
||||
##
|
||||
## 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
|
||||
## the Free Software Foundation; either version 2 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
'''
|
||||
RC-6 is a biphase/manchester based infrared remote control protocol.
|
||||
'''
|
||||
|
||||
from .pd import Decoder
|
||||
205
libsigrokdecode4DSL/decoders/ir_rc6/pd.py
Normal file
205
libsigrokdecode4DSL/decoders/ir_rc6/pd.py
Normal file
@@ -0,0 +1,205 @@
|
||||
##
|
||||
## This file is part of the libsigrokdecode project.
|
||||
##
|
||||
## Copyright (C) 2019 Benedikt Otto <benedikt_o@web.de>
|
||||
##
|
||||
## 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
|
||||
## the Free Software Foundation; either version 2 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
import sigrokdecode as srd
|
||||
|
||||
class SamplerateError(Exception):
|
||||
pass
|
||||
|
||||
class Decoder(srd.Decoder):
|
||||
api_version = 3
|
||||
id = 'ir_rc6'
|
||||
name = 'IR RC-6'
|
||||
longname = 'IR RC-6'
|
||||
desc = 'RC-6 infrared remote control protocol.'
|
||||
license = 'gplv2+'
|
||||
inputs = ['logic']
|
||||
outputs = []
|
||||
tags = ['IR']
|
||||
channels = (
|
||||
{'id': 'ir', 'name': 'IR', 'desc': 'IR data line'},
|
||||
)
|
||||
options = (
|
||||
{'id': 'polarity', 'desc': 'Polarity', 'default': 'auto',
|
||||
'values': ('auto', 'active-low', 'active-high')},
|
||||
)
|
||||
annotations = (
|
||||
('bit', 'Bit'),
|
||||
('sync', 'Sync'),
|
||||
('startbit', 'Startbit'),
|
||||
('field', 'Field'),
|
||||
('togglebit', 'Togglebit'),
|
||||
('address', 'Address'),
|
||||
('command', 'Command'),
|
||||
)
|
||||
annotation_rows = (
|
||||
('bits', 'Bits', (0,)),
|
||||
('fields', 'Fields', (1, 2, 3, 4, 5, 6)),
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
self.samplerate = None
|
||||
self.edges, self.deltas, self.bits = [], [], []
|
||||
self.state = 'IDLE'
|
||||
self.mode = 0
|
||||
|
||||
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
|
||||
# One bit: 0.889ms (one half low, one half high).
|
||||
self.halfbit = int((self.samplerate * 0.000889) / 2.0)
|
||||
|
||||
def putb(self, bit, data):
|
||||
self.put(bit[0], bit[1], self.out_ann, data)
|
||||
|
||||
def putbits(self, bit1, bit2, data):
|
||||
self.put(bit1[0], bit2[1], self.out_ann, data)
|
||||
|
||||
def putx(self, ss, es, data):
|
||||
self.put(ss, es, self.out_ann, data)
|
||||
|
||||
def handle_bit(self):
|
||||
if len(self.bits) != 6:
|
||||
return
|
||||
if self.bits[0][2] == 8 and self.bits[0][3] == 1:
|
||||
self.putb(self.bits[0], [1, ['Synchronisation', 'Sync']])
|
||||
else:
|
||||
return
|
||||
if self.bits[1][3] == 1:
|
||||
self.putb(self.bits[1], [2, ['Startbit', 'Start']])
|
||||
else:
|
||||
return
|
||||
self.mode = sum([self.bits[2 + i][3] << (2 - i) for i in range(3)])
|
||||
self.putbits(self.bits[2], self.bits[4], [3, ['Field: %d' % self.mode]])
|
||||
self.putb(self.bits[5], [4, ['Toggle: %d' % self.bits[5][3]]])
|
||||
|
||||
def handle_package(self):
|
||||
# Sync and start bits have to be 1.
|
||||
if self.bits[0][3] == 0 or self.bits[1][3] == 0:
|
||||
return
|
||||
if len(self.bits) <= 6:
|
||||
return
|
||||
|
||||
if self.mode == 0 and len(self.bits) == 22: # Mode 0 standard
|
||||
value = sum([self.bits[6 + i][3] << (7 - i) for i in range(8)])
|
||||
self.putbits(self.bits[6], self.bits[13], [5, ['Address: %0.2X' % value]])
|
||||
|
||||
value = sum([self.bits[14 + i][3] << (7 - i) for i in range(8)])
|
||||
self.putbits(self.bits[14], self.bits[21], [6, ['Data: %0.2X' % value]])
|
||||
|
||||
self.bits = []
|
||||
|
||||
if self.mode == 6 and len(self.bits) >= 15: # Mode 6
|
||||
if self.bits[6][3] == 0: # Short addr, Mode 6A
|
||||
value = sum([self.bits[6 + i][3] << (7 - i) for i in range(8)])
|
||||
self.putbits(self.bits[6], self.bits[13], [5, ['Address: %0.2X' % value]])
|
||||
|
||||
num_data_bits = len(self.bits) - 14
|
||||
value = sum([self.bits[14 + i][3] << (num_data_bits - 1 - i) for i in range(num_data_bits)])
|
||||
self.putbits(self.bits[14], self.bits[-1], [6, ['Data: %X' % value]])
|
||||
|
||||
self.bits = []
|
||||
|
||||
elif len(self.bits) >= 23: # Long addr, Mode 6B
|
||||
value = sum([self.bits[6 + i][3] << (15 - i) for i in range(16)])
|
||||
self.putbits(self.bits[6], self.bits[21], [5, ['Address: %0.2X' % value]])
|
||||
|
||||
num_data_bits = len(self.bits) - 22
|
||||
value = sum([self.bits[22 + i][3] << (num_data_bits - 1 - i) for i in range(num_data_bits)])
|
||||
self.putbits(self.bits[22], self.bits[-1], [6, ['Data: %X' % value]])
|
||||
|
||||
self.bits = []
|
||||
|
||||
def decode(self):
|
||||
if not self.samplerate:
|
||||
raise SamplerateError('Cannot decode without samplerate.')
|
||||
value = 0
|
||||
num_edges = -1
|
||||
self.invert = False
|
||||
|
||||
while True:
|
||||
conditions = [{0: 'e'}]
|
||||
if self.state == 'DATA':
|
||||
conditions.append({'skip': self.halfbit * 6})
|
||||
(self.ir,) = self.wait(conditions)
|
||||
|
||||
if len(conditions) == 2:
|
||||
if self.matched[1]:
|
||||
self.state = 'IDLE'
|
||||
|
||||
self.edges.append(self.samplenum)
|
||||
if len(self.edges) < 2:
|
||||
continue
|
||||
|
||||
delta = (self.edges[-1] - self.edges[-2]) / self.halfbit
|
||||
delta = int(delta + 0.5)
|
||||
self.deltas.append(delta)
|
||||
|
||||
if len(self.deltas) < 2:
|
||||
continue
|
||||
|
||||
if self.deltas[-2:] == [6, 2]:
|
||||
self.state = 'SYNC'
|
||||
num_edges = 0
|
||||
self.bits = []
|
||||
|
||||
if self.options['polarity'] == 'auto':
|
||||
value = 1
|
||||
else:
|
||||
value = self.ir if self.options['polarity'] == 'active-high' else 1 - self.ir
|
||||
|
||||
self.bits.append((self.edges[-3], self.edges[-1], 8, value))
|
||||
self.invert = self.ir == 0
|
||||
self.putb(self.bits[-1], [0, ['%d' % value]]) # Add bit.
|
||||
|
||||
if (num_edges % 2) == 0: # Only count every second edge.
|
||||
if self.deltas[-2] in [1, 2, 3] and self.deltas[-1] in [1, 2, 3, 6]:
|
||||
self.state = 'DATA'
|
||||
if self.deltas[-2] != self.deltas[-1]:
|
||||
# Insert border between 2 bits.
|
||||
self.edges.insert(-1, self.edges[-2] + self.deltas[-2] * self.halfbit)
|
||||
total = self.deltas[-1]
|
||||
self.deltas[-1] = self.deltas[-2]
|
||||
self.deltas.append(total - self.deltas[-1])
|
||||
|
||||
self.bits.append((self.edges[-4], self.edges[-2], self.deltas[-2] * 2, value))
|
||||
|
||||
num_edges += 1
|
||||
else:
|
||||
self.bits.append((self.edges[-3], self.edges[-1], self.deltas[-1] * 2, value))
|
||||
|
||||
self.putb(self.bits[-1], [0, ['%d' % value]]) # Add bit.
|
||||
|
||||
if len(self.bits) > 0:
|
||||
self.handle_bit()
|
||||
if self.state == 'IDLE':
|
||||
self.handle_package()
|
||||
|
||||
if self.options['polarity'] == 'auto':
|
||||
value = self.ir if self.invert else 1 - self.ir
|
||||
else:
|
||||
value = self.ir if self.options['polarity'] == 'active-low' else 1 - self.ir
|
||||
|
||||
num_edges += 1
|
||||
26
libsigrokdecode4DSL/decoders/ir_sirc/__init__.py
Normal file
26
libsigrokdecode4DSL/decoders/ir_sirc/__init__.py
Normal file
@@ -0,0 +1,26 @@
|
||||
##
|
||||
## This file is part of the libsigrokdecode project.
|
||||
##
|
||||
## Copyright (C) 2020 Tom Flanagan <knio@zkpq.ca>
|
||||
##
|
||||
## 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
|
||||
## the Free Software Foundation; either version 2 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
'''
|
||||
Decoder for the Sony IR remote control protocol (SIRC).
|
||||
|
||||
https://www.sbprojects.net/knowledge/ir/sirc.php
|
||||
'''
|
||||
|
||||
from .pd import Decoder
|
||||
201
libsigrokdecode4DSL/decoders/ir_sirc/lists.py
Normal file
201
libsigrokdecode4DSL/decoders/ir_sirc/lists.py
Normal file
@@ -0,0 +1,201 @@
|
||||
##
|
||||
## This file is part of the libsigrokdecode project.
|
||||
##
|
||||
## Copyright (C) 2020 Tom Flanagan <knio@zkpq.ca>
|
||||
##
|
||||
## 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
|
||||
## the Free Software Foundation; either version 2 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
NUMBERS = {
|
||||
0x00: '1',
|
||||
0x01: '2',
|
||||
0x02: '3',
|
||||
0x03: '4',
|
||||
0x04: '5',
|
||||
0x05: '6',
|
||||
0x06: '7',
|
||||
0x07: '8',
|
||||
0x08: '9',
|
||||
0x09: '0/10',
|
||||
}
|
||||
|
||||
ADDRESSES = {
|
||||
# TV
|
||||
(0x01, None): (['TV: ', 'TV:'], {
|
||||
0x15: 'Power',
|
||||
0x25: 'Input',
|
||||
|
||||
0x33: 'Right',
|
||||
0x34: 'Left',
|
||||
0x3A: 'Display',
|
||||
|
||||
0x60: 'Home',
|
||||
0x65: 'Enter',
|
||||
|
||||
0x74: 'Up',
|
||||
0x75: 'Down',
|
||||
|
||||
}),
|
||||
|
||||
# Video
|
||||
(0x0B, None): (['Video: ', 'V:'], {
|
||||
0x18: 'Stop',
|
||||
0x19: 'Pause',
|
||||
0x1A: 'Play',
|
||||
0x1B: 'Rewind',
|
||||
0x1C: 'Fast Forward',
|
||||
|
||||
0x42: 'Up',
|
||||
0x43: 'Down',
|
||||
0x4D: 'Home',
|
||||
|
||||
0x51: 'Enter',
|
||||
0x5A: 'Display',
|
||||
|
||||
0x61: 'Right',
|
||||
0x62: 'Left',
|
||||
}),
|
||||
|
||||
# BR Input select
|
||||
(0x10, 0x28): (['BlueRay: ', 'BR:'], {
|
||||
0x16: 'BlueRay',
|
||||
}),
|
||||
|
||||
# Amp, Game, Sat, Tuner, USB
|
||||
(0x10, 0x08): (['Playback: ', 'PB:'], {
|
||||
0x2A: 'Shuffle',
|
||||
0x2C: 'Repeat',
|
||||
0x2E: 'Folder Down',
|
||||
0x2F: 'Folder Up',
|
||||
|
||||
0x30: 'Previous',
|
||||
0x31: 'Next',
|
||||
0x32: 'Play',
|
||||
0x33: 'Rewind',
|
||||
0x34: 'Fast Forward',
|
||||
0x38: 'Stop',
|
||||
0x39: 'Pause',
|
||||
|
||||
0x73: 'Options',
|
||||
0x7D: 'Return',
|
||||
}),
|
||||
|
||||
# CD
|
||||
(0x11, None): (['CD: ', 'CD:'], {
|
||||
0x28: 'Display',
|
||||
|
||||
0x30: 'Previous',
|
||||
0x31: 'Next',
|
||||
0x32: 'Play',
|
||||
0x33: 'Rewind',
|
||||
0x34: 'Fast Forward',
|
||||
0x38: 'Stop',
|
||||
0x39: 'Pause',
|
||||
}),
|
||||
|
||||
# BD
|
||||
(0x1A, 0xE2): (['BlueRay: ', 'BD:'], {
|
||||
0x18: 'Stop',
|
||||
0x19: 'Pause',
|
||||
0x1A: 'Play',
|
||||
0x1B: 'Rewind',
|
||||
0x1C: 'Fast Forward',
|
||||
|
||||
0x29: 'Menu',
|
||||
0x2C: 'Top Menu',
|
||||
|
||||
0x39: 'Up',
|
||||
0x3A: 'Down',
|
||||
0x3B: 'Left',
|
||||
0x3C: 'Right',
|
||||
0x3D: 'Enter',
|
||||
0x3F: 'Options',
|
||||
|
||||
0x41: 'Display',
|
||||
0x42: 'Home',
|
||||
0x43: 'Return',
|
||||
|
||||
0x56: 'Next',
|
||||
0x57: 'Previous',
|
||||
}),
|
||||
|
||||
# DVD
|
||||
(0x1A, 0x49): (['DVD: ', 'DVD:'], {
|
||||
0x0B: 'Enter',
|
||||
0x0E: 'Return',
|
||||
0x17: 'Options',
|
||||
|
||||
0x1A: 'Top Menu',
|
||||
0x1B: 'Menu',
|
||||
|
||||
0x30: 'Previous',
|
||||
0x31: 'Next',
|
||||
0x32: 'Play',
|
||||
0x33: 'Rewind',
|
||||
0x34: 'Fast Forward',
|
||||
0x38: 'Stop',
|
||||
0x39: 'Pause',
|
||||
|
||||
0x54: 'Display',
|
||||
|
||||
0x7B: 'Left',
|
||||
0x7C: 'Right',
|
||||
0x79: 'Up',
|
||||
0x7A: 'Down',
|
||||
}),
|
||||
|
||||
# Amp, Game, Sat, Tuner, USB modes
|
||||
(0x30, None): (['Keypad: ', 'KP:'], {
|
||||
0x0C: 'Enter',
|
||||
|
||||
0x12: 'Volume Up',
|
||||
0x13: 'Volume Down',
|
||||
0x14: 'Mute',
|
||||
0x15: 'Power',
|
||||
|
||||
0x21: 'Tuner',
|
||||
0x22: 'Video',
|
||||
0x25: 'CD',
|
||||
|
||||
0x4D: 'Home',
|
||||
0x4B: 'Display',
|
||||
|
||||
0x60: 'Sleep',
|
||||
0x6A: 'TV',
|
||||
|
||||
0x53: 'Home',
|
||||
|
||||
0x7C: 'Game',
|
||||
0x7D: 'DVD',
|
||||
}),
|
||||
|
||||
# Amp, Game, Sat, Tuner, USB modes
|
||||
(0xB0, None): (['Arrows: ', 'Ar:'], {
|
||||
0x7A: 'Left',
|
||||
0x7B: 'Right',
|
||||
0x78: 'Up',
|
||||
0x79: 'Down',
|
||||
0x77: 'Amp Menu',
|
||||
}),
|
||||
|
||||
# TV mode
|
||||
(0x97, None): (['TV Extra', 'TV:'], {
|
||||
0x23: 'Return',
|
||||
0x36: 'Options',
|
||||
|
||||
}),
|
||||
}
|
||||
|
||||
for (address, extended), (name, commands) in ADDRESSES.items():
|
||||
commands.update(NUMBERS)
|
||||
215
libsigrokdecode4DSL/decoders/ir_sirc/pd.py
Normal file
215
libsigrokdecode4DSL/decoders/ir_sirc/pd.py
Normal file
@@ -0,0 +1,215 @@
|
||||
##
|
||||
## This file is part of the libsigrokdecode project.
|
||||
##
|
||||
## Copyright (C) 2020 Tom Flanagan <knio@zkpq.ca>
|
||||
##
|
||||
## 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
|
||||
## the Free Software Foundation; either version 2 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
from common.srdhelper import bitpack_lsb
|
||||
from .lists import ADDRESSES
|
||||
import sigrokdecode as srd
|
||||
|
||||
class SamplerateError(Exception):
|
||||
pass
|
||||
|
||||
class SIRCError(Exception):
|
||||
pass
|
||||
|
||||
class SIRCErrorSilent(SIRCError):
|
||||
pass
|
||||
|
||||
class Ann:
|
||||
BIT, AGC, PAUSE, START, CMD, ADDR, EXT, REMOTE, WARN = range(9)
|
||||
|
||||
AGC_USEC = 2400
|
||||
ONE_USEC = 1200
|
||||
ZERO_USEC = 600
|
||||
PAUSE_USEC = 600
|
||||
|
||||
class Decoder(srd.Decoder):
|
||||
api_version = 3
|
||||
id = 'ir_sirc'
|
||||
name = 'IR SIRC'
|
||||
longname = 'Sony IR (SIRC)'
|
||||
desc = 'Sony infrared remote control protocol (SIRC).'
|
||||
license = 'gplv2+'
|
||||
tags = ['IR']
|
||||
inputs = ['logic']
|
||||
outputs = []
|
||||
channels = (
|
||||
{'id': 'ir', 'name': 'IR', 'desc': 'IR data line'},
|
||||
)
|
||||
options = (
|
||||
{'id': 'polarity', 'desc': 'Polarity', 'default': 'active-low',
|
||||
'values': ('active-low', 'active-high')},
|
||||
)
|
||||
annotations = (
|
||||
('bit', 'Bit'),
|
||||
('agc', 'AGC'),
|
||||
('pause', 'Pause'),
|
||||
('start', 'Start'),
|
||||
('command', 'Command'),
|
||||
('address', 'Address'),
|
||||
('extended', 'Extended'),
|
||||
('remote', 'Remote'),
|
||||
('warning', 'Warning'),
|
||||
)
|
||||
annotation_rows = (
|
||||
('bits', 'Bits', (Ann.BIT, Ann.AGC, Ann.PAUSE)),
|
||||
('fields', 'Fields', (Ann.START, Ann.CMD, Ann.ADDR, Ann.EXT)),
|
||||
('remotes', 'Remotes', (Ann.REMOTE,)),
|
||||
('warnings', 'Warnings', (Ann.WARN,)),
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
pass
|
||||
|
||||
def start(self):
|
||||
self.out_ann = self.register(srd.OUTPUT_ANN)
|
||||
self.active = self.options['polarity'] == 'active-high'
|
||||
|
||||
def metadata(self, key, value):
|
||||
if key == srd.SRD_CONF_SAMPLERATE:
|
||||
self.samplerate = value
|
||||
self.snum_per_us = self.samplerate / 1e6
|
||||
|
||||
def putg(self, ss, es, cls, texts):
|
||||
self.put(ss, es, self.out_ann, [cls, texts])
|
||||
|
||||
def tolerance(self, ss, es, expected):
|
||||
microseconds = (es - ss) / self.snum_per_us
|
||||
tolerance = expected * 0.30
|
||||
return (expected - tolerance) < microseconds < (expected + tolerance)
|
||||
|
||||
def wait_wrap(self, conds, timeout):
|
||||
if timeout is not None:
|
||||
to = int(timeout * self.snum_per_us)
|
||||
conds.append({'skip': to})
|
||||
ss = self.samplenum
|
||||
pins = self.wait(conds)
|
||||
es = self.samplenum
|
||||
return pins, ss, es, self.matched
|
||||
|
||||
def read_pulse(self, high, time):
|
||||
e = 'f' if high else 'r'
|
||||
max_time = int(time * 1.30)
|
||||
(ir,), ss, es, (edge, timeout) = self.wait_wrap([{0: e}], max_time)
|
||||
if timeout or not self.tolerance(ss, es, time):
|
||||
raise SIRCError('Timeout')
|
||||
return ir, ss, es, (edge, timeout)
|
||||
|
||||
def read_bit(self):
|
||||
e = 'f' if self.active else 'r'
|
||||
_, high_ss, high_es, (edge, timeout) = self.wait_wrap([{0: e}], 2000)
|
||||
if timeout:
|
||||
raise SIRCError('Bit High Timeout')
|
||||
if self.tolerance(high_ss, high_es, ONE_USEC):
|
||||
bit = 1
|
||||
elif self.tolerance(high_ss, high_es, ZERO_USEC):
|
||||
bit = 0
|
||||
else:
|
||||
raise SIRCError('Bit Low Timeout')
|
||||
try:
|
||||
_, low_ss, low_es, _ = self.read_pulse(not self.active, PAUSE_USEC)
|
||||
good = True
|
||||
except SIRCError:
|
||||
low_es = high_es + int(PAUSE_USEC * self.snum_per_us)
|
||||
good = False
|
||||
self.putg(high_ss, low_es, Ann.BIT, ['{}'.format(bit)])
|
||||
return bit, high_ss, low_es, good
|
||||
|
||||
def read_signal(self):
|
||||
# Start code
|
||||
try:
|
||||
_, agc_ss, agc_es, _ = self.read_pulse(self.active, AGC_USEC)
|
||||
_, pause_ss, pause_es, _ = self.read_pulse(not self.active, PAUSE_USEC)
|
||||
except SIRCError:
|
||||
raise SIRCErrorSilent('not an SIRC message')
|
||||
self.putg(agc_ss, agc_es, Ann.AGC, ['AGC', 'A'])
|
||||
self.putg(pause_ss, pause_es, Ann.PAUSE, ['Pause', 'P'])
|
||||
self.putg(agc_ss, pause_es, Ann.START, ['Start', 'S'])
|
||||
|
||||
# Read bits
|
||||
bits = []
|
||||
while True:
|
||||
bit, ss, es, good = self.read_bit()
|
||||
bits.append((bit, ss, es))
|
||||
if len(bits) > 20:
|
||||
raise SIRCError('too many bits')
|
||||
if not good:
|
||||
if len(bits) == 12:
|
||||
command = bits[0:7]
|
||||
address = bits[7:12]
|
||||
extended = []
|
||||
elif len(bits) == 15:
|
||||
command = bits[0:7]
|
||||
address = bits[7:15]
|
||||
extended = []
|
||||
elif len(bits) == 20:
|
||||
command = bits[0:7]
|
||||
address = bits[7:12]
|
||||
extended = bits[12:20]
|
||||
else:
|
||||
raise SIRCError('incorrect bits count {}'.format(len(bits)))
|
||||
break
|
||||
|
||||
command_num = bitpack_lsb(command, 0)
|
||||
address_num = bitpack_lsb(address, 0)
|
||||
command_str = '0x{:02X}'.format(command_num)
|
||||
address_str = '0x{:02X}'.format(address_num)
|
||||
self.putg(command[0][1], command[-1][2], Ann.CMD, [
|
||||
'Command: {}'.format(command_str),
|
||||
'C:{}'.format(command_str),
|
||||
])
|
||||
self.putg(address[0][1], address[-1][2], Ann.ADDR, [
|
||||
'Address: {}'.format(address_str),
|
||||
'A:{}'.format(address_str),
|
||||
])
|
||||
extended_num = None
|
||||
if extended:
|
||||
extended_num = bitpack_lsb(extended, 0)
|
||||
extended_str = '0x{:02X}'.format(extended_num)
|
||||
self.putg(extended[0][1], extended[-1][2], Ann.EXT, [
|
||||
'Extended: {}'.format(extended_str),
|
||||
'E:{}'.format(extended_str),
|
||||
])
|
||||
return address_num, command_num, extended_num, bits[0][1], bits[-1][2]
|
||||
|
||||
def decode(self):
|
||||
if not self.samplerate:
|
||||
raise SamplerateError('Cannot decode without samplerate.')
|
||||
|
||||
unknown = (['Unknown Device: ', 'UNK: '], {})
|
||||
while True:
|
||||
e = 'h' if self.active else 'l'
|
||||
_, _, frame_ss, _ = self.wait_wrap([{0: e}], None)
|
||||
try:
|
||||
addr, cmd, ext, payload_ss, payload_es = self.read_signal()
|
||||
names, cmds = ADDRESSES.get((addr, ext), unknown)
|
||||
text = cmds.get(cmd, 'Unknown')
|
||||
self.putg(frame_ss, payload_es, Ann.REMOTE, [
|
||||
n + text for n in names
|
||||
])
|
||||
except SIRCErrorSilent as e:
|
||||
pass
|
||||
except SIRCError as e:
|
||||
self.putg(frame_ss, self.samplenum, Ann.WARN, [
|
||||
'Error: {}'.format(e),
|
||||
'Error',
|
||||
'E',
|
||||
])
|
||||
35
libsigrokdecode4DSL/decoders/lfast/__init__.py
Normal file
35
libsigrokdecode4DSL/decoders/lfast/__init__.py
Normal file
@@ -0,0 +1,35 @@
|
||||
##
|
||||
## This file is part of the libsigrokdecode project.
|
||||
##
|
||||
## Copyright (C) 2020 Soeren Apel <soeren@apelpie.net>
|
||||
##
|
||||
## 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
|
||||
## the Free Software Foundation; either version 2 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
'''
|
||||
LFAST is a physical communication interface used mainly by the NXP Zipwire
|
||||
interface. It's a framed asynchronous serial interface using differential
|
||||
TX/RX pairs, capable of data rates of up to 320 MBit/s.
|
||||
|
||||
This interface is also provided by Infineon as HSCT.
|
||||
|
||||
As with most differential signals, it's sufficient to measure TXP or RXP, no
|
||||
need for a differential probe. The REFCLK used by the hardware isn't needed by
|
||||
this protocol decoder either.
|
||||
|
||||
For details see https://www.nxp.com/docs/en/application-note/AN5134.pdf and
|
||||
https://hitex.co.uk/fileadmin/uk-files/downloads/ShieldBuddy/tc27xD_um_v2.2.pdf
|
||||
'''
|
||||
|
||||
from .pd import Decoder
|
||||
335
libsigrokdecode4DSL/decoders/lfast/pd.py
Normal file
335
libsigrokdecode4DSL/decoders/lfast/pd.py
Normal file
@@ -0,0 +1,335 @@
|
||||
##
|
||||
## This file is part of the libsigrokdecode project.
|
||||
##
|
||||
## Copyright (C) 2020 Soeren Apel <soeren@apelpie.net>
|
||||
##
|
||||
## 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
|
||||
## the Free Software Foundation; either version 2 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
import sigrokdecode as srd
|
||||
from common.srdhelper import bitpack
|
||||
import decimal
|
||||
|
||||
'''
|
||||
OUTPUT_PYTHON format:
|
||||
|
||||
[ss, es, data] where data is a data byte of the LFAST payload. All bytes of
|
||||
the payload are sent at once, each with its start and end sample.
|
||||
'''
|
||||
|
||||
# See tc27xD_um_v2.2.pdf, Table 20-10
|
||||
payload_sizes = {
|
||||
0b000: '8 bit',
|
||||
0b001: '32 bit / 4 byte',
|
||||
0b010: '64 bit / 8 byte',
|
||||
0b011: '96 bit / 12 byte',
|
||||
0b100: '128 bit / 16 byte',
|
||||
0b101: '256 bit / 32 byte',
|
||||
0b110: '512 bit / 64 byte',
|
||||
0b111: '288 bit / 36 byte'
|
||||
}
|
||||
|
||||
# See tc27xD_um_v2.2.pdf, Table 20-10
|
||||
payload_byte_sizes = {
|
||||
0b000: 1,
|
||||
0b001: 4,
|
||||
0b010: 8,
|
||||
0b011: 12,
|
||||
0b100: 16,
|
||||
0b101: 32,
|
||||
0b110: 64,
|
||||
0b111: 36
|
||||
}
|
||||
|
||||
# See tc27xD_um_v2.2.pdf, Table 20-11
|
||||
channel_types = {
|
||||
0b0000: 'Interface Control / PING',
|
||||
0b0001: 'Unsolicited Status (32 bit)',
|
||||
0b0010: 'Slave Interface Control / Read',
|
||||
0b0011: 'CTS Transfer',
|
||||
0b0100: 'Data Channel A',
|
||||
0b0101: 'Data Channel B',
|
||||
0b0110: 'Data Channel C',
|
||||
0b0111: 'Data Channel D',
|
||||
0b1000: 'Data Channel E',
|
||||
0b1001: 'Data Channel F',
|
||||
0b1010: 'Data Channel G',
|
||||
0b1011: 'Data Channel H',
|
||||
0b1100: 'Reserved',
|
||||
0b1101: 'Reserved',
|
||||
0b1110: 'Reserved',
|
||||
0b1111: 'Reserved',
|
||||
}
|
||||
|
||||
# See tc27xD_um_v2.2.pdf, Table 20-12
|
||||
control_payloads = {
|
||||
0x00: 'PING',
|
||||
0x01: 'Reserved',
|
||||
0x02: 'Slave interface clock multiplier start',
|
||||
0x04: 'Slave interface clock multiplier stop',
|
||||
0x08: 'Use 5 MBaud for M->S',
|
||||
0x10: 'Use 320 MBaud for M->S',
|
||||
0x20: 'Use 5 MBaud for S->M',
|
||||
0x40: 'Use 20 MBaud for S->M (needs 20 MHz SysClk)',
|
||||
0x80: 'Use 320 MBaud for S->M',
|
||||
0x31: 'Enable slave interface transmitter',
|
||||
0x32: 'Disable slave interface transmitter',
|
||||
0x34: 'Enable clock test mode',
|
||||
0x38: 'Disable clock test mode and payload loopback',
|
||||
0xFF: 'Enable payload loopback',
|
||||
}
|
||||
|
||||
|
||||
ann_bit, ann_sync, ann_header_pl_size, ann_header_ch_type, ann_header_cts, \
|
||||
ann_payload, ann_control_data, ann_sleepbit, ann_warning = range(9)
|
||||
state_sync, state_header, state_payload, state_sleepbit = range(4)
|
||||
|
||||
class Decoder(srd.Decoder):
|
||||
api_version = 3
|
||||
id = 'lfast'
|
||||
name = 'LFAST'
|
||||
longname = 'NXP LFAST interface'
|
||||
desc = 'Differential high-speed P2P interface'
|
||||
license = 'gplv2+'
|
||||
inputs = ['logic']
|
||||
outputs = ['lfast']
|
||||
tags = ['Embedded/industrial']
|
||||
channels = (
|
||||
{'id': 'data', 'name': 'Data', 'desc': 'TXP or RXP'},
|
||||
)
|
||||
annotations = (
|
||||
('bit', 'Bits'),
|
||||
('sync', 'Sync Pattern'),
|
||||
('header_pl_size', 'Payload Size'),
|
||||
('header_ch_type', 'Logical Channel Type'),
|
||||
('header_cts', 'Clear To Send'),
|
||||
('payload', 'Payload'),
|
||||
('ctrl_data', 'Control Data'),
|
||||
('sleep', 'Sleep Bit'),
|
||||
('warning', 'Warning'),
|
||||
)
|
||||
annotation_rows = (
|
||||
('bits', 'Bits', (ann_bit,)),
|
||||
('fields', 'Fields', (ann_sync, ann_header_pl_size, ann_header_ch_type,
|
||||
ann_header_cts, ann_payload, ann_control_data, ann_sleepbit,)),
|
||||
('warnings', 'Warnings', (ann_warning,)),
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
decimal.getcontext().rounding = decimal.ROUND_HALF_UP
|
||||
self.bit_len = 0xFFFFFFFF
|
||||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
self.prev_bit_len = self.bit_len
|
||||
self.ss = self.es = 0
|
||||
self.ss_payload = self.es_payload = 0
|
||||
self.ss_byte = 0
|
||||
self.bits = []
|
||||
self.payload = []
|
||||
self.payload_size = 0 # Expected number of bytes, as read from header
|
||||
self.bit_len = 0 # Length of one bit time, in samples
|
||||
self.timeout = 0 # Desired timeout for next edge, in samples
|
||||
self.ch_type_id = 0 # ID of channel type
|
||||
self.state = state_sync
|
||||
|
||||
def metadata(self, key, value):
|
||||
pass
|
||||
|
||||
def start(self):
|
||||
self.out_python = self.register(srd.OUTPUT_PYTHON)
|
||||
self.out_ann = self.register(srd.OUTPUT_ANN)
|
||||
|
||||
def put_ann(self, ss, es, ann_class, value):
|
||||
self.put(ss, es, self.out_ann, [ann_class, value])
|
||||
|
||||
def put_payload(self):
|
||||
self.put(self.ss_payload, self.es_payload, self.out_python, self.payload)
|
||||
|
||||
def handle_sync(self):
|
||||
if len(self.bits) == 1:
|
||||
self.ss_sync = self.ss_bit
|
||||
|
||||
if len(self.bits) == 16:
|
||||
value = bitpack(self.bits)
|
||||
if value == 0xA84B:
|
||||
self.put_ann(self.ss_sync, self.es_bit, ann_sync, ['Sync OK'])
|
||||
else:
|
||||
self.put_ann(self.ss_sync, self.es_bit, ann_warning, ['Wrong Sync Value: {:02X}'.format(value)])
|
||||
self.reset()
|
||||
|
||||
# Only continue if we didn't just reset
|
||||
if self.ss > 0:
|
||||
self.bits = []
|
||||
self.state = state_header
|
||||
self.timeout = int(9.4 * self.bit_len)
|
||||
|
||||
def handle_header(self):
|
||||
if len(self.bits) == 1:
|
||||
self.ss_header = self.ss_bit
|
||||
|
||||
if len(self.bits) == 8:
|
||||
# See tc27xD_um_v2.2.pdf, Figure 20-47, for the header structure
|
||||
bit_len = (self.es_bit - self.ss_header) / 8
|
||||
value = bitpack(self.bits)
|
||||
|
||||
ss = self.ss_header
|
||||
es = ss + 3 * bit_len
|
||||
size_id = (value & 0xE0) >> 5
|
||||
size = payload_sizes.get(size_id)
|
||||
self.payload_size = payload_byte_sizes.get(size_id)
|
||||
self.put_ann(int(ss), int(es), ann_header_pl_size, [size])
|
||||
|
||||
ss = es
|
||||
es = ss + 4 * bit_len
|
||||
self.ch_type_id = (value & 0x1E) >> 1
|
||||
ch_type = channel_types.get(self.ch_type_id)
|
||||
self.put_ann(int(ss), int(es), ann_header_ch_type, [ch_type])
|
||||
|
||||
ss = es
|
||||
es = ss + bit_len
|
||||
cts = value & 0x01
|
||||
self.put_ann(int(ss), int(es), ann_header_cts, ['{}'.format(cts)])
|
||||
|
||||
self.bits = []
|
||||
self.state = state_payload
|
||||
self.timeout = int(9.4 * self.bit_len)
|
||||
|
||||
def handle_payload(self):
|
||||
self.timeout = int((self.payload_size - len(self.payload)) * 8 * self.bit_len)
|
||||
|
||||
if len(self.bits) == 1:
|
||||
self.ss_byte = self.ss_bit
|
||||
if self.ss_payload == 0:
|
||||
self.ss_payload = self.ss_bit
|
||||
|
||||
if len(self.bits) == 8:
|
||||
value = bitpack(self.bits)
|
||||
value_hex = '{:02X}'.format(value)
|
||||
|
||||
# Control transfers have no SIPI payload, show them as control transfers
|
||||
# Check the channel_types list for the meaning of the magic values
|
||||
if (self.ch_type_id >= 0b0100) and (self.ch_type_id <= 0b1011):
|
||||
self.put_ann(self.ss_byte, self.es_bit, ann_payload, [value_hex])
|
||||
else:
|
||||
# Control transfers are 8-bit transfers, so only evaluate the first byte
|
||||
if len(self.payload) == 0:
|
||||
ctrl_data = control_payloads.get(value, value_hex)
|
||||
self.put_ann(self.ss_byte, self.es_bit, ann_control_data, [ctrl_data])
|
||||
else:
|
||||
self.put_ann(self.ss_byte, self.es_bit, ann_control_data, [value_hex])
|
||||
|
||||
self.bits = []
|
||||
self.es_payload = self.es_bit
|
||||
self.payload.append((self.ss_byte, self.es_payload, value))
|
||||
|
||||
if (len(self.payload) == self.payload_size):
|
||||
self.timeout = int(1.4 * self.bit_len)
|
||||
self.state = state_sleepbit
|
||||
|
||||
def handle_sleepbit(self):
|
||||
if len(self.bits) == 0:
|
||||
self.put_ann(self.ss_bit, self.es_bit, ann_sleepbit, ['No LVDS sleep mode request', 'No sleep', 'N'])
|
||||
elif len(self.bits) > 1:
|
||||
self.put_ann(self.ss_bit, self.es_bit, ann_warning, ['Expected only the sleep bit, got {} bits instead'.format(len(self.bits))])
|
||||
else:
|
||||
if self.bits[0] == 1:
|
||||
self.put_ann(self.ss_bit, self.es_bit, ann_sleepbit, ['LVDS sleep mode request', 'Sleep', 'Y'])
|
||||
else:
|
||||
self.put_ann(self.ss_bit, self.es_bit, ann_sleepbit, ['No LVDS sleep mode request', 'No sleep', 'N'])
|
||||
|
||||
# We only send the payload out if this is an actual data transfer;
|
||||
# check the channel_types list for the meaning of the magic values
|
||||
if (self.ch_type_id >= 0b0100) and (self.ch_type_id <= 0b1011):
|
||||
if len(self.payload) > 0:
|
||||
self.put_payload()
|
||||
|
||||
def decode(self):
|
||||
while True:
|
||||
if self.timeout == 0:
|
||||
rising_edge, = self.wait({0: 'e'})
|
||||
else:
|
||||
rising_edge, = self.wait([{0: 'e'}, {'skip': self.timeout}])
|
||||
|
||||
# If this is the first edge, we only update ss
|
||||
if self.ss == 0:
|
||||
self.ss = self.samplenum
|
||||
# Let's set the timeout for the sync pattern as well
|
||||
self.timeout = int(16.2 * self.prev_bit_len)
|
||||
continue
|
||||
|
||||
self.es = self.samplenum
|
||||
|
||||
# Check for the sleep bit if this is a timeout condition
|
||||
if (len(self.matched) == 2) and self.matched[1]:
|
||||
rising_edge = ~rising_edge
|
||||
if self.state == state_sync:
|
||||
self.reset()
|
||||
continue
|
||||
elif self.state == state_sleepbit:
|
||||
self.ss_bit += self.bit_len
|
||||
self.es_bit = self.ss_bit + self.bit_len
|
||||
self.handle_sleepbit()
|
||||
self.reset()
|
||||
continue
|
||||
|
||||
# Shouldn't happen but we check just in case
|
||||
if int(self.es - self.ss) == 0:
|
||||
continue
|
||||
|
||||
# We use the first bit to deduce the bit length
|
||||
if self.bit_len == 0:
|
||||
self.bit_len = self.es - self.ss
|
||||
|
||||
# Determine number of bits covered by this edge
|
||||
bit_count = (self.es - self.ss) / self.bit_len
|
||||
bit_count = int(decimal.Decimal(bit_count).to_integral_value())
|
||||
|
||||
if bit_count == 0:
|
||||
self.put_ann(self.ss, self.es, ann_warning, ['Bit time too short'])
|
||||
self.reset()
|
||||
continue
|
||||
|
||||
bit_value = '0' if rising_edge else '1'
|
||||
|
||||
divided_len = (self.es - self.ss) / bit_count
|
||||
for i in range(bit_count):
|
||||
self.ss_bit = int(self.ss + i * divided_len)
|
||||
self.es_bit = int(self.ss_bit + divided_len)
|
||||
self.put_ann(self.ss_bit, self.es_bit, ann_bit, [bit_value])
|
||||
|
||||
# Place the new bit at the front of the bit list
|
||||
self.bits.insert(0, (0 if rising_edge else 1))
|
||||
|
||||
if self.state == state_sync:
|
||||
self.handle_sync()
|
||||
elif self.state == state_header:
|
||||
self.handle_header()
|
||||
elif self.state == state_payload:
|
||||
self.handle_payload()
|
||||
elif self.state == state_sleepbit:
|
||||
self.handle_sleepbit()
|
||||
self.reset()
|
||||
|
||||
if self.ss == 0:
|
||||
break # Because reset() was called, invalidating everything
|
||||
|
||||
# Only update ss if we didn't just perform a reset
|
||||
if self.ss > 0:
|
||||
self.ss = self.samplenum
|
||||
|
||||
# If we got here when a timeout occurred, we have processed all null
|
||||
# bits that we could and should reset now to find the next packet
|
||||
if (len(self.matched) == 2) and self.matched[1]:
|
||||
self.reset()
|
||||
25
libsigrokdecode4DSL/decoders/ltc242x/__init__.py
Normal file
25
libsigrokdecode4DSL/decoders/ltc242x/__init__.py
Normal file
@@ -0,0 +1,25 @@
|
||||
##
|
||||
## This file is part of the libsigrokdecode project.
|
||||
##
|
||||
## Copyright (C) 2020 Analog Devices Inc.
|
||||
##
|
||||
## 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
|
||||
## the Free Software Foundation; either version 3 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
'''
|
||||
This decoder stacks on top of the 'spi' PD and decodes the
|
||||
Linear Technology LTC2421/LTC2422 protocol.
|
||||
'''
|
||||
|
||||
from .pd import Decoder
|
||||
86
libsigrokdecode4DSL/decoders/ltc242x/pd.py
Normal file
86
libsigrokdecode4DSL/decoders/ltc242x/pd.py
Normal file
@@ -0,0 +1,86 @@
|
||||
##
|
||||
## This file is part of the libsigrokdecode project.
|
||||
##
|
||||
## Copyright (C) 2020 Analog Devices Inc.
|
||||
##
|
||||
## 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
|
||||
## the Free Software Foundation; either version 3 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
import sigrokdecode as srd
|
||||
|
||||
input_voltage_format = ['%.6fV', '%.2fV']
|
||||
|
||||
class Decoder(srd.Decoder):
|
||||
api_version = 3
|
||||
id = 'ltc242x'
|
||||
name = 'LTC242x'
|
||||
longname = 'Linear Technology LTC242x'
|
||||
desc = 'Linear Technology LTC2421/LTC2422 1-/2-channel 20-bit ADC.'
|
||||
license = 'gplv2+'
|
||||
inputs = ['spi']
|
||||
outputs = []
|
||||
tags = ['IC', 'Analog/digital']
|
||||
annotations = (
|
||||
('ch0_voltage', 'CH0 voltage'),
|
||||
('ch1_voltage', 'CH1 voltage'),
|
||||
)
|
||||
annotation_rows = (
|
||||
('ch0_voltages', 'CH0 voltages', (0,)),
|
||||
('ch1_voltages', 'CH1 voltages', (1,)),
|
||||
)
|
||||
options = (
|
||||
{'id': 'vref', 'desc': 'Reference voltage (V)', 'default': 1.5},
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
self.data = 0
|
||||
self.ss, self.es = 0, 0
|
||||
|
||||
def start(self):
|
||||
self.out_ann = self.register(srd.OUTPUT_ANN)
|
||||
|
||||
def handle_input_voltage(self, data):
|
||||
input_voltage = data & 0x3FFFFF
|
||||
input_voltage = -(2**21 - input_voltage)
|
||||
input_voltage = (input_voltage / 0xfffff) * self.options['vref']
|
||||
ann = []
|
||||
for format in input_voltage_format:
|
||||
ann.append(format % input_voltage)
|
||||
|
||||
channel = (data & (1 << 22)) >> 22
|
||||
self.put(self.ss, self.es, self.out_ann, [channel, ann])
|
||||
|
||||
def decode(self, ss, es, data):
|
||||
ptype = data[0]
|
||||
|
||||
if ptype == 'CS-CHANGE':
|
||||
cs_old, cs_new = data[1:]
|
||||
if cs_old is not None and cs_old == 0 and cs_new == 1:
|
||||
self.es = es
|
||||
self.data >>= 1
|
||||
self.handle_input_voltage(self.data)
|
||||
|
||||
self.data = 0
|
||||
elif cs_old is not None and cs_old == 1 and cs_new == 0:
|
||||
self.ss = ss
|
||||
|
||||
elif ptype == 'BITS':
|
||||
miso = data[2]
|
||||
for bit in reversed(miso):
|
||||
self.data = self.data | bit[0]
|
||||
|
||||
self.data <<= 1
|
||||
25
libsigrokdecode4DSL/decoders/ltc26x7/__init__.py
Normal file
25
libsigrokdecode4DSL/decoders/ltc26x7/__init__.py
Normal file
@@ -0,0 +1,25 @@
|
||||
##
|
||||
## This file is part of the libsigrokdecode project.
|
||||
##
|
||||
## Copyright (C) 2020 Analog Devices Inc.
|
||||
##
|
||||
## 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
|
||||
## the Free Software Foundation; either version 3 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
'''
|
||||
This decoder stacks on top of the 'i2c' PD and decodes the
|
||||
Linear Technology LTC2607/LTC2617/LTC2627 protocol.
|
||||
'''
|
||||
|
||||
from .pd import Decoder
|
||||
187
libsigrokdecode4DSL/decoders/ltc26x7/pd.py
Normal file
187
libsigrokdecode4DSL/decoders/ltc26x7/pd.py
Normal file
@@ -0,0 +1,187 @@
|
||||
##
|
||||
## This file is part of the libsigrokdecode project.
|
||||
##
|
||||
## Copyright (C) 2020 Analog Devices Inc.
|
||||
##
|
||||
## 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
|
||||
## the Free Software Foundation; either version 3 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
import sigrokdecode as srd
|
||||
|
||||
slave_address = {
|
||||
0x00: ['GND', 'GND', 'GND', 'G'],
|
||||
0x01: ['FLOAT', 'FLOAT', 'FLOAT', 'F'],
|
||||
0x02: ['VCC', 'VCC', 'VCC', 'V'],
|
||||
}
|
||||
|
||||
commands = {
|
||||
0x00: ['Write Input Register', 'Write In Reg', 'Wr In Reg', 'WIR'],
|
||||
0x01: ['Update DAC', 'Update', 'U'],
|
||||
0x03: ['Write and Power Up DAC', 'Write & Power Up', 'W&PU'],
|
||||
0x04: ['Power Down DAC', 'Power Down', 'PD'],
|
||||
0x0F: ['No Operation', 'No Op', 'NO'],
|
||||
}
|
||||
|
||||
addresses = {
|
||||
0x00: ['DAC A', 'A'],
|
||||
0x01: ['DAC B', 'B'],
|
||||
0x0F: ['All DACs', 'All'],
|
||||
}
|
||||
|
||||
input_voltage_format = ['%.6fV', '%.2fV']
|
||||
|
||||
class Decoder(srd.Decoder):
|
||||
api_version = 3
|
||||
id = 'ltc26x7'
|
||||
name = 'LTC26x7'
|
||||
longname = 'Linear Technology LTC26x7'
|
||||
desc = 'Linear Technology LTC26x7 16-/14-/12-bit rail-to-rail DACs.'
|
||||
license = 'gplv2+'
|
||||
inputs = ['i2c']
|
||||
outputs = []
|
||||
tags = ['IC', 'Analog/digital']
|
||||
options = (
|
||||
{'id': 'chip', 'desc': 'Chip', 'default': 'ltc2607',
|
||||
'values': ('ltc2607', 'ltc2617', 'ltc2627')},
|
||||
{'id': 'vref', 'desc': 'Reference voltage (V)', 'default': 1.5},
|
||||
)
|
||||
annotations = (
|
||||
('slave_addr', 'Slave address'),
|
||||
('command', 'Command'),
|
||||
('address', 'Address'),
|
||||
('dac_a_voltage', 'DAC A voltage'),
|
||||
('dac_b_voltage', 'DAC B voltage'),
|
||||
)
|
||||
annotation_rows = (
|
||||
('addr_cmd', 'Address/command', (0, 1, 2)),
|
||||
('dac_a_voltages', 'DAC A voltages', (3,)),
|
||||
('dac_b_voltages', 'DAC B voltages', (4,)),
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
self.state = 'IDLE'
|
||||
self.ss = -1
|
||||
self.data = 0x00
|
||||
self.dac_val = 0
|
||||
|
||||
def start(self):
|
||||
self.out_ann = self.register(srd.OUTPUT_ANN)
|
||||
|
||||
def convert_ternary_str(self, n):
|
||||
if n == 0:
|
||||
return [0, 0, 0]
|
||||
nums = []
|
||||
while n:
|
||||
n, r = divmod(n, 3)
|
||||
nums.append(r)
|
||||
while len(nums) < 3:
|
||||
nums.append(0)
|
||||
return list(reversed(nums))
|
||||
|
||||
def handle_slave_addr(self, data):
|
||||
if data == 0x73:
|
||||
ann = ['Global address', 'Global addr', 'Glob addr', 'GA']
|
||||
self.put(self.ss, self.es, self.out_ann, [0, ann])
|
||||
return
|
||||
ann = ['CA2=%s CA1=%s CA0=%s', '2=%s 1=%s 0=%s', '%s %s %s', '%s %s %s']
|
||||
addr = 0
|
||||
for i in range(7):
|
||||
if i in [2, 3]:
|
||||
continue
|
||||
offset = i
|
||||
if i > 3:
|
||||
offset -= 2
|
||||
mask = 1 << i
|
||||
if data & mask:
|
||||
mask = 1 << offset
|
||||
addr |= mask
|
||||
|
||||
addr -= 0x04
|
||||
ternary_values = self.convert_ternary_str(addr)
|
||||
for i in range(len(ann)):
|
||||
ann[i] = ann[i] % (slave_address[ternary_values[0]][i],
|
||||
slave_address[ternary_values[1]][i],
|
||||
slave_address[ternary_values[2]][i])
|
||||
self.put(self.ss, self.es, self.out_ann, [0, ann])
|
||||
|
||||
def handle_cmd_addr(self, data):
|
||||
cmd_val = (data >> 4) & 0x0F
|
||||
self.dac_val = (data & 0x0F)
|
||||
sm = (self.ss + self.es) // 2
|
||||
|
||||
self.put(self.ss, sm, self.out_ann, [1, commands[cmd_val]])
|
||||
self.put(sm, self.es, self.out_ann, [2, addresses[self.dac_val]])
|
||||
|
||||
def handle_data(self, data):
|
||||
self.data = (self.data << 8) & 0xFF00
|
||||
self.data += data
|
||||
if self.options['chip'] == 'ltc2617':
|
||||
self.data = (self.data >> 2)
|
||||
self.data = (self.options['vref'] * self.data) / 0x3FFF
|
||||
elif self.options['chip'] == 'ltc2627':
|
||||
self.data = (self.data >> 4)
|
||||
self.data = (self.options['vref'] * self.data) / 0x0FFF
|
||||
else:
|
||||
self.data = (self.options['vref'] * self.data) / 0xFFFF
|
||||
ann = []
|
||||
for format in input_voltage_format:
|
||||
ann.append(format % self.data)
|
||||
self.data = 0
|
||||
|
||||
if self.dac_val == 0x0F: # All DACs (A and B).
|
||||
self.put(self.ss, self.es, self.out_ann, [3 + 0, ann])
|
||||
self.put(self.ss, self.es, self.out_ann, [3 + 1, ann])
|
||||
else:
|
||||
self.put(self.ss, self.es, self.out_ann, [3 + self.dac_val, ann])
|
||||
|
||||
def decode(self, ss, es, data):
|
||||
cmd, databyte = data
|
||||
self.es = es
|
||||
|
||||
# State machine.
|
||||
if self.state == 'IDLE':
|
||||
# Wait for an I²C START condition.
|
||||
if cmd != 'START':
|
||||
return
|
||||
self.state = 'GET SLAVE ADDR'
|
||||
elif self.state == 'GET SLAVE ADDR':
|
||||
# Wait for an address write operation.
|
||||
if cmd != 'ADDRESS WRITE':
|
||||
return
|
||||
self.ss = ss
|
||||
self.handle_slave_addr(databyte)
|
||||
self.ss = -1
|
||||
self.state = 'GET CMD ADDR'
|
||||
elif self.state == 'GET CMD ADDR':
|
||||
if cmd != 'DATA WRITE':
|
||||
return
|
||||
self.ss = ss
|
||||
self.handle_cmd_addr(databyte)
|
||||
self.ss = -1
|
||||
self.state = 'WRITE DATA'
|
||||
elif self.state == 'WRITE DATA':
|
||||
if cmd == 'DATA WRITE':
|
||||
if self.ss == -1:
|
||||
self.ss = ss
|
||||
self.data = databyte
|
||||
return
|
||||
self.handle_data(databyte)
|
||||
self.ss = -1
|
||||
elif cmd == 'STOP':
|
||||
self.state = 'IDLE'
|
||||
else:
|
||||
return
|
||||
54
libsigrokdecode4DSL/decoders/nes_gamepad/__init__.py
Normal file
54
libsigrokdecode4DSL/decoders/nes_gamepad/__init__.py
Normal file
@@ -0,0 +1,54 @@
|
||||
##
|
||||
## This file is part of the libsigrokdecode project.
|
||||
##
|
||||
## Copyright (C) 2019 Stephan Thiele <stephan.thiele@mailbox.org>
|
||||
##
|
||||
## 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
|
||||
## the Free Software Foundation; either version 2 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
'''
|
||||
This decoder stacks on top of the 'spi' PD and decodes the button states
|
||||
of an NES gamepad.
|
||||
|
||||
The SPI decoder needs to be configured as follows:
|
||||
|
||||
Clock polarity = 1
|
||||
Clock phase = 0
|
||||
Bit order = msb-first
|
||||
Word size = 8
|
||||
|
||||
Chip-select is not used and must not be assigned to any channel.
|
||||
|
||||
Hardware setup is as follows:
|
||||
___
|
||||
GND |o \
|
||||
CUP |o o| VCC
|
||||
OUT 0 |o o| D3
|
||||
D1 |o o| D4
|
||||
-----
|
||||
NES Gamepad Connector
|
||||
|
||||
VCC - Power 5V
|
||||
GND - Ground
|
||||
CUP - Shift register clock (CLK)
|
||||
OUT 0 - Shift register latch (optional)
|
||||
D1 - Gamepad data (MOSI)
|
||||
D3 - Data (unused)
|
||||
D4 - Data (unused)
|
||||
|
||||
Data pins D3 and D4 are not used by the standard gamepad but
|
||||
by special controllers like the Nintento Zapper light gun.
|
||||
'''
|
||||
|
||||
from .pd import Decoder
|
||||
105
libsigrokdecode4DSL/decoders/nes_gamepad/pd.py
Normal file
105
libsigrokdecode4DSL/decoders/nes_gamepad/pd.py
Normal file
@@ -0,0 +1,105 @@
|
||||
##
|
||||
## This file is part of the libsigrokdecode project.
|
||||
##
|
||||
## Copyright (C) 2019 Stephan Thiele <stephan.thiele@mailbox.org>
|
||||
##
|
||||
## 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
|
||||
## the Free Software Foundation; either version 2 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
import sigrokdecode as srd
|
||||
|
||||
class Decoder(srd.Decoder):
|
||||
api_version = 3
|
||||
id = 'nes_gamepad'
|
||||
name = 'NES gamepad'
|
||||
longname = 'Nintendo Entertainment System gamepad'
|
||||
desc = 'NES gamepad button states.'
|
||||
license = 'gplv2+'
|
||||
inputs = ['spi']
|
||||
outputs = []
|
||||
tags = ['Retro computing']
|
||||
options = (
|
||||
# Currently only the standard controller is supported. This might be
|
||||
# extended by special controllers like the Nintendo Zapper light gun.
|
||||
{'id': 'variant', 'desc': 'Gamepad variant',
|
||||
'default': 'Standard gamepad', 'values': ('Standard gamepad',)},
|
||||
)
|
||||
annotations = (
|
||||
('button', 'Button state'),
|
||||
('no-press', 'No button press'),
|
||||
('not-connected', 'Gamepad unconnected')
|
||||
)
|
||||
annotation_rows = (
|
||||
('buttons', 'Button states', (0,)),
|
||||
('no-presses', 'No button presses', (1,)),
|
||||
('not-connected-vals', 'Gamepad unconnected', (2,)),
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
self.reset()
|
||||
|
||||
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 handle_data(self, value):
|
||||
if value == 0xFF:
|
||||
self.putx([1, ['No button is pressed']])
|
||||
return
|
||||
|
||||
if value == 0x00:
|
||||
self.putx([2, ['Gamepad is not connected']])
|
||||
return
|
||||
|
||||
buttons = [
|
||||
'A',
|
||||
'B',
|
||||
'Select',
|
||||
'Start',
|
||||
'North',
|
||||
'South',
|
||||
'West',
|
||||
'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]])
|
||||
|
||||
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)
|
||||
26
libsigrokdecode4DSL/decoders/nrf905/__init__.py
Normal file
26
libsigrokdecode4DSL/decoders/nrf905/__init__.py
Normal file
@@ -0,0 +1,26 @@
|
||||
##
|
||||
## This file is part of the libsigrokdecode project.
|
||||
##
|
||||
## Copyright (C) 2020 Jorge Solla Rubiales <jorgesolla@gmail.com>
|
||||
##
|
||||
## 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
|
||||
## the Free Software Foundation; either version 2 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program; if not, write to the Free Software
|
||||
## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
##
|
||||
|
||||
'''
|
||||
This decoder stacks on top of the 'spi' PD and decodes the Nordic Semiconductor
|
||||
NRF905 (433/868/915MHz transceiver) command/responses.
|
||||
'''
|
||||
|
||||
from .pd import Decoder
|
||||
301
libsigrokdecode4DSL/decoders/nrf905/pd.py
Normal file
301
libsigrokdecode4DSL/decoders/nrf905/pd.py
Normal file
@@ -0,0 +1,301 @@
|
||||
##
|
||||
## This file is part of the libsigrokdecode project.
|
||||
##
|
||||
## Copyright (C) 2020 Jorge Solla Rubiales <jorgesolla@gmail.com>
|
||||
##
|
||||
## Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
## of this software and associated documentation files (the "Software"), to deal
|
||||
## in the Software without restriction, including without limitation the rights
|
||||
## to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
## copies of the Software, and to permit persons to whom the Software is
|
||||
## furnished to do so, subject to the following conditions:
|
||||
##
|
||||
## The above copyright notice and this permission notice shall be included in all
|
||||
## copies or substantial portions of the Software.
|
||||
##
|
||||
## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
## IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
## FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
## AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
## LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
## OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
## SOFTWARE.
|
||||
|
||||
import sigrokdecode as srd
|
||||
from common.srdhelper import SrdIntEnum
|
||||
|
||||
CFG_REGS = {
|
||||
0: [{'name': 'CH_NO', 'stbit': 7, 'nbits': 8}],
|
||||
1: [
|
||||
{'name': 'AUTO_RETRAN', 'stbit': 5, 'nbits': 1,
|
||||
'opts': {0: 'No retransmission', 1: 'Retransmission of data packet'}},
|
||||
{'name': 'RX_RED_PWR', 'stbit': 4, 'nbits': 1,
|
||||
'opts': {0: 'Normal operation', 1: 'Reduced power'}},
|
||||
{'name': 'PA_PWR', 'stbit': 3, 'nbits': 2,
|
||||
'opts': {0: '-10 dBm', 1: '-2 dBm', 2: '+6 dBm', 3: '+10 dBm'}},
|
||||
{'name': 'HFREQ_PLL', 'stbit': 1, 'nbits': 1,
|
||||
'opts': {0: '433 MHz', 1: '868 / 915 MHz'}},
|
||||
{'name': 'CH_NO_8', 'stbit': 0, 'nbits': 1},
|
||||
],
|
||||
2: [
|
||||
{'name': 'TX_AFW (TX addr width)', 'stbit': 6, 'nbits': 3},
|
||||
{'name': 'RX_AFW (RX addr width)', 'stbit': 2, 'nbits': 3},
|
||||
],
|
||||
3: [{'name': 'RW_PW (RX payload width)', 'stbit': 5, 'nbits': 6}],
|
||||
4: [{'name': 'TX_PW (TX payload width)', 'stbit': 5, 'nbits': 6}],
|
||||
5: [{'name': 'RX_ADDR_0', 'stbit': 7, 'nbits': 8}],
|
||||
6: [{'name': 'RX_ADDR_1', 'stbit': 7, 'nbits': 8}],
|
||||
7: [{'name': 'RX_ADDR_2', 'stbit': 7, 'nbits': 8}],
|
||||
8: [{'name': 'RX_ADDR_3', 'stbit': 7, 'nbits': 8}],
|
||||
9: [
|
||||
{'name': 'CRC_MODE', 'stbit': 7, 'nbits': 1,
|
||||
'opts': {0: '8 CRC check bit', 1: '16 CRC check bit'}},
|
||||
{'name': 'CRC_EN', 'stbit': 6, 'nbits': 1,
|
||||
'opts': {0: 'Disabled', 1: 'Enabled'}},
|
||||
{'name': 'XOR', 'stbit': 5, 'nbits': 3,
|
||||
'opts': {0: '4 MHz', 1: '8 MHz', 2: '12 MHz',
|
||||
3: '16 MHz', 4: '20 MHz'}},
|
||||
{'name': 'UP_CLK_EN', 'stbit': 2, 'nbits': 1,
|
||||
'opts': {0: 'No external clock signal avail.',
|
||||
1: 'External clock signal enabled'}},
|
||||
{'name': 'UP_CLK_FREQ', 'stbit': 1, 'nbits': 2,
|
||||
'opts': {0: '4 MHz', 1: '2 MHz', 2: '1 MHz', 3: '500 kHz'}},
|
||||
],
|
||||
}
|
||||
|
||||
CHN_CFG = [
|
||||
{'name': 'PA_PWR', 'stbit': 3, 'nbits': 2,
|
||||
'opts': {0: '-10 dBm', 1: '-2 dBm', 2: '+6 dBm', 3: '+10 dBm'}},
|
||||
{'name': 'HFREQ_PLL', 'stbit': 1, 'nbits': 1,
|
||||
'opts': {0: '433 MHz', 1: '868 / 915 MHz'}},
|
||||
]
|
||||
|
||||
STAT_REG = [
|
||||
{'name': 'AM', 'stbit': 7, 'nbits': 1},
|
||||
{'name': 'DR', 'stbit': 5, 'nbits': 1},
|
||||
]
|
||||
|
||||
Ann = SrdIntEnum.from_str('Ann', 'CMD REG_WR REG_RD TX RX RESP WARN')
|
||||
|
||||
class Decoder(srd.Decoder):
|
||||
api_version = 3
|
||||
id = 'nrf905'
|
||||
name = 'nRF905'
|
||||
longname = 'Nordic Semiconductor nRF905'
|
||||
desc = '433/868/933MHz transceiver chip.'
|
||||
license = 'mit'
|
||||
inputs = ['spi']
|
||||
outputs = []
|
||||
tags = ['IC', 'Wireless/RF']
|
||||
annotations = (
|
||||
('cmd', 'Command sent to the device'),
|
||||
('reg-write', 'Config register written to the device'),
|
||||
('reg-read', 'Config register read from the device'),
|
||||
('tx-data', 'Payload sent to the device'),
|
||||
('rx-data', 'Payload read from the device'),
|
||||
('resp', 'Response to commands received from the device'),
|
||||
('warning', 'Warning'),
|
||||
)
|
||||
annotation_rows = (
|
||||
('commands', 'Commands', (Ann.CMD,)),
|
||||
('responses', 'Responses', (Ann.RESP,)),
|
||||
('registers', 'Registers', (Ann.REG_WR, Ann.REG_RD)),
|
||||
('tx', 'Transmitted data', (Ann.TX,)),
|
||||
('rx', 'Received data', (Ann.RX,)),
|
||||
('warnings', 'Warnings', (Ann.WARN,)),
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
self.ss_cmd, self.es_cmd = 0, 0
|
||||
self.cs_asserted = False
|
||||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
self.mosi_bytes, self.miso_bytes = [], []
|
||||
self.cmd_samples = {'ss': 0, 'es': 0}
|
||||
|
||||
def start(self):
|
||||
self.out_ann = self.register(srd.OUTPUT_ANN)
|
||||
|
||||
def extract_bits(self, byte, start_bit, num_bits):
|
||||
begin = 7 - start_bit
|
||||
end = begin + num_bits
|
||||
if begin < 0 or end > 8:
|
||||
return 0
|
||||
binary = format(byte, '08b')[begin:end]
|
||||
return int(binary, 2)
|
||||
|
||||
def extract_vars(self, reg_vars, reg_value):
|
||||
# Iterate all vars on current register.
|
||||
data = ''
|
||||
for var in reg_vars:
|
||||
var_value = self.extract_bits(reg_value, var['stbit'], var['nbits'])
|
||||
data += var['name'] + ' = ' + str(var_value)
|
||||
opt = ''
|
||||
|
||||
# If var has options, just add the option meaning.
|
||||
if 'opts' in var:
|
||||
opt = var['opts'].get(var_value, 'unknown')
|
||||
data += ' (' + opt + ')'
|
||||
|
||||
# Add var separator.
|
||||
if reg_vars.index(var) != len(reg_vars) - 1:
|
||||
data += ' | '
|
||||
return data
|
||||
|
||||
def parse_config_register(self, addr, value, is_write):
|
||||
reg_value = value[0]
|
||||
data = 'CFG_REG[' + hex(addr) + '] -> '
|
||||
|
||||
# Get register vars for this register.
|
||||
if addr in CFG_REGS:
|
||||
reg_vars = CFG_REGS[addr]
|
||||
else:
|
||||
# Invalid register address.
|
||||
self.put(value[1], value[2],
|
||||
self.out_ann, [Ann.WARN, ['Invalid reg. addr']])
|
||||
return
|
||||
|
||||
data += self.extract_vars(reg_vars, reg_value)
|
||||
|
||||
ann = Ann.REG_WR if is_write else Ann.REG_RD
|
||||
self.put(value[1], value[2], self.out_ann, [ann, [data]])
|
||||
|
||||
def parse_config_registers(self, addr, registers, is_write):
|
||||
i = 0
|
||||
while i < len(registers):
|
||||
reg_addr = i + addr
|
||||
if reg_addr <= 9:
|
||||
self.parse_config_register(reg_addr, registers[i], is_write)
|
||||
i += 1
|
||||
|
||||
def dump_cmd_bytes(self, prefix, cmd_bytes, ann):
|
||||
ss, es = cmd_bytes[1][1], 0
|
||||
data = ''
|
||||
for byte in cmd_bytes[1:]:
|
||||
data += format(byte[0], '02X') + ' '
|
||||
es = byte[2]
|
||||
self.put(ss, es, self.out_ann, [ann, [prefix + data]])
|
||||
|
||||
def handle_WC(self):
|
||||
start_addr = self.mosi_bytes[0][0] & 0x0F
|
||||
if start_addr > 9:
|
||||
return
|
||||
self.parse_config_registers(start_addr, self.mosi_bytes[1:], True)
|
||||
|
||||
def handle_RC(self):
|
||||
start_addr = self.mosi_bytes[0][0] & 0x0F
|
||||
if start_addr > 9:
|
||||
return
|
||||
self.parse_config_registers(start_addr, self.miso_bytes[1:], False)
|
||||
|
||||
def handle_WTP(self):
|
||||
self.dump_cmd_bytes('Write TX payload.: ', self.mosi_bytes, Ann.TX)
|
||||
|
||||
def handle_RTP(self):
|
||||
self.dump_cmd_bytes('Read TX payload: ', self.miso_bytes, Ann.RESP)
|
||||
|
||||
def handle_WTA(self):
|
||||
self.dump_cmd_bytes('Write TX addr: ', self.mosi_bytes, Ann.REG_WR)
|
||||
|
||||
def handle_RTA(self):
|
||||
self.dump_cmd_bytes('Read TX addr: ', self.miso_bytes, Ann.RESP)
|
||||
|
||||
def handle_RRP(self):
|
||||
self.dump_cmd_bytes('Read RX payload: ', self.miso_bytes, Ann.RX)
|
||||
|
||||
def handle_CC(self):
|
||||
cmd, dta = self.mosi_bytes[0], self.mosi_bytes[1]
|
||||
channel = ((cmd[0] & 0x01) << 8) + dta
|
||||
data = self.extract_vars(CHN_CFG, cmd[0])
|
||||
data += '| CHN = ' + str(channel)
|
||||
self.put(self.mosi_bytes[0][1], self.mosi_bytes[1][2],
|
||||
self.out_ann, [Ann.REG_WR, [data]])
|
||||
|
||||
def handle_STAT(self):
|
||||
status = 'STAT = ' + self.extract_vars(STAT_REG, self.miso_bytes[0][0])
|
||||
self.put(self.miso_bytes[0][1], self.miso_bytes[0][2],
|
||||
self.out_ann, [Ann.REG_RD, [status]])
|
||||
|
||||
def process_cmd(self):
|
||||
cmd, cmd_name, cmd_hnd = '', '', None
|
||||
|
||||
for byte in self.mosi_bytes:
|
||||
cmd += hex(byte[0]) + ' '
|
||||
|
||||
cmd = self.mosi_bytes[0][0]
|
||||
|
||||
if (cmd & 0xF0) == 0x00:
|
||||
cmd_name, cmd_hnd = 'CMD: W_CONFIG (WC)', self.handle_WC
|
||||
elif (cmd & 0xF0) == 0x10:
|
||||
cmd_name, cmd_hnd = 'CMD: R_CONFIG (RC)', self.handle_RC
|
||||
elif cmd == 0x20:
|
||||
cmd_name, cmd_hnd = 'CMD: W_TX_PAYLOAD (WTP)', self.handle_WTP
|
||||
elif cmd == 0x21:
|
||||
cmd_name, cmd_hnd = 'CMD: R_TX_PAYLOAD (RTP)', self.handle_RTP
|
||||
elif cmd == 0x22:
|
||||
cmd_name, cmd_hnd = 'CMD: W_TX_ADDRESS (WTA)', self.handle_WTA
|
||||
elif cmd == 0x23:
|
||||
cmd_name, cmd_hnd = 'CMD: R_TX_ADDRESS (RTA)', self.handle_RTA
|
||||
elif cmd == 0x24:
|
||||
cmd_name, cmd_hnd = 'CMD: R_RX_PAYLOAD (RRP)', self.handle_RRP
|
||||
elif (cmd & 0xF0 == 0x80):
|
||||
cmd_name, cmd_hnd = 'CMD: CHANNEL_CONFIG (CC)', self.handle_CC
|
||||
|
||||
# Report command name.
|
||||
self.put(self.cmd_samples['ss'], self.cmd_samples['es'],
|
||||
self.out_ann, [Ann.CMD, [cmd_name]])
|
||||
|
||||
# Handle status byte.
|
||||
self.handle_STAT()
|
||||
|
||||
# Handle command.
|
||||
if cmd_hnd is not None:
|
||||
cmd_hnd()
|
||||
|
||||
def set_cs_status(self, sample, asserted):
|
||||
if self.cs_asserted == asserted:
|
||||
return
|
||||
|
||||
if asserted:
|
||||
self.cmd_samples['ss'] = sample
|
||||
self.cmd_samples['es'] = -1
|
||||
else:
|
||||
self.cmd_samples['es'] = sample
|
||||
|
||||
self.cs_asserted = asserted
|
||||
|
||||
def decode(self, ss, es, data):
|
||||
ptype, data1, data2 = data
|
||||
|
||||
if ptype == 'CS-CHANGE':
|
||||
if data1 is None and data2 is None:
|
||||
self.requirements_met = False
|
||||
raise ChannelError('CS# pin required.')
|
||||
|
||||
if data1 is None and data2 == 0:
|
||||
self.set_cs_status(ss, True)
|
||||
|
||||
elif data1 is None and data2 == 1:
|
||||
self.set_cs_status(ss, False)
|
||||
|
||||
elif data1 == 1 and data2 == 0:
|
||||
self.set_cs_status(ss, True)
|
||||
|
||||
elif data1 == 0 and data2 == 1:
|
||||
self.set_cs_status(ss, False)
|
||||
if len(self.mosi_bytes):
|
||||
self.process_cmd()
|
||||
self.reset()
|
||||
|
||||
elif ptype == 'DATA':
|
||||
# Ignore traffic if CS is not asserted.
|
||||
if self.cs_asserted is False:
|
||||
return
|
||||
|
||||
mosi, miso = data1, data2
|
||||
if miso is None or mosi is None:
|
||||
raise ChannelError('Both MISO and MOSI pins required.')
|
||||
|
||||
self.mosi_bytes.append((mosi, ss, es))
|
||||
self.miso_bytes.append((miso, ss, es))
|
||||
41
libsigrokdecode4DSL/decoders/numbers_and_state/__init__.py
Normal file
41
libsigrokdecode4DSL/decoders/numbers_and_state/__init__.py
Normal file
@@ -0,0 +1,41 @@
|
||||
##
|
||||
## This file is part of the libsigrokdecode project.
|
||||
##
|
||||
## Copyright (C) 2019 Comlab AG
|
||||
## Copyright (C) 2020 Gerhard Sittig <gerhard.sittig@gmx.net>
|
||||
##
|
||||
## 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
|
||||
## the Free Software Foundation; either version 2 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
'''
|
||||
This protocol decoder takes a set of logic input signals, and interprets
|
||||
their bit pattern according to user specifications as different kinds of
|
||||
numbers, or an enumeration of e.g. machine states.
|
||||
|
||||
Supported formats are: signed and unsigned integers, fixed point numbers,
|
||||
IEEE754 floating point numbers, and number to text mapping controlled by
|
||||
external data files. (Support for half precision floats depends on the
|
||||
Python runtime, and may not universally be available.)
|
||||
|
||||
User provided text mapping files can either use the JSON format:
|
||||
{"one": 1, "two": 2, "four": 4}
|
||||
or the Python programming language:
|
||||
enumtext = { 1: "one", 2: "two", 3: "three", }
|
||||
|
||||
In addition to all enum values on one row (sequential presentation of
|
||||
the data), a limited number of enum values also are shown in tabular
|
||||
presentation, which can help visualize state machines or task switches.
|
||||
'''
|
||||
|
||||
from .pd import Decoder
|
||||
377
libsigrokdecode4DSL/decoders/numbers_and_state/pd.py
Normal file
377
libsigrokdecode4DSL/decoders/numbers_and_state/pd.py
Normal file
@@ -0,0 +1,377 @@
|
||||
##
|
||||
## This file is part of the libsigrokdecode project.
|
||||
##
|
||||
## Copyright (C) 2019 Comlab AG
|
||||
## Copyright (C) 2020 Gerhard Sittig <gerhard.sittig@gmx.net>
|
||||
##
|
||||
## 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
|
||||
## the Free Software Foundation; either version 2 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
# This implementation started as a "vector slicer", then turned into the
|
||||
# "numbers and states" decoder, because users always had the freedom to
|
||||
# connect any logic signal to either of the decoder inputs. That's when
|
||||
# slicing vectors took second seat, and just was not needed any longer
|
||||
# in the strict sense.
|
||||
#
|
||||
# TODO
|
||||
# - Find an appropriate number of input channels, and maximum enum slots.
|
||||
# - Re-check correctness of signed integers. Signed fixed point is based
|
||||
# on integers and transparently benefits from fixes and improvements.
|
||||
# - Local formatting in individual decoders becomes obsolete when common
|
||||
# support for user selected formatting gets introduced.
|
||||
# - There is overlap with the 'parallel' decoder. Ideally the numbers
|
||||
# decoder could stack on top of parallel, but parallel currently is
|
||||
# severely limited in its number of input channels, and dramatically
|
||||
# widening the parallel decoder may be undesirable.
|
||||
|
||||
from common.srdhelper import bitpack
|
||||
import json
|
||||
import sigrokdecode as srd
|
||||
import struct
|
||||
|
||||
'''
|
||||
OUTPUT_PYTHON format:
|
||||
|
||||
Packet:
|
||||
[<ptype>, <pdata>]
|
||||
|
||||
This is a list of <ptype>s and their respective <pdata> values:
|
||||
- 'RAW': The data is a tuple of bit count and bit pattern (a number,
|
||||
assuming unsigned integer presentation of the input data bit pattern).
|
||||
- 'NUMBER': The data is the conversion result of the bit pattern.
|
||||
- 'ENUM': The data is a tuple of the raw number and its mapped text.
|
||||
'''
|
||||
|
||||
# TODO Better raise the number of channels to 32. This allows access to
|
||||
# IEEE754 single precision numbers, and shall cover most busses, _and_
|
||||
# remains within most logic analyzers' capabilities, and keeps the UI
|
||||
# dialog somewhat managable. What's a good default for the number of
|
||||
# enum slots (which translate to annotation rows)? Notice that 2 to the
|
||||
# power of the channel count is way out of the question. :)
|
||||
_max_channels = 16
|
||||
_max_enum_slots = 32
|
||||
|
||||
class ChannelError(Exception):
|
||||
pass
|
||||
|
||||
class Pin:
|
||||
CLK, BIT_0 = range(2)
|
||||
BIT_N = BIT_0 + _max_channels
|
||||
|
||||
class Ann:
|
||||
RAW, NUM = range(2)
|
||||
ENUM_0 = NUM + 1
|
||||
ENUM_OVR = ENUM_0 + _max_enum_slots
|
||||
ENUMS = range(ENUM_0, ENUM_OVR)
|
||||
WARN = ENUM_OVR + 1
|
||||
|
||||
@staticmethod
|
||||
def enum_indices():
|
||||
return [i for i in range(Ann.ENUMS)]
|
||||
|
||||
@staticmethod
|
||||
def get_enum_idx(code):
|
||||
if code in range(_max_enum_slots):
|
||||
return Ann.ENUM_0 + code
|
||||
return Ann.ENUM_OVR
|
||||
|
||||
def _channel_decl(count):
|
||||
return tuple([
|
||||
{'id': 'bit{}'.format(i), 'name': 'Bit{}'.format(i), 'desc': 'Bit position {}'.format(i)}
|
||||
for i in range(count)
|
||||
])
|
||||
|
||||
def _enum_cls_decl(count):
|
||||
return tuple([
|
||||
('enum{}'.format(i), 'Enumeration slot {}'.format(i))
|
||||
for i in range(count)
|
||||
] + [('enumovr', 'Enumeration overflow')])
|
||||
|
||||
def _enum_rows_decl(count):
|
||||
return tuple([
|
||||
('enums{}'.format(i), 'Enumeration slots {}'.format(i), (Ann.ENUM_0 + i,))
|
||||
for i in range(count)
|
||||
] + [('enumsovr', 'Enumeration overflows', (Ann.ENUM_OVR,))])
|
||||
|
||||
class Decoder(srd.Decoder):
|
||||
api_version = 3
|
||||
id = 'numbers_and_state'
|
||||
name = 'Numbers and State'
|
||||
longname = 'Interpret bit patters as numbers or state enums'
|
||||
desc = 'Interpret bit patterns as different kinds of numbers (integer, float, enum).'
|
||||
license = 'gplv2+'
|
||||
inputs = ['logic']
|
||||
outputs = ['numbers_and_state']
|
||||
tags = ['Encoding', 'Util']
|
||||
optional_channels = (
|
||||
{'id': 'clk', 'name': 'Clock', 'desc': 'Clock'},
|
||||
) + _channel_decl(_max_channels)
|
||||
options = (
|
||||
{'id': 'clkedge', 'desc': 'Clock edge', 'default': 'rising',
|
||||
'values': ('rising', 'falling', 'either')},
|
||||
{'id': 'count', 'desc': 'Total bits count', 'default': 0},
|
||||
{'id': 'interp', 'desc': 'Interpretation', 'default': 'unsigned',
|
||||
'values': ('unsigned', 'signed', 'fixpoint', 'fixsigned', 'ieee754', 'enum')},
|
||||
{'id': 'fracbits', 'desc': 'Fraction bits count', 'default': 0},
|
||||
{'id': 'mapping', 'desc': 'Enum to text map file',
|
||||
'default': 'enumtext.json'},
|
||||
{'id': 'format', 'desc': 'Number format', 'default': '-',
|
||||
'values': ('-', 'bin', 'oct', 'dec', 'hex')},
|
||||
)
|
||||
annotations = (
|
||||
('raw', 'Raw pattern'),
|
||||
('number', 'Number'),
|
||||
) + _enum_cls_decl(_max_enum_slots) + (
|
||||
('warning', 'Warning'),
|
||||
)
|
||||
annotation_rows = (
|
||||
('raws', 'Raw bits', (Ann.RAW,)),
|
||||
('numbers', 'Numbers', (Ann.NUM,)),
|
||||
) + _enum_rows_decl(_max_enum_slots) + (
|
||||
('warnings', 'Warnings', (Ann.WARN,)),
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
pass
|
||||
|
||||
def start(self):
|
||||
self.out_ann = self.register(srd.OUTPUT_ANN)
|
||||
self.out_python = self.register(srd.OUTPUT_PYTHON)
|
||||
|
||||
def putg(self, ss, es, cls, data):
|
||||
self.put(ss, es, self.out_ann, [cls, data])
|
||||
|
||||
def putpy(self, ss, es, ptype, pdata):
|
||||
self.put(ss, es, self.out_python, (ptype, pdata))
|
||||
|
||||
def grab_pattern(self, pins):
|
||||
'''Get a bit pattern from potentially incomplete probes' values.'''
|
||||
|
||||
# Pad and trim the input data, to achieve the user specified
|
||||
# total number of bits. Map all unassigned signals to 0 (low).
|
||||
# Return raw number (unsigned integer interpreation).
|
||||
bits = pins + (None,) * self.bitcount
|
||||
bits = bits[:self.bitcount]
|
||||
bits = [b if b in (0, 1) else 0 for b in bits]
|
||||
pattern = bitpack(bits)
|
||||
return pattern
|
||||
|
||||
def handle_pattern(self, ss, es, pattern):
|
||||
fmt = '{{:0{}b}}'.format(self.bitcount)
|
||||
txt = fmt.format(pattern)
|
||||
self.putg(ss, es, Ann.RAW, [txt])
|
||||
self.putpy(ss, es, 'RAW', (self.bitcount, pattern))
|
||||
|
||||
try:
|
||||
value = self.interpreter(ss, es, pattern)
|
||||
except:
|
||||
value = None
|
||||
if value is None:
|
||||
return
|
||||
self.putpy(ss, es, 'NUMBER', value)
|
||||
try:
|
||||
formatted = self.formatter(ss, es, value)
|
||||
except:
|
||||
formatted = None
|
||||
if formatted:
|
||||
self.putg(ss, es, Ann.NUM, formatted)
|
||||
if self.interpreter == self.interp_enum:
|
||||
cls = Ann.get_enum_idx(pattern)
|
||||
self.putg(ss, es, cls, formatted)
|
||||
self.putpy(ss, es, 'ENUM', (value, formatted))
|
||||
|
||||
def interp_unsigned(self, ss, es, pattern):
|
||||
value = pattern
|
||||
return value
|
||||
|
||||
def interp_signed(self, ss, es, pattern):
|
||||
if not 'signmask' in self.interp_state:
|
||||
self.interp_state.update({
|
||||
'signmask': 1 << (self.bitcount - 1),
|
||||
'signfull': 1 << self.bitcount,
|
||||
})
|
||||
is_neg = pattern & self.interp_state['signmask']
|
||||
if is_neg:
|
||||
value = -(self.interp_state['signfull'] - pattern)
|
||||
else:
|
||||
value = pattern
|
||||
return value
|
||||
|
||||
def interp_fixpoint(self, ss, es, pattern):
|
||||
if not 'fixdiv' in self.interp_state:
|
||||
self.interp_state.update({
|
||||
'fixsign': self.options['interp'] == 'fixsigned',
|
||||
'fixdiv': 2 ** self.options['fracbits'],
|
||||
})
|
||||
if self.interp_state['fixsign']:
|
||||
value = self.interp_signed(ss, es, pattern)
|
||||
else:
|
||||
value = self.interp_unsigned(ss, es, pattern)
|
||||
value /= self.interp_state['fixdiv']
|
||||
return value
|
||||
|
||||
def interp_ieee754(self, ss, es, pattern):
|
||||
if not 'ieee_has_16bit' in self.interp_state:
|
||||
self.interp_state.update({
|
||||
'ieee_fmt_int_16': '=H',
|
||||
'ieee_fmt_flt_16': '=e',
|
||||
'ieee_fmt_int_32': '=L',
|
||||
'ieee_fmt_flt_32': '=f',
|
||||
'ieee_fmt_int_64': '=Q',
|
||||
'ieee_fmt_flt_64': '=d',
|
||||
})
|
||||
try:
|
||||
fmt = self.interp_state.update['ieee_fmt_flt_16']
|
||||
has_16bit_support = 8 * struct.calcsize(fmt) == 16
|
||||
except:
|
||||
has_16bit_support = False
|
||||
self.interp_state['ieee_has_16bit'] = has_16bit_support
|
||||
if self.bitcount == 16:
|
||||
if not self.interp_state['ieee_has_16bit']:
|
||||
return None
|
||||
buff = struct.pack(self.interp_state['ieee_fmt_int_16'], pattern)
|
||||
value, = struct.unpack(self.interp_state['ieee_fmt_flt_16'], buff)
|
||||
return value
|
||||
if self.bitcount == 32:
|
||||
buff = struct.pack(self.interp_state['ieee_fmt_int_32'], pattern)
|
||||
value, = struct.unpack(self.interp_state['ieee_fmt_flt_32'], buff)
|
||||
return value
|
||||
if self.bitcount == 64:
|
||||
buff = struct.pack(self.interp_state['ieee_fmt_int_64'], pattern)
|
||||
value, = struct.unpack(self.interp_state['ieee_fmt_flt_64'], buff)
|
||||
return value
|
||||
return None
|
||||
|
||||
def interp_enum(self, ss, es, pattern):
|
||||
if not 'enum_map' in self.interp_state:
|
||||
self.interp_state.update({
|
||||
'enum_fn': self.options['mapping'],
|
||||
'enum_map': {},
|
||||
'enum_have_map': False,
|
||||
})
|
||||
try:
|
||||
fn = self.interp_state['enum_fn']
|
||||
# TODO Optionally try in several locations? Next to the
|
||||
# decoder implementation? Where else? Expect users to
|
||||
# enter absolute paths?
|
||||
with open(fn, 'r') as f:
|
||||
maptext = f.read()
|
||||
maptable = {}
|
||||
if fn.endswith('.js') or fn.endswith('.json'):
|
||||
# JSON requires string literals on the LHS, so the
|
||||
# table is written "in reverse order".
|
||||
js_table = json.loads(maptext)
|
||||
for k, v in js_table.items():
|
||||
maptable[v] = k
|
||||
elif fn.endswith('.py'):
|
||||
# Expect a specific identifier at the Python module
|
||||
# level, and assume that it's a dictionary.
|
||||
py_table = {}
|
||||
exec(maptext, py_table)
|
||||
maptable.update(py_table['enumtext'])
|
||||
self.interp_state['enum_map'].update(maptable)
|
||||
self.interp_state['enum_have_map'] = True
|
||||
except:
|
||||
# Silently ignore failure. This happens while the user
|
||||
# is typing the filename, and is non-fatal. If the file
|
||||
# exists and is not readable or not valid or of unknown
|
||||
# format, the worst thing that can happen is that the
|
||||
# decoder implementation keeps using "anonymous" phrases
|
||||
# until a mapping has become available. No harm is done.
|
||||
# This decoder cannot tell intermediate from final file
|
||||
# read attempts, so we cannot raise severity here.
|
||||
pass
|
||||
value = self.interp_state['enum_map'].get(pattern, None)
|
||||
if value is None:
|
||||
value = pattern
|
||||
return value
|
||||
|
||||
def format_native(self, ss, es, value):
|
||||
return ['{}'.format(value),]
|
||||
|
||||
def format_bin(self, ss, es, value):
|
||||
if not self.format_string:
|
||||
self.format_string = '{{:0{}b}}'.format(self.bitcount)
|
||||
return [self.format_string.format(value)]
|
||||
|
||||
def format_oct(self, ss, es, value):
|
||||
if not self.format_string:
|
||||
self.format_string = '{{:0{}o}}'.format((self.bitcount + 3 - 1) // 3)
|
||||
return [self.format_string.format(value)]
|
||||
|
||||
def format_dec(self, ss, es, value):
|
||||
if not self.format_string:
|
||||
self.format_string = '{:d}'
|
||||
return [self.format_string.format(value)]
|
||||
|
||||
def format_hex(self, ss, es, value):
|
||||
if not self.format_string:
|
||||
self.format_string = '{{:0{}x}}'.format((self.bitcount + 4 - 1) // 4)
|
||||
return [self.format_string.format(value)]
|
||||
|
||||
def decode(self):
|
||||
channels = [ch for ch in range(_max_channels) if self.has_channel(ch)]
|
||||
have_clk = Pin.CLK in channels
|
||||
if have_clk:
|
||||
channels.remove(Pin.CLK)
|
||||
if not channels:
|
||||
raise ChannelError("Need at least one bit channel.")
|
||||
if have_clk:
|
||||
clkedge = {
|
||||
'rising': 'r',
|
||||
'falling': 'f',
|
||||
'either': 'e',
|
||||
}.get(self.options['clkedge'])
|
||||
wait_cond = {Pin.CLK: clkedge}
|
||||
else:
|
||||
wait_cond = [{ch: 'e'} for ch in channels]
|
||||
|
||||
bitcount = self.options['count']
|
||||
if not bitcount:
|
||||
bitcount = channels[-1] - Pin.BIT_0 + 1
|
||||
self.bitcount = bitcount
|
||||
|
||||
self.interpreter = {
|
||||
'unsigned': self.interp_unsigned,
|
||||
'signed': self.interp_signed,
|
||||
'fixpoint': self.interp_fixpoint,
|
||||
'fixsigned': self.interp_fixpoint,
|
||||
'ieee754': self.interp_ieee754,
|
||||
'enum': self.interp_enum,
|
||||
}.get(self.options['interp'])
|
||||
self.interp_state = {}
|
||||
self.formatter = {
|
||||
'-': self.format_native,
|
||||
'bin': self.format_bin,
|
||||
'oct': self.format_oct,
|
||||
'dec': self.format_dec,
|
||||
'hex': self.format_hex,
|
||||
}.get(self.options['format'])
|
||||
self.format_string = None
|
||||
|
||||
pins = self.wait()
|
||||
ss = self.samplenum
|
||||
prev_pattern = self.grab_pattern(pins[Pin.BIT_0:])
|
||||
while True:
|
||||
pins = self.wait(wait_cond)
|
||||
es = self.samplenum
|
||||
pattern = self.grab_pattern(pins[Pin.BIT_0:])
|
||||
if pattern == prev_pattern:
|
||||
continue
|
||||
self.handle_pattern(ss, es, prev_pattern)
|
||||
ss = es
|
||||
prev_pattern = pattern
|
||||
27
libsigrokdecode4DSL/decoders/pjdl/__init__.py
Normal file
27
libsigrokdecode4DSL/decoders/pjdl/__init__.py
Normal file
@@ -0,0 +1,27 @@
|
||||
##
|
||||
## This file is part of the libsigrokdecode project.
|
||||
##
|
||||
## Copyright (C) 2020 Gerhard Sittig <gerhard.sittig@gmx.net>
|
||||
##
|
||||
## 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
|
||||
## the Free Software Foundation; either version 2 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
'''
|
||||
This protocol decoder interprets the PJDL data link of the PJON protocol.
|
||||
Bytes and frames get extracted from single wire serial communication
|
||||
(which often is referred to as "software bitbang" because that's what
|
||||
the Arduino reference implementation happens to do).
|
||||
'''
|
||||
|
||||
from .pd import Decoder
|
||||
723
libsigrokdecode4DSL/decoders/pjdl/pd.py
Normal file
723
libsigrokdecode4DSL/decoders/pjdl/pd.py
Normal file
@@ -0,0 +1,723 @@
|
||||
##
|
||||
## This file is part of the libsigrokdecode project.
|
||||
##
|
||||
## Copyright (C) 2020 Gerhard Sittig <gerhard.sittig@gmx.net>
|
||||
##
|
||||
## 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
|
||||
## the Free Software Foundation; either version 2 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
# See the https://www.pjon.org/ PJON project page and especially the
|
||||
# https://www.pjon.org/PJDL-specification-v4.1.php PJDL v4.1 spec for
|
||||
# the "Padded Jittering Data Link" single wire serial data link layer.
|
||||
|
||||
# TODO
|
||||
# - Improve (fix, and extend) carrier sense support. Detection of the
|
||||
# idle/busy connection state is incomplete and fragile. Getting 'IDLE'
|
||||
# operational in the PJDL decoder would greatly help the PJON decoder
|
||||
# to flush ACK details before the start of new frames.
|
||||
# - Check the correctness of timing assumptions. This implementation has
|
||||
# support for tolerances, which the spec does not discuss. Though real
|
||||
# world traffic was found to not decode at all with strict spec values
|
||||
# and without tolerances, while communication peers were able to talk
|
||||
# to each other. This needs more attention.
|
||||
# - Check robustness when input data contains glitches. The spec does
|
||||
# not discuss how to handle these. Data bit sampling happens to work
|
||||
# because their value is taken at the center of the bit time. But
|
||||
# pad bits suffer badly from glitches, which breaks frame inspection
|
||||
# as well.
|
||||
# - Cleanup the decoder implementation in general terms. Some details
|
||||
# have become obsolete ("edges", "pads"), and/or are covered by other
|
||||
# code paths.
|
||||
# - Implement more data link decoders which can feed their output into
|
||||
# the PJON protocol decoder. Candidates are: PJDLR, PJDLS, TSDL.
|
||||
# - Determine whether or not the data link layer should interpret any
|
||||
# frame content. From my perspective it should not, and needs not in
|
||||
# the strict sense. Possible gains would be getting the (expected!)
|
||||
# packet length, or whether a synchronous response is requested. But
|
||||
# this would duplicate knowledge which should remain internal to the
|
||||
# PJON decoder. And this link layer decoder neither shall assume that
|
||||
# the input data would be correct, or complete. Instead the decoder
|
||||
# shall remain usable on captures which demonstrate faults, and be
|
||||
# helpful in pointing them out. The design goal is to extract the
|
||||
# maximum of information possible, and pass it on as transparently
|
||||
# as possible.
|
||||
|
||||
import sigrokdecode as srd
|
||||
from common.srdhelper import bitpack
|
||||
from math import ceil, floor
|
||||
|
||||
'''
|
||||
OUTPUT_PYTHON format for stacked decoders:
|
||||
|
||||
General packet format:
|
||||
[<ptype>, <pdata>]
|
||||
|
||||
This is the list of <ptype>s and their respective <pdata> values:
|
||||
|
||||
Carrier sense:
|
||||
- 'IDLE': <pdata> is the pin level (always 0).
|
||||
- 'BUSY': <pdata> is always True.
|
||||
|
||||
Raw bit slots:
|
||||
- 'PAD_BIT': <pdata> is the pin level (always 1).
|
||||
- 'DATA_BIT': <pdata> is the pin level (0, or 1).
|
||||
- 'SHORT_BIT': <pdata> is the pin level (always 1).
|
||||
- 'SYNC_LOSS': <pdata> is an arbitrary text (internal use only).
|
||||
|
||||
Date bytes and frames:
|
||||
- 'SYNC_PAD': <pdata> is True. Spans the high pad bit as well as the
|
||||
low data bit.
|
||||
- 'DATA_BYTE': <pdata> is the byte value (0..255).
|
||||
- 'FRAME_INIT': <pdata> is True. Spans three sync pads.
|
||||
- 'FRAME_DATA': <pdata> is the sequence of bytes in the frame. Non-data
|
||||
phases in the frame get represented by strings instead of numbers
|
||||
('INIT', 'SYNC', 'SHORT', 'WAIT'). Frames can be incomplete, depending
|
||||
on the decoder's input data.
|
||||
- 'SYNC_RESP_WAIT': <pdata> is always True.
|
||||
|
||||
Notice that this link layer decoder is not aware of frame content. Will
|
||||
neither check packet length, nor variable width fields, nor verify the
|
||||
presence of requested synchronous responses. Cannot tell the sequence of
|
||||
frame bytes then ACK bytes (without wait phase) from just frame bytes.
|
||||
An upper layer protocol decoder will interpret content, the link layer
|
||||
decoder remains as transparent as possible, and will neither assume
|
||||
correct nor complete input data.
|
||||
'''
|
||||
|
||||
# Carrier sense, and synchronization loss implementation is currently
|
||||
# incomplete, and results in too many too short annotations, some of
|
||||
# them spurious and confusing. TODO Improve the implementation.
|
||||
_with_ann_carrier = False
|
||||
_with_ann_sync_loss = False
|
||||
|
||||
PIN_DATA, = range(1)
|
||||
ANN_CARRIER_BUSY, ANN_CARRIER_IDLE, \
|
||||
ANN_PAD_BIT, ANN_LOW_BIT, ANN_DATA_BIT, ANN_SHORT_DATA, ANN_SYNC_LOSS, \
|
||||
ANN_DATA_BYTE, \
|
||||
ANN_FRAME_INIT, ANN_FRAME_BYTES, ANN_FRAME_WAIT, \
|
||||
= range(11)
|
||||
|
||||
class SamplerateError(Exception):
|
||||
pass
|
||||
|
||||
class Decoder(srd.Decoder):
|
||||
api_version = 3
|
||||
id = 'pjdl'
|
||||
name = 'PJDL'
|
||||
longname = 'Padded Jittering Data Link'
|
||||
desc = 'PJDL, a single wire serial link layer for PJON.'
|
||||
license = 'gplv2+'
|
||||
inputs = ['logic']
|
||||
outputs = ['pjon_link']
|
||||
tags = ['Embedded/industrial']
|
||||
channels = (
|
||||
{'id': 'data' , 'name': 'DATA', 'desc': 'Single wire data'},
|
||||
)
|
||||
options = (
|
||||
{'id': 'mode', 'desc': 'Communication mode',
|
||||
'default': 1, 'values': (1, 2, 3, 4)},
|
||||
{'id': 'idle_add_us', 'desc': 'Added idle time (us)', 'default': 4},
|
||||
)
|
||||
annotations = (
|
||||
('cs_busy', 'Carrier busy'),
|
||||
('cs_idle', 'Carrier idle'),
|
||||
('bit_pad', 'Pad bit'),
|
||||
('bit_low', 'Low bit'),
|
||||
('bit_data', 'Data bit'),
|
||||
('bit_short', 'Short data'),
|
||||
('sync_loss', 'Sync loss'),
|
||||
('byte', 'Data byte'),
|
||||
('frame_init', 'Frame init'),
|
||||
('frame_bytes', 'Frame bytes'),
|
||||
('frame_wait', 'Frame wait'),
|
||||
)
|
||||
annotation_rows = (
|
||||
('carriers', 'Carriers', (ANN_CARRIER_BUSY, ANN_CARRIER_IDLE,)),
|
||||
('bits', 'Bits', (ANN_PAD_BIT, ANN_LOW_BIT, ANN_DATA_BIT, ANN_SHORT_DATA,)),
|
||||
('bytes', 'Bytes', (ANN_FRAME_INIT, ANN_DATA_BYTE, ANN_FRAME_WAIT,)),
|
||||
('frames', 'Frames', (ANN_FRAME_BYTES,)),
|
||||
('warns', 'Warnings', (ANN_SYNC_LOSS,)),
|
||||
)
|
||||
|
||||
# Communication modes' data bit and pad bit duration (in us), and
|
||||
# tolerances in percent and absolute (us).
|
||||
mode_times = {
|
||||
1: (44, 116),
|
||||
2: (40, 92),
|
||||
3: (28, 88),
|
||||
4: (26, 60),
|
||||
}
|
||||
time_tol_perc = 10
|
||||
time_tol_abs = 1.5
|
||||
|
||||
def __init__(self):
|
||||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
self.reset_state()
|
||||
|
||||
def reset_state(self):
|
||||
self.carrier_want_idle = True
|
||||
self.carrier_is_busy = False
|
||||
self.carrier_is_idle = False
|
||||
self.carrier_idle_ss = None
|
||||
self.carrier_busy_ss = None
|
||||
self.syncpad_fall_ss = None
|
||||
|
||||
self.edges = None
|
||||
self.symbols = None
|
||||
self.sync_pads = None
|
||||
self.data_bits = None
|
||||
self.frame_bytes = None
|
||||
self.short_bits = None
|
||||
|
||||
def start(self):
|
||||
self.out_ann = self.register(srd.OUTPUT_ANN)
|
||||
self.out_python = self.register(srd.OUTPUT_PYTHON)
|
||||
|
||||
def metadata(self, key, value):
|
||||
if key == srd.SRD_CONF_SAMPLERATE:
|
||||
self.samplerate = value
|
||||
self.span_prepare()
|
||||
|
||||
def putg(self, ss, es, data):
|
||||
cls = data[0]
|
||||
if not _with_ann_carrier and cls in (ANN_CARRIER_BUSY, ANN_CARRIER_IDLE):
|
||||
return
|
||||
if not _with_ann_sync_loss and cls in (ANN_SYNC_LOSS,):
|
||||
return
|
||||
self.put(ss, es, self.out_ann, data)
|
||||
|
||||
def putpy(self, ss, es, ptype, pdata):
|
||||
self.put(ss, es, self.out_python, [ptype, pdata])
|
||||
|
||||
def symbols_clear(self):
|
||||
syms = self.symbols or []
|
||||
self.symbols = []
|
||||
return syms
|
||||
|
||||
def symbols_append(self, ss, es, symbol, data = None):
|
||||
if self.symbols is None:
|
||||
self.symbols = []
|
||||
item = (ss, es, symbol, data)
|
||||
self.symbols.append(item)
|
||||
|
||||
def symbols_get_last(self, count = None):
|
||||
if not self.symbols:
|
||||
return None
|
||||
if count is None:
|
||||
count = 1
|
||||
if len(self.symbols) < count:
|
||||
return None
|
||||
items = self.symbols[-count:]
|
||||
if count == 1:
|
||||
items = items[0]
|
||||
return items
|
||||
|
||||
def symbols_update_last(self, ss, es, symbol, data = None):
|
||||
if not self.symbols:
|
||||
return None
|
||||
item = list(self.symbols[-1])
|
||||
if ss is not None:
|
||||
item[0] = ss
|
||||
if es is not None:
|
||||
item[1] = es
|
||||
if symbol is not None:
|
||||
item[2] = symbol
|
||||
if data is not None:
|
||||
item[3] = data
|
||||
self.symbols[-1] = tuple(item)
|
||||
|
||||
def symbols_has_prev(self, want_items):
|
||||
if not isinstance(want_items, (list, tuple,)):
|
||||
want_items = [want_items]
|
||||
if self.symbols is None:
|
||||
return False
|
||||
if len(self.symbols) < len(want_items):
|
||||
return False
|
||||
sym_off = len(self.symbols) - len(want_items)
|
||||
for idx, want_item in enumerate(want_items):
|
||||
if self.symbols[sym_off + idx][2] != want_item:
|
||||
return False
|
||||
return True
|
||||
|
||||
def symbols_collapse(self, count, symbol, data = None, squeeze = None):
|
||||
if self.symbols is None:
|
||||
return None
|
||||
if len(self.symbols) < count:
|
||||
return None
|
||||
self.symbols, last_data = self.symbols[:-count], self.symbols[-count:]
|
||||
while squeeze and self.symbols and self.symbols[-1][2] == squeeze:
|
||||
last_data.insert(0, self.symbols.pop())
|
||||
ss, es = last_data[0][0], last_data[-1][1]
|
||||
if data is None:
|
||||
data = last_data
|
||||
item = (ss, es, symbol, data)
|
||||
self.symbols.append(item)
|
||||
|
||||
def frame_flush(self):
|
||||
syms = self.symbols_clear()
|
||||
while syms and syms[0][2] == 'IDLE':
|
||||
syms.pop(0)
|
||||
while syms and syms[-1][2] == 'IDLE':
|
||||
syms.pop(-1)
|
||||
if not syms:
|
||||
return
|
||||
text = []
|
||||
data = []
|
||||
for sym in syms:
|
||||
if sym[2] == 'FRAME_INIT':
|
||||
text.append('INIT')
|
||||
data.append('INIT')
|
||||
continue
|
||||
if sym[2] == 'SYNC_PAD':
|
||||
if not text or text[-1] != 'SYNC':
|
||||
text.append('SYNC')
|
||||
data.append('SYNC')
|
||||
continue
|
||||
if sym[2] == 'DATA_BYTE':
|
||||
b = [bit[3] for bit in sym[3] if bit[2] == 'DATA_BIT']
|
||||
b = bitpack(b)
|
||||
text.append('{:02x}'.format(b))
|
||||
data.append(b)
|
||||
continue
|
||||
if sym[2] == 'SHORT_BIT':
|
||||
if not text or text[-1] != 'SHORT':
|
||||
text.append('SHORT')
|
||||
data.append('SHORT')
|
||||
continue
|
||||
if sym[2] == 'WAIT_ACK':
|
||||
text.append('WAIT')
|
||||
data.append('WAIT')
|
||||
continue
|
||||
text = ' '.join(text)
|
||||
ss, es = syms[0][0], syms[-1][1]
|
||||
self.putg(ss, es, [ANN_FRAME_BYTES, [text]])
|
||||
self.putpy(ss, es, 'FRAME_DATA', data)
|
||||
|
||||
def carrier_flush(self):
|
||||
# Force annotations if BUSY started, or if IDLE tracking started
|
||||
# and kept running for long enough. This will be called before
|
||||
# internal state reset, so we won't manipulate internal variables,
|
||||
# and can afford to emit annotations which haven't met their
|
||||
# proper end condition yet.
|
||||
if self.carrier_busy_ss:
|
||||
ss, es = self.carrier_busy_ss, self.samplenum
|
||||
self.putg(ss, es, [ANN_CARRIER_BUSY, ['BUSY']])
|
||||
if self.carrier_idle_ss:
|
||||
ss, es = self.carrier_idle_ss, self.samplenum
|
||||
ss += int(self.idle_width)
|
||||
if ss < es:
|
||||
self.putg(ss, es, [ANN_CARRIER_IDLE, ['IDLE']])
|
||||
|
||||
def carrier_set_idle(self, on, ss, es):
|
||||
if on:
|
||||
# IDLE starts here, or continues.
|
||||
if not self.carrier_idle_ss:
|
||||
self.carrier_idle_ss = int(ss)
|
||||
if not self.symbols_has_prev('IDLE'):
|
||||
self.symbols_append(ss, ss, 'IDLE')
|
||||
self.symbols_update_last(None, es, None)
|
||||
# HACK We have seen an IDLE condition. This implementation
|
||||
# loses details which are used to track IDLE, but it's more
|
||||
# important to start accumulation of a new frame here.
|
||||
self.frame_flush()
|
||||
self.reset_state()
|
||||
# end of HACK
|
||||
self.carrier_is_idle = True
|
||||
self.carrier_want_idle = False
|
||||
return
|
||||
# IDLE ends here.
|
||||
if self.symbols_has_prev('IDLE'):
|
||||
self.symbols_update_last(None, es, None)
|
||||
self.carrier_flush()
|
||||
self.carrier_is_idle = False
|
||||
self.carrier_idle_ss = None
|
||||
|
||||
def carrier_set_busy(self, on, snum):
|
||||
self.carrier_is_busy = on
|
||||
if on:
|
||||
self.carrier_is_idle = None
|
||||
if not self.carrier_busy_ss:
|
||||
self.carrier_busy_ss = snum
|
||||
return
|
||||
if self.carrier_busy_ss:
|
||||
self.putg(self.carrier_busy_ss, snum, [ANN_CARRIER_BUSY, ['BUSY']])
|
||||
self.carrier_busy_ss = None
|
||||
self.carrier_is_busy = False
|
||||
|
||||
def carrier_check(self, level, snum):
|
||||
|
||||
# When HIGH is seen, immediately end IDLE and switch to BUSY.
|
||||
if level:
|
||||
self.carrier_set_idle(False, snum, snum)
|
||||
self.carrier_set_busy(True, snum)
|
||||
return
|
||||
|
||||
# LOW is seen. Start tracking an IDLE period if not done yet.
|
||||
if not self.carrier_idle_ss:
|
||||
self.carrier_idle_ss = int(snum)
|
||||
|
||||
# End BUSY when LOW persisted for an exact data byte's length.
|
||||
# Start IDLE when LOW persisted for a data byte's length plus
|
||||
# the user specified additional period.
|
||||
span = snum - self.carrier_idle_ss
|
||||
if span >= self.byte_width:
|
||||
self.carrier_set_busy(False, snum)
|
||||
if span >= self.idle_width:
|
||||
self.carrier_set_idle(True, self.carrier_idle_ss + self.idle_width, snum)
|
||||
|
||||
def span_prepare(self):
|
||||
'''Prepare calculation of durations in terms of samples.'''
|
||||
|
||||
# Determine samples per microsecond, and sample counts for
|
||||
# several bit types, and sample count for a data byte's
|
||||
# length, including optional extra time. Determine ranges
|
||||
# for bit widths (tolerance margin).
|
||||
|
||||
# Get times in microseconds.
|
||||
mode_times = self.mode_times[self.options['mode']]
|
||||
mode_times = [t * 1.0 for t in mode_times]
|
||||
self.data_width, self.pad_width = mode_times
|
||||
self.byte_width = self.pad_width + 9 * self.data_width
|
||||
self.add_idle_width = self.options['idle_add_us']
|
||||
self.idle_width = self.byte_width + self.add_idle_width
|
||||
|
||||
# Derive ranges (add tolerance) and scale to sample counts.
|
||||
self.usec_width = self.samplerate / 1e6
|
||||
self.hold_high_width = 9 * self.time_tol_abs * self.usec_width
|
||||
|
||||
def _get_range(width):
|
||||
reladd = self.time_tol_perc / 100
|
||||
absadd = self.time_tol_abs
|
||||
lower = min(width * (1 - reladd), width - absadd)
|
||||
upper = max(width * (1 + reladd), width + absadd)
|
||||
lower = floor(lower * self.usec_width)
|
||||
upper = ceil(upper * self.usec_width)
|
||||
return (lower, upper + 1)
|
||||
|
||||
self.data_bit_1_range = _get_range(self.data_width * 1)
|
||||
self.data_bit_2_range = _get_range(self.data_width * 2)
|
||||
self.data_bit_3_range = _get_range(self.data_width * 3)
|
||||
self.data_bit_4_range = _get_range(self.data_width * 4)
|
||||
self.short_data_range = _get_range(self.data_width / 4)
|
||||
self.pad_bit_range = _get_range(self.pad_width)
|
||||
|
||||
self.data_width *= self.usec_width
|
||||
self.pad_width *= self.usec_width
|
||||
self.byte_width *= self.usec_width
|
||||
self.idle_width *= self.usec_width
|
||||
|
||||
self.lookahead_width = int(4 * self.data_width)
|
||||
|
||||
def span_snum_to_us(self, count):
|
||||
return count / self.usec_width
|
||||
|
||||
def span_is_pad(self, span):
|
||||
return span in range(*self.pad_bit_range)
|
||||
|
||||
def span_is_data(self, span):
|
||||
if span in range(*self.data_bit_1_range):
|
||||
return 1
|
||||
if span in range(*self.data_bit_2_range):
|
||||
return 2
|
||||
if span in range(*self.data_bit_3_range):
|
||||
return 3
|
||||
if span in range(*self.data_bit_4_range):
|
||||
return 4
|
||||
return False
|
||||
|
||||
def span_is_short(self, span):
|
||||
return span in range(*self.short_data_range)
|
||||
|
||||
def wait_until(self, want):
|
||||
'''Wait until a given location, but keep sensing carrier.'''
|
||||
|
||||
# Implementor's note: Avoids skip values below 1. This version
|
||||
# "may overshoot" by one sample. Which should be acceptable for
|
||||
# this specific use case (can put the sample point of a bit time
|
||||
# out of the center by some 4% under worst case conditions).
|
||||
|
||||
want = int(want)
|
||||
while True:
|
||||
diff = max(want - self.samplenum, 1)
|
||||
pins = self.wait([{PIN_DATA: 'e'}, {'skip': diff}])
|
||||
self.carrier_check(pins[PIN_DATA], self.samplenum)
|
||||
if self.samplenum >= want:
|
||||
return pins
|
||||
# UNREACH
|
||||
|
||||
def decode(self):
|
||||
if not self.samplerate or self.samplerate < 1e6:
|
||||
raise SamplerateError('Need a samplerate of at least 1MSa/s')
|
||||
|
||||
# As a special case the first low period in the input capture is
|
||||
# saught regardless of whether we can see its falling edge. This
|
||||
# approach is also used to recover after synchronization was lost.
|
||||
#
|
||||
# The important condition here in the main loop is: Get the next
|
||||
# edge's position, but time out after a maximum period of four
|
||||
# data bits. This allows for the detection of SYNC pulses, also
|
||||
# responds "soon enough" to DATA bits where edges can be few
|
||||
# within a data byte. Also avoids excessive waits for unexpected
|
||||
# communication errors.
|
||||
#
|
||||
# DATA bits within a byte are taken at fixed intervals relative
|
||||
# to the SYNC-PAD's falling edge. It's essential to check the
|
||||
# carrier at every edge, also during DATA bit sampling. Simple
|
||||
# skips to the desired sample point could break that feature.
|
||||
while True:
|
||||
|
||||
# Help kick-start the IDLE condition detection after
|
||||
# decoder state reset.
|
||||
if not self.edges:
|
||||
curr_level, = self.wait({PIN_DATA: 'l'})
|
||||
self.carrier_check(curr_level, self.samplenum)
|
||||
self.edges = [self.samplenum]
|
||||
continue
|
||||
|
||||
# Advance to the next edge, or over a medium span without an
|
||||
# edge. Prepare to classify the distance to derive bit types
|
||||
# from these details.
|
||||
last_snum = self.samplenum
|
||||
curr_level, = self.wait([{PIN_DATA: 'e'}, {'skip': self.lookahead_width}])
|
||||
self.carrier_check(curr_level, self.samplenum)
|
||||
bit_level = curr_level
|
||||
edge_seen = self.matched[0]
|
||||
if edge_seen:
|
||||
bit_level = 1 - bit_level
|
||||
if not self.edges:
|
||||
self.edges = [self.samplenum]
|
||||
continue
|
||||
self.edges.append(self.samplenum)
|
||||
curr_snum = self.samplenum
|
||||
|
||||
# Check bit width (can also be multiple data bits).
|
||||
span = self.edges[-1] - self.edges[-2]
|
||||
is_pad = bit_level and self.span_is_pad(span)
|
||||
is_data = self.span_is_data(span)
|
||||
is_short = bit_level and self.span_is_short(span)
|
||||
|
||||
if is_pad:
|
||||
# BEWARE! Use ss value of last edge (genuinely seen, or
|
||||
# inserted after a DATA byte) for PAD bit annotations.
|
||||
ss, es = self.edges[-2], curr_snum
|
||||
texts = ['PAD', '{:d}'.format(bit_level)]
|
||||
self.putg(ss, es, [ANN_PAD_BIT, texts])
|
||||
self.symbols_append(ss, es, 'PAD_BIT', bit_level)
|
||||
ss, es = self.symbols_get_last()[:2]
|
||||
self.putpy(ss, es, 'PAD_BIT', bit_level)
|
||||
continue
|
||||
|
||||
if is_short:
|
||||
ss, es = last_snum, curr_snum
|
||||
texts = ['SHORT', '{:d}'.format(bit_level)]
|
||||
self.putg(ss, es, [ANN_SHORT_DATA, texts])
|
||||
self.symbols_append(ss, es, 'SHORT_BIT', bit_level)
|
||||
ss, es = self.symbols_get_last()[:2]
|
||||
self.putpy(ss, es, 'SHORT_BIT', bit_level)
|
||||
continue
|
||||
|
||||
# Force IDLE period check when the decoder seeks to sync
|
||||
# to the input data stream.
|
||||
if not bit_level and not self.symbols and self.carrier_want_idle:
|
||||
continue
|
||||
|
||||
# Accept arbitrary length LOW phases after DATA bytes(!) or
|
||||
# SHORT pulses, but not within a DATA byte or SYNC-PAD etc.
|
||||
# This covers the late start of the next SYNC-PAD (byte of
|
||||
# a frame, or ACK byte after a frame, or the start of the
|
||||
# next frame).
|
||||
if not bit_level:
|
||||
if self.symbols_has_prev('DATA_BYTE'):
|
||||
continue
|
||||
if self.symbols_has_prev('SHORT_BIT'):
|
||||
continue
|
||||
if self.symbols_has_prev('WAIT_ACK'):
|
||||
continue
|
||||
|
||||
# Get (consume!) the LOW DATA bit after a PAD.
|
||||
took_low = False
|
||||
if is_data and not bit_level and self.symbols_has_prev('PAD_BIT'):
|
||||
took_low = True
|
||||
is_data -= 1
|
||||
next_snum = int(last_snum + self.data_width)
|
||||
ss, es = last_snum, next_snum
|
||||
texts = ['ZERO', '{:d}'.format(bit_level)]
|
||||
self.putg(ss, es, [ANN_LOW_BIT, texts])
|
||||
self.symbols_append(ss, es, 'ZERO_BIT', bit_level)
|
||||
ss, es = self.symbols_get_last()[:2]
|
||||
self.putpy(ss, es, 'DATA_BIT', bit_level)
|
||||
self.data_fall_time = last_snum
|
||||
last_snum = next_snum
|
||||
# Turn the combination of PAD and LOW DATA into SYNC-PAD.
|
||||
# Start data bit accumulation after a SYNC-PAD was seen.
|
||||
sync_pad_seq = ['PAD_BIT', 'ZERO_BIT']
|
||||
if self.symbols_has_prev(sync_pad_seq):
|
||||
self.symbols_collapse(len(sync_pad_seq), 'SYNC_PAD')
|
||||
ss, es = self.symbols_get_last()[:2]
|
||||
self.putpy(ss, es, 'SYNC_PAD', True)
|
||||
self.data_bits = []
|
||||
# Turn three subsequent SYNC-PAD into FRAME-INIT. Start the
|
||||
# accumulation of frame bytes when FRAME-INIT was seen.
|
||||
frame_init_seq = 3 * ['SYNC_PAD']
|
||||
if self.symbols_has_prev(frame_init_seq):
|
||||
self.symbols_collapse(len(frame_init_seq), 'FRAME_INIT')
|
||||
# Force a flush of the previous frame after we have
|
||||
# reliably detected the start of another one. This is a
|
||||
# workaround for this decoder's inability to detect the
|
||||
# end of a frame after an ACK was seen or byte counts
|
||||
# have been reached. We cannot assume perfect input,
|
||||
# thus we leave all interpretation of frame content to
|
||||
# upper layers. Do keep the recently queued FRAME_INIT
|
||||
# symbol across the flush operation.
|
||||
if len(self.symbols) > 1:
|
||||
keep = self.symbols.pop(-1)
|
||||
self.frame_flush()
|
||||
self.symbols.clear()
|
||||
self.symbols.append(keep)
|
||||
ss, es = self.symbols_get_last()[:2]
|
||||
texts = ['FRAME INIT', 'INIT', 'I']
|
||||
self.putg(ss, es, [ANN_FRAME_INIT, texts])
|
||||
self.putpy(ss, es, 'FRAME_INIT', True)
|
||||
self.frame_bytes = []
|
||||
# Collapse SYNC-PAD after SHORT+ into a WAIT-ACK. Include
|
||||
# all leading SHORT bits in the WAIT as well.
|
||||
wait_ack_seq = ['SHORT_BIT', 'SYNC_PAD']
|
||||
if self.symbols_has_prev(wait_ack_seq):
|
||||
self.symbols_collapse(len(wait_ack_seq), 'WAIT_ACK',
|
||||
squeeze = 'SHORT_BIT')
|
||||
ss, es = self.symbols_get_last()[:2]
|
||||
texts = ['WAIT for sync response', 'WAIT response', 'WAIT', 'W']
|
||||
self.putg(ss, es, [ANN_FRAME_WAIT, texts])
|
||||
self.putpy(ss, es, 'SYNC_RESP_WAIT', True)
|
||||
if took_low and not is_data:
|
||||
# Start at the very next edge if we just consumed a LOW
|
||||
# after a PAD bit, and the DATA bit count is exhausted.
|
||||
# This improves robustness, deals with inaccurate edge
|
||||
# positions. (Motivated by real world captures, the spec
|
||||
# would not discuss bit time tolerances.)
|
||||
continue
|
||||
|
||||
# When we get here, the only remaining (the only supported)
|
||||
# activity is the collection of a data byte's DATA bits.
|
||||
# These are not taken by the main loop's "edge search, with
|
||||
# a timeout" approach, which is "too tolerant". Instead all
|
||||
# DATA bits get sampled at a fixed interval and relative to
|
||||
# the SYNC-PAD's falling edge. We expect to have seen the
|
||||
# data byte' SYNC-PAD before. If we haven't, the decoder is
|
||||
# not yet synchronized to the input data.
|
||||
if not is_data:
|
||||
fast_cont = edge_seen and curr_level
|
||||
ss, es = last_snum, curr_snum
|
||||
texts = ['failed pulse length check', 'pulse length', 'length']
|
||||
self.putg(ss, es, [ANN_SYNC_LOSS, texts])
|
||||
self.frame_flush()
|
||||
self.carrier_flush()
|
||||
self.reset_state()
|
||||
if fast_cont:
|
||||
self.edges = [self.samplenum]
|
||||
continue
|
||||
if not self.symbols_has_prev('SYNC_PAD'):
|
||||
# Fast reponse to the specific combination of: no-sync,
|
||||
# edge seen, and current high level. In this case we
|
||||
# can reset internal state, but also can continue the
|
||||
# interpretation right after the most recently seen
|
||||
# rising edge, which could start the next PAD time.
|
||||
# Otherwise continue slow interpretation after reset.
|
||||
fast_cont = edge_seen and curr_level
|
||||
self.frame_flush()
|
||||
self.carrier_flush()
|
||||
self.reset_state()
|
||||
if fast_cont:
|
||||
self.edges = [self.samplenum]
|
||||
continue
|
||||
|
||||
# The main loop's "edge search with period timeout" approach
|
||||
# can have provided up to three more DATA bits after the LOW
|
||||
# bit of the SYNC-PAD. Consume them immediately in that case,
|
||||
# otherwise .wait() for their sample point. Stick with float
|
||||
# values for bit sample points and bit time boundaries for
|
||||
# improved accuracy, only round late to integers when needed.
|
||||
bit_field = []
|
||||
bit_ss = self.data_fall_time + self.data_width
|
||||
for bit_idx in range(8):
|
||||
bit_es = bit_ss + self.data_width
|
||||
bit_snum = (bit_es + bit_ss) / 2
|
||||
if bit_snum > self.samplenum:
|
||||
bit_level, = self.wait_until(bit_snum)
|
||||
ss, es = ceil(bit_ss), floor(bit_es)
|
||||
texts = ['{:d}'.format(bit_level)]
|
||||
self.putg(ss, es, [ANN_DATA_BIT, texts])
|
||||
self.symbols_append(ss, es, 'DATA_BIT', bit_level)
|
||||
ss, es = self.symbols_get_last()[:2]
|
||||
self.putpy(ss, es, 'DATA_BIT', bit_level)
|
||||
bit_field.append(bit_level)
|
||||
if self.data_bits is not None:
|
||||
self.data_bits.append(bit_level)
|
||||
bit_ss = bit_es
|
||||
end_snum = bit_es
|
||||
curr_level, = self.wait_until(end_snum)
|
||||
curr_snum = self.samplenum
|
||||
|
||||
# We are at the exact _calculated_ boundary of the last DATA
|
||||
# bit time. Improve robustness for those situations where
|
||||
# the transmitter's and the sender's timings differ within a
|
||||
# margin, and the transmitter may hold the last DATA bit's
|
||||
# HIGH level for a little longer.
|
||||
#
|
||||
# When no falling edge is seen within the maximum tolerance
|
||||
# for the last DATA bit, then this could be the combination
|
||||
# of a HIGH DATA bit and a PAD bit without a LOW in between.
|
||||
# Fake an edge in that case, to re-use existing code paths.
|
||||
# Make sure to keep referencing times to the last SYNC pad's
|
||||
# falling edge. This is the last reliable condition we have.
|
||||
if curr_level:
|
||||
hold = self.hold_high_width
|
||||
curr_level, = self.wait([{PIN_DATA: 'l'}, {'skip': int(hold)}])
|
||||
self.carrier_check(curr_level, self.samplenum)
|
||||
if self.matched[1]:
|
||||
self.edges.append(curr_snum)
|
||||
curr_level = 1 - curr_level
|
||||
curr_snum = self.samplenum
|
||||
|
||||
# Get the byte value from the bits (when available).
|
||||
# TODO Has the local 'bit_field' become obsolete, or should
|
||||
# self.data_bits go away?
|
||||
data_byte = bitpack(bit_field)
|
||||
if self.data_bits is not None:
|
||||
data_byte = bitpack(self.data_bits)
|
||||
self.data_bits.clear()
|
||||
if self.frame_bytes is not None:
|
||||
self.frame_bytes.append(data_byte)
|
||||
|
||||
# Turn a sequence of a SYNC-PAD and eight DATA bits into a
|
||||
# DATA-BYTE symbol.
|
||||
byte_seq = ['SYNC_PAD'] + 8 * ['DATA_BIT']
|
||||
if self.symbols_has_prev(byte_seq):
|
||||
self.symbols_collapse(len(byte_seq), 'DATA_BYTE')
|
||||
ss, es = self.symbols_get_last()[:2]
|
||||
texts = ['{:02x}'.format(data_byte)]
|
||||
self.putg(ss, es, [ANN_DATA_BYTE, texts])
|
||||
self.putpy(ss, es, 'DATA_BYTE', data_byte)
|
||||
|
||||
# Optionally terminate the accumulation of a frame when a
|
||||
# WAIT-ACK period was followed by a DATA-BYTE? This could
|
||||
# flush the current packet before the next FRAME-INIT or
|
||||
# IDLE are seen, and increases usability for short input
|
||||
# data (aggressive trimming). It won't help when WAIT is
|
||||
# not seen, though.
|
||||
sync_resp_seq = ['WAIT_ACK'] + ['DATA_BYTE']
|
||||
if self.symbols_has_prev(sync_resp_seq):
|
||||
self.frame_flush()
|
||||
25
libsigrokdecode4DSL/decoders/pjon/__init__.py
Normal file
25
libsigrokdecode4DSL/decoders/pjon/__init__.py
Normal file
@@ -0,0 +1,25 @@
|
||||
##
|
||||
## This file is part of the libsigrokdecode project.
|
||||
##
|
||||
## Copyright (C) 2020 Gerhard Sittig <gerhard.sittig@gmx.net>
|
||||
##
|
||||
## 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
|
||||
## the Free Software Foundation; either version 2 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
'''
|
||||
This protocol decoder interprets the PJON protocol on top of the PJDL
|
||||
link layer (and potentially other link layers).
|
||||
'''
|
||||
|
||||
from .pd import Decoder
|
||||
603
libsigrokdecode4DSL/decoders/pjon/pd.py
Normal file
603
libsigrokdecode4DSL/decoders/pjon/pd.py
Normal file
@@ -0,0 +1,603 @@
|
||||
##
|
||||
## This file is part of the libsigrokdecode project.
|
||||
##
|
||||
## Copyright (C) 2020 Gerhard Sittig <gerhard.sittig@gmx.net>
|
||||
##
|
||||
## 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
|
||||
## the Free Software Foundation; either version 2 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
# See the https://www.pjon.org/ PJON project page and especially the
|
||||
# https://www.pjon.org/PJON-protocol-specification-v3.2.php protocol
|
||||
# specification, which can use different link layers.
|
||||
|
||||
# TODO
|
||||
# - Check for the correct order of optional fields (the spec is not as
|
||||
# explicit on these details as I'd expect).
|
||||
# - Check decoder's robustness, completeness, and correctness when more
|
||||
# captures become available. Currently there are only few, which only
|
||||
# cover minimal communication, and none of the protocol's flexibility.
|
||||
# The decoder was essentially written based on the available docs, and
|
||||
# then took some arbitrary choices and liberties to cope with real life
|
||||
# data from an example setup. Strictly speaking this decoder violates
|
||||
# the spec, and errs towards the usability side.
|
||||
|
||||
import sigrokdecode as srd
|
||||
import struct
|
||||
|
||||
ANN_RX_INFO, ANN_HDR_CFG, ANN_PKT_LEN, ANN_META_CRC, ANN_TX_INFO, \
|
||||
ANN_SVC_ID, ANN_PKT_ID, ANN_ANON_DATA, ANN_PAYLOAD, ANN_END_CRC, \
|
||||
ANN_SYN_RSP, \
|
||||
ANN_RELATION, \
|
||||
ANN_WARN, \
|
||||
= range(13)
|
||||
|
||||
def calc_crc8(data):
|
||||
crc = 0
|
||||
for b in data:
|
||||
crc ^= b
|
||||
for i in range(8):
|
||||
odd = crc % 2
|
||||
crc >>= 1
|
||||
if odd:
|
||||
crc ^= 0x97
|
||||
return crc
|
||||
|
||||
def calc_crc32(data):
|
||||
crc = 0xffffffff
|
||||
for b in data:
|
||||
crc ^= b
|
||||
for i in range(8):
|
||||
odd = crc % 2
|
||||
crc >>= 1
|
||||
if odd:
|
||||
crc ^= 0xedb88320
|
||||
crc ^= 0xffffffff
|
||||
return crc
|
||||
|
||||
class Decoder(srd.Decoder):
|
||||
api_version = 3
|
||||
id = 'pjon'
|
||||
name = 'PJON'
|
||||
longname = 'PJON'
|
||||
desc = 'The PJON protocol.'
|
||||
license = 'gplv2+'
|
||||
inputs = ['pjon_link']
|
||||
outputs = []
|
||||
tags = ['Embedded/industrial']
|
||||
annotations = (
|
||||
('rx_info', 'Receiver ID'),
|
||||
('hdr_cfg', 'Header config'),
|
||||
('pkt_len', 'Packet length'),
|
||||
('meta_crc', 'Meta CRC'),
|
||||
('tx_info', 'Sender ID'),
|
||||
('port', 'Service ID'),
|
||||
('pkt_id', 'Packet ID'),
|
||||
('anon', 'Anonymous data'),
|
||||
('payload', 'Payload'),
|
||||
('end_crc', 'End CRC'),
|
||||
('syn_rsp', 'Sync response'),
|
||||
('relation', 'Relation'),
|
||||
('warning', 'Warning'),
|
||||
)
|
||||
annotation_rows = (
|
||||
('fields', 'Fields', (
|
||||
ANN_RX_INFO, ANN_HDR_CFG, ANN_PKT_LEN, ANN_META_CRC, ANN_TX_INFO,
|
||||
ANN_SVC_ID, ANN_ANON_DATA, ANN_PAYLOAD, ANN_END_CRC, ANN_SYN_RSP,
|
||||
)),
|
||||
('relations', 'Relations', (ANN_RELATION,)),
|
||||
('warnings', 'Warnings', (ANN_WARN,)),
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
self.reset_frame()
|
||||
|
||||
def reset_frame(self):
|
||||
self.frame_ss = None
|
||||
self.frame_es = None
|
||||
self.frame_rx_id = None
|
||||
self.frame_tx_id = None
|
||||
self.frame_payload_text = None
|
||||
self.frame_bytes = None
|
||||
self.frame_has_ack = None
|
||||
self.ack_bytes = None
|
||||
self.ann_ss = None
|
||||
self.ann_es = None
|
||||
|
||||
def start(self):
|
||||
self.out_ann = self.register(srd.OUTPUT_ANN)
|
||||
|
||||
def putg(self, ss, es, ann, data):
|
||||
self.put(ss, es, self.out_ann, [ann, data])
|
||||
|
||||
def frame_flush(self):
|
||||
if not self.frame_bytes:
|
||||
return
|
||||
if not self.frame_ss or not self.frame_es:
|
||||
return
|
||||
|
||||
# Emit "communication relation" details.
|
||||
# TODO Include the service ID (port number) as well?
|
||||
text = []
|
||||
if self.frame_rx_id is not None:
|
||||
text.append("RX {}".format(self.frame_rx_id[-1]))
|
||||
if self.frame_tx_id is not None:
|
||||
text.append("TX {}".format(self.frame_tx_id[-1]))
|
||||
if self.frame_payload_text is not None:
|
||||
text.append("DATA {}".format(self.frame_payload_text))
|
||||
if self.frame_has_ack is not None:
|
||||
text.append("ACK {:02x}".format(self.frame_has_ack))
|
||||
if text:
|
||||
text = " - ".join(text)
|
||||
self.putg(self.frame_ss, self.frame_es, ANN_RELATION, [text])
|
||||
|
||||
def handle_field_get_desc(self, idx = None):
|
||||
'''Lookup description of a PJON frame field.'''
|
||||
if not self.field_desc:
|
||||
return None
|
||||
if idx is None:
|
||||
idx = self.field_desc_idx
|
||||
if idx >= 0 and idx >= len(self.field_desc):
|
||||
return None
|
||||
if idx < 0 and abs(idx) > len(self.field_desc):
|
||||
return None
|
||||
desc = self.field_desc[idx]
|
||||
return desc
|
||||
|
||||
def handle_field_add_desc(self, fmt, hdl, cls = None):
|
||||
'''Register description for a PJON frame field.'''
|
||||
item = {
|
||||
'format': fmt,
|
||||
'width': struct.calcsize(fmt),
|
||||
'handler': hdl,
|
||||
'anncls': cls,
|
||||
}
|
||||
self.field_desc.append(item)
|
||||
|
||||
def handle_field_seed_desc(self):
|
||||
'''Seed list of PJON frame fields' descriptions.'''
|
||||
|
||||
# At the start of a PJON frame, the layout of only two fields
|
||||
# is known. Subsequent fields (their presence, and width) depend
|
||||
# on the content of the header config field.
|
||||
|
||||
self.field_desc = []
|
||||
self.handle_field_add_desc('<B', self.handle_field_rx_id, ANN_RX_INFO)
|
||||
self.handle_field_add_desc('<B', self.handle_field_config, ANN_HDR_CFG)
|
||||
|
||||
self.field_desc_idx = 0
|
||||
self.field_desc_got = 0
|
||||
|
||||
self.frame_ss = None
|
||||
self.frame_es = None
|
||||
self.frame_rx_id = None
|
||||
self.frame_is_broadcast = None
|
||||
self.frame_tx_id = None
|
||||
self.frame_payload = None
|
||||
self.frame_payload_text = None
|
||||
self.frame_has_ack = None
|
||||
|
||||
def handle_field_rx_id(self, b):
|
||||
'''Process receiver ID field of a PJON frame.'''
|
||||
|
||||
b = b[0]
|
||||
|
||||
# Provide text presentation, caller emits frame field annotation.
|
||||
if b == 255: # "not assigned"
|
||||
id_txt = 'NA'
|
||||
elif b == 0: # "broadcast"
|
||||
id_txt = 'BC'
|
||||
else: # unicast
|
||||
id_txt = '{:d}'.format(b)
|
||||
texts = [
|
||||
'RX_ID {}'.format(id_txt),
|
||||
'{}'.format(id_txt),
|
||||
]
|
||||
|
||||
# Track RX info for communication relation emission.
|
||||
self.frame_rx_id = (b, id_txt)
|
||||
self.frame_is_broadcast = b == 0
|
||||
|
||||
return texts
|
||||
|
||||
def handle_field_config(self, b):
|
||||
'''Process header config field of a PJON frame.'''
|
||||
|
||||
# Caller provides a list of values. We want a single scalar.
|
||||
b = b[0]
|
||||
|
||||
# Get the config flags.
|
||||
self.cfg_shared = b & (1 << 0)
|
||||
self.cfg_tx_info = b & (1 << 1)
|
||||
self.cfg_sync_ack = b & (1 << 2)
|
||||
self.cfg_async_ack = b & (1 << 3)
|
||||
self.cfg_port = b & (1 << 4)
|
||||
self.cfg_crc32 = b & (1 << 5)
|
||||
self.cfg_len16 = b & (1 << 6)
|
||||
self.cfg_pkt_id = b & (1 << 7)
|
||||
|
||||
# Get a textual presentation of the flags.
|
||||
text = []
|
||||
text.append('pkt_id' if self.cfg_pkt_id else '-') # packet number
|
||||
text.append('len16' if self.cfg_len16 else '-') # 16bit length not 8bit
|
||||
text.append('crc32' if self.cfg_crc32 else '-') # 32bit CRC not 8bit
|
||||
text.append('svc_id' if self.cfg_port else '-') # port aka service ID
|
||||
text.append('ack_mode' if self.cfg_async_ack else '-') # async response
|
||||
text.append('ack' if self.cfg_sync_ack else '-') # synchronous response
|
||||
text.append('tx_info' if self.cfg_tx_info else '-') # sender address
|
||||
text.append('bus_id' if self.cfg_shared else '-') # "shared" vs "local"
|
||||
text = ' '.join(text)
|
||||
bits = '{:08b}'.format(b)
|
||||
texts = [
|
||||
'CFG {:s}'.format(text),
|
||||
'CFG {}'.format(bits),
|
||||
bits
|
||||
]
|
||||
|
||||
# TODO Come up with the most appropriate phrases for this logic.
|
||||
# Are separate instruction groups with repeated conditions more
|
||||
# readable than one common block which registers fields _and_
|
||||
# updates the overhead size? Or is the latter preferrable due to
|
||||
# easier maintenance and less potential for inconsistency?
|
||||
|
||||
# Get the size of variable width fields, to calculate the size
|
||||
# of the packet overhead (the part that is not the payload data).
|
||||
# This lets us derive the payload length when we later receive
|
||||
# the frame's total length.
|
||||
u8_fmt = '>B'
|
||||
u16_fmt = '>H'
|
||||
u32_fmt = '>L'
|
||||
len_fmt = u16_fmt if self.cfg_len16 else u8_fmt
|
||||
bus_fmt = '>4B'
|
||||
crc_fmt = u32_fmt if self.cfg_crc32 else u8_fmt
|
||||
self.cfg_overhead = 0
|
||||
self.cfg_overhead += struct.calcsize(u8_fmt) # receiver ID
|
||||
self.cfg_overhead += struct.calcsize(u8_fmt) # header config
|
||||
self.cfg_overhead += struct.calcsize(len_fmt) # packet length
|
||||
self.cfg_overhead += struct.calcsize(u8_fmt) # initial CRC, always CRC8
|
||||
# TODO Check for completeness and correctness.
|
||||
if self.cfg_shared:
|
||||
self.cfg_overhead += struct.calcsize(u32_fmt) # receiver bus
|
||||
if self.cfg_tx_info:
|
||||
if self.cfg_shared:
|
||||
self.cfg_overhead += struct.calcsize(u32_fmt) # sender bus
|
||||
self.cfg_overhead += struct.calcsize(u8_fmt) # sender ID
|
||||
if self.cfg_port:
|
||||
self.cfg_overhead += struct.calcsize(u16_fmt) # service ID
|
||||
if self.cfg_pkt_id:
|
||||
self.cfg_overhead += struct.calcsize(u16_fmt) # packet ID
|
||||
self.cfg_overhead += struct.calcsize(crc_fmt) # end CRC
|
||||
|
||||
# Register more frame fields as we learn about their presence and
|
||||
# format. Up to this point only receiver ID and header config were
|
||||
# registered since their layout is fixed.
|
||||
#
|
||||
# Packet length and meta CRC are always present but can be of
|
||||
# variable width. Optional fields follow the meta CRC and preceed
|
||||
# the payload bytes. Notice that payload length isn't known here
|
||||
# either, though its position is known already. The packet length
|
||||
# is yet to get received. Subtracting the packet overhead from it
|
||||
# (which depends on the header configuration) will provide that
|
||||
# information.
|
||||
#
|
||||
# TODO Check for completeness and correctness.
|
||||
# TODO Optionally fold overhead size arith and field registration
|
||||
# into one block of instructions, to reduce the redundancy in the
|
||||
# condition checks, and raise awareness for incomplete sequences
|
||||
# during maintenance.
|
||||
self.handle_field_add_desc(len_fmt, self.handle_field_pkt_len, ANN_PKT_LEN)
|
||||
self.handle_field_add_desc(u8_fmt, self.handle_field_meta_crc, ANN_META_CRC)
|
||||
if self.cfg_shared:
|
||||
self.handle_field_add_desc(bus_fmt, self.handle_field_rx_bus, ANN_ANON_DATA)
|
||||
if self.cfg_tx_info:
|
||||
if self.cfg_shared:
|
||||
self.handle_field_add_desc(bus_fmt, self.handle_field_tx_bus, ANN_ANON_DATA)
|
||||
self.handle_field_add_desc(u8_fmt, self.handle_field_tx_id, ANN_ANON_DATA)
|
||||
if self.cfg_port:
|
||||
self.handle_field_add_desc(u16_fmt, ['PORT {:d}', '{:d}'], ANN_ANON_DATA)
|
||||
if self.cfg_pkt_id:
|
||||
self.handle_field_add_desc(u16_fmt, ['PKT {:04x}', '{:04x}'], ANN_ANON_DATA)
|
||||
pl_fmt = '>{:d}B'.format(0)
|
||||
self.handle_field_add_desc(pl_fmt, self.handle_field_payload, ANN_PAYLOAD)
|
||||
self.handle_field_add_desc(crc_fmt, self.handle_field_end_crc, ANN_END_CRC)
|
||||
|
||||
# Emit warning annotations for invalid flag combinations.
|
||||
warn_texts = []
|
||||
wants_ack = self.cfg_sync_ack or self.cfg_async_ack
|
||||
if wants_ack and not self.cfg_tx_info:
|
||||
warn_texts.append('ACK request without TX info')
|
||||
if wants_ack and self.frame_is_broadcast:
|
||||
warn_texts.append('ACK request for broadcast')
|
||||
if self.cfg_sync_ack and self.cfg_async_ack:
|
||||
warn_texts.append('sync and async ACK request')
|
||||
if self.cfg_len16 and not self.cfg_crc32:
|
||||
warn_texts.append('extended length needs CRC32')
|
||||
if warn_texts:
|
||||
warn_texts = ', '.join(warn_texts)
|
||||
self.putg(self.ann_ss, self.ann_es, ANN_WARN, [warn_texts])
|
||||
|
||||
# Have the caller emit the annotation for configuration data.
|
||||
return texts
|
||||
|
||||
def handle_field_pkt_len(self, b):
|
||||
'''Process packet length field of a PJON frame.'''
|
||||
|
||||
# Caller provides a list of values. We want a single scalar.
|
||||
b = b[0]
|
||||
|
||||
# The wire communicates the total packet length. Some of it is
|
||||
# overhead (non-payload data), while its volume is variable in
|
||||
# size (depends on the header configuration).
|
||||
#
|
||||
# Derive the payload size from previously observed flags. Update
|
||||
# the previously registered field description (the second last
|
||||
# item in the list, before the end CRC).
|
||||
|
||||
pkt_len = b
|
||||
pl_len = b - self.cfg_overhead
|
||||
warn_texts = []
|
||||
if pkt_len not in range(self.cfg_overhead, 65536):
|
||||
warn_texts.append('suspicious packet length')
|
||||
if pkt_len > 15 and not self.cfg_crc32:
|
||||
warn_texts.append('length above 15 needs CRC32')
|
||||
if pl_len < 1:
|
||||
warn_texts.append('suspicious payload length')
|
||||
pl_len = 0
|
||||
if warn_texts:
|
||||
warn_texts = ', '.join(warn_texts)
|
||||
self.putg(self.ann_ss, self.ann_es, ANN_WARN, [warn_texts])
|
||||
pl_fmt = '>{:d}B'.format(pl_len)
|
||||
|
||||
desc = self.handle_field_get_desc(-2)
|
||||
desc['format'] = pl_fmt
|
||||
desc['width'] = struct.calcsize(pl_fmt)
|
||||
|
||||
# Have the caller emit the annotation for the packet length.
|
||||
# Provide information of different detail level for zooming.
|
||||
texts = [
|
||||
'LENGTH {:d} (PAYLOAD {:d})'.format(pkt_len, pl_len),
|
||||
'LEN {:d} (PL {:d})'.format(pkt_len, pl_len),
|
||||
'{:d} ({:d})'.format(pkt_len, pl_len),
|
||||
'{:d}'.format(pkt_len),
|
||||
]
|
||||
return texts
|
||||
|
||||
def handle_field_common_crc(self, have, is_meta):
|
||||
'''Process a CRC field of a PJON frame.'''
|
||||
|
||||
# CRC algorithm and width are configurable, and can differ
|
||||
# across meta and end checksums in a frame's fields.
|
||||
caption = 'META' if is_meta else 'END'
|
||||
crc_len = 8 if is_meta else 32 if self.cfg_crc32 else 8
|
||||
crc_bytes = crc_len // 8
|
||||
crc_fmt = '{:08x}' if crc_len == 32 else '{:02x}'
|
||||
have_text = crc_fmt.format(have)
|
||||
|
||||
# Check received against expected checksum. Emit warnings.
|
||||
warn_texts = []
|
||||
data = self.frame_bytes[:-crc_bytes]
|
||||
want = calc_crc32(data) if crc_len == 32 else calc_crc8(data)
|
||||
if want != have:
|
||||
want_text = crc_fmt.format(want)
|
||||
warn_texts.append('CRC mismatch - want {} have {}'.format(want_text, have_text))
|
||||
if warn_texts:
|
||||
warn_texts = ', '.join(warn_texts)
|
||||
self.putg(self.ann_ss, self.ann_es, ANN_WARN, [warn_texts])
|
||||
|
||||
# Provide text representation for frame field, caller emits
|
||||
# the annotation.
|
||||
texts = [
|
||||
'{}_CRC {}'.format(caption, have_text),
|
||||
'CRC {}'.format(have_text),
|
||||
have_text,
|
||||
]
|
||||
return texts
|
||||
|
||||
def handle_field_meta_crc(self, b):
|
||||
'''Process initial CRC (meta) field of a PJON frame.'''
|
||||
# Caller provides a list of values. We want a single scalar.
|
||||
b = b[0]
|
||||
return self.handle_field_common_crc(b, True)
|
||||
|
||||
def handle_field_end_crc(self, b):
|
||||
'''Process end CRC (total frame) field of a PJON frame.'''
|
||||
# Caller provides a list of values. We want a single scalar.
|
||||
b = b[0]
|
||||
return self.handle_field_common_crc(b, False)
|
||||
|
||||
def handle_field_common_bus(self, b):
|
||||
'''Common handling of bus ID details. Used for RX and TX.'''
|
||||
bus_id = b[:4]
|
||||
bus_num = struct.unpack('>L', bytearray(bus_id))
|
||||
bus_txt = '.'.join(['{:d}'.format(b) for b in bus_id])
|
||||
return bus_num, bus_txt
|
||||
|
||||
def handle_field_rx_bus(self, b):
|
||||
'''Process receiver bus ID field of a PJON frame.'''
|
||||
|
||||
# When we get here, there always should be an RX ID already.
|
||||
bus_num, bus_txt = self.handle_field_common_bus(b[:4])
|
||||
rx_txt = "{} {}".format(bus_txt, self.frame_rx_id[-1])
|
||||
self.frame_rx_id = (bus_num, self.frame_rx_id[0], rx_txt)
|
||||
|
||||
# Provide text representation for frame field, caller emits
|
||||
# the annotation.
|
||||
texts = [
|
||||
'RX_BUS {}'.format(bus_txt),
|
||||
bus_txt,
|
||||
]
|
||||
return texts
|
||||
|
||||
def handle_field_tx_bus(self, b):
|
||||
'''Process transmitter bus ID field of a PJON frame.'''
|
||||
|
||||
# The TX ID field is optional, as is the use of bus ID fields.
|
||||
# In the TX info case the TX bus ID is seen before the TX ID.
|
||||
bus_num, bus_txt = self.handle_field_common_bus(b[:4])
|
||||
self.frame_tx_id = (bus_num, None, bus_txt)
|
||||
|
||||
# Provide text representation for frame field, caller emits
|
||||
# the annotation.
|
||||
texts = [
|
||||
'TX_BUS {}'.format(bus_txt),
|
||||
bus_txt,
|
||||
]
|
||||
return texts
|
||||
|
||||
def handle_field_tx_id(self, b):
|
||||
'''Process transmitter ID field of a PJON frame.'''
|
||||
|
||||
b = b[0]
|
||||
|
||||
id_txt = "{:d}".format(b)
|
||||
if self.frame_tx_id is None:
|
||||
self.frame_tx_id = (b, id_txt)
|
||||
else:
|
||||
tx_txt = "{} {}".format(self.frame_tx_id[-1], id_txt)
|
||||
self.frame_tx_id = (self.frame_tx_id[0], b, tx_txt)
|
||||
|
||||
# Provide text representation for frame field, caller emits
|
||||
# the annotation.
|
||||
texts = [
|
||||
'TX_ID {}'.format(id_txt),
|
||||
id_txt,
|
||||
]
|
||||
return texts
|
||||
|
||||
def handle_field_payload(self, b):
|
||||
'''Process payload data field of a PJON frame.'''
|
||||
|
||||
text = ' '.join(['{:02x}'.format(v) for v in b])
|
||||
self.frame_payload = b[:]
|
||||
self.frame_payload_text = text
|
||||
|
||||
texts = [
|
||||
'PAYLOAD {}'.format(text),
|
||||
text,
|
||||
]
|
||||
return texts
|
||||
|
||||
def handle_field_sync_resp(self, b):
|
||||
'''Process synchronous response for a PJON frame.'''
|
||||
|
||||
self.frame_has_ack = b
|
||||
|
||||
texts = [
|
||||
'ACK {:02x}'.format(b),
|
||||
'{:02x}'.format(b),
|
||||
]
|
||||
return texts
|
||||
|
||||
def decode(self, ss, es, data):
|
||||
ptype, pdata = data
|
||||
|
||||
# Start frame bytes accumulation when FRAME_INIT is seen. Flush
|
||||
# previously accumulated frame bytes when a new frame starts.
|
||||
if ptype == 'FRAME_INIT':
|
||||
self.frame_flush()
|
||||
self.reset_frame()
|
||||
self.frame_bytes = []
|
||||
self.handle_field_seed_desc()
|
||||
self.frame_ss = ss
|
||||
self.frame_es = es
|
||||
return
|
||||
|
||||
# Use IDLE as another (earlier) trigger to flush frames. Also
|
||||
# trigger flushes on FRAME-DATA which mean that the link layer
|
||||
# inspection has seen the end of a protocol frame.
|
||||
#
|
||||
# TODO Improve usability? Emit warnings for PJON frames where
|
||||
# FRAME_DATA was seen but FRAME_INIT wasn't? So that users can
|
||||
# become aware of broken frames.
|
||||
if ptype in ('IDLE', 'FRAME_DATA'):
|
||||
self.frame_flush()
|
||||
self.reset_frame()
|
||||
return
|
||||
|
||||
# Switch from data bytes to response bytes when WAIT is seen.
|
||||
if ptype == 'SYNC_RESP_WAIT':
|
||||
self.ack_bytes = []
|
||||
self.ann_ss, self.ann_es = None, None
|
||||
return
|
||||
|
||||
# Accumulate data bytes as they arrive. Put them in the bucket
|
||||
# which corresponds to its most recently seen leader.
|
||||
if ptype == 'DATA_BYTE':
|
||||
b = pdata
|
||||
self.frame_es = es
|
||||
|
||||
# Are we collecting response bytes (ACK)?
|
||||
if self.ack_bytes is not None:
|
||||
if not self.ann_ss:
|
||||
self.ann_ss = ss
|
||||
self.ack_bytes.append(b)
|
||||
self.ann_es = es
|
||||
text = self.handle_field_sync_resp(b)
|
||||
if text:
|
||||
self.putg(self.ann_ss, self.ann_es, ANN_SYN_RSP, text)
|
||||
self.ann_ss, self.ann_es = None, None
|
||||
return
|
||||
|
||||
# Are we collecting frame content?
|
||||
if self.frame_bytes is not None:
|
||||
if not self.ann_ss:
|
||||
self.ann_ss = ss
|
||||
self.frame_bytes.append(b)
|
||||
self.ann_es = es
|
||||
|
||||
# Has the field value become available yet?
|
||||
desc = self.handle_field_get_desc()
|
||||
if not desc:
|
||||
return
|
||||
width = desc.get('width', None)
|
||||
if not width:
|
||||
return
|
||||
self.field_desc_got += 1
|
||||
if self.field_desc_got != width:
|
||||
return
|
||||
|
||||
# Grab most recent received field as a byte array. Get
|
||||
# the values that it contains.
|
||||
fmt = desc.get('format', '>B')
|
||||
raw = bytearray(self.frame_bytes[-width:])
|
||||
values = struct.unpack(fmt, raw)
|
||||
|
||||
# Process the value, and get its presentation. Can be
|
||||
# mere formatting, or serious execution of logic.
|
||||
hdl = desc.get('handler', '{!r}')
|
||||
if isinstance(hdl, str):
|
||||
text = [hdl.format(*values)]
|
||||
elif isinstance(hdl, (list, tuple)):
|
||||
text = [f.format(*values) for f in hdl]
|
||||
elif hdl:
|
||||
text = hdl(values)
|
||||
cls = desc.get('anncls', ANN_ANON_DATA)
|
||||
|
||||
# Emit annotation unless the handler routine already did.
|
||||
if cls is not None and text:
|
||||
self.putg(self.ann_ss, self.ann_es, cls, text)
|
||||
self.ann_ss, self.ann_es = None, None
|
||||
|
||||
# Advance scan position for to-get-received field.
|
||||
self.field_desc_idx += 1
|
||||
self.field_desc_got = 0
|
||||
return
|
||||
|
||||
# Unknown phase, not collecting. Not synced yet to the input?
|
||||
return
|
||||
|
||||
# Unknown or unhandled kind of link layer output.
|
||||
return
|
||||
24
libsigrokdecode4DSL/decoders/sae_j1850_vpw/__init__.py
Normal file
24
libsigrokdecode4DSL/decoders/sae_j1850_vpw/__init__.py
Normal file
@@ -0,0 +1,24 @@
|
||||
##
|
||||
## This file is part of the libsigrokdecode project.
|
||||
##
|
||||
## Copyright (C) 2016 Anthony Symons <antus@pcmhacking.net>
|
||||
##
|
||||
## 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
|
||||
## the Free Software Foundation; either version 2 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
'''
|
||||
SAE J1850 Variable Pulse Width decoder. Decode GM VPW 1X and 4X Vehicle Bus.
|
||||
'''
|
||||
|
||||
from .pd import Decoder
|
||||
165
libsigrokdecode4DSL/decoders/sae_j1850_vpw/pd.py
Normal file
165
libsigrokdecode4DSL/decoders/sae_j1850_vpw/pd.py
Normal file
@@ -0,0 +1,165 @@
|
||||
##
|
||||
## This file is part of the libsigrokdecode project.
|
||||
##
|
||||
## Copyright (C) 2016 Anthony Symons <antus@pcmhacking.net>
|
||||
##
|
||||
## 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
|
||||
## the Free Software Foundation; either version 2 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
import sigrokdecode as srd
|
||||
|
||||
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)
|
||||
|
||||
class Decoder(srd.Decoder):
|
||||
api_version = 3
|
||||
id = 'sae_j1850_vpw'
|
||||
name = 'SAE J1850 VPW'
|
||||
longname = 'SAE J1850 VPW.'
|
||||
desc = 'SAE J1850 Variable Pulse Width 1x and 4x.'
|
||||
license = 'gplv2+'
|
||||
inputs = ['logic']
|
||||
outputs = []
|
||||
tags = ['Automotive']
|
||||
channels = (
|
||||
{'id': 'data', 'name': 'Data', 'desc': 'Data line'},
|
||||
)
|
||||
annotations = (
|
||||
('raw', 'Raw'),
|
||||
('sof', 'SOF'),
|
||||
('ifs', 'EOF/IFS'),
|
||||
('data', 'Data'),
|
||||
('packet', 'Packet'),
|
||||
)
|
||||
annotation_rows = (
|
||||
('raws', 'Raws', (Ann.ANN_RAW, Ann.ANN_SOF, Ann.ANN_IFS,)),
|
||||
('bytes', 'Bytes', (Ann.ANN_DATA,)),
|
||||
('packets', 'Packets', (Ann.ANN_PACKET,)),
|
||||
)
|
||||
|
||||
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)
|
||||
|
||||
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 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):
|
||||
if not self.samplerate:
|
||||
raise SamplerateError('Cannot decode without samplerate.')
|
||||
|
||||
self.wait({0: 'e'})
|
||||
es = self.samplenum
|
||||
while True:
|
||||
ss = es
|
||||
pin, = self.wait({0: 'e'})
|
||||
es = self.samplenum
|
||||
|
||||
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)
|
||||
28
libsigrokdecode4DSL/decoders/sdq/__init__.py
Normal file
28
libsigrokdecode4DSL/decoders/sdq/__init__.py
Normal file
@@ -0,0 +1,28 @@
|
||||
##
|
||||
## This file is part of the libsigrokdecode project.
|
||||
##
|
||||
## Copyright (C) 2019-2020 Philip Åkesson <philip.akesson@gmail.com>
|
||||
##
|
||||
## 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
|
||||
## the Free Software Foundation; either version 2 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
'''
|
||||
The SDQ protocol was developed by Texas Instruments, and is used in
|
||||
devices like battery pack authentication. Apple uses SDQ in MagSafe
|
||||
and Lightning connectors, as well as some batteries.
|
||||
|
||||
See https://www.ti.com/lit/ds/symlink/bq26100.pdf for details.
|
||||
'''
|
||||
|
||||
from .pd import Decoder
|
||||
131
libsigrokdecode4DSL/decoders/sdq/pd.py
Normal file
131
libsigrokdecode4DSL/decoders/sdq/pd.py
Normal file
@@ -0,0 +1,131 @@
|
||||
##
|
||||
## This file is part of the libsigrokdecode project.
|
||||
##
|
||||
## Copyright (C) 2019-2020 Philip Åkesson <philip.akesson@gmail.com>
|
||||
##
|
||||
## 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
|
||||
## the Free Software Foundation; either version 2 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
from common.srdhelper import bitpack
|
||||
import sigrokdecode as srd
|
||||
|
||||
class SamplerateError(Exception):
|
||||
pass
|
||||
|
||||
class Pin:
|
||||
SDQ, = range(1)
|
||||
|
||||
class Ann:
|
||||
BIT, BYTE, BREAK, = range(3)
|
||||
|
||||
class Decoder(srd.Decoder):
|
||||
api_version = 3
|
||||
id = 'sdq'
|
||||
name = 'SDQ'
|
||||
longname = 'Texas Instruments SDQ'
|
||||
desc = 'Texas Instruments SDQ. The SDQ protocol is also used by Apple.'
|
||||
license = 'gplv2+'
|
||||
inputs = ['logic']
|
||||
outputs = []
|
||||
tags = ['Embedded/industrial']
|
||||
channels = (
|
||||
{'id': 'sdq', 'name': 'SDQ', 'desc': 'Single wire SDQ data line.'},
|
||||
)
|
||||
options = (
|
||||
{'id': 'bitrate', 'desc': 'Bit rate', 'default': 98425},
|
||||
)
|
||||
annotations = (
|
||||
('bit', 'Bit'),
|
||||
('byte', 'Byte'),
|
||||
('break', 'Break'),
|
||||
)
|
||||
annotation_rows = (
|
||||
('bits', 'Bits', (Ann.BIT,)),
|
||||
('bytes', 'Bytes', (Ann.BYTE,)),
|
||||
('breaks', 'Breaks', (Ann.BREAK,)),
|
||||
)
|
||||
|
||||
def puts(self, data):
|
||||
self.put(self.startsample, self.samplenum, self.out_ann, data)
|
||||
|
||||
def putetu(self, data):
|
||||
self.put(self.startsample, self.startsample + int(self.bit_width), self.out_ann, data)
|
||||
|
||||
def putbetu(self, data):
|
||||
self.put(self.bytepos, self.startsample + int(self.bit_width), self.out_ann, data)
|
||||
|
||||
def __init__(self):
|
||||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
self.samplerate = None
|
||||
self.startsample = 0
|
||||
self.bits = []
|
||||
self.bytepos = 0
|
||||
|
||||
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 handle_bit(self, bit):
|
||||
self.bits.append(bit)
|
||||
self.putetu([Ann.BIT, [
|
||||
'Bit: {:d}'.format(bit),
|
||||
'{:d}'.format(bit),
|
||||
]])
|
||||
|
||||
if len(self.bits) == 8:
|
||||
byte = bitpack(self.bits)
|
||||
self.putbetu([Ann.BYTE, [
|
||||
'Byte: 0x{:02x}'.format(byte),
|
||||
'0x{:02x}'.format(byte),
|
||||
]])
|
||||
self.bits = []
|
||||
self.bytepos = 0
|
||||
|
||||
def handle_break(self):
|
||||
self.puts([Ann.BREAK, ['Break', 'BR']])
|
||||
self.bits = []
|
||||
self.startsample = self.samplenum
|
||||
self.bytepos = 0
|
||||
|
||||
def decode(self):
|
||||
if not self.samplerate:
|
||||
raise SamplerateError('Cannot decode without samplerate.')
|
||||
self.bit_width = float(self.samplerate) / float(self.options['bitrate'])
|
||||
self.half_bit_width = self.bit_width / 2.0
|
||||
# BREAK if the line is low for longer than this.
|
||||
break_threshold = self.bit_width * 1.2
|
||||
|
||||
# Wait until the line is high before inspecting input data.
|
||||
sdq, = self.wait({Pin.SDQ: 'h'})
|
||||
while True:
|
||||
# Get the length of a low pulse (falling to rising edge).
|
||||
sdq, = self.wait({Pin.SDQ: 'f'})
|
||||
self.startsample = self.samplenum
|
||||
if self.bytepos == 0:
|
||||
self.bytepos = self.samplenum
|
||||
sdq, = self.wait({Pin.SDQ: 'r'})
|
||||
|
||||
# Check for 0 or 1 data bits, or the BREAK symbol.
|
||||
delta = self.samplenum - self.startsample
|
||||
if delta > break_threshold:
|
||||
self.handle_break()
|
||||
elif delta > self.half_bit_width:
|
||||
self.handle_bit(0)
|
||||
else:
|
||||
self.handle_bit(1)
|
||||
25
libsigrokdecode4DSL/decoders/signature/__init__.py
Normal file
25
libsigrokdecode4DSL/decoders/signature/__init__.py
Normal file
@@ -0,0 +1,25 @@
|
||||
##
|
||||
## This file is part of the libsigrokdecode project.
|
||||
##
|
||||
## Copyright (C) 2019 Shirow Miura <shirowmiura@gmail.com>
|
||||
##
|
||||
## 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
|
||||
## the Free Software Foundation; either version 2 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
'''
|
||||
Signature analysis function for troubleshooting logic circuits.
|
||||
This generates the same signature as Hewlett-Packard 5004A.
|
||||
'''
|
||||
|
||||
from .pd import Decoder
|
||||
142
libsigrokdecode4DSL/decoders/signature/pd.py
Normal file
142
libsigrokdecode4DSL/decoders/signature/pd.py
Normal file
@@ -0,0 +1,142 @@
|
||||
##
|
||||
## This file is part of the libsigrokdecode project.
|
||||
##
|
||||
## Copyright (C) 2019 Shirow Miura <shirowmiura@gmail.com>
|
||||
##
|
||||
## 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
|
||||
## the Free Software Foundation; either version 2 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
import sigrokdecode as srd
|
||||
|
||||
symbol_map = {
|
||||
0b0000: '0',
|
||||
0b1000: '1',
|
||||
0b0100: '2',
|
||||
0b1100: '3',
|
||||
0b0010: '4',
|
||||
0b1010: '5',
|
||||
0b0110: '6',
|
||||
0b1110: '7',
|
||||
0b0001: '8',
|
||||
0b1001: '9',
|
||||
0b0101: 'A',
|
||||
0b1101: 'C',
|
||||
0b0011: 'F',
|
||||
0b1011: 'H',
|
||||
0b0111: 'P',
|
||||
0b1111: 'U',
|
||||
}
|
||||
|
||||
START, STOP, CLOCK, DATA = range(4)
|
||||
|
||||
class Decoder(srd.Decoder):
|
||||
api_version = 3
|
||||
id = 'signature'
|
||||
name = 'Signature'
|
||||
longname = 'Signature analysis'
|
||||
desc = 'Annotate signature of logic patterns.'
|
||||
license = 'gplv2+'
|
||||
inputs = ['logic']
|
||||
outputs = []
|
||||
tags = ['Debug/trace', 'Util', 'Encoding']
|
||||
channels = (
|
||||
{'id': 'start', 'name': 'START', 'desc': 'START channel'},
|
||||
{'id': 'stop', 'name': 'STOP', 'desc': 'STOP channel'},
|
||||
{'id': 'clk', 'name': 'CLOCK', 'desc': 'CLOCK channel'},
|
||||
{'id': 'data', 'name': 'DATA', 'desc': 'DATA channel'},
|
||||
)
|
||||
options = (
|
||||
{'id': 'start_edge', 'desc': 'START edge polarity',
|
||||
'default': 'rising', 'values': ('rising', 'falling')},
|
||||
{'id': 'stop_edge', 'desc': 'STOP edge polarity',
|
||||
'default': 'rising', 'values': ('rising', 'falling')},
|
||||
{'id': 'clk_edge', 'desc': 'CLOCK edge polarity',
|
||||
'default': 'falling', 'values': ('rising', 'falling')},
|
||||
{'id': 'annbits', 'desc': 'Enable bit level annotations',
|
||||
'default': 'no', 'values': ('yes', 'no')},
|
||||
)
|
||||
annotations = (
|
||||
('bit0', 'Bit0'),
|
||||
('bit1', 'Bit1'),
|
||||
('start', 'START'),
|
||||
('stop', 'STOP'),
|
||||
('signature', 'Signature')
|
||||
)
|
||||
annotation_rows = (
|
||||
('bits', 'Bits', (0, 1, 2, 3)),
|
||||
('signatures', 'Signatures', (4,))
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
pass
|
||||
|
||||
def start(self):
|
||||
self.out_ann = self.register(srd.OUTPUT_ANN)
|
||||
|
||||
def putsig(self, ss, es, signature):
|
||||
s = ''.join([symbol_map[(signature >> 0) & 0x0f],
|
||||
symbol_map[(signature >> 4) & 0x0f],
|
||||
symbol_map[(signature >> 8) & 0x0f],
|
||||
symbol_map[(signature >> 12) & 0x0f]])
|
||||
self.put(ss, es, self.out_ann, [4, [s]])
|
||||
|
||||
def putb(self, ss, ann):
|
||||
self.put(ss, self.samplenum, self.out_ann, ann)
|
||||
|
||||
def decode(self):
|
||||
opt = self.options
|
||||
start_edge_mode_rising = opt['start_edge'] == 'rising'
|
||||
stop_edge_mode_rising = opt['stop_edge'] == 'rising'
|
||||
annbits = opt['annbits'] == 'yes'
|
||||
gate_is_open = False
|
||||
sample_start = None
|
||||
started = False
|
||||
last_samplenum = 0
|
||||
prev_start = 0 if start_edge_mode_rising else 1
|
||||
prev_stop = 0 if stop_edge_mode_rising else 1
|
||||
shiftreg = 0
|
||||
|
||||
while True:
|
||||
start, stop, _, data = self.wait({CLOCK: opt['clk_edge']})
|
||||
if start != prev_start and not gate_is_open:
|
||||
gate_is_open = (start == 1) if start_edge_mode_rising else (start == 0)
|
||||
if gate_is_open:
|
||||
# Start sampling.
|
||||
sample_start = self.samplenum
|
||||
started = True
|
||||
elif stop != prev_stop and gate_is_open:
|
||||
gate_is_open = not ((stop == 1) if stop_edge_mode_rising else (stop == 0))
|
||||
if not gate_is_open:
|
||||
# Stop sampling.
|
||||
if annbits:
|
||||
self.putb(last_samplenum, [3, ['STOP', 'STP', 'P']])
|
||||
self.putsig(sample_start, self.samplenum, shiftreg)
|
||||
shiftreg = 0
|
||||
sample_start = None
|
||||
if gate_is_open:
|
||||
if annbits:
|
||||
if started:
|
||||
s = '<{}>'.format(data)
|
||||
self.putb(last_samplenum, [2, ['START' + s, 'STR' + s, 'S' + s]])
|
||||
started = False
|
||||
else:
|
||||
self.putb(last_samplenum, [data, [str(data)]])
|
||||
incoming = (bin(shiftreg & 0x0291).count('1') + data) & 1
|
||||
shiftreg = (incoming << 15) | (shiftreg >> 1)
|
||||
prev_start = start
|
||||
prev_stop = stop
|
||||
last_samplenum = self.samplenum
|
||||
30
libsigrokdecode4DSL/decoders/sipi/__init__.py
Normal file
30
libsigrokdecode4DSL/decoders/sipi/__init__.py
Normal file
@@ -0,0 +1,30 @@
|
||||
##
|
||||
## This file is part of the libsigrokdecode project.
|
||||
##
|
||||
## Copyright (C) 2020 Soeren Apel <soeren@apelpie.net>
|
||||
##
|
||||
## 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
|
||||
## the Free Software Foundation; either version 2 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
'''
|
||||
The Serial Inter-Processor Interface (SIPI) is a higher-level protocol that runs
|
||||
over the LFAST physical interface. Together, they form the NXP Zipwire interface.
|
||||
|
||||
The SIPI interface is also provided by Infineon as HSST, using HSCT for transport.
|
||||
|
||||
For details see https://www.nxp.com/docs/en/application-note/AN5134.pdf and
|
||||
https://hitex.co.uk/fileadmin/uk-files/downloads/ShieldBuddy/tc27xD_um_v2.2.pdf
|
||||
'''
|
||||
|
||||
from .pd import Decoder
|
||||
181
libsigrokdecode4DSL/decoders/sipi/pd.py
Normal file
181
libsigrokdecode4DSL/decoders/sipi/pd.py
Normal file
@@ -0,0 +1,181 @@
|
||||
##
|
||||
## This file is part of the libsigrokdecode project.
|
||||
##
|
||||
## Copyright (C) 2020 Soeren Apel <soeren@apelpie.net>
|
||||
##
|
||||
## 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
|
||||
## the Free Software Foundation; either version 2 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
import sigrokdecode as srd
|
||||
from binascii import crc_hqx
|
||||
|
||||
# See tc27xD_um_v2.2.pdf, Table 20-2
|
||||
# (name, addr byte count, data byte count)
|
||||
command_codes = {
|
||||
0b00000: ('Read byte', 4, 0),
|
||||
0b00001: ('Read 2 byte', 4, 0),
|
||||
0b00010: ('Read 4 byte', 4, 0),
|
||||
# Reserved
|
||||
0b00100: ('Write byte with ACK', 4, 4),
|
||||
0b00101: ('Write 2 byte with ACK', 4, 4),
|
||||
0b00110: ('Write 4 byte with ACK', 4, 4),
|
||||
# Reserved
|
||||
0b01000: ('ACK', 0, 0),
|
||||
0b01001: ('NACK (Target Error)', 0, 0),
|
||||
0b01010: ('Read Answer with ACK', 4, 4),
|
||||
# Reserved
|
||||
0b01100: ('Trigger with ACK', 0, 0),
|
||||
# Reserved
|
||||
# Reserved
|
||||
# Reserved
|
||||
# Reserved
|
||||
# Reserved
|
||||
0b10010: ('Read 4-byte JTAG ID', 0, 0),
|
||||
# Reserved
|
||||
# Reserved
|
||||
# Reserved
|
||||
# Reserved
|
||||
0b10111: ('Stream 32 byte with ACK', 0, 32)
|
||||
# Rest is reserved
|
||||
}
|
||||
|
||||
|
||||
ann_header_tag, ann_header_cmd, ann_header_ch, ann_address, ann_data, \
|
||||
ann_crc, ann_warning = range(7)
|
||||
|
||||
class Decoder(srd.Decoder):
|
||||
api_version = 3
|
||||
id = 'sipi'
|
||||
name = 'SIPI (Zipwire)'
|
||||
longname = 'NXP SIPI interface'
|
||||
desc = 'Serial Inter-Processor Interface (SIPI) aka Zipwire, aka HSSL'
|
||||
license = 'gplv2+'
|
||||
inputs = ['lfast']
|
||||
outputs = []
|
||||
tags = ['Embedded/industrial']
|
||||
annotations = (
|
||||
('header_tag', 'Transaction Tag'),
|
||||
('header_cmd', 'Command Code'),
|
||||
('header_ch', 'Channel'),
|
||||
('address', 'Address'),
|
||||
('data', 'Data'),
|
||||
('crc', 'CRC'),
|
||||
('warning', 'Warning'),
|
||||
)
|
||||
annotation_rows = (
|
||||
('fields', 'Fields', (ann_header_tag, ann_header_cmd,
|
||||
ann_header_ch, ann_address, ann_data, ann_crc,)),
|
||||
('warnings', 'Warnings', (ann_warning,)),
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
self.byte_len = 0
|
||||
self.frame_len = 0
|
||||
|
||||
def start(self):
|
||||
self.out_ann = self.register(srd.OUTPUT_ANN)
|
||||
self.out_binary = self.register(srd.OUTPUT_BINARY)
|
||||
|
||||
def put_ann(self, ss, es, ann_class, value):
|
||||
self.put(int(ss), int(es), self.out_ann, [ann_class, value])
|
||||
|
||||
def put_header(self, ss_header, es_header, value):
|
||||
ss = ss_header
|
||||
es = ss + 3 * self.bit_len
|
||||
tag = (value & 0xE000) >> 13
|
||||
self.put_ann(ss, es, ann_header_tag, ['{:02X}'.format(tag)])
|
||||
|
||||
ss = es
|
||||
es = ss + 5 * self.bit_len
|
||||
cmd_id = (value & 0x1F00) >> 8
|
||||
cmd_name, self.addr_len, self.data_len = \
|
||||
command_codes.get(cmd_id, ('Reserved ({:02X})'.format(cmd_id), 0, 0))
|
||||
self.frame_len = 2 + 2 + self.addr_len + self.data_len # +Header +CRC
|
||||
self.put_ann(ss, es, ann_header_cmd, [cmd_name])
|
||||
|
||||
# Bits 4..7 are reserved and should be 0, warn if they're not
|
||||
ss = es
|
||||
es = ss + 4 * self.bit_len
|
||||
reserved_bits = (value & 0x00F0) >> 4
|
||||
if reserved_bits > 0:
|
||||
self.put_ann(ss, es, ann_warning, ['Reserved bits #4..7 should be 0'])
|
||||
|
||||
ss = es
|
||||
es = ss + 3 * self.bit_len
|
||||
ch = (value & 0x000E) >> 1 # See tc27xD_um_v2.2.pdf, Table 20-1
|
||||
self.put_ann(ss, es, ann_header_ch, [str(ch)])
|
||||
|
||||
# Bit 0 is reserved and should be 0, warn if it's not
|
||||
if (value & 0x0001) == 0x0001:
|
||||
ss = es
|
||||
es = ss + self.bit_len
|
||||
self.put_ann(ss, es, ann_warning, ['Reserved bit #0 should be 0'])
|
||||
|
||||
def put_payload(self, data):
|
||||
byte_idx = 0
|
||||
if self.addr_len > 0:
|
||||
for value_tuple in data[:self.addr_len]:
|
||||
ss, es, value = value_tuple
|
||||
self.put_ann(ss, es, ann_address, ['{:02X}'.format(value)])
|
||||
byte_idx = self.addr_len
|
||||
|
||||
if self.data_len > 0:
|
||||
for value_tuple in data[byte_idx:]:
|
||||
ss, es, value = value_tuple
|
||||
self.put_ann(ss, es, ann_data, ['{:02X}'.format(value)])
|
||||
|
||||
def put_crc(self, ss, es, crc_value, crc_payload_data):
|
||||
crc_payload = []
|
||||
for value_tuple in crc_payload_data:
|
||||
crc_payload.append(value_tuple[2])
|
||||
|
||||
calculated_crc = crc_hqx(bytes(crc_payload), 0xFFFF)
|
||||
|
||||
if calculated_crc == crc_value:
|
||||
self.put_ann(ss, es, ann_crc, ['CRC OK'])
|
||||
else:
|
||||
self.put_ann(ss, es, ann_crc, ['Have {:02X} but calculated {:02X}'.format(crc_value, calculated_crc)])
|
||||
self.put_ann(ss, es, ann_warning, ['CRC mismatch'])
|
||||
|
||||
def decode(self, ss, es, data):
|
||||
if len(data) == 1:
|
||||
self.put_ann(ss, es, ann_warning, ['Header too short'])
|
||||
return
|
||||
|
||||
# ss and es are now unused, we use them as local variables instead
|
||||
|
||||
self.bit_len = (data[0][1] - data[0][0]) / 8.0
|
||||
|
||||
byte_idx = 0
|
||||
|
||||
ss = data[byte_idx][0]
|
||||
es = data[byte_idx + 1][1]
|
||||
self.put_header(ss, es, (data[byte_idx][2] << 8) + data[byte_idx + 1][2])
|
||||
byte_idx += 2
|
||||
|
||||
payload_len = self.frame_len - 2 - 2 # -Header -CRC
|
||||
if payload_len > 0:
|
||||
self.put_payload(data[byte_idx:-2])
|
||||
byte_idx += payload_len
|
||||
|
||||
ss = data[byte_idx][0]
|
||||
es = data[byte_idx + 1][1]
|
||||
if byte_idx == len(data) - 2:
|
||||
# CRC is calculated over header + payload bytes
|
||||
self.put_crc(ss, es, (data[byte_idx][2] << 8) + data[byte_idx + 1][2], data[0:-2])
|
||||
else:
|
||||
self.put_ann(ss, es, ann_warning, ['CRC incomplete or missing'])
|
||||
26
libsigrokdecode4DSL/decoders/sle44xx/__init__.py
Normal file
26
libsigrokdecode4DSL/decoders/sle44xx/__init__.py
Normal file
@@ -0,0 +1,26 @@
|
||||
##
|
||||
## This file is part of the libsigrokdecode project.
|
||||
##
|
||||
## Copyright (C) 2019 Federico Cerutti <federico@ceres-c.it>
|
||||
##
|
||||
## 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
|
||||
## the Free Software Foundation; either version 2 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
'''
|
||||
SLE 4418/28/32/42 memory cards implement a 2-wire protocol (CLK and I/O)
|
||||
for data communication, along with the RST signal which resets the card's
|
||||
internal state, and can terminate currently executing long memory reads.
|
||||
'''
|
||||
|
||||
from .pd import Decoder
|
||||
541
libsigrokdecode4DSL/decoders/sle44xx/pd.py
Normal file
541
libsigrokdecode4DSL/decoders/sle44xx/pd.py
Normal file
@@ -0,0 +1,541 @@
|
||||
##
|
||||
## This file is part of the libsigrokdecode project.
|
||||
##
|
||||
## Copyright (C) 2019 Federico Cerutti <federico@ceres-c.it>
|
||||
##
|
||||
## 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
|
||||
## the Free Software Foundation; either version 2 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
from common.srdhelper import bitpack_lsb
|
||||
import sigrokdecode as srd
|
||||
|
||||
class Pin:
|
||||
RST, CLK, IO, = range(3)
|
||||
|
||||
class Ann:
|
||||
RESET_SYM, INTR_SYM, START_SYM, STOP_SYM, BIT_SYM, \
|
||||
ATR_BYTE, CMD_BYTE, OUT_BYTE, PROC_BYTE, \
|
||||
ATR_DATA, CMD_DATA, OUT_DATA, PROC_DATA, \
|
||||
= range(13)
|
||||
|
||||
class Bin:
|
||||
BYTES, = range(1)
|
||||
|
||||
class Decoder(srd.Decoder):
|
||||
api_version = 3
|
||||
id = 'sle44xx'
|
||||
name = 'SLE 44xx'
|
||||
longname = 'SLE44xx memory card'
|
||||
desc = 'SLE 4418/28/32/42 memory card serial protocol'
|
||||
license = 'gplv2+'
|
||||
inputs = ['logic']
|
||||
outputs = []
|
||||
tags = ['Memory']
|
||||
channels = (
|
||||
{'id': 'rst', 'name': 'RST', 'desc': 'Reset line'},
|
||||
{'id': 'clk', 'name': 'CLK', 'desc': 'Clock line'},
|
||||
{'id': 'io', 'name': 'I/O', 'desc': 'I/O data line'},
|
||||
)
|
||||
annotations = (
|
||||
('reset_sym', 'Reset Symbol'),
|
||||
('intr_sym', 'Interrupt Symbol'),
|
||||
('start_sym', 'Start Symbol'),
|
||||
('stop_sym', 'Stop Symbol'),
|
||||
('bit_sym', 'Bit Symbol'),
|
||||
('atr_byte', 'ATR Byte'),
|
||||
('cmd_byte', 'Command Byte'),
|
||||
('out_byte', 'Outgoing Byte'),
|
||||
('proc_byte', 'Processing Byte'),
|
||||
('atr_data', 'ATR data'),
|
||||
('cmd_data', 'Command data'),
|
||||
('out_data', 'Outgoing data'),
|
||||
('proc_data', 'Processing data'),
|
||||
)
|
||||
annotation_rows = (
|
||||
('symbols', 'Symbols', (Ann.RESET_SYM, Ann.INTR_SYM,
|
||||
Ann.START_SYM, Ann.STOP_SYM, Ann.BIT_SYM,)),
|
||||
('fields', 'Fields', (Ann.ATR_BYTE,
|
||||
Ann.CMD_BYTE, Ann.OUT_BYTE, Ann.PROC_BYTE,)),
|
||||
('operations', 'Operations', (Ann.ATR_DATA,
|
||||
Ann.CMD_DATA, Ann.OUT_DATA, Ann.PROC_DATA,)),
|
||||
)
|
||||
binary = (
|
||||
('bytes', 'Bytes'),
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
self.samplerate = None
|
||||
self.max_addr = 256
|
||||
self.bits = []
|
||||
self.atr_bytes = []
|
||||
self.cmd_bytes = []
|
||||
self.cmd_proc = None
|
||||
self.out_len = None
|
||||
self.out_bytes = []
|
||||
self.proc_state = None
|
||||
self.state = None
|
||||
|
||||
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)
|
||||
self.out_binary = self.register(srd.OUTPUT_BINARY)
|
||||
|
||||
def putx(self, ss, es, cls, data):
|
||||
self.put(ss, es, self.out_ann, [cls, data,])
|
||||
|
||||
def putb(self, ss, es, cls , data):
|
||||
self.put(ss, es, self.out_binary, [cls, data,])
|
||||
|
||||
def snums_to_usecs(self, snum_count):
|
||||
if not self.samplerate:
|
||||
return None
|
||||
snums_per_usec = self.samplerate / 1e6
|
||||
usecs = snum_count / snums_per_usec
|
||||
return usecs
|
||||
|
||||
def lookup_proto_ann_txt(self, key, variables):
|
||||
ann = {
|
||||
'RESET_SYM': [Ann.RESET_SYM, 'Reset', 'R',],
|
||||
'INTR_SYM': [Ann.INTR_SYM, 'Interrupt', 'Intr', 'I',],
|
||||
'START_SYM': [Ann.START_SYM, 'Start', 'ST', 'S',],
|
||||
'STOP_SYM': [Ann.STOP_SYM, 'Stop', 'SP', 'P',],
|
||||
'BIT_SYM': [Ann.BIT_SYM, '{bit}',],
|
||||
'ATR_BYTE': [Ann.ATR_BYTE,
|
||||
'Answer To Reset: {data:02x}',
|
||||
'ATR: {data:02x}',
|
||||
'{data:02x}',
|
||||
],
|
||||
'CMD_BYTE': [Ann.CMD_BYTE,
|
||||
'Command: {data:02x}',
|
||||
'Cmd: {data:02x}',
|
||||
'{data:02x}',
|
||||
],
|
||||
'OUT_BYTE': [Ann.OUT_BYTE,
|
||||
'Outgoing data: {data:02x}',
|
||||
'Data: {data:02x}',
|
||||
'{data:02x}',
|
||||
],
|
||||
'PROC_BYTE': [Ann.PROC_BYTE,
|
||||
'Internal processing: {data:02x}',
|
||||
'Proc: {data:02x}',
|
||||
'{data:02x}',
|
||||
],
|
||||
'ATR_DATA': [Ann.ATR_DATA,
|
||||
'Answer To Reset: {data}',
|
||||
'ATR: {data}',
|
||||
'{data}',
|
||||
],
|
||||
'CMD_DATA': [Ann.CMD_DATA,
|
||||
'Command: {data}',
|
||||
'Cmd: {data}',
|
||||
'{data}',
|
||||
],
|
||||
'OUT_DATA': [Ann.OUT_DATA,
|
||||
'Outgoing: {data}',
|
||||
'Out: {data}',
|
||||
'{data}',
|
||||
],
|
||||
'PROC_DATA': [Ann.PROC_DATA,
|
||||
'Processing: {data}',
|
||||
'Proc: {data}',
|
||||
'{data}',
|
||||
],
|
||||
}.get(key, None)
|
||||
if ann is None:
|
||||
return None, []
|
||||
cls, texts = ann[0], ann[1:]
|
||||
texts = [t.format(**variables) for t in texts]
|
||||
return cls, texts
|
||||
|
||||
def text_for_accu_bytes(self, accu):
|
||||
if not accu:
|
||||
return None, None, None, None
|
||||
ss, es = accu[0][1], accu[-1][2]
|
||||
data = [a[0] for a in accu]
|
||||
text = " ".join(['{:02x}'.format(a) for a in data])
|
||||
return ss, es, data, text
|
||||
|
||||
def flush_queued(self):
|
||||
'''Flush previously accumulated operations details.'''
|
||||
|
||||
# Can be called when either the completion of an operation got
|
||||
# detected (reliably), or when some kind of reset condition was
|
||||
# met while a potential previously observed operation has not
|
||||
# been postprocessed yet (best effort). Should not harm when the
|
||||
# routine gets invoked while no data was collected yet, or was
|
||||
# flushed already.
|
||||
# BEWARE! Will void internal state. Should really only get called
|
||||
# "between operations", NOT between fields of an operation.
|
||||
|
||||
if self.atr_bytes:
|
||||
key = 'ATR_DATA'
|
||||
ss, es, _, text = self.text_for_accu_bytes(self.atr_bytes)
|
||||
cls, texts = self.lookup_proto_ann_txt(key, {'data': text})
|
||||
self.putx(ss, es, cls, texts)
|
||||
|
||||
if self.cmd_bytes:
|
||||
key = 'CMD_DATA'
|
||||
ss, es, _, text = self.text_for_accu_bytes(self.cmd_bytes)
|
||||
cls, texts = self.lookup_proto_ann_txt(key, {'data': text})
|
||||
self.putx(ss, es, cls, texts)
|
||||
|
||||
if self.out_bytes:
|
||||
key = 'OUT_DATA'
|
||||
ss, es, _, text = self.text_for_accu_bytes(self.out_bytes)
|
||||
cls, texts = self.lookup_proto_ann_txt(key, {'data': text})
|
||||
self.putx(ss, es, cls, texts)
|
||||
|
||||
if self.proc_state:
|
||||
key = 'PROC_DATA'
|
||||
ss = self.proc_state['ss']
|
||||
es = self.proc_state['es']
|
||||
clk = self.proc_state['clk']
|
||||
high = self.proc_state['io1']
|
||||
text = '{clk} clocks, I/O {high}'.format(clk = clk, high = int(high))
|
||||
usecs = self.snums_to_usecs(es - ss)
|
||||
if usecs:
|
||||
msecs = usecs / 1000
|
||||
text = '{msecs:.2f} ms, {text}'.format(msecs = msecs, text = text)
|
||||
cls, texts = self.lookup_proto_ann_txt(key, {'data': text})
|
||||
self.putx(ss, es, cls, texts)
|
||||
|
||||
self.atr_bytes = None
|
||||
self.cmd_bytes = None
|
||||
self.cmd_proc = None
|
||||
self.out_len = None
|
||||
self.out_bytes = None
|
||||
self.proc_state = None
|
||||
self.state = None
|
||||
|
||||
def handle_reset(self, ss, es, has_clk):
|
||||
self.flush_queued()
|
||||
key = '{}_SYM'.format('RESET' if has_clk else 'INTR')
|
||||
cls, texts = self.lookup_proto_ann_txt(key, {})
|
||||
self.putx(ss, es, cls, texts)
|
||||
self.bits = []
|
||||
self.state = 'ATR' if has_clk else None
|
||||
|
||||
def handle_command(self, ss, is_start):
|
||||
if is_start:
|
||||
self.flush_queued()
|
||||
key = '{}_SYM'.format('START' if is_start else 'STOP')
|
||||
cls, texts = self.lookup_proto_ann_txt(key, {})
|
||||
self.putx(ss, ss, cls, texts)
|
||||
self.bits = []
|
||||
self.state = 'CMD' if is_start else 'DATA'
|
||||
|
||||
def command_check(self, ctrl, addr, data):
|
||||
'''Interpret CTRL/ADDR/DATA command entry.'''
|
||||
|
||||
# See the Siemens Datasheet section 2.3 Commands. The abbreviated
|
||||
# text variants are my guesses, terse for readability at coarser
|
||||
# zoom levels.
|
||||
codes_table = {
|
||||
0x30: {
|
||||
'fmt': [
|
||||
'read main memory, addr {addr:02x}',
|
||||
'RD-M @{addr:02x}',
|
||||
],
|
||||
'len': lambda ctrl, addr, data: self.max_addr - addr,
|
||||
},
|
||||
0x31: {
|
||||
'fmt': [
|
||||
'read security memory',
|
||||
'RD-S',
|
||||
],
|
||||
'len': 4,
|
||||
},
|
||||
0x33: {
|
||||
'fmt': [
|
||||
'compare verification data, addr {addr:02x}, data {data:02x}',
|
||||
'CMP-V @{addr:02x} ={data:02x}',
|
||||
],
|
||||
'proc': True,
|
||||
},
|
||||
0x34: {
|
||||
'fmt': [
|
||||
'read protection memory, addr {addr:02x}',
|
||||
'RD-P @{addr:02x}',
|
||||
],
|
||||
'len': 4,
|
||||
},
|
||||
0x38: {
|
||||
'fmt': [
|
||||
'update main memory, addr {addr:02x}, data {data:02x}',
|
||||
'WR-M @{addr:02x} ={data:02x}',
|
||||
],
|
||||
'proc': True,
|
||||
},
|
||||
0x39: {
|
||||
'fmt': [
|
||||
'update security memory, addr {addr:02x}, data {data:02x}',
|
||||
'WR-S @{addr:02x} ={data:02x}',
|
||||
],
|
||||
'proc': True,
|
||||
},
|
||||
0x3c: {
|
||||
'fmt': [
|
||||
'write protection memory, addr {addr:02x}, data {data:02x}',
|
||||
'WR-P @{addr:02x} ={data:02x}',
|
||||
],
|
||||
'proc': True,
|
||||
},
|
||||
}
|
||||
code = codes_table.get(ctrl, {})
|
||||
dflt_fmt = [
|
||||
'unknown, ctrl {ctrl:02x}, addr {addr:02x}, data {data:02x}',
|
||||
'UNK-{ctrl:02x} @{addr:02x}, ={data:02x}',
|
||||
]
|
||||
fmt = code.get('fmt', dflt_fmt)
|
||||
if not isinstance(fmt, (list, tuple,)):
|
||||
fmt = [fmt,]
|
||||
texts = [f.format(ctrl = ctrl, addr = addr, data = data) for f in fmt]
|
||||
length = code.get('len', None)
|
||||
if callable(length):
|
||||
length = length(ctrl, addr, data)
|
||||
is_proc = code.get('proc', False)
|
||||
return texts, length, is_proc
|
||||
|
||||
def processing_start(self, ss, es, io_high):
|
||||
self.proc_state = {
|
||||
'ss': ss or es,
|
||||
'es': es or ss,
|
||||
'clk': 0,
|
||||
'io1': bool(io_high),
|
||||
}
|
||||
|
||||
def processing_update(self, es, clk_inc, io_high):
|
||||
if es is not None and es > self.proc_state['es']:
|
||||
self.proc_state['es'] = es
|
||||
self.proc_state['clk'] += clk_inc
|
||||
if io_high:
|
||||
self.proc_state['io1'] = True
|
||||
|
||||
def handle_data_byte(self, ss, es, data, bits):
|
||||
'''Accumulate CMD or OUT data bytes.'''
|
||||
|
||||
if self.state == 'ATR':
|
||||
if not self.atr_bytes:
|
||||
self.atr_bytes = []
|
||||
self.atr_bytes.append([data, ss, es, bits,])
|
||||
if len(self.atr_bytes) == 4:
|
||||
self.flush_queued()
|
||||
return
|
||||
|
||||
if self.state == 'CMD':
|
||||
if not self.cmd_bytes:
|
||||
self.cmd_bytes = []
|
||||
self.cmd_bytes.append([data, ss, es, bits,])
|
||||
if len(self.cmd_bytes) == 3:
|
||||
ctrl, addr, data = [c[0] for c in self.cmd_bytes]
|
||||
texts, length, proc = self.command_check(ctrl, addr, data)
|
||||
# Immediately emit the annotation to not lose the text,
|
||||
# and to support zoom levels for this specific case.
|
||||
ss, es = self.cmd_bytes[0][1], self.cmd_bytes[-1][2]
|
||||
cls = Ann.CMD_DATA
|
||||
self.putx(ss, es, cls, texts)
|
||||
self.cmd_bytes = []
|
||||
# Prepare to continue either at OUT or PROC after CMD.
|
||||
self.out_len = length
|
||||
self.cmd_proc = bool(proc)
|
||||
self.state = None
|
||||
return
|
||||
|
||||
if self.state == 'OUT':
|
||||
if not self.out_bytes:
|
||||
self.out_bytes = []
|
||||
self.out_bytes.append([data, ss, es, bits,])
|
||||
if self.out_len is not None and len(self.out_bytes) == self.out_len:
|
||||
self.flush_queued()
|
||||
return
|
||||
|
||||
def handle_data_bit(self, ss, es, bit):
|
||||
'''Gather 8 bits of data (or track processing progress).'''
|
||||
|
||||
# Switch late from DATA to either OUT or PROC. We can tell the
|
||||
# type and potentially fixed length at the end of CMD already,
|
||||
# but a START/STOP condition may void this information. So we
|
||||
# do the switch at the first data bit after CMD.
|
||||
# In the OUT case data bytes get accumulated, until either the
|
||||
# expected byte count is reached, or another CMD starts. In the
|
||||
# PROC case a high I/O level terminates execution.
|
||||
if self.state == 'DATA':
|
||||
if self.out_len:
|
||||
self.state = 'OUT'
|
||||
elif self.cmd_proc:
|
||||
self.state = 'PROC'
|
||||
self.processing_start(ss or es, es or ss, bit == 1)
|
||||
else:
|
||||
# Implementor's note: Handle unknown situations like
|
||||
# outgoing data bytes, for the user's convenience. This
|
||||
# will show OUT bytes even if it's just processing CLK
|
||||
# cycles with constant or irrelevant I/O bit patterns.
|
||||
self.state = 'OUT'
|
||||
if self.state == 'PROC':
|
||||
high = bit == 1
|
||||
if ss is not None:
|
||||
self.processing_update(ss, 0, high)
|
||||
if es is not None:
|
||||
self.processing_update(es, 1, high)
|
||||
if high:
|
||||
self.flush_queued()
|
||||
return
|
||||
|
||||
# This routine gets called two times per bit value. Track the
|
||||
# bit's value and ss timestamp when the bit period starts. And
|
||||
# update the es timestamp at the end of the bit's validity.
|
||||
if ss is not None:
|
||||
self.bits.append([bit, ss, es or ss])
|
||||
return
|
||||
if es is None:
|
||||
# Unexpected invocation. Could be a glitch or invalid input
|
||||
# data, or an interaction with RESET/START/STOP conditions.
|
||||
self.bits = []
|
||||
return
|
||||
if not self.bits:
|
||||
return
|
||||
if bit is not None:
|
||||
self.bits[-1][0] = bit
|
||||
# TODO Check for consistent bit level at ss and es when
|
||||
# the information was available? Is bit data sampled at
|
||||
# different clock edges depending whether data is sent
|
||||
# or received?
|
||||
self.bits[-1][2] = es
|
||||
# Emit the bit's annotation. See if a byte was received.
|
||||
bit, ss, es = self.bits[-1]
|
||||
cls, texts = self.lookup_proto_ann_txt('BIT_SYM', {'bit': bit})
|
||||
self.putx(ss, es, cls, texts)
|
||||
if len(self.bits) < 8:
|
||||
return
|
||||
|
||||
# Get the data byte value, and the byte's ss/es. Emit the byte's
|
||||
# annotation and binary output. Pass the byte to upper layers.
|
||||
# TODO Vary annotation classes with the byte's position within
|
||||
# a field? To tell CTRL/ADDR/DATA of a CMD entry apart?
|
||||
bits = self.bits
|
||||
self.bits = []
|
||||
data = bitpack_lsb(bits, 0)
|
||||
ss = bits[0][1]
|
||||
es = bits[-1][2]
|
||||
|
||||
key = '{}_BYTE'.format(self.state)
|
||||
cls, texts = self.lookup_proto_ann_txt(key, {'data': data})
|
||||
if cls:
|
||||
self.putx(ss, es, cls, texts)
|
||||
self.putb(ss, es, Bin.BYTES, bytes([data]))
|
||||
|
||||
self.handle_data_byte(ss, es, data, bits)
|
||||
|
||||
def decode(self):
|
||||
'''Decoder's main data interpretation loop.'''
|
||||
|
||||
# Signal conditions tracked by the protocol decoder:
|
||||
# - Rising and falling RST edges, which span the width of a
|
||||
# high-active RESET pulse. RST has highest priority, no
|
||||
# other activity can take place in this period.
|
||||
# - Rising and falling CLK edges when RST is active. The
|
||||
# CLK pulse when RST is asserted will reset the card's
|
||||
# address counter. RST alone can terminate memory reads.
|
||||
# - Rising and falling CLK edges when RST is inactive. This
|
||||
# determines the period where BIT values are valid.
|
||||
# - I/O edges during high CLK. These are START and STOP
|
||||
# conditions that tell COMMAND and DATA phases apart.
|
||||
# - Rise of I/O during internal processing. This expression
|
||||
# is an unconditional part of the .wait() condition set. It
|
||||
# is assumed that skipping this match in many cases is more
|
||||
# efficient than the permanent re-construction of the .wait()
|
||||
# condition list in every loop iteration, and preferrable to
|
||||
# the maintainance cost of duplicating RST and CLK handling
|
||||
# when checking I/O during internal processing.
|
||||
(
|
||||
COND_RESET_START, COND_RESET_STOP,
|
||||
COND_RSTCLK_START, COND_RSTCLK_STOP,
|
||||
COND_DATA_START, COND_DATA_STOP,
|
||||
COND_CMD_START, COND_CMD_STOP,
|
||||
COND_PROC_IOH,
|
||||
) = range(9)
|
||||
conditions = [
|
||||
{Pin.RST: 'r'},
|
||||
{Pin.RST: 'f'},
|
||||
{Pin.RST: 'h', Pin.CLK: 'r'},
|
||||
{Pin.RST: 'h', Pin.CLK: 'f'},
|
||||
{Pin.RST: 'l', Pin.CLK: 'r'},
|
||||
{Pin.RST: 'l', Pin.CLK: 'f'},
|
||||
{Pin.CLK: 'h', Pin.IO: 'f'},
|
||||
{Pin.CLK: 'h', Pin.IO: 'r'},
|
||||
{Pin.RST: 'l', Pin.IO: 'r'},
|
||||
]
|
||||
|
||||
ss_reset = es_reset = ss_clk = es_clk = None
|
||||
while True:
|
||||
|
||||
is_outgoing = self.state == 'OUT'
|
||||
is_processing = self.state == 'PROC'
|
||||
pins = self.wait(conditions)
|
||||
io = pins[Pin.IO]
|
||||
|
||||
# Handle RESET conditions, including an optional CLK pulse
|
||||
# while RST is asserted.
|
||||
if self.matched[COND_RESET_START]:
|
||||
self.flush_queued()
|
||||
ss_reset = self.samplenum
|
||||
es_reset = ss_clk = es_clk = None
|
||||
continue
|
||||
if self.matched[COND_RESET_STOP]:
|
||||
es_reset = self.samplenum
|
||||
self.handle_reset(ss_reset or 0, es_reset, ss_clk and es_clk)
|
||||
ss_reset = es_reset = ss_clk = es_clk = None
|
||||
continue
|
||||
if self.matched[COND_RSTCLK_START]:
|
||||
ss_clk = self.samplenum
|
||||
es_clk = None
|
||||
continue
|
||||
if self.matched[COND_RSTCLK_STOP]:
|
||||
es_clk = self.samplenum
|
||||
continue
|
||||
|
||||
# Handle data bits' validity boundaries. Also covers the
|
||||
# periodic check for high I/O level and update of details
|
||||
# during internal processing.
|
||||
if self.matched[COND_DATA_START]:
|
||||
self.handle_data_bit(self.samplenum, None, io)
|
||||
continue
|
||||
if self.matched[COND_DATA_STOP]:
|
||||
self.handle_data_bit(None, self.samplenum, None)
|
||||
continue
|
||||
|
||||
# Additional check for idle I/O during internal processing,
|
||||
# independent of CLK edges this time. This assures that the
|
||||
# decoder ends processing intervals as soon as possible, at
|
||||
# the most precise timestamp.
|
||||
if is_processing and self.matched[COND_PROC_IOH]:
|
||||
self.handle_data_bit(self.samplenum, self.samplenum, io)
|
||||
continue
|
||||
|
||||
# The START/STOP conditions are only applicable outside of
|
||||
# "outgoing data" or "internal processing" periods. This is
|
||||
# what the data sheet specifies.
|
||||
if not is_outgoing and not is_processing:
|
||||
if self.matched[COND_CMD_START]:
|
||||
self.handle_command(self.samplenum, True)
|
||||
continue
|
||||
if self.matched[COND_CMD_STOP]:
|
||||
self.handle_command(self.samplenum, False)
|
||||
continue
|
||||
31
libsigrokdecode4DSL/decoders/st25r39xx_spi/__init__.py
Normal file
31
libsigrokdecode4DSL/decoders/st25r39xx_spi/__init__.py
Normal file
@@ -0,0 +1,31 @@
|
||||
##
|
||||
## This file is part of the libsigrokdecode project.
|
||||
##
|
||||
## Copyright (C) 2019-2020 Benjamin Vernoux <bvernoux@gmail.com>
|
||||
##
|
||||
## 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
|
||||
## the Free Software Foundation; either version 2 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
'''
|
||||
This decoder stacks on top of the SPI PD and decodes the st25r39xx High performance NFC universal device and EMVCo reader protocol (SPI mode).
|
||||
|
||||
It has been successfully tested with the st25r3916, other chips of this family may or may not be fully supported but not been verified.
|
||||
|
||||
Please note that the SPI interface uses clock polarity 0 and clock phase 1, which is not the default setting.
|
||||
|
||||
Details:
|
||||
https://www.st.com/resource/en/datasheet/st25r3916.pdf
|
||||
'''
|
||||
|
||||
from .pd import Decoder
|
||||
231
libsigrokdecode4DSL/decoders/st25r39xx_spi/lists.py
Normal file
231
libsigrokdecode4DSL/decoders/st25r39xx_spi/lists.py
Normal file
@@ -0,0 +1,231 @@
|
||||
##
|
||||
## This file is part of the libsigrokdecode project.
|
||||
##
|
||||
## Copyright (C) 2019-2020 Benjamin Vernoux <bvernoux@gmail.com>
|
||||
##
|
||||
## 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
|
||||
## the Free Software Foundation; either version 2 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
## v0.1 - 17 September 2019 B.VERNOUX using ST25R3916 Datasheet DS12484 Rev 1 (January 2019)
|
||||
## v0.2 - 28 April 2020 B.VERNOUX using ST25R3916 Datasheet DS12484 Rev 2 (December 2019) https://www.st.com/resource/en/datasheet/st25r3916.pdf
|
||||
## v0.3 - 17 June 2020 B.VERNOUX using ST25R3916 Datasheet DS12484 Rev 3 (04 June 2020) https://www.st.com/resource/en/datasheet/st25r3916.pdf
|
||||
|
||||
## ST25R3916 Datasheet DS12484 Rev 3 (04 June 2020) §4.4 Direct commands
|
||||
dir_cmd = {
|
||||
# addr: 'name'
|
||||
# Set Default
|
||||
0xC0: 'SET_DEFAULT',
|
||||
0xC1: 'SET_DEFAULT',
|
||||
# Stop All Activities
|
||||
0xC2: 'STOP',
|
||||
0xC3: 'STOP',
|
||||
# Transmit With CRC
|
||||
0xC4: 'TXCRC',
|
||||
# Transmit Without CRC
|
||||
0xC5: 'TXNOCRC',
|
||||
# Transmit REQA
|
||||
0xC6: 'TXREQA',
|
||||
# Transmit WUPA
|
||||
0xC7: 'TXWUPA',
|
||||
# NFC Initial Field ON
|
||||
0xC8: 'NFCINITFON',
|
||||
# NFC Response Field ON
|
||||
0xC9: 'NFCRESFON',
|
||||
# Go to Sense (Idle)
|
||||
0xCD: 'GOIDLE',
|
||||
# Go to Sleep (Halt)
|
||||
0xCE: 'GOHALT',
|
||||
# Mask Receive Data / Stops receivers and RX decoders
|
||||
0xD0: 'STOPRX',
|
||||
# Unmask Receive Data / Starts receivers and RX decoders
|
||||
0xD1: 'STARRX',
|
||||
# Change AM Modulation state
|
||||
0xD2: 'SETAMSTATE',
|
||||
# Measure Amplitude
|
||||
0xD3: 'MAMP',
|
||||
# Reset RX Gain
|
||||
0xD5: 'RSTRXGAIN',
|
||||
# Adjust Regulators
|
||||
0xD6: 'ADJREG',
|
||||
# Calibrate Driver Timing
|
||||
0xD8: 'CALDRVTIM',
|
||||
# Measure Phase
|
||||
0xD9: 'MPHASE',
|
||||
# Clear RSSI
|
||||
0xDA: 'CLRRSSI',
|
||||
# Clear FIFO
|
||||
0xDB: 'CLRFIFO',
|
||||
# Enter Transparent Mode
|
||||
0xDC: 'TRMODE',
|
||||
# Calibrate Capacitive Sensor
|
||||
0xDD: 'CALCAPA',
|
||||
# Measure Capacitance
|
||||
0xDE: 'MCAPA',
|
||||
# Measure Power Supply
|
||||
0xDF: 'MPOWER',
|
||||
# Start General Purpose Timer
|
||||
0xE0: 'STARGPTIM',
|
||||
# Start Wake-up Timer
|
||||
0xE1: 'STARWTIM',
|
||||
# Start Mask-receive Timer
|
||||
0xE2: 'STARMSKTIM',
|
||||
# Start No-response Timer
|
||||
0xE3: 'STARNRESPTIM',
|
||||
# Start PPON2 Timer
|
||||
0xE4: 'STARPPON2TIM',
|
||||
# Stop No-response Timer
|
||||
0xE8: 'STOPNRESTIM',
|
||||
# RFU / Not Used
|
||||
0xFA: 'RFU',
|
||||
# Register Space-B Access
|
||||
0xFB: 'REGSPACEB',
|
||||
# Register Test access
|
||||
0xFC: 'TESTACCESS'
|
||||
# Other codes => RFU / Not Used
|
||||
}
|
||||
|
||||
## ST25R3916 Datasheet DS12484 Rev 2 (December 2019) §4.5 Registers Table 17. List of registers - Space A
|
||||
## ST25R3916 Datasheet DS12484 Rev 2 (December 2019) §4.3.3 Serial peripheral interface (SPI) Table 11. SPI operation modes
|
||||
regsSpaceA = {
|
||||
# addr: 'name'
|
||||
# §4.5 Registers Table 17. List of registers - Space A
|
||||
# IO configuration
|
||||
0x00: 'IOCFG1',
|
||||
0x01: 'IOCFG2',
|
||||
# Operation control and mode definition
|
||||
0x02: 'OPCTRL',
|
||||
0x03: 'MODEDEF',
|
||||
0x04: 'BITRATE',
|
||||
# Protocol configuration
|
||||
0x05: 'TYPEA',
|
||||
0x06: 'TYPEB',
|
||||
0x07: 'TYPEBF',
|
||||
0x08: 'NFCIP1',
|
||||
0x09: 'STREAM',
|
||||
0x0A: 'AUX',
|
||||
# Receiver configuration
|
||||
0x0B: 'RXCFG1',
|
||||
0x0C: 'RXCFG2',
|
||||
0x0D: 'RXCFG3',
|
||||
0x0E: 'RXCFG4',
|
||||
# Timer definition
|
||||
0x0F: 'MSKRXTIM',
|
||||
0x10: 'NRESPTIM1',
|
||||
0x11: 'NRESPTIM2',
|
||||
0x12: 'TIMEMV',
|
||||
0x13: 'GPTIM1',
|
||||
0x14: 'GPTIM2',
|
||||
0x15: 'PPON2',
|
||||
# Interrupt and associated reporting
|
||||
0x16: 'MSKMAINIRQ',
|
||||
0x17: 'MSKTIMNFCIRQ',
|
||||
0x18: 'MSKERRWAKEIRQ',
|
||||
0x19: 'TARGIRQ',
|
||||
0x1A: 'MAINIRQ',
|
||||
0x1B: 'TIMNFCIRQ',
|
||||
0x1C: 'ERRWAKEIRQ',
|
||||
0x1D: 'TARGIRQ',
|
||||
0x1E: 'FIFOSTAT1',
|
||||
0x1F: 'FIFOSTAT2',
|
||||
0x20: 'COLLDISP',
|
||||
0x21: 'TARGDISP',
|
||||
# Definition of number of transmitted bytes
|
||||
0x22: 'NBTXB1',
|
||||
0x23: 'NBTXB2',
|
||||
0x24: 'BITRATEDET',
|
||||
# A/D converter output
|
||||
0x25: 'ADCONVOUT',
|
||||
# Antenna calibration
|
||||
0x26: 'ANTTUNECTRL1',
|
||||
0x27: 'ANTTUNECTRL2',
|
||||
# Antenna driver and modulation
|
||||
0x28: 'TXDRV',
|
||||
0x29: 'TARGMOD',
|
||||
# External field detector threshold
|
||||
0x2A: 'EXTFIELDON',
|
||||
0x2B: 'EXTFIELDOFF',
|
||||
# Regulator
|
||||
0x2C: 'REGVDDCTRL',
|
||||
# Receiver state display
|
||||
0x2D: 'RSSIDISP',
|
||||
0x2E: 'GAINSTATE',
|
||||
# Capacitive sensor
|
||||
0x2F: 'CAPACTRL',
|
||||
0x30: 'CAPADISP',
|
||||
# Auxiliary display
|
||||
0x31: 'AUXDISP',
|
||||
# Wake-up
|
||||
0x32: 'WAKETIMCTRL',
|
||||
0x33: 'AMPCFG',
|
||||
0x34: 'AMPREF',
|
||||
0x35: 'AMPAAVGDISP',
|
||||
0x36: 'AMPDISP',
|
||||
0x37: 'PHASECFG',
|
||||
0x38: 'PHASEREF',
|
||||
0x39: 'PHASEAAVGDISP',
|
||||
0x3A: 'PHASEDISP',
|
||||
0x3B: 'CAPACFG',
|
||||
0x3C: 'CAPAREF',
|
||||
0x3D: 'CAPAAAVGDISP',
|
||||
0x3E: 'CAPADISP',
|
||||
# IC identity
|
||||
0x3F: 'ICIDENT',
|
||||
## ST25R3916 Datasheet DS12484 Rev 2 (December 2019) §4.3.3 Serial peripheral interface (SPI) Table 11. SPI operation modes
|
||||
0xA0: 'PT_memLoadA',
|
||||
0xA8: 'PT_memLoadF',
|
||||
0xAC: 'PT_memLoadTSN',
|
||||
0xBF: 'PT_memRead'
|
||||
}
|
||||
|
||||
## ST25R3916 Datasheet DS12484 Rev 2 (December 2019) §4.5 Registers Table 18. List of registers - Space B
|
||||
regsSpaceB = {
|
||||
# addr: 'name'
|
||||
# §4.5 Registers Table 18. List of registers - Space B
|
||||
# Protocol configuration
|
||||
0x05: 'EMDSUPPRCONF',
|
||||
0x06: 'SUBCSTARTIM',
|
||||
# Receiver configuration
|
||||
0x0B: 'P2PRXCONF',
|
||||
0x0C: 'CORRCONF1',
|
||||
0x0D: 'CORRCONF2',
|
||||
# Timer definition
|
||||
0x0F: 'SQUELSHTIM',
|
||||
0x15: 'NFCGUARDTIM',
|
||||
# Antenna driver and modulation
|
||||
0x28: 'AUXMODSET',
|
||||
0x29: 'TXDRVTIM',
|
||||
# External field detector threshold
|
||||
0x2A: 'RESAMMODE',
|
||||
0x2B: 'TXDRVTIMDISP',
|
||||
# Regulator
|
||||
0x2C: 'REGDISP',
|
||||
# Protection
|
||||
0x30: 'OSHOOTCONF1',
|
||||
0x31: 'OSHOOTCONF2',
|
||||
0x32: 'USHOOTCONF1',
|
||||
0x33: 'USHOOTCONF2'
|
||||
}
|
||||
|
||||
## ST25R3916 Datasheet DS12484 Rev 2 (December 2019) §4.4.17 Test access
|
||||
regsTest = {
|
||||
# addr: 'name'
|
||||
# §4.4.17 Test access (Typo in datasheet it is not register 0x00 but 0x01)
|
||||
0x01: 'ANTSTOBS'
|
||||
}
|
||||
|
||||
## Optional TODO add important status bit fields / ANN_STATUS
|
||||
## Interrupt and associated reporting => Registers Space A from Address (hex) 0x16 to 0x21
|
||||
## §4.5.58 RSSI display register
|
||||
## §4.5.59 Gain reduction state register
|
||||
## ...
|
||||
|
||||
350
libsigrokdecode4DSL/decoders/st25r39xx_spi/pd.py
Normal file
350
libsigrokdecode4DSL/decoders/st25r39xx_spi/pd.py
Normal file
@@ -0,0 +1,350 @@
|
||||
##
|
||||
## This file is part of the libsigrokdecode project.
|
||||
##
|
||||
## Copyright (C) 2019-2020 Benjamin Vernoux <bvernoux@gmail.com>
|
||||
##
|
||||
## 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
|
||||
## the Free Software Foundation; either version 2 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
import sigrokdecode as srd
|
||||
from collections import namedtuple
|
||||
from common.srdhelper import SrdIntEnum
|
||||
from .lists import *
|
||||
|
||||
Ann = SrdIntEnum.from_str('Ann', 'BURST_READ BURST_WRITE \
|
||||
BURST_READB BURST_WRITEB BURST_READT BURST_WRITET \
|
||||
DIRECTCMD FIFO_WRITE FIFO_READ STATUS WARN')
|
||||
|
||||
Pos = namedtuple('Pos', ['ss', 'es'])
|
||||
Data = namedtuple('Data', ['mosi', 'miso'])
|
||||
|
||||
class Decoder(srd.Decoder):
|
||||
api_version = 3
|
||||
id = 'st25r39xx_spi'
|
||||
name = 'ST25R39xx (SPI mode)'
|
||||
longname = 'STMicroelectronics ST25R39xx'
|
||||
desc = 'High performance NFC universal device and EMVCo reader protocol.'
|
||||
license = 'gplv2+'
|
||||
inputs = ['spi']
|
||||
outputs = []
|
||||
tags = ['IC', 'Wireless/RF']
|
||||
annotations = (
|
||||
('Read', 'Burst register read'),
|
||||
('Write', 'Burst register write'),
|
||||
('ReadB', 'Burst register SpaceB read'),
|
||||
('WriteB', 'Burst register SpaceB write'),
|
||||
('ReadT', 'Burst register Test read'),
|
||||
('WriteT', 'Burst register Test write'),
|
||||
('Cmd', 'Direct command'),
|
||||
('FIFOW', 'FIFO write'),
|
||||
('FIFOR', 'FIFO read'),
|
||||
('status_reg', 'Status register'),
|
||||
('warning', 'Warning'),
|
||||
)
|
||||
annotation_rows = (
|
||||
('regs', 'Regs', (Ann.prefixes('BURST_'))),
|
||||
('cmds', 'Commands', (Ann.DIRECTCMD,)),
|
||||
('data', 'Data', (Ann.prefixes('FIFO_'))),
|
||||
('status', 'Status register', (Ann.STATUS,)),
|
||||
('warnings', 'Warnings', (Ann.WARN,)),
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
self.next()
|
||||
self.requirements_met = True
|
||||
self.cs_was_released = False
|
||||
|
||||
def start(self):
|
||||
self.out_ann = self.register(srd.OUTPUT_ANN)
|
||||
|
||||
def warn(self, pos, msg):
|
||||
'''Put a warning message 'msg' at 'pos'.'''
|
||||
self.put(pos.ss, pos.es, self.out_ann, [Ann.WARN, [msg]])
|
||||
|
||||
def putp(self, pos, ann, msg):
|
||||
'''Put an annotation message 'msg' at 'pos'.'''
|
||||
self.put(pos.ss, pos.es, self.out_ann, [ann, [msg]])
|
||||
|
||||
def putp2(self, pos, ann, msg1, msg2):
|
||||
'''Put an annotation message 'msg' at 'pos'.'''
|
||||
self.put(pos.ss, pos.es, self.out_ann, [ann, [msg1, msg2]])
|
||||
|
||||
def next(self):
|
||||
'''Resets the decoder after a complete command was decoded.'''
|
||||
# 'True' for the first byte after CS# went low.
|
||||
self.first = True
|
||||
|
||||
# The current command, and the minimum and maximum number
|
||||
# of data bytes to follow.
|
||||
self.cmd = None
|
||||
self.min = 0
|
||||
self.max = 0
|
||||
|
||||
# Used to collect the bytes after the command byte
|
||||
# (and the start/end sample number).
|
||||
self.mb = []
|
||||
self.ss_mb = -1
|
||||
self.es_mb = -1
|
||||
|
||||
def mosi_bytes(self):
|
||||
'''Returns the collected MOSI bytes of a multi byte command.'''
|
||||
return [b.mosi for b in self.mb]
|
||||
|
||||
def miso_bytes(self):
|
||||
'''Returns the collected MISO bytes of a multi byte command.'''
|
||||
return [b.miso for b in self.mb]
|
||||
|
||||
def decode_command(self, pos, b):
|
||||
'''Decodes the command byte 'b' at position 'pos' and prepares
|
||||
the decoding of the following data bytes.'''
|
||||
c = self.parse_command(b)
|
||||
if c is None:
|
||||
self.warn(pos, 'Unknown command')
|
||||
return
|
||||
|
||||
self.cmd, self.dat, self.min, self.max = c
|
||||
|
||||
if self.cmd == 'Cmd':
|
||||
self.putp(pos, Ann.DIRECTCMD, self.format_command())
|
||||
else:
|
||||
# Don't output anything now, the command is merged with
|
||||
# the data bytes following it.
|
||||
self.ss_mb = pos.ss
|
||||
|
||||
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'):
|
||||
return self.cmd
|
||||
if self.cmd == 'Cmd':
|
||||
reg = dir_cmd.get(self.dat, 'Unknown direct command')
|
||||
return '{} {}'.format(self.cmd, reg)
|
||||
else:
|
||||
return 'TODO Cmd {}'.format(self.cmd)
|
||||
|
||||
def parse_command(self, b):
|
||||
'''Parses the command byte.
|
||||
Returns a tuple consisting of:
|
||||
- the name of the command
|
||||
- additional data needed to dissect the following bytes
|
||||
- minimum number of following bytes
|
||||
- maximum number of following bytes (None for infinite)
|
||||
'''
|
||||
addr = b & 0x3F
|
||||
# previous command was 'Space B'
|
||||
if self.cmd == 'Space B':
|
||||
if (b & 0xC0) == 0x00:
|
||||
return ('WriteB', addr, 1, 99999)
|
||||
if (b & 0xC0) == 0x40:
|
||||
return ('ReadB', addr, 1, 99999)
|
||||
else:
|
||||
self.warn(pos, 'Unknown address/command combination')
|
||||
# previous command was 'TestAccess'
|
||||
elif self.cmd == 'TestAccess':
|
||||
if (b & 0xC0) == 0x00:
|
||||
return ('WriteT', addr, 1, 99999)
|
||||
if (b & 0xC0) == 0x40:
|
||||
return ('ReadT', addr, 1, 99999)
|
||||
else:
|
||||
self.warn(pos, 'Unknown address/command combination')
|
||||
else:
|
||||
# Space A regs or other operation modes (except Space B)
|
||||
# Register Write 0b00xxxxxx 0x00 to 0x3F => 'Write'
|
||||
# Register Read 0b01xxxxxx 0x40 to 0x7F => 'Read'
|
||||
if (b <= 0x7F):
|
||||
if (b & 0xC0) == 0x00:
|
||||
return ('Write', addr, 1, 99999)
|
||||
if (b & 0xC0) == 0x40:
|
||||
return ('Read', addr, 1, 99999)
|
||||
else:
|
||||
self.warn(pos, 'Unknown address/command combination')
|
||||
else:
|
||||
# FIFO Load 0b10000000 0x80 => 'FIFO Write'
|
||||
# PT_memory loadA-config 0b10100000 0xA0 => 'Write'
|
||||
# PT_memory loadF-config 0b10101000 0xA8 => 'Write'
|
||||
# PT_memory loadTSN data 0b10101100 0xAC => 'Write'
|
||||
# PT_memory Read 0b10111111 0xBF => 'Read'
|
||||
# FIFO Read 0b10011111 0x9F => 'FIFO Read'
|
||||
# Direct Command 0b11xxx1xx 0xC0 to 0xE8 => 'Cmd'
|
||||
# Register Space-B Access 0b11111011 0xFB => 'Space B'
|
||||
# Register Test Access 0b11111100 0xFC => 'TestAccess'
|
||||
if b == 0x80:
|
||||
return ('FIFO Write', b, 1, 99999)
|
||||
if b == 0xA0:
|
||||
return ('Write', b, 1, 99999)
|
||||
if b == 0xA8:
|
||||
return ('Write', b, 1, 99999)
|
||||
if b == 0xAC:
|
||||
return ('Write', b, 1, 99999)
|
||||
if b == 0xBF:
|
||||
return ('Read', b, 1, 99999)
|
||||
if b == 0x9F:
|
||||
return ('FIFO Read', b, 1, 99999)
|
||||
if (b >= 0x0C and b <= 0xE8) :
|
||||
return ('Cmd', b, 0, 0)
|
||||
if b == 0xFB:
|
||||
return ('Space B', b, 0, 0)
|
||||
if b == 0xFC:
|
||||
return ('TestAccess', b, 0, 0)
|
||||
else:
|
||||
self.warn(pos, 'Unknown address/command combination')
|
||||
|
||||
def decode_reg(self, pos, ann, regid, data):
|
||||
'''Decodes a register.
|
||||
pos -- start and end sample numbers of the register
|
||||
ann -- the annotation number that is used to output the register.
|
||||
regid -- may be either an integer used as a key for the 'regs'
|
||||
dictionary, or a string directly containing a register name.'
|
||||
data -- the register content.
|
||||
'''
|
||||
if type(regid) == int:
|
||||
if (ann == Ann.FIFO_READ) or (ann == Ann.FIFO_WRITE):
|
||||
name = ''
|
||||
elif (ann == Ann.BURST_READB) or (ann == Ann.BURST_WRITEB):
|
||||
# Get the name of the register.
|
||||
if regid not in regsSpaceB:
|
||||
self.warn(pos, 'Unknown register SpaceB')
|
||||
return
|
||||
name = '{} ({:02X})'.format(regsSpaceB[regid], regid)
|
||||
elif (ann == Ann.BURST_READT) or (ann == Ann.BURST_WRITET):
|
||||
# Get the name of the register.
|
||||
if regid not in regsTest:
|
||||
self.warn(pos, 'Unknown register Test')
|
||||
return
|
||||
name = '{} ({:02X})'.format(regsTest[regid], regid)
|
||||
else:
|
||||
# Get the name of the register.
|
||||
if regid not in regsSpaceA:
|
||||
self.warn(pos, 'Unknown register SpaceA')
|
||||
return
|
||||
name = '{} ({:02X})'.format(regsSpaceA[regid], regid)
|
||||
else:
|
||||
name = regid
|
||||
|
||||
if regid == 'STATUS' and ann == Ann.STATUS:
|
||||
label = 'Status'
|
||||
self.decode_status_reg(pos, ann, data, label)
|
||||
else:
|
||||
label = '{}: {}'.format(self.format_command(), name)
|
||||
self.decode_mb_data(pos, ann, data, label)
|
||||
|
||||
def decode_status_reg(self, pos, ann, data, label):
|
||||
'''Decodes the data bytes 'data' of a status register at position
|
||||
'pos'. The decoded data is prefixed with 'label'.'''
|
||||
|
||||
def decode_mb_data(self, pos, ann, data, label):
|
||||
'''Decodes the data bytes 'data' of a multibyte command at position
|
||||
'pos'. The decoded data is prefixed with 'label'.'''
|
||||
|
||||
def escape(b):
|
||||
return '{:02X}'.format(b)
|
||||
|
||||
data = ' '.join([escape(b) for b in data])
|
||||
if (ann == Ann.FIFO_WRITE) or (ann == Ann.FIFO_READ):
|
||||
text = '{}{}'.format(label, data)
|
||||
else:
|
||||
text = '{} = {}'.format(label, data)
|
||||
self.putp(pos, ann, text)
|
||||
|
||||
def finish_command(self, pos):
|
||||
'''Decodes the remaining data bytes at position 'pos'.'''
|
||||
if self.cmd == 'Write':
|
||||
self.decode_reg(pos, Ann.BURST_WRITE, self.dat, self.mosi_bytes())
|
||||
elif self.cmd == 'Read':
|
||||
self.decode_reg(pos, Ann.BURST_READ, self.dat, self.miso_bytes())
|
||||
elif self.cmd == 'WriteB':
|
||||
self.decode_reg(pos, Ann.BURST_WRITEB, self.dat, self.mosi_bytes())
|
||||
elif self.cmd == 'ReadB':
|
||||
self.decode_reg(pos, Ann.BURST_READB, self.dat, self.miso_bytes())
|
||||
elif self.cmd == 'WriteT':
|
||||
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':
|
||||
self.decode_reg(pos, Ann.FIFO_WRITE, self.dat, self.mosi_bytes())
|
||||
elif self.cmd == 'FIFO Read':
|
||||
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())
|
||||
else:
|
||||
self.warn(pos, 'Unhandled command {}'.format(self.cmd))
|
||||
|
||||
def decode(self, ss, es, data):
|
||||
if not self.requirements_met:
|
||||
return
|
||||
|
||||
ptype, data1, data2 = data
|
||||
|
||||
if ptype == 'CS-CHANGE':
|
||||
if data1 is None:
|
||||
if data2 is None:
|
||||
self.requirements_met = False
|
||||
raise ChannelError('CS# pin required.')
|
||||
elif data2 == 1:
|
||||
self.cs_was_released = True
|
||||
|
||||
if data1 == 0 and data2 == 1:
|
||||
# Rising edge, the complete command is transmitted, process
|
||||
# the bytes that were sent after the command byte.
|
||||
if self.cmd:
|
||||
# Check if we got the minimum number of data bytes
|
||||
# after the command byte.
|
||||
if len(self.mb) < self.min:
|
||||
self.warn((ss, ss), 'Missing data bytes')
|
||||
elif self.mb:
|
||||
self.finish_command(Pos(self.ss_mb, self.es_mb))
|
||||
|
||||
self.next()
|
||||
self.cs_was_released = True
|
||||
|
||||
elif ptype == 'DATA' and self.cs_was_released:
|
||||
mosi, miso = data1, data2
|
||||
pos = Pos(ss, es)
|
||||
|
||||
if miso is None or mosi is None:
|
||||
self.requirements_met = False
|
||||
raise ChannelError('Both MISO and MOSI pins are required.')
|
||||
|
||||
if self.first:
|
||||
# Register Space-B Access 0b11111011 0xFB => 'Space B'
|
||||
if mosi == 0xFB:
|
||||
self.first = True
|
||||
# First MOSI byte 'Space B' command.
|
||||
self.decode_command(pos, mosi)
|
||||
# First MISO byte is always the status register.
|
||||
#self.decode_reg(pos, ANN_STATUS, 'STATUS', [miso])
|
||||
# Register TestAccess Access 0b11111100 0xFC => 'TestAccess'
|
||||
elif mosi == 0xFC:
|
||||
self.first = True
|
||||
# First MOSI byte 'TestAccess' command.
|
||||
self.decode_command(pos, mosi)
|
||||
# First MISO byte is always the status register.
|
||||
#self.decode_reg(pos, ANN_STATUS, 'STATUS', [miso])
|
||||
else:
|
||||
self.first = False
|
||||
# First MOSI byte is always the command.
|
||||
self.decode_command(pos, mosi)
|
||||
# First MISO byte is always the status register.
|
||||
#self.decode_reg(pos, ANN_STATUS, 'STATUS', [miso])
|
||||
else:
|
||||
if not self.cmd or len(self.mb) >= self.max:
|
||||
self.warn(pos, 'Excess byte')
|
||||
else:
|
||||
# Collect the bytes after the command byte.
|
||||
if self.ss_mb == -1:
|
||||
self.ss_mb = ss
|
||||
self.es_mb = es
|
||||
self.mb.append(Data(mosi, miso))
|
||||
25
libsigrokdecode4DSL/decoders/tdm_audio/__init__.py
Normal file
25
libsigrokdecode4DSL/decoders/tdm_audio/__init__.py
Normal file
@@ -0,0 +1,25 @@
|
||||
##
|
||||
## This file is part of the libsigrokdecode project.
|
||||
##
|
||||
## Copyright (C) 2019 Ben Dooks <ben.dooks@codethink.co.uk>
|
||||
##
|
||||
## 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
|
||||
## the Free Software Foundation; either version 2 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
'''
|
||||
TDM Audio is an audio serial bus for moving audio data between devices
|
||||
(usually on the same board) which can carry one or more channels of data.
|
||||
'''
|
||||
|
||||
from .pd import Decoder
|
||||
116
libsigrokdecode4DSL/decoders/tdm_audio/pd.py
Normal file
116
libsigrokdecode4DSL/decoders/tdm_audio/pd.py
Normal file
@@ -0,0 +1,116 @@
|
||||
##
|
||||
## This file is part of the libsigrokdecode project.
|
||||
##
|
||||
## Copyright (C) 2019 Ben Dooks <ben.dooks@codethink.co.uk>
|
||||
##
|
||||
## 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
|
||||
## the Free Software Foundation; either version 2 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
import sigrokdecode as srd
|
||||
|
||||
MAX_CHANNELS = 8
|
||||
|
||||
class Decoder(srd.Decoder):
|
||||
api_version = 3
|
||||
id = 'tdm_audio'
|
||||
name = 'TDM audio'
|
||||
longname = 'Time division multiplex audio'
|
||||
desc = 'TDM multi-channel audio protocol.'
|
||||
license = 'gplv2+'
|
||||
inputs = ['logic']
|
||||
outputs = []
|
||||
tags = ['Audio']
|
||||
channels = (
|
||||
{ 'id': 'clock', 'name': 'Bitclk', 'desc': 'Data bit clock' },
|
||||
{ 'id': 'frame', 'name': 'Framesync', 'desc': 'Frame sync' },
|
||||
{ 'id': 'data', 'name': 'Data', 'desc': 'Serial data' },
|
||||
)
|
||||
options = (
|
||||
{'id': 'bps', 'desc': 'Bits per sample', 'default': 16 },
|
||||
{'id': 'channels', 'desc': 'Channels per frame', 'default': MAX_CHANNELS },
|
||||
{'id': 'edge', 'desc': 'Clock edge to sample on', 'default': 'rising', 'values': ('rising', 'falling') }
|
||||
)
|
||||
annotations = tuple(('ch%d' % i, 'Ch%d' % i) for i in range(MAX_CHANNELS))
|
||||
annotation_rows = tuple(('ch%d-vals' % i, 'Ch%d' % i, (i,)) for i in range(MAX_CHANNELS))
|
||||
|
||||
def __init__(self):
|
||||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
self.samplerate = None
|
||||
self.channels = MAX_CHANNELS
|
||||
self.channel = 0
|
||||
self.bitdepth = 16
|
||||
self.bitcount = 0
|
||||
self.samplecount = 0
|
||||
self.lastsync = 0
|
||||
self.lastframe = 0
|
||||
self.data = 0
|
||||
self.ss_block = None
|
||||
|
||||
def metdatadata(self, key, value):
|
||||
if key == srd.SRD_CONF_SAMPLERATE:
|
||||
self.samplerate = value
|
||||
|
||||
def start(self):
|
||||
self.out_ann = self.register(srd.OUTPUT_ANN)
|
||||
self.bitdepth = self.options['bps']
|
||||
self.edge = self.options['edge']
|
||||
|
||||
def decode(self):
|
||||
while True:
|
||||
# Wait for edge of clock (sample on rising/falling edge).
|
||||
clock, frame, data = self.wait({0: self.edge[0]})
|
||||
|
||||
self.data = (self.data << 1) | data
|
||||
self.bitcount += 1
|
||||
|
||||
if self.ss_block is not None:
|
||||
if self.bitcount >= self.bitdepth:
|
||||
self.bitcount = 0
|
||||
self.channel += 1
|
||||
|
||||
c1 = 'Channel %d' % self.channel
|
||||
c2 = 'C%d' % self.channel
|
||||
c3 = '%d' % self.channel
|
||||
if self.bitdepth <= 8:
|
||||
v = '%02x' % self.data
|
||||
elif self.bitdepth <= 16:
|
||||
v = '%04x' % self.data
|
||||
else:
|
||||
v = '%08x' % self.data
|
||||
|
||||
if self.channel < self.channels:
|
||||
ch = self.channel
|
||||
else:
|
||||
ch = 0
|
||||
|
||||
self.put(self.ss_block, self.samplenum, self.out_ann,
|
||||
[ch, ['%s: %s' % (c1, v), '%s: %s' % (c2, v),
|
||||
'%s: %s' % (c3, v)]])
|
||||
self.data = 0
|
||||
self.ss_block = self.samplenum
|
||||
self.samplecount += 1
|
||||
|
||||
# Check for new frame.
|
||||
# Note, frame may be a single clock, or active for the first
|
||||
# sample in the frame.
|
||||
if frame != self.lastframe and frame == 1:
|
||||
self.channel = 0
|
||||
self.bitcount = 0
|
||||
self.data = 0
|
||||
if self.ss_block is None:
|
||||
self.ss_block = 0
|
||||
|
||||
self.lastframe = frame
|
||||
28
libsigrokdecode4DSL/decoders/xy2-100/__init__.py
Normal file
28
libsigrokdecode4DSL/decoders/xy2-100/__init__.py
Normal file
@@ -0,0 +1,28 @@
|
||||
##
|
||||
## This file is part of the libsigrokdecode project.
|
||||
##
|
||||
## Copyright (C) 2019 Uli Huber
|
||||
##
|
||||
## 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
|
||||
## the Free Software Foundation; either version 2 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
'''
|
||||
XY2-100 is a serial bus for connecting galvo systems to controllers
|
||||
|
||||
Details:
|
||||
|
||||
http://www.newson.be/doc.php?id=XY2-100
|
||||
'''
|
||||
|
||||
from .pd import Decoder
|
||||
242
libsigrokdecode4DSL/decoders/xy2-100/pd.py
Normal file
242
libsigrokdecode4DSL/decoders/xy2-100/pd.py
Normal file
@@ -0,0 +1,242 @@
|
||||
##
|
||||
## This file is part of the libsigrokdecode project.
|
||||
##
|
||||
## Copyright (C) 2019 Uli Huber
|
||||
## Copyright (C) 2020 Soeren Apel
|
||||
##
|
||||
## 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
|
||||
## the Free Software Foundation; either version 2 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License
|
||||
## along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
##
|
||||
|
||||
import sigrokdecode as srd
|
||||
|
||||
ann_bit, ann_stat_bit, ann_type, ann_command, ann_parameter, ann_parity, ann_pos, ann_status, ann_warning = range(9)
|
||||
frame_type_none, frame_type_command, frame_type_16bit_pos, frame_type_18bit_pos = range(4)
|
||||
|
||||
class Decoder(srd.Decoder):
|
||||
api_version = 3
|
||||
id = 'xy2-100'
|
||||
name = 'XY2-100'
|
||||
longname = 'XY2-100(E) and XY-200(E) galvanometer protocol'
|
||||
desc = 'Serial protocol for galvanometer positioning in laser systems'
|
||||
license = 'gplv2+'
|
||||
inputs = ['logic']
|
||||
outputs = []
|
||||
|
||||
tags = ['Embedded/industrial']
|
||||
|
||||
channels = (
|
||||
{'id': 'clk', 'name': 'CLK', 'desc': 'Clock'},
|
||||
{'id': 'sync', 'name': 'SYNC', 'desc': 'Sync'},
|
||||
{'id': 'data', 'name': 'DATA', 'desc': 'X, Y or Z axis data'},
|
||||
)
|
||||
optional_channels = (
|
||||
{'id': 'status', 'name': 'STAT', 'desc': 'X, Y or Z axis status'},
|
||||
)
|
||||
|
||||
annotations = (
|
||||
('bit', 'Data Bit'),
|
||||
('stat_bit', 'Status Bit'),
|
||||
('type', 'Frame Type'),
|
||||
('command', 'Command'),
|
||||
('parameter', 'Parameter'),
|
||||
('parity', 'Parity'),
|
||||
('position', 'Position'),
|
||||
('status', 'Status'),
|
||||
('warning', 'Human-readable warnings'),
|
||||
)
|
||||
annotation_rows = (
|
||||
('bits', 'Data Bits', (ann_bit,)),
|
||||
('stat_bits', 'Status Bits', (ann_stat_bit,)),
|
||||
('data', 'Data', (ann_type, ann_command, ann_parameter, ann_parity)),
|
||||
('positions', 'Positions', (ann_pos,)),
|
||||
('statuses', 'Statuses', (ann_status,)),
|
||||
('warnings', 'Warnings', (ann_warning,)),
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
self.samplerate = None
|
||||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
self.bits = []
|
||||
self.stat_bits = []
|
||||
self.stat_skip_bit = True
|
||||
|
||||
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 put_ann(self, ss, es, ann_class, value):
|
||||
self.put(ss, es, self.out_ann, [ann_class, value])
|
||||
|
||||
def process_bit(self, sync, bit_ss, bit_es, bit_value):
|
||||
self.put_ann(bit_ss, bit_es, ann_bit, ['%d' % bit_value])
|
||||
self.bits.append((bit_ss, bit_es, bit_value))
|
||||
|
||||
if sync == 0:
|
||||
if len(self.bits) < 20:
|
||||
self.put_ann(self.bits[0][0], bit_es, ann_warning, ['Not enough data bits'])
|
||||
self.reset()
|
||||
return
|
||||
|
||||
# Bit structure:
|
||||
# 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
||||
# T --------------- 18-bit pos ----------------- PARITY or
|
||||
# -TYPE-- ------------ 16-bit pos -------------- PARITY or
|
||||
# -TYPE-- -8-bit command -8-bit parameter value- PARITY
|
||||
|
||||
# Calculate parity, excluding the parity bit itself
|
||||
parity = 0
|
||||
for ss, es, value in self.bits[:-1]:
|
||||
parity ^= value
|
||||
|
||||
par_ss, par_es, par_value = self.bits[19]
|
||||
parity_even = 0
|
||||
parity_odd = 0
|
||||
if (par_value == parity):
|
||||
parity_even = 1
|
||||
else:
|
||||
parity_odd = 1
|
||||
|
||||
type_1_value = self.bits[0][2]
|
||||
type_3_value = (self.bits[0][2] << 2) | (self.bits[1][2] << 1) | self.bits[2][2]
|
||||
|
||||
# Determine frame type
|
||||
type = frame_type_none
|
||||
parity_status = ['X', 'Unknown']
|
||||
type_ss = self.bits[0][0]
|
||||
type_es = self.bits[2][1]
|
||||
|
||||
### 18-bit position
|
||||
if (type_1_value == 1) and (parity_odd == 1):
|
||||
type = frame_type_18bit_pos
|
||||
type_es = self.bits[0][1]
|
||||
self.put_ann(self.bits[0][0], bit_es, ann_warning, ['Careful: 18-bit position frames with wrong parity and command frames with wrong parity cannot be identified'])
|
||||
### 16-bit position
|
||||
elif (type_3_value == 1):
|
||||
type = frame_type_16bit_pos
|
||||
if (parity_even == 1):
|
||||
parity_status = ['OK']
|
||||
else:
|
||||
parity_status = ['NOK']
|
||||
self.put_ann(self.bits[0][0], bit_es, ann_warning, ['Parity error', 'PE'])
|
||||
### Command
|
||||
elif (type_3_value == 7) and (parity_even == 1):
|
||||
type = frame_type_command
|
||||
self.put_ann(self.bits[0][0], bit_es, ann_warning, ['Careful: 18-bit position frames with wrong parity and command frames with wrong parity cannot be identified'])
|
||||
### Other
|
||||
else:
|
||||
self.put_ann(self.bits[0][0], bit_es, ann_warning, ['Error', 'Unknown command or parity error'])
|
||||
self.reset()
|
||||
return
|
||||
|
||||
# Output command and parity annotations
|
||||
if (type == frame_type_16bit_pos):
|
||||
self.put_ann(type_ss, type_es, ann_type, ['16 bit Position Frame', '16 bit Pos', 'Pos', 'P'])
|
||||
if (type == frame_type_18bit_pos):
|
||||
self.put_ann(type_ss, type_es, ann_type, ['18 bit Position Frame', '18 bit Pos', 'Pos', 'P'])
|
||||
if (type == frame_type_command):
|
||||
self.put_ann(type_ss, type_es, ann_type, ['Command Frame', 'Command', 'C'])
|
||||
|
||||
self.put_ann(par_ss, par_es, ann_parity, parity_status)
|
||||
|
||||
# Output value
|
||||
if (type == frame_type_16bit_pos) or (type == frame_type_18bit_pos):
|
||||
pos = 0
|
||||
|
||||
if (type == frame_type_16bit_pos):
|
||||
count = 15
|
||||
for ss, es, value in self.bits[3:19]:
|
||||
pos |= value << count
|
||||
count -= 1
|
||||
pos = pos if pos < 32768 else pos - 65536
|
||||
else:
|
||||
count = 17
|
||||
for ss, es, value in self.bits[3:19]:
|
||||
pos |= value << count
|
||||
count -= 1
|
||||
pos = pos if pos < 131072 else pos - 262144
|
||||
|
||||
self.put_ann(type_es, par_ss, ann_pos, ['%d' % pos])
|
||||
|
||||
if (type == frame_type_command):
|
||||
count = 7
|
||||
cmd = 0
|
||||
cmd_es = 0
|
||||
for ss, es, value in self.bits[3:11]:
|
||||
cmd |= value << count
|
||||
count -= 1
|
||||
cmd_es = es
|
||||
self.put_ann(type_es, cmd_es, ann_command, ['Command 0x%X' % cmd, 'Cmd 0x%X' % cmd, '0x%X' % cmd])
|
||||
|
||||
count = 7
|
||||
param = 0
|
||||
for ss, es, value in self.bits[11:19]:
|
||||
param |= value << count
|
||||
count -= 1
|
||||
self.put_ann(cmd_es, par_ss, ann_parameter, ['Parameter 0x%X / %d' % (param, param), '0x%X / %d' % (param, param),'0x%X' % param])
|
||||
|
||||
self.reset()
|
||||
|
||||
def process_stat_bit(self, sync, bit_ss, bit_es, bit_value):
|
||||
if self.stat_skip_bit:
|
||||
self.stat_skip_bit = False
|
||||
return
|
||||
|
||||
self.put_ann(bit_ss, bit_es, ann_stat_bit, ['%d' % bit_value])
|
||||
self.stat_bits.append((bit_ss, bit_es, bit_value))
|
||||
|
||||
if (sync == 0) and (len(self.stat_bits) == 19):
|
||||
stat_ss = self.stat_bits[0][0]
|
||||
stat_es = self.stat_bits[18][1]
|
||||
|
||||
status = 0
|
||||
count = 18
|
||||
for ss, es, value in self.stat_bits:
|
||||
status |= value << count
|
||||
count -= 1
|
||||
self.put_ann(stat_ss, stat_es, ann_status, ['Status 0x%X' % status, '0x%X' % status])
|
||||
|
||||
def decode(self):
|
||||
bit_ss = None
|
||||
bit_es = None
|
||||
bit_value = 0
|
||||
stat_ss = None
|
||||
stat_es = None
|
||||
stat_value = 0
|
||||
sync_value = 0
|
||||
has_stat = self.has_channel(3)
|
||||
|
||||
while True:
|
||||
# Wait for any edge on clk
|
||||
clk, sync, data, stat = self.wait({0: 'e'})
|
||||
|
||||
if clk == 1:
|
||||
stat_value = stat
|
||||
|
||||
bit_es = self.samplenum
|
||||
if bit_ss:
|
||||
self.process_bit(sync_value, bit_ss, bit_es, bit_value)
|
||||
bit_ss = self.samplenum
|
||||
else:
|
||||
bit_value = data
|
||||
sync_value = sync
|
||||
|
||||
stat_es = self.samplenum
|
||||
if stat_ss and has_stat:
|
||||
self.process_stat_bit(sync_value, stat_ss, stat_es, stat_value)
|
||||
stat_ss = self.samplenum
|
||||
Reference in New Issue
Block a user