Unverified Commit b018742e authored by DreamSourceLab's avatar DreamSourceLab Committed by GitHub
Browse files

Merge pull request #262 from abougouffa/master

Fix #258, and add new decoders
parents c5c12248 ce11f856
......@@ -92,6 +92,7 @@ endif()
find_package(Threads)
find_package(PythonLibs 3 EXACT)
find_package(Boost 1.42 COMPONENTS filesystem system thread REQUIRED)
find_package(libusb-1.0 REQUIRED)
find_package(libzip REQUIRED)
......@@ -390,6 +391,7 @@ else()
list(APPEND DSVIEW_LINK_LIBS ${PKGDEPS_LIBRARIES})
endif()
list(APPEND DSVIEW_LINK_LIBS ${PYTHON_LIBRARIES})
add_executable(${PROJECT_NAME}
${DSView_SOURCES}
......
##
## This file is part of the libsigrokdecode project.
##
## Copyright (C) 2019 Marco Geisler <m-sigrok@mageis.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/>.
##
'''
This decoder stacks on top of the 'spi' PD and decodes the protocol spoken
by the Texas Instruments low-power sub-1GHz RF transceiver chips.
Details:
http://www.ti.com/lit/ds/symlink/cc1101.pdf
'''
from .pd import Decoder
##
## This file is part of the libsigrokdecode project.
##
## Copyright (C) 2019 Marco Geisler <m-sigrok@mageis.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/>.
##
regs = {
# addr: 'name'
0x00: 'IOCFG2',
0x01: 'IOCFG1',
0x02: 'IOCFG0',
0x03: 'FIFOTHR',
0x04: 'SYNC1',
0x05: 'SYNC0',
0x06: 'PKTLEN',
0x07: 'PKTCTRL1',
0x08: 'PKTCTRL0',
0x09: 'ADDR',
0x0A: 'CHANNR',
0x0B: 'FSCTRL1',
0x0C: 'FSCTRL0',
0x0D: 'FREQ2',
0x0E: 'FREQ1',
0x0F: 'FREQ0',
0x10: 'MDMCFG4',
0x11: 'MDMCFG3',
0x12: 'MDMCFG2',
0x13: 'MDMCFG1',
0x14: 'MDMCFG0',
0x15: 'DEVIATN',
0x16: 'MCSM2',
0x17: 'MCSM1',
0x18: 'MCSM0',
0x19: 'FOCCFG',
0x1A: 'BSCFG',
0x1B: 'AGCTRL2',
0x1C: 'AGCTRL1',
0x1D: 'AGCTRL0',
0x1E: 'WOREVT1',
0x1F: 'WOREVT0',
0x20: 'WORCTRL',
0x21: 'FREND1',
0x22: 'FREND0',
0x23: 'FSCAL3',
0x24: 'FSCAL2',
0x25: 'FSCAL1',
0x26: 'FSCAL0',
0x27: 'RCCTRL1',
0x28: 'RCCTRL0',
0x29: 'FSTEST',
0x2A: 'PTEST',
0x2B: 'AGCTEST',
0x2C: 'TEST2',
0x2D: 'TEST1',
0x2E: 'TEST0',
0x30: 'PARTNUM',
0x31: 'VERSION',
0x32: 'FREQEST',
0x33: 'LQI',
0x34: 'RSSI',
0x35: 'MARCSTATE',
0x36: 'WORTIME1',
0x37: 'WORTIME0',
0x38: 'PKTSTATUS',
0x39: 'VCO_VC_DAC',
0x3A: 'TXBYTES',
0x3B: 'RXBYTES',
0x3C: 'RCCTRL1_STATUS',
0x3D: 'RCCTRL0_STATUS',
0x3E: 'PATABLE',
0x3F: 'FIFO'
}
strobes = {
# addr: 'name'
0x30: 'SRES',
0x31: 'SFSTXON',
0x32: 'SXOFF',
0x33: 'SCAL',
0x34: 'SRX',
0x35: 'STX',
0x36: 'SIDLE',
0x37: '',
0x38: 'SWOR',
0x39: 'SPWD',
0x3A: 'SFRX',
0x3B: 'SFTX',
0x3C: 'SWORRST',
0x3D: 'SNOP'
}
status_reg_states = {
# value: 'state name'
0b000: 'IDLE',
0b001: 'RX',
0b010: 'TX',
0b011: 'FSTXON',
0b100: 'CALIBRATE',
0b101: 'SETTLING',
0b110: 'RXFIFO_OVERFLOW',
0b111: 'TXFIFO_OVERFLOW'
}
##
## This file is part of the libsigrokdecode project.
##
## Copyright (C) 2019 Marco Geisler <m-sigrok@mageis.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
from collections import namedtuple
from .lists import *
ANN_STROBE, ANN_SINGLE_READ, ANN_SINGLE_WRITE, ANN_BURST_READ, \
ANN_BURST_WRITE, ANN_STATUS_READ, ANN_STATUS, ANN_WARN = range(8)
Pos = namedtuple('Pos', ['ss', 'es'])
Data = namedtuple('Data', ['mosi', 'miso'])
class Decoder(srd.Decoder):
api_version = 3
id = 'cc1101'
name = 'CC1101'
longname = 'Texas Instruments CC1101'
desc = 'Low-power sub-1GHz RF transceiver chip.'
license = 'gplv2+'
inputs = ['spi']
outputs = []
tags = ['IC', 'Wireless/RF']
annotations = (
('strobe', 'Command strobe'),
('single_read', 'Single register read'),
('single_write', 'Single register write'),
('burst_read', 'Burst register read'),
('burst_write', 'Burst register write'),
('status', 'Status register'),
('warning', 'Warning'),
)
annotation_rows = (
('cmd', 'Commands', (ANN_STROBE,)),
('data', 'Data', (ANN_SINGLE_READ, ANN_SINGLE_WRITE, ANN_BURST_READ,
ANN_BURST_WRITE, ANN_STATUS_READ)),
('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 == 'Strobe':
self.putp(pos, ANN_STROBE, 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 ('Read', 'Burst read', 'Write', 'Burst write', 'Status read'):
return self.cmd
if self.cmd == 'Strobe':
reg = strobes.get(self.dat, 'unknown strobe')
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
if (addr < 0x30) or (addr == 0x3E) or (addr == 0x3F):
if (b & 0xC0) == 0x00:
return ('Write', addr, 1, 1)
if (b & 0xC0) == 0x40:
return ('Burst write', addr, 1, 99999)
if (b & 0xC0) == 0x80:
return ('Read', addr, 1, 1)
if (b & 0xC0) == 0xC0:
return ('Burst read', addr, 1, 99999)
else:
self.warn(pos, 'unknown address/command combination')
else:
if (b & 0x40) == 0x00:
return ('Strobe', addr, 0, 0)
if (b & 0xC0) == 0xC0:
return ('Status read', addr, 1, 99999)
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:
# Get the name of the register.
if regid not in regs:
self.warn(pos, 'unknown register')
return
name = '{} ({:02X})'.format(regs[regid], regid)
else:
name = regid
if regid == 'STATUS' and ann == ANN_STATUS:
label = 'Status'
self.decode_status_reg(pos, ann, data, label)
else:
if self.cmd in ('Write', 'Read', 'Status read', 'Burst read', 'Burst write'):
label = '{}: {}'.format(self.format_command(), name)
else:
label = 'Reg ({}) {}'.format(self.cmd, 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'.'''
status = data[0]
# bit 7 --> CHIP_RDYn
if status & 0b10000000 == 0b10000000:
longtext_chiprdy = 'CHIP_RDYn is high! '
else:
longtext_chiprdy = ''
# bits 6:4 --> STATE
state = (status & 0x70) >> 4
longtext_state = 'STATE is {}, '.format(status_reg_states[state])
# bits 3:0 --> FIFO_BYTES_AVAILABLE
fifo_bytes = status & 0x0F
if self.cmd in ('Single read', 'Status read', 'Burst read'):
longtext_fifo = '{} bytes available in RX FIFO'.format(fifo_bytes)
else:
longtext_fifo = '{} bytes free in TX FIFO'.format(fifo_bytes)
text = '{} = {:02X}'.format(label, status)
longtext = ''.join([text, '; ', longtext_chiprdy, longtext_state, longtext_fifo])
self.putp2(pos, ann, longtext, text)
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])
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_SINGLE_WRITE, self.dat, self.mosi_bytes())
elif self.cmd == 'Burst write':
self.decode_reg(pos, ANN_BURST_WRITE, self.dat, self.mosi_bytes())
elif self.cmd == 'Read':
self.decode_reg(pos, ANN_SINGLE_READ, self.dat, self.miso_bytes())
elif self.cmd == 'Burst read':
self.decode_reg(pos, ANN_BURST_READ, self.dat, self.miso_bytes())
elif self.cmd == 'Strobe':
self.decode_reg(pos, ANN_STROBE, self.dat, self.mosi_bytes())
elif self.cmd == 'Status read':
self.decode_reg(pos, ANN_STATUS_READ, self.dat, self.miso_bytes())
else:
self.warn(pos, 'unhandled command')
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 required.')
if self.first:
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))
##
## This file is part of the libsigrokdecode project.
##
## Copyright (C) 2012 Uwe Hermann <uwe@hermann-uwe.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/>.
##
'''
This decoder stacks on top of the 'onewire_network' PD and decodes the
Maxim DS2408 1-Wire 8-channel addressable switch protocol.
'''
from .pd import Decoder
##
## This file is part of the libsigrokdecode project.
##
## Copyright (C) 2019 Mariusz Bialonczyk <manio@skyboo.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
# Dictionary of FUNCTION commands and their names.
command = {
0xf0: 'Read PIO Registers',
0xf5: 'Channel Access Read',
0x5a: 'Channel Access Write',
0xcc: 'Write Conditional Search Register',
0xc3: 'Reset Activity Latches',
0x3c: 'Disable Test Mode',
}
class Decoder(srd.Decoder):
api_version = 3
id = 'ds2408'
name = 'DS2408'
longname = 'Maxim DS2408'
desc = '1-Wire 8-channel addressable switch.'
license = 'gplv2+'
inputs = ['onewire_network']
outputs = []
tags = ['Embedded/industrial', 'IC']
annotations = (
('text', 'Human-readable text'),
)
def __init__(self):
self.reset()
def reset(self):
# Bytes for function command.
self.bytes = []
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 decode(self, ss, es, data):
code, val = data
if code == 'RESET/PRESENCE':
self.ss, self.es = ss, es
self.putx([0, ['Reset/presence: %s'
% ('true' if val else 'false')]])
self.bytes = []
elif code == 'ROM':
self.ss, self.es = ss, es
family_code = val & 0xff
self.putx([0, ['ROM: 0x%016x (family code 0x%02x)' % (val, family_code)]])
self.bytes = []
elif code == 'DATA':
self.bytes.append(val)
if 1 == len(self.bytes):
self.ss, self.es = ss, es
if val not in command:
self.putx([0, ['Unrecognized command: 0x%02x' % val]])
else:
self.putx([0, ['%s (0x%02x)' % (command[val], val)]])
elif 0xf0 == self.bytes[0]: # Read PIO Registers
if 2 == len(self.bytes):
self.ss = ss
elif 3 == len(self.bytes):
self.es = es
self.putx([0, ['Target address: 0x%04x'
% ((self.bytes[2] << 8) + self.bytes[1])]])
elif 3 < len(self.bytes):
self.ss, self.es = ss, es
self.putx([0, ['Data: 0x%02x' % self.bytes[-1]]])
elif 0xf5 == self.bytes[0]: # Channel Access Read
if 2 == len(self.bytes):
self.ss = ss
elif 2 < len(self.bytes):
self.ss, self.es = ss, es
self.putx([0, ['PIO sample: 0x%02x' % self.bytes[-1]]])
elif 0x5a == self.bytes[0]: # Channel Access Write
if 2 == len(self.bytes):
self.ss = ss
elif 3 == len(self.bytes):
self.es = es
if (self.bytes[-1] == (self.bytes[-2] ^ 0xff)):
self.putx([0, ['Data: 0x%02x (bit-inversion correct: 0x%02x)' % (self.bytes[-2], self.bytes[-1])]])
else:
self.putx([0, ['Data error: second byte (0x%02x) is not bit-inverse of first (0x%02x)' % (self.bytes[-1], self.bytes[-2])]])
elif 3 < len(self.bytes):
self.ss, self.es = ss, es
if 0xaa == self.bytes[-1]:
self.putx([0, ['Success']])
elif 0xff == self.bytes[-1]:
self.putx([0, ['Fail New State']])
elif 0xcc == self.bytes[0]: # Write Conditional Search Register
if 2 == len(self.bytes):
self.ss = ss
elif 3 == len(self.bytes):
self.es = es
self.putx([0, ['Target address: 0x%04x'
% ((self.bytes[2] << 8) + self.bytes[1])]])
elif 3 < len(self.bytes):
self.ss, self.es = ss, es
self.putx([0, ['Data: 0x%02x' % self.bytes[-1]]])
elif 0xc3 == self.bytes[0]: # Reset Activity Latches
if 2 == len(self.bytes):
self.ss = ss
elif 2 < len(self.bytes):
self.ss, self.es = ss, es
if 0xaa == self.bytes[-1]:
self.putx([0, ['Success']])
else:
self.putx([0, ['Invalid byte']])
##
## This file is part of the libsigrokdecode project.
##
## Copyright (C) 2019 Jiahao Li <reg@ljh.me>
##
## 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.
'''
This decoder stacks on top of the 'spi' PD and decodes the protocol spoken
by the Microchip ENC28J60 Ethernet chip.
Details:
http://ww1.microchip.com/downloads/en/DeviceDoc/39662e.pdf
'''
from .pd import Decoder
##
## This file is part of the libsigrokdecode project.
##
## Copyright (C) 2019 Jiahao Li <reg@ljh.me>
##
## 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.
REGS = [
[
'ERDPTL',
'ERDPTH',
'EWRPTL',
'EWRPTH',
'ETXSTL',
'ETXSTH',
'ETXNDL',
'ETXNDH',
'ERXSTL',
'ERXSTH',
'ERXNDL',
'ERXNDH',
'ERXRDPTL',
'ERXRDPTH',
'ERXWRPTL',
'ERXWRPTH',
'EDMASTL',
'EDMASTH',
'EDMANDL',
'EDMANDH',
'EDMADSTL',
'EDMADSTH',
'EDMACSL',
'EDMACSH',
'—',
'—',
'Reserved',
'EIE',
'EIR',
'ESTAT',
'ECON2',
'ECON1',
],
[
'EHT0',
'EHT1',
'EHT2',
'EHT3',
'EHT4',
'EHT5',
'EHT6',
'EHT7',
'EPMM0',
'EPMM1',
'EPMM2',
'EPMM3',
'EPMM4',
'EPMM5',
'EPMM6',
'EPMM7',
'EPMCSL',
'EPMCSH',
'—',
'—',
'EPMOL',
'EPMOH',
'Reserved',
'Reserved',
'ERXFCON',
'EPKTCNT',
'Reserved',
'EIE',
'EIR',
'ESTAT',
'ECON2',
'ECON1',
],
[
'MACON1',
'Reserved',
'MACON3',
'MACON4',
'MABBIPG',
'—',
'MAIPGL',
'MAIPGH',
'MACLCON1',
'MACLCON2',
'MAMXFLL',
'MAMXFLH',
'Reserved',
'Reserved',
'Reserved',
'—',
'Reserved',
'Reserved',
'MICMD',
'—',
'MIREGADR',
'Reserved',
'MIWRL',
'MIWRH',
'MIRDL',
'MIRDH',
'Reserved',
'EIE',
'EIR',
'ESTAT',
'ECON2',
'ECON1',
],
[
'MAADR5',
'MAADR6',
'MAADR3',
'MAADR4',
'MAADR1',
'MAADR2',
'EBSTSD',
'EBSTCON',
'EBSTCSL',
'EBSTCSH',
'MISTAT',
'—',
'—',
'—',
'—',
'—',
'—',
'—',
'EREVID',
'—',
'—',
'ECOCON',
'Reserved',
'EFLOCON',
'EPAUSL',
'EPAUSH',
'Reserved',
'EIE',
'EIR',
'ESTAT',
'ECON2',
'ECON1',
],
]
##
## This file is part of the libsigrokdecode project.
##
## Copyright (C) 2019 Jiahao Li <reg@ljh.me>
##
## 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 .lists import *
OPCODE_MASK = 0b11100000
REG_ADDR_MASK = 0b00011111
OPCODE_HANDLERS = {
0b00000000: '_process_rcr',
0b00100000: '_process_rbm',
0b01000000: '_process_wcr',
0b01100000: '_process_wbm',
0b10000000: '_process_bfs',
0b10100000: '_process_bfc',
0b11100000: '_process_src',
}
(ANN_RCR, ANN_RBM, ANN_WCR, ANN_WBM, ANN_BFS, ANN_BFC, ANN_SRC, ANN_DATA,
ANN_REG_ADDR, ANN_WARNING) = range(10)
REG_ADDR_ECON1 = 0x1F
BIT_ECON1_BSEL0 = 0b00000001
BIT_ECON1_BSEL1 = 0b00000010
class Decoder(srd.Decoder):
api_version = 3
id = 'enc28j60'
name = 'ENC28J60'
longname = 'Microchip ENC28J60'
desc = 'Microchip ENC28J60 10Base-T Ethernet controller protocol.'
license = 'mit'
inputs = ['spi']
outputs = []
tags = ['Embedded/industrial', 'Networking']
annotations = (
('rcr', 'Read Control Register'),
('rbm', 'Read Buffer Memory'),
('wcr', 'Write Control Register'),
('wbm', 'Write Buffer Memory'),
('bfs', 'Bit Field Set'),
('bfc', 'Bit Field Clear'),
('src', 'System Reset Command'),
('data', 'Data'),
('reg-addr', 'Register Address'),
('warning', 'Warning'),
)
annotation_rows = (
('commands', 'Commands',
(ANN_RCR, ANN_RBM, ANN_WCR, ANN_WBM, ANN_BFS, ANN_BFC, ANN_SRC)),
('fields', 'Fields', (ANN_DATA, ANN_REG_ADDR)),
('warnings', 'Warnings', (ANN_WARNING,)),
)
def __init__(self):
self.reset()
def reset(self):
self.mosi = []
self.miso = []
self.ranges = []
self.cmd_ss = None
self.cmd_es = None
self.range_ss = None
self.range_es = None
self.active = False
self.bsel0 = None
self.bsel1 = None
def start(self):
self.out_ann = self.register(srd.OUTPUT_ANN)
def putc(self, data):
self.put(self.cmd_ss, self.cmd_es, self.out_ann, data)
def putr(self, data):
self.put(self.range_ss, self.range_es, self.out_ann, data)
def _process_command(self):
if len(self.mosi) == 0:
self.active = False
return
header = self.mosi[0]
opcode = header & OPCODE_MASK
if opcode not in OPCODE_HANDLERS:
self._put_command_warning("Unknown opcode.")
self.active = False
return
getattr(self, OPCODE_HANDLERS[opcode])()
self.active = False
def _get_register_name(self, reg_addr):
if (self.bsel0 is None) or (self.bsel1 is None):
# We don't know the bank we're in yet.
return None
else:
bank = (self.bsel1 << 1) + self.bsel0
return REGS[bank][reg_addr]
def _put_register_header(self):
reg_addr = self.mosi[0] & REG_ADDR_MASK
reg_name = self._get_register_name(reg_addr)
self.range_ss, self.range_es = self.cmd_ss, self.ranges[1][0]
if reg_name is None:
# We don't know the bank we're in yet.
self.putr([ANN_REG_ADDR, [
'Reg Bank ? Addr 0x{0:02X}'.format(reg_addr),
'?:{0:02X}'.format(reg_addr)]])
self.putr([ANN_WARNING, ['Warning: Register bank not known yet.',
'Warning']])
else:
self.putr([ANN_REG_ADDR, ['Reg {0}'.format(reg_name),
'{0}'.format(reg_name)]])
if (reg_name == '-') or (reg_name == 'Reserved'):
self.putr([ANN_WARNING, ['Warning: Invalid register accessed.',
'Warning']])
def _put_data_byte(self, data, byte_index, binary=False):
self.range_ss = self.ranges[byte_index][0]
if byte_index == len(self.mosi) - 1:
self.range_es = self.cmd_es
else:
self.range_es = self.ranges[byte_index + 1][0]
if binary:
self.putr([ANN_DATA, ['Data 0b{0:08b}'.format(data),
'{0:08b}'.format(data)]])
else:
self.putr([ANN_DATA, ['Data 0x{0:02X}'.format(data),
'{0:02X}'.format(data)]])
def _put_command_warning(self, reason):
self.putc([ANN_WARNING, ['Warning: {0}'.format(reason), 'Warning']])
def _process_rcr(self):
self.putc([ANN_RCR, ['Read Control Register', 'RCR']])
if (len(self.mosi) != 2) and (len(self.mosi) != 3):
self._put_command_warning('Invalid command length.')
return
self._put_register_header()
reg_name = self._get_register_name(self.mosi[0] & REG_ADDR_MASK)
if reg_name is None:
# We can't tell if we're accessing MAC/MII registers or not
# Let's trust the user in this case.
pass
else:
if (reg_name[0] == 'M') and (len(self.mosi) != 3):
self._put_command_warning('Attempting to read a MAC/MII '
+ 'register without using the dummy byte.')
return
if (reg_name[0] != 'M') and (len(self.mosi) != 2):
self._put_command_warning('Attempting to read a non-MAC/MII '
+ 'register using the dummy byte.')
return
if len(self.mosi) == 2:
self._put_data_byte(self.miso[1], 1)
else:
self.range_ss, self.range_es = self.ranges[1][0], self.ranges[2][0]
self.putr([ANN_DATA, ['Dummy Byte', 'Dummy']])
self._put_data_byte(self.miso[2], 2)
def _process_rbm(self):
if self.mosi[0] != 0b00111010:
self._put_command_warning('Invalid header byte.')
return
self.putc([ANN_RBM, ['Read Buffer Memory: Length {0}'.format(
len(self.mosi) - 1), 'RBM']])
for i in range(1, len(self.miso)):
self._put_data_byte(self.miso[i], i)
def _process_wcr(self):
self.putc([ANN_WCR, ['Write Control Register', 'WCR']])
if len(self.mosi) != 2:
self._put_command_warning('Invalid command length.')
return
self._put_register_header()
self._put_data_byte(self.mosi[1], 1)
if self.mosi[0] & REG_ADDR_MASK == REG_ADDR_ECON1:
self.bsel0 = (self.mosi[1] & BIT_ECON1_BSEL0) >> 0
self.bsel1 = (self.mosi[1] & BIT_ECON1_BSEL1) >> 1
def _process_wbm(self):
if self.mosi[0] != 0b01111010:
self._put_command_warning('Invalid header byte.')
return
self.putc([ANN_WBM, ['Write Buffer Memory: Length {0}'.format(
len(self.mosi) - 1), 'WBM']])
for i in range(1, len(self.mosi)):
self._put_data_byte(self.mosi[i], i)
def _process_bfc(self):
self.putc([ANN_BFC, ['Bit Field Clear', 'BFC']])
if len(self.mosi) != 2:
self._put_command_warning('Invalid command length.')
return
self._put_register_header()
self._put_data_byte(self.mosi[1], 1, True)
if self.mosi[0] & REG_ADDR_MASK == REG_ADDR_ECON1:
if self.mosi[1] & BIT_ECON1_BSEL0:
self.bsel0 = 0
if self.mosi[1] & BIT_ECON1_BSEL1:
self.bsel1 = 0
def _process_bfs(self):
self.putc([ANN_BFS, ['Bit Field Set', 'BFS']])
if len(self.mosi) != 2:
self._put_command_warning('Invalid command length.')
return
self._put_register_header()
self._put_data_byte(self.mosi[1], 1, True)
if self.mosi[0] & REG_ADDR_MASK == REG_ADDR_ECON1:
if self.mosi[1] & BIT_ECON1_BSEL0:
self.bsel0 = 1
if self.mosi[1] & BIT_ECON1_BSEL1:
self.bsel1 = 1
def _process_src(self):
self.putc([ANN_SRC, ['System Reset Command', 'SRC']])
if len(self.mosi) != 1:
self._put_command_warning('Invalid command length.')
return
self.bsel0 = 0
self.bsel1 = 0
def decode(self, ss, es, data):
ptype, data1, data2 = data
if ptype == 'CS-CHANGE':
new_cs = data2
if new_cs == 0:
self.active = True
self.cmd_ss = ss
self.mosi = []
self.miso = []
self.ranges = []
elif new_cs == 1:
if self.active:
self.cmd_es = es
self._process_command()
elif ptype == 'DATA':
mosi, miso = data1, data2
self.mosi.append(mosi)
self.miso.append(miso)
self.ranges.append((ss, es))
##
## This file is part of the libsigrokdecode project.
##
## Copyright (C) 2019 Mickael Bosch <mickael.bosch@linux.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 'i2c' PD and decodes the NXP Semiconductors
PCA9571 8-bit I²C output expander protocol.
'''
from .pd import Decoder
##
## This file is part of the libsigrokdecode project.
##
## Copyright (C) 2019 Mickael Bosch <mickael.bosch@linux.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
NUM_OUTPUT_CHANNELS = 8
# TODO: Other I²C functions: general call / reset address, device ID address.
class Decoder(srd.Decoder):
api_version = 3
id = 'pca9571'
name = 'PCA9571'
longname = 'NXP PCA9571'
desc = 'NXP PCA9571 8-bit I²C output expander.'
license = 'gplv2+'
inputs = ['i2c']
outputs = []
tags = ['Embedded/industrial', 'IC']
annotations = (
('register', 'Register type'),
('value', 'Register value'),
('warning', 'Warning messages'),
)
annotation_rows = (
('regs', 'Registers', (0, 1)),
('warnings', 'Warnings', (2,)),
)
def __init__(self):
self.reset()
def reset(self):
self.state = 'IDLE'
self.last_write = 0xFF # Chip port default state is high.
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 handle_io(self, b):
if self.state == 'READ DATA':
operation = ['Outputs read', 'R']
if b != self.last_write:
self.putx([2, ['Warning: read value and last write value '
'(%02X) are different' % self.last_write]])
else:
operation = ['Outputs set', 'W']
self.last_write = b
self.putx([1, [operation[0] + ': %02X' % b,
operation[1] + ': %02X' % b]])
def check_correct_chip(self, addr):
if addr != 0x25:
self.putx([2, ['Warning: I²C slave 0x%02X not a PCA9571 '
'compatible chip.' % addr]])
return False
return True
def decode(self, ss, es, data):
cmd, databyte = data
self.ss, self.es = ss, es
# State machine.
if cmd in ('ACK', 'BITS'): # Discard 'ACK' and 'BITS'.
pass
elif cmd in ('START', 'START REPEAT'): # Start a communication.
self.state = 'GET SLAVE ADDR'
elif cmd in ('NACK', 'STOP'): # Reset the state machine.
self.state = 'IDLE'
elif cmd in ('ADDRESS READ', 'ADDRESS WRITE'):
if ((self.state == 'GET SLAVE ADDR') and
self.check_correct_chip(databyte)):
if cmd == 'ADDRESS READ':
self.state = 'READ DATA'
else:
self.state = 'WRITE DATA'
else:
self.state = 'IDLE'
elif cmd in ('DATA READ', 'DATA WRITE'):
if self.state in ('READ DATA', 'WRITE DATA'):
self.handle_io(databyte)
else:
self.state = 'IDLE'
##
## 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/>.
##
'''
This decoder decodes the output of a 7-segment display.
'''
from .pd import Decoder
##
## 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 ChannelError(Exception):
pass
digits = {
(0, 0, 0, 0, 0, 0, 0): ' ',
(1, 1, 1, 1, 1, 1, 0): '0',
(0, 1, 1, 0, 0, 0, 0): '1',
(1, 1, 0, 1, 1, 0, 1): '2',
(1, 1, 1, 1, 0, 0, 1): '3',
(0, 1, 1, 0, 0, 1, 1): '4',
(1, 0, 1, 1, 0, 1, 1): '5',
(1, 0, 1, 1, 1, 1, 1): '6',
(1, 1, 1, 0, 0, 0, 0): '7',
(1, 1, 1, 1, 1, 1, 1): '8',
(1, 1, 1, 1, 0, 1, 1): '9',
(1, 1, 1, 0, 1, 1, 1): 'A',
(0, 0, 1, 1, 1, 1, 1): 'B',
(1, 0, 0, 1, 1, 1, 0): 'C',
(0, 1, 1, 1, 1, 0, 1): 'D',
(1, 0, 0, 1, 1, 1, 1): 'E',
(1, 0, 0, 0, 1, 1, 1): 'F',
}
class Decoder(srd.Decoder):
api_version = 3
id = 'seven_segment'
name = '7-segment'
longname = '7-segment display'
desc = '7-segment display protocol.'
license = 'gplv2+'
inputs = ['logic']
outputs = []
tags = ['Display']
channels = (
{'id': 'a', 'name': 'A', 'desc': 'Segment A'},
{'id': 'b', 'name': 'B', 'desc': 'Segment B'},
{'id': 'c', 'name': 'C', 'desc': 'Segment C'},
{'id': 'd', 'name': 'D', 'desc': 'Segment D'},
{'id': 'e', 'name': 'E', 'desc': 'Segment E'},
{'id': 'f', 'name': 'F', 'desc': 'Segment F'},
{'id': 'g', 'name': 'G', 'desc': 'Segment G'},
)
optional_channels = (
{'id': 'dp', 'name': 'DP', 'desc': 'Decimal point'},
)
options = (
{'id': 'polarity', 'desc': 'Expected polarity',
'default': 'common-cathode', 'values': ('common-cathode', 'common-anode')},
)
annotations = (
('decoded-digit', 'Decoded digit'),
)
annotation_rows = (
('decoded-digits', 'Decoded digits', (0,)),
)
def __init__(self):
self.reset()
def reset(self):
pass
def start(self):
self.out_ann = self.register(srd.OUTPUT_ANN)
def putb(self, ss_block, es_block, data):
self.put(ss_block, es_block, self.out_ann, data)
def pins_to_hex(self, pins):
return digits.get(pins, None)
def decode(self):
oldpins = self.wait()
# Check if at least the 7 signals are present.
if False in [p in (0, 1) for p in oldpins[:7]]:
raise ChannelError('7 or 8 pins have to be present.')
lastpos = self.samplenum
self.have_dp = self.has_channel(7)
conditions = [{0: 'e'}, {1: 'e'}, {2: 'e'}, {3: 'e'}, {4: 'e'}, {5: 'e'}, {6: 'e'}]
if self.have_dp:
conditions.append({7: 'e'})
while True:
# Wait for any change.
pins = self.wait(conditions)
if self.options['polarity'] == 'common-anode':
# Invert all data lines if a common anode display is used.
if self.have_dp:
oldpins = tuple((1 - state for state in oldpins))
else:
oldpins = tuple((1 - state for state in oldpins[:7]))
# Convert to character string.
digit = self.pins_to_hex(oldpins[:7])
if digit is not None:
dp = oldpins[7]
# Check if decimal point is present and active.
if self.have_dp and dp == 1:
digit += '.'
self.putb(lastpos, self.samplenum, [0, [digit]])
lastpos = self.samplenum
oldpins = pins
##
## This file is part of the libsigrokdecode project.
##
## Copyright (C) 2018 Stefan Petersen <spe@ciellt.se>
##
## 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 Xicor X2444M/P
nonvolatile static RAM protocol.
'''
from .pd import Decoder
##
## This file is part of the libsigrokdecode project.
##
## Copyright (C) 2018 Stefan Petersen <spe@ciellt.se>
##
## 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 re
import sigrokdecode as srd
registers = {
0x80: ['WRDS', 0, lambda _: ''],
0x81: ['STO', 1, lambda _: ''],
0x82: ['SLEEP', 2, lambda _: ''],
0x83: ['WRITE', 3, lambda v: '0x%x' % v],
0x84: ['WREN', 4, lambda _: ''],
0x85: ['RCL', 5, lambda _: ''],
0x86: ['READ', 6, lambda v: '0x%x' % v],
0x87: ['READ', 7, lambda v: '0x%x' % v],
}
class Decoder(srd.Decoder):
api_version = 3
id = 'x2444m'
name = 'X2444M/P'
longname = 'Xicor X2444M/P'
desc = 'Xicor X2444M/P nonvolatile static RAM protocol.'
license = 'gplv2+'
inputs = ['spi']
outputs = []
tags = ['IC', 'Memory']
annotations = (
('wrds', 'Write disable'),
('sto', 'Store RAM data in EEPROM'),
('sleep', 'Enter sleep mode'),
('write', 'Write data into RAM'),
('wren', 'Write enable'),
('rcl', 'Recall EEPROM data into RAM'),
('read', 'Data read from RAM'),
('read', 'Data read from RAM'),
)
def __init__(self):
self.reset()
def reset(self):
self.cs_start = 0
self.cs_asserted = False
self.cmd_digit = 0
def start(self):
self.out_ann = self.register(srd.OUTPUT_ANN)
def putreadwrite(self, ss, es, reg, idx, addr, value):
self.put(ss, es, self.out_ann,
[idx, ['%s: %s => 0x%4.4x' % (reg, addr, value),
'%s: %s => 0x%4.4x' % (reg[0], addr, value), reg[0]]])
def putcmd(self, ss, es, reg, idx):
self.put(ss, es, self.out_ann, [idx, [reg, reg[0]]])
def decode(self, ss, es, data):
ptype, mosi, miso = data
if ptype == 'DATA':
if not self.cs_asserted:
return
if self.cmd_digit == 0:
self.addr = mosi
self.addr_start = ss
elif self.cmd_digit > 0:
self.read_value = (self.read_value << 8) + miso
self.write_value = (self.write_value << 8) + mosi
self.cmd_digit += 1
elif ptype == 'CS-CHANGE':
self.cs_asserted = (miso == 1)
# When not asserted, CS has just changed from asserted to deasserted.
if not self.cs_asserted:
# Only one digit, simple command. Else read/write.
if self.cmd_digit == 1:
name, idx, decoder = registers[self.addr & 0x87]
self.putcmd(self.addr_start, es, name, idx)
elif self.cmd_digit > 1:
name, idx, decoder = registers[self.addr & 0x87]
if name == 'READ':
value = self.read_value
elif name == 'WRITE':
value = self.write_value
else:
value = 0
self.putreadwrite(self.addr_start, es, name, idx,
decoder((self.addr >> 3) & 0x0f), value)
if self.cs_asserted:
self.cs_start = ss
self.cmd_digit = 0
self.read_value = 0
self.write_value = 0
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment