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.
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
Currently supported technologies are Flash, EEPROM and FRAM (ferroelectric
|
||||
RAM). The latter two are nonvolatile random access storage devices with much
|
||||
higher endurance than flash memory. Flash has a typical endurance of 10-100K
|
||||
writes per page. The figures for EEPROM and FRAM are 1-4M and 10^12 writes
|
||||
respectively. In the case of the FAT filing system 1M page writes probably
|
||||
corresponds to 1M filesystem writes because FAT repeatedly updates the
|
||||
allocation tables in the low numbered sectors. Under `littlefs` I would expect
|
||||
the endurance to be substantially better owing to its wear levelling
|
||||
architecture; over-provisioning should enhance this.
|
||||
Currently supported technologies are SPIRAM (PSRAM), Flash, EEPROM, and FRAM
|
||||
(ferroelectric RAM). The latter two are nonvolatile random access storage
|
||||
devices with much higher endurance than flash memory. Flash has a typical
|
||||
endurance of 10-100K writes per page. The figures for EEPROM and FRAM are 1-4M
|
||||
and 10^12 writes respectively. In the case of the FAT filing system 1M page
|
||||
writes probably corresponds to 1M filesystem writes because FAT repeatedly
|
||||
updates the allocation tables in the low numbered sectors. Under `littlefs` I
|
||||
would expect the endurance to be substantially better owing to its wear
|
||||
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
|
||||
|
||||
|
@ -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 | 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 | 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
|
||||
following have been tested to date:
|
||||
|
@ -102,8 +108,8 @@ This requires setting `cmd5=False`.
|
|||
|
||||
## 1.5 Performance
|
||||
|
||||
FRAM is truly byte-addressable: its speed is limited only by the speed of the
|
||||
I2C or SPI interface (SPI being much faster).
|
||||
FRAM and SPIRAM are truly byte-addressable: speed is limited only by the speed
|
||||
of the I2C or SPI interface (SPI being much faster).
|
||||
|
||||
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
|
||||
|
@ -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
|
||||
block device protocol as described
|
||||
[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
|
||||
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
|
||||
|
|
|
@ -1,22 +1,22 @@
|
|||
# 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.
|
||||
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 Pyboard filesystem as a disk device or to be addressed as an
|
||||
array of bytes.
|
||||
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.
|
||||
|
@ -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,
|
||||
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:
|
||||
```python
|
||||
machine.Pin.board.EN_3V3.value(1)
|
||||
|
@ -226,8 +226,8 @@ mounted on /fram):
|
|||
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.
|
||||
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
|
||||
|
||||
|
@ -238,8 +238,8 @@ 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)
|
||||
[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,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