kopia lustrzana https://github.com/peterhinch/micropython_eeprom
Support SPI FRAM 256 and 512KiB devices.
rodzic
55f62ce514
commit
9ebeeda5b0
|
@ -0,0 +1,163 @@
|
||||||
|
# 1. Base classes for memory device drivers
|
||||||
|
|
||||||
|
This doc is primarily to aid those wishing to use these base classes to write
|
||||||
|
drivers for additional memory devices. It describes the two classes in
|
||||||
|
`bdevice.py` namely `BlockDevice` and the subclass `FlashDevice`. Both provide
|
||||||
|
hardware-independent abstractions of memory devices. The base class provides
|
||||||
|
the API. This has the following characteristics:
|
||||||
|
1. Support for single or multiple chips on the same bus. Multiple chips are
|
||||||
|
automatically configured as a single byte array.
|
||||||
|
2. The byte array can be accessed using Python slice syntax.
|
||||||
|
3. Alternatively the array can be formatted and mounted as a filesystem using
|
||||||
|
methods in the `uos` module. Any filesystem supported by the MicroPython build
|
||||||
|
may be employed: FAT and littlefs have been tested. The latter is recommended.
|
||||||
|
|
||||||
|
The `BlockDevice` class supports byte-addressable technologies such as EEPROM
|
||||||
|
and FRAM. Such devices can be written on a single byte basis. Where a chip also
|
||||||
|
offers multi-byte writes this optimisation can be handled in the user driver:
|
||||||
|
see the EEPROM drivers for examples of this.
|
||||||
|
|
||||||
|
`FlashDevice` subclasses `BlockDevice` to support devices which must buffer a
|
||||||
|
sector of data for writing. The API continues to support byte addressing: this
|
||||||
|
is achieved by modifying the buffer contents and writing it out when necessary.
|
||||||
|
|
||||||
|
# 2. The BlockDevice class
|
||||||
|
|
||||||
|
The class provides these characteristics:
|
||||||
|
1. An API which represents multiple physical devices as a single byte array.
|
||||||
|
The physical means of achieving this is provided in the hardware subclass.
|
||||||
|
2. An implementation of the `AbstractBlockDev` protocol with extended
|
||||||
|
interface as required by littlefs as documented
|
||||||
|
[here](http://docs.micropython.org/en/latest/library/uos.html).
|
||||||
|
3. An API based on Python slice notation for byte level access to the array.
|
||||||
|
4. Support for the `len` operator.
|
||||||
|
|
||||||
|
## 2.1 Constructor
|
||||||
|
|
||||||
|
Constructor args - mandatory, positional, integer
|
||||||
|
1. `nbits` Block size reported to the filesystem expressed as a number of
|
||||||
|
bits: the block size is `2^nbits`. The usual value is 9 (512 bit block).
|
||||||
|
2. `nchips` Number of chips in the array.
|
||||||
|
3. `chip_size` Size of each chip in bytes.
|
||||||
|
|
||||||
|
## 2.2 Necessary subclass support
|
||||||
|
|
||||||
|
The subclass must provide a method `readwrite` taking the following args:
|
||||||
|
1. `addr` Address relative to the start of the array.
|
||||||
|
2. `buf` A buffer holding data to write or to contain data to be read.
|
||||||
|
3. `read` Boolean: `True` to read, `False` to write.
|
||||||
|
|
||||||
|
The amount of data read or written is defined by the length of the buffer.
|
||||||
|
|
||||||
|
Return value: the buffer.
|
||||||
|
|
||||||
|
The method must handle the case where a buffer crosses chip boundaries. This
|
||||||
|
involves physical accesses to each chip and reading or writing partial buffer
|
||||||
|
contents. Addresses are converted by the method to chip-relative addresses.
|
||||||
|
|
||||||
|
## 2.3 The `AbstractBlockDev` protocol
|
||||||
|
|
||||||
|
This is provided by the following methods:
|
||||||
|
1. `sync()` In the `BlockDevice` class this does nothing. It is defined in the
|
||||||
|
`FlashDevice` class [section 3.3](./BASE_CLASSES.md#33-methods).
|
||||||
|
2. `readblocks(blocknum, buf, offset=0)` Converts the block address and offset
|
||||||
|
to an absolute address into the array and calls `readwrite`.
|
||||||
|
3. `writeblocks(blocknum, buf, offset=0` Works as above.
|
||||||
|
4. `ioctl` This supports the following operands:
|
||||||
|
|
||||||
|
3. `sync` Calls the `.sync()` method.
|
||||||
|
4. `sector count` Returns `chip_size` * `nchips` // `block_size`
|
||||||
|
5. `block size` Returns block size calculated as in section 2.1.
|
||||||
|
6. `erase` Necessary for correct filesystem operation: returns 0.
|
||||||
|
|
||||||
|
The drivers make no use of the block size: it exists only for filesystems. The
|
||||||
|
`readwrite` method hides any physical device structure presenting an array of
|
||||||
|
bytes. The specified block size must match the intended filesystem. Littlefs
|
||||||
|
requires >=128 bytes, FATFS requires >=512 bytes. All testing was done with 512
|
||||||
|
byte blocks.
|
||||||
|
|
||||||
|
## 2.4 Byte level access
|
||||||
|
|
||||||
|
This is provided by `__getitem__` and `__setitem__`. The `addr` arg can be an
|
||||||
|
integer or a slice, enabling the following syntax examples:
|
||||||
|
```python
|
||||||
|
a = eep[1000] # Read a single byte
|
||||||
|
eep[1000] = 42 # write a byte
|
||||||
|
eep[1000:1004] = b'\x11\x22\x33\x44' # Write 4 consecutive bytes
|
||||||
|
b = eep[1000:1004] # Read 4 consecutive bytes
|
||||||
|
```
|
||||||
|
The last example necessarily performs allocation in the form of a buffer for
|
||||||
|
the resultant data. Applications can perform allocation-free reading by calling
|
||||||
|
the `readwrite` method directly.
|
||||||
|
|
||||||
|
## 2.5 The len operator
|
||||||
|
|
||||||
|
This returns the array size in bytes.
|
||||||
|
|
||||||
|
# 3. The FlashDevice class
|
||||||
|
|
||||||
|
By subclassing `BlockDevice`, `FlashDevice` provides the same API for flash
|
||||||
|
devices. At a hardware level reading is byte addressable in a similar way to
|
||||||
|
EEPROM and FRAM devices. These chips do not support writing arbitrary data to
|
||||||
|
individual byte addresses. Writing is done by erasing a block, then rewriting
|
||||||
|
it with new contents. To provide logical byte level writing it is necessary to
|
||||||
|
read and buffer the block containing the byte, update the byte, erase the block
|
||||||
|
and write out the buffer.
|
||||||
|
|
||||||
|
In practice this would be slow and inefficient - erasure is a slow process and
|
||||||
|
results in wear. The `FlashDevice` class defers writing the buffer until it is
|
||||||
|
necessary to buffer a different block.
|
||||||
|
|
||||||
|
The class caches a single sector. In currently supported devices this is 4KiB
|
||||||
|
of RAM. This is adequate for littlefs, however under FATFS wear can be reduced
|
||||||
|
by cacheing more than one sector. These drivers are primarily intended for
|
||||||
|
littlefs with its wear levelling design.
|
||||||
|
|
||||||
|
## 3.1 Constructor
|
||||||
|
|
||||||
|
Constructor args - mandatory, positional, integer
|
||||||
|
1. `nbits` Block size reported to the filesystem expressed as a number of
|
||||||
|
bits: the block size is `2^nbits`. The usual value is 9 (512 bit block).
|
||||||
|
2. `nchips` Number of chips in the array.
|
||||||
|
3. `chip_size` Size of each chip in bytes.
|
||||||
|
4. `sec_size` Physical sector size of the device in bytes.
|
||||||
|
|
||||||
|
## 3.2 Necessary subclass support
|
||||||
|
|
||||||
|
A subclass supporting a flash device must provide the following methods:
|
||||||
|
1. `readwrite(addr, buf, read)` Args as defined in section 2.2. This calls the
|
||||||
|
`.read` or `.write` methods of `FlashDevice` as required.
|
||||||
|
2. `rdchip(addr, mvb)` Args `addr`: address into the array, `mvb` a
|
||||||
|
`memoryview` into a buffer for read data. This reads from the chip into the
|
||||||
|
`memoryview`.
|
||||||
|
3. `flush(cache, addr)` Args `cache` a buffer holding one sector of data,
|
||||||
|
`addr` address into the array of the start of a physical sector. Erase the
|
||||||
|
sector and write out the data in `cache`.
|
||||||
|
|
||||||
|
The constructor must call `initialise()` after the hardware has been
|
||||||
|
initialised to ensure valid cache contents.
|
||||||
|
|
||||||
|
## 3.3 Methods
|
||||||
|
|
||||||
|
1. `read(addr, mvb`) Args `addr` address into array, `mvb` a `memoryview` into
|
||||||
|
a buffer. Fills the `memoryview` with data read. If some or all of the data is
|
||||||
|
cached, the cached data is provided.
|
||||||
|
2. `write(addr, mvb`) Args `addr` address into array, `mvb` a `memoryview`
|
||||||
|
into a buffer. If the address range is cached, the cache contents are updated.
|
||||||
|
More generally the currently cached data is written out using `flush`, a new
|
||||||
|
sector is cached, and the contents updated. Depending on the size of the data
|
||||||
|
buffer this may occur multiple times.
|
||||||
|
3. `sync()` This flushes the current cache. An optimisation is provided by the
|
||||||
|
`._dirty` flag. This ensures that the cache is only flushed if its contents
|
||||||
|
have been modified since it was last written out.
|
||||||
|
4. `is_empty(addr, ev=0xff)` Arg: `addr` start address of a sector. Reads the
|
||||||
|
sector returning `True` if all bytes match `ev`. Enables a subclass to avoid
|
||||||
|
erasing a sector which is already empty.
|
||||||
|
5. `initialise()` Called by the subclass constructor to populate the cache
|
||||||
|
with the contents of sector 0.
|
||||||
|
|
||||||
|
# 4. References
|
||||||
|
|
||||||
|
[uos docs](http://docs.micropython.org/en/latest/library/uos.html)
|
||||||
|
[Custom block devices](http://docs.micropython.org/en/latest/reference/filesystem.html#custom-block-devices)
|
||||||
|
[Littlefs](https://github.com/ARMmbed/littlefs/blob/master/DESIGN.md)
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
These drivers support nonvolatile memory chips and the littlefs filesystem.
|
These drivers support nonvolatile memory chips and the littlefs filesystem.
|
||||||
|
|
||||||
|
Now includes support for 256 and 512KiB FRAM devices.
|
||||||
|
|
||||||
Currently supported devices include 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
|
||||||
|
@ -66,6 +68,8 @@ In the table below the Interface column includes page size in bytes.
|
||||||
| Microchip | 24xx256 | I2C 128 | 32KiB | EEPROM | [I2C.md](./eeprom/i2c/I2C.md) |
|
| Microchip | 24xx256 | I2C 128 | 32KiB | EEPROM | [I2C.md](./eeprom/i2c/I2C.md) |
|
||||||
| Microchip | 24xx128 | I2C 128 | 16KiB | EEPROM | [I2C.md](./eeprom/i2c/I2C.md) |
|
| Microchip | 24xx128 | I2C 128 | 16KiB | EEPROM | [I2C.md](./eeprom/i2c/I2C.md) |
|
||||||
| Microchip | 24xx64 | I2C 128 | 8KiB | EEPROM | [I2C.md](./eeprom/i2c/I2C.md) |
|
| Microchip | 24xx64 | I2C 128 | 8KiB | EEPROM | [I2C.md](./eeprom/i2c/I2C.md) |
|
||||||
|
| Adafruit | 4719 | SPI n/a | 512KiB | FRAM | [FRAM_SPI.md](./fram/FRAM_SPI.md) |
|
||||||
|
| Adafruit | 4718 | SPI n/a | 256KiB | FRAM | [FRAM_SPI.md](./fram/FRAM_SPI.md) |
|
||||||
| Adafruit | 1895 | I2C n/a | 32KiB | FRAM | [FRAM.md](./fram/FRAM.md) |
|
| Adafruit | 1895 | I2C n/a | 32KiB | FRAM | [FRAM.md](./fram/FRAM.md) |
|
||||||
|
|
||||||
The flash driver now has the capability to support a variety of chips. The
|
The flash driver now has the capability to support a variety of chips. The
|
||||||
|
@ -85,7 +89,7 @@ for compatibility.
|
||||||
## 1.5 Performance
|
## 1.5 Performance
|
||||||
|
|
||||||
FRAM is truly byte-addressable: its speed is limited only by the speed of the
|
FRAM is truly byte-addressable: its speed is limited only by the speed of the
|
||||||
I2C interface.
|
I2C or SPI interface (SPI being much faster).
|
||||||
|
|
||||||
Reading from EEPROM chips is fast. Writing is slower, typically around 5ms.
|
Reading from EEPROM chips is fast. Writing is slower, typically around 5ms.
|
||||||
However where multiple bytes are written, that 5ms applies to a page of data so
|
However where multiple bytes are written, that 5ms applies to a page of data so
|
||||||
|
|
|
@ -0,0 +1,241 @@
|
||||||
|
# 1. A MicroPython SPI FRAM driver
|
||||||
|
|
||||||
|
A driver to enable the Pyboard 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 Pyboard 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 devicess 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.
|
||||||
|
|
||||||
|
Installation: copy files 1 and 2 (optionally 3) to the target filesystem.
|
||||||
|
|
||||||
|
# 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).
|
||||||
|
|
||||||
|
`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.git)
|
||||||
|
for other 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)
|
|
@ -0,0 +1,77 @@
|
||||||
|
# littlefs_test.py Extended filesystem test of FRAM devices
|
||||||
|
# Create multiple binary files of varying length and verify that they can be
|
||||||
|
# read back correctly. Rewrite files with new lengths then check that all files
|
||||||
|
# are OK.
|
||||||
|
|
||||||
|
import uos
|
||||||
|
from machine import SPI, Pin
|
||||||
|
from fram_spi_test import get_fram
|
||||||
|
|
||||||
|
directory = '/fram'
|
||||||
|
a = bytearray(range(256))
|
||||||
|
b = bytearray(256)
|
||||||
|
files = {} # n:length
|
||||||
|
errors = 0
|
||||||
|
|
||||||
|
def fname(n):
|
||||||
|
return '{}/{:05d}'.format(directory, n + 1) # Names start 00001
|
||||||
|
|
||||||
|
def fcreate(n): # Create a binary file of random length
|
||||||
|
length = int.from_bytes(uos.urandom(2), 'little') + 1 # 1-65536 bytes
|
||||||
|
length &= 0x3ff # 1-1023 for FRAM
|
||||||
|
linit = length
|
||||||
|
with open(fname(n), 'wb') as f:
|
||||||
|
while(length):
|
||||||
|
nw = min(length, 256)
|
||||||
|
f.write(a[:nw])
|
||||||
|
length -= nw
|
||||||
|
files[n] = length
|
||||||
|
return linit
|
||||||
|
|
||||||
|
def fcheck(n):
|
||||||
|
length = files[n]
|
||||||
|
with open(fname(n), 'rb') as f:
|
||||||
|
while(length):
|
||||||
|
nr = f.readinto(b)
|
||||||
|
if not nr:
|
||||||
|
return False
|
||||||
|
if a[:nr] != b[:nr]:
|
||||||
|
return False
|
||||||
|
length -= nr
|
||||||
|
return True
|
||||||
|
|
||||||
|
def check_all():
|
||||||
|
global errors
|
||||||
|
for n in files:
|
||||||
|
if fcheck(n):
|
||||||
|
print('File {:d} OK'.format(n))
|
||||||
|
else:
|
||||||
|
print('Error in file', n)
|
||||||
|
errors += 1
|
||||||
|
print('Total errors:', errors)
|
||||||
|
|
||||||
|
|
||||||
|
def remove_all():
|
||||||
|
for n in files:
|
||||||
|
uos.remove(fname(n))
|
||||||
|
|
||||||
|
def main():
|
||||||
|
fram = get_fram()
|
||||||
|
try:
|
||||||
|
uos.mount(fram, directory)
|
||||||
|
except OSError: # Already mounted
|
||||||
|
pass
|
||||||
|
for n in range(128):
|
||||||
|
length = fcreate(n)
|
||||||
|
print('Created', n, length)
|
||||||
|
print('Created files', files)
|
||||||
|
check_all()
|
||||||
|
for _ in range(100):
|
||||||
|
for x in range(5): # Rewrite 5 files with new lengths
|
||||||
|
n = int.from_bytes(uos.urandom(1), 'little') & 0x7f
|
||||||
|
length = fcreate(n)
|
||||||
|
print('Rewrote', n, length)
|
||||||
|
check_all()
|
||||||
|
remove_all()
|
||||||
|
|
||||||
|
print('main() to run littlefs test. Filesystem must exist.')
|
|
@ -0,0 +1,133 @@
|
||||||
|
# fram_spi.py Supports Fujitsu 256KiB and 512KiB FRAM devices
|
||||||
|
# M85RS2MT Adafruit https://www.adafruit.com/product/4718
|
||||||
|
# M85RS4MT Adafruit https://www.adafruit.com/product/4719
|
||||||
|
|
||||||
|
# These chips are almost identical. Command sets are identical.
|
||||||
|
# Product ID 1st byte, LS 4 bits is density 0x8 == 2MiB 0x9 == 4MiB
|
||||||
|
|
||||||
|
# Released under the MIT License (MIT). See LICENSE.
|
||||||
|
# Copyright (c) 2020 Peter Hinch
|
||||||
|
|
||||||
|
from micropython import const
|
||||||
|
from bdevice import BlockDevice
|
||||||
|
# import time # for sleep command
|
||||||
|
|
||||||
|
# Command set
|
||||||
|
_WREN = const(6)
|
||||||
|
_WRDI = const(4)
|
||||||
|
_RDSR = const(5) # Read status reg
|
||||||
|
_WRSR = const(1)
|
||||||
|
_READ = const(3)
|
||||||
|
_WRITE = const(2)
|
||||||
|
_RDID = const(0x9f)
|
||||||
|
# _FSTRD = const(0x0b) No obvious difference to _READ
|
||||||
|
_SLEEP = const(0xb9)
|
||||||
|
|
||||||
|
|
||||||
|
class FRAM(BlockDevice):
|
||||||
|
def __init__(self, spi, cspins, size=512, verbose=True, block_size=9):
|
||||||
|
if size not in (256, 512):
|
||||||
|
raise ValueError('FRAM size must be 256 or 512')
|
||||||
|
super().__init__(block_size, len(cspins), size * 1024)
|
||||||
|
self._spi = spi
|
||||||
|
self._cspins = cspins
|
||||||
|
self._ccs = None # Chip select Pin object for current chip
|
||||||
|
self._bufp = bytearray(5) # instruction + 3 byte address + 1 byte value
|
||||||
|
mvp = memoryview(self._bufp) # cost-free slicing
|
||||||
|
self._mvp = mvp
|
||||||
|
# Check hardware
|
||||||
|
density = 8 if size == 256 else 9
|
||||||
|
for n, cs in enumerate(cspins):
|
||||||
|
mvp[:] = b'\0\0\0\0\0'
|
||||||
|
mvp[0] = _RDID
|
||||||
|
cs(0)
|
||||||
|
self._spi.write_readinto(mvp, mvp)
|
||||||
|
cs(1)
|
||||||
|
# Ignore bits labelled "proprietary"
|
||||||
|
if mvp[1] != 4 or mvp[2] != 0x7f:
|
||||||
|
s = 'FRAM not found at cspins[{}].'
|
||||||
|
raise RuntimeError(s.format(n))
|
||||||
|
if (mvp[3] & 0x1f) != density:
|
||||||
|
s = 'FRAM at cspins[{}] is incorrect size.'
|
||||||
|
raise RuntimeError(s.format(n))
|
||||||
|
if verbose:
|
||||||
|
s = 'Total FRAM size {} bytes in {} devices.'
|
||||||
|
print(s.format(self._a_bytes, n + 1))
|
||||||
|
# Set up status register on each chip
|
||||||
|
for cs in cspins:
|
||||||
|
self._wrctrl(cs, True)
|
||||||
|
mvp[0] = _WRSR
|
||||||
|
mvp[1] = 0 # No block protect or SR protect
|
||||||
|
cs(0)
|
||||||
|
self._spi.write(mvp[:2])
|
||||||
|
cs(1)
|
||||||
|
self._wrctrl(cs, False) # Disable write to array
|
||||||
|
|
||||||
|
for n, cs in enumerate(self._cspins):
|
||||||
|
mvp[0] = _RDSR
|
||||||
|
cs(0)
|
||||||
|
self._spi.write_readinto(mvp[:2], mvp[:2])
|
||||||
|
cs(1)
|
||||||
|
if mvp[1]:
|
||||||
|
s = 'FRAM has bad status at cspins[{}].'
|
||||||
|
raise RuntimeError(s.format(n))
|
||||||
|
|
||||||
|
def _wrctrl(self, cs, en): # Enable/Disable device write
|
||||||
|
mvp = self._mvp
|
||||||
|
mvp[0] = _WREN if en else _WRDI
|
||||||
|
cs(0)
|
||||||
|
self._spi.write(mvp[:1])
|
||||||
|
cs(1)
|
||||||
|
|
||||||
|
#def sleep(self, on):
|
||||||
|
#mvp = self._mvp
|
||||||
|
#mvp[0] = _SLEEP
|
||||||
|
#for cs in self._cspins:
|
||||||
|
#cs(0)
|
||||||
|
#if on:
|
||||||
|
#self._spi.write(mvp[:1])
|
||||||
|
#else:
|
||||||
|
#time.sleep_us(500)
|
||||||
|
#cs(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("FRAM 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 >> 16
|
||||||
|
mvp[2] = (la >> 8) & 0xff
|
||||||
|
mvp[3] = la & 0xff
|
||||||
|
pe = (addr & ~0xff) + 0x100 # byte 0 of next chip
|
||||||
|
return min(nbytes, pe - la)
|
||||||
|
|
||||||
|
# Interface to bdevice
|
||||||
|
def readwrite(self, addr, buf, read):
|
||||||
|
nbytes = len(buf)
|
||||||
|
mvb = memoryview(buf)
|
||||||
|
mvp = self._mvp
|
||||||
|
start = 0 # Offset into buf.
|
||||||
|
while nbytes > 0:
|
||||||
|
npage = self._getaddr(addr, nbytes) # No of bytes that fit on current chip
|
||||||
|
cs = self._ccs
|
||||||
|
if read:
|
||||||
|
mvp[0] = _READ
|
||||||
|
cs(0)
|
||||||
|
self._spi.write(mvp[:4])
|
||||||
|
self._spi.readinto(mvb[start : start + npage])
|
||||||
|
cs(1)
|
||||||
|
else:
|
||||||
|
self._wrctrl(cs, True)
|
||||||
|
mvp[0] = _WRITE
|
||||||
|
cs(0)
|
||||||
|
self._spi.write(mvp[:4])
|
||||||
|
self._spi.write(mvb[start: start + npage])
|
||||||
|
cs(1)
|
||||||
|
self._wrctrl(cs, False)
|
||||||
|
nbytes -= npage
|
||||||
|
start += npage
|
||||||
|
addr += npage
|
||||||
|
return buf
|
|
@ -0,0 +1,131 @@
|
||||||
|
# fram_spi_ test.py MicroPython test program for Adafruit SPI FRAM devices.
|
||||||
|
|
||||||
|
# Released under the MIT License (MIT). See LICENSE.
|
||||||
|
# Copyright (c) 2020 Peter Hinch
|
||||||
|
|
||||||
|
import uos
|
||||||
|
import time
|
||||||
|
from machine import SPI, Pin
|
||||||
|
from fram_spi import FRAM
|
||||||
|
|
||||||
|
cspins = (Pin(Pin.board.Y5, Pin.OUT, value=1), Pin(Pin.board.Y4, Pin.OUT, value=1))
|
||||||
|
|
||||||
|
# Return an FRAM array. Adapt for platforms other than Pyboard.
|
||||||
|
def get_fram():
|
||||||
|
if uos.uname().machine.split(' ')[0][:4] == 'PYBD':
|
||||||
|
Pin.board.EN_3V3.value(1)
|
||||||
|
time.sleep(0.1) # Allow decouplers to charge
|
||||||
|
fram = FRAM(SPI(2, baudrate=25_000_000), cspins, size=512) # Change size as required
|
||||||
|
print('Instantiated FRAM')
|
||||||
|
return fram
|
||||||
|
|
||||||
|
# Dumb file copy utility to help with managing FRAM contents at the REPL.
|
||||||
|
def cp(source, dest):
|
||||||
|
if dest.endswith('/'): # minimal way to allow
|
||||||
|
dest = ''.join((dest, source.split('/')[-1])) # cp /sd/file /fram/
|
||||||
|
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():
|
||||||
|
fram = get_fram()
|
||||||
|
sa = 1000
|
||||||
|
for v in range(256):
|
||||||
|
fram[sa + v] = v
|
||||||
|
for v in range(256):
|
||||||
|
if fram[sa + v] != v:
|
||||||
|
print('Fail at address {} data {} should be {}'.format(sa + v, fram[sa + v], v))
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
print('Test of byte addressing passed')
|
||||||
|
data = uos.urandom(30)
|
||||||
|
sa = 2000
|
||||||
|
fram[sa:sa + 30] = data
|
||||||
|
if fram[sa:sa + 30] == data:
|
||||||
|
print('Test of slice readback passed')
|
||||||
|
# On FRAM the only meaningful block test is on a chip boundary.
|
||||||
|
block = fram._c_bytes
|
||||||
|
if fram._a_bytes > block:
|
||||||
|
res = _testblock(fram, 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):
|
||||||
|
fram = get_fram()
|
||||||
|
if format:
|
||||||
|
uos.VfsFat.mkfs(fram)
|
||||||
|
vfs=uos.VfsFat(fram)
|
||||||
|
try:
|
||||||
|
uos.mount(vfs,'/fram')
|
||||||
|
except OSError: # Already mounted
|
||||||
|
pass
|
||||||
|
print('Contents of "/": {}'.format(uos.listdir('/')))
|
||||||
|
print('Contents of "/fram": {}'.format(uos.listdir('/fram')))
|
||||||
|
print(uos.statvfs('/fram'))
|
||||||
|
|
||||||
|
def cptest():
|
||||||
|
fram = get_fram()
|
||||||
|
if 'fram' in uos.listdir('/'):
|
||||||
|
print('Device already mounted.')
|
||||||
|
else:
|
||||||
|
vfs=uos.VfsFat(fram)
|
||||||
|
try:
|
||||||
|
uos.mount(vfs,'/fram')
|
||||||
|
except OSError:
|
||||||
|
print('Fail mounting device. Have you formatted it?')
|
||||||
|
return
|
||||||
|
print('Mounted device.')
|
||||||
|
cp('fram_spi_test.py', '/fram/')
|
||||||
|
cp('fram_spi.py', '/fram/')
|
||||||
|
print('Contents of "/fram": {}'.format(uos.listdir('/fram')))
|
||||||
|
print(uos.statvfs('/fram'))
|
||||||
|
|
||||||
|
# ***** TEST OF HARDWARE *****
|
||||||
|
def full_test():
|
||||||
|
fram = get_fram()
|
||||||
|
page = 0
|
||||||
|
for sa in range(0, len(fram), 256):
|
||||||
|
data = uos.urandom(256)
|
||||||
|
fram[sa:sa + 256] = data
|
||||||
|
if fram[sa:sa + 256] == data:
|
||||||
|
print('Page {} passed'.format(page))
|
||||||
|
else:
|
||||||
|
print('Page {} readback failed.'.format(page))
|
||||||
|
page += 1
|
Ładowanie…
Reference in New Issue