Porównaj commity

...

4 Commity

Autor SHA1 Wiadomość Data
Peter Hinch 75e8311ffb EEPROM: spi presence detect address randomisation. 2024-01-17 09:34:08 +00:00
Peter Hinch 5861e93f48 EEPROM: SPI and I2C full_test now more rigorous. 2024-01-15 12:05:21 +00:00
Peter Hinch 3b2e2530ef EEPROM: Add release notes. 2024-01-14 13:56:07 +00:00
Peter Hinch 2a2ceab903 EEPROM: Support i2c 2KiB eeproms, also MIP install. 2024-01-14 11:28:15 +00:00
9 zmienionych plików z 231 dodań i 203 usunięć

Wyświetl plik

@ -1,7 +1,8 @@
# 1. A MicroPython I2C EEPROM driver
This driver supports chips from the 64KiB 25xx512 series and related chips with
smaller capacities.
smaller capacities, now including chips as small as 2KiB with single byte
addressing.
From one to eight chips may be used to construct a nonvolatile memory module
with sizes upto 512KiB. The driver allows the memory either to be mounted in
@ -20,6 +21,14 @@ the subsequent improvements to MicroPython to achieve these advantages:
7. Alternatively it can support byte-level access using Python slice syntax.
8. RAM allocations are reduced.
## 1.1 Release Notes
January 2024 Fixes a bug whereby incorrect page size caused data corruption.
Thanks are due to Abel Deuring for help in diagnosing and fixing this, also for
educating me on the behaviour of various types of EEPROM chip. This release also
supports some chips of 2KiB and below which store the upper three address bits
in the chip address. See [6. Small chips case study](./I2C.md#6-small-chips-case-study).
##### [Main readme](../../README.md)
# 2. Connections
@ -77,11 +86,31 @@ Other platforms may vary.
1. `eeprom_i2c.py` Device driver.
2. `bdevice.py` (In root directory) Base class for the device driver.
3. `eep_i2c.py` Pyboard test programs for above.
4. `wemos_i2c_eeprom.py` Test program using a Wemos D1 mini ESP8266 board.
3. `eep_i2c.py` Pyboard test programs for above (adapt for other hosts).
Installation: copy files 1 and 2 (optionally 3 and/or 4) to the target
filesystem.
## 3.1 Installation
This installs the above files in the `lib` directory.
On networked hardware this may be done with `mip` which is included in recent
firmware. On non-networked hardware this is done using the official
[mpremote utility](http://docs.micropython.org/en/latest/reference/mpremote.html)
which should be installed on the PC as described in this doc.
#### Any hardware
On the PC issue:
```bash
$ mpremote mip install "github:peterhinch/micropython_eeprom/eeprom/i2c"
```
#### Networked hardware
At the device REPL issue:
```python
>>> import mip
>>> mip.install("github:peterhinch/micropython_eeprom/eeprom/i2c")
```
# 4. The device driver
@ -312,7 +341,9 @@ lists the contents. It also prints the outcome of `uos.statvfs` on the array.
Tests copying the source files to the filesystem. The test will fail if the
filesystem was not formatted. Lists the contents of the mountpoint and prints
the outcome of `uos.statvfs`.
the outcome of `uos.statvfs`. This test does not run on ESP8266 owing to a
missing Python language feature. Use File Copy or `upysh` as described below to
verify the filesystem.
## 5.5 File copy
@ -328,5 +359,21 @@ mounted on /eeprom):
cp('/flash/main.py','/eeprom/')
```
See `upysh` in [micropython-lib](https://github.com/micropython/micropython-lib.git)
for other filesystem tools for use at the REPL.
See `upysh` in [micropython-lib](https://github.com/micropython/micropython-lib/tree/master/micropython/upysh)
for filesystem tools for use at the REPL.
# 6. Small chips case study
A generic 2KiB EEPROM was tested. Performing an I2C scan revealed that it
occupied 8 I2C addresses starting at 80 (0x50). Note it would be impossible to
configure such chips in a multi-chip array as all eight addresses are used: the
chip can be regarded as an array of eight 256 byte virtual chips. The driver was
therefore initialised as follows:
```python
i2c = SoftI2C(scl=Pin(9, Pin.OPEN_DRAIN, value=1), sda=Pin(8, Pin.OPEN_DRAIN, value=1))
eep = EEPROM(i2c, 256, addr=0x50)
```
A hard I2C interface would also work. At risk of stating the obvious it is not
possible to build a filesystem on a chip of this size. Tests `eep_i2c.test` and
`eep_i2c.full_test` should be run and will work if the driver is correctly
configured.

Wyświetl plik

@ -5,20 +5,44 @@
import uos
import time
from machine import I2C, Pin
from machine import I2C, Pin, SoftI2C
from eeprom_i2c import EEPROM, T24C512
# Return an EEPROM array. Adapt for platforms other than Pyboard or chips
# smaller than 64KiB.
def get_eep():
# Special code for Pyboard D: enable 3.3V output
if uos.uname().machine.split(" ")[0][:4] == "PYBD":
Pin.board.EN_3V3.value(1)
time.sleep(0.1) # Allow decouplers to charge
eep = EEPROM(I2C(2), T24C512)
if uos.uname().sysname == "esp8266": # ESP8266 test fixture
eep = EEPROM(SoftI2C(scl=Pin(13, Pin.OPEN_DRAIN), sda=Pin(12, Pin.OPEN_DRAIN)), T24C512)
elif uos.uname().sysname == "esp32": # ChronoDot on ESP32-S3
eep = EEPROM(SoftI2C(scl=Pin(9, Pin.OPEN_DRAIN), sda=Pin(8, Pin.OPEN_DRAIN)), 256, addr=0x50)
else: # Pyboard D test fixture
eep = EEPROM(I2C(2), T24C512)
print("Instantiated EEPROM")
return eep
# Yield pseudorandom bytes (random module not available on all ports)
def psrand8(x=0x3FBA2):
while True:
x ^= (x & 0x1FFFF) << 13
x ^= x >> 17
x ^= (x & 0x1FFFFFF) << 5
yield x & 0xFF
# Given a source of pseudorandom bytes yield pseudorandom 256 byte buffer.
def psrand256(rand, ba=bytearray(256)):
while True:
for z in range(256):
ba[z] = next(rand)
yield ba
# Dumb file copy utility to help with managing EEPROM contents at the REPL.
def cp(source, dest):
if dest.endswith("/"): # minimal way to allow
@ -144,35 +168,52 @@ def cptest(eep=None): # Assumes pre-existing filesystem of either type
print("Fail mounting device. Have you formatted it?")
return
print("Mounted device.")
cp(__file__, "/eeprom/")
# We may have the source file or a precompiled binary (*.mpy)
cp(__file__.replace("eep", "eeprom"), "/eeprom/")
print('Contents of "/eeprom": {}'.format(uos.listdir("/eeprom")))
print(uos.statvfs("/eeprom"))
try:
cp(__file__, "/eeprom/")
# We may have the source file or a precompiled binary (*.mpy)
cp(__file__.replace("eep", "eeprom"), "/eeprom/")
print('Contents of "/eeprom": {}'.format(uos.listdir("/eeprom")))
print(uos.statvfs("/eeprom"))
except NameError:
print("Test cannot be performed by this MicroPython port. Consider using upysh.")
# ***** TEST OF HARDWARE *****
def full_test(eep=None, block_size=128):
# Write pseudorandom data to entire array, then read back. Fairly rigorous test.
def full_test(eep=None):
eep = eep if eep else get_eep()
page = 0
for sa in range(0, len(eep), block_size):
data = uos.urandom(block_size)
eep[sa : sa + block_size] = data
if eep[sa : sa + block_size] == data:
print("Page {} passed".format(page))
print("Testing with 256 byte blocks of random data...")
r = psrand8() # Instantiate random byte generator
ps = psrand256(r) # Random 256 byte blocks
for sa in range(0, len(eep), 256):
ea = sa + 256
eep[sa:ea] = next(ps)
print(f"Address {sa}..{ea} written\r", end="")
print()
r = psrand8() # Instantiate new random byte generator with same seed
ps = psrand256(r) # Random 256 byte blocks
for sa in range(0, len(eep), 256):
ea = sa + 256
if eep[sa:ea] == next(ps):
print(f"Address {sa}..{ea} readback passed\r", end="")
else:
print("Page {} readback failed.".format(page))
page += 1
print(f"Address {sa}..{ea} readback failed.")
print()
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.
def help():
st = """Available commands:
help() Print this text.
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)
Utilities:
get_eep() Initialise and return an EEPROM instance.
cp() Very crude file copy utility.
"""
print(st)
help()

Wyświetl plik

@ -40,6 +40,7 @@ class EEPROM(EepromDevice):
self._i2c_addr = 0 # I2C address of current chip
self._buf1 = bytearray(1)
self._addrbuf = bytearray(2) # Memory offset into current chip
self._onebyte = chip_size <= 256 # Single byte address
# superclass figures out _page_size and _page_mask
super().__init__(block_size, nchips, chip_size, page_size, verbose)
@ -90,12 +91,14 @@ class EEPROM(EepromDevice):
start = 0 # Offset into buf.
while nbytes > 0:
npage = self._getaddr(addr, nbytes) # No. of bytes in current page
assert npage > 0
# assert npage > 0
# Offset address into chip: one or two bytes
vaddr = self._addrbuf[1:] if self._onebyte else self._addrbuf
if read:
self._i2c.writeto(self._i2c_addr, self._addrbuf)
self._i2c.writeto(self._i2c_addr, vaddr)
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, (vaddr, buf[start : start + npage]))
self._wait_rdy()
nbytes -= npage
start += npage

Wyświetl plik

@ -0,0 +1,8 @@
{
"urls": [
["bdevice.py", "github:peterhinch/micropython_eeprom/bdevice.py"],
["eep_i2c.py", "github:peterhinch/micropython_eeprom/eeprom/i2c/eep_i2c.py"],
["eeprom_i2c.py", "github:peterhinch/micropython_eeprom/eeprom/i2c/eeprom_i2c.py"]
],
"version": "0.1"
}

Wyświetl plik

@ -1,142 +0,0 @@
# wemos_fi2c_eeprom.py Test I2C EEPROM chips with ESP8266 host
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2020 Peter Hinch
import uos
from machine import I2C, Pin
from eeprom_i2c import EEPROM, T24C512
i2c = I2C(-1, scl=Pin(13, Pin.OPEN_DRAIN), sda=Pin(12, Pin.OPEN_DRAIN))
# Return an EEPROM array. Adapt for platforms other than Pyboard or chips
# smaller than 64KiB.
def get_eep():
eep = EEPROM(i2c, T24C512)
print("Instantiated EEPROM")
return eep
# Dumb file copy utility to help with managing EEPROM contents at the REPL.
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
# ***** TEST OF DRIVER *****
def _testblock(eep, bs):
d0 = b"this >"
d1 = b"<is the boundary"
d2 = d0 + d1
garbage = b"xxxxxxxxxxxxxxxxxxx"
start = bs - len(d0)
end = start + len(garbage)
eep[start:end] = garbage
res = eep[start:end]
if res != garbage:
return "Block test fail 1:" + str(list(res))
end = start + len(d0)
eep[start:end] = d0
end = start + len(garbage)
res = eep[start:end]
if res != b"this >xxxxxxxxxxxxx":
return "Block test fail 2:" + str(list(res))
start = bs
end = bs + len(d1)
eep[start:end] = d1
start = bs - len(d0)
end = start + len(d2)
res = eep[start:end]
if res != d2:
return "Block test fail 3:" + str(list(res))
def test():
eep = get_eep()
sa = 1000
for v in range(256):
eep[sa + v] = v
for v in range(256):
if eep[sa + v] != v:
print(
"Fail at address {} data {} should be {}".format(sa + v, eep[sa + v], v)
)
break
else:
print("Test of byte addressing passed")
data = uos.urandom(30)
sa = 2000
eep[sa : sa + 30] = data
if eep[sa : sa + 30] == data:
print("Test of slice readback passed")
block = 256
res = _testblock(eep, block)
if res is None:
print("Test block boundary {} passed".format(block))
else:
print("Test block boundary {} fail".format(block))
print(res)
block = eep._c_bytes
if eep._a_bytes > block:
res = _testblock(eep, block)
if res is None:
print("Test chip boundary {} passed".format(block))
else:
print("Test chip boundary {} fail".format(block))
print(res)
else:
print("Test chip boundary skipped: only one chip!")
# ***** TEST OF FILESYSTEM MOUNT *****
def fstest(format=False):
eep = get_eep()
# ***** CODE FOR LITTLEFS *****
if format:
uos.VfsLfs2.mkfs(eep)
try:
uos.mount(eep, "/eeprom")
except OSError: # Already mounted
pass
print('Contents of "/": {}'.format(uos.listdir("/")))
print('Contents of "/eeprom": {}'.format(uos.listdir("/eeprom")))
print(uos.statvfs("/eeprom"))
def cptest():
eep = get_eep()
if "eeprom" in uos.listdir("/"):
print("Device already mounted.")
else:
try:
uos.mount(eep, "/eeprom")
except OSError:
print("Fail mounting device. Have you formatted it?")
return
print("Mounted device.")
cp("eep_i2c.py", "/eeprom/")
cp("eeprom_i2c.py", "/eeprom/")
print('Contents of "/eeprom": {}'.format(uos.listdir("/eeprom")))
print(uos.statvfs("/eeprom"))
# ***** TEST OF HARDWARE *****
def full_test():
eep = get_eep()
page = 0
for sa in range(0, len(eep), 128):
data = uos.urandom(128)
eep[sa : sa + 128] = data
if eep[sa : sa + 128] == data:
print("Page {} passed".format(page))
else:
print("Page {} readback failed.".format(page))
page += 1

Wyświetl plik

@ -82,7 +82,29 @@ The SPI bus is fast: wiring should be short and direct.
2. `bdevice.py` (In root directory) Base class for the device driver.
3. `eep_spi.py` Test programs for above.
Installation: copy files 1 and 2 (optionally 3) to the target filesystem.
## 3.1 Installation
This installs the first three files in the `lib` directory.
On networked hardware this may be done with `mip` which is included in recent
firmware. On non-networked hardware this is done using the official
[mpremote utility](http://docs.micropython.org/en/latest/reference/mpremote.html)
which should be installed on the PC as described in this doc.
#### Any hardware
On the PC issue:
```bash
$ mpremote mip install "github:peterhinch/micropython_eeprom/eeprom/spi"
```
#### Networked hardware
At the device REPL issue:
```python
>>> import mip
>>> mip.install("github:peterhinch/micropython_eeprom/eeprom/spi")
```
# 4. The device driver

Wyświetl plik

@ -1,29 +1,61 @@
# eep_spi.py MicroPython test program for Microchip SPI EEPROM devices.
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2019-2022 Peter Hinch
# Copyright (c) 2019-2024 Peter Hinch
import uos
import time
from machine import SPI, Pin
from machine import SPI, Pin, SoftSPI
from eeprom_spi import EEPROM
ESP8266 = uos.uname().sysname == "esp8266"
# Add extra pins if using multiple chips
cspins = (Pin(Pin.board.Y5, Pin.OUT, value=1), Pin(Pin.board.Y4, Pin.OUT, value=1))
if ESP8266:
cspins = (Pin(5, Pin.OUT, value=1), Pin(14, Pin.OUT, value=1))
else:
cspins = (Pin(Pin.board.Y5, Pin.OUT, value=1), Pin(Pin.board.Y4, Pin.OUT, value=1))
# Return an EEPROM array. Adapt for platforms other than Pyboard.
def get_eep(stm):
if uos.uname().machine.split(" ")[0][:4] == "PYBD":
Pin.board.EN_3V3.value(1)
time.sleep(0.1) # Allow decouplers to charge
if stm:
eep = EEPROM(SPI(2, baudrate=5_000_000), cspins, 256)
if ESP8266:
spi = SoftSPI(baudrate=5_000_000, sck=Pin(4), miso=Pin(0), mosi=Pin(2))
else: # Pyboard. 1.22.1 hard SPI seems to have a read bug
# spi = SPI(2, baudrate=5_000_000)
spi = SoftSPI(baudrate=5_000_000, sck=Pin('Y6'), miso=Pin('Y7'), mosi=Pin('Y8'))
eep = EEPROM(spi, cspins, 256)
else:
eep = EEPROM(SPI(2, baudrate=20_000_000), cspins, 128)
if ESP8266:
spi = SoftSPI(baudrate=20_000_000, sck=Pin(4), miso=Pin(0), mosi=Pin(2))
else:
# spi = SPI(2, baudrate=20_000_000)
spi = SoftSPI(baudrate=20_000_000, sck=Pin('Y6'), miso=Pin('Y7'), mosi=Pin('Y8'))
eep = EEPROM(spi, cspins, 128)
print("Instantiated EEPROM")
return eep
# Yield pseudorandom bytes (random module not available on all ports)
def psrand8(x=0x3FBA2):
while True:
x ^= (x & 0x1FFFF) << 13
x ^= x >> 17
x ^= (x & 0x1FFFFFF) << 5
yield x & 0xFF
# Given a source of pseudorandom bytes yield pseudorandom 256 byte buffer.
def psrand256(rand, ba=bytearray(256)):
while True:
for z in range(256):
ba[z] = next(rand)
yield ba
# Dumb file copy utility to help with managing EEPROM contents at the REPL.
def cp(source, dest):
if dest.endswith("/"): # minimal way to allow
@ -149,27 +181,36 @@ def cptest(stm=False): # Assumes pre-existing filesystem of either type
print("Fail mounting device. Have you formatted it?")
return
print("Mounted device.")
cp(__file__, "/eeprom/")
# We may have the source file or a precompiled binary (*.mpy)
cp(__file__.replace("eep", "eeprom"), "/eeprom/")
print('Contents of "/eeprom": {}'.format(uos.listdir("/eeprom")))
print(uos.statvfs("/eeprom"))
try:
cp(__file__, "/eeprom/")
# We may have the source file or a precompiled binary (*.mpy)
cp(__file__.replace("eep", "eeprom"), "/eeprom/")
print('Contents of "/eeprom": {}'.format(uos.listdir("/eeprom")))
print(uos.statvfs("/eeprom"))
except NameError:
print("Test cannot be performed by this MicroPython port. Consider using upysh.")
# ***** TEST OF HARDWARE *****
def full_test(stm=False):
eep = get_eep(stm)
block = 0
print("Testing with 256 byte blocks of random data...")
r = psrand8() # Instantiate random byte generator
ps = psrand256(r) # Random 256 byte blocks
for sa in range(0, len(eep), 256):
data = uos.urandom(256)
eep[sa : sa + 256] = data
got = eep[sa : sa + 256]
if got == data:
print(f"Block {block} passed")
ea = sa + 256
eep[sa:ea] = next(ps)
print(f"Address {sa}..{ea} written\r", end="")
print()
r = psrand8() # Instantiate new random byte generator with same seed
ps = psrand256(r) # Random 256 byte blocks
for sa in range(0, len(eep), 256):
ea = sa + 256
if eep[sa:ea] == next(ps):
print(f"Address {sa}..{ea} readback passed\r", end="")
else:
print(f"Block {block} readback failed.")
break
block += 1
print(f"Address {sa}..{ea} readback failed.")
print()
def help():

Wyświetl plik

@ -46,10 +46,10 @@ class EEPROM(EepromDevice):
def _devtest(self, cs, la, v=None):
buf = bytearray(1)
mvp = self._mvp
mvp[:] = b"\0" * 5
# mvp[1] = la >> 16
# mvp[2] = (la >> 8) & 0xFF
# mvp[3] = la & 0xFF
# mvp[:] = b"\0" * 5 # test with addr 0
mvp[1] = la >> 16
mvp[2] = (la >> 8) & 0xFF
mvp[3] = la & 0xFF
mvp[0] = _READ
cs(0)
self._spi.write(mvp[:4])

Wyświetl plik

@ -0,0 +1,8 @@
{
"urls": [
["bdevice.py", "github:peterhinch/micropython_eeprom/bdevice.py"],
["eep_spi.py", "github:peterhinch/micropython_eeprom/eeprom/spi/eep_spi.py"],
["eeprom_spi.py", "github:peterhinch/micropython_eeprom/eeprom/spi/eeprom_spi.py"]
],
"version": "0.1"
}