Skip to content

Commit ab0adab

Browse files
committed
Fix bugs in I2C EEPROM. Auto detect page size.
1 parent 4a8c00b commit ab0adab

File tree

4 files changed

+124
-45
lines changed

4 files changed

+124
-45
lines changed

bdevice.py

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,7 @@ def __getitem__(self, addr):
3434
# Handle special cases of a slice. Always return a pair of positive indices.
3535
def _do_slice(self, addr):
3636
if not (addr.step is None or addr.step == 1):
37-
raise NotImplementedError(
38-
"only slices with step=1 (aka None) are supported"
39-
)
37+
raise NotImplementedError("only slices with step=1 (aka None) are supported")
4038
start = addr.start if addr.start is not None else 0
4139
stop = addr.stop if addr.stop is not None else self._a_bytes
4240
start = start if start >= 0 else self._a_bytes + start
@@ -82,6 +80,41 @@ def ioctl(self, op, arg): # ioctl calls: see extmod/vfs.h
8280
return 0
8381

8482

83+
class EepromDevice(BlockDevice):
84+
def __init__(self, nbits, nchips, chip_size, page_size, verbose):
85+
super().__init__(nbits, nchips, chip_size)
86+
# Handle page size arg
87+
if page_size not in (None, 16, 32, 64, 128, 256):
88+
raise ValueError(f"Invalid page size: {page_size}")
89+
self._set_pagesize(page_size) # Set page size
90+
verbose and print("Page size:", self._page_size)
91+
92+
def _psize(self, ps): # Set page size and page mask
93+
self._page_size = ps
94+
self._page_mask = ~(ps - 1)
95+
96+
def get_page_size(self): # For test script
97+
return self._page_size
98+
99+
def _set_pagesize(self, page_size):
100+
if page_size is None: # Measure it
101+
self._psize(16) # Conservative
102+
old = self[:129] # Save old contents (nonvolatile!)
103+
self._psize(256) # Ambitious
104+
r = (16, 32, 64, 128) # Legal page sizes + 256
105+
for x in r:
106+
self[x] = 255 # Write single bytes, don't invoke page write
107+
self[0:129] = b"\0" * 129 # Zero 129 bytes attempting to use 256 byte pages
108+
try:
109+
ps = next(z for z in r if self[z])
110+
except StopIteration:
111+
ps = 256
112+
self._psize(ps)
113+
self[:129] = old
114+
else: # Validated page_size was supplied
115+
self._psize(page_size)
116+
117+
85118
# Hardware agnostic base class for flash memory.
86119

87120
_RDBUFSIZE = const(32) # Size of read buffer for erasure test
@@ -118,9 +151,7 @@ def read(self, addr, mvb):
118151
boff += nr
119152
# addr now >= self._acache: read from cache.
120153
sa = addr - self._acache # Offset into cache
121-
nr = min(
122-
nbytes, self._acache + self.sec_size - addr
123-
) # No of bytes to read from cache
154+
nr = min(nbytes, self._acache + self.sec_size - addr) # No of bytes to read from cache
124155
mvb[boff : boff + nr] = self._mvd[sa : sa + nr]
125156
if nbytes - nr: # Get any remaining data from chip
126157
self.rdchip(addr + nr, mvb[boff + nr :])

eeprom/i2c/I2C.md

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,9 @@ EEPROM Pin numbers assume a PDIP package (8 pin plastic dual-in-line).
4343
| 8 Vcc | 3V3 | 3V3 |
4444

4545
For multiple chips the address lines A0, A1 and A2 of each chip need to be
46-
wired to 3V3 in such a way as to give each device a unique address. These must
47-
start at zero and be contiguous:
46+
wired to 3V3 in such a way as to give each device a unique address. In the case
47+
where chips are to form a single array these must start at zero and be
48+
contiguous:
4849

