Add SPI support. Reorganise directories.

pull/1/head
Peter Hinch 2019-12-13 18:49:48 +00:00
rodzic 43421c55e7
commit 992e35b994
7 zmienionych plików z 744 dodań i 220 usunięć

245
README.md
Wyświetl plik

@ -1,224 +1,43 @@
# 1. A MicroPython EEPROM driver
# MicroPython EEPROM drivers
This enables MicroPython to access Microchip EEPROM devices. Unlike flash
memory, EEPROMs may be written on a byte addressable basis. Its 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 relatively
rapidly.
EEPROM is a form of nonvolatile random access storage.
From one to eight chips may be used to construct a nonvolatile memory module
with sizes upto 512KiB. 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.
Where multiple chips are used, all must be the same size.
These drivers enable MicroPython to access Microchip EEPROM devices. There are
two variants, one for chips based on the I2C interface and a second for a 1MBit
SPI chip.
The work was inspired by [this one](https://github.com/dda/MicroPython.git).
This was written some five years ago. The driver in this repo employs some of
the subsequent improvements to MicroPython to achieve these advantages:
1. It supports multiple EEPROM chips to configure a single array.
2. Writes are up to 1000x faster by using ACK polling and page writes.
3. Page access improves the speed of multi-byte reads.
4. It is cross-platform.
5. The I2C 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 reduced.
Unlike flash memory, EEPROMs may be written on a byte addressable basis. Their
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
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).
# 2. Connections
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
the mean time per byte is quicker by a factor of the page size (128 or 256
bytes depending on the device).
Any I2C interface may be used. The table below assumes a Pyboard running I2C(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).
The drivers support creating multi-chip arrays. In the case of I2C chips, up to
eight devices may share the bus. In the case of SPI expansion has no absolute
limit as each chip has its own chip select line.
| EEPROM | PB |
|:------:|:---:|
| 1 A0 | Gnd |
| 2 A1 | Gnd |
| 3 A2 | Gnd |
| 4 Vss | Gnd |
| 5 Sda | Y10 |
| 6 Scl | Y9 |
| 7 WPA1 | Gnd |
| 8 Vcc | 3V3 |
Devices or arrays of devices may be mounted as a filesystem or may be treated
as an array of bytes.
For multiple chips the address lines A0, A1 and A2 of each chip 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:
For I2C devices see [I2C.md](./i2c/I2C.md). For SPI see [SPI.md](./spi/SPI.md).
| Chip no. | A2 | A1 | A0 |
|:--------:|:---:|:---:|:---:|
| 0 | Gnd | Gnd | Gnd |
| 1 | Gnd | Gnd | 3V3 |
| 2 | Gnd | 3V3 | Gnd |
| 3 | Gnd | 3V3 | 3V3 |
| 4 | 3V3 | Gnd | Gnd |
| 5 | 3V3 | Gnd | 3V3 |
| 6 | 3V3 | 3V3 | Gnd |
| 7 | 3V3 | 3V3 | 3V3 |
# Choice of interface
Multiple chips should have 3V3, Gnd, SCL and SDA lines wired in parallel.
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
those lines, although these may be provided on the target device. The
supported EEPROM devices limit expansion to a maximum of 8 chips on a bus.
The I2C interface requires pullups, typically 3.3KΩ to 3.3V although any value
up to 10KΩ will suffice. The Pyboard 1.x has these on board. The Pyboard D has
them only on I2C(1). Even if boards have pullups, additional externalresistors
will do no harm.
SPI requires no pullups, but uses three pins plus one for each connected chip.
It is much faster than I2C, but in the case of EEPROMs the benefit is only
apparent on reads: write speed is limited by the EEPROM device. In principle
expansion is limited only by the number of available pins. (In practice
electrical limits may also apply).
If you use a Pyboard D and power the EEPROMs 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. `eeprom.py` Device driver.
2. `eep_test.py` Test programs for above.
# 4. The device driver
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 one or more 64KiB devices:
```python
import uos
from machine import I2C
from eeprom import EEPROM, T24C512
eep = EEPROM(I2C(2), T24C512)
uos.VfsFat.mkfs(eep) # Omit this to mount an existing filesystem
vfs = uos.VfsFat(eep)
uos.mount(vfs,'/eeprom')
```
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. As a filesystem the limited size is an
issue, but a potential use case is for pickling Python objects for example to
achieve persistence when issuing `pyb.standby()`; also for holding a small
frequently updated persistent btree database.
The I2C bus must be instantiated using the `machine` module.
## 4.1 The EEPROM class
An `EEPROM` instance represents a logical EEPROM: this may consist of multiple
physical devices on a common I2C bus.
### 4.1.1 Constructor
This scans the I2C bus - if one or more correctly addressed chips are detected
an EEPROM array is instantiated. A `RuntimeError` will be raised if no device
is detected or if device address lines are not wired as described in
[Connections](./README.md#2-connections).
Arguments:
1. `i2c` Mandatory. An initialised master mode I2C bus.
2. `chip_size=T24C512` The chip size in bits. The module provides constants
`T24C64`, `T24C128`, `T24C256`, `T24C512` for the supported chip sizes.
3. `verbose=True` If True, the constructor issues information on the EEPROM
devices it has detected.
### 4.1.2 Methods providing byte level access
#### 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 eeprom import EEPROM, T24C512
eep = EEPROM(I2C(1), T24C512)
eep[2000] = 42
print(eep[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 eeprom import EEPROM, T24C512
eep = EEPROM(I2C(1), T24C512)
eep[2000:2002] = bytearray((42, 43))
print(eep[2000:2002]) # Returns a bytearray
```
Three argument slices are not supported: a third arg will be ignored.
#### 4.1.2.2 readwrite
This is a byte-level alternative to slice notation. It has the potential
advantage 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 Methods providing the block protocol
For the protocol definition see
[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)`
where `eep` is the `EEPROM` instance.
#### 4.1.4.2 scan
Scans the I2C bus and returns the number of EEPROM 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.
# 5. Test program eep_test.py
This assumes a Pyboard 1.x or Pyboard D with EEPROM(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.
## 5.2 full_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 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 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 EEPROM becoming
full) it is up to the caller to handle it. For example (assuming the EEPROM is
mounted on /eeprom):
```python
cp('/flash/main.py','/eeprom/')
```
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 EEPROM can be mounted.
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.

222
i2c/I2C.md 100644
Wyświetl plik

@ -0,0 +1,222 @@
# 1. A MicroPython I2C EEPROM driver
This driver supports chips from the 64KiB 25xx512 series and related chips with
smaller capacities.
From one to eight chips may be used to construct a nonvolatile memory module
with sizes upto 512KiB. 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.
Where multiple chips are used, all must be the same size.
The work was inspired by [this one](https://github.com/dda/MicroPython.git).
This was written some five years ago. The driver in this repo employs some of
the subsequent improvements to MicroPython to achieve these advantages:
1. It supports multiple EEPROM chips to configure a single array.
2. Writes are up to 1000x faster by using ACK polling and page writes.
3. Page access improves the speed of multi-byte reads.
4. It is cross-platform.
5. The I2C 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 reduced.
# 2. Connections
Any I2C interface may be used. The table below assumes a Pyboard running I2C(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).
| EEPROM | PB |
|:------:|:---:|
| 1 A0 | Gnd |
| 2 A1 | Gnd |
| 3 A2 | Gnd |
| 4 Vss | Gnd |
| 5 Sda | Y10 |
| 6 Scl | Y9 |
| 7 WPA1 | Gnd |
| 8 Vcc | 3V3 |
For multiple chips the address lines A0, A1 and A2 of each chip 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:
| Chip no. | A2 | A1 | A0 |
|:--------:|:---:|:---:|:---:|
| 0 | Gnd | Gnd | Gnd |
| 1 | Gnd | Gnd | 3V3 |
| 2 | Gnd | 3V3 | Gnd |
| 3 | Gnd | 3V3 | 3V3 |
| 4 | 3V3 | Gnd | Gnd |
| 5 | 3V3 | Gnd | 3V3 |
| 6 | 3V3 | 3V3 | Gnd |
| 7 | 3V3 | 3V3 | 3V3 |
Multiple chips should have 3V3, Gnd, SCL and SDA lines wired in parallel.
The I2C interface requires pullups, typically 3.3KΩ to 3.3V although any value
up to 10KΩ will suffice. The Pyboard 1.x has these on board. The Pyboard D has
them only on I2C(1). Even if boards have pullups, additional externalresistors
will do no harm.
If you use a Pyboard D and power the EEPROMs 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. `eeprom_i2c.py` Device driver.
2. `eep_i2c.py` Test programs for above.
# 4. The device driver
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 one or more 64KiB devices:
```python
import uos
from machine import I2C
from eeprom_i2c import EEPROM, T24C512
eep = EEPROM(I2C(2), T24C512)
uos.VfsFat.mkfs(eep) # Omit this to mount an existing filesystem
vfs = uos.VfsFat(eep)
uos.mount(vfs,'/eeprom')
```
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. As a filesystem the limited size is an
issue, but a potential use case is for pickling Python objects for example to
achieve persistence when issuing `pyb.standby()`; also for holding a small
frequently updated persistent btree database.
The I2C bus must be instantiated using the `machine` module.
## 4.1 The EEPROM class
An `EEPROM` instance represents a logical EEPROM: this may consist of multiple
physical devices on a common I2C bus.
### 4.1.1 Constructor
This scans the I2C bus - if one or more correctly addressed chips are detected
an EEPROM array is instantiated. A `RuntimeError` will be raised if no device
is detected or if device address lines are not wired as described in
[Connections](./README.md#2-connections).
Arguments:
1. `i2c` Mandatory. An initialised master mode I2C bus.
2. `chip_size=T24C512` The chip size in bits. The module provides constants
`T24C64`, `T24C128`, `T24C256`, `T24C512` for the supported chip sizes.
3. `verbose=True` If True, the constructor issues information on the EEPROM
devices it has detected.
### 4.1.2 Methods providing byte level access
#### 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 eeprom_i2c import EEPROM, T24C512
eep = EEPROM(I2C(1), T24C512)
eep[2000] = 42
print(eep[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 eeprom_i2c import EEPROM, T24C512
eep = EEPROM(I2C(1), T24C512)
eep[2000:2002] = bytearray((42, 43))
print(eep[2000:2002]) # Returns a bytearray
```
Three argument slices are not supported: any third arg will be ignored. One
argument slices (`eep[:5]` or `eep[13100:]`) and negative args are supported.
#### 4.1.2.2 readwrite
This is a byte-level alternative to slice notation. It has the potential
advantage 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 Methods providing the block protocol
For the protocol definition see
[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)`
where `eep` is the `EEPROM` instance.
#### 4.1.4.2 scan
Scans the I2C bus and returns the number of EEPROM 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.
# 5. Test program eep_i2c.py
This assumes a Pyboard 1.x or Pyboard D with EEPROM(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.
## 5.2 full_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 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 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 EEPROM becoming
full) it is up to the caller to handle it. For example (assuming the EEPROM is
mounted on /eeprom):
```python
cp('/flash/main.py','/eeprom/')
```
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 EEPROM can be mounted.

Wyświetl plik

@ -1,11 +1,11 @@
# eep_test.py MicroPython driver for Microchip EEPROM devices.
# eep_i2c.py MicroPython test program for Microchip I2C EEPROM devices.
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2019 Peter Hinch
import uos
from machine import I2C, Pin
from eeprom import EEPROM, T24C512
from eeprom_i2c import EEPROM, T24C512
# Return an EEPROM array. Adapt for platforms other than Pyboard or chips
# smaller than 64KiB.

Wyświetl plik

@ -1,4 +1,4 @@
# eeprom.py MicroPython driver for Microchip EEPROM devices.
# eeprom_i2c.py MicroPython driver for Microchip I2C EEPROM devices.
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2019 Peter Hinch
@ -28,6 +28,14 @@ class EEPROM():
self._buf1 = bytearray(1)
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
def scan(self, verbose, chip_size):
devices = self._i2c.scan() # All devices on I2C bus
@ -58,9 +66,10 @@ class EEPROM():
def __setitem__(self, addr, value):
if isinstance(addr, slice):
start, stop = self.do_slice(addr)
try:
if len(value) == (addr.stop - addr.start):
return self.readwrite(addr.start, value, False)
if len(value) == (stop - start):
return self.readwrite(start, value, False)
else:
raise RuntimeError('Slice must have same length as data')
except TypeError:
@ -72,8 +81,9 @@ class EEPROM():
def __getitem__(self, addr):
if isinstance(addr, slice):
buf = bytearray(addr.stop - addr.start)
return self.readwrite(addr.start, buf, True)
start, stop = self.do_slice(addr)
buf = bytearray(stop - start)
return self.readwrite(start, buf, True)
self._getaddr(addr, 1)
self._i2c.writeto(self._i2c_addr, self._addrbuf)
self._i2c.readfrom_into(self._i2c_addr, self._buf1)

214
spi/SPI.md 100644
Wyświetl plik

@ -0,0 +1,214 @@
# 1. A MicroPython SPI EEPROM driver
This driver supports the Microchip 25xx1024 series of 128KiB SPI EEPROMs.
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.
The driver has the following attributes:
1. It supports multiple EEPROM chips to configure a single array.
2. For performance, writes use page writes where possible.
3. Page access improves the speed of multi-byte reads.
4. It is cross-platform.
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.
# 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).
| EEPROM | PB | Signal |
|:-------:|:---:|:------:|
| 1 CS | Y5 | SS/ |
| 2 SO | Y7 | MISO |
| 3 WP/ | 3V3 | |
| 4 Vss | Gnd | |
| 5 SI | Y8 | MOSI |
| 6 SCK | Y6 | SCK |
| 7 HOLD/ | 3V3 | |
| 8 Vcc | 3V3 | |
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.
If you use a Pyboard D and power the EEPROMs 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. `eeprom_spi.py` Device driver.
2. `eep_spi.py` Test programs for above.
# 4. The device driver
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:
```python
import uos
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)
uos.VfsFat.mkfs(eep) # Omit this to mount an existing filesystem
vfs = uos.VfsFat(eep)
uos.mount(vfs,'/eeprom')
```
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. As a filesystem the limited size is an
issue, but a potential use case is for pickling Python objects for example to
achieve persistence when issuing `pyb.standby()`; also for holding a small
frequently updated persistent btree database.
The SPI bus must be instantiated using the `machine` module.
## 4.1 The EEPROM class
An `EEPROM` instance represents a logical EEPROM: this may consist of multiple
physical devices on a common SPI bus.
### 4.1.1 Constructor
This test each chip in the list of chip select pins - if a chip is detected on
each chip select line an EEPROM array is instantiated. A `RuntimeError` will be
raised if a device is not detected on a CS line.
Arguments:
1. `spi` Mandatory. An initialised SPI bus.
2. `cspins` A list or tuple of `Pin` instances. Each `Pin` must be initialised
as an output (`Pin.OUT`) and with `value=1`.
3. `verbose=True` If True, the constructor issues information on the EEPROM
devices it has detected.
SPI baudrate: The 25LC1024 supports baudrates of upto 20MHz. If this value is
specified the platform will produce the highest available frequency not
exceeding this figure.
### 4.1.2 Methods providing byte level access
The examples below assume two devices, one with `CS` connected to Pyboard pin
Y4 and the other with `CS` connected to Y5.
#### 4.1.2.1 `__getitem__` and `__setitem__`
These provides single byte or multi-byte access using slice notation. Example
of single byte access:
```python
from machine import SPI, Pin
from 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)
eep[2000] = 42
print(eep[2000]) # Return an integer
```
It is also possible to use slice notation to read or write multiple bytes. If
writing, the size of the slice must match the length of the buffer:
```python
from machine import SPI, Pin
from 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)
eep[2000:2002] = bytearray((42, 43))
print(eep[2000:2002]) # Returns a bytearray
```
Three argument slices are not supported: any third arg will be ignored. One
argument slices (`eep[:5]` or `eep[13100:]`) and negative args are supported.
#### 4.1.2.2 readwrite
This is a byte-level alternative to slice notation. It has the potential
advantage 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 Methods providing the block protocol
For the protocol definition see
[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)`
where `eep` is the `EEPROM` instance.
#### 4.1.4.2 scan
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
pin does not correspond to a valid chip.
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.
#### 4.1.4.3 erase
Erases the entire array.
# 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.
## 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.
## 5.2 full_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 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 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 EEPROM becoming
full) it is up to the caller to handle it. For example (assuming the EEPROM is
mounted on /eeprom):
```python
cp('/flash/main.py','/eeprom/')
```
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 EEPROM can be mounted.

74
spi/eep_spi.py 100644
Wyświetl plik

@ -0,0 +1,74 @@
# eep_spi.py MicroPython test program for Microchip SPI EEPROM devices.
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2019 Peter Hinch
import uos
from machine import SPI, Pin
from eeprom_spi import EEPROM
# Add extra pins if using multiple chips
cspins = (Pin(Pin.board.Y5, Pin.OUT, value=1),)
# Return an EEPROM array. Adapt for platforms other than Pyboard.
def get_eep():
if uos.uname().machine.split(' ')[0][:4] == 'PYBD':
Pin.board.EN_3V3.value(1)
eep = EEPROM(SPI(2, baudrate=20_000_000), cspins)
print('Instantiated EEPROM')
return eep
# Dumb file copy utility to help with managing EEPROM contents at the REPL.
def cp(source, dest):
if dest.endswith('/'): # minimal way to allow
dest = ''.join((dest, source.split('/')[-1])) # cp /sd/file /eeprom/
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
def test():
eep = get_eep()
sa = 1000
for v in range(256):
eep[sa + v] = v
for v in range(256):
if eep[sa + v] != v:
print('Fail at address {} data {} should be {}'.format(sa + v, eep[sa + v], v))
break
else:
print('Test of byte addressing passed')
data = uos.urandom(30)
sa = 2000
eep[sa:sa + 30] = data
if eep[sa:sa + 30] == data:
print('Test of slice readback passed')
def fstest(format=False):
eep = get_eep()
if format:
uos.VfsFat.mkfs(eep)
vfs=uos.VfsFat(eep)
try:
uos.mount(vfs,'/eeprom')
except OSError: # Already mounted
pass
print('Contents of "/": {}'.format(uos.listdir('/')))
print('Contents of "/eeprom": {}'.format(uos.listdir('/eeprom')))
print(uos.statvfs('/eeprom'))
def full_test():
eep = get_eep()
page = 0
for sa in range(0, len(eep), 256):
data = uos.urandom(256)
eep[sa:sa + 256] = data
got = eep[sa:sa + 256]
if got == data:
print('Page {} passed'.format(page))
else:
print('Page {} readback failed.'.format(page))
break
page += 1

185
spi/eeprom_spi.py 100644
Wyświetl plik

@ -0,0 +1,185 @@
# eeprom_spi.py MicroPython driver for Microchip SPI EEPROM devices,
# currently only 25xx1024.
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2019 Peter Hinch
import time
from micropython import const
_SIZE = const(131072) # Chip size 128KiB
# Supported instruction set
_READ = const(3)
_WRITE = const(2)
_WREN = const(6)
_WRDI = const(4)
_RDSR = const(5)
_WRSR = const(1)
_RDID = const(0xab)
_CE = const(0xc7)
# Logical EEPROM device consisting of an arbitrary number of physical chips
# sharing an SPI bus.
class EEPROM():
def __init__(self, spi, cspins, verbose=True):
self._spi = spi
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._bufp = bytearray(5) # instruction + 3 byte address + 1 byte value
self._mvp = memoryview(self._bufp) # cost-free slicing
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
def scan(self, verbose):
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])
cs(1)
if mvp[4] != 0x29:
raise RuntimeError('EEPROM not found at cs[{}].'.format(n))
if verbose:
s = '{} chips detected. Total EEPROM size {}bytes.'
print(s.format(n + 1, self._a_bytes))
def erase(self):
mvp = self._mvp
for cs in self._cspins: # For each chip
mvp[0] = _WREN
cs(0)
self._spi.write(mvp[:1]) # Enable write
cs(1)
mvp[0] = _CE
cs(0)
self._spi.write(mvp[:1]) # Start erase
cs(1)
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
mvp = self._mvp
cs = self._ccs # Chip is already current
while True:
mvp[0] = _RDSR
cs(0)
self._spi.write_readinto(mvp[:2], mvp[:2])
cs(1)
assert not mvp[1] & 0xC # BP0, BP1 assumed 0
if not (mvp[1] & 1):
break
time.sleep_ms(1)
def __setitem__(self, addr, value):
if isinstance(addr, slice): # value is a buffer
start, stop = self.do_slice(addr)
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[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):
start, stop = self.do_slice(addr)
buf = bytearray(stop - start)
return self.readwrite(start, buf, True)
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):
if addr >= self._a_bytes:
raise RuntimeError("EEPROM Address is out of range")
ca, la = divmod(addr, self._c_bytes) # ca == chip no, la == offset into chip
self._ccs = self._cspins[ca] # Current chip select
mvp = self._mvp
mvp[1] = la >> 16
mvp[2] = (la >> 8) & 0xff
mvp[3] = la & 0xff
pe = (addr & ~0xff) + 0x100 # byte 0 of next page
return min(nbytes, pe - la)
# Read or write multiple bytes at an arbitrary address
def readwrite(self, addr, buf, read):
nbytes = len(buf)
mvb = memoryview(buf)
mvp = self._mvp
start = 0
while nbytes > 0:
npage = self._getaddr(addr, nbytes) # No. of bytes in current page
cs = self._ccs
assert npage > 0
if read:
mvp[0] = _READ
cs(0)
self._spi.write(mvp[:4])
self._spi.readinto(mvb[start : start + npage])
cs(1)
else:
mvp[0] = _WREN
cs(0)
self._spi.write(mvp[:1])
cs(1)
mvp[0] = _WRITE
cs(0)
self._spi.write(mvp[:4])
self._spi.write(mvb[start: start + npage])
cs(1) # Trigger write start
self._wait_rdy() # Wait until done (6ms max)
nbytes -= npage
start += npage
addr += npage
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