diff --git a/README.md b/README.md index 59c2d2b..05118a9 100644 --- a/README.md +++ b/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 diff --git a/fram/FRAM_SPI.md b/fram/FRAM_SPI.md index b8e0edb..57d9f3b 100644 --- a/fram/FRAM_SPI.md +++ b/fram/FRAM_SPI.md @@ -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) diff --git a/spiram/SPIRAM.md b/spiram/SPIRAM.md new file mode 100644 index 0000000..9e298e8 --- /dev/null +++ b/spiram/SPIRAM.md @@ -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. diff --git a/spiram/fs_test.py b/spiram/fs_test.py new file mode 100644 index 0000000..3172c0c --- /dev/null +++ b/spiram/fs_test.py @@ -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.') diff --git a/spiram/spiram.py b/spiram/spiram.py new file mode 100644 index 0000000..76de4b4 --- /dev/null +++ b/spiram/spiram.py @@ -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) diff --git a/spiram/spiram_test.py b/spiram/spiram_test.py new file mode 100644 index 0000000..65a3f21 --- /dev/null +++ b/spiram/spiram_test.py @@ -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'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