kopia lustrzana https://github.com/peterhinch/micropython_eeprom
Add SPIRAM (PSRAM) support.
rodzic
da8c9f54f1
commit
2f4bb08820
32
README.md
32
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.
|
||||||
|
|
||||||
Now includes support for 256 and 512KiB FRAM devices.
|
Now includes support for 256 and 512KiB FRAM devices and 8MiB PSRAM chips.
|
||||||
|
|
||||||
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
|
||||||
|
@ -28,15 +28,18 @@ The drivers have the following common features:
|
||||||
|
|
||||||
## 1.2 Technologies
|
## 1.2 Technologies
|
||||||
|
|
||||||
Currently supported technologies are Flash, EEPROM and FRAM (ferroelectric
|
Currently supported technologies are SPIRAM (PSRAM), Flash, EEPROM, and FRAM
|
||||||
RAM). The latter two are nonvolatile random access storage devices with much
|
(ferroelectric RAM). The latter two are nonvolatile random access storage
|
||||||
higher endurance than flash memory. Flash has a typical endurance of 10-100K
|
devices with much higher endurance than flash memory. Flash has a typical
|
||||||
writes per page. The figures for EEPROM and FRAM are 1-4M and 10^12 writes
|
endurance of 10-100K writes per page. The figures for EEPROM and FRAM are 1-4M
|
||||||
respectively. In the case of the FAT filing system 1M page writes probably
|
and 10^12 writes respectively. In the case of the FAT filing system 1M page
|
||||||
corresponds to 1M filesystem writes because FAT repeatedly updates the
|
writes probably corresponds to 1M filesystem writes because FAT repeatedly
|
||||||
allocation tables in the low numbered sectors. Under `littlefs` I would expect
|
updates the allocation tables in the low numbered sectors. Under `littlefs` I
|
||||||
the endurance to be substantially better owing to its wear levelling
|
would expect the endurance to be substantially better owing to its wear
|
||||||
architecture; over-provisioning should enhance this.
|
levelling architecture; over-provisioning should enhance this.
|
||||||
|
|
||||||
|
SPIRAM has huge capacity and effectively infinite endurance. Unlike the other
|
||||||
|
technologies it is volatile: contents are lost after a power cycle.
|
||||||
|
|
||||||
## 1.3 Organisation of this repo
|
## 1.3 Organisation of this repo
|
||||||
|
|
||||||
|
@ -71,6 +74,9 @@ In the table below the Interface column includes page size in bytes.
|
||||||
| Adafruit | 4719 | SPI n/a | 512KiB | FRAM | [FRAM_SPI.md](./fram/FRAM_SPI.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 | 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) |
|
||||||
|
| Adafruit | 4677 | SPI n/a | 8MiB | SPIRAM | [SPIRAM.md](./spiram/SPIRAM.md) |
|
||||||
|
|
||||||
|
The SPIRAM chip is equivalent to Espressif ESP-PSRAM64H.
|
||||||
|
|
||||||
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
|
||||||
following have been tested to date:
|
following have been tested to date:
|
||||||
|
@ -102,8 +108,8 @@ This requires setting `cmd5=False`.
|
||||||
|
|
||||||
## 1.5 Performance
|
## 1.5 Performance
|
||||||
|
|
||||||
FRAM is truly byte-addressable: its speed is limited only by the speed of the
|
FRAM and SPIRAM are truly byte-addressable: speed is limited only by the speed
|
||||||
I2C or SPI interface (SPI being much faster).
|
of the 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
|
||||||
|
@ -139,7 +145,7 @@ The larger capacity chips generally use SPI.
|
||||||
A key aim of these drivers is support for littlefs. This requires the extended
|
A key aim of these drivers is support for littlefs. This requires the extended
|
||||||
block device protocol as described
|
block device protocol as described
|
||||||
[here](http://docs.micropython.org/en/latest/reference/filesystem.html) and
|
[here](http://docs.micropython.org/en/latest/reference/filesystem.html) and
|
||||||
[in the uos doc](http://docs.micropython.org/en/latest/library/uos.html).
|
[in the uos doc](http://docs.micropython.org/en/latest/library/os.html).
|
||||||
This protocol describes a block structured API capable of handling offsets into
|
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
|
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
|
block structuring inherent in the hardware. The device driver must enable
|
||||||
|
|
|
@ -1,22 +1,22 @@
|
||||||
# 1. A MicroPython SPI FRAM driver
|
# 1. A MicroPython SPI FRAM driver
|
||||||
|
|
||||||
A driver to enable the Pyboard to access Ferroelectric RAM (FRAM) boards from
|
A driver to enable MicroPython hosts to access Ferroelectric RAM (FRAM) boards
|
||||||
Adafruit, namely [the 256KiB board](https://www.adafruit.com/product/4718) and
|
from Adafruit, namely [the 256KiB board](https://www.adafruit.com/product/4718)
|
||||||
[the 512KiB board](https://www.adafruit.com/product/4719). FRAM is a technology
|
and [the 512KiB board](https://www.adafruit.com/product/4719). FRAM is a
|
||||||
offering nonvolatile memory with extremely long endurance and fast access,
|
technology offering nonvolatile memory with extremely long endurance and fast
|
||||||
avoiding the
|
access, avoiding the limitations of Flash memory. Its endurance is specified as
|
||||||
limitations of Flash memory. Its endurance is specified as 10**13 writes,
|
10**13 writes, contrasted with 10,000 which is the quoted endurance of the
|
||||||
contrasted with 10,000 which is the quoted endurance of the Pyboard's onboard
|
Pyboard's onboard Flash memory. In data logging applications the latter can be
|
||||||
Flash memory. In data logging applications the latter can be exceeded relatively
|
exceeded relatively rapidly. Flash writes can be slow because of the need for a
|
||||||
rapidly. Flash writes can be slow because of the need for a sector erase: this
|
sector erase: this is not a fast process. FRAM is byte addressable and is not
|
||||||
is not a fast process. FRAM is byte addressable and is not subject to this
|
subject to this limitation. Compared to a Micro SD card fitted to the Pyboard
|
||||||
limitation. Compared to a Micro SD card fitted to the Pyboard it offers lower
|
it offers lower power consumption and longer endurance, albeit at a smaller
|
||||||
power consumption and longer endurance, albeit at a smaller capacity.
|
capacity.
|
||||||
|
|
||||||
An arbitrary number of boards may be used to construct a nonvolatile memory
|
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
|
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
|
mounted in the host filesystem as a disk device or to be addressed as an array
|
||||||
array of bytes.
|
of bytes.
|
||||||
|
|
||||||
For users interested in the technology [this](https://www.mouser.com/pdfDOCS/cypress-fram-whitepaper.pdf)
|
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.
|
is worth reading. Clue: the FRAM cell contains no iron.
|
||||||
|
@ -45,7 +45,7 @@ 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,
|
must be wired to a single board's CS line. Multiple boards should have Vin, Gnd,
|
||||||
SCK, MOSI and MISO lines wired in parallel.
|
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
|
If you use a Pyboard D and power the devices from the 3V3 output you will need
|
||||||
to enable the voltage rail by issuing:
|
to enable the voltage rail by issuing:
|
||||||
```python
|
```python
|
||||||
machine.Pin.board.EN_3V3.value(1)
|
machine.Pin.board.EN_3V3.value(1)
|
||||||
|
@ -226,8 +226,8 @@ mounted on /fram):
|
||||||
cp('/flash/main.py','/fram/')
|
cp('/flash/main.py','/fram/')
|
||||||
```
|
```
|
||||||
|
|
||||||
See `upysh` in [micropython-lib](https://github.com/micropython/micropython-lib.git)
|
See `upysh` in [micropython-lib](https://github.com/micropython/micropython-lib/tree/master/micropython/upysh)
|
||||||
for other filesystem tools for use at the REPL.
|
for more fully developed filesystem tools for use at the REPL.
|
||||||
|
|
||||||
# 6. Low power operation
|
# 6. Low power operation
|
||||||
|
|
||||||
|
@ -238,8 +238,8 @@ requirement.
|
||||||
|
|
||||||
# 7. References
|
# 7. References
|
||||||
|
|
||||||
[256KiB Adafruit board](http://www.adafruit.com/product/4718)
|
[256KiB Adafruit board](http://www.adafruit.com/product/4718)
|
||||||
[512KiB Adafruit board](http://www.adafruit.com/product/4719)
|
[512KiB Adafruit board](http://www.adafruit.com/product/4719)
|
||||||
[256KiB Chip datasheet](https://cdn-shop.adafruit.com/product-files/4718/4718_MB85RS2MTA.pdf)
|
[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)
|
[512KiB Chip datasheet](https://cdn-shop.adafruit.com/product-files/4719/4719_MB85RS4MT.pdf)
|
||||||
[Technology](https://www.mouser.com/pdfDOCS/cypress-fram-whitepaper.pdf)
|
[Technology](https://www.mouser.com/pdfDOCS/cypress-fram-whitepaper.pdf)
|
||||||
|
|
|
@ -0,0 +1,224 @@
|
||||||
|
# 1. A MicroPython SPIRAM driver
|
||||||
|
|
||||||
|
A driver to enable MicroPython targets to access the SPIRAM (PSRAM) board from
|
||||||
|
Adafruit, namely [the 8MiB board](https://www.adafruit.com/product/4677). The
|
||||||
|
SPIRAM chip is equivalent to Espressif ESP-PSRAM64H. SPIRAM offers infinite
|
||||||
|
endurance and fast access but is volatile: its contents are lost on power down.
|
||||||
|
|
||||||
|
An arbitrary number of boards may be used to construct a memory array whose
|
||||||
|
size is a multiple of 8MiB. 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.
|
||||||
|
|
||||||
|
##### [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 RAM chip, connect to a Pyboard as
|
||||||
|
below (n/c indicates no connection):
|
||||||
|
|
||||||
|
| Pin | Signal | PB | Signal |
|
||||||
|
|:---:|:------:|:---:|:------:|
|
||||||
|
| 1 | CE/ | Y5 | SS/ |
|
||||||
|
| 2 | SO | Y7 | MISO |
|
||||||
|
| 3 | SIO2 | n/c | |
|
||||||
|
| 4 | Vss | Gnd | Gnd |
|
||||||
|
| 5 | SI | Y8 | MOSI |
|
||||||
|
| 6 | SCLK | Y6 | Sck |
|
||||||
|
| 7 | SIO3 | n/c | |
|
||||||
|
| 8 | Vcc | 3V3 | 3V3 |
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
# 3. Files
|
||||||
|
|
||||||
|
1. `spiram.py` Device driver.
|
||||||
|
2. `bdevice.py` (In root directory) Base class for the device driver.
|
||||||
|
3. `spiram_test.py` Test programs for above. Assumes two 8MiB boards with CS
|
||||||
|
connected to pins Y4 and Y5 respectively. Adapt for other configurations.
|
||||||
|
4. `fs_test.py` A torture test for littlefs.
|
||||||
|
|
||||||
|
Installation: copy files 1 and 2 to the target filesystem. `spiram_test.py`
|
||||||
|
has a function `test()` which provides quick verification of hardware, but
|
||||||
|
`cspins` and `get_spiram` at the start of the file may need adaptation to your
|
||||||
|
hardware.
|
||||||
|
|
||||||
|
# 4. The device driver
|
||||||
|
|
||||||
|
The driver supports mounting the SPIRAM chips as a filesystem. After power up
|
||||||
|
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 spiram import SPIRAM
|
||||||
|
cspins = (Pin(Pin.board.Y5, Pin.OUT, value=1),)
|
||||||
|
ram = SPIRAM(SPI(2, baudrate=25_000_000), cspins)
|
||||||
|
# Format the filesystem
|
||||||
|
os.VfsLfs2.mkfs(ram) # Omit this to mount an existing filesystem
|
||||||
|
os.mount(ram,"/ram")
|
||||||
|
```
|
||||||
|
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. Typical use-cases involve temporary
|
||||||
|
files. 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. In the mode used
|
||||||
|
by the driver the chips are specified to a baudrate of 33MHz. I tested on a
|
||||||
|
Pyboard D, specifying 25MHz - this produced an actual baudrate of 18MHz.
|
||||||
|
|
||||||
|
## 4.1 The SPIRAM class
|
||||||
|
|
||||||
|
An `SPIRAM` instance represents a logical RAM: 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 SPIRAM 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=8192` Chip size in KiB.
|
||||||
|
4. `verbose=True` If `True`, the constructor issues information on the SPIRAM
|
||||||
|
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. Note
|
||||||
|
that, after power up, initial contents of RAM chips should be assumed to be
|
||||||
|
random.
|
||||||
|
|
||||||
|
#### 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 spiram import SPIRAM
|
||||||
|
cspins = (Pin(Pin.board.Y5, Pin.OUT, value=1),)
|
||||||
|
ram = SPIRAM(SPI(2), cspins)
|
||||||
|
ram[2000] = 42
|
||||||
|
print(ram[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 spiram import SPIRAM
|
||||||
|
cspins = (Pin(Pin.board.Y5, Pin.OUT, value=1),)
|
||||||
|
ram = SPIRAM(SPI(2), cspins)
|
||||||
|
ram[2000:2003] = "ABC"
|
||||||
|
print(ram[2000:2003]) # Returns a bytearray
|
||||||
|
```
|
||||||
|
Three argument slices are not supported: a third arg (other than 1) will cause
|
||||||
|
an exception. One argument slices (`ram[:5]` or `ram[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 RAM array in bytes may be retrieved by issuing `len(ram)`
|
||||||
|
where `ram` is the `SPIRAM` 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 spiram_test.py
|
||||||
|
|
||||||
|
This assumes a Pyboard 1.x or Pyboard D with SPIRAM(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 a 2048 byte block with
|
||||||
|
random data, reads it back, and checks the outcome before moving to the next
|
||||||
|
block. Existing data will be lost. This will detect serious hardware errors but
|
||||||
|
is not a comprehensive RAM chip test.
|
||||||
|
|
||||||
|
## 5.3 fstest()
|
||||||
|
|
||||||
|
Formats the RAM array as a littlefs filesystem and mounts the device on `/ram`.
|
||||||
|
Lists the contents (which will be empty) and prints the outcome of `os.statvfs`
|
||||||
|
on the array.
|
||||||
|
|
||||||
|
## 5.4 cptest()
|
||||||
|
|
||||||
|
Very simple filesystem test. If a filesystem is already mounted on `/ram`,
|
||||||
|
prints a message; otherwise formats the array with littlefs and mounts it.
|
||||||
|
Copies the source files to the filesystem, lists the contents of the mountpoint
|
||||||
|
and prints the outcome of `os.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 RAM becoming
|
||||||
|
full) it is up to the caller to handle it. For example (assuming the RAM is
|
||||||
|
mounted on /ram):
|
||||||
|
|
||||||
|
```python
|
||||||
|
cp('/flash/main.py','/ram/')
|
||||||
|
```
|
||||||
|
|
||||||
|
See the official `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. Test program fs_test.py
|
||||||
|
|
||||||
|
This is a torture test for littlefs. It creates many binary files of varying
|
||||||
|
length and verifies that they can be read back correctly. It rewrites files
|
||||||
|
with new lengths and checks that all files are OK. Run time is many minutes
|
||||||
|
depending on platform.
|
|
@ -0,0 +1,78 @@
|
||||||
|
# fs_test.py Extended filesystem test of SPIRAM 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 os
|
||||||
|
from machine import SPI, Pin
|
||||||
|
from spiram_test import get_spiram
|
||||||
|
|
||||||
|
directory = '/ram'
|
||||||
|
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(os.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:
|
||||||
|
os.remove(fname(n))
|
||||||
|
|
||||||
|
def main():
|
||||||
|
ram = get_spiram()
|
||||||
|
os.VfsLfs2.mkfs(ram) # Format littlefs
|
||||||
|
try:
|
||||||
|
os.mount(ram,'/ram')
|
||||||
|
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(os.urandom(1), 'little') & 0x7f
|
||||||
|
length = fcreate(n)
|
||||||
|
print('Rewrote', n, length)
|
||||||
|
check_all()
|
||||||
|
remove_all()
|
||||||
|
|
||||||
|
print('main() to run littlefs test. Erases any data on RAM.')
|
|
@ -0,0 +1,96 @@
|
||||||
|
# spiram.py Supports 8MiB SPI RAM
|
||||||
|
# Adafruit https://www.adafruit.com/product/4677
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# Command set
|
||||||
|
_WRITE = const(2)
|
||||||
|
_READ = const(3)
|
||||||
|
_RSTEN = const(0x66)
|
||||||
|
_RESET = const(0x99)
|
||||||
|
_RDID = const(0x9f)
|
||||||
|
|
||||||
|
|
||||||
|
class SPIRAM(BlockDevice):
|
||||||
|
def __init__(self, spi, cspins, size=8192, verbose=True, block_size=9):
|
||||||
|
if size != 8192:
|
||||||
|
print('SPIRAM size other than 8192KiB may not work.')
|
||||||
|
super().__init__(block_size, len(cspins), size * 1024)
|
||||||
|
self._spi = spi
|
||||||
|
self._cspins = cspins
|
||||||
|
self._ccs = None # Chip select Pin object for current chip
|
||||||
|
bufp = bytearray(6) # instruction + 3 byte address + 2 byte value
|
||||||
|
mvp = memoryview(bufp) # cost-free slicing
|
||||||
|
self._mvp = mvp
|
||||||
|
# Check hardware
|
||||||
|
for n, cs in enumerate(cspins):
|
||||||
|
mvp[:] = b'\0\0\0\0\0\0'
|
||||||
|
mvp[0] = _RDID
|
||||||
|
cs(0)
|
||||||
|
self._spi.write_readinto(mvp, mvp)
|
||||||
|
cs(1)
|
||||||
|
if mvp[4] != 0x0d or mvp[5] != 0x5d:
|
||||||
|
print("Warning: expected manufacturer ID not found.")
|
||||||
|
|
||||||
|
if verbose:
|
||||||
|
s = 'Total SPIRAM size {} KiB in {} devices.'
|
||||||
|
print(s.format(self._a_bytes//1024, n + 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("SPIRAM 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 & -self._c_bytes) + self._c_bytes # 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:
|
||||||
|
nchip = 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 + nchip])
|
||||||
|
cs(1)
|
||||||
|
else:
|
||||||
|
mvp[0] = _WRITE
|
||||||
|
cs(0)
|
||||||
|
self._spi.write(mvp[:4])
|
||||||
|
self._spi.write(mvb[start: start + nchip])
|
||||||
|
cs(1)
|
||||||
|
nbytes -= nchip
|
||||||
|
start += nchip
|
||||||
|
addr += nchip
|
||||||
|
return buf
|
||||||
|
|
||||||
|
# Reset is unnecessary because it restores the default power-up state.
|
||||||
|
#def _reset(self, cs, bufr = bytearray(1)):
|
||||||
|
#cs(0)
|
||||||
|
#bufr[0] = _RSTEN
|
||||||
|
#self._spi.write(bufr)
|
||||||
|
#cs(1)
|
||||||
|
#cs(0)
|
||||||
|
#bufr[0] = _RESET
|
||||||
|
#self._spi.write(bufr)
|
||||||
|
#cs(1)
|
|
@ -0,0 +1,128 @@
|
||||||
|
# spiram_ test.py MicroPython test program for Adafruit SPIRAM device
|
||||||
|
# Adafruit https://www.adafruit.com/product/4677
|
||||||
|
|
||||||
|
|
||||||
|
# Released under the MIT License (MIT). See LICENSE.
|
||||||
|
# Copyright (c) 2021 Peter Hinch
|
||||||
|
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
from machine import SPI, Pin
|
||||||
|
from spiram import SPIRAM
|
||||||
|
|
||||||
|
cspins = (Pin(Pin.board.Y5, Pin.OUT, value=1), Pin(Pin.board.Y4, Pin.OUT, value=1))
|
||||||
|
|
||||||
|
# Return an RAM array. Adapt for platforms other than Pyboard.
|
||||||
|
def get_spiram():
|
||||||
|
if os.uname().machine.split(' ')[0][:4] == 'PYBD':
|
||||||
|
Pin.board.EN_3V3.value(1)
|
||||||
|
time.sleep(0.1) # Allow decouplers to charge
|
||||||
|
ram = SPIRAM(SPI(2, baudrate=25_000_000), cspins)
|
||||||
|
print('Instantiated RAM')
|
||||||
|
return ram
|
||||||
|
|
||||||
|
# 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 /ram/
|
||||||
|
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():
|
||||||
|
ram = get_spiram()
|
||||||
|
sa = 1000
|
||||||
|
for v in range(256):
|
||||||
|
ram[sa + v] = v
|
||||||
|
for v in range(256):
|
||||||
|
if ram[sa + v] != v:
|
||||||
|
print('Fail at address {} data {} should be {}'.format(sa + v, ram[sa + v], v))
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
print('Test of byte addressing passed')
|
||||||
|
data = os.urandom(30)
|
||||||
|
sa = 2000
|
||||||
|
ram[sa:sa + 30] = data
|
||||||
|
if ram[sa:sa + 30] == data:
|
||||||
|
print('Test of slice readback passed')
|
||||||
|
# On SPIRAM the only meaningful block test is on a chip boundary.
|
||||||
|
block = ram._c_bytes
|
||||||
|
if ram._a_bytes > block:
|
||||||
|
res = _testblock(ram, 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():
|
||||||
|
ram = get_spiram()
|
||||||
|
os.VfsLfs2.mkfs(ram) # Format littlefs
|
||||||
|
try:
|
||||||
|
os.mount(ram,'/ram')
|
||||||
|
except OSError: # Already mounted
|
||||||
|
pass
|
||||||
|
print('Contents of "/": {}'.format(os.listdir('/')))
|
||||||
|
print('Contents of "/ram": {}'.format(os.listdir('/ram')))
|
||||||
|
print(os.statvfs('/ram'))
|
||||||
|
|
||||||
|
def cptest():
|
||||||
|
ram = get_spiram()
|
||||||
|
if 'ram' in os.listdir('/'):
|
||||||
|
print('Device already mounted.')
|
||||||
|
else:
|
||||||
|
os.VfsLfs2.mkfs(ram) # Format littlefs
|
||||||
|
os.mount(ram,'/ram')
|
||||||
|
print('Formatted and mounted device.')
|
||||||
|
cp('/sd/spiram_test.py', '/ram/')
|
||||||
|
cp('/sd/spiram.py', '/ram/')
|
||||||
|
print('Contents of "/ram": {}'.format(os.listdir('/ram')))
|
||||||
|
print(os.statvfs('/ram'))
|
||||||
|
|
||||||
|
# ***** TEST OF HARDWARE *****
|
||||||
|
def full_test():
|
||||||
|
bsize = 2048
|
||||||
|
ram = get_spiram()
|
||||||
|
page = 0
|
||||||
|
for sa in range(0, len(ram), bsize):
|
||||||
|
data = os.urandom(bsize)
|
||||||
|
ram[sa:sa + bsize] = data
|
||||||
|
if ram[sa:sa + bsize] == data:
|
||||||
|
print('Page {} passed'.format(page))
|
||||||
|
else:
|
||||||
|
print('Page {} readback failed.'.format(page))
|
||||||
|
page += 1
|
Ładowanie…
Reference in New Issue