Fix bugs in I2C EEPROM. Auto detect page size.

pull/24/head
Peter Hinch 2024-01-09 14:38:53 +00:00
rodzic 4a8c00bf98
commit ab0adab7d8
4 zmienionych plików z 123 dodań i 44 usunięć

Wyświetl plik

@ -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 :])

Wyświetl plik

@ -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

Wyświetl plik

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

Wyświetl plik

@ -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