kopia lustrzana https://github.com/peterhinch/micropython_eeprom
249 wiersze
9.9 KiB
Markdown
249 wiersze
9.9 KiB
Markdown
# 1. A MicroPython SPI FRAM driver
|
|
|
|
A driver to enable MicroPython hosts to access Ferroelectric RAM (FRAM) boards
|
|
from Adafruit, namely [the 256KiB board](https://www.adafruit.com/product/4718)
|
|
and [the 512KiB board](https://www.adafruit.com/product/4719). FRAM is a
|
|
technology offering nonvolatile memory with extremely long endurance and fast
|
|
access, avoiding the limitations of Flash memory. Its endurance is specified as
|
|
10**13 writes, contrasted with 10,000 which is the quoted endurance of the
|
|
Pyboard's onboard Flash memory. In data logging applications the latter can be
|
|
exceeded relatively rapidly. Flash writes can be slow because of the need for a
|
|
sector erase: this is not a fast process. FRAM is byte addressable and is not
|
|
subject to this limitation. Compared to a Micro SD card fitted to the Pyboard
|
|
it offers lower power consumption and longer endurance, albeit at a smaller
|
|
capacity.
|
|
|
|
An arbitrary number of boards may be used to construct a nonvolatile memory
|
|
array with size from 256KiB upwards. The driver allows the memory either to be
|
|
mounted in the host filesystem as a disk device or to be addressed as an array
|
|
of bytes.
|
|
|
|
For users interested in the technology [this](https://www.mouser.com/pdfDOCS/cypress-fram-whitepaper.pdf)
|
|
is worth reading. Clue: the FRAM cell contains no iron.
|
|
|
|
##### [Main readme](../README.md)
|
|
|
|
# 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 FRAM BOARD, connect to a Pyboard
|
|
as below (n/c indicates no connection):
|
|
|
|
| FRAM Signal | PB | Signal |
|
|
|:-----------:|:---:|:------:|
|
|
| Vin | 3V3 | 3V3 |
|
|
| 3V3 | n/c | n/c |
|
|
| Gnd | Gnd | Gnd |
|
|
| SCK | Y6 | SCK |
|
|
| MISO | Y7 | MISO |
|
|
| MOSI | Y8 | MOSI |
|
|
| CS | Y5 | SS/ |
|
|
| WP/ | n/c | n/c |
|
|
| HOLD/ | n/c | n/c |
|
|
|
|
For multiple boards a separate CS pin must be assigned to each one: each pin
|
|
must be wired to a single board's CS line. Multiple boards should have Vin, Gnd,
|
|
SCK, MOSI and MISO lines wired in parallel.
|
|
|
|
If you use a Pyboard D and power the devices from the 3V3 output you will need
|
|
to enable the voltage rail by issuing:
|
|
```python
|
|
machine.Pin.board.EN_3V3.value(1)
|
|
time.sleep(0.1) # Allow decouplers to charge
|
|
```
|
|
Other platforms may vary.
|
|
|
|
At the time of writing schematics for the Adafruit boards were unavailable but
|
|
measurement indicated that CS, WP/ and HOLD/ are pulled up with 10KΩ. It is
|
|
therefore safe to leave WP/ and HOLD/ unconnected, and CS will behave properly
|
|
at power-up.
|
|
|
|
# 3. Files
|
|
|
|
1. `fram_spi.py` Device driver.
|
|
2. `bdevice.py` (In root directory) Base class for the device driver.
|
|
3. `fram_spi_test.py` Test programs for above. Assumes two 512KiB boards with
|
|
CS connected to pins Y4 and Y5 respectively. Adapt for other configurations.
|
|
4. `fram_fs_test.py` A torture test for littlefs.
|
|
|
|
Installation: copy files 1 and 2 to the target filesystem. `fram_spi_test.py`
|
|
has a function `test()` which provides quick verification of hardware, but
|
|
`cspins` and `get_fram` at the start of the file may need adaptation to your
|
|
hardware.
|
|
|
|
# 4. The device driver
|
|
|
|
The driver supports mounting the FRAM 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 one or more devices and also assumes the
|
|
littlefs filesystem:
|
|
|
|
```python
|
|
import os
|
|
from machine import SPI, Pin
|
|
from fram_spi import FRAM
|
|
cspins = (Pin(Pin.board.Y5, Pin.OUT, value=1),)
|
|
fram = FRAM(SPI(2, baudrate=25_000_000), cspins)
|
|
# Format the filesystem
|
|
os.VfsLfs2.mkfs(fram) # Omit this to mount an existing filesystem
|
|
os.mount(fram,'/fram')
|
|
```
|
|
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. The chips are
|
|
specified to a baudrate of 40MHz. I tested on a Pyboard D, specifying 25MHz -
|
|
this produced an actual baudrate of 18MHz.
|
|
|
|
## 4.1 The FRAM class
|
|
|
|
An `FRAM` instance represents a logical FRAM: this may consist of multiple
|
|
physical devices on a common SPI bus.
|
|
|
|
### 4.1.1 Constructor
|
|
|
|
This checks each CS line for an attached board of the correct type and of the
|
|
specified size. A `RuntimeError` will occur in case of error, e.g. bad ID, no
|
|
device detected or size not matching that specified to the constructor. If all
|
|
is OK an FRAM instance is created.
|
|
|
|
Arguments:
|
|
1. `spi` Mandatory. An initialised SPIbus 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=512` Chip size in KiB.
|
|
4. `verbose=True` If `True`, the constructor issues information on the FRAM
|
|
devices it has detected.
|
|
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.
|
|
|
|
### 4.1.2 Methods providing byte level access
|
|
|
|
It is possible to read and write individual bytes or arrays of arbitrary size.
|
|
Arrays will be somewhat faster owing to more efficient bus utilisation.
|
|
|
|
#### 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 fram_spi import FRAM
|
|
cspins = (Pin(Pin.board.Y5, Pin.OUT, value=1),)
|
|
fram = FRAM(SPI(2), cspins)
|
|
fram[2000] = 42
|
|
print(fram[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 fram_spi import FRAM
|
|
cspins = (Pin(Pin.board.Y5, Pin.OUT, value=1),)
|
|
fram = FRAM(SPI(2), cspins)
|
|
fram[2000:2002] = bytearray((42, 43))
|
|
print(fram[2000:2002]) # Returns a bytearray
|
|
```
|
|
Three argument slices are not supported: a third arg (other than 1) will cause
|
|
an exception. One argument slices (`fram[:5]` or `fram[32760:]`) and negative
|
|
args are supported.
|
|
|
|
#### 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
|
|
|
|
#### The len() operator
|
|
|
|
The size of the FRAM array in bytes may be retrieved by issuing `len(fram)`
|
|
where `fram` is the `FRAM` instance.
|
|
|
|
### 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).
|
|
|
|
These methods exist purely to support the block protocol. They are undocumented:
|
|
their use in application code is not recommended.
|
|
|
|
`readblocks()`
|
|
`writeblocks()`
|
|
`ioctl()`
|
|
|
|
# 5. Test program fram_spi_test.py
|
|
|
|
This assumes a Pyboard 1.x or Pyboard D with FRAM(s) wired as above. 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 128 byte page with
|
|
random data, reads it back, and checks the outcome. Existing array data will be
|
|
lost.
|
|
|
|
## 5.3 fstest(format=False)
|
|
|
|
If `True` is passed, formats the FRAM array as a FAT filesystem and mounts
|
|
the device on `/fram`. 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 FRAM becoming
|
|
full) it is up to the caller to handle it. For example (assuming the FRAM is
|
|
mounted on /fram):
|
|
|
|
```python
|
|
cp('/flash/main.py','/fram/')
|
|
```
|
|
|
|
See `upysh` in [micropython-lib](https://github.com/micropython/micropython-lib/tree/master/micropython/upysh)
|
|
for more fully developed filesystem tools for use at the REPL.
|
|
|
|
# 6. Low power operation
|
|
|
|
In the absence of an SPI clock signal the chip is specified to draw 50μA max.
|
|
This can be reduced to 8μA max by issuing a sleep command. Code to support this
|
|
is provided in `fram_spi.py` but is commented out; it is a somewhat specialised
|
|
requirement.
|
|
|
|
# 7. References
|
|
|
|
[256KiB Adafruit board](http://www.adafruit.com/product/4718)
|
|
[512KiB Adafruit board](http://www.adafruit.com/product/4719)
|
|
[256KiB Chip datasheet](https://cdn-shop.adafruit.com/product-files/4718/4718_MB85RS2MTA.pdf)
|
|
[512KiB Chip datasheet](https://cdn-shop.adafruit.com/product-files/4719/4719_MB85RS4MT.pdf)
|
|
[Technology](https://www.mouser.com/pdfDOCS/cypress-fram-whitepaper.pdf)
|