Refactor: common base class in bdevice.py

pull/1/head
Peter Hinch 2019-12-14 12:20:49 +00:00
rodzic 992e35b994
commit 1c659ce333
5 zmienionych plików z 114 dodań i 109 usunięć

47
bdevice.py 100644
Wyświetl plik

@ -0,0 +1,47 @@
# bdevice.py Hardware-agnostic base class for block devices.
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2019 Peter Hinch
# Hardware-independent class implementing the uos.AbstractBlockDev protocol with
# simple and extended interface. It should therefore support 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.
class BlockDevice:
def __init__(self, nbits, nchips, chip_size):
self._c_bytes = chip_size # Size of chip in bytes
self._a_bytes = chip_size * nchips # Size of array
self._nbits = nbits # Block size in bits
self._block_size = 2**nbits
def __len__(self):
return self._a_bytes
# 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):
raise NotImplementedError('only slices with step=1 (aka None) are supported')
start = addr.start if addr.start is not None else 0
stop = addr.stop if addr.stop is not None else self._a_bytes
start = start if start >= 0 else self._a_bytes + start
stop = stop if stop >= 0 else self._a_bytes + stop
return start, stop
# IOCTL protocol.
def readblocks(self, blocknum, buf, offset=0):
return self.readwrite(offset + (blocknum << self._nbits), buf, True)
def writeblocks(self, blocknum, buf, offset=0):
self.readwrite(offset + (blocknum << self._nbits), buf, False)
def ioctl(self, op, arg):
#print("ioctl(%d, %r)" % (op, arg))
if op == 4: # BP_IOCTL_SEC_COUNT
return self._a_bytes >> self._nbits
if op == 5: # BP_IOCTL_SEC_SIZE
return self._block_size

Wyświetl plik

