spi: Bug in presence detect.

pull/24/head
Peter Hinch 2024-01-12 08:39:07 +00:00
rodzic 9a9c588f9b
commit e06fd66bb3
5 zmienionych plików z 210 dodań i 168 usunięć

Wyświetl plik

@ -4,7 +4,7 @@
# Documentation in BASE_CLASSES.md # Documentation in BASE_CLASSES.md
# Released under the MIT License (MIT). See LICENSE. # Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2019 Peter Hinch # Copyright (c) 2019-2024 Peter Hinch
from micropython import const from micropython import const
@ -80,6 +80,7 @@ class BlockDevice:
return 0 return 0
# Hardware agnostic base class for EEPROM arrays
class EepromDevice(BlockDevice): class EepromDevice(BlockDevice):
def __init__(self, nbits, nchips, chip_size, page_size, verbose): def __init__(self, nbits, nchips, chip_size, page_size, verbose):
super().__init__(nbits, nchips, chip_size) super().__init__(nbits, nchips, chip_size)
@ -96,26 +97,22 @@ class EepromDevice(BlockDevice):
def get_page_size(self): # For test script def get_page_size(self): # For test script
return self._page_size return self._page_size
# Measuring page size should not be done in production code. See docs.
def _set_pagesize(self, page_size): def _set_pagesize(self, page_size):
if page_size is None: # Measure it if page_size is None: # Measure it.
self._psize(16) # Conservative self._psize(16) # Conservative
old = self[:129] # Save old contents (nonvolatile!) 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: try:
self._psize(256) # Ambitious ps = next(z for z in r if self[z])
r = (16, 32, 64, 128) # Legal page sizes + 256 except StopIteration:
for x in r: ps = 256
self[x] = 255 # Write single bytes, don't invoke page write self._psize(ps)
self[0:129] = b"\0" * 129 # Zero 129 bytes attempting to use 256 byte pages self[:129] = old
try:
ps = next(z for z in r if self[z])
except StopIteration:
ps = 256
self._psize(ps)
self[:129] = old
except: # If anything goes wrong, restore old data and raise
for n, v in enumerate(old):
self[n] = v
raise
else: # Validated page_size was supplied else: # Validated page_size was supplied
self._psize(page_size) self._psize(page_size)

Wyświetl plik

