From: Uwe Hermann Date: Wed, 19 Nov 2014 17:57:56 +0000 (+0100) Subject: Add 24xx I2C EEPROM protocol decoder. X-Git-Tag: libsigrokdecode-0.4.0~137 X-Git-Url: https://sigrok.org/gitweb/?p=libsigrokdecode.git;a=commitdiff_plain;h=c4d5221077ef95488325dd104a806eadb5b5f13b Add 24xx I2C EEPROM protocol decoder. --- diff --git a/decoders/eeprom24xx/__init__.py b/decoders/eeprom24xx/__init__.py new file mode 100644 index 0000000..de19da4 --- /dev/null +++ b/decoders/eeprom24xx/__init__.py @@ -0,0 +1,26 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 Uwe Hermann +## +## 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 'i2c' PD and decodes the +industry standard 24xx series serial EEPROM protocol. +''' + +from .pd import Decoder diff --git a/decoders/eeprom24xx/lists.py b/decoders/eeprom24xx/lists.py new file mode 100644 index 0000000..ff4b3ed --- /dev/null +++ b/decoders/eeprom24xx/lists.py @@ -0,0 +1,171 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 Uwe Hermann +## +## 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 +## + +# +# Chip specific properties: +# +# - vendor: chip manufacturer +# - model: chip model +# - size: total EEPROM size (in number of bytes) +# - page_size: page size (in number of bytes) +# - page_wraparound: Whether writes wrap-around at page boundaries +# - addr_bytes: number of EEPROM address bytes used +# - addr_pins: number of address pins (A0/A1/A2) on this chip +# - max_speed: max. supported I²C speed (in kHz) +# +chips = { + # Generic chip (128 bytes, 8 bytes page size) + 'generic': { + 'vendor': '', + 'model': 'Generic', + 'size': 128, + 'page_size': 8, + 'page_wraparound': True, + 'addr_bytes': 1, + 'addr_pins': 3, + 'max_speed': 400, + }, + + # Microchip + 'microchip_24aa65': { + 'vendor': 'Microchip', + 'model': '24AA65', + 'size': 8 * 1024, + 'page_size': 64, # Actually 8, but there are 8 pages of "input cache" + 'page_wraparound': True, + 'addr_bytes': 2, + 'addr_pins': 3, + 'max_speed': 400, + }, + 'microchip_24lc65': { + 'vendor': 'Microchip', + 'model': '24LC65', + 'size': 8 * 1024, + 'page_size': 64, # Actually 8, but there are 8 pages of "input cache" + 'page_wraparound': True, + 'addr_bytes': 2, + 'addr_pins': 3, + 'max_speed': 400, + }, + 'microchip_24c65': { + 'vendor': 'Microchip', + 'model': '24C65', + 'size': 8 * 1024, + 'page_size': 64, # Actually 8, but there are 8 pages of "input cache" + 'page_wraparound': True, + 'addr_bytes': 2, + 'addr_pins': 3, + 'max_speed': 400, + }, + 'microchip_24aa64': { + 'vendor': 'Microchip', + 'model': '24AA64', + 'size': 8 * 1024, + 'page_size': 32, + 'page_wraparound': True, + 'addr_bytes': 2, + 'addr_pins': 3, + 'max_speed': 400, # 100 for VCC < 2.5V + }, + 'microchip_24lc64': { + 'vendor': 'Microchip', + 'model': '24LC64', + 'size': 8 * 1024, + 'page_size': 32, + 'page_wraparound': True, + 'addr_bytes': 2, + 'addr_pins': 3, + 'max_speed': 400, + }, + 'microchip_24aa02uid': { + 'vendor': 'Microchip', + 'model': '24AA02UID', + 'size': 256, + 'page_size': 8, + 'page_wraparound': True, + 'addr_bytes': 1, + 'addr_pins': 0, # Pins A0, A1, A2 not used + 'max_speed': 400, + }, + 'microchip_24aa025uid': { + 'vendor': 'Microchip', + 'model': '24AA025UID', + 'size': 256, + 'page_size': 16, + 'page_wraparound': True, + 'addr_bytes': 1, + 'addr_pins': 3, + 'max_speed': 400, + }, + 'microchip_24aa025uid_sot23': { + 'vendor': 'Microchip', + 'model': '24AA025UID (SOT-23)', + 'size': 256, + 'page_size': 16, + 'page_wraparound': True, + 'addr_bytes': 1, + 'addr_pins': 2, # SOT-23 package: A2 not available + 'max_speed': 400, + }, + + # Siemens + 'siemens_slx_24c01': { + 'vendor': 'Siemens', + 'model': 'SLx 24C01', + 'size': 128, + 'page_size': 8, + 'page_wraparound': True, + 'addr_bytes': 1, + 'addr_pins': 0, # Pins A0, A1, A2 are not connected (NC) + 'max_speed': 400, + }, + 'siemens_slx_24c02': { + 'vendor': 'Siemens', + 'model': 'SLx 24C02', + 'size': 256, + 'page_size': 8, + 'page_wraparound': True, + 'addr_bytes': 1, + 'addr_pins': 0, # Pins A0, A1, A2 are not connected (NC) + 'max_speed': 400, + }, + + # ST + 'st_m24c01': { + 'vendor': 'ST', + 'model': 'M24C01', + 'size': 128, + 'page_size': 16, + 'page_wraparound': True, + 'addr_bytes': 1, + 'addr_pins': 3, # Called E0, E1, E2 on this chip. + 'max_speed': 400, + }, + 'st_m24c02': { + 'vendor': 'ST', + 'model': 'M24C02', + 'size': 256, + 'page_size': 16, + 'page_wraparound': True, + 'addr_bytes': 1, + 'addr_pins': 3, # Called E0, E1, E2 on this chip. + 'max_speed': 400, + }, +} diff --git a/decoders/eeprom24xx/pd.py b/decoders/eeprom24xx/pd.py new file mode 100644 index 0000000..0738c06 --- /dev/null +++ b/decoders/eeprom24xx/pd.py @@ -0,0 +1,430 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 Uwe Hermann +## +## 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 +## + +import sigrokdecode as srd +from .lists import * + +class Decoder(srd.Decoder): + api_version = 2 + id = 'eeprom24xx' + name = '24xx EEPROM' + longname = '24xx I²C EEPROM' + desc = '24xx series I²C EEPROM protocol.' + license = 'gplv2+' + inputs = ['i2c'] + outputs = ['eeprom24xx'] + options = ( + {'id': 'chip', 'desc': 'Chip', 'default': 'generic', + 'values': tuple(chips.keys())}, + {'id': 'addr_counter', 'desc': 'Initial address counter value', + 'default': 0}, + ) + annotations = ( + # Warnings + ('warnings', 'Warnings'), + # Bits/bytes + ('control-code', 'Control code'), + ('address-pin', 'Address pin (A0/A1/A2)'), + ('rw-bit', 'Read/write bit'), + ('word-addr-byte', 'Word address byte'), + ('data-byte', 'Data byte'), + # Fields + ('control-word', 'Control word'), + ('word-addr', 'Word address'), + ('data', 'Data'), + # Operations + ('byte-write', 'Byte write'), + ('page-write', 'Page write'), + ('cur-addr-read', 'Current address read'), + ('random-read', 'Random read'), + ('seq-random-read', 'Sequential random read'), + ('seq-cur-addr-read', 'Sequential current address read'), + ('ack-polling', 'Acknowledge polling'), + ('set-bank-addr', 'Set bank address'), # SBA. Only 34AA04. + ('read-bank-addr', 'Read bank address'), # RBA. Only 34AA04. + ('set-wp', 'Set write protection'), # SWP + ('clear-all-wp', 'Clear all write protection'), # CWP + ('read-wp', 'Read write protection status'), # RPS + ) + annotation_rows = ( + ('bits-bytes', 'Bits/bytes', (1, 2, 3, 4, 5)), + ('fields', 'Fields', (6, 7, 8)), + ('ops', 'Operations', tuple(range(9, 21))), + ('warnings', 'Warnings', (0,)), + ) + binary = ( + ('binary', 'Binary'), + ) + + def __init__(self, **kwargs): + self.reset() + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + self.out_bin = self.register(srd.OUTPUT_BINARY) + self.chip = chips[self.options['chip']] + self.addr_counter = self.options['addr_counter'] + + def putb(self, data): + self.put(self.ss_block, self.es_block, self.out_ann, data) + + def putbin(self, data): + self.put(self.ss_block, self.es_block, self.out_bin, data) + + def putbits(self, bit1, bit2, bits, data): + self.put(bits[bit1][1], bits[bit2][2], self.out_ann, data) + + def reset(self): + self.state = 'WAIT FOR START' + self.packets = [] + self.bytebuf = [] + self.is_cur_addr_read = False + self.is_random_access_read = False + self.is_seq_random_read = False + self.is_byte_write = False + self.is_page_write = False + + def packet_append(self): + self.packets.append([self.ss, self.es, self.cmd, self.databyte, self.bits]) + if self.cmd in ('DATA READ', 'DATA WRITE'): + self.bytebuf.append(self.databyte) + + def hexbytes(self, idx): + return ' '.join(['%02X' % b for b in self.bytebuf[idx:]]) + + def put_control_word(self, bits): + s = ''.join(['%d' % b[0] for b in reversed(bits[4:])]) + self.putbits(7, 4, bits, [1, ['Control code bits: ' + s, + 'Control code: ' + s, 'Ctrl code: ' + s, 'Ctrl code', 'Ctrl', 'C']]) + for i in reversed(range(self.chip['addr_pins'])): + self.putbits(i + 1, i + 1, bits, + [2, ['Address bit %d: %d' % (i, bits[i + 1][0]), + 'Addr bit %d' % i, 'A%d' % i, 'A']]) + s1 = 'read' if bits[0][0] == 1 else 'write' + s2 = 'R' if bits[0][0] == 1 else 'W' + self.putbits(0, 0, bits, [3, ['R/W bit: ' + s1, 'R/W', 'RW', s2]]) + self.putbits(7, 0, bits, [6, ['Control word', 'Control', 'CW', 'C']]) + + def put_word_addr(self, p): + if self.chip['addr_bytes'] == 1: + a = p[1][3] + self.put(p[1][0], p[1][1], self.out_ann, + [4, ['Word address byte: %02X' % a, 'Word addr byte: %02X' % a, + 'Addr: %02X' % a, 'A: %02X' % a, '%02X' % a]]) + self.put(p[1][0], p[1][1], self.out_ann, [7, ['Word address', + 'Word addr', 'Addr', 'A']]) + self.addr_counter = a + else: + a = p[1][3] + self.put(p[1][0], p[1][1], self.out_ann, + [4, ['Word address high byte: %02X' % a, + 'Word addr high byte: %02X' % a, + 'Addr high: %02X' % a, 'AH: %02X' % a, '%02X' % a]]) + a = p[2][3] + self.put(p[2][0], p[2][1], self.out_ann, + [4, ['Word address low byte: %02X' % a, + 'Word addr low byte: %02X' % a, + 'Addr low: %02X' % a, 'AL: %02X' % a, '%02X' % a]]) + self.put(p[1][0], p[2][1], self.out_ann, [7, ['Word address', + 'Word addr', 'Addr', 'A']]) + self.addr_counter = (p[1][3] << 8) | p[2][3] + + def put_data_byte(self, p): + if self.chip['addr_bytes'] == 1: + s = '%02X' % self.addr_counter + else: + s = '%04X' % self.addr_counter + self.put(p[0], p[1], self.out_ann, [5, ['Data byte %s: %02X' % \ + (s, p[3]), 'Data byte: %02X' % p[3], \ + 'Byte: %02X' % p[3], 'DB: %02X' % p[3], '%02X' % p[3]]]) + + def put_data_bytes(self, idx, cls, s): + for p in self.packets[idx:]: + self.put_data_byte(p) + self.addr_counter += 1 + self.put(self.packets[idx][0], self.packets[-1][1], self.out_ann, + [8, ['Data', 'D']]) + a = ''.join(['%s' % c[0] for c in s.split()]).upper() + self.putb([cls, ['%s (%s): %s' % (s, self.addr_and_len(), \ + self.hexbytes(self.chip['addr_bytes'])), + '%s (%s)' % (s, self.addr_and_len()), s, a, s[0]]]) + self.putbin((0, bytes(self.bytebuf[self.chip['addr_bytes']:]))) + + def addr_and_len(self): + if self.chip['addr_bytes'] == 1: + a = '%02X' % self.bytebuf[0] + else: + a = '%02X%02X' % tuple(self.bytebuf[:2]) + num_data_bytes = len(self.bytebuf) - self.chip['addr_bytes'] + d = '%d bytes' % num_data_bytes + if num_data_bytes <= 1: + d = d[:-1] + return 'addr=%s, %s' % (a, d) + + def decide_on_seq_or_rnd_read(self): + if len(self.bytebuf) < 2: + self.reset() + return + if len(self.bytebuf) == 2: + self.is_random_access_read = True + else: + self.is_seq_random_read = True + + def put_operation(self): + idx = 1 + self.chip['addr_bytes'] + if self.is_byte_write: + # Byte write: word address, one data byte. + self.put_word_addr(self.packets) + self.put_data_bytes(idx, 9, 'Byte write') + elif self.is_page_write: + # Page write: word address, two or more data bytes. + self.put_word_addr(self.packets) + intitial_addr = self.addr_counter + self.put_data_bytes(idx, 10, 'Page write') + num_bytes_to_write = len(self.packets[idx:]) + if num_bytes_to_write > self.chip['page_size']: + self.putb([0, ['Warning: Wrote %d bytes but page size is ' + 'only %d bytes!' % (num_bytes_to_write, + self.chip['page_size'])]]) + page1 = int(intitial_addr / self.chip['page_size']) + page2 = int((self.addr_counter - 1) / self.chip['page_size']) + if page1 != page2: + self.putb([0, ['Warning: Page write crossed page boundary ' + 'from page %d to %d!' % (page1, page2)]]) + elif self.is_cur_addr_read: + # Current address read: no word address, one data byte. + self.put_data_byte(self.packets[1]) + self.put(self.packets[1][0], self.packets[-1][1], self.out_ann, + [8, ['Data', 'D']]) + self.putb([11, ['Current address read: %02X' % self.bytebuf[0], + 'Current address read', 'Cur addr read', 'CAR', 'C']]) + self.putbin((0, bytes([self.bytebuf[0]]))) + self.addr_counter += 1 + elif self.is_random_access_read: + # Random access read: word address, one data byte. + self.put_control_word(self.packets[idx][4]) + self.put_word_addr(self.packets) + self.put_data_bytes(idx + 1, 12, 'Random access read') + elif self.is_seq_random_read: + # Sequential random read: word address, two or more data bytes. + self.put_control_word(self.packets[idx][4]) + self.put_word_addr(self.packets) + self.put_data_bytes(idx + 1, 13, 'Sequential random read') + + def handle_wait_for_start(self): + # Wait for an I²C START condition. + if self.cmd not in ('START', 'START REPEAT'): + return + self.ss_block = self.ss + self.state = 'GET CONTROL WORD' + + def handle_get_control_word(self): + # The packet after START must be an ADDRESS READ or ADDRESS WRITE. + if self.cmd not in ('ADDRESS READ', 'ADDRESS WRITE'): + self.reset() + return + self.packet_append() + self.put_control_word(self.bits) + self.state = '%s GET ACK NACK AFTER CONTROL WORD' % self.cmd[8] + + def handle_r_get_ack_nack_after_control_word(self): + if self.cmd == 'ACK': + self.state = 'R GET WORD ADDR OR BYTE' + elif self.cmd == 'NACK': + self.es_block = self.es + self.putb([0, ['Warning: No reply from slave!']]) + self.reset() + else: + self.reset() + + def handle_r_get_word_addr_or_byte(self): + if self.cmd == 'STOP': + self.es_block = self.es + self.putb([0, ['Warning: Slave replied, but master aborted!']]) + self.reset() + return + elif self.cmd != 'DATA READ': + self.reset() + return + self.packet_append() + self.state = 'R GET ACK NACK AFTER WORD ADDR OR BYTE' + + def handle_r_get_ack_nack_after_word_addr_or_byte(self): + if self.cmd == 'ACK': + self.state = 'R GET RESTART' + elif self.cmd == 'NACK': + self.is_cur_addr_read = True + self.state = 'GET STOP AFTER LAST BYTE' + else: + self.reset() + + def handle_r_get_restart(self): + if self.cmd == 'RESTART': + self.state = 'R READ BYTE' + else: + self.reset() + + def handle_r_read_byte(self): + if self.cmd == 'DATA READ': + self.packet_append() + self.state = 'R GET ACK NACK AFTER BYTE WAS READ' + else: + self.reset() + + def handle_r_get_ack_nack_after_byte_was_read(self): + if self.cmd == 'ACK': + self.state = 'R READ BYTE' + elif self.cmd == 'NACK': + # It's either a RANDOM READ or a SEQUENTIAL READ. + self.state = 'GET STOP AFTER LAST BYTE' + else: + self.reset() + + def handle_w_get_ack_nack_after_control_word(self): + if self.cmd == 'ACK': + self.state = 'W GET WORD ADDR' + elif self.cmd == 'NACK': + self.es_block = self.es + self.putb([0, ['Warning: No reply from slave!']]) + self.reset() + else: + self.reset() + + def handle_w_get_word_addr(self): + if self.cmd == 'STOP': + self.es_block = self.es + self.putb([0, ['Warning: Slave replied, but master aborted!']]) + self.reset() + return + elif self.cmd != 'DATA WRITE': + self.reset() + return + self.packet_append() + self.state = 'W GET ACK AFTER WORD ADDR' + + def handle_w_get_ack_after_word_addr(self): + if self.cmd == 'ACK': + self.state = 'W DETERMINE EEPROM READ OR WRITE' + else: + self.reset() + + def handle_w_determine_eeprom_read_or_write(self): + if self.cmd == 'START REPEAT': + # It's either a RANDOM ACCESS READ or SEQUENTIAL RANDOM READ. + self.state = 'R2 GET CONTROL WORD' + elif self.cmd == 'DATA WRITE': + self.packet_append() + self.state = 'W GET ACK NACK AFTER BYTE WAS WRITTEN' + else: + self.reset() + + def handle_w_write_byte(self): + if self.cmd == 'DATA WRITE': + self.packet_append() + self.state = 'W GET ACK NACK AFTER BYTE WAS WRITTEN' + elif self.cmd == 'STOP': + if len(self.bytebuf) < 2: + self.reset() + return + self.es_block = self.es + if len(self.bytebuf) == 2: + self.is_byte_write = True + else: + self.is_page_write = True + self.put_operation() + self.reset() + elif self.cmd == 'START REPEAT': + # It's either a RANDOM ACCESS READ or SEQUENTIAL RANDOM READ. + self.state = 'R2 GET CONTROL WORD' + else: + self.reset() + + def handle_w_get_ack_nack_after_byte_was_written(self): + if self.cmd == 'ACK': + self.state = 'W WRITE BYTE' + else: + self.reset() + + def handle_r2_get_control_word(self): + if self.cmd == 'ADDRESS READ': + self.packet_append() + self.state = 'R2 GET ACK AFTER ADDR READ' + else: + self.reset() + + def handle_r2_get_ack_after_addr_read(self): + if self.cmd == 'ACK': + self.state = 'R2 READ BYTE' + else: + self.reset() + + def handle_r2_read_byte(self): + if self.cmd == 'DATA READ': + self.packet_append() + self.state = 'R2 GET ACK NACK AFTER BYTE WAS READ' + elif self.cmd == 'STOP': + self.decide_on_seq_or_rnd_read() + self.es_block = self.es + self.putb([0, ['Warning: STOP expected after a NACK (not ACK)']]) + self.put_operation() + self.reset() + else: + self.reset() + + def handle_r2_get_ack_nack_after_byte_was_read(self): + if self.cmd == 'ACK': + self.state = 'R2 READ BYTE' + elif self.cmd == 'NACK': + self.decide_on_seq_or_rnd_read() + self.state = 'GET STOP AFTER LAST BYTE' + else: + self.reset() + + def handle_get_stop_after_last_byte(self): + if self.cmd == 'STOP': + self.es_block = self.es + self.put_operation() + self.reset() + elif self.cmd == 'START REPEAT': + self.es_block = self.es + self.putb([0, ['Warning: STOP expected (not RESTART)']]) + self.put_operation() + self.reset() + self.ss_block = self.ss + self.state = 'GET CONTROL WORD' + else: + self.reset() + + def decode(self, ss, es, data): + self.cmd, self.databyte = data + + # Collect the 'BITS' packet, then return. The next packet is + # guaranteed to belong to these bits we just stored. + if self.cmd == 'BITS': + self.bits = self.databyte + return + + # Store the start/end samples of this I²C packet. + self.ss, self.es = ss, es + + # State machine. + s = 'handle_%s' % self.state.lower().replace(' ', '_') + handle_state = getattr(self, s) + handle_state()