@ -69,7 +69,10 @@ Other platforms may vary.
# 3. Files # 3. Files
1. `eeprom_i2c.py` Device driver. 1. `eeprom_i2c.py` Device driver.
2. `eep_i2c.py` Test programs for above. 2. `bdevice.py` (In root directory) Base class for the device driver.
3. `eep_i2c.py` Test programs for above.
Installation: copy files 1 and 2 (optionally 3) to the target filesystem.
# 4. The device driver # 4. The device driver
@ -90,10 +93,10 @@ The above will reformat a drive with an existing filesystem: to mount an
existing filesystem simply omit the commented line. existing filesystem simply omit the commented line.
Note that, at the outset, you need to decide whether to use the array as a Note that, at the outset, you need to decide whether to use the array as a
mounted filesystem or as a byte array. As a filesystem the limited size is an mounted filesystem or as a byte array. The filesystem is relatively small but
issue, but a potential use case is for pickling Python objects for example to has high integrity owing to the hardware longevity. Typical use-cases involve
achieve persistence when issuing `pyb.standby()`; also for holding a small files which are frequently updated. These include files used for storing Python
frequently updated persistent btree database. objects serialised using Pickle/ujson or files holding a btree database.
The I2C bus must be instantiated using the `machine` module. The I2C bus must be instantiated using the `machine` module.
@ -126,7 +129,7 @@ of single byte access:
```python ```python
from machine import I2C from machine import I2C
from eeprom_i2c import EEPROM, T24C512 from eeprom_i2c import EEPROM, T24C512
eep = EEPROM(I2C(1), T24C512) eep = EEPROM(I2C(2), T24C512)
eep[2000] = 42 eep[2000] = 42
print(eep[2000]) # Return an integer print(eep[2000]) # Return an integer
``` ```
@ -135,12 +138,13 @@ writing, the size of the slice must match the length of the buffer:
```python ```python
from machine import I2C from machine import I2C
from eeprom_i2c import EEPROM, T24C512 from eeprom_i2c import EEPROM, T24C512
eep = EEPROM(I2C(1), T24C512) eep = EEPROM(I2C(2), T24C512)
eep[2000:2002] = bytearray((42, 43)) eep[2000:2002] = bytearray((42, 43))
print(eep[2000:2002]) # Returns a bytearray print(eep[2000:2002]) # Returns a bytearray
``` ```
Three argument slices are not supported: any third arg will be ignored. One Three argument slices are not supported: a third arg (other than 1) will cause
argument slices (`eep[:5]` or `eep[13100:]`) and negative args are supported. an exception. One argument slices (`eep[:5]` or `eep[13100:]`) and negative
args are supported.
#### 4.1.2.2 readwrite #### 4.1.2.2 readwrite
@ -153,23 +157,14 @@ advantage of using a pre-allocated buffer. Arguments:
determines the quantity of data read or written. A `RuntimeError` will be 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. thrown if the read or write extends beyond the end of the physical space.
### 4.1.3 Methods providing the block protocol ### 4.1.3 Other methods
For the protocol definition see #### The len() operator
[the pyb documentation](http://docs.micropython.org/en/latest/library/uos.html#uos.AbstractBlockDev)
`readblocks()`
`writeblocks()`
`ioctl()`
### 4.1.4 Other methods
#### 4.1.4.1 The len() operator
The size of the EEPROM array in bytes may be retrieved by issuing `len(eep)` The size of the EEPROM array in bytes may be retrieved by issuing `len(eep)`
where `eep` is the `EEPROM` instance. where `eep` is the `EEPROM` instance.
#### 4.1.4.2 scan #### scan
Scans the I2C bus and returns the number of EEPROM devices detected. Scans the I2C bus and returns the number of EEPROM devices detected.
@ -177,6 +172,16 @@ Other than for debugging there is no need to call `scan()`: the constructor
will throw a `RuntimeError` if it fails to communicate with and correctly will throw a `RuntimeError` if it fails to communicate with and correctly
identify the chip. identify the chip.
### 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 eep_i2c.py # 5. Test program eep_i2c.py
This assumes a Pyboard 1.x or Pyboard D with EEPROM(s) wired as above. It This assumes a Pyboard 1.x or Pyboard D with EEPROM(s) wired as above. It

Wyświetl plik

@ -5,6 +5,7 @@
import time import time
from micropython import const from micropython import const
from bdevice import BlockDevice
ADDR = const(0x50) # Base address of chip ADDR = const(0x50) # Base address of chip
@ -15,27 +16,18 @@ T24C64 = const(8192) # 8KiB 64Kbits
# Logical EEPROM device consists of 1-8 physical chips. Chips must all be the # Logical EEPROM device consists of 1-8 physical chips. Chips must all be the
# same size, and must have contiguous addresses starting from 0x50. # same size, and must have contiguous addresses starting from 0x50.
class EEPROM(): class EEPROM(BlockDevice):
def __init__(self, i2c, chip_size=T24C512, verbose=True): def __init__(self, i2c, chip_size=T24C512, verbose=True):
self._i2c = i2c self._i2c = i2c
if chip_size not in (T24C64, T24C128, T24C256, T24C512): if chip_size not in (T24C64, T24C128, T24C256, T24C512):
raise RuntimeError('Invalid chip size', chip_size) raise RuntimeError('Invalid chip size', chip_size)
nchips = self.scan(verbose, chip_size) # No. of EEPROM chips nchips = self.scan(verbose, chip_size) # No. of EEPROM chips
self._c_bytes = chip_size # Size of chip in bytes super().__init__(9, nchips, chip_size)
self._a_bytes = chip_size * nchips # Size of array
self._i2c_addr = 0 # I2C address of current chip self._i2c_addr = 0 # I2C address of current chip
self._buf1 = bytearray(1) self._buf1 = bytearray(1)
self._addrbuf = bytearray(2) # Memory offset into current chip self._addrbuf = bytearray(2) # Memory offset into current chip
# Handle special cases of a slice. Always return a pair of positive indices.
def do_slice(self, addr):
start = addr.start if addr.start is not None else 0
stop = addr.stop if addr.stop is not None else self._a_bytes
start = start if start >= 0 else self._a_bytes + start
stop = stop if stop >= 0 else self._a_bytes + stop
return start, stop
# Check for a valid hardware configuration # Check for a valid hardware configuration
def scan(self, verbose, chip_size): def scan(self, verbose, chip_size):
devices = self._i2c.scan() # All devices on I2C bus devices = self._i2c.scan() # All devices on I2C bus
@ -50,9 +42,6 @@ class EEPROM():
print(s.format(nchips, chip_size * nchips)) print(s.format(nchips, chip_size * nchips))
return nchips return nchips
def __len__(self):
return self._a_bytes
def _wait_rdy(self): # After a write, wait for device to become ready def _wait_rdy(self): # After a write, wait for device to become ready
self._buf1[0] = 0 self._buf1[0] = 0
while True: while True:
@ -119,17 +108,3 @@ class EEPROM():
start += npage start += npage
addr += npage addr += npage
return buf return buf
# IOCTL protocol. Emulate block size of 512 bytes for now.
def readblocks(self, blocknum, buf):
return self.readwrite(blocknum << 9, buf, True)
def writeblocks(self, blocknum, buf):
self.readwrite(blocknum << 9, buf, False)
def ioctl(self, op, arg):
#print("ioctl(%d, %r)" % (op, arg))
if op == 4: # BP_IOCTL_SEC_COUNT
return self._a_bytes >> 9
if op == 5: # BP_IOCTL_SEC_SIZE
return 512

Wyświetl plik

@ -46,7 +46,10 @@ Other platforms may vary.
# 3. Files # 3. Files
1. `eeprom_spi.py` Device driver. 1. `eeprom_spi.py` Device driver.
2. `eep_spi.py` Test programs for above. 2. `bdevice.py` (In root directory) Base class for the device driver.
3. `eep_spi.py` Test programs for above.
Installation: copy files 1 and 2 (optionally 3) to the target filesystem.
# 4. The device driver # 4. The device driver
@ -68,10 +71,10 @@ The above will reformat a drive with an existing filesystem: to mount an
existing filesystem simply omit the commented line. existing filesystem simply omit the commented line.
Note that, at the outset, you need to decide whether to use the array as a Note that, at the outset, you need to decide whether to use the array as a
mounted filesystem or as a byte array. As a filesystem the limited size is an mounted filesystem or as a byte array. The filesystem is relatively small but
issue, but a potential use case is for pickling Python objects for example to has high integrity owing to the hardware longevity. Typical use-cases involve
achieve persistence when issuing `pyb.standby()`; also for holding a small files which are frequently updated. These include files used for storing Python
frequently updated persistent btree database. objects serialised using Pickle/ujson or files holding a btree database.
The SPI bus must be instantiated using the `machine` module. The SPI bus must be instantiated using the `machine` module.
@ -125,8 +128,9 @@ eep = EEPROM(SPI(2, baudrate=20_000_000), cspins)
eep[2000:2002] = bytearray((42, 43)) eep[2000:2002] = bytearray((42, 43))
print(eep[2000:2002]) # Returns a bytearray print(eep[2000:2002]) # Returns a bytearray
``` ```
Three argument slices are not supported: any third arg will be ignored. One Three argument slices are not supported: a third arg (other than 1) will cause
argument slices (`eep[:5]` or `eep[13100:]`) and negative args are supported. an exception. One argument slices (`eep[:5]` or `eep[13100:]`) and negative
args are supported.
#### 4.1.2.2 readwrite #### 4.1.2.2 readwrite
@ -139,23 +143,14 @@ advantage of using a pre-allocated buffer. Arguments:
determines the quantity of data read or written. A `RuntimeError` will be 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. thrown if the read or write extends beyond the end of the physical space.
### 4.1.3 Methods providing the block protocol ### 4.1.3 Other methods
For the protocol definition see #### The len() operator
[the pyb documentation](http://docs.micropython.org/en/latest/library/uos.html#uos.AbstractBlockDev)
`readblocks()`
`writeblocks()`
`ioctl()`
### 4.1.4 Other methods
#### 4.1.4.1 The len() operator
The size of the EEPROM array in bytes may be retrieved by issuing `len(eep)` The size of the EEPROM array in bytes may be retrieved by issuing `len(eep)`
where `eep` is the `EEPROM` instance. where `eep` is the `EEPROM` instance.
#### 4.1.4.2 scan #### scan
Activate each chip select in turn checking for a valid device and returns the Activate each chip select in turn checking for a valid device and returns the
number of EEPROM devices detected. A `RuntimeError` will be raised if any CS number of EEPROM devices detected. A `RuntimeError` will be raised if any CS
@ -165,10 +160,20 @@ Other than for debugging there is no need to call `scan()`: the constructor
will throw a `RuntimeError` if it fails to communicate with and correctly will throw a `RuntimeError` if it fails to communicate with and correctly
identify the chip. identify the chip.
#### 4.1.4.3 erase #### erase
Erases the entire array. Erases the entire array.
### 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 eep_spi.py # 5. Test program eep_spi.py
This assumes a Pyboard 1.x or Pyboard D with EEPROM(s) wired as above. It This assumes a Pyboard 1.x or Pyboard D with EEPROM(s) wired as above. It

Wyświetl plik

@ -6,42 +6,33 @@
import time import time
from micropython import const from micropython import const
from bdevice import BlockDevice
_SIZE = const(131072) # Chip size 128KiB _SIZE = const(131072) # Chip size 128KiB
# Supported instruction set # Supported instruction set
_READ = const(3) _READ = const(3)
_WRITE = const(2) _WRITE = const(2)
_WREN = const(6) _WREN = const(6) # Write enable
_WRDI = const(4) _RDSR = const(5) # Read status register
_RDSR = const(5) _RDID = const(0xab) # Read chip ID
_WRSR = const(1) _CE = const(0xc7) # Chip erase
_RDID = const(0xab) # Not implemented: Write disable and Write status register
_CE = const(0xc7) # _WRDI = const(4)
# _WRSR = const(1)
# Logical EEPROM device consisting of an arbitrary number of physical chips # Logical EEPROM device comprising one or more physical chips sharing an SPI bus.
# sharing an SPI bus. class EEPROM(BlockDevice):
class EEPROM():
def __init__(self, spi, cspins, verbose=True): def __init__(self, spi, cspins, verbose=True):
# args: virtual block size in bits, no. of chips, bytes in each chip
super().__init__(9, len(cspins), _SIZE)
self._spi = spi self._spi = spi
self._cspins = cspins self._cspins = cspins
nchips = len(cspins) # No. of EEPROM chips
# size as a bound variable for future bigger chips
self._c_bytes = _SIZE # Size of chip in bytes
self._a_bytes = _SIZE * nchips # Size of array
self._ccs = None # Chip select Pin object for current chip self._ccs = None # Chip select Pin object for current chip
self._bufp = bytearray(5) # instruction + 3 byte address + 1 byte value self._bufp = bytearray(5) # instruction + 3 byte address + 1 byte value
self._mvp = memoryview(self._bufp) # cost-free slicing self._mvp = memoryview(self._bufp) # cost-free slicing
self.scan(verbose) self.scan(verbose)
# Handle special cases of a slice. Always return a pair of positive indices.
def do_slice(self, addr):
start = addr.start if addr.start is not None else 0
stop = addr.stop if addr.stop is not None else self._a_bytes
start = start if start >= 0 else self._a_bytes + start
stop = stop if stop >= 0 else self._a_bytes + stop
return start, stop
# Check for a valid hardware configuration # Check for a valid hardware configuration
def scan(self, verbose): def scan(self, verbose):
mvp = self._mvp mvp = self._mvp
@ -70,9 +61,6 @@ class EEPROM():
cs(1) cs(1)
self._wait_rdy() # Wait for erase to complete self._wait_rdy() # Wait for erase to complete
def __len__(self):
return self._a_bytes
def _wait_rdy(self): # After a write, wait for device to become ready def _wait_rdy(self): # After a write, wait for device to become ready
mvp = self._mvp mvp = self._mvp
cs = self._ccs # Chip is already current cs = self._ccs # Chip is already current
@ -81,8 +69,7 @@ class EEPROM():
cs(0) cs(0)
self._spi.write_readinto(mvp[:2], mvp[:2]) self._spi.write_readinto(mvp[:2], mvp[:2])
cs(1) cs(1)
assert not mvp[1] & 0xC # BP0, BP1 assumed 0 if not mvp[1]: # We never set BP0 or BP1 so ready state is 0.
if not (mvp[1] & 1):
break break
time.sleep_ms(1) time.sleep_ms(1)
@ -169,17 +156,3 @@ class EEPROM():
start += npage start += npage
addr += npage addr += npage
return buf return buf
# IOCTL protocol. Emulate block size of 512 bytes.
def readblocks(self, blocknum, buf):
return self.readwrite(blocknum << 9, buf, True)
def writeblocks(self, blocknum, buf):
self.readwrite(blocknum << 9, buf, False)
def ioctl(self, op, arg):
#print("ioctl(%d, %r)" % (op, arg))
if op == 4: # BP_IOCTL_SEC_COUNT
return self._a_bytes >> 9
if op == 5: # BP_IOCTL_SEC_SIZE
return 512