4950
| Chip no. | A2 | A1 | A0 |
5051
|:--------:|:---:|:---:|:---:|
@@ -130,26 +131,37 @@ Arguments:
130131
devices it has detected.
131132
4. `block_size=9` The block size reported to the filesystem. The size in bytes
132133
is `2**block_size` so is 512 bytes by default.
133-
5. `addr` override base address for first chip
134-
6. `max_chips_count` override max_chips_count
135-
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.
136-
137-
With `addr` and `max_chips_count` override, it's possible to make multiple
138-
configuration
139-
140-
example:
141-
142-
array with custom chips count:
134+
5. `addr` override base address for first chip.
135+
6. `max_chips_count` override max_chips_count.
136+
7. `page_size=None` EEPROM chips have a page buffer. By default the driver
137+
determines the size of this automatically. It is possible to override this by
138+
passing an integer being the page size in bytes: 16, 32, 64, 128 or 256. The
139+
page size may vary between chips from different manufacturers even for the
140+
same storage size. Note that specifying too large a value will most likely lead
141+
to data corruption in write operations and will cause the test script's basic
142+
test to fail. The correct value for a device may be found in in the chip
143+
datasheet. It is also reported if `verbose` is set. Auto-detecting page size
144+
carries a risk of data loss if power fails while auto-detect is in progress.
145+
146+
In most cases only the first two arguments are used, with an array being
147+
instantiated with (for example):
148+
```python
149+
from machine import I2C
150+
from eeprom_i2c import EEPROM, T24C512
151+
eep = EEPROM(I2C(2), T24C512)
152+
```
153+
It is possible to configure multiple chips as multiple arrays. This is done by
154+
means of the `addr` and `max_chips_count` args. Examples:
143155
```python
144-
eeprom0 = EEPROM( i2c, max_chips_count=2 )
145-
eeprom1 = EEPROM( i2c, addr=0x52, max_chips_count=2 )
156+
eeprom0 = EEPROM(i2c, max_chips_count = 2)
157+
eeprom1 = EEPROM(i2c, addr = 0x52, max_chips_count = 2)
146158
```
147-
1st array using address 0x50 and 0x51 and 2nd using array address 0x52 and 0x53.
159+
1st array uses address 0x50 and 0x51 and 2nd uses address 0x52 and 0x53.
148160

149161
individual chip usage:
150162
```python
151-
eeprom0 = EEPROM( i2c, addr=0x50, max_chips_count=1 )
152-
eeprom1 = EEPROM( i2c, addr=0x51, max_chips_count=1 )
163+
eeprom0 = EEPROM(i2c, addr = 0x50, max_chips_count = 1)
164+
eeprom1 = EEPROM(i2c, addr = 0x51, max_chips_count = 1)
153165
```
154166

155167
### 4.1.2 Methods providing byte level access

eeprom/i2c/eep_i2c.py

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# eep_i2c.py MicroPython test program for Microchip I2C EEPROM devices.
22

33
# Released under the MIT License (MIT). See LICENSE.
4-
# Copyright (c) 2019 Peter Hinch
4+
# Copyright (c) 2019-2024 Peter Hinch
55

66
import uos
77
import time
@@ -23,13 +23,19 @@ def get_eep():
2323
def cp(source, dest):
2424
if dest.endswith("/"): # minimal way to allow
2525
dest = "".join((dest, source.split("/")[-1])) # cp /sd/file /eeprom/
26-
with open(source, "rb") as infile: # Caller should handle any OSError
27-
with open(dest, "wb") as outfile: # e.g file not found
28-
while True:
29-
buf = infile.read(100)
30-
outfile.write(buf)
31-
if len(buf) < 100:
32-
break
26+
try:
27+
with open(source, "rb") as infile: # Caller should handle any OSError
28+
with open(dest, "wb") as outfile: # e.g file not found
29+
while True:
30+
buf = infile.read(100)
31+
outfile.write(buf)
32+
if len(buf) < 100:
33+
break
34+
except OSError as e:
35+
if e.errno == 28:
36+
print("Insufficient space for copy.")
37+
else:
38+
raise
3339

3440

3541
# ***** TEST OF DRIVER *****
@@ -94,6 +100,14 @@ def test(eep=None):
94100
print(res)
95101
else:
96102
print("Test chip boundary skipped: only one chip!")
103+
pe = eep.get_page_size() + 1 # One byte past page
104+
eep[pe] = 0xFF
105+
eep[:257] = b"\0" * 257
106+
print("Test page size: ", end="")
107+
if eep[pe]:
108+
print("FAIL")
109+
else:
110+
print("passed")
97111

98112

99113
# ***** TEST OF FILESYSTEM MOUNT *****
@@ -149,3 +163,16 @@ def full_test(eep=None, block_size=128):
149163
else:
150164
print("Page {} readback failed.".format(page))
151165
page += 1
166+
167+
168+
help = """Available tests:
169+
test() Basic fuctional test
170+
full_test() Read-write test of EEPROM chip(s)
171+
fstest() Check or create a filesystem.
172+
cptest() Check a filesystem by copying source files to it.
173+
174+
Utilities:
175+
get_eep() Initialise and return an EEPROM instance.
176+
cp() Very crude file copy utility.
177+
"""
178+
print(help)

