kopia lustrzana https://github.com/peterhinch/micropython_eeprom
Add SPI support. Reorganise directories.
rodzic
43421c55e7
commit
992e35b994
245
README.md
245
README.md
|
@ -1,224 +1,43 @@
|
||||||
# 1. A MicroPython EEPROM driver
|
# MicroPython EEPROM drivers
|
||||||
|
|
||||||
This enables MicroPython to access Microchip EEPROM devices. Unlike flash
|
EEPROM is a form of nonvolatile random access storage.
|
||||||
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.
|
|
||||||
|
|
||||||
From one to eight chips may be used to construct a nonvolatile memory module
|
These drivers enable MicroPython to access Microchip EEPROM devices. There are
|
||||||
with sizes upto 512KiB. The driver allows the memory either to be mounted in
|
two variants, one for chips based on the I2C interface and a second for a 1MBit
|
||||||
the target filesystem as a disk device or to be addressed as an array of bytes.
|
SPI chip.
|
||||||
Where multiple chips are used, all must be the same size.
|
|
||||||
|
|
||||||
The work was inspired by [this one](https://github.com/dda/MicroPython.git).
|
Unlike flash memory, EEPROMs may be written on a byte addressable basis. Their
|
||||||
This was written some five years ago. The driver in this repo employs some of
|
endurance is specified as a million writes compared to the 10K typical of most
|
||||||
the subsequent improvements to MicroPython to achieve these advantages:
|
flash memory. In applications such as data logging the latter can be exceeded
|
||||||
1. It supports multiple EEPROM chips to configure a single array.
|
relatively rapidly. For extreme endurance ferroelectric RAM has almost infinite
|
||||||
2. Writes are up to 1000x faster by using ACK polling and page writes.
|
endurance but at higher cost per byte. See [this driver](https://github.com/peterhinch/micropython-fram).
|
||||||
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
|
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)
|
The drivers support creating multi-chip arrays. In the case of I2C chips, up to
|
||||||
as per the test program. To wire up a single EEPROM chip, connect to a Pyboard
|
eight devices may share the bus. In the case of SPI expansion has no absolute
|
||||||
as below. Pin numbers assume a PDIP package (8 pin plastic dual-in-line).
|
limit as each chip has its own chip select line.
|
||||||
|
|
||||||
| EEPROM | PB |
|
Devices or arrays of devices may be mounted as a filesystem or may be treated
|
||||||
|:------:|:---:|
|
as an array of bytes.
|
||||||
| 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
|
For I2C devices see [I2C.md](./i2c/I2C.md). For SPI see [SPI.md](./spi/SPI.md).
|
||||||
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 |
|
# Choice of interface
|
||||||
|:--------:|:---:|:---:|:---:|
|
|
||||||
| 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 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
|
SPI requires no pullups, but uses three pins plus one for each connected chip.
|
||||||
up to 10KΩ will suffice. The Pyboard 1.x has these on board. The Pyboard D has
|
It is much faster than I2C, but in the case of EEPROMs the benefit is only
|
||||||
them only on I2C(1). Even if boards have pullups, additional externalresistors
|
apparent on reads: write speed is limited by the EEPROM device. In principle
|
||||||
will do no harm.
|
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
|
In the case of the Microchip devices supported, the SPI chip is larger at
|
||||||
to enable the voltage rail by issuing:
|
128KiB compared to a maximum of 64KiB in the I2C range.
|
||||||
```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.
|
|
||||||
|
|
|
@ -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.
|
|
@ -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.
|
# Released under the MIT License (MIT). See LICENSE.
|
||||||
# Copyright (c) 2019 Peter Hinch
|
# Copyright (c) 2019 Peter Hinch
|
||||||
|
|
||||||
import uos
|
import uos
|
||||||
from machine import I2C, Pin
|
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
|
# Return an EEPROM array. Adapt for platforms other than Pyboard or chips
|
||||||
# smaller than 64KiB.
|
# smaller than 64KiB.
|
|
@ -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.
|
# Released under the MIT License (MIT). See LICENSE.
|
||||||
# Copyright (c) 2019 Peter Hinch
|
# Copyright (c) 2019 Peter Hinch
|
||||||
|
@ -28,6 +28,14 @@ class EEPROM():
|
||||||
self._buf1 = bytearray(1)
|
self._buf1 = bytearray(1)
|
||||||
self._addrbuf = bytearray(2) # Memory offset into current chip
|
self._addrbuf = bytearray(2) # Memory offset into current chip
|
||||||
|
|
||||||
|
# Handle special cases of a slice. Always return a pair of positive indices.
|
||||||
|
def do_slice(self, addr):
|
||||||
|
start = addr.start if addr.start is not None else 0
|
||||||
|
stop = addr.stop if addr.stop is not None else self._a_bytes
|
||||||
|
start = start if start >= 0 else self._a_bytes + start
|
||||||
|
stop = stop if stop >= 0 else self._a_bytes + stop
|
||||||
|
return start, stop
|
||||||
|
|
||||||
# Check for a valid hardware configuration
|
# Check for a valid hardware configuration
|
||||||
def scan(self, verbose, chip_size):
|
def scan(self, verbose, chip_size):
|
||||||
devices = self._i2c.scan() # All devices on I2C bus
|
devices = self._i2c.scan() # All devices on I2C bus
|
||||||
|
@ -58,9 +66,10 @@ class EEPROM():
|
||||||
|
|
||||||
def __setitem__(self, addr, value):
|
def __setitem__(self, addr, value):
|
||||||
if isinstance(addr, slice):
|
if isinstance(addr, slice):
|
||||||
|
start, stop = self.do_slice(addr)
|
||||||
try:
|
try:
|
||||||
if len(value) == (addr.stop - addr.start):
|
if len(value) == (stop - start):
|
||||||
return self.readwrite(addr.start, value, False)
|
return self.readwrite(start, value, False)
|
||||||
else:
|
else:
|
||||||
raise RuntimeError('Slice must have same length as data')
|
raise RuntimeError('Slice must have same length as data')
|
||||||
except TypeError:
|
except TypeError:
|
||||||
|
@ -72,8 +81,9 @@ class EEPROM():
|
||||||
|
|
||||||
def __getitem__(self, addr):
|
def __getitem__(self, addr):
|
||||||
if isinstance(addr, slice):
|
if isinstance(addr, slice):
|
||||||
buf = bytearray(addr.stop - addr.start)
|
start, stop = self.do_slice(addr)
|
||||||
return self.readwrite(addr.start, buf, True)
|
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)
|
|
@ -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.
|
|
@ -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
|
|
@ -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
|
Ładowanie…
Reference in New Issue