Prepare for STM chip. Devolve further code to base class.

pull/1/head
Peter Hinch 2019-12-18 17:24:50 +00:00
rodzic ebea14bd6f
commit d792a2be7f
6 zmienionych plików z 142 dodań i 96 usunięć

Wyświetl plik

@ -29,7 +29,7 @@ The drivers have the following common features:
Currently supported technologies are EEPROM and FRAM (ferroelectric RAM). These
are nonvolatile random access storage devices with much higher endurance than
flash memory. Flash has a typical endurance of 10K writes per page. The figures
for EEPROM and FRAM are 1M and 10^12 writes respectively. In the case of the
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. If `littlefs` is used I would expect the endurance to be substantially
@ -37,25 +37,27 @@ better owing to its wear levelling architecture.
## 1.3 Supported chips
These currently include Microchip EEPROM chips and
These currently include Microchip and STM EEPROM chips and
[this Adafruit FRAM board](http://www.adafruit.com/product/1895). Note that the
largest EEPROM chip uses SPI: see [below](./README.md#2-choice-of-interface)
for a discussion of the merits and drawbacks of each interface.
Supported devices. Microchip manufacture each chip in different variants with
letters denoted by "xx" below. The variants cover parameters such as minimum
Vcc value and do not affect the API.
Vcc value and do not affect the API. There are two variants of the STM chip,
M95M02-DRMN6TP and M95M02-DWMN3TP/K. The latter has a wider temperature range.
In the table below the Interface column includes page size in bytes.
| Manufacturer | Part | Interface | Bytes | Technology | Docs |
|:------------:|:--------:|:---------:|:------:|:----------:|:-------------------------:|
| Microchip | 25xx1024 | SPI 256 | 128KiB | EEPROM | [SPI.md](./spi/SPI.md) |
| Microchip | 24xx512 | I2C 128 | 64KiB | EEPROM | [I2C.md](./i2c/I2C.md) |
| Microchip | 24xx256 | I2C 128 | 32KiB | EEPROM | [I2C.md](./i2c/I2C.md) |
| Microchip | 24xx128 | I2C 128 | 16KiB | EEPROM | [I2C.md](./i2c/I2C.md) |
| Microchip | 24xx64 | I2C 128 | 8KiB | EEPROM | [I2C.md](./i2c/I2C.md) |
| Adafruit | 1895 | I2C n/a | 32KiB | FRAM | [FRAM.md](./fram/FRAM.md) |
| Manufacturer | Part | Interface | Bytes | Technology | Docs |
|:------------:|:---------:|:---------:|:------:|:----------:|:-------------------------:|
| STM | M95M02-DR | SPI 256 | 256KiB | EEPROM | [SPI.md](./spi/SPI.md) |
| Microchip | 25xx1024 | SPI 256 | 128KiB | EEPROM | [SPI.md](./spi/SPI.md) |
| Microchip | 24xx512 | I2C 128 | 64KiB | EEPROM | [I2C.md](./i2c/I2C.md) |
| Microchip | 24xx256 | I2C 128 | 32KiB | EEPROM | [I2C.md](./i2c/I2C.md) |
| Microchip | 24xx128 | I2C 128 | 16KiB | EEPROM | [I2C.md](./i2c/I2C.md) |
| Microchip | 24xx64 | I2C 128 | 8KiB | EEPROM | [I2C.md](./i2c/I2C.md) |
| Adafruit | 1895 | I2C n/a | 32KiB | FRAM | [FRAM.md](./fram/FRAM.md) |
Documentation:
[SPI.md](./spi/SPI.md)
@ -92,7 +94,7 @@ electrical limits may also apply).
In the case of the Microchip devices supported, the SPI chip is larger at
128KiB compared to a maximum of 64KiB in the I2C range.
# 3. Design details
# 3. Design details and test results
The fact that the API enables accessing blocks of data at arbitrary addresses
implies that the handling of page addressing is done in the driver. This
@ -100,5 +102,10 @@ contrasts with drivers intended only for filesystem access. These devolve the
detail of page addressing to the filesystem by specifying the correct page size
in the ioctl and (if necessary) implementing a block erase method.
The nature of the drivers in this repo implies that the block address in the
ioctl is arbitrary.
The nature of the drivers in this repo implies that the page size in the ioctl
is arbitrary. Littlefs requires a minimum size of 128 bytes -
[theoretically 104](https://github.com/ARMmbed/littlefs/blob/master/DESIGN.md)
but the driver only allows powers of 2. Testing was done with 512 bytes.
Currently I have not had success with littlefs but it hasn't yet officially
been released. The test programs therefore use FAT.

Wyświetl plik

@ -18,10 +18,22 @@ class BlockDevice:
self._a_bytes = chip_size * nchips # Size of array
self._nbits = nbits # Block size in bits
self._block_size = 2**nbits
self._rwbuf = bytearray(1)
def __len__(self):
return self._a_bytes
def __setitem__(self, addr, value):
if isinstance(addr, slice):
return self._wslice(addr, value)
self._rwbuf[0] = value
self.readwrite(addr, self._rwbuf, False)
def __getitem__(self, addr):
if isinstance(addr, slice):
return self._rslice(addr)
return self.readwrite(addr, self._rwbuf, True)[0]
# Handle special cases of a slice. Always return a pair of positive indices.
def _do_slice(self, addr):
if not (addr.step is None or addr.step == 1):
@ -32,7 +44,7 @@ class BlockDevice:
stop = stop if stop >= 0 else self._a_bytes + stop
return start, stop
def wslice(self, addr, value):
def _wslice(self, addr, value):
start, stop = self._do_slice(addr)
try:
if len(value) == (stop - start):
@ -43,7 +55,7 @@ class BlockDevice:
raise RuntimeError('Can only assign bytes/bytearray to a slice')
return res
def rslice(self, addr):
def _rslice(self, addr):
start, stop = self._do_slice(addr)
buf = bytearray(stop - start)
return self.readwrite(start, buf, True)

Wyświetl plik

@ -53,22 +53,6 @@ class EEPROM(BlockDevice):
finally:
time.sleep_ms(1)
def __setitem__(self, addr, value):
if isinstance(addr, slice):
return self.wslice(addr, value)
self._buf1[0] = value
self._getaddr(addr, 1)
self._i2c.writevto(self._i2c_addr, (self._addrbuf, self._buf1))
self._wait_rdy() # Wait for write to complete
def __getitem__(self, addr):
if isinstance(addr, slice):
return self.rslice(addr)
self._getaddr(addr, 1)
self._i2c.writeto(self._i2c_addr, self._addrbuf)
self._i2c.readfrom_into(self._i2c_addr, self._buf1)
return self._buf1[0]
# Given an address, set ._i2c_addr and ._addrbuf and return the number of
# bytes that can be processed in the current page
def _getaddr(self, addr, nbytes): # Set up _addrbuf and _i2c_addr

Wyświetl plik

@ -1,6 +1,9 @@
# 1. A MicroPython SPI EEPROM driver
This driver supports the Microchip 25xx1024 series of 128KiB SPI EEPROMs.
This driver supports the Microchip 25xx1024 series of 128KiB SPI EEPROMs and
the STM M95M02-DR 256KiB device. These have 1M and 4M cycles of write endurance
respectively (compared to 10K for Pyboard Flash memory).
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
filesystem as a disk device or to be addressed as an array of bytes.
@ -13,13 +16,23 @@ The driver has the following attributes:
5. The SPI bus can be shared with other chips.
6. It supports filesystem mounting.
7. Alternatively it can support byte-level access using Python slice syntax.
8. RAM allocations are minimised.
8. RAM allocations are minimised. Buffer sizes are tiny.
## 1.1 This document
Code samples assume one or more Microchip devices. If using the STM chip the
SPI baudrate should be 5MHz and the chip size must be specified to the `EEPROM`
constructor, e.g.:
```python
eep = EEPROM(SPI(2, baudrate=5_000_000), cspins, 256)
```
# 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 EEPROM chip, connect to a Pyboard
as below. Pin numbers assume a PDIP package (8 pin plastic dual-in-line).
as below. Pin numbers assume a PDIP package (8 pin plastic dual-in-line) for
the Microchip device and 8 pin SOIC for the STM chip.
| EEPROM | Signal | PB | Signal |
|:-------:|:------:|:---:|:------:|
@ -34,8 +47,7 @@ as below. Pin numbers assume a PDIP package (8 pin plastic dual-in-line).
For multiple chips a separate CS pin must be assigned to each chip: each one
must be wired to a single chip's CS line. Multiple chips should have 3V3, Gnd,
SCL, MOSI and MISO lines wired in parallel. The SPI bus is fast: wiring should
be short and direct.
SCL, MOSI and MISO lines wired in parallel.
If you use a Pyboard D and power the EEPROMs from the 3V3 output you will need
to enable the voltage rail by issuing:
@ -44,6 +56,14 @@ machine.Pin.board.EN_3V3.value(1)
```
Other platforms may vary.
## 2.1 SPI Bus
The Microchip devices support baudrates up to 20MHz. The STM chip has a maximum
of 5MHz. Both support the default SPI mode: simply specify the baudrate to the
constructor.
The SPI bus is fast: wiring should be short and direct.
# 3. Files
1. `eeprom_spi.py` Device driver.
@ -56,7 +76,7 @@ Installation: copy files 1 and 2 (optionally 3) to the target filesystem.
The driver supports mounting the EEPROM chips as a filesystem. Initially the
device will be unformatted so it is necessary to issue code along these lines to
format the device. Code assumes two devices:
format the device. Code assumes two Microchip devices:
```python
import uos
@ -94,9 +114,10 @@ Arguments:
1. `spi` Mandatory. An initialised SPI bus 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. `verbose=True` If `True`, the constructor issues information on the EEPROM
3. `size=128` Chip size in KiB. Set to 256 for the STM chip.
4. `verbose=True` If `True`, the constructor issues information on the EEPROM
devices it has detected.
4. `block_size=9` The block size reported to the filesystem. The size in bytes
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.
SPI baudrate: The 25LC1024 supports baudrates of upto 20MHz. If this value is
@ -108,7 +129,8 @@ exceeding this figure.
It is possible to read and write individual bytes or arrays of arbitrary size.
Larger arrays are faster, especially when writing: the driver uses the chip's
hardware page access where possible. Writing a page (256 bytes) takes the same
time (~5ms) as writing a single byte.
time as writing a single byte. This is 6ms max on the Microchip and 10ms max on
the STM.
The examples below assume two devices, one with `CS` connected to Pyboard pin
Y4 and the other with `CS` connected to Y5.
@ -171,7 +193,7 @@ identify the chip.
#### erase
Erases the entire array.
Erases the entire array. Available only on the Microchip device.
### 4.1.4 Methods providing the block protocol
@ -216,28 +238,29 @@ possible to use JSON/pickle to store objects in a filesystem.
# 5. Test program eep_spi.py
This assumes a Pyboard 1.x or Pyboard D with EEPROM(s) wired as above. It
provides the following.
This assumes a Pyboard 1.x or Pyboard D with two EEPROMs wired to SPI(2) as
above with chip selects connected to pins `Y4` and `Y5`. It provides the
following. In all cases the stm arg should be `True` if using the STM chips.
## 5.1 test()
## 5.1 test(stm=False)
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()
## 5.2 full_test(stm=False)
This is a hardware test. Tests the entire array. Fills each 256 byte page with
random data, reads it back, and checks the outcome. Existing array data will be
lost.
## 5.3 fstest(format=False)
## 5.3 fstest(format=False, stm=False)
If `True` is passed, formats the EEPROM array as a FAT filesystem and mounts
the device on `/eeprom`. If no arg is passed it mounts the array and lists the
contents. It also prints the outcome of `uos.statvfs` on the array.
## 5.4 cptest()
## 5.4 cptest(stm=False)
Tests copying the source files to the filesystem. The test will fail if the
filesystem was not formatted. Lists the contents of the mountpoint and prints

Wyświetl plik

@ -10,10 +10,13 @@ from eeprom_spi import EEPROM
cspins = (Pin(Pin.board.Y5, Pin.OUT, value=1), Pin(Pin.board.Y4, Pin.OUT, value=1))
# Return an EEPROM array. Adapt for platforms other than Pyboard.
def get_eep():
def get_eep(stm):
if uos.uname().machine.split(' ')[0][:4] == 'PYBD':
Pin.board.EN_3V3.value(1)
eep = EEPROM(SPI(2, baudrate=20_000_000), cspins)
if stm:
eep = EEPROM(SPI(2, baudrate=5_000_000), cspins, 256)
else:
eep = EEPROM(SPI(2, baudrate=20_000_000), cspins)
print('Instantiated EEPROM')
return eep
@ -56,8 +59,8 @@ def _testblock(eep, bs):
if res != d2:
return 'Block test fail 3:' + res
def test():
eep = get_eep()
def test(stm=False):
eep = get_eep(stm)
sa = 1000
for v in range(256):
eep[sa + v] = v
@ -92,8 +95,8 @@ def test():
print('Test chip boundary skipped: only one chip!')
# ***** TEST OF FILESYSTEM MOUNT *****
def fstest(format=False):
eep = get_eep()
def fstest(format=False, stm=False):
eep = get_eep(stm)
if format:
uos.VfsFat.mkfs(eep)
vfs=uos.VfsFat(eep)
@ -105,8 +108,8 @@ def fstest(format=False):
print('Contents of "/eeprom": {}'.format(uos.listdir('/eeprom')))
print(uos.statvfs('/eeprom'))
def cptest():
eep = get_eep()
def cptest(stm=False):
eep = get_eep(stm)
if 'eeprom' in uos.listdir('/'):
print('Device already mounted.')
else:
@ -124,8 +127,8 @@ def cptest():
# ***** TEST OF HARDWARE *****
def full_test():
eep = get_eep()
def full_test(stm=False):
eep = get_eep(stm)
page = 0
for sa in range(0, len(eep), 256):
data = uos.urandom(256)

Wyświetl plik

@ -1,5 +1,6 @@
# eeprom_spi.py MicroPython driver for Microchip SPI EEPROM devices,
# currently only 25xx1024.
# eeprom_spi.py MicroPython driver for Microchip 128KiB SPI EEPROM device,
# also STM 256KiB chip
# TODO the latter not yet tested.
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2019 Peter Hinch
@ -8,24 +9,29 @@ import time
from micropython import const
from bdevice import BlockDevice
_SIZE = const(131072) # Chip size 128KiB
# Supported instruction set
_READ = const(3)
_WRITE = const(2)
_WREN = const(6) # Write enable
_RDSR = const(5) # Read status register
_RDID = const(0xab) # Read chip ID
_CE = const(0xc7) # Chip erase
_CE = const(0xc7) # Chip erase (Microchip only)
# Not implemented: Write disable and Write status register
# _WRDI = const(4)
# _WRSR = const(1)
#_RDID_STM = const(0x83) # STM only read ID page
#_WRID_STM = const(0x82)
#_STM_ID = const(0x30) # Arbitrary ID for STM chip
# Logical EEPROM device comprising one or more physical chips sharing an SPI bus.
class EEPROM(BlockDevice):
def __init__(self, spi, cspins, verbose=True, block_size=9):
def __init__(self, spi, cspins, size=128, verbose=True, block_size=9):
# args: virtual block size in bits, no. of chips, bytes in each chip
super().__init__(block_size, len(cspins), _SIZE)
if size not in (128, 256):
raise ValueError('Valid sizes are 128 or 256')
super().__init__(block_size, len(cspins), size * 1024)
self._stm = size == 256
self._spi = spi
self._cspins = cspins
self._ccs = None # Chip select Pin object for current chip
@ -33,22 +39,62 @@ class EEPROM(BlockDevice):
self._mvp = memoryview(self._bufp) # cost-free slicing
self.scan(verbose)
# Check for a valid hardware configuration
def scan(self, verbose):
# STM Datasheet too vague about the ID block. Do we need _WREN? Do we need to poll ready?
#def _stm_rdid(self):
#mvp = self._mvp
#mvp[:] = b'\0\0\0\0\0'
#mvp[0] = _RDID_STM
#cs(0)
#self._spi.write_readinto(mvp, mvp)
#cs(1)
#return mvp[4]
#def _stm_wrid(self):
#mvp = self._mvp
#mvp[:] = b'\0\0\0\0\0'
#mvp[0] = _WRID_STM
#mvp[5] = _STM_ID
#cs(0)
#self._spi.write(mvp)
#cs(1)
# Check for a valid hardware configuration: just see if we can write to offset 0
# Tested (on Microchip), but probably better to use ID block
def _stm_scan(self):
for n in range(len(self._cspins)):
ta = n * self._c_bytes
v = self[ta]
vx = v^0xff
self[ta] = vx
if self[ta] == vx: # Wrote OK, put back
self[ta] = v
else:
raise RuntimeError('EEPROM not found at cs[{}].'.format(n))
return n
# Scan for Microchip devices: read manf ID
def _mc_scan(self):
mvp = self._mvp
for n, cs in enumerate(self._cspins):
mvp[:] = b'\0\0\0\0\0'
mvp[0] = _RDID
cs(0)
self._spi.write_readinto(mvp[:5], mvp[:5])
self._spi.write_readinto(mvp, mvp)
cs(1)
if mvp[4] != 0x29:
raise RuntimeError('EEPROM not found at cs[{}].'.format(n))
return n
# Check for a valid hardware configuration
def scan(self, verbose):
n = self._stm_scan() if self._stm else self._mc_scan()
if verbose:
s = '{} chips detected. Total EEPROM size {}bytes.'
print(s.format(n + 1, self._a_bytes))
def erase(self):
if self._stm:
raise RuntimeError('Erase not available on STM chip')
mvp = self._mvp
for cs in self._cspins: # For each chip
mvp[0] = _WREN
@ -73,35 +119,6 @@ class EEPROM(BlockDevice):
break
time.sleep_ms(1)
def __setitem__(self, addr, value):
if isinstance(addr, slice):
return self.wslice(addr, value)
mvp = self._mvp
mvp[0] = _WREN
self._getaddr(addr, 1) # Sets mv[1:4], updates ._ccs
cs = self._ccs # Retrieve current cs pin
cs(0)
self._spi.write(mvp[:1])
cs(1)
mvp[0] = _WRITE
mvp[4] = value
cs(0)
self._spi.write(mvp[:5])
cs(1) # Trigger write
self._wait_rdy() # Wait for write to complete
def __getitem__(self, addr):
if isinstance(addr, slice):
return self.rslice(addr)
mvp = self._mvp
mvp[0] = _READ
self._getaddr(addr, 1)
cs = self._ccs
cs(0)
self._spi.write_readinto(mvp[:5], mvp[:5])
cs(1)
return mvp[4]
# Given an address, set current chip select and address buffer.
# Return the number of bytes that can be processed in the current page.
def _getaddr(self, addr, nbytes):