2
0
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:
DreamSourceLab
2021-03-16 19:47:54 +08:00
committed by GitHub
61 changed files with 9514 additions and 7 deletions

1
.gitignore vendored
View File

@@ -7,6 +7,7 @@
*.lo
CMakeFiles
CMakeCache.txt
CMakeLists.txt.user
*.cxx_parameters
autom4te.cache
!cmake_modules

View File

@@ -1,18 +1,20 @@
# DSView
![DreamSourceLab Logo](DSView/icons/dsl_logo.svg)
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

View 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

View 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

View 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

View 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

View 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

View 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'],
}

View 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

View 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

View 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
]

View 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

View 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

View 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()

View 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

View 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)

View 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

View 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)

View 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

View 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)

View 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

View 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,
}

View 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}])

View 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

View 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

View 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

View 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)

View 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',
])

View 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

View 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()

View 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

View 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

View 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

View 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

View 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

View 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)

View 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

View 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))

View 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

View 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

View 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

View 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()

View 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

View 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

View 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

View 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)

View 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

View 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)

View 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

View 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

View 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

View 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'])

View 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

View 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

View 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

View 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
## ...

View 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))

View 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

View 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

View 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

View 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