micropython_eeprom/eeprom/spi/eeprom_spi.py

171 wiersze
5.7 KiB
Python

# eeprom_spi.py MicroPython driver for EEPROM chips (see README.md for
# tested devices).
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2019-2022 Peter Hinch
import time
from micropython import const
from bdevice import BlockDevice
# Supported instruction set - common to both chips:
_READ = const(3)
_WRITE = const(2)
_WREN = const(6) # Write enable
_RDSR = const(5) # Read status register
# Microchip only:
_RDID = const(0xAB) # Read chip ID
_CE = const(0xC7) # Chip erase
# STM only:
_RDID_STM = const(0x83) # Read ID page
_WRID_STM = const(0x82)
_STM_ID = const(0x30) # Arbitrary ID for STM chip
# Not implemented: Write disable and Write status register
# _WRDI = const(4)
# _WRSR = const(1)
# Logical EEPROM device comprising one or more physical chips sharing an SPI bus.
class EEPROM(BlockDevice):
def __init__(self, spi, cspins, size=128, verbose=True, block_size=9):
# args: virtual block size in bits, no. of chips, bytes in each chip
if size not in (64, 128, 256):
print("Warning: possible unsupported chip. Size:", size)
super().__init__(block_size, len(cspins), size * 1024)
self._stm = size == 256
self._spi = spi
self._cspins = cspins
self._ccs = None # Chip select Pin object for current chip
self._bufp = bytearray(5) # instruction + 3 byte address + 1 byte value
self._mvp = memoryview(self._bufp) # cost-free slicing
self.scan(verbose)
# Read ID block ID[0]
def _stm_rdid(self, n):
cs = self._cspins[n]
mvp = self._mvp
mvp[:] = b"\0\0\0\0\0"
mvp[0] = _RDID_STM
cs(0)
self._spi.write_readinto(mvp, mvp)
cs(1)
return mvp[4]
# Write a fixed value to ID[0]
def _stm_wrid(self, n):
cs = self._ccs
mvp = self._mvp
mvp[0] = _WREN
cs(0)
self._spi.write(mvp[:1]) # Enable write
cs(1)
mvp[:] = b"\0\0\0\0\0"
mvp[0] = _WRID_STM
mvp[4] = _STM_ID
cs(0)
self._spi.write(mvp)
cs(1)
self._wait_rdy()
# Check for valid hardware on each CS pin: use ID block
def _stm_scan(self):
for n, cs in enumerate(self._cspins):
self._ccs = cs
if self._stm_rdid(n) != _STM_ID:
self._stm_wrid(n)
if self._stm_rdid(n) != _STM_ID:
raise RuntimeError("M95M02 chip not found at cs[{}].".format(n))
return n
# Scan for Microchip devices: read manf ID
def _mc_scan(self):
mvp = self._mvp
for n, cs in enumerate(self._cspins):
mvp[:] = b"\0\0\0\0\0"
mvp[0] = _RDID
cs(0)
self._spi.write_readinto(mvp, mvp)
cs(1)
if mvp[4] != 0x29:
raise RuntimeError("25xx1024 chip not found at cs[{}].".format(n))
return n
# Check for a valid hardware configuration
def scan(self, verbose):
n = self._stm_scan() if self._stm else self._mc_scan()
if verbose:
s = "{} chips detected. Total EEPROM size {}bytes."
print(s.format(n + 1, self._a_bytes))
def erase(self):
if self._stm:
raise RuntimeError("Erase not available on STM chip")
mvp = self._mvp
for cs in self._cspins: # For each chip
mvp[0] = _WREN
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 _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)
if not mvp[1]: # We never set BP0 or BP1 so ready state is 0.
break
time.sleep_ms(1)
# 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 # Offset into buf.
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