kopia lustrzana https://github.com/peterhinch/micropython_eeprom
First pass at flash driver.
rodzic
04d8c9b85a
commit
49991d1e02
86
README.md
86
README.md
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
These drivers support nonvolatile memory chips and the littlefs filesystem.
|
These drivers support nonvolatile memory chips and the littlefs filesystem.
|
||||||
|
|
||||||
Currently supported devices use technologies having superior performance
|
Currently supported devices include technologies having superior performance
|
||||||
compared to flash. Resultant storage has much higher write endurance. In some
|
compared to flash. Resultant storage has much higher write endurance. In some
|
||||||
cases read and write access times may be shorter. EEPROM and FRAM chips have
|
cases read and write access times may be shorter. EEPROM and FRAM chips have
|
||||||
much lower standby current than SD cards, benefiting micropower applications.
|
much lower standby current than SD cards, benefiting micropower applications.
|
||||||
|
@ -26,14 +26,15 @@ The drivers have the following common features:
|
||||||
|
|
||||||
## 1.2 Technologies
|
## 1.2 Technologies
|
||||||
|
|
||||||
Currently supported technologies are EEPROM and FRAM (ferroelectric RAM). These
|
Currently supported technologies are Flash, EEPROM and FRAM (ferroelectric
|
||||||
are nonvolatile random access storage devices with much higher endurance than
|
RAM). The latter two are nonvolatile random access storage devices with much
|
||||||
flash memory. Flash has a typical endurance of 10K writes per page. The figures
|
higher endurance than flash memory. Flash has a typical endurance of 10-100K
|
||||||
for EEPROM and FRAM are 1-4M and 10^12 writes respectively. In the case of the
|
writes per page. The figures for EEPROM and FRAM are 1-4M and 10^12 writes
|
||||||
FAT filing system 1M page writes probably corresponds to 1M filesystem writes
|
respectively. In the case of the FAT filing system 1M page writes probably
|
||||||
because FAT repeatedly updates the allocation tables in the low numbered
|
corresponds to 1M filesystem writes because FAT repeatedly updates the
|
||||||
sectors. If `littlefs` is used I would expect the endurance to be substantially
|
allocation tables in the low numbered sectors. Under `littlefs` I would expect
|
||||||
better owing to its wear levelling architecture.
|
the endurance to be substantially better owing to its wear levelling
|
||||||
|
architecture.
|
||||||
|
|
||||||
## 1.3 Supported chips
|
## 1.3 Supported chips
|
||||||
|
|
||||||
|
@ -49,20 +50,23 @@ M95M02-DRMN6TP and M95M02-DWMN3TP/K. The latter has a wider temperature range.
|
||||||
|
|
||||||
In the table below the Interface column includes page size in bytes.
|
In the table below the Interface column includes page size in bytes.
|
||||||
|
|
||||||
| Manufacturer | Part | Interface | Bytes | Technology | Docs |
|
| Manufacturer | Part | Interface | Bytes | Technology | Docs |
|
||||||
|:------------:|:---------:|:---------:|:------:|:----------:|:-------------------------:|
|
|:------------:|:---------:|:---------:|:------:|:----------:|:----------------------------:|
|
||||||
| STM | M95M02-DR | SPI 256 | 256KiB | EEPROM | [SPI.md](./spi/SPI.md) |
|
| Cypress | S25FL256L | SPI 4096 | 32MiB | Flash | [FLASH.md](./flash/FLASH.md) |
|
||||||
| Microchip | 25xx1024 | SPI 256 | 128KiB | EEPROM | [SPI.md](./spi/SPI.md) |
|
| Cypress | S25FL128L | SPI 4096 | 16MiB | Flash | [FLASH.md](./flash/FLASH.md) |
|
||||||
| Microchip | 24xx512 | I2C 128 | 64KiB | EEPROM | [I2C.md](./i2c/I2C.md) |
|
| STM | M95M02-DR | SPI 256 | 256KiB | EEPROM | [SPI.md](./spi/SPI.md) |
|
||||||
| Microchip | 24xx256 | I2C 128 | 32KiB | EEPROM | [I2C.md](./i2c/I2C.md) |
|
| Microchip | 25xx1024 | SPI 256 | 128KiB | EEPROM | [SPI.md](./spi/SPI.md) |
|
||||||
| Microchip | 24xx128 | I2C 128 | 16KiB | EEPROM | [I2C.md](./i2c/I2C.md) |
|
| Microchip | 24xx512 | I2C 128 | 64KiB | EEPROM | [I2C.md](./i2c/I2C.md) |
|
||||||
| Microchip | 24xx64 | I2C 128 | 8KiB | EEPROM | [I2C.md](./i2c/I2C.md) |
|
| Microchip | 24xx256 | I2C 128 | 32KiB | EEPROM | [I2C.md](./i2c/I2C.md) |
|
||||||
| Adafruit | 1895 | I2C n/a | 32KiB | FRAM | [FRAM.md](./fram/FRAM.md) |
|
| Microchip | 24xx128 | I2C 128 | 16KiB | EEPROM | [I2C.md](./i2c/I2C.md) |
|
||||||
|
| Microchip | 24xx64 | I2C 128 | 8KiB | EEPROM | [I2C.md](./i2c/I2C.md) |
|
||||||
|
| Adafruit | 1895 | I2C n/a | 32KiB | FRAM | [FRAM.md](./fram/FRAM.md) |
|
||||||
|
|
||||||
Documentation:
|
Documentation:
|
||||||
[SPI.md](./spi/SPI.md)
|
[SPI.md](./spi/SPI.md)
|
||||||
[I2C.md](./i2c/I2C.md)
|
[I2C.md](./i2c/I2C.md)
|
||||||
[FRAM.md](./fram/FRAM.md)
|
[FRAM.md](./fram/FRAM.md)
|
||||||
|
[FLASH.md](./flash/FLASH.md)
|
||||||
|
|
||||||
## 1.4 Performance
|
## 1.4 Performance
|
||||||
|
|
||||||
|
@ -78,6 +82,11 @@ The drivers provide the benefit of page writing in a way which is transparent.
|
||||||
If you write a block of data to an arbitrary address, page writes will be used
|
If you write a block of data to an arbitrary address, page writes will be used
|
||||||
to minimise total time.
|
to minimise total time.
|
||||||
|
|
||||||
|
In the case of flash memory page writing is mandatory: a sector is written by
|
||||||
|
first erasing it, a process which is slow. This physical limitation means that
|
||||||
|
the driver must buffer an entire 4096 byte sector. This contrasts with FRAM and
|
||||||
|
EEPROM drivers where the buffering comprises a few bytes.
|
||||||
|
|
||||||
# 2. Choice of interface
|
# 2. Choice of interface
|
||||||
|
|
||||||
The principal merit of I2C is to minimise pin count. It uses two pins
|
The principal merit of I2C is to minimise pin count. It uses two pins
|
||||||
|
@ -91,21 +100,38 @@ apparent on reads: write speed is limited by the EEPROM device. In principle
|
||||||
expansion is limited only by the number of available pins. (In practice
|
expansion is limited only by the number of available pins. (In practice
|
||||||
electrical limits may also apply).
|
electrical limits may also apply).
|
||||||
|
|
||||||
In the case of the Microchip devices supported, the SPI chip is larger at
|
The larger capacity chips generally use SPI.
|
||||||
128KiB compared to a maximum of 64KiB in the I2C range.
|
|
||||||
|
|
||||||
# 3. Design details and test results
|
# 3. Design details, littlefs support
|
||||||
|
|
||||||
The fact that the API enables accessing blocks of data at arbitrary addresses
|
A key aim of these drivers is support for littlefs. This requires the extended
|
||||||
implies that the handling of page addressing is done in the driver. This
|
block device protocol as described
|
||||||
contrasts with drivers intended only for filesystem access. These devolve the
|
[here](http://docs.micropython.org/en/latest/reference/filesystem.html) and
|
||||||
detail of page addressing to the filesystem by specifying the correct page size
|
[in the uos doc](http://docs.micropython.org/en/latest/library/uos.html).
|
||||||
in the ioctl and (if necessary) implementing a block erase method.
|
This protocol describes a block structured API capable of handling offsets into
|
||||||
|
the block. It is therefore necessary for the device driver to deal with any
|
||||||
|
block structuring inherent in the hardware. The device driver must enable
|
||||||
|
access to varying amounts of data at arbitrary physical addresses.
|
||||||
|
|
||||||
The nature of the drivers in this repo implies that the page size in the ioctl
|
These drivers achieve this by implementing a device-dependent `readwrite`
|
||||||
is arbitrary. Littlefs requires a minimum size of 128 bytes -
|
method which provides read and write access to arbitrary addresses, with data
|
||||||
|
volumes which can span page and chip boundaries. A benefit of this is that the
|
||||||
|
array of chips can be presented as a large byte array. This array is accessible
|
||||||
|
by Python slice notation: behaviour provided by the hardware-independent base
|
||||||
|
class.
|
||||||
|
|
||||||
|
A consequence of the above is that the page size in the ioctl does not have any
|
||||||
|
necessary connection with the memory hardware, so the drivers enable the value
|
||||||
|
to be specified as a constructor argument. Littlefs requires a minimum size of
|
||||||
|
128 bytes -
|
||||||
[theoretically 104](https://github.com/ARMmbed/littlefs/blob/master/DESIGN.md).
|
[theoretically 104](https://github.com/ARMmbed/littlefs/blob/master/DESIGN.md).
|
||||||
The driver only allows powers of 2. Testing was done with 512 bytes.
|
The drivers only allow powers of 2: in principle 128 bytes could be used. The
|
||||||
|
default in MicroPython's littlefs implementation is 512 bytes and all testing
|
||||||
|
was done with this value. FAT requires 512 bytes minimum: FAT testing was done
|
||||||
|
with the same block size.
|
||||||
|
|
||||||
The test programs use littlefs and therefore require MicroPython V1.12 or
|
The test programs use littlefs and therefore require MicroPython V1.12 or
|
||||||
later.
|
later. On platforms that don't support littlefs the options are either to adapt
|
||||||
|
the test programs for FAT (code is commented out) or to build firmware with
|
||||||
|
littlefs support. This can be done by passing `MICROPY_VFS_LFS2=1` to the
|
||||||
|
`make` command.
|
||||||
|
|
81
bdevice.py
81
bdevice.py
|
@ -1,10 +1,12 @@
|
||||||
# bdevice.py Hardware-agnostic base class for block devices.
|
# bdevice.py Hardware-agnostic base classes.
|
||||||
|
# BlockDevice Base class for general block devices e.g. EEPROM, FRAM.
|
||||||
|
# FlashDevice Base class for generic Flash memory (subclass of BlockDevice).
|
||||||
|
|
||||||
# Released under the MIT License (MIT). See LICENSE.
|
# Released under the MIT License (MIT). See LICENSE.
|
||||||
# Copyright (c) 2019 Peter Hinch
|
# Copyright (c) 2019 Peter Hinch
|
||||||
|
|
||||||
# Hardware-independent class implementing the uos.AbstractBlockDev protocol with
|
# BlockDevice: hardware-independent class implementing the uos.AbstractBlockDev
|
||||||
# simple and extended interface. It should therefore support littlefs.
|
# protocol with extended interface. It supports littlefs.
|
||||||
# http://docs.micropython.org/en/latest/reference/filesystem.html#custom-block-devices
|
# http://docs.micropython.org/en/latest/reference/filesystem.html#custom-block-devices
|
||||||
|
|
||||||
# The subclass must implement .readwrite which can read or write arbitrary amounts
|
# The subclass must implement .readwrite which can read or write arbitrary amounts
|
||||||
|
@ -76,3 +78,76 @@ class BlockDevice:
|
||||||
if op == 6: # Erase
|
if op == 6: # Erase
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
# Hardware agnostic base class for flash memory, where a single sector is cached.
|
||||||
|
# This minimises RAM usage. Under FAT wear is reduced if you cache at least two
|
||||||
|
# sectors. This driver is primarily intended for littlefs which has no such issue.
|
||||||
|
|
||||||
|
# Subclass must provide these hardware-dependent methods:
|
||||||
|
# .rdchip(addr, mvb) Read from chip into memoryview: data guaranteed not to be cached.
|
||||||
|
# .flush(cache, addr) Erase physical sector and write out an entire cached sector.
|
||||||
|
# .readwrite As per base class.
|
||||||
|
|
||||||
|
class FlashDevice(BlockDevice):
|
||||||
|
|
||||||
|
def __init__(self, nbits, nchips, chip_size, sec_size):
|
||||||
|
super().__init__(nbits, nchips, chip_size)
|
||||||
|
self.sec_size = sec_size
|
||||||
|
self._cache_mask = sec_size - 1 # For 4K sector size: 0xfff
|
||||||
|
self._fmask = self._cache_mask ^ 0x3fffffff # 4K -> 0x3ffff000
|
||||||
|
self._cache = bytearray(sec_size) # Cache always contains one sector
|
||||||
|
self._mvd = memoryview(self._cache)
|
||||||
|
self._acache = 0 # Address in chip of byte 0 of current cached sector
|
||||||
|
|
||||||
|
def read(self, addr, mvb):
|
||||||
|
nbytes = len(mvb)
|
||||||
|
next_sec = self._acache + self.sec_size # Start of next sector
|
||||||
|
if addr >= next_sec or addr + nbytes <= self._acache:
|
||||||
|
self.rdchip(addr, mvb) # No data is cached: just read from device
|
||||||
|
else:
|
||||||
|
# Some of address range is cached
|
||||||
|
boff = 0 # Offset into buf
|
||||||
|
if addr < self._acache: # Read data prior to cache from chip
|
||||||
|
nr = self._acache - addr
|
||||||
|
self.rdchip(addr, mvb[:nr])
|
||||||
|
addr = self._acache # Start of cached data
|
||||||
|
nbytes -= nr
|
||||||
|
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
|
||||||
|
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 : ])
|
||||||
|
return mvb
|
||||||
|
|
||||||
|
def synchronise(self):
|
||||||
|
# print('SYNCHRONISE')
|
||||||
|
self.flush(self._mvd, self._acache) # Write out old data
|
||||||
|
|
||||||
|
# TODO Performance enhancement: if cache intersects address range, update it first.
|
||||||
|
# Currently in this case it would be written twice.
|
||||||
|
def write(self, addr, mvb):
|
||||||
|
nbytes = len(mvb)
|
||||||
|
acache = self._acache
|
||||||
|
boff = 0 # Offset into buf.
|
||||||
|
while nbytes:
|
||||||
|
if (addr & self._fmask) != acache:
|
||||||
|
self.synchronise() # Erase sector and write out old data
|
||||||
|
self._fill_cache(addr) # Cache sector which includes addr
|
||||||
|
offs = addr & self._cache_mask # Offset into cache
|
||||||
|
npage = min(nbytes, self.sec_size - offs) # No. of bytes in current sector
|
||||||
|
self._mvd[offs : offs + npage] = mvb[boff : boff + npage]
|
||||||
|
nbytes -= npage
|
||||||
|
boff += npage
|
||||||
|
addr += npage
|
||||||
|
return mvb
|
||||||
|
|
||||||
|
# Cache the sector which contains a given byte addresss. Save sector
|
||||||
|
# start address.
|
||||||
|
def _fill_cache(self, addr):
|
||||||
|
addr &= self._fmask
|
||||||
|
self.rdchip(addr, self._mvd)
|
||||||
|
self._acache = addr
|
||||||
|
|
||||||
|
def initialise(self):
|
||||||
|
self._fill_cache(0)
|
||||||
|
|
|
@ -0,0 +1,257 @@
|
||||||
|
# 1. A MicroPython Flash memory driver
|
||||||
|
|
||||||
|
This driver supports the Cypress S25FL256L and S25FL128L chips, providing 64MiB
|
||||||
|
and 32MiB respectively. These have 100K cycles of write endurance (compared to
|
||||||
|
10K for Pyboard Flash memory).
|
||||||
|
|
||||||
|
Multiple chips may be used to construct a single logical nonvolatile memory
|
||||||
|
module. The driver allows the memory either to be mounted in the target
|
||||||
|
filesystem as a disk device or to be addressed as an array of bytes.
|
||||||
|
|
||||||
|
The driver has the following attributes:
|
||||||
|
1. It supports multiple Flash chips to configure a single array.
|
||||||
|
2. It is cross-platform.
|
||||||
|
3. The SPI bus can be shared with other chips.
|
||||||
|
4. It supports filesystem mounting.
|
||||||
|
5. Alternatively it can support byte-level access using Python slice syntax.
|
||||||
|
|
||||||
|
Flash technology requires a sector buffer. Consequently this driver uses 4KiB
|
||||||
|
of RAM (compared to minuscule amounts for the FRAM and EEPROM drivers). This is
|
||||||
|
an inevitable price for the large capacity of flash chips.
|
||||||
|
|
||||||
|
FAT and littlefs filesystems are supported but the latter is preferred owing to
|
||||||
|
its resilience and wear levelling characteristics.
|
||||||
|
|
||||||
|
# 2. Connections
|
||||||
|
|
||||||
|
Any SPI interface may be used. The table below assumes a Pyboard running SPI(2)
|
||||||
|
as per the test program. To wire up a single flash chip, connect to a Pyboard
|
||||||
|
as below. Pin numbers an 8 pin SOIC or WSON package. Inputs marked `nc` may be
|
||||||
|
connected to 3V3 or left unconnected.
|
||||||
|
|
||||||
|
| Flash | Signal | PB | Signal |
|
||||||
|
|:-----:|:-------:|:---:|:------:|
|
||||||
|
| 1 | CS | Y5 | SS/ |
|
||||||
|
| 2 | SO | Y7 | MISO |
|
||||||
|
| 3 | WP/ | nc | - |
|
||||||
|
| 4 | Vss | Gnd | Gnd |
|
||||||
|
| 5 | SI | Y8 | MOSI |
|
||||||
|
| 6 | SCK | Y6 | SCK |
|
||||||
|
| 7 | RESET/ | nc | - |
|
||||||
|
| 8 | Vcc | 3V3 | 3V3 |
|
||||||
|
|
||||||
|
For multiple chips a separate CS pin must be assigned to each chip: each one
|
||||||
|
must be wired to a single chip's CS line. Multiple chips should have 3V3, Gnd,
|
||||||
|
SCL, MOSI and MISO lines wired in parallel.
|
||||||
|
|
||||||
|
If you use a Pyboard D and power the chips from the 3V3 output you will need
|
||||||
|
to enable the voltage rail by issuing:
|
||||||
|
```python
|
||||||
|
machine.Pin.board.EN_3V3.value(1)
|
||||||
|
```
|
||||||
|
Other platforms may vary.
|
||||||
|
|
||||||
|
## 2.1 SPI Bus
|
||||||
|
|
||||||
|
The devices support baudrates up to 50MHz. In practice MicroPython targets do
|
||||||
|
not support such high rates. In testing I found it necessary to specify 5MHz
|
||||||
|
otherwise erratic results occurred. This was probably because of my breadboard
|
||||||
|
test setup. On a PCB I would hope to run at a sunbstantially higher rate. The
|
||||||
|
SPI bus is fast: wiring should be short and direct.
|
||||||
|
|
||||||
|
# 3. Files
|
||||||
|
|
||||||
|
1. `flash_spi.py` Device driver.
|
||||||
|
2. `bdevice.py` (In root directory) Base class for the device driver.
|
||||||
|
3. `flash_test.py` Test programs for above.
|
||||||
|
|
||||||
|
Installation: copy files 1 and 2 (optionally 3) to the target filesystem.
|
||||||
|
|
||||||
|
# 4. The device driver
|
||||||
|
|
||||||
|
The driver supports mounting the Flash chips as a filesystem. Initially the
|
||||||
|
device will be unformatted so it is necessary to issue code along these lines
|
||||||
|
to format the device. Code assumes two devices and also assumes the littlefs
|
||||||
|
filesystem:
|
||||||
|
|
||||||
|
```python
|
||||||
|
import os
|
||||||
|
from machine import SPI, Pin
|
||||||
|
from flash_spi import FLASH
|
||||||
|
cspins = (Pin(Pin.board.Y5, Pin.OUT, value=1), Pin(Pin.board.Y4, Pin.OUT, value=1))
|
||||||
|
flash = FLASH(SPI(2, baudrate=20_000_000), cspins)
|
||||||
|
# Format the filesystem
|
||||||
|
os.VfsLfs2.mkfs(flash) # Omit this to mount an existing filesystem
|
||||||
|
os.mount(flash,'/fl_ext')
|
||||||
|
```
|
||||||
|
The above will reformat a drive with an existing filesystem: to mount an
|
||||||
|
existing filesystem simply omit the commented line.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
The SPI bus must be instantiated using the `machine` module.
|
||||||
|
|
||||||
|
## 4.1 The FLASH class
|
||||||
|
|
||||||
|
An `FLASH` instance represents a logical flash memory: this may consist of
|
||||||
|
multiple physical devices on a common SPI bus.
|
||||||
|
|
||||||
|
### 4.1.1 Constructor
|
||||||
|
|
||||||
|
This tests each chip in the list of chip select pins - if a chip is detected on
|
||||||
|
each chip select line a flash 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`.
|
||||||
|
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=16384` Chip size in KiB. Set to 32768 for the S25FL256L chip.
|
||||||
|
4. `verbose=True` If `True`, the constructor issues information on the flash
|
||||||
|
devices it has detected.
|
||||||
|
5. `sec_size=4096` Chip sector size.
|
||||||
|
6. `block_size=9` The block size reported to the filesystem. The size in bytes
|
||||||
|
is `2**block_size` so is 512 bytes by default.
|
||||||
|
|
||||||
|
### 4.1.2 Methods providing byte level access
|
||||||
|
|
||||||
|
It is possible to read and write individual bytes or arrays of arbitrary size.
|
||||||
|
Because of the very large size of the supported devices this mode is most
|
||||||
|
likely to be of use for debugging. When writing in this mode it is necessary to
|
||||||
|
be aware of the characteristics of flash devices. The memory is structured in
|
||||||
|
blocks of 4096 bytes. To write a byte a block has to be read into RAM and the
|
||||||
|
byte changed. The block on chip is erased then the new data written out. This
|
||||||
|
process is slow (~300ms). In practice writing is deferred until it is necessary
|
||||||
|
to access a different block: it is therefore faster to write data to
|
||||||
|
consecutive addresses. Writing individual bytes to random addresses would be
|
||||||
|
slow and cause undue wear because of the repeated need to erase and write
|
||||||
|
sectors.
|
||||||
|
|
||||||
|
The examples below assume two devices, one with `CS` connected to Pyboard pin
|
||||||
|
Y4 and the other with `CS` connected to Y5.
|
||||||
|
|
||||||
|
#### 4.1.2.1 `__getitem__` and `__setitem__`
|
||||||
|
|
||||||
|
These provides single byte or multi-byte access using slice notation. Example
|
||||||
|
of single byte access:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from machine import SPI, Pin
|
||||||
|
from flash_spi import FLASH
|
||||||
|
cspins = (Pin(Pin.board.Y5, Pin.OUT, value=1), Pin(Pin.board.Y4, Pin.OUT, value=1))
|
||||||
|
flash = FLASH(SPI(2, baudrate=20_000_000), cspins)
|
||||||
|
flash[2000] = 42
|
||||||
|
print(flash[2000]) # Return an integer
|
||||||
|
```
|
||||||
|
It is also possible to use slice notation to read or write multiple bytes. If
|
||||||
|
writing, the size of the slice must match the length of the buffer:
|
||||||
|
```python
|
||||||
|
from machine import SPI, Pin
|
||||||
|
from flash_spi import FLASH
|
||||||
|
cspins = (Pin(Pin.board.Y5, Pin.OUT, value=1), Pin(Pin.board.Y4, Pin.OUT, value=1))
|
||||||
|
flash = FLASH(SPI(2, baudrate=20_000_000), cspins)
|
||||||
|
flash[2000:2002] = bytearray((42, 43))
|
||||||
|
print(flash[2000:2002]) # Returns a bytearray
|
||||||
|
```
|
||||||
|
Three argument slices are not supported: a third arg (other than 1) will cause
|
||||||
|
an exception. One argument slices (`flash[:5]` or `flash[13100:]`) and negative
|
||||||
|
args are supported. See [section 4.2](./SPI.md#42-byte-addressing-usage-example)
|
||||||
|
for a typical application.
|
||||||
|
|
||||||
|
#### 4.1.2.2 readwrite
|
||||||
|
|
||||||
|
This is a byte-level alternative to slice notation. It has the potential
|
||||||
|
advantage when reading of using a pre-allocated buffer. Arguments:
|
||||||
|
1. `addr` Starting byte address
|
||||||
|
2. `buf` A `bytearray` or `bytes` instance containing data to write. In the
|
||||||
|
read case it must be a (mutable) `bytearray` to hold data read.
|
||||||
|
3. `read` If `True`, perform a read otherwise write. The size of the buffer
|
||||||
|
determines the quantity of data read or written. A `RuntimeError` will be
|
||||||
|
thrown if the read or write extends beyond the end of the physical space.
|
||||||
|
|
||||||
|
### 4.1.3 Other methods
|
||||||
|
|
||||||
|
#### synchronise
|
||||||
|
|
||||||
|
This causes the cached sector to be written to the device. Should be called
|
||||||
|
prior to power down. **TODO: check flush/synchronise**
|
||||||
|
|
||||||
|
#### The len() operator
|
||||||
|
|
||||||
|
The size of the flash array in bytes may be retrieved by issuing `len(flash)`
|
||||||
|
where `flash` is the `FLASH` instance.
|
||||||
|
|
||||||
|
#### scan
|
||||||
|
|
||||||
|
Activate each chip select in turn checking for a valid device and returns the
|
||||||
|
number of flash devices detected. A `RuntimeError` will be raised if any CS
|
||||||
|
pin does not correspond to a valid chip.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
#### erase
|
||||||
|
|
||||||
|
Erases the entire array. Beware: this takes many minutes.
|
||||||
|
|
||||||
|
### 4.1.4 Methods providing the block protocol
|
||||||
|
|
||||||
|
These are provided by the base class. For the protocol definition see
|
||||||
|
[the pyb documentation](http://docs.micropython.org/en/latest/library/uos.html#uos.AbstractBlockDev)
|
||||||
|
also [here](http://docs.micropython.org/en/latest/reference/filesystem.html#custom-block-devices).
|
||||||
|
|
||||||
|
`readblocks()`
|
||||||
|
`writeblocks()`
|
||||||
|
`ioctl()`
|
||||||
|
|
||||||
|
# 5. Test program flash_spi.py
|
||||||
|
|
||||||
|
This assumes a Pyboard 1.x or Pyboard D with two chips wired to SPI(2) as
|
||||||
|
above with chip selects connected to pins `Y4` and `Y5`. 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.
|
||||||
|
|
||||||
|
## 5.2 full_test()
|
||||||
|
|
||||||
|
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. **TODO long run time.**
|
||||||
|
|
||||||
|
## 5.3 fstest(format=False)
|
||||||
|
|
||||||
|
If `True` is passed, formats the flash array as a littlefs filesystem and mounts
|
||||||
|
the device on `/fl_ext`. If no arg is passed it mounts the array and lists the
|
||||||
|
contents. It also prints the outcome of `uos.statvfs` on the array.
|
||||||
|
|
||||||
|
## 5.4 cptest()
|
||||||
|
|
||||||
|
Tests copying the source files to the filesystem. The test will fail if the
|
||||||
|
filesystem was not formatted. Lists the contents of the mountpoint and prints
|
||||||
|
the outcome of `uos.statvfs`.
|
||||||
|
|
||||||
|
## 5.5 File copy
|
||||||
|
|
||||||
|
A rudimentary `cp(source, dest)` function is provided as a generic file copy
|
||||||
|
routine for setup and debugging purposes at the REPL. The first argument is the
|
||||||
|
full pathname to the source file. The second may be a full path to the
|
||||||
|
destination file or a directory specifier which must have a trailing '/'. If an
|
||||||
|
OSError is thrown (e.g. by the source file not existing or the flash becoming
|
||||||
|
full) it is up to the caller to handle it. For example (assuming the flash is
|
||||||
|
mounted on /fl_ext):
|
||||||
|
|
||||||
|
```python
|
||||||
|
cp('/flash/main.py','/fl_ext/')
|
||||||
|
```
|
||||||
|
|
||||||
|
See `upysh` in [micropython-lib](https://github.com/micropython/micropython-lib.git)
|
||||||
|
for other filesystem tools for use at the REPL.
|
|
@ -0,0 +1,164 @@
|
||||||
|
# flash_spi.py MicroPython driver for Cypress S25FL128L 16MiB and S25FL256L 32MiB
|
||||||
|
# flash devices.
|
||||||
|
|
||||||
|
# Released under the MIT License (MIT). See LICENSE.
|
||||||
|
# Copyright (c) 2019 Peter Hinch
|
||||||
|
|
||||||
|
import time
|
||||||
|
from micropython import const
|
||||||
|
from bdevice import FlashDevice
|
||||||
|
|
||||||
|
# Supported instruction set:
|
||||||
|
# 4 byte address commands
|
||||||
|
_READ = const(0x13)
|
||||||
|
_PP = const(0x12) # Page program
|
||||||
|
_SE = const(0x21) # Sector erase
|
||||||
|
# No address
|
||||||
|
_WREN = const(6) # Write enable
|
||||||
|
_RDSR1 = const(5) # Read status register 1
|
||||||
|
_RDSR2 = const(7) # Read status register 2
|
||||||
|
_RDID = const(0x9f) # Read manufacturer ID
|
||||||
|
_CE = const(0xc7) # Chip erase (takes minutes)
|
||||||
|
|
||||||
|
_SEC_SIZE = const(4096) # Flash sector size 0x1000
|
||||||
|
|
||||||
|
# Logical Flash device comprising one or more physical chips sharing an SPI bus.
|
||||||
|
class FLASH(FlashDevice):
|
||||||
|
|
||||||
|
def __init__(self, spi, cspins, size=16384, verbose=True, sec_size=_SEC_SIZE, block_size=9):
|
||||||
|
# args: virtual block size in bits, no. of chips, bytes in each chip
|
||||||
|
if size not in (16384, 32768):
|
||||||
|
raise ValueError('Valid sizes: 16384 or 32768KiB')
|
||||||
|
super().__init__(block_size, len(cspins), size * 1024, sec_size)
|
||||||
|
self._spi = spi
|
||||||
|
self._cspins = cspins
|
||||||
|
self._ccs = None # Chip select Pin object for current chip
|
||||||
|
self._bufp = bytearray(6) # instruction + 4 byte address + 1 byte value
|
||||||
|
self._mvp = memoryview(self._bufp) # cost-free slicing
|
||||||
|
self._page_size = 256 # Write uses 256 byte pages.
|
||||||
|
self.scan(verbose)
|
||||||
|
self.initialise() # Initially cache sector 0
|
||||||
|
|
||||||
|
# **** API SPECIAL METHODS ****
|
||||||
|
# Scan: read manf ID
|
||||||
|
def scan(self, verbose):
|
||||||
|
mvp = self._mvp
|
||||||
|
for n, cs in enumerate(self._cspins):
|
||||||
|
mvp[:] = b'\0\0\0\0\0\0'
|
||||||
|
mvp[0] = _RDID
|
||||||
|
cs(0)
|
||||||
|
self._spi.write_readinto(mvp[:4], mvp[:4])
|
||||||
|
cs(1)
|
||||||
|
if mvp[1] != 1 or mvp[2] != 0x60 or not (mvp[3] == 0x18 or mvp[3] == 0x19):
|
||||||
|
raise RuntimeError('Flash not found at cs[{}].'.format(n))
|
||||||
|
if verbose:
|
||||||
|
s = '{} chips detected. Total flash size {}MiB.'
|
||||||
|
print(s.format(n + 1, self._a_bytes // (1024 * 1024)))
|
||||||
|
return n
|
||||||
|
|
||||||
|
# Chip erase. Can take minutes.
|
||||||
|
def erase(self):
|
||||||
|
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
|
||||||
|
|
||||||
|
# **** INTERFACE FOR BASE CLASS ****
|
||||||
|
# Write cache to a sector starting at byte address addr
|
||||||
|
def flush(self, cache, addr): # cache is memoryview into buffer
|
||||||
|
self._sector_erase(addr)
|
||||||
|
mvp = self._mvp
|
||||||
|
nbytes = self.sec_size
|
||||||
|
ps = self._page_size
|
||||||
|
start = 0 # Current offset into cache buffer
|
||||||
|
while nbytes > 0:
|
||||||
|
# write one page at a time
|
||||||
|
self._getaddr(addr, 1)
|
||||||
|
cs = self._ccs # Current chip select from _getaddr
|
||||||
|
mvp[0] = _WREN
|
||||||
|
cs(0)
|
||||||
|
self._spi.write(mvp[:1]) # Enable write
|
||||||
|
cs(1)
|
||||||
|
mvp[0] = _PP
|
||||||
|
cs(0)
|
||||||
|
self._spi.write(mvp[:5]) # Start write
|
||||||
|
self._spi.write(cache[start : start + ps])
|
||||||
|
cs(1)
|
||||||
|
self._wait_rdy() # Wait for write to complete
|
||||||
|
nbytes -= ps
|
||||||
|
start += ps
|
||||||
|
addr += ps
|
||||||
|
|
||||||
|
# Read from chip into a memoryview. Address range guaranteed not to be cached.
|
||||||
|
def rdchip(self, addr, mvb):
|
||||||
|
nbytes = len(mvb)
|
||||||
|
mvp = self._mvp
|
||||||
|
start = 0 # Offset into buf.
|
||||||
|
while nbytes > 0:
|
||||||
|
npage = self._getaddr(addr, nbytes) # No. of bytes in current chip
|
||||||
|
cs = self._ccs
|
||||||
|
mvp[0] = _READ
|
||||||
|
cs(0)
|
||||||
|
self._spi.write(mvp[:5])
|
||||||
|
self._spi.readinto(mvb[start : start + npage])
|
||||||
|
cs(1)
|
||||||
|
nbytes -= npage
|
||||||
|
start += npage
|
||||||
|
addr += npage
|
||||||
|
|
||||||
|
# Read or write multiple bytes at an arbitrary address.
|
||||||
|
# **** Also part of API ****
|
||||||
|
def readwrite(self, addr, buf, read):
|
||||||
|
mvb = memoryview(buf)
|
||||||
|
self.read(addr, mvb) if read else self.write(addr, mvb)
|
||||||
|
return buf
|
||||||
|
|
||||||
|
# **** INTERNAL METHODS ****
|
||||||
|
def _wait_rdy(self): # After a write, wait for device to become ready
|
||||||
|
mvp = self._mvp
|
||||||
|
cs = self._ccs # Chip is already current
|
||||||
|
while True:
|
||||||
|
mvp[0] = _RDSR1
|
||||||
|
cs(0)
|
||||||
|
self._spi.write_readinto(mvp[:2], mvp[:2])
|
||||||
|
cs(1)
|
||||||
|
if not (mvp[1] & 1):
|
||||||
|
break
|
||||||
|
time.sleep_ms(1)
|
||||||
|
|
||||||
|
# Given an address, set current chip select and address buffer.
|
||||||
|
# Return the number of bytes that can be processed in the current chip.
|
||||||
|
def _getaddr(self, addr, nbytes):
|
||||||
|
if addr >= self._a_bytes:
|
||||||
|
raise RuntimeError("Flash Address is out of range")
|
||||||
|
ca, la = divmod(addr, self._c_bytes) # ca == chip no, la == offset into chip
|
||||||
|
self._ccs = self._cspins[ca] # Current chip select
|
||||||
|
mvp = self._mvp
|
||||||
|
mvp[1] = la >> 24
|
||||||
|
mvp[2] = la >> 16 & 0xff
|
||||||
|
mvp[3] = (la >> 8) & 0xff
|
||||||
|
mvp[4] = la & 0xff
|
||||||
|
pe = (addr & -self._c_bytes) + self._c_bytes # Byte 0 of next chip
|
||||||
|
return min(nbytes, pe - la)
|
||||||
|
|
||||||
|
# Erase sector. Address is start byte address of sector.
|
||||||
|
def _sector_erase(self, addr):
|
||||||
|
self._getaddr(addr, 1)
|
||||||
|
cs = self._ccs # Current chip select from _getaddr
|
||||||
|
mvp = self._mvp
|
||||||
|
mvp[0] = _WREN
|
||||||
|
cs(0)
|
||||||
|
self._spi.write(mvp[:1]) # Enable write
|
||||||
|
cs(1)
|
||||||
|
mvp[0] = _SE
|
||||||
|
cs(0)
|
||||||
|
self._spi.write(mvp[:5]) # Start erase
|
||||||
|
cs(1)
|
||||||
|
self._wait_rdy() # Wait for erase to complete
|
|
@ -0,0 +1,138 @@
|
||||||
|
# flash_test.py MicroPython test program for Cypress SPI Flash devices.
|
||||||
|
|
||||||
|
# Released under the MIT License (MIT). See LICENSE.
|
||||||
|
# Copyright (c) 2019 Peter Hinch
|
||||||
|
|
||||||
|
import uos
|
||||||
|
from machine import SPI, Pin
|
||||||
|
from flash_spi import FLASH
|
||||||
|
# Add extra pins if using multiple chips
|
||||||
|
cspins = (Pin(Pin.board.Y5, Pin.OUT, value=1), Pin(Pin.board.Y4, Pin.OUT, value=1))
|
||||||
|
|
||||||
|
# Return an EEPROM array. Adapt for platforms other than Pyboard.
|
||||||
|
def get_device():
|
||||||
|
if uos.uname().machine.split(' ')[0][:4] == 'PYBD':
|
||||||
|
Pin.board.EN_3V3.value(1)
|
||||||
|
flash = FLASH(SPI(2, baudrate=5_000_000), cspins)
|
||||||
|
print('Instantiated Flash')
|
||||||
|
return flash
|
||||||
|
|
||||||
|
# Dumb file copy utility to help with managing EEPROM contents at the REPL.
|
||||||
|
def cp(source, dest):
|
||||||
|
if dest.endswith('/'): # minimal way to allow
|
||||||
|
dest = ''.join((dest, source.split('/')[-1])) # cp /sd/file /fl_ext/
|
||||||
|
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
|
||||||
|
|
||||||
|
# ***** TEST OF DRIVER *****
|
||||||
|
def _testblock(eep, bs):
|
||||||
|
d0 = b'this >'
|
||||||
|
d1 = b'<is the boundary'
|
||||||
|
d2 = d0 + d1
|
||||||
|
garbage = b'xxxxxxxxxxxxxxxxxxx'
|
||||||
|
start = bs - len(d0)
|
||||||
|
end = start + len(garbage)
|
||||||
|
eep[start : end] = garbage
|
||||||
|
res = eep[start : end]
|
||||||
|
if res != garbage:
|
||||||
|
return 'Block test fail 1:' + str(list(res))
|
||||||
|
end = start + len(d0)
|
||||||
|
eep[start : end] = d0
|
||||||
|
end = start + len(garbage)
|
||||||
|
res = eep[start : end]
|
||||||
|
if res != b'this >xxxxxxxxxxxxx':
|
||||||
|
return 'Block test fail 2:' + str(list(res))
|
||||||
|
start = bs
|
||||||
|
end = bs + len(d1)
|
||||||
|
eep[start : end] = d1
|
||||||
|
start = bs - len(d0)
|
||||||
|
end = start + len(d2)
|
||||||
|
res = eep[start : end]
|
||||||
|
if res != d2:
|
||||||
|
return 'Block test fail 3:' + str(list(res))
|
||||||
|
|
||||||
|
def test():
|
||||||
|
eep = get_device()
|
||||||
|
sa = 1000
|
||||||
|
for v in range(256):
|
||||||
|
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))
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
print('Test of byte addressing passed')
|
||||||
|
data = uos.urandom(30)
|
||||||
|
sa = 2000
|
||||||
|
eep[sa:sa + 30] = data
|
||||||
|
if eep[sa:sa + 30] == data:
|
||||||
|
print('Test of slice readback passed')
|
||||||
|
|
||||||
|
block = 256
|
||||||
|
res = _testblock(eep, block)
|
||||||
|
if res is None:
|
||||||
|
print('Test block boundary {} passed'.format(block))
|
||||||
|
else:
|
||||||
|
print('Test block boundary {} fail'.format(block))
|
||||||
|
print(res)
|
||||||
|
block = eep._c_bytes
|
||||||
|
if eep._a_bytes > block:
|
||||||
|
res = _testblock(eep, block)
|
||||||
|
if res is None:
|
||||||
|
print('Test chip boundary {} passed'.format(block))
|
||||||
|
else:
|
||||||
|
print('Test chip boundary {} fail'.format(block))
|
||||||
|
print(res)
|
||||||
|
else:
|
||||||
|
print('Test chip boundary skipped: only one chip!')
|
||||||
|
|
||||||
|
# ***** TEST OF FILESYSTEM MOUNT *****
|
||||||
|
def fstest(format=False):
|
||||||
|
eep = get_device()
|
||||||
|
# ***** CODE FOR LITTLEFS *****
|
||||||
|
if format:
|
||||||
|
uos.VfsLfs2.mkfs(eep)
|
||||||
|
try:
|
||||||
|
uos.mount(eep,'/fl_ext')
|
||||||
|
except OSError: # Already mounted
|
||||||
|
pass
|
||||||
|
print('Contents of "/": {}'.format(uos.listdir('/')))
|
||||||
|
print('Contents of "/fl_ext": {}'.format(uos.listdir('/fl_ext')))
|
||||||
|
print(uos.statvfs('/fl_ext'))
|
||||||
|
|
||||||
|
def cptest():
|
||||||
|
eep = get_device()
|
||||||
|
if 'fl_ext' in uos.listdir('/'):
|
||||||
|
print('Device already mounted.')
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
uos.mount(eep,'/fl_ext')
|
||||||
|
except OSError:
|
||||||
|
print('Fail mounting device. Have you formatted it?')
|
||||||
|
return
|
||||||
|
print('Mounted device.')
|
||||||
|
cp('flash_test.py', '/fl_ext/')
|
||||||
|
cp('flash_spi.py', '/fl_ext/')
|
||||||
|
print('Contents of "/fl_ext": {}'.format(uos.listdir('/fl_ext')))
|
||||||
|
print(uos.statvfs('/fl_ext'))
|
||||||
|
|
||||||
|
|
||||||
|
# ***** TEST OF HARDWARE *****
|
||||||
|
def full_test():
|
||||||
|
eep = get_device()
|
||||||
|
page = 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))
|
||||||
|
else:
|
||||||
|
print('Page {} readback failed.'.format(page))
|
||||||
|
break
|
||||||
|
page += 1
|
|
@ -38,13 +38,13 @@ def _testblock(eep, bs):
|
||||||
eep[start : end] = garbage
|
eep[start : end] = garbage
|
||||||
res = eep[start : end]
|
res = eep[start : end]
|
||||||
if res != garbage:
|
if res != garbage:
|
||||||
return 'Block test fail 1:' + res
|
return 'Block test fail 1:' + str(list(res))
|
||||||
end = start + len(d0)
|
end = start + len(d0)
|
||||||
eep[start : end] = d0
|
eep[start : end] = d0
|
||||||
end = start + len(garbage)
|
end = start + len(garbage)
|
||||||
res = eep[start : end]
|
res = eep[start : end]
|
||||||
if res != b'this >xxxxxxxxxxxxx':
|
if res != b'this >xxxxxxxxxxxxx':
|
||||||
return 'Block test fail 2:' + res
|
return 'Block test fail 2:' + str(list(res))
|
||||||
start = bs
|
start = bs
|
||||||
end = bs + len(d1)
|
end = bs + len(d1)
|
||||||
eep[start : end] = d1
|
eep[start : end] = d1
|
||||||
|
@ -52,7 +52,7 @@ def _testblock(eep, bs):
|
||||||
end = start + len(d2)
|
end = start + len(d2)
|
||||||
res = eep[start : end]
|
res = eep[start : end]
|
||||||
if res != d2:
|
if res != d2:
|
||||||
return 'Block test fail 3:' + res
|
return 'Block test fail 3:' + str(list(res))
|
||||||
|
|
||||||
def test():
|
def test():
|
||||||
fram = get_fram()
|
fram = get_fram()
|
||||||
|
|
|
@ -240,9 +240,9 @@ lost.
|
||||||
|
|
||||||
## 5.3 fstest(format=False)
|
## 5.3 fstest(format=False)
|
||||||
|
|
||||||
If `True` is passed, formats the EEPROM array as a FAT filesystem and mounts
|
If `True` is passed, formats the EEPROM array as a littlefs filesystem and
|
||||||
the device on `/eeprom`. If no arg is passed it mounts the array and lists the
|
mounts the device on `/eeprom`. If no arg is passed it mounts the array and
|
||||||
contents. It also prints the outcome of `uos.statvfs` on the array.
|
lists the contents. It also prints the outcome of `uos.statvfs` on the array.
|
||||||
|
|
||||||
## 5.4 cptest()
|
## 5.4 cptest()
|
||||||
|
|
||||||
|
|
|
@ -39,13 +39,13 @@ def _testblock(eep, bs):
|
||||||
eep[start : end] = garbage
|
eep[start : end] = garbage
|
||||||
res = eep[start : end]
|
res = eep[start : end]
|
||||||
if res != garbage:
|
if res != garbage:
|
||||||
return 'Block test fail 1:' + res
|
return 'Block test fail 1:' + str(list(res))
|
||||||
end = start + len(d0)
|
end = start + len(d0)
|
||||||
eep[start : end] = d0
|
eep[start : end] = d0
|
||||||
end = start + len(garbage)
|
end = start + len(garbage)
|
||||||
res = eep[start : end]
|
res = eep[start : end]
|
||||||
if res != b'this >xxxxxxxxxxxxx':
|
if res != b'this >xxxxxxxxxxxxx':
|
||||||
return 'Block test fail 2:' + res
|
return 'Block test fail 2:' + str(list(res))
|
||||||
start = bs
|
start = bs
|
||||||
end = bs + len(d1)
|
end = bs + len(d1)
|
||||||
eep[start : end] = d1
|
eep[start : end] = d1
|
||||||
|
@ -53,7 +53,7 @@ def _testblock(eep, bs):
|
||||||
end = start + len(d2)
|
end = start + len(d2)
|
||||||
res = eep[start : end]
|
res = eep[start : end]
|
||||||
if res != d2:
|
if res != d2:
|
||||||
return 'Block test fail 3:' + res
|
return 'Block test fail 3:' + str(list(res))
|
||||||
|
|
||||||
def test():
|
def test():
|
||||||
eep = get_eep()
|
eep = get_eep()
|
||||||
|
|
|
@ -257,9 +257,9 @@ lost.
|
||||||
|
|
||||||
## 5.3 fstest(format=False, stm=False)
|
## 5.3 fstest(format=False, stm=False)
|
||||||
|
|
||||||
If `True` is passed, formats the EEPROM array as a FAT filesystem and mounts
|
If `True` is passed, formats the EEPROM array as a littlefs filesystem and
|
||||||
the device on `/eeprom`. If no arg is passed it mounts the array and lists the
|
mounts the device on `/eeprom`. If no arg is passed it mounts the array and
|
||||||
contents. It also prints the outcome of `uos.statvfs` on the array.
|
lists the contents. It also prints the outcome of `uos.statvfs` on the array.
|
||||||
|
|
||||||
## 5.4 cptest(stm=False)
|
## 5.4 cptest(stm=False)
|
||||||
|
|
||||||
|
|
|
@ -43,13 +43,13 @@ def _testblock(eep, bs):
|
||||||
eep[start : end] = garbage
|
eep[start : end] = garbage
|
||||||
res = eep[start : end]
|
res = eep[start : end]
|
||||||
if res != garbage:
|
if res != garbage:
|
||||||
return 'Block test fail 1:' + res
|
return 'Block test fail 1:' + str(list(res))
|
||||||
end = start + len(d0)
|
end = start + len(d0)
|
||||||
eep[start : end] = d0
|
eep[start : end] = d0
|
||||||
end = start + len(garbage)
|
end = start + len(garbage)
|
||||||
res = eep[start : end]
|
res = eep[start : end]
|
||||||
if res != b'this >xxxxxxxxxxxxx':
|
if res != b'this >xxxxxxxxxxxxx':
|
||||||
return 'Block test fail 2:' + res
|
return 'Block test fail 2:' + str(list(res))
|
||||||
start = bs
|
start = bs
|
||||||
end = bs + len(d1)
|
end = bs + len(d1)
|
||||||
eep[start : end] = d1
|
eep[start : end] = d1
|
||||||
|
@ -57,7 +57,7 @@ def _testblock(eep, bs):
|
||||||
end = start + len(d2)
|
end = start + len(d2)
|
||||||
res = eep[start : end]
|
res = eep[start : end]
|
||||||
if res != d2:
|
if res != d2:
|
||||||
return 'Block test fail 3:' + res
|
return 'Block test fail 3:' + str(list(res))
|
||||||
|
|
||||||
def test(stm=False):
|
def test(stm=False):
|
||||||
eep = get_eep(stm)
|
eep = get_eep(stm)
|
||||||
|
|
Ładowanie…
Reference in New Issue