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
|
# Documentation in BASE_CLASSES.md
|
||||||
|
|
||||||
# Released under the MIT License (MIT). See LICENSE.
|
# Released under the MIT License (MIT). See LICENSE.
|
||||||
# Copyright (c) 2019 Peter Hinch
|
# Copyright (c) 2019-2024 Peter Hinch
|
||||||
|
|
||||||
from micropython import const
|
from micropython import const
|
||||||
|
|
||||||
|
@ -80,6 +80,7 @@ class BlockDevice:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
# Hardware agnostic base class for EEPROM arrays
|
||||||
class EepromDevice(BlockDevice):
|
class EepromDevice(BlockDevice):
|
||||||
def __init__(self, nbits, nchips, chip_size, page_size, verbose):
|
def __init__(self, nbits, nchips, chip_size, page_size, verbose):
|
||||||
super().__init__(nbits, nchips, chip_size)
|
super().__init__(nbits, nchips, chip_size)
|
||||||
|
@ -96,26 +97,22 @@ class EepromDevice(BlockDevice):
|
||||||
def get_page_size(self): # For test script
|
def get_page_size(self): # For test script
|
||||||
return self._page_size
|
return self._page_size
|
||||||
|
|
||||||
|
# Measuring page size should not be done in production code. See docs.
|
||||||
def _set_pagesize(self, page_size):
|
def _set_pagesize(self, page_size):
|
||||||
if page_size is None: # Measure it
|
if page_size is None: # Measure it.
|
||||||
self._psize(16) # Conservative
|
self._psize(16) # Conservative
|
||||||
old = self[:129] # Save old contents (nonvolatile!)
|
old = self[:129] # Save old contents (nonvolatile!)
|
||||||
|
self._psize(256) # Ambitious
|
||||||
|
r = (16, 32, 64, 128) # Legal page sizes + 256
|
||||||
|
for x in r:
|
||||||
|
self[x] = 255 # Write single bytes, don't invoke page write
|
||||||
|
self[0:129] = b"\0" * 129 # Zero 129 bytes attempting to use 256 byte pages
|
||||||
try:
|
try:
|
||||||
self._psize(256) # Ambitious
|
ps = next(z for z in r if self[z])
|
||||||
r = (16, 32, 64, 128) # Legal page sizes + 256
|
except StopIteration:
|
||||||
for x in r:
|
ps = 256
|
||||||
self[x] = 255 # Write single bytes, don't invoke page write
|
self._psize(ps)
|
||||||
self[0:129] = b"\0" * 129 # Zero 129 bytes attempting to use 256 byte pages
|
self[:129] = old
|
||||||
try:
|
|
||||||
ps = next(z for z in r if self[z])
|
|
||||||
except StopIteration:
|
|
||||||
ps = 256
|
|
||||||
self._psize(ps)
|
|
||||||
self[:129] = old
|
|
||||||
except: # If anything goes wrong, restore old data and raise
|
|
||||||
for n, v in enumerate(old):
|
|
||||||
self[n] = v
|
|
||||||
raise
|
|
||||||
else: # Validated page_size was supplied
|
else: # Validated page_size was supplied
|
||||||
self._psize(page_size)
|
self._psize(page_size)
|
||||||
|
|
||||||
|
|
|
@ -131,17 +131,13 @@ Arguments:
|
||||||
devices it has detected.
|
devices it has detected.
|
||||||
4. `block_size=9` The block size reported to the filesystem. The size in bytes
|
4. `block_size=9` The block size reported to the filesystem. The size in bytes
|
||||||
is `2**block_size` so is 512 bytes by default.
|
is `2**block_size` so is 512 bytes by default.
|
||||||
5. `addr` override base address for first chip.
|
5. `addr` Override base address for first chip. See
|
||||||
6. `max_chips_count` override max_chips_count.
|
[4.1.6 Special configurations](./I2C.md#416-special-configurations).
|
||||||
|
6. `max_chips_count` Override max_chips_count - see above reference.
|
||||||
7. `page_size=None` EEPROM chips have a page buffer. By default the driver
|
7. `page_size=None` EEPROM chips have a page buffer. By default the driver
|
||||||
determines the size of this automatically. It is possible to override this by
|
determines the size of this automatically. It is possible to override this by
|
||||||
passing an integer being the page size in bytes: 16, 32, 64, 128 or 256. The
|
passing an integer being the page size in bytes: 16, 32, 64, 128 or 256. See
|
||||||
page size may vary between chips from different manufacturers even for the
|
[4.1.5 Page size](./I2C.md#414-page-size) for issues surrounding this.
|
||||||
same storage size. Note that specifying too large a value will most likely lead
|
|
||||||
to data corruption in write operations and will cause the test script's basic
|
|
||||||
test to fail. The correct value for a device may be found in in the chip
|
|
||||||
datasheet. It is also reported if `verbose` is set. Auto-detecting page size
|
|
||||||
carries a risk of data loss if power fails while auto-detect is in progress.
|
|
||||||
|
|
||||||
In most cases only the first two arguments are used, with an array being
|
In most cases only the first two arguments are used, with an array being
|
||||||
instantiated with (for example):
|
instantiated with (for example):
|
||||||
|
@ -150,26 +146,13 @@ from machine import I2C
|
||||||
from eeprom_i2c import EEPROM, T24C512
|
from eeprom_i2c import EEPROM, T24C512
|
||||||
eep = EEPROM(I2C(2), T24C512)
|
eep = EEPROM(I2C(2), T24C512)
|
||||||
```
|
```
|
||||||
It is possible to configure multiple chips as multiple arrays. This is done by
|
|
||||||
means of the `addr` and `max_chips_count` args. Examples:
|
|
||||||
```python
|
|
||||||
eeprom0 = EEPROM(i2c, max_chips_count = 2)
|
|
||||||
eeprom1 = EEPROM(i2c, addr = 0x52, max_chips_count = 2)
|
|
||||||
```
|
|
||||||
1st array uses address 0x50 and 0x51 and 2nd uses address 0x52 and 0x53.
|
|
||||||
|
|
||||||
individual chip usage:
|
|
||||||
```python
|
|
||||||
eeprom0 = EEPROM(i2c, addr = 0x50, max_chips_count = 1)
|
|
||||||
eeprom1 = EEPROM(i2c, addr = 0x51, max_chips_count = 1)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4.1.2 Methods providing byte level access
|
### 4.1.2 Methods providing byte level access
|
||||||
|
|
||||||
It is possible to read and write individual bytes or arrays of arbitrary size.
|
It is possible to read and write individual bytes or arrays of arbitrary size.
|
||||||
Larger arrays are faster, especially when writing: the driver uses the chip's
|
Larger arrays are faster, especially when writing: the driver uses the chip's
|
||||||
hardware page access where possible. Writing a page (128 bytes) takes the same
|
hardware page access where possible. Writing a page takes the same time (~5ms)
|
||||||
time (~5ms) as writing a single byte.
|
as writing a single byte.
|
||||||
|
|
||||||
#### 4.1.2.1 `__getitem__` and `__setitem__`
|
#### 4.1.2.1 `__getitem__` and `__setitem__`
|
||||||
|
|
||||||
|
@ -223,6 +206,10 @@ Other than for debugging there is no need to call `scan()`: the constructor
|
||||||
will throw a `RuntimeError` if it fails to communicate with and correctly
|
will throw a `RuntimeError` if it fails to communicate with and correctly
|
||||||
identify the chip.
|
identify the chip.
|
||||||
|
|
||||||
|
#### get_page_size
|
||||||
|
|
||||||
|
Return the page size in bytes.
|
||||||
|
|
||||||
### 4.1.4 Methods providing the block protocol
|
### 4.1.4 Methods providing the block protocol
|
||||||
|
|
||||||
These are provided by the base class. For the protocol definition see
|
These are provided by the base class. For the protocol definition see
|
||||||
|
@ -236,6 +223,37 @@ their use in application code is not recommended.
|
||||||
`writeblocks()`
|
`writeblocks()`
|
||||||
`ioctl()`
|
`ioctl()`
|
||||||
|
|
||||||
|
### 4.1.5 Page size
|
||||||
|
|
||||||
|
EEPROM chips have a RAM buffer enabling fast writing of data blocks. Writing a
|
||||||
|
page takes the same time (~5ms) as writing a single byte. The page size may vary
|
||||||
|
between chips from different manufacturers even for the same storage size.
|
||||||
|
Specifying too large a value will most likely lead to data corruption in write
|
||||||
|
operations and will cause the test script's basic test to fail. Too small a
|
||||||
|
value will impact write performance. The correct value for a device may be found
|
||||||
|
in in the chip datasheet. It is also reported if `verbose` is set and when
|
||||||
|
running the test scripts.
|
||||||
|
|
||||||
|
Auto-detecting page size carries a risk of data loss if power fails while
|
||||||
|
auto-detect is in progress. In production code the value should be specified
|
||||||
|
explicitly.
|
||||||
|
|
||||||
|
### 4.1.6 Special configurations
|
||||||
|
|
||||||
|
It is possible to configure multiple chips as multiple arrays. This is done by
|
||||||
|
means of the `addr` and `max_chips_count` args. Examples:
|
||||||
|
```python
|
||||||
|
eeprom0 = EEPROM(i2c, max_chips_count = 2)
|
||||||
|
eeprom1 = EEPROM(i2c, addr = 0x52, max_chips_count = 2)
|
||||||
|
```
|
||||||
|
1st array uses address 0x50 and 0x51 and 2nd uses address 0x52 and 0x53.
|
||||||
|
|
||||||
|
Individual chip usage:
|
||||||
|
```python
|
||||||
|
eeprom0 = EEPROM(i2c, addr = 0x50, max_chips_count = 1)
|
||||||
|
eeprom1 = EEPROM(i2c, addr = 0x51, max_chips_count = 1)
|
||||||
|
```
|
||||||
|
|
||||||
## 4.2 Byte addressing usage example
|
## 4.2 Byte addressing usage example
|
||||||
|
|
||||||
A sample application: saving a configuration dict (which might be large and
|
A sample application: saving a configuration dict (which might be large and
|
||||||
|
@ -268,20 +286,21 @@ possible to use JSON/pickle to store objects in a filesystem.
|
||||||
|
|
||||||
# 5. Test program eep_i2c.py
|
# 5. Test program eep_i2c.py
|
||||||
|
|
||||||
This assumes a Pyboard 1.x or Pyboard D with EEPROM(s) wired as above. It
|
This assumes a Pyboard 1.x or Pyboard D with EEPROM(s) wired as above. On other
|
||||||
provides the following.
|
hardware, adapt `get_eep` at the start of the script. It provides the following.
|
||||||
|
|
||||||
## 5.1 test()
|
## 5.1 test()
|
||||||
|
|
||||||
This performs a basic test of single and multi-byte access to chip 0. The test
|
This performs a basic test of single and multi-byte access to chip 0. The test
|
||||||
reports how many chips can be accessed. Existing array data will be lost. This
|
reports how many chips can be accessed. The current page size is printed and its
|
||||||
primarily tests the driver: as a hardware test it is not exhaustive.
|
validity is tested. Existing array data will be lost. This primarily tests the
|
||||||
|
driver: as a hardware test it is not exhaustive.
|
||||||
|
|
||||||
## 5.2 full_test()
|
## 5.2 full_test()
|
||||||
|
|
||||||
This is a hardware test. Tests the entire array. Fills each 128 byte page with
|
This is a hardware test. Tests the entire array. Fills the array with random
|
||||||
random data, reads it back, and checks the outcome. Existing array data will be
|
data in blocks of 256 byes. After each block is written, it is read back and the
|
||||||
lost.
|
contents compared to the data written. Existing array data will be lost.
|
||||||
|
|
||||||
## 5.3 fstest(format=False)
|
## 5.3 fstest(format=False)
|
||||||
|
|
||||||
|
|
|
@ -18,9 +18,12 @@ The driver has the following attributes:
|
||||||
7. Alternatively it can support byte-level access using Python slice syntax.
|
7. Alternatively it can support byte-level access using Python slice syntax.
|
||||||
8. RAM allocations are minimised. Buffer sizes are tiny.
|
8. RAM allocations are minimised. Buffer sizes are tiny.
|
||||||
|
|
||||||
##### [Main readme](../../README.md)
|
## 1.1 Notes
|
||||||
|
|
||||||
## 1.1 This document
|
As of Jan 2024 this driver has been updated to fix a bug where the device page
|
||||||
|
size was less than 256. A further aim was to make the driver more generic, with
|
||||||
|
a better chance of working with other SPI EEPROM chips. The constructor has
|
||||||
|
additional optional args to support this.
|
||||||
|
|
||||||
Code samples assume one or more Microchip devices. If using the STM chip the
|
Code samples assume one or more Microchip devices. If using the STM chip the
|
||||||
SPI baudrate should be 5MHz and the chip size must be specified to the `EEPROM`
|
SPI baudrate should be 5MHz and the chip size must be specified to the `EEPROM`
|
||||||
|
@ -29,6 +32,8 @@ constructor, e.g.:
|
||||||
eep = EEPROM(SPI(2, baudrate=5_000_000), cspins, 256)
|
eep = EEPROM(SPI(2, baudrate=5_000_000), cspins, 256)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
##### [Main readme](../../README.md)
|
||||||
|
|
||||||
# 2. Connections
|
# 2. Connections
|
||||||
|
|
||||||
Any SPI interface may be used. The table below assumes a Pyboard running SPI(2)
|
Any SPI interface may be used. The table below assumes a Pyboard running SPI(2)
|
||||||
|
@ -91,7 +96,7 @@ import os
|
||||||
from machine import SPI, Pin
|
from machine import SPI, Pin
|
||||||
from eeprom_spi import EEPROM
|
from eeprom_spi import EEPROM
|
||||||
cspins = (Pin(Pin.board.Y5, Pin.OUT, value=1), Pin(Pin.board.Y4, Pin.OUT, value=1))
|
cspins = (Pin(Pin.board.Y5, Pin.OUT, value=1), Pin(Pin.board.Y4, Pin.OUT, value=1))
|
||||||
eep = EEPROM(SPI(2, baudrate=20_000_000), cspins)
|
eep = EEPROM(SPI(2, baudrate=20_000_000), cspins, 128) # 128KiB chips
|
||||||
# Format the filesystem
|
# Format the filesystem
|
||||||
os.VfsLfs2.mkfs(eep) # Omit this to mount an existing filesystem
|
os.VfsLfs2.mkfs(eep) # Omit this to mount an existing filesystem
|
||||||
os.mount(eep,'/eeprom')
|
os.mount(eep,'/eeprom')
|
||||||
|
@ -103,14 +108,15 @@ Note that, at the outset, you need to decide whether to use the array as a
|
||||||
mounted filesystem or as a byte array. The filesystem is relatively small but
|
mounted filesystem or as a byte array. The filesystem is relatively small but
|
||||||
has high integrity owing to the hardware longevity. Typical use-cases involve
|
has high integrity owing to the hardware longevity. Typical use-cases involve
|
||||||
files which are frequently updated. These include files used for storing Python
|
files which are frequently updated. These include files used for storing Python
|
||||||
objects serialised using Pickle/ujson or files holding a btree database.
|
objects serialised using Pickle/json or files holding a btree database.
|
||||||
|
|
||||||
The SPI bus must be instantiated using the `machine` module.
|
The SPI bus must be instantiated using the `machine` module.
|
||||||
|
|
||||||
## 4.1 The EEPROM class
|
## 4.1 The EEPROM class
|
||||||
|
|
||||||
An `EEPROM` instance represents a logical EEPROM: this may consist of multiple
|
An `EEPROM` instance represents a logical EEPROM: this may consist of multiple
|
||||||
physical devices on a common SPI bus.
|
physical devices on a common SPI bus. Alternatively multiple EEPROM instances
|
||||||
|
may share the bus, differentiated by their CS pins.
|
||||||
|
|
||||||
### 4.1.1 Constructor
|
### 4.1.1 Constructor
|
||||||
|
|
||||||
|
@ -119,14 +125,21 @@ each chip select line an EEPROM array is instantiated. A `RuntimeError` will be
|
||||||
raised if a device is not detected on a CS line.
|
raised if a device is not detected on a CS line.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
1. `spi` Mandatory. An initialised SPI bus created by `machine`.
|
1. `spi` An initialised SPI bus created by `machine`.
|
||||||
2. `cspins` A list or tuple of `Pin` instances. Each `Pin` must be initialised
|
2. `cspins` A list or tuple of `Pin` instances. Each `Pin` must be initialised
|
||||||
as an output (`Pin.OUT`) and with `value=1` and be created by `machine`.
|
as an output (`Pin.OUT`) and with `value=1` and be created by `machine`.
|
||||||
3. `size=128` Chip size in KiB. Set to 256 for the STM chip.
|
3. `size` Chip size in KiB. Set to 256 for the STM chip, 128 for the Microchip.
|
||||||
4. `verbose=True` If `True`, the constructor issues information on the EEPROM
|
4. `verbose=True` If `True`, the constructor performs a presence check for an
|
||||||
devices it has detected.
|
EEPROM on each chip select pin and reports devices it has detected. See
|
||||||
|
[4.1.5 Auto detection](./SPI.md#415-auto-detection) for observations on
|
||||||
|
production code.
|
||||||
5. `block_size=9` The block size reported to the filesystem. The size in bytes
|
5. `block_size=9` The block size reported to the filesystem. The size in bytes
|
||||||
is `2**block_size` so is 512 bytes by default.
|
is `2**block_size` so is 512 bytes by default.
|
||||||
|
6. `page_size=None` EEPROM devices have a RAM buffer enabling fast writes. The
|
||||||
|
driver determines this automatically by default. It is possible to override
|
||||||
|
this by passing an integer being the page size in bytes: 16, 32, 64, 128 or 256.
|
||||||
|
See [4.1.5 Auto detection](./SPI.md#415-auto-detection) for reasons why this
|
||||||
|
is advised in production code.
|
||||||
|
|
||||||
SPI baudrate: The 25LC1024 supports baudrates of upto 20MHz. If this value is
|
SPI baudrate: The 25LC1024 supports baudrates of upto 20MHz. If this value is
|
||||||
specified the platform will produce the highest available frequency not
|
specified the platform will produce the highest available frequency not
|
||||||
|
@ -152,7 +165,7 @@ of single byte access:
|
||||||
from machine import SPI, Pin
|
from machine import SPI, Pin
|
||||||
from eeprom_spi import EEPROM
|
from eeprom_spi import EEPROM
|
||||||
cspins = (Pin(Pin.board.Y5, Pin.OUT, value=1), Pin(Pin.board.Y4, Pin.OUT, value=1))
|
cspins = (Pin(Pin.board.Y5, Pin.OUT, value=1), Pin(Pin.board.Y4, Pin.OUT, value=1))
|
||||||
eep = EEPROM(SPI(2, baudrate=20_000_000), cspins)
|
eep = EEPROM(SPI(2, baudrate=20_000_000), cspins, 128)
|
||||||
eep[2000] = 42
|
eep[2000] = 42
|
||||||
print(eep[2000]) # Return an integer
|
print(eep[2000]) # Return an integer
|
||||||
```
|
```
|
||||||
|
@ -162,7 +175,7 @@ writing, the size of the slice must match the length of the buffer:
|
||||||
from machine import SPI, Pin
|
from machine import SPI, Pin
|
||||||
from eeprom_spi import EEPROM
|
from eeprom_spi import EEPROM
|
||||||
cspins = (Pin(Pin.board.Y5, Pin.OUT, value=1), Pin(Pin.board.Y4, Pin.OUT, value=1))
|
cspins = (Pin(Pin.board.Y5, Pin.OUT, value=1), Pin(Pin.board.Y4, Pin.OUT, value=1))
|
||||||
eep = EEPROM(SPI(2, baudrate=20_000_000), cspins)
|
eep = EEPROM(SPI(2, baudrate=20_000_000), cspins, 128)
|
||||||
eep[2000:2002] = bytearray((42, 43))
|
eep[2000:2002] = bytearray((42, 43))
|
||||||
print(eep[2000:2002]) # Returns a bytearray
|
print(eep[2000:2002]) # Returns a bytearray
|
||||||
```
|
```
|
||||||
|
@ -187,7 +200,7 @@ advantage when reading of using a pre-allocated buffer. Arguments:
|
||||||
#### The len operator
|
#### The len operator
|
||||||
|
|
||||||
The size of the EEPROM array in bytes may be retrieved by issuing `len(eep)`
|
The size of the EEPROM array in bytes may be retrieved by issuing `len(eep)`
|
||||||
where `eep` is the `EEPROM` instance.
|
where `eep` is an `EEPROM` instance.
|
||||||
|
|
||||||
#### scan
|
#### scan
|
||||||
|
|
||||||
|
@ -201,7 +214,11 @@ identify the chip.
|
||||||
|
|
||||||
#### erase
|
#### erase
|
||||||
|
|
||||||
Erases the entire array. Available only on the Microchip device.
|
Zero the entire array. Can take several seconds.
|
||||||
|
|
||||||
|
#### get_page_size
|
||||||
|
|
||||||
|
Return the page size in bytes.
|
||||||
|
|
||||||
### 4.1.4 Methods providing the block protocol
|
### 4.1.4 Methods providing the block protocol
|
||||||
|
|
||||||
|
@ -216,6 +233,19 @@ their use in application code is not recommended.
|
||||||
`writeblocks()`
|
`writeblocks()`
|
||||||
`ioctl()`
|
`ioctl()`
|
||||||
|
|
||||||
|
### 4.1.5 Auto detection
|
||||||
|
|
||||||
|
The driver constructor uses auto-detection in two circumstances:
|
||||||
|
* If `verbose` is specified, it checks each chip select for chip presence.
|
||||||
|
* If `page_size` is set to `None` the value is determined by measurement.
|
||||||
|
|
||||||
|
In both cases data is written to the chips, then restored from RAM. If a power
|
||||||
|
outage were to occur while either process was in progress, corruption could
|
||||||
|
occur. It is therefore recommended that, in production code, `verbose` is
|
||||||
|
`False` and `page_size` is set to an integer. The page size may be determined
|
||||||
|
from the chip datasheet. It is also printed on instantiation if `verbose` is
|
||||||
|
set: running any of the test scripts will do this.
|
||||||
|
|
||||||
## 4.2 Byte addressing usage example
|
## 4.2 Byte addressing usage example
|
||||||
|
|
||||||
A sample application: saving a configuration dict (which might be large and
|
A sample application: saving a configuration dict (which might be large and
|
||||||
|
@ -225,7 +255,7 @@ import ujson
|
||||||
from machine import SPI, Pin
|
from machine import SPI, Pin
|
||||||
from eeprom_spi import EEPROM
|
from eeprom_spi import EEPROM
|
||||||
cspins = (Pin(Pin.board.Y5, Pin.OUT, value=1), Pin(Pin.board.Y4, Pin.OUT, value=1))
|
cspins = (Pin(Pin.board.Y5, Pin.OUT, value=1), Pin(Pin.board.Y4, Pin.OUT, value=1))
|
||||||
eep = EEPROM(SPI(2, baudrate=20_000_000), cspins)
|
eep = EEPROM(SPI(2, baudrate=20_000_000), cspins, 128)
|
||||||
d = {1:'one', 2:'two'} # Some kind of large object
|
d = {1:'one', 2:'two'} # Some kind of large object
|
||||||
wdata = ujson.dumps(d).encode('utf8')
|
wdata = ujson.dumps(d).encode('utf8')
|
||||||
sl = '{:10d}'.format(len(wdata)).encode('utf8')
|
sl = '{:10d}'.format(len(wdata)).encode('utf8')
|
||||||
|
@ -252,18 +282,20 @@ possible to use JSON/pickle to store objects in a filesystem.
|
||||||
This assumes a Pyboard 1.x or Pyboard D with two EEPROMs wired to SPI(2) as
|
This assumes a Pyboard 1.x or Pyboard D with two EEPROMs wired to SPI(2) as
|
||||||
above with chip selects connected to pins `Y4` and `Y5`. It provides the
|
above with chip selects connected to pins `Y4` and `Y5`. It provides the
|
||||||
following. In all cases the stm arg should be `True` if using the STM chips.
|
following. In all cases the stm arg should be `True` if using the STM chips.
|
||||||
|
On other hardware, adapt `cspins` and `get_eep` at the start of the script.
|
||||||
|
|
||||||
## 5.1 test(stm=False)
|
## 5.1 test(stm=False)
|
||||||
|
|
||||||
This performs a basic test of single and multi-byte access to chip 0. The test
|
This performs a basic test of single and multi-byte access to chip 0. The test
|
||||||
reports how many chips can be accessed. Existing array data will be lost. This
|
reports how many chips can be accessed. The current page size is printed and its
|
||||||
primarily tests the driver: as a hardware test it is not exhaustive.
|
validity is tested. Existing array data will be lost. This primarily tests the
|
||||||
|
driver: as a hardware test it is not exhaustive.
|
||||||
|
|
||||||
## 5.2 full_test(stm=False)
|
## 5.2 full_test(stm=False)
|
||||||
|
|
||||||
This is a hardware test. Tests the entire array. Fills each 256 byte page with
|
This is a hardware test. Tests the entire array. Fills the array with random
|
||||||
random data, reads it back, and checks the outcome. Existing array data will be
|
data in blocks of 256 byes. After each block is written, it is read back and the
|
||||||
lost.
|
contents compared to the data written. Existing array data will be lost.
|
||||||
|
|
||||||
## 5.3 fstest(format=False, stm=False)
|
## 5.3 fstest(format=False, stm=False)
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ def get_eep(stm):
|
||||||
if stm:
|
if stm:
|
||||||
eep = EEPROM(SPI(2, baudrate=5_000_000), cspins, 256)
|
eep = EEPROM(SPI(2, baudrate=5_000_000), cspins, 256)
|
||||||
else:
|
else:
|
||||||
eep = EEPROM(SPI(2, baudrate=20_000_000), cspins)
|
eep = EEPROM(SPI(2, baudrate=20_000_000), cspins, 128)
|
||||||
print("Instantiated EEPROM")
|
print("Instantiated EEPROM")
|
||||||
return eep
|
return eep
|
||||||
|
|
||||||
|
@ -28,13 +28,19 @@ def get_eep(stm):
|
||||||
def cp(source, dest):
|
def cp(source, dest):
|
||||||
if dest.endswith("/"): # minimal way to allow
|
if dest.endswith("/"): # minimal way to allow
|
||||||
dest = "".join((dest, source.split("/")[-1])) # cp /sd/file /eeprom/
|
dest = "".join((dest, source.split("/")[-1])) # cp /sd/file /eeprom/
|
||||||
with open(source, "rb") as infile: # Caller should handle any OSError
|
try:
|
||||||
with open(dest, "wb") as outfile: # e.g file not found
|
with open(source, "rb") as infile: # Caller should handle any OSError
|
||||||
while True:
|
with open(dest, "wb") as outfile: # e.g file not found
|
||||||
buf = infile.read(100)
|
while True:
|
||||||
outfile.write(buf)
|
buf = infile.read(100)
|
||||||
if len(buf) < 100:
|
outfile.write(buf)
|
||||||
break
|
if len(buf) < 100:
|
||||||
|
break
|
||||||
|
except OSError as e:
|
||||||
|
if e.errno == 28:
|
||||||
|
print("Insufficient space for copy.")
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
# ***** TEST OF DRIVER *****
|
# ***** TEST OF DRIVER *****
|
||||||
|
@ -72,9 +78,7 @@ def test(stm=False):
|
||||||
eep[sa + v] = v
|
eep[sa + v] = v
|
||||||
for v in range(256):
|
for v in range(256):
|
||||||
if eep[sa + v] != v:
|
if eep[sa + v] != v:
|
||||||
print(
|
print("Fail at address {} data {} should be {}".format(sa + v, eep[sa + v], v))
|
||||||
"Fail at address {} data {} should be {}".format(sa + v, eep[sa + v], v)
|
|
||||||
)
|
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
print("Test of byte addressing passed")
|
print("Test of byte addressing passed")
|
||||||
|
@ -101,6 +105,14 @@ def test(stm=False):
|
||||||
print(res)
|
print(res)
|
||||||
else:
|
else:
|
||||||
print("Test chip boundary skipped: only one chip!")
|
print("Test chip boundary skipped: only one chip!")
|
||||||
|
pe = eep.get_page_size() # One byte past page
|
||||||
|
eep[pe] = 0xFF
|
||||||
|
eep[:257] = b"\0" * 257
|
||||||
|
print("Test page size: ", end="")
|
||||||
|
if eep[pe]:
|
||||||
|
print("FAIL")
|
||||||
|
else:
|
||||||
|
print("passed")
|
||||||
|
|
||||||
|
|
||||||
# ***** TEST OF FILESYSTEM MOUNT *****
|
# ***** TEST OF FILESYSTEM MOUNT *****
|
||||||
|
@ -137,8 +149,9 @@ def cptest(stm=False): # Assumes pre-existing filesystem of either type
|
||||||
print("Fail mounting device. Have you formatted it?")
|
print("Fail mounting device. Have you formatted it?")
|
||||||
return
|
return
|
||||||
print("Mounted device.")
|
print("Mounted device.")
|
||||||
cp("eep_spi.py", "/eeprom/")
|
cp(__file__, "/eeprom/")
|
||||||
cp("eeprom_spi.py", "/eeprom/")
|
# We may have the source file or a precompiled binary (*.mpy)
|
||||||
|
cp(__file__.replace("eep", "eeprom"), "/eeprom/")
|
||||||
print('Contents of "/eeprom": {}'.format(uos.listdir("/eeprom")))
|
print('Contents of "/eeprom": {}'.format(uos.listdir("/eeprom")))
|
||||||
print(uos.statvfs("/eeprom"))
|
print(uos.statvfs("/eeprom"))
|
||||||
|
|
||||||
|
@ -146,23 +159,29 @@ def cptest(stm=False): # Assumes pre-existing filesystem of either type
|
||||||
# ***** TEST OF HARDWARE *****
|
# ***** TEST OF HARDWARE *****
|
||||||
def full_test(stm=False):
|
def full_test(stm=False):
|
||||||
eep = get_eep(stm)
|
eep = get_eep(stm)
|
||||||
page = 0
|
block = 0
|
||||||
for sa in range(0, len(eep), 256):
|
for sa in range(0, len(eep), 256):
|
||||||
data = uos.urandom(256)
|
data = uos.urandom(256)
|
||||||
eep[sa : sa + 256] = data
|
eep[sa : sa + 256] = data
|
||||||
got = eep[sa : sa + 256]
|
got = eep[sa : sa + 256]
|
||||||
if got == data:
|
if got == data:
|
||||||
print("Page {} passed".format(page))
|
print(f"Block {block} passed")
|
||||||
else:
|
else:
|
||||||
print("Page {} readback failed.".format(page))
|
print(f"Block {block} readback failed.")
|
||||||
break
|
break
|
||||||
page += 1
|
block += 1
|
||||||
|
|
||||||
|
|
||||||
test_str = """Available tests (see SPI.md):
|
def help():
|
||||||
test(stm=False) Basic hardware test.
|
test_str = """Available commands (see SPI.md):
|
||||||
full_test(stm=False) Thorough hardware test.
|
help() Print this text.
|
||||||
fstest(format=False, stm=False) Filesystem test (see doc).
|
test(stm=False) Basic hardware test.
|
||||||
cptest(stm=False) Copy files to filesystem (see doc).
|
full_test(stm=False) Thorough hardware test.
|
||||||
|
fstest(format=False, stm=False) Filesystem test (see doc).
|
||||||
|
cptest(stm=False) Copy files to filesystem (see doc).
|
||||||
|
stm: True is 256K chip, 5MHz bus. False is 128K chip, 20MHz bus.
|
||||||
"""
|
"""
|
||||||
print(test_str)
|
print(test_str)
|
||||||
|
|
||||||
|
|
||||||
|
help()
|
||||||
|
|
|
@ -2,114 +2,89 @@
|
||||||
# tested devices).
|
# tested devices).
|
||||||
|
|
||||||
# Released under the MIT License (MIT). See LICENSE.
|
# Released under the MIT License (MIT). See LICENSE.
|
||||||
# Copyright (c) 2019-2022 Peter Hinch
|
# Copyright (c) 2019-2024 Peter Hinch
|
||||||
|
|
||||||
|
# Thanks are due to Abel Deuring for help in diagnosing and fixing a page size issue.
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
from os import urandom
|
||||||
from micropython import const
|
from micropython import const
|
||||||
from bdevice import BlockDevice
|
from bdevice import EepromDevice
|
||||||
|
|
||||||
# Supported instruction set - common to both chips:
|
# Supported instruction set - common to both chips:
|
||||||
_READ = const(3)
|
_READ = const(3)
|
||||||
_WRITE = const(2)
|
_WRITE = const(2)
|
||||||
_WREN = const(6) # Write enable
|
_WREN = const(6) # Write enable
|
||||||
_RDSR = const(5) # Read status register
|
_RDSR = const(5) # Read status register
|
||||||
# Microchip only:
|
|
||||||
_RDID = const(0xAB) # Read chip ID
|
|
||||||
_CE = const(0xC7) # Chip erase
|
|
||||||
# STM only:
|
|
||||||
_RDID_STM = const(0x83) # Read ID page
|
|
||||||
_WRID_STM = const(0x82)
|
|
||||||
_STM_ID = const(0x30) # Arbitrary ID for STM chip
|
|
||||||
# Not implemented: Write disable and Write status register
|
|
||||||
# _WRDI = const(4)
|
|
||||||
# _WRSR = const(1)
|
|
||||||
|
|
||||||
# Logical EEPROM device comprising one or more physical chips sharing an SPI bus.
|
# Logical EEPROM device comprising one or more physical chips sharing an SPI bus.
|
||||||
class EEPROM(BlockDevice):
|
# args: SPI bus, tuple of CS Pin instances, chip size in KiB
|
||||||
def __init__(self, spi, cspins, size=128, verbose=True, block_size=9):
|
# verbose: Test for chip presence and report
|
||||||
# args: virtual block size in bits, no. of chips, bytes in each chip
|
# block_size: Sector size for filesystems. See docs.
|
||||||
|
# erok: True if chip supports erase.
|
||||||
|
# page_size: None is auto detect. See docs.
|
||||||
|
class EEPROM(EepromDevice):
|
||||||
|
def __init__(self, spi, cspins, size, verbose=True, block_size=9, page_size=None):
|
||||||
if size not in (64, 128, 256):
|
if size not in (64, 128, 256):
|
||||||
print("Warning: possible unsupported chip. Size:", size)
|
print(f"Warning: possible unsupported chip. Size: {size}KiB")
|
||||||
super().__init__(block_size, len(cspins), size * 1024)
|
|
||||||
self._stm = size == 256
|
|
||||||
self._spi = spi
|
self._spi = spi
|
||||||
self._cspins = cspins
|
self._cspins = cspins
|
||||||
self._ccs = None # Chip select Pin object for current chip
|
self._ccs = None # Chip select Pin object for current chip
|
||||||
|
self._size = size * 1024 # Chip size in bytes
|
||||||
self._bufp = bytearray(5) # instruction + 3 byte address + 1 byte value
|
self._bufp = bytearray(5) # instruction + 3 byte address + 1 byte value
|
||||||
self._mvp = memoryview(self._bufp) # cost-free slicing
|
self._mvp = memoryview(self._bufp) # cost-free slicing
|
||||||
self.scan(verbose)
|
if verbose: # Test for presence of devices
|
||||||
|
self.scan()
|
||||||
|
# superclass figures out _page_size and _page_mask
|
||||||
|
super().__init__(block_size, len(cspins), self._size, page_size, verbose)
|
||||||
|
if verbose:
|
||||||
|
print(f"Total EEPROM size {self._a_bytes:,} bytes.")
|
||||||
|
|
||||||
# Read ID block ID[0]
|
# Low level device presence detect. Reads a location, then writes to it. If
|
||||||
def _stm_rdid(self, n):
|
# a write value is passed, uses that, otherwise writes the one's complement
|
||||||
cs = self._cspins[n]
|
# of the value read.
|
||||||
|
def _devtest(self, cs, la, v=None):
|
||||||
|
buf = bytearray(1)
|
||||||
mvp = self._mvp
|
mvp = self._mvp
|
||||||
mvp[:] = b"\0\0\0\0\0"
|
mvp[:] = b"\0" * 5
|
||||||
mvp[0] = _RDID_STM
|
# mvp[1] = la >> 16
|
||||||
|
# mvp[2] = (la >> 8) & 0xFF
|
||||||
|
# mvp[3] = la & 0xFF
|
||||||
|
mvp[0] = _READ
|
||||||
cs(0)
|
cs(0)
|
||||||
self._spi.write_readinto(mvp, mvp)
|
self._spi.write(mvp[:4])
|
||||||
|
res = self._spi.read(1)
|
||||||
cs(1)
|
cs(1)
|
||||||
return mvp[4]
|
|
||||||
|
|
||||||
# Write a fixed value to ID[0]
|
|
||||||
def _stm_wrid(self, n):
|
|
||||||
cs = self._ccs
|
|
||||||
mvp = self._mvp
|
|
||||||
mvp[0] = _WREN
|
mvp[0] = _WREN
|
||||||
cs(0)
|
cs(0)
|
||||||
self._spi.write(mvp[:1]) # Enable write
|
self._spi.write(mvp[:1])
|
||||||
cs(1)
|
cs(1)
|
||||||
mvp[:] = b"\0\0\0\0\0"
|
mvp[0] = _WRITE
|
||||||
mvp[0] = _WRID_STM
|
|
||||||
mvp[4] = _STM_ID
|
|
||||||
cs(0)
|
cs(0)
|
||||||
self._spi.write(mvp)
|
self._spi.write(mvp[:4])
|
||||||
cs(1)
|
buf[0] = res[0] ^ 0xFF if v is None else v
|
||||||
self._wait_rdy()
|
self._spi.write(buf)
|
||||||
|
cs(1) # Trigger write start
|
||||||
|
self._ccs = cs
|
||||||
|
self._wait_rdy() # Wait until done (6ms max)
|
||||||
|
return res[0]
|
||||||
|
|
||||||
# Check for valid hardware on each CS pin: use ID block
|
def scan(self):
|
||||||
def _stm_scan(self):
|
# Generate a random address to minimise wear
|
||||||
|
la = int.from_bytes(urandom(3), "little") % self._size
|
||||||
for n, cs in enumerate(self._cspins):
|
for n, cs in enumerate(self._cspins):
|
||||||
self._ccs = cs
|
old = self._devtest(cs, la)
|
||||||
if self._stm_rdid(n) != _STM_ID:
|
new = self._devtest(cs, la, old)
|
||||||
self._stm_wrid(n)
|
if old != new ^ 0xFF:
|
||||||
if self._stm_rdid(n) != _STM_ID:
|
raise RuntimeError(f"Chip not found at cs[{n}]")
|
||||||
raise RuntimeError("M95M02 chip not found at cs[{}].".format(n))
|
print(f"{n + 1} chips detected.")
|
||||||
return n
|
return n
|
||||||
|
|
||||||
# Scan for Microchip devices: read manf ID
|
|
||||||
def _mc_scan(self):
|
|
||||||
mvp = self._mvp
|
|
||||||
for n, cs in enumerate(self._cspins):
|
|
||||||
mvp[:] = b"\0\0\0\0\0"
|
|
||||||
mvp[0] = _RDID
|
|
||||||
cs(0)
|
|
||||||
self._spi.write_readinto(mvp, mvp)
|
|
||||||
cs(1)
|
|
||||||
if mvp[4] != 0x29:
|
|
||||||
raise RuntimeError("25xx1024 chip not found at cs[{}].".format(n))
|
|
||||||
return n
|
|
||||||
|
|
||||||
# Check for a valid hardware configuration
|
|
||||||
def scan(self, verbose):
|
|
||||||
n = self._stm_scan() if self._stm else self._mc_scan()
|
|
||||||
if verbose:
|
|
||||||
s = "{} chips detected. Total EEPROM size {}bytes."
|
|
||||||
print(s.format(n + 1, self._a_bytes))
|
|
||||||
|
|
||||||
def erase(self):
|
def erase(self):
|
||||||
if self._stm:
|
|
||||||
raise RuntimeError("Erase not available on STM chip")
|
|
||||||
mvp = self._mvp
|
mvp = self._mvp
|
||||||
for cs in self._cspins: # For each chip
|
block = b"\0" * 256
|
||||||
mvp[0] = _WREN
|
for n in range(0, self._a_bytes, 256):
|
||||||
cs(0)
|
self[n : n + 256] = block
|
||||||
self._spi.write(mvp[:1]) # Enable write
|
|
||||||
cs(1)
|
|
||||||
mvp[0] = _CE
|
|
||||||
cs(0)
|
|
||||||
self._spi.write(mvp[:1]) # Start erase
|
|
||||||
cs(1)
|
|
||||||
self._wait_rdy() # Wait for erase to complete
|
|
||||||
|
|
||||||
def _wait_rdy(self): # After a write, wait for device to become ready
|
def _wait_rdy(self): # After a write, wait for device to become ready
|
||||||
mvp = self._mvp
|
mvp = self._mvp
|
||||||
|
@ -134,7 +109,7 @@ class EEPROM(BlockDevice):
|
||||||
mvp[1] = la >> 16
|
mvp[1] = la >> 16
|
||||||
mvp[2] = (la >> 8) & 0xFF
|
mvp[2] = (la >> 8) & 0xFF
|
||||||
mvp[3] = la & 0xFF
|
mvp[3] = la & 0xFF
|
||||||
pe = (addr & ~0xFF) + 0x100 # byte 0 of next page
|
pe = (la & self._page_mask) + self._page_size # byte 0 of next page
|
||||||
return min(nbytes, pe - la)
|
return min(nbytes, pe - la)
|
||||||
|
|
||||||
# Read or write multiple bytes at an arbitrary address
|
# Read or write multiple bytes at an arbitrary address
|
||||||
|
|
Ładowanie…
Reference in New Issue