From bc8b29ea8479ffc68faad747fe3cb1efe84b6133 Mon Sep 17 00:00:00 2001 From: Peter Hinch Date: Fri, 10 Jan 2020 18:46:57 +0000 Subject: [PATCH] Testing complete. --- README.md | 11 ++++------- bdevice.py | 36 +++++++++++------------------------- flash/FLASH.md | 35 +++++++++++------------------------ flash/flash_spi.py | 1 + flash/flash_test.py | 5 +---- 5 files changed, 28 insertions(+), 60 deletions(-) diff --git a/README.md b/README.md index f212bb4..f63f2b2 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ 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. +architecture; over-provisioning should enhance this. ## 1.3 Organisation of this repo @@ -69,9 +69,6 @@ In the table below the Interface column includes page size in bytes. | Microchip | 24xx64 | I2C 128 | 8KiB | EEPROM | [I2C.md](./eeprom/i2c/I2C.md) | | Adafruit | 1895 | I2C n/a | 32KiB | FRAM | [FRAM.md](./fram/FRAM.md) | -Note that the flash driver is **under development** and has a possible issue -discussed in [FLASH.md](./flash/FLASH.md). - ## 1.5 Performance FRAM is truly byte-addressable: its speed is limited only by the speed of the @@ -86,9 +83,9 @@ The drivers provide the benefit of page writing in a way which is transparent. If you write a block of data to an arbitrary address, page writes will be used to minimise total time. -In the case of flash memory page writing is mandatory: a sector is written by -first erasing it, a process which is slow. This physical limitation means that -the driver must buffer an entire 4096 byte sector. This contrasts with FRAM and +In the case of flash, page writing is mandatory: a sector is written by first +erasing it, a process which is slow. This physical limitation means that the +driver must buffer an entire 4096 byte sector. This contrasts with FRAM and EEPROM drivers where the buffering comprises a few bytes. # 2. Choice of interface diff --git a/bdevice.py b/bdevice.py index cbdf21e..5ea4bf0 100644 --- a/bdevice.py +++ b/bdevice.py @@ -1,20 +1,14 @@ # bdevice.py Hardware-agnostic base classes. # BlockDevice Base class for general block devices e.g. EEPROM, FRAM. # FlashDevice Base class for generic Flash memory (subclass of BlockDevice). +# Documentation in BASE_CLASSES.md # Released under the MIT License (MIT). See LICENSE. # Copyright (c) 2019 Peter Hinch -# BlockDevice: hardware-independent class implementing the uos.AbstractBlockDev -# protocol with extended interface. It supports littlefs. -# http://docs.micropython.org/en/latest/reference/filesystem.html#custom-block-devices - -# The subclass must implement .readwrite which can read or write arbitrary amounts -# of data to arbitrary addresses. IOW .readwrite handles physical block structure -# while ioctl supports a virtual block size. - from micropython import const + class BlockDevice: def __init__(self, nbits, nchips, chip_size): @@ -66,34 +60,26 @@ class BlockDevice: # IOCTL protocol. def sync(self): # Nothing to do for unbuffered devices. Subclass overrides. - return 0 + return def readblocks(self, blocknum, buf, offset=0): self.readwrite(offset + (blocknum << self._nbits), buf, True) - def writeblocks(self, blocknum, buf, offset=None): - offset = 0 if offset is None else offset + def writeblocks(self, blocknum, buf, offset=0): self.readwrite(offset + (blocknum << self._nbits), buf, False) def ioctl(self, op, arg): # ioctl calls: see extmod/vfs.h if op == 3: # SYNCHRONISE - return self.sync() + self.sync() + return if op == 4: # BP_IOCTL_SEC_COUNT return self._a_bytes >> self._nbits if op == 5: # BP_IOCTL_SEC_SIZE return self._block_size - if op == 6: # Erase + if op == 6: # ERASE return 0 -# Hardware agnostic base class for flash memory, where a single sector is cached. -# This minimises RAM usage. Under FAT wear is reduced if you cache at least two -# sectors. This driver is primarily intended for littlefs which has no such issue. -# Class assumes erased state is 0xff. - -# Subclass must provide these hardware-dependent methods: -# .rdchip(addr, mvb) Read from chip into memoryview: data guaranteed not to be cached. -# .flush(cache, addr) Erase physical sector and write out an entire cached sector. -# .readwrite As per base class. +# Hardware agnostic base class for flash memory. _RDBUFSIZE = const(32) # Size of read buffer for erasure test @@ -172,14 +158,14 @@ class FlashDevice(BlockDevice): def initialise(self): self._fill_cache(0) - # Return True if a sector is erased. Assumes erased == ff. - def is_empty(self, addr): + # Return True if a sector is erased. + def is_empty(self, addr, ev=0xff): mvb = self._mvbuf erased = True nbufs = self.sec_size // _RDBUFSIZE # Read buffers per sector for _ in range(nbufs): self.rdchip(addr, mvb) - if any(True for x in mvb if x != 0xff): + if any(True for x in mvb if x != ev): erased = False break addr += _RDBUFSIZE diff --git a/flash/FLASH.md b/flash/FLASH.md index a24f219..3f5cd3d 100644 --- a/flash/FLASH.md +++ b/flash/FLASH.md @@ -2,7 +2,8 @@ This driver supports the Cypress S25FL256L and S25FL128L chips, providing 64MiB and 32MiB respectively. These have 100K cycles of write endurance (compared to -10K for Pyboard Flash memory). +10K for Pyboard Flash memory). These were the largest capacity available with a +sector size small enough for microcontroller use. Multiple chips may be used to construct a single logical nonvolatile memory module. The driver allows the memory either to be mounted in the target @@ -22,23 +23,8 @@ an inevitable price for the large capacity of flash chips. FAT and littlefs filesystems are supported but the latter is preferred owing to its resilience and wear levelling characteristics. -Byte level access on such large devices probably has few use cases other than -for facilitating effective hardware tests and diagnostics. - -## 1.1 Test Status - -The driver has been tested with both chip types. Single chip setups work fine. -With pairs of devices I have seen sporadic single bit errors, especially at -high baudrates. I strongly suspect hardware issues with my breadboard lash-up -and am awaiting PCB's in manufacture. The reasons I suspect hardware are: - - 1. Errors are usually single bit: it's hard to see how the driver could do - this. - 2. Errors are not consistent. - 3. They are baudrate dependent. - 4. The breadboard is truly revoltming. - -However until I can test with a PCB the possibility of a bug remains. +Arguably byte level access on such large devices has few use cases other than +for facilitating effective hardware tests and for diagnostics. ##### [Main readme](../README.md) @@ -74,9 +60,10 @@ Other platforms may vary but the Cypress chips require a 3.3V supply. ## 2.1 SPI Bus The devices support baudrates up to 50MHz. In practice MicroPython targets do -not support such high rates. In testing I found it necessary to specify 5MHz -otherwise erratic results occurred. This was probably because of my breadboard -test setup as described above. SPI bus wiring should be short and direct. +not support such high rates. The test programs specify 20MHz, but in practice +the Pyboard D delivers 15MHz. Testing was done at this rate. In testing a +"lashup" breadboard was unsatisfactory: a problem entirely fixed with a PCB. +Bus lines should be short and direct. # 3. Files @@ -109,7 +96,7 @@ import os from machine import SPI, Pin from flash_spi import FLASH cspins = (Pin(Pin.board.Y5, Pin.OUT, value=1), Pin(Pin.board.Y4, Pin.OUT, value=1)) -flash = FLASH(SPI(2, baudrate=5_000_000), cspins) +flash = FLASH(SPI(2, baudrate=20_000_000), cspins) # Format the filesystem os.VfsLfs2.mkfs(flash) # Omit this to mount an existing filesystem os.mount(flash,'/fl_ext') @@ -172,7 +159,7 @@ of single byte access: from machine import SPI, Pin from flash_spi import FLASH cspins = (Pin(Pin.board.Y5, Pin.OUT, value=1), Pin(Pin.board.Y4, Pin.OUT, value=1)) -flash = FLASH(SPI(2, baudrate=5_000_000), cspins) +flash = FLASH(SPI(2, baudrate=20_000_000), cspins) flash[2000] = 42 print(flash[2000]) # Return an integer ``` @@ -182,7 +169,7 @@ writing, the size of the slice must match the length of the buffer: from machine import SPI, Pin from flash_spi import FLASH cspins = (Pin(Pin.board.Y5, Pin.OUT, value=1), Pin(Pin.board.Y4, Pin.OUT, value=1)) -flash = FLASH(SPI(2, baudrate=5_000_000), cspins) +flash = FLASH(SPI(2, baudrate=20_000_000), cspins) flash[2000:2002] = bytearray((42, 43)) print(flash[2000:2002]) # Returns a bytearray ``` diff --git a/flash/flash_spi.py b/flash/flash_spi.py index bf5c3c2..c8bd759 100644 --- a/flash/flash_spi.py +++ b/flash/flash_spi.py @@ -109,6 +109,7 @@ class FLASH(FlashDevice): self._spi.write(mvp[:5]) self._spi.readinto(mvb[start : start + npage]) cs(1) +# print('addr {} npage {} data {}'.format(addr, npage, mvb[start])) nbytes -= npage start += npage addr += npage diff --git a/flash/flash_test.py b/flash/flash_test.py index 589d0eb..fdd9731 100644 --- a/flash/flash_test.py +++ b/flash/flash_test.py @@ -16,7 +16,7 @@ def get_device(): Pin.board.EN_3V3.value(1) # Adjust to suit number of chips and their wiring. cspins = (Pin(Pin.board.Y5, Pin.OUT, value=1), Pin(Pin.board.Y4, Pin.OUT, value=1)) - flash = FLASH(SPI(2, baudrate=5_000_000), cspins, size=32768) + flash = FLASH(SPI(2, baudrate=20_000_000), cspins, size=32768) print('Instantiated Flash') return flash @@ -157,6 +157,3 @@ def full_test(count=10): if g != data[n]: print('{} {:2x} {:2x} {:2x}'.format(n, data[n], g, got1[n])) break - -# Fail seems to be on write: two readbacks are always identical. -# Error is on MSB of byte 0: write a 1 and get 0