kopia lustrzana https://github.com/peterhinch/micropython_eeprom
spi: Bug in presence detect.
rodzic
9a9c588f9b
commit
e06fd66bb3
31
bdevice.py
31
bdevice.py
|
@ -4,7 +4,7 @@
|
|||
# Documentation in BASE_CLASSES.md
|
||||
|
||||
# Released under the MIT License (MIT). See LICENSE.
|
||||
# Copyright (c) 2019 Peter Hinch
|
||||
# Copyright (c) 2019-2024 Peter Hinch
|
||||
|
||||
from micropython import const
|
||||
|
||||
|
@ -80,6 +80,7 @@ class BlockDevice:
|
|||
return 0
|
||||
|
||||
|
||||
# Hardware agnostic base class for EEPROM arrays
|
||||
class EepromDevice(BlockDevice):
|
||||
def __init__(self, nbits, nchips, chip_size, page_size, verbose):
|
||||
super().__init__(nbits, nchips, chip_size)
|
||||
|
@ -96,26 +97,22 @@ class EepromDevice(BlockDevice):
|
|||
def get_page_size(self): # For test script
|
||||
return self._page_size
|
||||
|
||||
# Measuring page size should not be done in production code. See docs.
|
||||
def _set_pagesize(self, page_size):
|
||||
if page_size is None: # Measure it
|
||||
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:
|
||||
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
|
||||
except: # If anything goes wrong, restore old data and raise
|
||||
for n, v in enumerate(old):
|
||||
self[n] = v
|
||||
raise
|
||||
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)
|
||||
|
||||
|
|
|
@ -131,17 +131,13 @@ 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.
|
||||
5. `addr` Override base address for first chip. See
|
||||
[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
|
||||
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.
|
||||
passing an integer being the page size in bytes: 16, 32, 64, 128 or 256. See
|
||||
[4.1.5 Page size](./I2C.md#414-page-size) for issues surrounding this.
|
||||
|
||||
In most cases only the first two arguments are used, with an array being
|
||||
instantiated with (for example):
|
||||
|
@ -150,26 +146,13 @@ 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)
|
||||
```
|
||||
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
|
||||
|
||||
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
|
||||
hardware page access where possible. Writing a page (128 bytes) takes the same
|
||||
time (~5ms) as writing a single byte.
|
||||
hardware page access where possible. Writing a page takes the same time (~5ms)
|
||||
as writing a single byte.
|
||||
|
||||
#### 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
|
||||
identify the chip.
|
||||
|
||||
#### get_page_size
|
||||
|
||||
Return the page size in bytes.
|
||||
|
||||
### 4.1.4 Methods providing the block protocol
|
||||
|
||||
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()`
|
||||
`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
|
||||
|
||||
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
|
||||
|
||||
This assumes a Pyboard 1.x or Pyboard D with EEPROM(s) wired as above. It
|
||||
provides the following.
|
||||
This assumes a Pyboard 1.x or Pyboard D with EEPROM(s) wired as above. On other
|
||||
hardware, adapt `get_eep` at the start of the script. It provides the following.
|
||||
|
||||
## 5.1 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
|
||||
primarily tests the driver: as a hardware test it is not exhaustive.
|
||||
reports how many chips can be accessed. The current page size is printed and its
|
||||
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()
|
||||
|
||||
This is a hardware test. Tests the entire array. Fills each 128 byte page with
|
||||
random data, reads it back, and checks the outcome. Existing array data will be
|
||||
lost.
|
||||
This is a hardware test. Tests the entire array. Fills the array with random
|
||||
data in blocks of 256 byes. After each block is written, it is read back and the
|
||||
contents compared to the data written. Existing array data will be lost.
|
||||
|
||||
## 5.3 fstest(format=False)
|
||||
|
||||
|
|
|
@ -18,9 +18,12 @@ The driver has the following attributes:
|
|||
7. Alternatively it can support byte-level access using Python slice syntax.
|
||||
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
|
||||
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)
|
||||
```
|
||||
|
||||
##### [Main readme](../../README.md)
|
||||
|
||||
# 2. Connections
|
||||
|
||||
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 eeprom_spi import EEPROM
|
||||
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
|
||||
os.VfsLfs2.mkfs(eep) # Omit this to mount an existing filesystem
|
||||
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
|
||||
has high integrity owing to the hardware longevity. Typical use-cases involve
|
||||
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.
|
||||
|
||||
## 4.1 The EEPROM class
|
||||
|
||||
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
|
||||
|
||||
|
@ -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.
|
||||
|
||||
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
|
||||
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.
|
||||
4. `verbose=True` If `True`, the constructor issues information on the EEPROM
|
||||
devices it has detected.
|
||||
3. `size` Chip size in KiB. Set to 256 for the STM chip, 128 for the Microchip.
|
||||
4. `verbose=True` If `True`, the constructor performs a presence check for an
|
||||
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
|
||||
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
|
||||
specified the platform will produce the highest available frequency not
|
||||
|
@ -152,7 +165,7 @@ of single byte access:
|
|||
from machine import SPI, Pin
|
||||
from eeprom_spi import EEPROM
|
||||
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
|
||||
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 eeprom_spi import EEPROM
|
||||
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))
|
||||
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 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
|
||||
|
||||
|
@ -201,7 +214,11 @@ identify the chip.
|
|||
|
||||
#### 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
|
||||
|
||||
|
@ -216,6 +233,19 @@ their use in application code is not recommended.
|
|||
`writeblocks()`
|
||||
`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
|
||||
|
||||
A sample application: saving a configuration dict (which might be large and
|
||||
|
@ -225,7 +255,7 @@ import ujson
|
|||
from machine import SPI, Pin
|
||||
from eeprom_spi import EEPROM
|
||||
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
|
||||
wdata = ujson.dumps(d).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
|
||||
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.
|
||||
On other hardware, adapt `cspins` and `get_eep` at the start of the script.
|
||||
|
||||
## 5.1 test(stm=False)
|
||||
|
||||
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
|
||||
primarily tests the driver: as a hardware test it is not exhaustive.
|
||||
reports how many chips can be accessed. The current page size is printed and its
|
||||
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)
|
||||
|
||||
This is a hardware test. Tests the entire array. Fills each 256 byte page with
|
||||
random data, reads it back, and checks the outcome. Existing array data will be
|
||||
lost.
|
||||
This is a hardware test. Tests the entire array. Fills the array with random
|
||||
data in blocks of 256 byes. After each block is written, it is read back and the
|
||||
contents compared to the data written. Existing array data will be lost.
|
||||
|
||||
## 5.3 fstest(format=False, stm=False)
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ def get_eep(stm):
|
|||
if stm:
|
||||
eep = EEPROM(SPI(2, baudrate=5_000_000), cspins, 256)
|
||||
else:
|
||||
eep = EEPROM(SPI(2, baudrate=20_000_000), cspins)
|
||||
eep = EEPROM(SPI(2, baudrate=20_000_000), cspins, 128)
|
||||
print("Instantiated EEPROM")
|
||||
return eep
|
||||
|
||||
|
@ -28,13 +28,19 @@ def get_eep(stm):
|
|||
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 *****
|
||||
|
@ -72,9 +78,7 @@ def test(stm=False):
|
|||
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)
|
||||
)
|
||||
print("Fail at address {} data {} should be {}".format(sa + v, eep[sa + v], v))
|
||||
break
|
||||
else:
|
||||
print("Test of byte addressing passed")
|
||||
|
@ -101,6 +105,14 @@ def test(stm=False):
|
|||
print(res)
|
||||
else:
|
||||
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 *****
|
||||
|
@ -137,8 +149,9 @@ def cptest(stm=False): # Assumes pre-existing filesystem of either type
|
|||
print("Fail mounting device. Have you formatted it?")
|
||||
return
|
||||
print("Mounted device.")
|
||||
cp("eep_spi.py", "/eeprom/")
|
||||
cp("eeprom_spi.py", "/eeprom/")
|
||||
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"))
|
||||
|
||||
|
@ -146,23 +159,29 @@ def cptest(stm=False): # Assumes pre-existing filesystem of either type
|
|||
# ***** TEST OF HARDWARE *****
|
||||
def full_test(stm=False):
|
||||
eep = get_eep(stm)
|
||||
page = 0
|
||||
block = 0
|
||||
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("Page {} passed".format(page))
|
||||
print(f"Block {block} passed")
|
||||
else:
|
||||
print("Page {} readback failed.".format(page))
|
||||
print(f"Block {block} readback failed.")
|
||||
break
|
||||
page += 1
|
||||
block += 1
|
||||
|
||||
|
||||
test_str = """Available tests (see SPI.md):
|
||||
test(stm=False) Basic hardware test.
|
||||
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).
|
||||
def help():
|
||||
test_str = """Available commands (see SPI.md):
|
||||
help() Print this text.
|
||||
test(stm=False) Basic hardware test.
|
||||
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()
|
||||
|
|
|
@ -2,114 +2,89 @@
|
|||
# tested devices).
|
||||
|
||||
# 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
|
||||
from os import urandom
|
||||
from micropython import const
|
||||
from bdevice import BlockDevice
|
||||
from bdevice import EepromDevice
|
||||
|
||||
# Supported instruction set - common to both chips:
|
||||
_READ = const(3)
|
||||
_WRITE = const(2)
|
||||
_WREN = const(6) # Write enable
|
||||
_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.
|
||||
class EEPROM(BlockDevice):
|
||||
def __init__(self, spi, cspins, size=128, verbose=True, block_size=9):
|
||||
# args: virtual block size in bits, no. of chips, bytes in each chip
|
||||
# args: SPI bus, tuple of CS Pin instances, chip size in KiB
|
||||
# verbose: Test for chip presence and report
|
||||
# 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):
|
||||
print("Warning: possible unsupported chip. Size:", size)
|
||||
super().__init__(block_size, len(cspins), size * 1024)
|
||||
self._stm = size == 256
|
||||
print(f"Warning: possible unsupported chip. Size: {size}KiB")
|
||||
self._spi = spi
|
||||
self._cspins = cspins
|
||||
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._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]
|
||||
def _stm_rdid(self, n):
|
||||
cs = self._cspins[n]
|
||||
# Low level device presence detect. Reads a location, then writes to it. If
|
||||
# a write value is passed, uses that, otherwise writes the one's complement
|
||||
# of the value read.
|
||||
def _devtest(self, cs, la, v=None):
|
||||
buf = bytearray(1)
|
||||
mvp = self._mvp
|
||||
mvp[:] = b"\0\0\0\0\0"
|
||||
mvp[0] = _RDID_STM
|
||||
mvp[:] = b"\0" * 5
|
||||
# mvp[1] = la >> 16
|
||||
# mvp[2] = (la >> 8) & 0xFF
|
||||
# mvp[3] = la & 0xFF
|
||||
mvp[0] = _READ
|
||||
cs(0)
|
||||
self._spi.write_readinto(mvp, mvp)
|
||||
self._spi.write(mvp[:4])
|
||||
res = self._spi.read(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
|
||||
cs(0)
|
||||
self._spi.write(mvp[:1]) # Enable write
|
||||
self._spi.write(mvp[:1])
|
||||
cs(1)
|
||||
mvp[:] = b"\0\0\0\0\0"
|
||||
mvp[0] = _WRID_STM
|
||||
mvp[4] = _STM_ID
|
||||
mvp[0] = _WRITE
|
||||
cs(0)
|
||||
self._spi.write(mvp)
|
||||
cs(1)
|
||||
self._wait_rdy()
|
||||
self._spi.write(mvp[:4])
|
||||
buf[0] = res[0] ^ 0xFF if v is None else v
|
||||
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 _stm_scan(self):
|
||||
def 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):
|
||||
self._ccs = cs
|
||||
if self._stm_rdid(n) != _STM_ID:
|
||||
self._stm_wrid(n)
|
||||
if self._stm_rdid(n) != _STM_ID:
|
||||
raise RuntimeError("M95M02 chip not found at cs[{}].".format(n))
|
||||
old = self._devtest(cs, la)
|
||||
new = self._devtest(cs, la, old)
|
||||
if old != new ^ 0xFF:
|
||||
raise RuntimeError(f"Chip not found at cs[{n}]")
|
||||
print(f"{n + 1} chips detected.")
|
||||
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):
|
||||
if self._stm:
|
||||
raise RuntimeError("Erase not available on STM chip")
|
||||
mvp = self._mvp
|
||||
for cs in self._cspins: # For each chip
|
||||
mvp[0] = _WREN
|
||||
cs(0)
|
||||
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
|
||||
block = b"\0" * 256
|
||||
for n in range(0, self._a_bytes, 256):
|
||||
self[n : n + 256] = block
|
||||
|
||||
def _wait_rdy(self): # After a write, wait for device to become ready
|
||||
mvp = self._mvp
|
||||
|
@ -134,7 +109,7 @@ class EEPROM(BlockDevice):
|
|||
mvp[1] = la >> 16
|
||||
mvp[2] = (la >> 8) & 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)
|
||||
|
||||
# Read or write multiple bytes at an arbitrary address
|
||||
|
|
Ładowanie…
Reference in New Issue