kopia lustrzana https://github.com/peterhinch/micropython_eeprom
Porównaj commity
6 Commity
4a8c00bf98
...
e06fd66bb3
Autor | SHA1 | Data |
---|---|---|
Peter Hinch | e06fd66bb3 | |
Peter Hinch | 9a9c588f9b | |
Peter Hinch | a5ebd63bfa | |
Peter Hinch | ab0adab7d8 | |
Peter Hinch | b26867febc | |
Abel Deuring | d3bdef839a |
47
bdevice.py
47
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
|
||||
|
||||
|
@ -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,43 @@ 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)
|
||||
# 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
|
||||
|
||||
# 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.
|
||||
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 +153,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 :])
|
||||
|
|
|
@ -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,34 +131,28 @@ 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. 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. See
|
||||
[4.1.5 Page size](./I2C.md#414-page-size) for issues surrounding this.
|
||||
|
||||
With `addr` and `max_chips_count` override, it's possible to make multiple
|
||||
configuration
|
||||
|
||||
example:
|
||||
|
||||
array with custom chips count:
|
||||
```python
|
||||
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.
|
||||
|
||||
individual chip usage:
|
||||
```python
|
||||
eeprom0 = EEPROM( i2c, addr=0x50, max_chips_count=1 )
|
||||
eeprom1 = EEPROM( i2c, addr=0x51, max_chips_count=1 )
|
||||
```
|
||||
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)
|
||||
```
|
||||
|
||||
### 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__`
|
||||
|
||||
|
@ -211,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
|
||||
|
@ -224,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
|
||||
|
@ -256,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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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