2019-12-24 15:16:09 +00:00
|
|
|
# 1. A MicroPython Flash memory driver
|
|
|
|
|
2020-02-11 14:36:54 +00:00
|
|
|
This driver supports the Cypress S25FL256L and S25FL128L chips, providing 32MiB
|
|
|
|
and 16MiB respectively. These have 100K cycles of write endurance (compared to
|
2020-01-10 18:46:57 +00:00
|
|
|
10K for Pyboard Flash memory). These were the largest capacity available with a
|
|
|
|
sector size small enough for microcontroller use.
|
2019-12-24 15:16:09 +00:00
|
|
|
|
2020-02-11 12:16:02 +00:00
|
|
|
Thanks to a patch from Daniel Thompson this now has the capability to support
|
|
|
|
smaller NOR Flash chips with 24-bit addressing. I lack the chips to test this
|
|
|
|
so am unable to support such use. The author tested on an XPX XT25F32B.
|
|
|
|
|
2019-12-24 15:16:09 +00:00
|
|
|
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.
|
|
|
|
|
2020-01-10 18:46:57 +00:00
|
|
|
Arguably byte level access on such large devices has few use cases other than
|
|
|
|
for facilitating effective hardware tests and for diagnostics.
|
2020-01-01 14:15:40 +00:00
|
|
|
|
2019-12-30 10:48:46 +00:00
|
|
|
##### [Main readme](../README.md)
|
|
|
|
|
2019-12-24 15:16:09 +00:00
|
|
|
# 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 |
|
|
|
|
|:-----:|:-------:|:---:|:------:|
|
2019-12-26 18:08:59 +00:00
|
|
|
| 1 | CS/ | Y5 | SS/ |
|
2019-12-24 15:16:09 +00:00
|
|
|
| 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
|
2020-01-11 18:02:19 +00:00
|
|
|
being wired to a single chip's CS line. The test program assumes a second chip
|
|
|
|
with CS connected to Y4. Multiple chips should have 3V3, Gnd, SCL, MOSI and
|
|
|
|
MISO lines wired in parallel.
|
2019-12-24 15:16:09 +00:00
|
|
|
|
|
|
|
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)
|
|
|
|
```
|
2019-12-30 09:51:10 +00:00
|
|
|
Other platforms may vary but the Cypress chips require a 3.3V supply.
|
2019-12-24 15:16:09 +00:00
|
|
|
|
2020-02-11 12:16:02 +00:00
|
|
|
It is wise to add a pullup resistor (say 10KΩ) from each CS/ line to 3.3V. This
|
|
|
|
ensures that chips are deselected at initial power up when the microcontroller
|
|
|
|
I/O pins are high impedance.
|
|
|
|
|
2019-12-24 15:16:09 +00:00
|
|
|
## 2.1 SPI Bus
|
|
|
|
|
|
|
|
The devices support baudrates up to 50MHz. In practice MicroPython targets do
|
2020-01-10 18:46:57 +00:00
|
|
|
not support such high rates. The test programs specify 20MHz, but in practice
|
|
|
|
the Pyboard D delivers 15MHz. Testing was done at this rate. In testing a
|
|
|
|
"lashup" breadboard was unsatisfactory: a problem entirely fixed with a PCB.
|
|
|
|
Bus lines should be short and direct.
|
2019-12-24 15:16:09 +00:00
|
|
|
|
|
|
|
# 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.
|
2019-12-26 18:08:59 +00:00
|
|
|
4. `littlefs_test.py` Torture test for the littlefs filesystem on the flash
|
2020-01-01 14:15:40 +00:00
|
|
|
array. Requires `flash_test.py` which it uses for hardware configuration.
|
2020-01-11 18:02:19 +00:00
|
|
|
5. `wemos_flash.py` Test program running on a Wemos D1 Mini ESP8266 board.
|
2019-12-24 15:16:09 +00:00
|
|
|
|
2020-01-11 18:02:19 +00:00
|
|
|
Installation: copy files 1 and 2 (3 - 5 are optional) to the target filesystem.
|
2020-01-01 14:15:40 +00:00
|
|
|
The `flash_test` script assumes two S25FL256L chips connected to SPI(2) with
|
|
|
|
CS/ pins wired to Pyboard pins Y4 and Y5. The `get_device` function may be
|
|
|
|
adapted for other setups and is shared with `littlefs_test`.
|
|
|
|
|
|
|
|
For a quick check of hardware issue:
|
|
|
|
```python
|
|
|
|
import flash_test
|
|
|
|
flash_test.test()
|
|
|
|
```
|
2019-12-24 15:16:09 +00:00
|
|
|
|
|
|
|
# 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
|
2019-12-30 09:51:10 +00:00
|
|
|
to format the device. Code assumes two devices and the (recommended) littlefs
|
2019-12-24 15:16:09 +00:00
|
|
|
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))
|
2020-01-10 18:46:57 +00:00
|
|
|
flash = FLASH(SPI(2, baudrate=20_000_000), cspins)
|
2019-12-24 15:16:09 +00:00
|
|
|
# Format the filesystem
|
|
|
|
os.VfsLfs2.mkfs(flash) # Omit this to mount an existing filesystem
|
|
|
|
os.mount(flash,'/fl_ext')
|
|
|
|
```
|
2019-12-30 09:51:10 +00:00
|
|
|
The above will reformat a drive with an existing filesystem erasing all files:
|
|
|
|
to mount an existing filesystem omit the commented line.
|
2019-12-24 15:16:09 +00:00
|
|
|
|
|
|
|
Note that, at the outset, you need to decide whether to use the array as a
|
2019-12-30 09:51:10 +00:00
|
|
|
mounted filesystem or as a byte array. Most use cases for flash will require a
|
|
|
|
filesystem, although byte level reads may be used to debug filesystem issues.
|
2019-12-24 15:16:09 +00:00
|
|
|
|
|
|
|
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
|
2019-12-30 09:51:10 +00:00
|
|
|
raised if a device is not detected on a CS line. The test has no effect on
|
|
|
|
the array contents.
|
2019-12-24 15:16:09 +00:00
|
|
|
|
|
|
|
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`.
|
2020-02-11 12:16:02 +00:00
|
|
|
3. `size=None` Chip size in KiB. The size is read from the chip. If a value
|
|
|
|
is passed, the actual size is compared with the passed value: a mismatch will
|
|
|
|
raise a `ValueError`. Optionally set to 32768 for the S25FL256L chip or 16384
|
|
|
|
for the S25FL128L.
|
2019-12-24 15:16:09 +00:00
|
|
|
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))
|
2020-01-10 18:46:57 +00:00
|
|
|
flash = FLASH(SPI(2, baudrate=20_000_000), cspins)
|
2019-12-24 15:16:09 +00:00
|
|
|
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))
|
2020-01-10 18:46:57 +00:00
|
|
|
flash = FLASH(SPI(2, baudrate=20_000_000), cspins)
|
2019-12-24 15:16:09 +00:00
|
|
|
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
|
|
|
|
|
2019-12-30 09:51:10 +00:00
|
|
|
#### sync
|
2019-12-24 15:16:09 +00:00
|
|
|
|
2019-12-30 09:51:10 +00:00
|
|
|
This causes the cached sector to be written to the device. In normal filesystem
|
|
|
|
use this need not be called. If byte-level writes have been performed it should
|
|
|
|
be called prior to power down.
|
2019-12-24 15:16:09 +00:00
|
|
|
|
2019-12-30 09:51:10 +00:00
|
|
|
#### The len operator
|
2019-12-24 15:16:09 +00:00
|
|
|
|
|
|
|
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
|
2019-12-30 09:51:10 +00:00
|
|
|
identify each chip.
|
2019-12-24 15:16:09 +00:00
|
|
|
|
|
|
|
#### 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
|
2020-01-01 14:15:40 +00:00
|
|
|
primarily tests the driver: as a hardware test it is not exhaustive. It does
|
|
|
|
provide a quick verification that all chips can be accessed.
|
2019-12-24 15:16:09 +00:00
|
|
|
|
2019-12-30 09:51:10 +00:00
|
|
|
## 5.2 full_test(count=10)
|
2019-12-24 15:16:09 +00:00
|
|
|
|
2019-12-30 09:51:10 +00:00
|
|
|
This is a hardware test. Tests the entire array. Creates an array of 256 bytes
|
|
|
|
of random data and writes it to a random address. After synchronising the cache
|
|
|
|
with the hardware, reads it back, and checks the outcome. Existing array data
|
|
|
|
will be lost. The arg determines the number of passes.
|
2019-12-24 15:16:09 +00:00
|
|
|
|
|
|
|
## 5.3 fstest(format=False)
|
|
|
|
|
2019-12-30 09:51:10 +00:00
|
|
|
If `True` is passed, formats the flash array as a littlefs filesystem deleting
|
|
|
|
existing contents. In both cases of the arg it mounts the device on `/fl_ext`
|
|
|
|
lists the contents of the mountpoint. It also prints the outcome of
|
|
|
|
`uos.statvfs` on the mountpoint.
|
2019-12-24 15:16:09 +00:00
|
|
|
|
|
|
|
## 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.
|