eeprom/i2c/eeprom_i2c.py

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
# eeprom_i2c.py MicroPython driver for Microchip I2C EEPROM devices.
22

33
# Released under the MIT License (MIT). See LICENSE.
4-
# Copyright (c) 2019 Peter Hinch
4+
# Copyright (c) 2019-2024 Peter Hinch
5+
6+
# Thanks are due to Abel Deuring for help in diagnosing and fixing a page size issue.
57

68
import time
79
from micropython import const
8-
from bdevice import BlockDevice
10+
from bdevice import EepromDevice
911

1012
_ADDR = const(0x50) # Base address of chip
1113
_MAX_CHIPS_COUNT = const(8) # Max number of chips
@@ -18,19 +20,28 @@
1820

1921
# Logical EEPROM device consists of 1-8 physical chips. Chips must all be the
2022
# same size, and must have contiguous addresses.
21-
class EEPROM(BlockDevice):
22-
def __init__(self, i2c, chip_size=T24C512, verbose=True, block_size=9, addr=_ADDR, max_chips_count=_MAX_CHIPS_COUNT, page_size=7):
23+
class EEPROM(EepromDevice):
24+
def __init__(
25+
self,
26+
i2c,
27+
chip_size=T24C512,
28+
verbose=True,
29+
block_size=9,
30+
addr=_ADDR,
31+
max_chips_count=_MAX_CHIPS_COUNT,
32+
page_size=None,
33+
):
2334
self._i2c = i2c
2435
if chip_size not in (T24C32, T24C64, T24C128, T24C256, T24C512):
2536
print("Warning: possible unsupported chip. Size:", chip_size)
26-
nchips, min_chip_address = self.scan(verbose, chip_size, addr, max_chips_count) # No. of EEPROM chips
27-
super().__init__(block_size, nchips, chip_size)
37+
# Get no. of EEPROM chips
38+
nchips, min_chip_address = self.scan(verbose, chip_size, addr, max_chips_count)
2839
self._min_chip_address = min_chip_address
2940
self._i2c_addr = 0 # I2C address of current chip
3041
self._buf1 = bytearray(1)
3142
self._addrbuf = bytearray(2) # Memory offset into current chip
32-
self._page_size = 2 ** page_size
33-
self._page_mask = ~(self._page_size - 1)
43+
# superclass figures out _page_size and _page_mask
44+
super().__init__(block_size, nchips, chip_size, page_size, verbose)
3445

3546
# Check for a valid hardware configuration
3647
def scan(self, verbose, chip_size, addr, max_chips_count):
@@ -41,9 +52,9 @@ def scan(self, verbose, chip_size, addr, max_chips_count):
4152
raise RuntimeError("EEPROM not found.")
4253
eeproms = sorted(eeproms)
4354
if len(set(eeproms)) != len(eeproms):
44-
raise RuntimeError('Duplicate addresses were found', eeproms)
55+
raise RuntimeError("Duplicate addresses were found", eeproms)
4556
if (eeproms[-1] - eeproms[0] + 1) != len(eeproms):
46-
raise RuntimeError('Non-contiguous chip addresses', eeproms)
57+
raise RuntimeError("Non-contiguous chip addresses", eeproms)
4758
if verbose:
4859
s = "{} chips detected. Total EEPROM size {}bytes."
4960
print(s.format(nchips, chip_size * nchips))
@@ -69,7 +80,7 @@ def _getaddr(self, addr, nbytes): # Set up _addrbuf and _i2c_addr
6980
self._addrbuf[0] = (la >> 8) & 0xFF
7081
self._addrbuf[1] = la & 0xFF
7182
self._i2c_addr = self._min_chip_address + ca
72-
pe = (addr & self._page_mask) + self._page_size # byte 0 of next page
83+
pe = (la & self._page_mask) + self._page_size # byte 0 of next page
7384
return min(nbytes, pe - la)
7485

7586
# Read or write multiple bytes at an arbitrary address
@@ -84,9 +95,7 @@ def readwrite(self, addr, buf, read):
8495
self._i2c.writeto(self._i2c_addr, self._addrbuf)
8596
self._i2c.readfrom_into(self._i2c_addr, mvb[start : start + npage])
8697
else:
87-
self._i2c.writevto(
88-
self._i2c_addr, (self._addrbuf, buf[start : start + npage])
89-
)
98+
self._i2c.writevto(self._i2c_addr, (self._addrbuf, buf[start : start + npage]))
9099
self._wait_rdy()
91100
nbytes -= npage
92101
start += npage

0 commit comments

Comments
 (0)