Porównaj commity

...

6 Commity

Autor SHA1 Wiadomość Data
Peter Hinch e06fd66bb3 spi: Bug in presence detect. 2024-01-12 08:39:07 +00:00
Peter Hinch 9a9c588f9b bdevice.py: Protect _set_pagesize against exceptions. 2024-01-10 08:20:39 +00:00
Peter Hinch a5ebd63bfa Merge branch 'master' of https://github.com/peterhinch/micropython_eeprom
Merge _getaddr bugfix.
2024-01-09 14:41:13 +00:00
Peter Hinch ab0adab7d8 Fix bugs in I2C EEPROM. Auto detect page size. 2024-01-09 14:38:53 +00:00
Peter Hinch b26867febc
Merge pull request #23 from adeuring/master
Fix the page end calculation in eeprom_i2c.py for multiple chip arrays.
2024-01-09 09:51:51 +00:00
Abel Deuring d3bdef839a
Fix the page end calculation in eeprom_i2c.py for multiple chip arrays. 2024-01-08 16:21:29 +01:00
7 zmienionych plików z 306 dodań i 180 usunięć

Wyświetl plik

@ -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 :])

Wyświetl plik

@ -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)

Wyświetl plik

@ -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)

Wyświetl plik

@ -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

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.
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)

Wyświetl plik

@ -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()

Wyświetl plik

@ -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