@ -131,17 +131,13 @@ Arguments:
devices it has detected. devices it has detected.
4. `block_size=9` The block size reported to the filesystem. The size in bytes 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. is `2**block_size` so is 512 bytes by default.
5. `addr` override base address for first chip. 5. `addr` Override base address for first chip. See
6. `max_chips_count` override max_chips_count. [4.1.6 Special configurations](./I2C.md#416-special-configurations).
6. `max_chips_count` Override max_chips_count - see above reference.
7. `page_size=None` EEPROM chips have a page buffer. By default the driver 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 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 passing an integer being the page size in bytes: 16, 32, 64, 128 or 256. See
page size may vary between chips from different manufacturers even for the [4.1.5 Page size](./I2C.md#414-page-size) for issues surrounding this.
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.
In most cases only the first two arguments are used, with an array being In most cases only the first two arguments are used, with an array being
instantiated with (for example): instantiated with (for example):
@ -150,26 +146,13 @@ from machine import I2C
from eeprom_i2c import EEPROM, T24C512 from eeprom_i2c import EEPROM, T24C512
eep = EEPROM(I2C(2), 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)
```
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)
```
### 4.1.2 Methods providing byte level access ### 4.1.2 Methods providing byte level access
It is possible to read and write individual bytes or arrays of arbitrary size. It is possible to read and write individual bytes or arrays of arbitrary size.
Larger arrays are faster, especially when writing: the driver uses the chip's Larger arrays are faster, especially when writing: the driver uses the chip's
hardware page access where possible. Writing a page (128 bytes) takes the same hardware page access where possible. Writing a page takes the same time (~5ms)
time (~5ms) as writing a single byte. as writing a single byte.
#### 4.1.2.1 `__getitem__` and `__setitem__` #### 4.1.2.1 `__getitem__` and `__setitem__`
@ -223,6 +206,10 @@ Other than for debugging there is no need to call `scan()`: the constructor
will throw a `RuntimeError` if it fails to communicate with and correctly will throw a `RuntimeError` if it fails to communicate with and correctly
identify the chip. identify the chip.
#### get_page_size
Return the page size in bytes.
### 4.1.4 Methods providing the block protocol ### 4.1.4 Methods providing the block protocol
These are provided by the base class. For the protocol definition see These are provided by the base class. For the protocol definition see
@ -236,6 +223,37 @@ their use in application code is not recommended.
`writeblocks()` `writeblocks()`
`ioctl()` `ioctl()`
### 4.1.5 Page size
EEPROM chips have a RAM buffer enabling fast writing of data blocks. Writing a
page takes the same time (~5ms) as writing a single byte. The page size may vary
between chips from different manufacturers even for the same storage size.
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. Too small a
value will impact write performance. The correct value for a device may be found
in in the chip datasheet. It is also reported if `verbose` is set and when
running the test scripts.
Auto-detecting page size carries a risk of data loss if power fails while
auto-detect is in progress. In production code the value should be specified
explicitly.
### 4.1.6 Special configurations
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)
```
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)
```
## 4.2 Byte addressing usage example ## 4.2 Byte addressing usage example
A sample application: saving a configuration dict (which might be large and A sample application: saving a configuration dict (which might be large and
@ -268,20 +286,21 @@ possible to use JSON/pickle to store objects in a filesystem.
# 5. Test program eep_i2c.py # 5. Test program eep_i2c.py
This assumes a Pyboard 1.x or Pyboard D with EEPROM(s) wired as above. It This assumes a Pyboard 1.x or Pyboard D with EEPROM(s) wired as above. On other
provides the following. hardware, adapt `get_eep` at the start of the script. It provides the following.
## 5.1 test() ## 5.1 test()
This performs a basic test of single and multi-byte access to chip 0. The test This performs a basic test of single and multi-byte access to chip 0. The test
reports how many chips can be accessed. Existing array data will be lost. This reports how many chips can be accessed. The current page size is printed and its
primarily tests the driver: as a hardware test it is not exhaustive. validity is tested. Existing array data will be lost. This primarily tests the
driver: as a hardware test it is not exhaustive.
## 5.2 full_test() ## 5.2 full_test()
This is a hardware test. Tests the entire array. Fills each 128 byte page with This is a hardware test. Tests the entire array. Fills the array with random
random data, reads it back, and checks the outcome. Existing array data will be data in blocks of 256 byes. After each block is written, it is read back and the
lost. contents compared to the data written. Existing array data will be lost.
## 5.3 fstest(format=False) ## 5.3 fstest(format=False)

Wyświetl plik

@ -18,9 +18,12 @@ The driver has the following attributes:
7. Alternatively it can support byte-level access using Python slice syntax. 7. Alternatively it can support byte-level access using Python slice syntax.
8. RAM allocations are minimised. Buffer sizes are tiny. 8. RAM allocations are minimised. Buffer sizes are tiny.
##### [Main readme](../../README.md) ## 1.1 Notes
## 1.1 This document As of Jan 2024 this driver has been updated to fix a bug where the device page
size was less than 256. A further aim was to make the driver more generic, with
a better chance of working with other SPI EEPROM chips. The constructor has
additional optional args to support this.
Code samples assume one or more Microchip devices. If using the STM chip the Code samples assume one or more Microchip devices. If using the STM chip the
SPI baudrate should be 5MHz and the chip size must be specified to the `EEPROM` SPI baudrate should be 5MHz and the chip size must be specified to the `EEPROM`
@ -29,6 +32,8 @@ constructor, e.g.:
eep = EEPROM(SPI(2, baudrate=5_000_000), cspins, 256) eep = EEPROM(SPI(2, baudrate=5_000_000), cspins, 256)
``` ```
##### [Main readme](../../README.md)
# 2. Connections # 2. Connections
Any SPI interface may be used. The table below assumes a Pyboard running SPI(2) Any SPI interface may be used. The table below assumes a Pyboard running SPI(2)
@ -91,7 +96,7 @@ import os
from machine import SPI, Pin from machine import SPI, Pin
from eeprom_spi import EEPROM from eeprom_spi import EEPROM
cspins = (Pin(Pin.board.Y5, Pin.OUT, value=1), Pin(Pin.board.Y4, Pin.OUT, value=1)) cspins = (Pin(Pin.board.Y5, Pin.OUT, value=1), Pin(Pin.board.Y4, Pin.OUT, value=1))
eep = EEPROM(SPI(2, baudrate=20_000_000), cspins) eep = EEPROM(SPI(2, baudrate=20_000_000), cspins, 128) # 128KiB chips
# Format the filesystem # Format the filesystem
os.VfsLfs2.mkfs(eep) # Omit this to mount an existing filesystem os.VfsLfs2.mkfs(eep) # Omit this to mount an existing filesystem
os.mount(eep,'/eeprom') os.mount(eep,'/eeprom')
@ -103,14 +108,15 @@ Note that, at the outset, you need to decide whether to use the array as a
mounted filesystem or as a byte array. The filesystem is relatively small but mounted filesystem or as a byte array. The filesystem is relatively small but
has high integrity owing to the hardware longevity. Typical use-cases involve has high integrity owing to the hardware longevity. Typical use-cases involve
files which are frequently updated. These include files used for storing Python files which are frequently updated. These include files used for storing Python
objects serialised using Pickle/ujson or files holding a btree database. objects serialised using Pickle/json or files holding a btree database.
The SPI bus must be instantiated using the `machine` module. The SPI bus must be instantiated using the `machine` module.
## 4.1 The EEPROM class ## 4.1 The EEPROM class
An `EEPROM` instance represents a logical EEPROM: this may consist of multiple An `EEPROM` instance represents a logical EEPROM: this may consist of multiple
physical devices on a common SPI bus. physical devices on a common SPI bus. Alternatively multiple EEPROM instances
may share the bus, differentiated by their CS pins.
### 4.1.1 Constructor ### 4.1.1 Constructor
@ -119,14 +125,21 @@ each chip select line an EEPROM array is instantiated. A `RuntimeError` will be
raised if a device is not detected on a CS line. raised if a device is not detected on a CS line.
Arguments: Arguments:
1. `spi` Mandatory. An initialised SPI bus created by `machine`. 1. `spi` An initialised SPI bus created by `machine`.
2. `cspins` A list or tuple of `Pin` instances. Each `Pin` must be initialised 2. `cspins` A list or tuple of `Pin` instances. Each `Pin` must be initialised
as an output (`Pin.OUT`) and with `value=1` and be created by `machine`. as an output (`Pin.OUT`) and with `value=1` and be created by `machine`.
3. `size=128` Chip size in KiB. Set to 256 for the STM chip. 3. `size` Chip size in KiB. Set to 256 for the STM chip, 128 for the Microchip.
4. `verbose=True` If `True`, the constructor issues information on the EEPROM 4. `verbose=True` If `True`, the constructor performs a presence check for an
devices it has detected. EEPROM on each chip select pin and reports devices it has detected. See
[4.1.5 Auto detection](./SPI.md#415-auto-detection) for observations on
production code.
5. `block_size=9` The block size reported to the filesystem. The size in bytes 5. `block_size=9` The block size reported to the filesystem. The size in bytes
is `2**block_size` so is 512 bytes by default. is `2**block_size` so is 512 bytes by default.
6. `page_size=None` EEPROM devices have a RAM buffer enabling fast writes. The
driver determines this automatically by default. It is possible to override
this by passing an integer being the page size in bytes: 16, 32, 64, 128 or 256.
See [4.1.5 Auto detection](./SPI.md#415-auto-detection) for reasons why this
is advised in production code.
SPI baudrate: The 25LC1024 supports baudrates of upto 20MHz. If this value is SPI baudrate: The 25LC1024 supports baudrates of upto 20MHz. If this value is
specified the platform will produce the highest available frequency not specified the platform will produce the highest available frequency not
@ -152,7 +165,7 @@ of single byte access:
from machine import SPI, Pin from machine import SPI, Pin
from eeprom_spi import EEPROM from eeprom_spi import EEPROM
cspins = (Pin(Pin.board.Y5, Pin.OUT, value=1), Pin(Pin.board.Y4, Pin.OUT, value=1)) cspins = (Pin(Pin.board.Y5, Pin.OUT, value=1), Pin(Pin.board.Y4, Pin.OUT, value=1))
eep = EEPROM(SPI(2, baudrate=20_000_000), cspins) eep = EEPROM(SPI(2, baudrate=20_000_000), cspins, 128)
eep[2000] = 42 eep[2000] = 42
print(eep[2000]) # Return an integer print(eep[2000]) # Return an integer
``` ```
@ -162,7 +175,7 @@ writing, the size of the slice must match the length of the buffer:
from machine import SPI, Pin from machine import SPI, Pin
from eeprom_spi import EEPROM from eeprom_spi import EEPROM
cspins = (Pin(Pin.board.Y5, Pin.OUT, value=1), Pin(Pin.board.Y4, Pin.OUT, value=1)) cspins = (Pin(Pin.board.Y5, Pin.OUT, value=1), Pin(Pin.board.Y4, Pin.OUT, value=1))
eep = EEPROM(SPI(2, baudrate=20_000_000), cspins) eep = EEPROM(SPI(2, baudrate=20_000_000), cspins, 128)
eep[2000:2002] = bytearray((42, 43)) eep[2000:2002] = bytearray((42, 43))
print(eep[2000:2002]) # Returns a bytearray print(eep[2000:2002]) # Returns a bytearray
``` ```
@ -187,7 +200,7 @@ advantage when reading of using a pre-allocated buffer. Arguments:
#### The len operator #### The len operator
The size of the EEPROM array in bytes may be retrieved by issuing `len(eep)` The size of the EEPROM array in bytes may be retrieved by issuing `len(eep)`
where `eep` is the `EEPROM` instance. where `eep` is an `EEPROM` instance.
#### scan #### scan
@ -201,7 +214,11 @@ identify the chip.
#### erase #### erase
Erases the entire array. Available only on the Microchip device. Zero the entire array. Can take several seconds.
#### get_page_size
Return the page size in bytes.
### 4.1.4 Methods providing the block protocol ### 4.1.4 Methods providing the block protocol
@ -216,6 +233,19 @@ their use in application code is not recommended.
`writeblocks()` `writeblocks()`
`ioctl()` `ioctl()`
### 4.1.5 Auto detection
The driver constructor uses auto-detection in two circumstances:
* If `verbose` is specified, it checks each chip select for chip presence.
* If `page_size` is set to `None` the value is determined by measurement.
In both cases data is written to the chips, then restored from RAM. If a power
outage were to occur while either process was in progress, corruption could
occur. It is therefore recommended that, in production code, `verbose` is
`False` and `page_size` is set to an integer. The page size may be determined
from the chip datasheet. It is also printed on instantiation if `verbose` is
set: running any of the test scripts will do this.
## 4.2 Byte addressing usage example ## 4.2 Byte addressing usage example
A sample application: saving a configuration dict (which might be large and A sample application: saving a configuration dict (which might be large and
@ -225,7 +255,7 @@ import ujson
from machine import SPI, Pin from machine import SPI, Pin
from eeprom_spi import EEPROM from eeprom_spi import EEPROM
cspins = (Pin(Pin.board.Y5, Pin.OUT, value=1), Pin(Pin.board.Y4, Pin.OUT, value=1)) cspins = (Pin(Pin.board.Y5, Pin.OUT, value=1), Pin(Pin.board.Y4, Pin.OUT, value=1))
eep = EEPROM(SPI(2, baudrate=20_000_000), cspins) eep = EEPROM(SPI(2, baudrate=20_000_000), cspins, 128)
d = {1:'one', 2:'two'} # Some kind of large object d = {1:'one', 2:'two'} # Some kind of large object
wdata = ujson.dumps(d).encode('utf8') wdata = ujson.dumps(d).encode('utf8')
sl = '{:10d}'.format(len(wdata)).encode('utf8') sl = '{:10d}'.format(len(wdata)).encode('utf8')
@ -252,18 +282,20 @@ possible to use JSON/pickle to store objects in a filesystem.
This assumes a Pyboard 1.x or Pyboard D with two EEPROMs wired to SPI(2) as This assumes a Pyboard 1.x or Pyboard D with two EEPROMs wired to SPI(2) as
above with chip selects connected to pins `Y4` and `Y5`. It provides the above with chip selects connected to pins `Y4` and `Y5`. It provides the
following. In all cases the stm arg should be `True` if using the STM chips. following. In all cases the stm arg should be `True` if using the STM chips.
On other hardware, adapt `cspins` and `get_eep` at the start of the script.
## 5.1 test(stm=False) ## 5.1 test(stm=False)
This performs a basic test of single and multi-byte access to chip 0. The test This performs a basic test of single and multi-byte access to chip 0. The test
reports how many chips can be accessed. Existing array data will be lost. This reports how many chips can be accessed. The current page size is printed and its
primarily tests the driver: as a hardware test it is not exhaustive. validity is tested. Existing array data will be lost. This primarily tests the
driver: as a hardware test it is not exhaustive.
## 5.2 full_test(stm=False) ## 5.2 full_test(stm=False)
This is a hardware test. Tests the entire array. Fills each 256 byte page with This is a hardware test. Tests the entire array. Fills the array with random
random data, reads it back, and checks the outcome. Existing array data will be data in blocks of 256 byes. After each block is written, it is read back and the
lost. contents compared to the data written. Existing array data will be lost.
## 5.3 fstest(format=False, stm=False) ## 5.3 fstest(format=False, stm=False)

Wyświetl plik

@ -19,7 +19,7 @@ def get_eep(stm):
if stm: if stm:
eep = EEPROM(SPI(2, baudrate=5_000_000), cspins, 256) eep = EEPROM(SPI(2, baudrate=5_000_000), cspins, 256)
else: else:
eep = EEPROM(SPI(2, baudrate=20_000_000), cspins) eep = EEPROM(SPI(2, baudrate=20_000_000), cspins, 128)
print("Instantiated EEPROM") print("Instantiated EEPROM")
return eep return eep
@ -28,13 +28,19 @@ def get_eep(stm):
def cp(source, dest): def cp(source, dest):
if dest.endswith("/"): # minimal way to allow if dest.endswith("/"): # minimal way to allow
dest = "".join((dest, source.split("/")[-1])) # cp /sd/file /eeprom/ dest = "".join((dest, source.split("/")[-1])) # cp /sd/file /eeprom/
with open(source, "rb") as infile: # Caller should handle any OSError try:
with open(dest, "wb") as outfile: # e.g file not found with open(source, "rb") as infile: # Caller should handle any OSError
while True: with open(dest, "wb") as outfile: # e.g file not found
buf = infile.read(100) while True:
outfile.write(buf) buf = infile.read(100)
if len(buf) < 100: outfile.write(buf)
break if len(buf) < 100:
break
except OSError as e:
if e.errno == 28:
print("Insufficient space for copy.")
else:
raise
# ***** TEST OF DRIVER ***** # ***** TEST OF DRIVER *****
@ -72,9 +78,7 @@ def test(stm=False):
eep[sa + v] = v eep[sa + v] = v
for v in range(256): for v in range(256):
if eep[sa + v] != v: if eep[sa + v] != v:
print( print("Fail at address {} data {} should be {}".format(sa + v, eep[sa + v], v))
"Fail at address {} data {} should be {}".format(sa + v, eep[sa + v], v)
)
break break
else: else:
print("Test of byte addressing passed") print("Test of byte addressing passed")
@ -101,6 +105,14 @@ def test(stm=False):
print(res) print(res)
else: else:
print("Test chip boundary skipped: only one chip!") print("Test chip boundary skipped: only one chip!")
pe = eep.get_page_size() # 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 ***** # ***** TEST OF FILESYSTEM MOUNT *****
@ -137,8 +149,9 @@ def cptest(stm=False): # Assumes pre-existing filesystem of either type
print("Fail mounting device. Have you formatted it?") print("Fail mounting device. Have you formatted it?")
return return
print("Mounted device.") print("Mounted device.")
cp("eep_spi.py", "/eeprom/") cp(__file__, "/eeprom/")
cp("eeprom_spi.py", "/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('Contents of "/eeprom": {}'.format(uos.listdir("/eeprom")))
print(uos.statvfs("/eeprom")) print(uos.statvfs("/eeprom"))
@ -146,23 +159,29 @@ def cptest(stm=False): # Assumes pre-existing filesystem of either type
# ***** TEST OF HARDWARE ***** # ***** TEST OF HARDWARE *****
def full_test(stm=False): def full_test(stm=False):
eep = get_eep(stm) eep = get_eep(stm)
page = 0 block = 0
for sa in range(0, len(eep), 256): for sa in range(0, len(eep), 256):
data = uos.urandom(256) data = uos.urandom(256)
eep[sa : sa + 256] = data eep[sa : sa + 256] = data
got = eep[sa : sa + 256] got = eep[sa : sa + 256]
if got == data: if got == data:
print("Page {} passed".format(page)) print(f"Block {block} passed")
else: else:
print("Page {} readback failed.".format(page)) print(f"Block {block} readback failed.")
break break
page += 1 block += 1
test_str = """Available tests (see SPI.md): def help():
test(stm=False) Basic hardware test. test_str = """Available commands (see SPI.md):
full_test(stm=False) Thorough hardware test. help() Print this text.
fstest(format=False, stm=False) Filesystem test (see doc). test(stm=False) Basic hardware test.
cptest(stm=False) Copy files to filesystem (see doc). full_test(stm=False) Thorough hardware test.
fstest(format=False, stm=False) Filesystem test (see doc).
cptest(stm=False) Copy files to filesystem (see doc).
stm: True is 256K chip, 5MHz bus. False is 128K chip, 20MHz bus.
""" """
print(test_str) print(test_str)
help()

Wyświetl plik

@ -2,114 +2,89 @@
# tested devices). # tested devices).
# Released under the MIT License (MIT). See LICENSE. # Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2019-2022 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 import time
from os import urandom
from micropython import const from micropython import const
from bdevice import BlockDevice from bdevice import EepromDevice
# Supported instruction set - common to both chips: # Supported instruction set - common to both chips:
_READ = const(3) _READ = const(3)
_WRITE = const(2) _WRITE = const(2)
_WREN = const(6) # Write enable _WREN = const(6) # Write enable
_RDSR = const(5) # Read status register _RDSR = const(5) # Read status register
# Microchip only:
_RDID = const(0xAB) # Read chip ID
_CE = const(0xC7) # Chip erase
# STM only:
_RDID_STM = const(0x83) # Read ID page
_WRID_STM = const(0x82)
_STM_ID = const(0x30) # Arbitrary ID for STM chip
# Not implemented: Write disable and Write status register
# _WRDI = const(4)
# _WRSR = const(1)
# Logical EEPROM device comprising one or more physical chips sharing an SPI bus. # Logical EEPROM device comprising one or more physical chips sharing an SPI bus.
class EEPROM(BlockDevice): # args: SPI bus, tuple of CS Pin instances, chip size in KiB
def __init__(self, spi, cspins, size=128, verbose=True, block_size=9): # verbose: Test for chip presence and report
# args: virtual block size in bits, no. of chips, bytes in each chip # block_size: Sector size for filesystems. See docs.
# erok: True if chip supports erase.
# page_size: None is auto detect. See docs.
class EEPROM(EepromDevice):
def __init__(self, spi, cspins, size, verbose=True, block_size=9, page_size=None):
if size not in (64, 128, 256): if size not in (64, 128, 256):
print("Warning: possible unsupported chip. Size:", size) print(f"Warning: possible unsupported chip. Size: {size}KiB")
super().__init__(block_size, len(cspins), size * 1024)
self._stm = size == 256
self._spi = spi self._spi = spi
self._cspins = cspins self._cspins = cspins
self._ccs = None # Chip select Pin object for current chip self._ccs = None # Chip select Pin object for current chip
self._size = size * 1024 # Chip size in bytes
self._bufp = bytearray(5) # instruction + 3 byte address + 1 byte value self._bufp = bytearray(5) # instruction + 3 byte address + 1 byte value
self._mvp = memoryview(self._bufp) # cost-free slicing self._mvp = memoryview(self._bufp) # cost-free slicing
self.scan(verbose) if verbose: # Test for presence of devices
self.scan()
# superclass figures out _page_size and _page_mask
super().__init__(block_size, len(cspins), self._size, page_size, verbose)
if verbose:
print(f"Total EEPROM size {self._a_bytes:,} bytes.")
# Read ID block ID[0] # Low level device presence detect. Reads a location, then writes to it. If
def _stm_rdid(self, n): # a write value is passed, uses that, otherwise writes the one's complement
cs = self._cspins[n] # of the value read.
def _devtest(self, cs, la, v=None):
buf = bytearray(1)
mvp = self._mvp mvp = self._mvp
mvp[:] = b"\0\0\0\0\0" mvp[:] = b"\0" * 5
mvp[0] = _RDID_STM # mvp[1] = la >> 16
# mvp[2] = (la >> 8) & 0xFF
# mvp[3] = la & 0xFF
mvp[0] = _READ
cs(0) cs(0)
self._spi.write_readinto(mvp, mvp) self._spi.write(mvp[:4])
res = self._spi.read(1)
cs(1) cs(1)
return mvp[4]
# Write a fixed value to ID[0]
def _stm_wrid(self, n):
cs = self._ccs
mvp = self._mvp
mvp[0] = _WREN mvp[0] = _WREN
cs(0) cs(0)
self._spi.write(mvp[:1]) # Enable write self._spi.write(mvp[:1])
cs(1) cs(1)
mvp[:] = b"\0\0\0\0\0" mvp[0] = _WRITE
mvp[0] = _WRID_STM
mvp[4] = _STM_ID
cs(0) cs(0)
self._spi.write(mvp) self._spi.write(mvp[:4])
cs(1) buf[0] = res[0] ^ 0xFF if v is None else v
self._wait_rdy() self._spi.write(buf)
cs(1) # Trigger write start
self._ccs = cs
self._wait_rdy() # Wait until done (6ms max)
return res[0]
# Check for valid hardware on each CS pin: use ID block def scan(self):
def _stm_scan(self): # Generate a random address to minimise wear
la = int.from_bytes(urandom(3), "little") % self._size
for n, cs in enumerate(self._cspins): for n, cs in enumerate(self._cspins):
self._ccs = cs old = self._devtest(cs, la)
if self._stm_rdid(n) != _STM_ID: new = self._devtest(cs, la, old)
self._stm_wrid(n) if old != new ^ 0xFF:
if self._stm_rdid(n) != _STM_ID: raise RuntimeError(f"Chip not found at cs[{n}]")
raise RuntimeError("M95M02 chip not found at cs[{}].".format(n)) print(f"{n + 1} chips detected.")
return n return n
# Scan for Microchip devices: read manf ID
def _mc_scan(self):
mvp = self._mvp
for n, cs in enumerate(self._cspins):
mvp[:] = b"\0\0\0\0\0"
mvp[0] = _RDID
cs(0)
self._spi.write_readinto(mvp, mvp)
cs(1)
if mvp[4] != 0x29:
raise RuntimeError("25xx1024 chip not found at cs[{}].".format(n))
return n
# Check for a valid hardware configuration
def scan(self, verbose):
n = self._stm_scan() if self._stm else self._mc_scan()
if verbose:
s = "{} chips detected. Total EEPROM size {}bytes."
print(s.format(n + 1, self._a_bytes))
def erase(self): def erase(self):
if self._stm:
raise RuntimeError("Erase not available on STM chip")
mvp = self._mvp mvp = self._mvp
for cs in self._cspins: # For each chip block = b"\0" * 256
mvp[0] = _WREN for n in range(0, self._a_bytes, 256):
cs(0) self[n : n + 256] = block
self._spi.write(mvp[:1]) # Enable write
cs(1)
mvp[0] = _CE
cs(0)
self._spi.write(mvp[:1]) # Start erase
cs(1)
self._wait_rdy() # Wait for erase to complete
def _wait_rdy(self): # After a write, wait for device to become ready def _wait_rdy(self): # After a write, wait for device to become ready
mvp = self._mvp mvp = self._mvp
@ -134,7 +109,7 @@ class EEPROM(BlockDevice):
mvp[1] = la >> 16 mvp[1] = la >> 16
mvp[2] = (la >> 8) & 0xFF mvp[2] = (la >> 8) & 0xFF
mvp[3] = la & 0xFF mvp[3] = la & 0xFF
pe = (addr & ~0xFF) + 0x100 # byte 0 of next page pe = (la & self._page_mask) + self._page_size # byte 0 of next page
return min(nbytes, pe - la) return min(nbytes, pe - la)
# Read or write multiple bytes at an arbitrary address # Read or write multiple bytes at an arbitrary address