diff --git a/bdevice.py b/bdevice.py index 803f6c7..285fd66 100644 --- a/bdevice.py +++ b/bdevice.py @@ -34,9 +34,7 @@ class BlockDevice: # Handle special cases of a slice. Always return a pair of positive indices. def _do_slice(self, addr): if not (addr.step is None or addr.step == 1): - raise NotImplementedError( - "only slices with step=1 (aka None) are supported" - ) + raise NotImplementedError("only slices with step=1 (aka None) are supported") start = addr.start if addr.start is not None else 0 stop = addr.stop if addr.stop is not None else self._a_bytes start = start if start >= 0 else self._a_bytes + start @@ -82,6 +80,41 @@ class BlockDevice: return 0 +class EepromDevice(BlockDevice): + def __init__(self, nbits, nchips, chip_size, page_size, verbose): + super().__init__(nbits, nchips, chip_size) + # Handle page size arg + if page_size not in (None, 16, 32, 64, 128, 256): + raise ValueError(f"Invalid page size: {page_size}") + self._set_pagesize(page_size) # Set page size + verbose and print("Page size:", self._page_size) + + def _psize(self, ps): # Set page size and page mask + self._page_size = ps + self._page_mask = ~(ps - 1) + + def get_page_size(self): # For test script + return self._page_size + + def _set_pagesize(self, page_size): + if page_size is None: # Measure it + self._psize(16) # Conservative + old = self[:129] # Save old contents (nonvolatile!) + self._psize(256) # Ambitious + r = (16, 32, 64, 128) # Legal page sizes + 256 + for x in r: + self[x] = 255 # Write single bytes, don't invoke page write + self[0:129] = b"\0" * 129 # Zero 129 bytes attempting to use 256 byte pages + try: + ps = next(z for z in r if self[z]) + except StopIteration: + ps = 256 + self._psize(ps) + self[:129] = old + else: # Validated page_size was supplied + self._psize(page_size) + + # Hardware agnostic base class for flash memory. _RDBUFSIZE = const(32) # Size of read buffer for erasure test @@ -118,9 +151,7 @@ class FlashDevice(BlockDevice): boff += nr # addr now >= self._acache: read from cache. sa = addr - self._acache # Offset into cache - nr = min( - nbytes, self._acache + self.sec_size - addr - ) # No of bytes to read from cache + nr = min(nbytes, self._acache + self.sec_size - addr) # No of bytes to read from cache mvb[boff : boff + nr] = self._mvd[sa : sa + nr] if nbytes - nr: # Get any remaining data from chip self.rdchip(addr + nr, mvb[boff + nr :]) diff --git a/eeprom/i2c/I2C.md b/eeprom/i2c/I2C.md index 04a7afc..c3cab09 100644 --- a/eeprom/i2c/I2C.md +++ b/eeprom/i2c/I2C.md @@ -43,8 +43,9 @@ EEPROM Pin numbers assume a PDIP package (8 pin plastic dual-in-line). | 8 Vcc | 3V3 | 3V3 | For multiple chips the address lines A0, A1 and A2 of each chip need to be -wired to 3V3 in such a way as to give each device a unique address. These must -start at zero and be contiguous: +wired to 3V3 in such a way as to give each device a unique address. In the case +where chips are to form a single array these must start at zero and be +contiguous: | Chip no. | A2 | A1 | A0 | |:--------:|:---:|:---:|:---:| @@ -130,26 +131,37 @@ Arguments: devices it has detected. 4. `block_size=9` The block size reported to the filesystem. The size in bytes is `2**block_size` so is 512 bytes by default. - 5. `addr` override base address for first chip - 6. `max_chips_count` override max_chips_count - 7. `page_size=7` The binary logarithm of the page size of the EEPROMs, i.e., the page size in bytes is `2 ** page_size`. The page size may vary between chips from different manufacturers even for the same storage size. Note that specifying a too large value will most likely lead to data corruption in write operations. Look up the correct value for your setup in the chip's datasheet. + 5. `addr` override base address for first chip. + 6. `max_chips_count` override max_chips_count. + 7. `page_size=None` EEPROM chips have a page buffer. By default the driver + determines the size of this automatically. It is possible to override this by + passing an integer being the page size in bytes: 16, 32, 64, 128 or 256. The + page size may vary between chips from different manufacturers even for the + same storage size. Note that specifying too large a value will most likely lead + to data corruption in write operations and will cause the test script's basic + test to fail. The correct value for a device may be found in in the chip + datasheet. It is also reported if `verbose` is set. Auto-detecting page size + carries a risk of data loss if power fails while auto-detect is in progress. - With `addr` and `max_chips_count` override, it's possible to make multiple - configuration - - example: - - array with custom chips count: +In most cases only the first two arguments are used, with an array being +instantiated with (for example): +```python +from machine import I2C +from eeprom_i2c import EEPROM, T24C512 +eep = EEPROM(I2C(2), T24C512) +``` +It is possible to configure multiple chips as multiple arrays. This is done by +means of the `addr` and `max_chips_count` args. Examples: ```python - eeprom0 = EEPROM( i2c, max_chips_count=2 ) - eeprom1 = EEPROM( i2c, addr=0x52, max_chips_count=2 ) + eeprom0 = EEPROM(i2c, max_chips_count = 2) + eeprom1 = EEPROM(i2c, addr = 0x52, max_chips_count = 2) ``` - 1st array using address 0x50 and 0x51 and 2nd using array address 0x52 and 0x53. + 1st array uses address 0x50 and 0x51 and 2nd uses address 0x52 and 0x53. individual chip usage: ```python - eeprom0 = EEPROM( i2c, addr=0x50, max_chips_count=1 ) - eeprom1 = EEPROM( i2c, addr=0x51, max_chips_count=1 ) + eeprom0 = EEPROM(i2c, addr = 0x50, max_chips_count = 1) + eeprom1 = EEPROM(i2c, addr = 0x51, max_chips_count = 1) ``` ### 4.1.2 Methods providing byte level access diff --git a/eeprom/i2c/eep_i2c.py b/eeprom/i2c/eep_i2c.py index d05030a..5428846 100644 --- a/eeprom/i2c/eep_i2c.py +++ b/eeprom/i2c/eep_i2c.py @@ -1,7 +1,7 @@ # eep_i2c.py MicroPython test program for Microchip I2C EEPROM devices. # Released under the MIT License (MIT). See LICENSE. -# Copyright (c) 2019 Peter Hinch +# Copyright (c) 2019-2024 Peter Hinch import uos import time @@ -23,13 +23,19 @@ def get_eep(): def cp(source, dest): if dest.endswith("/"): # minimal way to allow dest = "".join((dest, source.split("/")[-1])) # cp /sd/file /eeprom/ - with open(source, "rb") as infile: # Caller should handle any OSError - with open(dest, "wb") as outfile: # e.g file not found - while True: - buf = infile.read(100) - outfile.write(buf) - if len(buf) < 100: - break + try: + with open(source, "rb") as infile: # Caller should handle any OSError + with open(dest, "wb") as outfile: # e.g file not found + while True: + buf = infile.read(100) + outfile.write(buf) + if len(buf) < 100: + break + except OSError as e: + if e.errno == 28: + print("Insufficient space for copy.") + else: + raise # ***** TEST OF DRIVER ***** @@ -94,6 +100,14 @@ def test(eep=None): print(res) else: print("Test chip boundary skipped: only one chip!") + pe = eep.get_page_size() + 1 # One byte past page + eep[pe] = 0xFF + eep[:257] = b"\0" * 257 + print("Test page size: ", end="") + if eep[pe]: + print("FAIL") + else: + print("passed") # ***** TEST OF FILESYSTEM MOUNT ***** @@ -149,3 +163,16 @@ def full_test(eep=None, block_size=128): else: print("Page {} readback failed.".format(page)) page += 1 + + +help = """Available tests: +test() Basic fuctional test +full_test() Read-write test of EEPROM chip(s) +fstest() Check or create a filesystem. +cptest() Check a filesystem by copying source files to it. + +Utilities: +get_eep() Initialise and return an EEPROM instance. +cp() Very crude file copy utility. +""" +print(help) diff --git a/eeprom/i2c/eeprom_i2c.py b/eeprom/i2c/eeprom_i2c.py index c64a7c6..075a08f 100644 --- a/eeprom/i2c/eeprom_i2c.py +++ b/eeprom/i2c/eeprom_i2c.py @@ -1,11 +1,13 @@ # eeprom_i2c.py MicroPython driver for Microchip I2C EEPROM devices. # Released under the MIT License (MIT). See LICENSE. -# Copyright (c) 2019 Peter Hinch +# Copyright (c) 2019-2024 Peter Hinch + +# Thanks are due to Abel Deuring for help in diagnosing and fixing a page size issue. import time from micropython import const -from bdevice import BlockDevice +from bdevice import EepromDevice _ADDR = const(0x50) # Base address of chip _MAX_CHIPS_COUNT = const(8) # Max number of chips @@ -18,19 +20,28 @@ T24C32 = const(4096) # 4KiB 32Kbits # Logical EEPROM device consists of 1-8 physical chips. Chips must all be the # same size, and must have contiguous addresses. -class EEPROM(BlockDevice): - def __init__(self, i2c, chip_size=T24C512, verbose=True, block_size=9, addr=_ADDR, max_chips_count=_MAX_CHIPS_COUNT, page_size=7): +class EEPROM(EepromDevice): + def __init__( + self, + i2c, + chip_size=T24C512, + verbose=True, + block_size=9, + addr=_ADDR, + max_chips_count=_MAX_CHIPS_COUNT, + page_size=None, + ): self._i2c = i2c if chip_size not in (T24C32, T24C64, T24C128, T24C256, T24C512): print("Warning: possible unsupported chip. Size:", chip_size) - nchips, min_chip_address = self.scan(verbose, chip_size, addr, max_chips_count) # No. of EEPROM chips - super().__init__(block_size, nchips, chip_size) + # Get no. of EEPROM chips + nchips, min_chip_address = self.scan(verbose, chip_size, addr, max_chips_count) self._min_chip_address = min_chip_address self._i2c_addr = 0 # I2C address of current chip self._buf1 = bytearray(1) self._addrbuf = bytearray(2) # Memory offset into current chip - self._page_size = 2 ** page_size - self._page_mask = ~(self._page_size - 1) + # superclass figures out _page_size and _page_mask + super().__init__(block_size, nchips, chip_size, page_size, verbose) # Check for a valid hardware configuration def scan(self, verbose, chip_size, addr, max_chips_count): @@ -41,9 +52,9 @@ class EEPROM(BlockDevice): raise RuntimeError("EEPROM not found.") eeproms = sorted(eeproms) if len(set(eeproms)) != len(eeproms): - raise RuntimeError('Duplicate addresses were found', eeproms) + raise RuntimeError("Duplicate addresses were found", eeproms) if (eeproms[-1] - eeproms[0] + 1) != len(eeproms): - raise RuntimeError('Non-contiguous chip addresses', eeproms) + raise RuntimeError("Non-contiguous chip addresses", eeproms) if verbose: s = "{} chips detected. Total EEPROM size {}bytes." print(s.format(nchips, chip_size * nchips)) @@ -69,7 +80,7 @@ class EEPROM(BlockDevice): self._addrbuf[0] = (la >> 8) & 0xFF self._addrbuf[1] = la & 0xFF self._i2c_addr = self._min_chip_address + ca - pe = (addr & self._page_mask) + self._page_size # byte 0 of next page + pe = (la & self._page_mask) + self._page_size # byte 0 of next page return min(nbytes, pe - la) # Read or write multiple bytes at an arbitrary address @@ -84,9 +95,7 @@ class EEPROM(BlockDevice): self._i2c.writeto(self._i2c_addr, self._addrbuf) self._i2c.readfrom_into(self._i2c_addr, mvb[start : start + npage]) else: - self._i2c.writevto( - self._i2c_addr, (self._addrbuf, buf[start : start + npage]) - ) + self._i2c.writevto(self._i2c_addr, (self._addrbuf, buf[start : start + npage])) self._wait_rdy() nbytes -= npage start += npage