micropython_eeprom/flash/flash_spi.py

197 wiersze
6.7 KiB
Python

# flash_spi.py MicroPython driver for SPI NOR flash devices.
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2019-2020 Peter Hinch
import time
from micropython import const
from bdevice import FlashDevice
# Supported instruction set:
# 3 and 4 byte address commands
_READ = const(0)
_PP = const(1)
_SE = const(2)
_CMDS3BA = b"\x03\x02\x20"
_CMDS4BA = b"\x13\x12\x21"
# No address
_WREN = const(6) # Write enable
_RDSR1 = const(5) # Read status register 1
_RDID = const(0x9F) # Read manufacturer ID
_CE = const(0xC7) # Chip erase (takes minutes)
_SEC_SIZE = const(4096) # Flash sector size 0x1000
# Logical Flash device comprising one or more physical chips sharing an SPI bus.
class FLASH(FlashDevice):
def __init__(
self,
spi,
cspins,
size=None,
verbose=True,
sec_size=_SEC_SIZE,
block_size=9,
cmd5=None,
):
self._spi = spi
self._cspins = cspins
self._ccs = None # Chip select Pin object for current chip
self._bufp = bytearray(6) # instruction + 4 byte address + 1 byte value
self._mvp = memoryview(self._bufp) # cost-free slicing
self._page_size = 256 # Write uses 256 byte pages.
# Defensive code: application should have done the following.
# Pyboard D 3V3 output may just have been switched on.
for cs in cspins: # Deselect all chips
cs(1)
time.sleep_ms(1) # Meet Tpu 300μs
size = self.scan(verbose, size) # KiB
super().__init__(block_size, len(cspins), size * 1024, sec_size)
# Select the correct command set
if (cmd5 is None and size <= 4096) or (cmd5 == False):
self._cmds = _CMDS3BA
self._cmdlen = 4
else:
self._cmds = _CMDS4BA
self._cmdlen = 5
self.initialise() # Initially cache sector 0
# **** API SPECIAL METHODS ****
# Scan: return chip size in KiB as read from ID.
def scan(self, verbose, size):
mvp = self._mvp
for n, cs in enumerate(self._cspins):
mvp[:] = b"\0\0\0\0\0\0"
mvp[0] = _RDID
cs(0)
self._spi.write_readinto(mvp[:4], mvp[:4])
cs(1)
scansize = 1 << (mvp[3] - 10)
if size is None:
size = scansize # Save size of 1st chip
if size != scansize: # Mismatch passed size or 1st chip.
raise ValueError(
"Flash size mismatch: expected {}KiB, found {}KiB".format(
size, scansize
)
)
if verbose:
s = "{} chips detected. Total flash size {}MiB."
n += 1
print(s.format(n, (n * size) // 1024))
return size
# Chip erase. Can take minutes.
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
# **** INTERFACE FOR BASE CLASS ****
# Write cache to a sector starting at byte address addr
def flush(self, cache, addr): # cache is memoryview into buffer
self._sector_erase(addr)
mvp = self._mvp
nbytes = self.sec_size
ps = self._page_size
start = 0 # Current offset into cache buffer
while nbytes > 0:
# write one page at a time
self._getaddr(addr, 1)
cs = self._ccs # Current chip select from _getaddr
mvp[0] = _WREN
cs(0)
self._spi.write(mvp[:1]) # Enable write
cs(1)
mvp[0] = self._cmds[_PP]
cs(0)
self._spi.write(mvp[: self._cmdlen]) # Start write
self._spi.write(cache[start : start + ps])
cs(1)
self._wait_rdy() # Wait for write to complete
nbytes -= ps
start += ps
addr += ps
# Read from chip into a memoryview. Address range guaranteed not to be cached.
def rdchip(self, addr, mvb):
nbytes = len(mvb)
mvp = self._mvp
start = 0 # Offset into buf.
while nbytes > 0:
npage = self._getaddr(addr, nbytes) # No. of bytes in current chip
cs = self._ccs
mvp[0] = self._cmds[_READ]
cs(0)
self._spi.write(mvp[: self._cmdlen])
self._spi.readinto(mvb[start : start + npage])
cs(1)
nbytes -= npage
start += npage
addr += npage
# Read or write multiple bytes at an arbitrary address.
# **** Also part of API ****
def readwrite(self, addr, buf, read):
mvb = memoryview(buf)
self.read(addr, mvb) if read else self.write(addr, mvb)
return buf
# **** INTERNAL METHODS ****
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: # TODO read status register 2, raise OSError on nonzero.
mvp[0] = _RDSR1
cs(0)
self._spi.write_readinto(mvp[:2], mvp[:2])
cs(1)
if not (mvp[1] & 1):
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 chip.
def _getaddr(self, addr, nbytes):
if addr >= self._a_bytes:
raise RuntimeError("Flash 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
cmdlen = self._cmdlen
mvp = self._mvp[:cmdlen]
if cmdlen > 3:
mvp[-4] = la >> 24
mvp[-3] = la >> 16 & 0xFF
mvp[-2] = (la >> 8) & 0xFF
mvp[-1] = la & 0xFF
pe = (addr & -self._c_bytes) + self._c_bytes # Byte 0 of next chip
return min(nbytes, pe - la)
# Erase sector. Address is start byte address of sector. Optimisation: skip
# if sector is already erased.
def _sector_erase(self, addr):
if not self.is_empty(addr):
self._getaddr(addr, 1)
cs = self._ccs # Current chip select from _getaddr
mvp = self._mvp
mvp[0] = _WREN
cs(0)
self._spi.write(mvp[:1]) # Enable write
cs(1)
mvp[0] = self._cmds[_SE]
cs(0)
self._spi.write(mvp[: self._cmdlen]) # Start erase
cs(1)
self._wait_rdy() # Wait for erase to complete