kopia lustrzana https://github.com/peterhinch/micropython_eeprom
Add FRAM. Various minor changes.
rodzic
6a86e2aadd
commit
985c09ae4f
93
README.md
93
README.md
|
@ -1,32 +1,76 @@
|
||||||
# MicroPython EEPROM drivers
|
# 1. MicroPython drivers for nonvolatile memory
|
||||||
|
|
||||||
EEPROM is a form of nonvolatile random access storage.
|
These drivers support nonvolatile memory chips.
|
||||||
|
|
||||||
These drivers enable MicroPython to access Microchip EEPROM devices. There are
|
Currently supported devices use technologies having superior performance
|
||||||
two variants, one for chips based on the I2C interface and a second for a 1MBit
|
compared to flash. Resultant storage has much higher write endurance. In some
|
||||||
SPI chip.
|
cases read and write access times may be shorter. EEPROM and FRAM chips have
|
||||||
|
much lower standby current than SD cards, benefiting micropower applications.
|
||||||
|
|
||||||
Unlike flash memory, EEPROMs may be written on a byte addressable basis. Their
|
The drivers present a common API having the features listed below.
|
||||||
endurance is specified as a million writes compared to the 10K typical of most
|
|
||||||
flash memory. In applications such as data logging the latter can be exceeded
|
## 1.1 Features common to all drivers
|
||||||
relatively rapidly. For extreme endurance ferroelectric RAM has almost infinite
|
|
||||||
endurance but at higher cost per byte. See [this driver](https://github.com/peterhinch/micropython-fram).
|
The drivers have the following common features:
|
||||||
|
1. Support for single or multiple chips on the same bus. Multiple chips are
|
||||||
|
automatically configured as a single array.
|
||||||
|
2. This can be accessed as an array of bytes, using Python slice syntax or via
|
||||||
|
a `readwrite` method.
|
||||||
|
3. Alternatively the array can be formatted and mounted as a filesystem using
|
||||||
|
methods in the `uos` module. Any filesystem supported by the MicroPython build
|
||||||
|
may be employed.
|
||||||
|
4. Drivers are portable: buses and pins should be instantiated using the
|
||||||
|
`machine` module.
|
||||||
|
5. Buses may be shared with other hardware. This assumes that the application
|
||||||
|
pays due accord to differing electrical constraints such as baudrate.
|
||||||
|
|
||||||
|
## 1.2 Technologies
|
||||||
|
|
||||||
|
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
|
||||||
|
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
|
||||||
|
better owing to its wear levelling architecture.
|
||||||
|
|
||||||
|
## 1.3 Supported chips
|
||||||
|
|
||||||
|
These currently include Microchip 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.
|
||||||
|
|
||||||
|
In the table below the Interface column includes page size in bytes.
|
||||||
|
| Manufacurer | 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) |
|
||||||
|
|
||||||
|
## 1.4 Performance
|
||||||
|
|
||||||
|
FRAM is truly byte-addressable: its speed is limited only by the speed of the
|
||||||
|
I2C interface.
|
||||||
|
|
||||||
Reading from EEPROM chips is fast. Writing is slower, typically around 5ms.
|
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
|
However where multiple bytes are written, that 5ms applies to a page of data so
|
||||||
the mean time per byte is quicker by a factor of the page size (128 or 256
|
the mean time per byte is quicker by a factor of the page size (128 or 256
|
||||||
bytes depending on the device).
|
bytes depending on the device).
|
||||||
|
|
||||||
The drivers support creating multi-chip arrays. In the case of I2C chips, up to
|
The drivers provide the benefit of page writing in a way which is transparent.
|
||||||
eight devices may share the bus. In the case of SPI expansion has no absolute
|
If you write a block of data to an arbitrary address, page writes will be used
|
||||||
limit as each chip has its own chip select line.
|
to minimise total time.
|
||||||
|
|
||||||
Devices or arrays of devices may be mounted as a filesystem or may be treated
|
# 2. Choice of interface
|
||||||
as an array of bytes.
|
|
||||||
|
|
||||||
For I2C devices see [I2C.md](./i2c/I2C.md). For SPI see [SPI.md](./spi/SPI.md).
|
|
||||||
|
|
||||||
# Choice of interface
|
|
||||||
|
|
||||||
The principal merit of I2C is to minimise pin count. It uses two pins
|
The principal merit of I2C is to minimise pin count. It uses two pins
|
||||||
regardless of the number of chips connected. It requires pullup resistors on
|
regardless of the number of chips connected. It requires pullup resistors on
|
||||||
|
@ -41,3 +85,14 @@ electrical limits may also apply).
|
||||||
|
|
||||||
In the case of the Microchip devices supported, the SPI chip is larger at
|
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.
|
128KiB compared to a maximum of 64KiB in the I2C range.
|
||||||
|
|
||||||
|
# 3. Design details
|
||||||
|
|
||||||
|
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
|
||||||
|
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.
|
||||||
|
|
18
bdevice.py
18
bdevice.py
|
@ -23,7 +23,7 @@ class BlockDevice:
|
||||||
return self._a_bytes
|
return self._a_bytes
|
||||||
|
|
||||||
# Handle special cases of a slice. Always return a pair of positive indices.
|
# Handle special cases of a slice. Always return a pair of positive indices.
|
||||||
def do_slice(self, addr):
|
def _do_slice(self, addr):
|
||||||
if not (addr.step is None or addr.step == 1):
|
if not (addr.step is None or addr.step == 1):
|
||||||
raise NotImplementedError('only slices with step=1 (aka None) are supported')
|
raise NotImplementedError('only slices with step=1 (aka None) are supported')
|
||||||
start = addr.start if addr.start is not None else 0
|
start = addr.start if addr.start is not None else 0
|
||||||
|
@ -32,6 +32,22 @@ class BlockDevice:
|
||||||
stop = stop if stop >= 0 else self._a_bytes + stop
|
stop = stop if stop >= 0 else self._a_bytes + stop
|
||||||
return start, stop
|
return start, stop
|
||||||
|
|
||||||
|
def wslice(self, addr, value):
|
||||||
|
start, stop = self._do_slice(addr)
|
||||||
|
try:
|
||||||
|
if len(value) == (stop - start):
|
||||||
|
res = self.readwrite(start, value, False)
|
||||||
|
else:
|
||||||
|
raise RuntimeError('Slice must have same length as data')
|
||||||
|
except TypeError:
|
||||||
|
raise RuntimeError('Can only assign bytes/bytearray to a slice')
|
||||||
|
return res
|
||||||
|
|
||||||
|
def rslice(self, addr):
|
||||||
|
start, stop = self._do_slice(addr)
|
||||||
|
buf = bytearray(stop - start)
|
||||||
|
return self.readwrite(start, buf, True)
|
||||||
|
|
||||||
# IOCTL protocol.
|
# IOCTL protocol.
|
||||||
def readblocks(self, blocknum, buf, offset=0):
|
def readblocks(self, blocknum, buf, offset=0):
|
||||||
return self.readwrite(offset + (blocknum << self._nbits), buf, True)
|
return self.readwrite(offset + (blocknum << self._nbits), buf, True)
|
||||||
|
|
|
@ -0,0 +1,246 @@
|
||||||
|
# 1. A MicroPython FRAM driver
|
||||||
|
|
||||||
|
A driver to enable the Pyboard to access the Ferroelectric RAM (FRAM) board from
|
||||||
|
[Adafruit](http://www.adafruit.com/product/1895). 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**12 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. The downside is limited capacity. Compared to a Micro SD card fitted
|
||||||
|
to the Pyboard it offers lower power consumption and longer endurance.
|
||||||
|
|
||||||
|
From one to eight boards may be used to construct a nonvolatile memory module
|
||||||
|
with size ranging from 32KiB to 256KiB. 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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## 1.1 Changes compared to the old FRAM driver
|
||||||
|
|
||||||
|
API now matches other devices with support for slice syntax. Reduced RAM
|
||||||
|
allocation by virtue of `memorview` instances and pre-allocated buffers. Now
|
||||||
|
supports littlefs or FAT filesystems.
|
||||||
|
|
||||||
|
# 2. Connections
|
||||||
|
|
||||||
|
To wire up a single FRAM module, connect to the Pyboard as below (nc indicates
|
||||||
|
no connection).
|
||||||
|
|
||||||
|
| FRAM | L | R |
|
||||||
|
|:-------:|:---:|:---:|
|
||||||
|
| Vcc | 3V3 | 3V3 |
|
||||||
|
| Gnd | GND | GND |
|
||||||
|
| WP | nc | nc |
|
||||||
|
| SCL | X9 | Y9 |
|
||||||
|
| SDA | X10 | Y10 |
|
||||||
|
| A2 | nc | nc |
|
||||||
|
| A1 | nc | nc |
|
||||||
|
| A0 | nc | nc |
|
||||||
|
|
||||||
|
For multiple modules the address lines A0, A1 and A2 of each module need to be
|
||||||
|
wired to 3V3 in such a way as to give each device a unique address. These must
|
||||||
|
start at zero and be contiguous. Pins are internally pulled down, pins marked
|
||||||
|
`nc` may be left unconnected or linked to Gnd.
|
||||||
|
| Chip no. | A2 | A1 | A0 |
|
||||||
|
|:--------:|:---:|:---:|:---:|
|
||||||
|
| 0 | nc | nc | nc |
|
||||||
|
| 1 | nc | nc | 3V3 |
|
||||||
|
| 2 | nc | 3V3 | nc |
|
||||||
|
| 3 | nc | 3V3 | 3V3 |
|
||||||
|
| 4 | 3V3 | nc | nc |
|
||||||
|
| 5 | 3V3 | nc | 3V3 |
|
||||||
|
| 6 | 3V3 | 3V3 | nc |
|
||||||
|
| 7 | 3V3 | 3V3 | Gnd |
|
||||||
|
|
||||||
|
Multiple modules should have 3V3, Gnd, SCL and SDA lines wired in parallel.
|
||||||
|
|
||||||
|
The I2C interface requires pullups: these are provided on the Adafruit board.
|
||||||
|
|
||||||
|
If you use a Pyboard D and power the FRAMs from the 3V3 output you will need
|
||||||
|
to enable the voltage rail by issuing:
|
||||||
|
```python
|
||||||
|
machine.Pin.board.EN_3V3.value(1)
|
||||||
|
```
|
||||||
|
Other platforms may vary.
|
||||||
|
|
||||||
|
# 3. Files
|
||||||
|
|
||||||
|
1. `fram_i2c.py` Device driver.
|
||||||
|
2. `bdevice.py` (In root directory) Base class for the device driver.
|
||||||
|
3. `fram_test.py` Test programs for above.
|
||||||
|
|
||||||
|
Installation: copy files 1 and 2 (optionally 3) to the target filesystem.
|
||||||
|
|
||||||
|
# 4. The device driver
|
||||||
|
|
||||||
|
The driver supports mounting the FRAM 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 one or more devices:
|
||||||
|
|
||||||
|
```python
|
||||||
|
import uos
|
||||||
|
from machine import I2C
|
||||||
|
from fram_i2c import FRAM
|
||||||
|
fram = FRAM(I2C(2))
|
||||||
|
uos.VfsFat.mkfs(fram) # Omit this to mount an existing filesystem
|
||||||
|
vfs = uos.VfsFat(fram)
|
||||||
|
uos.mount(vfs,'/fram')
|
||||||
|
```
|
||||||
|
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. The filesystem is relatively small but
|
||||||
|
has high integrity owing to the hardware longevity. Typical use-cases involve
|
||||||
|
files which are frequently updated. These include files used for storing Python
|
||||||
|
objects serialised using pickle/ujson or files holding a btree database.
|
||||||
|
|
||||||
|
The I2C bus must be instantiated using the `machine` module.
|
||||||
|
|
||||||
|
## 4.1 The FRAM class
|
||||||
|
|
||||||
|
An `FRAM` instance represents a logical FRAM: this may consist of multiple
|
||||||
|
physical devices on a common I2C bus.
|
||||||
|
|
||||||
|
### 4.1.1 Constructor
|
||||||
|
|
||||||
|
This scans the I2C bus and checks if one or more correctly addressed chips are
|
||||||
|
detected. Each chip is checked for correct ID data. A `RuntimeError` will occur
|
||||||
|
in case of error, e.g. bad ID, no device detected or device address lines not
|
||||||
|
wired as described in [Connections](./README.md#2-connections). If all is OK an
|
||||||
|
FRAM instance is created.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
1. `i2c` Mandatory. An initialised master mode I2C bus created by `machine`.
|
||||||
|
2. `verbose=True` If `True`, the constructor issues information on the FRAM
|
||||||
|
devices it has detected.
|
||||||
|
3. `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.
|
||||||
|
|
||||||
|
#### 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 I2C
|
||||||
|
from fram_i2c import FRAM
|
||||||
|
fram = FRAM(I2C(2))
|
||||||
|
fram[2000] = 42
|
||||||
|
print(fram[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 I2C
|
||||||
|
from fram_i2c import FRAM
|
||||||
|
fram = FRAM(I2C(2))
|
||||||
|
fram[2000:2002] = bytearray((42, 43))
|
||||||
|
print(fram[2000:2002]) # Returns a bytearray
|
||||||
|
```
|
||||||
|
Three argument slices are not supported: a third arg (other than 1) will cause
|
||||||
|
an exception. One argument slices (`fram[:5]` or `fram[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 FRAM array in bytes may be retrieved by issuing `len(fram)`
|
||||||
|
where `fram` is the `FRAM` instance.
|
||||||
|
|
||||||
|
#### scan
|
||||||
|
|
||||||
|
Scans the I2C bus and returns the number of FRAM devices detected.
|
||||||
|
|
||||||
|
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
|
||||||
|
identify the chip(s).
|
||||||
|
|
||||||
|
### 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 fram_test.py
|
||||||
|
|
||||||
|
This assumes a Pyboard 1.x or Pyboard D with FRAM(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 each 128 byte page with
|
||||||
|
random data, reads it back, and checks the outcome. Existing array data will be
|
||||||
|
lost.
|
||||||
|
|
||||||
|
## 5.3 fstest(format=False)
|
||||||
|
|
||||||
|
If `True` is passed, formats the FRAM array as a FAT filesystem and mounts
|
||||||
|
the device on `/fram`. 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()
|
||||||
|
|
||||||
|
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
|
||||||
|
the outcome of `uos.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 FRAM becoming
|
||||||
|
full) it is up to the caller to handle it. For example (assuming the FRAM is
|
||||||
|
mounted on /fram):
|
||||||
|
|
||||||
|
```python
|
||||||
|
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.
|
||||||
|
|
||||||
|
# 6. ESP8266
|
||||||
|
|
||||||
|
Currently the ESP8266 does not support concurrent mounting of multiple
|
||||||
|
filesystems. Consequently the onboard flash must be unmounted (with
|
||||||
|
`uos.umount()`) before the FRAM can be mounted.
|
||||||
|
|
||||||
|
# 7. References
|
||||||
|
|
||||||
|
[Adafruit board](http://www.adafruit.com/product/1895)
|
||||||
|
[Chip datasheet](https://cdn-learn.adafruit.com/assets/assets/000/043/904/original/MB85RC256V-DS501-00017-3v0-E.pdf?1500009796)
|
||||||
|
[Technology](https://www.mouser.com/pdfDOCS/cypress-fram-whitepaper.pdf)
|
|
@ -0,0 +1,91 @@
|
||||||
|
# fram_i2c.py Driver for Adafruit 32K Ferroelectric RAM module (Fujitsu MB85RC256V)
|
||||||
|
|
||||||
|
# Released under the MIT License (MIT). See LICENSE.
|
||||||
|
# Copyright (c) 2019 Peter Hinch
|
||||||
|
|
||||||
|
from micropython import const
|
||||||
|
from bdevice import BlockDevice
|
||||||
|
|
||||||
|
_SIZE = const(32768) # Chip size 32KiB
|
||||||
|
_ADDR = const(0x50) # FRAM I2C address 0x50 to 0x57
|
||||||
|
_FRAM_SLAVE_ID = const(0xf8) # FRAM device ID location
|
||||||
|
_MANF_ID = const(0x0a)
|
||||||
|
_PRODUCT_ID = const(0x510)
|
||||||
|
|
||||||
|
|
||||||
|
# A logical ferroelectric RAM made up of from 1 to 8 chips
|
||||||
|
class FRAM(BlockDevice):
|
||||||
|
def __init__(self, i2c, verbose=True, block_size=9):
|
||||||
|
self._i2c = i2c
|
||||||
|
self._buf1 = bytearray(1)
|
||||||
|
self._addrbuf = bytearray(2) # Memory offset into current chip
|
||||||
|
self._buf3 = bytearray(3)
|
||||||
|
self._nchips = self.scan(verbose, _SIZE)
|
||||||
|
super().__init__(block_size, self._nchips, _SIZE)
|
||||||
|
self._i2c_addr = None # i2c address of current chip
|
||||||
|
|
||||||
|
def scan(self, verbose, chip_size):
|
||||||
|
devices = self._i2c.scan()
|
||||||
|
chips = [d for d in devices if d in range(_ADDR, _ADDR + 8)]
|
||||||
|
nchips = len(chips)
|
||||||
|
if nchips == 0:
|
||||||
|
raise RuntimeError('FRAM not found.')
|
||||||
|
if min(chips) != _ADDR or (max(chips) - _ADDR) >= nchips:
|
||||||
|
raise RuntimeError('Non-contiguous chip addresses', chips)
|
||||||
|
for chip in chips:
|
||||||
|
if not self._available(chip):
|
||||||
|
raise RuntimeError('FRAM at address 0x{:02x} reports an error'.format(chip))
|
||||||
|
if verbose:
|
||||||
|
s = '{} chips detected. Total FRAM size {}bytes.'
|
||||||
|
print(s.format(nchips, chip_size * nchips))
|
||||||
|
return nchips
|
||||||
|
|
||||||
|
def _available(self, device_addr):
|
||||||
|
res = self._buf3
|
||||||
|
self._i2c.readfrom_mem_into(_FRAM_SLAVE_ID >> 1, device_addr << 1, res)
|
||||||
|
manufacturerID = (res[0] << 4) + (res[1] >> 4)
|
||||||
|
productID = ((res[1] & 0x0F) << 8) + res[2]
|
||||||
|
return manufacturerID == _MANF_ID and productID == _PRODUCT_ID
|
||||||
|
|
||||||
|
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))
|
||||||
|
|
||||||
|
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]
|
||||||
|
|
||||||
|
# In the context of FRAM a page == a chip.
|
||||||
|
# Args: an address and a no. of bytes. Set ._i2c_addr to correct chip.
|
||||||
|
# Return the no. of bytes available to access on that chip.
|
||||||
|
def _getaddr(self, addr, nbytes): # Set up _addrbuf and i2c_addr
|
||||||
|
if addr >= self._a_bytes:
|
||||||
|
raise RuntimeError('FRAM Address is out of range')
|
||||||
|
ca, la = divmod(addr, self._c_bytes) # ca == chip no, la == offset into chip
|
||||||
|
self._addrbuf[0] = (la >> 8) & 0xff
|
||||||
|
self._addrbuf[1] = la & 0xff
|
||||||
|
self._i2c_addr = _ADDR + ca
|
||||||
|
return min(nbytes, self._c_bytes - la)
|
||||||
|
|
||||||
|
def readwrite(self, addr, buf, read):
|
||||||
|
nbytes = len(buf)
|
||||||
|
mvb = memoryview(buf)
|
||||||
|
start = 0 # Offset into buf.
|
||||||
|
while nbytes > 0:
|
||||||
|
npage = self._getaddr(addr, nbytes) # No of bytes that fit on current chip
|
||||||
|
if read:
|
||||||
|
self._i2c.writeto(self._i2c_addr, self._addrbuf)
|
||||||
|
self._i2c.readfrom_into(self._i2c_addr, mvb[start : start + npage]) # Sequential read
|
||||||
|
else:
|
||||||
|
self._i2c.writevto(self._i2c_addr, (self._addrbuf, buf[start: start + npage]))
|
||||||
|
nbytes -= npage
|
||||||
|
start += npage
|
||||||
|
addr += npage
|
||||||
|
return buf
|
|
@ -0,0 +1,127 @@
|
||||||
|
# fram_test.py MicroPython test program for Adafruit FRAM devices.
|
||||||
|
|
||||||
|
# Released under the MIT License (MIT). See LICENSE.
|
||||||
|
# Copyright (c) 2019 Peter Hinch
|
||||||
|
|
||||||
|
import uos
|
||||||
|
from machine import I2C, Pin
|
||||||
|
from fram_i2c import FRAM
|
||||||
|
|
||||||
|
# Return an FRAM array. Adapt for platforms other than Pyboard.
|
||||||
|
def get_fram():
|
||||||
|
if uos.uname().machine.split(' ')[0][:4] == 'PYBD':
|
||||||
|
Pin.board.EN_3V3.value(1)
|
||||||
|
fram = FRAM(I2C(2))
|
||||||
|
print('Instantiated FRAM')
|
||||||
|
return fram
|
||||||
|
|
||||||
|
# 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 /fram/
|
||||||
|
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:' + 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:' + 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:' + res
|
||||||
|
|
||||||
|
def test():
|
||||||
|
fram = get_fram()
|
||||||
|
sa = 1000
|
||||||
|
for v in range(256):
|
||||||
|
fram[sa + v] = v
|
||||||
|
for v in range(256):
|
||||||
|
if fram[sa + v] != v:
|
||||||
|
print('Fail at address {} data {} should be {}'.format(sa + v, fram[sa + v], v))
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
print('Test of byte addressing passed')
|
||||||
|
data = uos.urandom(30)
|
||||||
|
sa = 2000
|
||||||
|
fram[sa:sa + 30] = data
|
||||||
|
if fram[sa:sa + 30] == data:
|
||||||
|
print('Test of slice readback passed')
|
||||||
|
# On FRAM the only meaningful block test is on a chip boundary.
|
||||||
|
block = fram._c_bytes
|
||||||
|
if fram._a_bytes > block:
|
||||||
|
res = _testblock(fram, 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(format=False):
|
||||||
|
fram = get_fram()
|
||||||
|
if format:
|
||||||
|
uos.VfsFat.mkfs(fram)
|
||||||
|
vfs=uos.VfsFat(fram)
|
||||||
|
try:
|
||||||
|
uos.mount(vfs,'/fram')
|
||||||
|
except OSError: # Already mounted
|
||||||
|
pass
|
||||||
|
print('Contents of "/": {}'.format(uos.listdir('/')))
|
||||||
|
print('Contents of "/fram": {}'.format(uos.listdir('/fram')))
|
||||||
|
print(uos.statvfs('/fram'))
|
||||||
|
|
||||||
|
def cptest():
|
||||||
|
fram = get_fram()
|
||||||
|
if 'fram' in uos.listdir('/'):
|
||||||
|
print('Device already mounted.')
|
||||||
|
else:
|
||||||
|
vfs=uos.VfsFat(fram)
|
||||||
|
try:
|
||||||
|
uos.mount(vfs,'/fram')
|
||||||
|
except OSError:
|
||||||
|
print('Fail mounting device. Have you formatted it?')
|
||||||
|
return
|
||||||
|
print('Mounted device.')
|
||||||
|
cp('fram_test.py', '/fram/')
|
||||||
|
cp('fram_i2c.py', '/fram/')
|
||||||
|
print('Contents of "/fram": {}'.format(uos.listdir('/fram')))
|
||||||
|
print(uos.statvfs('/fram'))
|
||||||
|
|
||||||
|
# ***** TEST OF HARDWARE *****
|
||||||
|
def full_test():
|
||||||
|
fram = get_fram()
|
||||||
|
page = 0
|
||||||
|
for sa in range(0, len(fram), 256):
|
||||||
|
data = uos.urandom(256)
|
||||||
|
fram[sa:sa + 256] = data
|
||||||
|
if fram[sa:sa + 256] == data:
|
||||||
|
print('Page {} passed'.format(page))
|
||||||
|
else:
|
||||||
|
print('Page {} readback failed.'.format(page))
|
||||||
|
page += 1
|
55
i2c/I2C.md
55
i2c/I2C.md
|
@ -113,11 +113,13 @@ is detected or if device address lines are not wired as described in
|
||||||
[Connections](./README.md#2-connections).
|
[Connections](./README.md#2-connections).
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
1. `i2c` Mandatory. An initialised master mode I2C bus.
|
1. `i2c` Mandatory. An initialised master mode I2C bus created by `machine`.
|
||||||
2. `chip_size=T24C512` The chip size in bits. The module provides constants
|
2. `chip_size=T24C512` The chip size in bits. The module provides constants
|
||||||
`T24C64`, `T24C128`, `T24C256`, `T24C512` for the supported chip sizes.
|
`T24C64`, `T24C128`, `T24C256`, `T24C512` for the supported chip sizes.
|
||||||
3. `verbose=True` If True, the constructor issues information on the EEPROM
|
3. `verbose=True` If `True`, the constructor issues information on the EEPROM
|
||||||
devices it has detected.
|
devices it has detected.
|
||||||
|
4. `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
|
### 4.1.2 Methods providing byte level access
|
||||||
|
|
||||||
|
@ -149,7 +151,8 @@ print(eep[2000:2002]) # Returns a bytearray
|
||||||
```
|
```
|
||||||
Three argument slices are not supported: a third arg (other than 1) will cause
|
Three argument slices are not supported: a third arg (other than 1) will cause
|
||||||
an exception. One argument slices (`eep[:5]` or `eep[13100:]`) and negative
|
an exception. One argument slices (`eep[:5]` or `eep[13100:]`) and negative
|
||||||
args are supported.
|
args are supported. See [section 4.2](./I2C.md#42-byte-addressing-usage-example)
|
||||||
|
for a typical application.
|
||||||
|
|
||||||
#### 4.1.2.2 readwrite
|
#### 4.1.2.2 readwrite
|
||||||
|
|
||||||
|
@ -187,6 +190,36 @@ also [here](http://docs.micropython.org/en/latest/reference/filesystem.html#cust
|
||||||
`writeblocks()`
|
`writeblocks()`
|
||||||
`ioctl()`
|
`ioctl()`
|
||||||
|
|
||||||
|
## 4.2 Byte addressing usage example
|
||||||
|
|
||||||
|
A sample application: saving a configuration dict (which might be large and
|
||||||
|
complicated):
|
||||||
|
```python
|
||||||
|
import ujson
|
||||||
|
from machine import I2C
|
||||||
|
from eeprom_i2c import EEPROM, T24C512
|
||||||
|
eep = EEPROM(I2C(2), T24C512)
|
||||||
|
d = {1:'one', 2:'two'} # Some kind of large object
|
||||||
|
wdata = ujson.dumps(d).encode('utf8')
|
||||||
|
sl = '{:10d}'.format(len(wdata)).encode('utf8')
|
||||||
|
eep[0 : len(sl)] = sl # Save data length in locations 0-9
|
||||||
|
start = 10 # Data goes in 10:
|
||||||
|
end = start + len(wdata)
|
||||||
|
eep[start : end] = wdata
|
||||||
|
```
|
||||||
|
After a power cycle the data may be read back. Instantiate `eep` as above, then
|
||||||
|
issue:
|
||||||
|
```python
|
||||||
|
slen = int(eep[:10].decode().strip()) # retrieve object size
|
||||||
|
start = 10
|
||||||
|
end = start + slen
|
||||||
|
d = ujson.loads(eep[start : end])
|
||||||
|
```
|
||||||
|
It is much more efficient in space and performance to store data in binary form
|
||||||
|
but in many cases code simplicity matters, especially where the data structure
|
||||||
|
is subject to change. An alternative to JSON is the pickle module. It is also
|
||||||
|
possible to use JSON/pickle to store objects in a filesystem.
|
||||||
|
|
||||||
# 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
|
||||||
|
@ -195,12 +228,14 @@ provides the following.
|
||||||
## 5.1 test()
|
## 5.1 test()
|
||||||
|
|
||||||
This performs a basic test of single and multi-byte access to chip 0. The 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.
|
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()
|
||||||
|
|
||||||
Tests the entire array. Fills each 128 byte page with random data, reads it
|
This is a hardware test. Tests the entire array. Fills each 128 byte page with
|
||||||
back, and checks the outcome. Existing array data will be lost.
|
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)
|
||||||
|
|
||||||
|
@ -208,7 +243,13 @@ 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
|
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.
|
contents. It also prints the outcome of `uos.statvfs` on the array.
|
||||||
|
|
||||||
## 5.4 File copy
|
## 5.4 cptest()
|
||||||
|
|
||||||
|
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
|
||||||
|
the outcome of `uos.statvfs`.
|
||||||
|
|
||||||
|
## 5.5 File copy
|
||||||
|
|
||||||
A rudimentary `cp(source, dest)` function is provided as a generic 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
|
routine for setup and debugging purposes at the REPL. The first argument is the
|
||||||
|
|
|
@ -28,6 +28,33 @@ def cp(source, dest):
|
||||||
if len(buf) < 100:
|
if len(buf) < 100:
|
||||||
break
|
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:' + 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:' + 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:' + res
|
||||||
|
|
||||||
def test():
|
def test():
|
||||||
eep = get_eep()
|
eep = get_eep()
|
||||||
sa = 1000
|
sa = 1000
|
||||||
|
@ -45,6 +72,25 @@ def test():
|
||||||
if eep[sa:sa + 30] == data:
|
if eep[sa:sa + 30] == data:
|
||||||
print('Test of slice readback passed')
|
print('Test of slice readback passed')
|
||||||
|
|
||||||
|
block = 256
|
||||||
|
res = _testblock(eep, block)
|
||||||
|
if res is None:
|
||||||
|
print('Test block boundary {} passed'.format(block))
|
||||||
|
else:
|
||||||
|
print('Test block boundary {} fail'.format(block))
|
||||||
|
print(res)
|
||||||
|
block = eep._c_bytes
|
||||||
|
if eep._a_bytes > block:
|
||||||
|
res = _testblock(eep, 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(format=False):
|
def fstest(format=False):
|
||||||
eep = get_eep()
|
eep = get_eep()
|
||||||
if format:
|
if format:
|
||||||
|
@ -58,6 +104,24 @@ def fstest(format=False):
|
||||||
print('Contents of "/eeprom": {}'.format(uos.listdir('/eeprom')))
|
print('Contents of "/eeprom": {}'.format(uos.listdir('/eeprom')))
|
||||||
print(uos.statvfs('/eeprom'))
|
print(uos.statvfs('/eeprom'))
|
||||||
|
|
||||||
|
def cptest():
|
||||||
|
eep = get_eep()
|
||||||
|
if 'eeprom' in uos.listdir('/'):
|
||||||
|
print('Device already mounted.')
|
||||||
|
else:
|
||||||
|
vfs=uos.VfsFat(eep)
|
||||||
|
try:
|
||||||
|
uos.mount(vfs,'/eeprom')
|
||||||
|
except OSError:
|
||||||
|
print('Fail mounting device. Have you formatted it?')
|
||||||
|
return
|
||||||
|
print('Mounted device.')
|
||||||
|
cp('eep_i2c.py', '/eeprom/')
|
||||||
|
cp('eeprom_i2c.py', '/eeprom/')
|
||||||
|
print('Contents of "/eeprom": {}'.format(uos.listdir('/eeprom')))
|
||||||
|
print(uos.statvfs('/eeprom'))
|
||||||
|
|
||||||
|
# ***** TEST OF HARDWARE *****
|
||||||
def full_test():
|
def full_test():
|
||||||
eep = get_eep()
|
eep = get_eep()
|
||||||
page = 0
|
page = 0
|
||||||
|
|
|
@ -7,7 +7,7 @@ import time
|
||||||
from micropython import const
|
from micropython import const
|
||||||
from bdevice import BlockDevice
|
from bdevice import BlockDevice
|
||||||
|
|
||||||
ADDR = const(0x50) # Base address of chip
|
_ADDR = const(0x50) # Base address of chip
|
||||||
|
|
||||||
T24C512 = const(65536) # 64KiB 512Kbits
|
T24C512 = const(65536) # 64KiB 512Kbits
|
||||||
T24C256 = const(32768) # 32KiB 256Kbits
|
T24C256 = const(32768) # 32KiB 256Kbits
|
||||||
|
@ -18,12 +18,12 @@ T24C64 = const(8192) # 8KiB 64Kbits
|
||||||
# same size, and must have contiguous addresses starting from 0x50.
|
# same size, and must have contiguous addresses starting from 0x50.
|
||||||
class EEPROM(BlockDevice):
|
class EEPROM(BlockDevice):
|
||||||
|
|
||||||
def __init__(self, i2c, chip_size=T24C512, verbose=True):
|
def __init__(self, i2c, chip_size=T24C512, verbose=True, block_size=9):
|
||||||
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
|
||||||
super().__init__(9, nchips, chip_size)
|
super().__init__(block_size, nchips, chip_size)
|
||||||
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
|
||||||
|
@ -31,11 +31,11 @@ class EEPROM(BlockDevice):
|
||||||
# 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
|
||||||
eeproms = [d for d in devices if ADDR <= d < ADDR + 8] # EEPROM chips
|
eeproms = [d for d in devices if _ADDR <= d < _ADDR + 8] # EEPROM chips
|
||||||
nchips = len(eeproms)
|
nchips = len(eeproms)
|
||||||
if nchips == 0:
|
if nchips == 0:
|
||||||
raise RuntimeError('EEPROM not found.')
|
raise RuntimeError('EEPROM not found.')
|
||||||
if min(eeproms) != ADDR or (max(eeproms) - ADDR + 1) > nchips:
|
if min(eeproms) != _ADDR or (max(eeproms) - _ADDR) >= nchips:
|
||||||
raise RuntimeError('Non-contiguous chip addresses', eeproms)
|
raise RuntimeError('Non-contiguous chip addresses', eeproms)
|
||||||
if verbose:
|
if verbose:
|
||||||
s = '{} chips detected. Total EEPROM size {}bytes.'
|
s = '{} chips detected. Total EEPROM size {}bytes.'
|
||||||
|
@ -55,14 +55,7 @@ class EEPROM(BlockDevice):
|
||||||
|
|
||||||
def __setitem__(self, addr, value):
|
def __setitem__(self, addr, value):
|
||||||
if isinstance(addr, slice):
|
if isinstance(addr, slice):
|
||||||
start, stop = self.do_slice(addr)
|
return self.wslice(addr, value)
|
||||||
try:
|
|
||||||
if len(value) == (stop - start):
|
|
||||||
return self.readwrite(start, value, False)
|
|
||||||
else:
|
|
||||||
raise RuntimeError('Slice must have same length as data')
|
|
||||||
except TypeError:
|
|
||||||
raise RuntimeError('Can only assign bytes/bytearray to a slice')
|
|
||||||
self._buf1[0] = value
|
self._buf1[0] = value
|
||||||
self._getaddr(addr, 1)
|
self._getaddr(addr, 1)
|
||||||
self._i2c.writevto(self._i2c_addr, (self._addrbuf, self._buf1))
|
self._i2c.writevto(self._i2c_addr, (self._addrbuf, self._buf1))
|
||||||
|
@ -70,9 +63,7 @@ class EEPROM(BlockDevice):
|
||||||
|
|
||||||
def __getitem__(self, addr):
|
def __getitem__(self, addr):
|
||||||
if isinstance(addr, slice):
|
if isinstance(addr, slice):
|
||||||
start, stop = self.do_slice(addr)
|
return self.rslice(addr)
|
||||||
buf = bytearray(stop - start)
|
|
||||||
return self.readwrite(start, buf, True)
|
|
||||||
self._getaddr(addr, 1)
|
self._getaddr(addr, 1)
|
||||||
self._i2c.writeto(self._i2c_addr, self._addrbuf)
|
self._i2c.writeto(self._i2c_addr, self._addrbuf)
|
||||||
self._i2c.readfrom_into(self._i2c_addr, self._buf1)
|
self._i2c.readfrom_into(self._i2c_addr, self._buf1)
|
||||||
|
@ -86,7 +77,7 @@ class EEPROM(BlockDevice):
|
||||||
ca, la = divmod(addr, self._c_bytes) # ca == chip no, la == offset into chip
|
ca, la = divmod(addr, self._c_bytes) # ca == chip no, la == offset into chip
|
||||||
self._addrbuf[0] = (la >> 8) & 0xff
|
self._addrbuf[0] = (la >> 8) & 0xff
|
||||||
self._addrbuf[1] = la & 0xff
|
self._addrbuf[1] = la & 0xff
|
||||||
self._i2c_addr = ADDR + ca
|
self._i2c_addr = _ADDR + ca
|
||||||
pe = (addr & ~0x7f) + 0x80 # byte 0 of next page
|
pe = (addr & ~0x7f) + 0x80 # byte 0 of next page
|
||||||
return min(nbytes, pe - la)
|
return min(nbytes, pe - la)
|
||||||
|
|
||||||
|
@ -94,7 +85,7 @@ class EEPROM(BlockDevice):
|
||||||
def readwrite(self, addr, buf, read):
|
def readwrite(self, addr, buf, read):
|
||||||
nbytes = len(buf)
|
nbytes = len(buf)
|
||||||
mvb = memoryview(buf)
|
mvb = memoryview(buf)
|
||||||
start = 0
|
start = 0 # Offset into buf.
|
||||||
while nbytes > 0:
|
while nbytes > 0:
|
||||||
npage = self._getaddr(addr, nbytes) # No. of bytes in current page
|
npage = self._getaddr(addr, nbytes) # No. of bytes in current page
|
||||||
assert npage > 0
|
assert npage > 0
|
||||||
|
|
61
spi/SPI.md
61
spi/SPI.md
|
@ -34,7 +34,8 @@ 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
|
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,
|
must be wired to a single chip's CS line. Multiple chips should have 3V3, Gnd,
|
||||||
SCL, MOSI and MISO lines wired in parallel.
|
SCL, MOSI and MISO lines wired in parallel. The SPI bus is fast: wiring should
|
||||||
|
be short and direct.
|
||||||
|
|
||||||
If you use a Pyboard D and power the EEPROMs from the 3V3 output you will need
|
If you use a Pyboard D and power the EEPROMs from the 3V3 output you will need
|
||||||
to enable the voltage rail by issuing:
|
to enable the voltage rail by issuing:
|
||||||
|
@ -90,11 +91,13 @@ each chip select line an EEPROM array is instantiated. A `RuntimeError` will be
|
||||||
raised if a device is not detected on a CS line.
|
raised if a device is not detected on a CS line.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
1. `spi` Mandatory. An initialised SPI bus.
|
1. `spi` Mandatory. An initialised SPI bus created by `machine`.
|
||||||
2. `cspins` A list or tuple of `Pin` instances. Each `Pin` must be initialised
|
2. `cspins` A list or tuple of `Pin` instances. Each `Pin` must be initialised
|
||||||
as an output (`Pin.OUT`) and with `value=1`.
|
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. `verbose=True` If `True`, the constructor issues information on the EEPROM
|
||||||
devices it has detected.
|
devices it has detected.
|
||||||
|
4. `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
|
SPI baudrate: The 25LC1024 supports baudrates of upto 20MHz. If this value is
|
||||||
specified the platform will produce the highest available frequency not
|
specified the platform will produce the highest available frequency not
|
||||||
|
@ -135,7 +138,8 @@ print(eep[2000:2002]) # Returns a bytearray
|
||||||
```
|
```
|
||||||
Three argument slices are not supported: a third arg (other than 1) will cause
|
Three argument slices are not supported: a third arg (other than 1) will cause
|
||||||
an exception. One argument slices (`eep[:5]` or `eep[13100:]`) and negative
|
an exception. One argument slices (`eep[:5]` or `eep[13100:]`) and negative
|
||||||
args are supported.
|
args are supported. See [section 4.2](./SPI.md#42-byte-addressing-usage-example)
|
||||||
|
for a typical application.
|
||||||
|
|
||||||
#### 4.1.2.2 readwrite
|
#### 4.1.2.2 readwrite
|
||||||
|
|
||||||
|
@ -179,6 +183,37 @@ also [here](http://docs.micropython.org/en/latest/reference/filesystem.html#cust
|
||||||
`writeblocks()`
|
`writeblocks()`
|
||||||
`ioctl()`
|
`ioctl()`
|
||||||
|
|
||||||
|
## 4.2 Byte addressing usage example
|
||||||
|
|
||||||
|
A sample application: saving a configuration dict (which might be large and
|
||||||
|
complicated):
|
||||||
|
```python
|
||||||
|
import ujson
|
||||||
|
from machine import SPI, Pin
|
||||||
|
from eeprom_spi import EEPROM
|
||||||
|
cspins = (Pin(Pin.board.Y5, Pin.OUT, value=1), Pin(Pin.board.Y4, Pin.OUT, value=1))
|
||||||
|
eep = EEPROM(SPI(2, baudrate=20_000_000), cspins)
|
||||||
|
d = {1:'one', 2:'two'} # Some kind of large object
|
||||||
|
wdata = ujson.dumps(d).encode('utf8')
|
||||||
|
sl = '{:10d}'.format(len(wdata)).encode('utf8')
|
||||||
|
eep[0 : len(sl)] = sl # Save data length in locations 0-9
|
||||||
|
start = 10 # Data goes in 10:
|
||||||
|
end = start + len(wdata)
|
||||||
|
eep[start : end] = wdata
|
||||||
|
```
|
||||||
|
After a power cycle the data may be read back. Instantiate `eep` as above, then
|
||||||
|
issue:
|
||||||
|
```python
|
||||||
|
slen = int(eep[:10].decode().strip()) # retrieve object size
|
||||||
|
start = 10
|
||||||
|
end = start + slen
|
||||||
|
d = ujson.loads(eep[start : end])
|
||||||
|
```
|
||||||
|
It is much more efficient in space and performance to store data in binary form
|
||||||
|
but in many cases code simplicity matters, especially where the data structure
|
||||||
|
is subject to change. An alternative to JSON is the pickle module. It is also
|
||||||
|
possible to use JSON/pickle to store objects in a filesystem.
|
||||||
|
|
||||||
# 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
|
||||||
|
@ -187,12 +222,14 @@ provides the following.
|
||||||
## 5.1 test()
|
## 5.1 test()
|
||||||
|
|
||||||
This performs a basic test of single and multi-byte access to chip 0. The 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.
|
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()
|
||||||
|
|
||||||
Tests the entire array. Fills each 128 byte page with random data, reads it
|
This is a hardware test. Tests the entire array. Fills each 256 byte page with
|
||||||
back, and checks the outcome. Existing array data will be lost.
|
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)
|
||||||
|
|
||||||
|
@ -200,7 +237,13 @@ 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
|
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.
|
contents. It also prints the outcome of `uos.statvfs` on the array.
|
||||||
|
|
||||||
## 5.4 File copy
|
## 5.4 cptest()
|
||||||
|
|
||||||
|
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
|
||||||
|
the outcome of `uos.statvfs`.
|
||||||
|
|
||||||
|
## 5.5 File copy
|
||||||
|
|
||||||
A rudimentary `cp(source, dest)` function is provided as a generic 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
|
routine for setup and debugging purposes at the REPL. The first argument is the
|
||||||
|
|
|
@ -7,7 +7,7 @@ import uos
|
||||||
from machine import SPI, Pin
|
from machine import SPI, Pin
|
||||||
from eeprom_spi import EEPROM
|
from eeprom_spi import EEPROM
|
||||||
# Add extra pins if using multiple chips
|
# Add extra pins if using multiple chips
|
||||||
cspins = (Pin(Pin.board.Y5, Pin.OUT, value=1),)
|
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.
|
# Return an EEPROM array. Adapt for platforms other than Pyboard.
|
||||||
def get_eep():
|
def get_eep():
|
||||||
|
@ -29,6 +29,33 @@ def cp(source, dest):
|
||||||
if len(buf) < 100:
|
if len(buf) < 100:
|
||||||
break
|
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:' + 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:' + 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:' + res
|
||||||
|
|
||||||
def test():
|
def test():
|
||||||
eep = get_eep()
|
eep = get_eep()
|
||||||
sa = 1000
|
sa = 1000
|
||||||
|
@ -46,6 +73,25 @@ def test():
|
||||||
if eep[sa:sa + 30] == data:
|
if eep[sa:sa + 30] == data:
|
||||||
print('Test of slice readback passed')
|
print('Test of slice readback passed')
|
||||||
|
|
||||||
|
block = 256
|
||||||
|
res = _testblock(eep, block)
|
||||||
|
if res is None:
|
||||||
|
print('Test block boundary {} passed'.format(block))
|
||||||
|
else:
|
||||||
|
print('Test block boundary {} fail'.format(block))
|
||||||
|
print(res)
|
||||||
|
block = eep._c_bytes
|
||||||
|
if eep._a_bytes > block:
|
||||||
|
res = _testblock(eep, 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(format=False):
|
def fstest(format=False):
|
||||||
eep = get_eep()
|
eep = get_eep()
|
||||||
if format:
|
if format:
|
||||||
|
@ -59,6 +105,25 @@ def fstest(format=False):
|
||||||
print('Contents of "/eeprom": {}'.format(uos.listdir('/eeprom')))
|
print('Contents of "/eeprom": {}'.format(uos.listdir('/eeprom')))
|
||||||
print(uos.statvfs('/eeprom'))
|
print(uos.statvfs('/eeprom'))
|
||||||
|
|
||||||
|
def cptest():
|
||||||
|
eep = get_eep()
|
||||||
|
if 'eeprom' in uos.listdir('/'):
|
||||||
|
print('Device already mounted.')
|
||||||
|
else:
|
||||||
|
vfs=uos.VfsFat(eep)
|
||||||
|
try:
|
||||||
|
uos.mount(vfs,'/eeprom')
|
||||||
|
except OSError:
|
||||||
|
print('Fail mounting device. Have you formatted it?')
|
||||||
|
return
|
||||||
|
print('Mounted device.')
|
||||||
|
cp('eep_spi.py', '/eeprom/')
|
||||||
|
cp('eeprom_spi.py', '/eeprom/')
|
||||||
|
print('Contents of "/eeprom": {}'.format(uos.listdir('/eeprom')))
|
||||||
|
print(uos.statvfs('/eeprom'))
|
||||||
|
|
||||||
|
|
||||||
|
# ***** TEST OF HARDWARE *****
|
||||||
def full_test():
|
def full_test():
|
||||||
eep = get_eep()
|
eep = get_eep()
|
||||||
page = 0
|
page = 0
|
||||||
|
|
|
@ -23,9 +23,9 @@ _CE = const(0xc7) # Chip erase
|
||||||
# Logical EEPROM device comprising one or more physical chips sharing an SPI bus.
|
# Logical EEPROM device comprising one or more physical chips sharing an SPI bus.
|
||||||
class EEPROM(BlockDevice):
|
class EEPROM(BlockDevice):
|
||||||
|
|
||||||
def __init__(self, spi, cspins, verbose=True):
|
def __init__(self, spi, cspins, verbose=True, block_size=9):
|
||||||
# args: virtual block size in bits, no. of chips, bytes in each chip
|
# args: virtual block size in bits, no. of chips, bytes in each chip
|
||||||
super().__init__(9, len(cspins), _SIZE)
|
super().__init__(block_size, len(cspins), _SIZE)
|
||||||
self._spi = spi
|
self._spi = spi
|
||||||
self._cspins = cspins
|
self._cspins = cspins
|
||||||
self._ccs = None # Chip select Pin object for current chip
|
self._ccs = None # Chip select Pin object for current chip
|
||||||
|
@ -74,15 +74,8 @@ class EEPROM(BlockDevice):
|
||||||
time.sleep_ms(1)
|
time.sleep_ms(1)
|
||||||
|
|
||||||
def __setitem__(self, addr, value):
|
def __setitem__(self, addr, value):
|
||||||
if isinstance(addr, slice): # value is a buffer
|
if isinstance(addr, slice):
|
||||||
start, stop = self.do_slice(addr)
|
return self.wslice(addr, value)
|
||||||
try:
|
|
||||||
if len(value) == (stop - start):
|
|
||||||
return self.readwrite(start, value, False)
|
|
||||||
else:
|
|
||||||
raise RuntimeError('Slice must have same length as data')
|
|
||||||
except TypeError:
|
|
||||||
raise RuntimeError('Can only assign bytes/bytearray to a slice')
|
|
||||||
mvp = self._mvp
|
mvp = self._mvp
|
||||||
mvp[0] = _WREN
|
mvp[0] = _WREN
|
||||||
self._getaddr(addr, 1) # Sets mv[1:4], updates ._ccs
|
self._getaddr(addr, 1) # Sets mv[1:4], updates ._ccs
|
||||||
|
@ -99,9 +92,7 @@ class EEPROM(BlockDevice):
|
||||||
|
|
||||||
def __getitem__(self, addr):
|
def __getitem__(self, addr):
|
||||||
if isinstance(addr, slice):
|
if isinstance(addr, slice):
|
||||||
start, stop = self.do_slice(addr)
|
return self.rslice(addr)
|
||||||
buf = bytearray(stop - start)
|
|
||||||
return self.readwrite(start, buf, True)
|
|
||||||
mvp = self._mvp
|
mvp = self._mvp
|
||||||
mvp[0] = _READ
|
mvp[0] = _READ
|
||||||
self._getaddr(addr, 1)
|
self._getaddr(addr, 1)
|
||||||
|
@ -130,7 +121,7 @@ class EEPROM(BlockDevice):
|
||||||
nbytes = len(buf)
|
nbytes = len(buf)
|
||||||
mvb = memoryview(buf)
|
mvb = memoryview(buf)
|
||||||
mvp = self._mvp
|
mvp = self._mvp
|
||||||
start = 0
|
start = 0 # Offset into buf.
|
||||||
while nbytes > 0:
|
while nbytes > 0:
|
||||||
npage = self._getaddr(addr, nbytes) # No. of bytes in current page
|
npage = self._getaddr(addr, nbytes) # No. of bytes in current page
|
||||||
cs = self._ccs
|
cs = self._ccs
|
||||||
|
|
Ładowanie…
Reference in New Issue