2020-02-09 16:51:01 +00:00
|
|
|
# flash_spi.py MicroPython driver for SPI NOR flash devices.
|
2019-12-24 15:16:09 +00:00
|
|
|
|
|
|
|
# Released under the MIT License (MIT). See LICENSE.
|
2020-02-11 17:07:22 +00:00
|
|
|
# Copyright (c) 2019-2020 Peter Hinch
|
2019-12-24 15:16:09 +00:00
|
|
|
|
|
|
|
import time
|
|
|
|
from micropython import const
|
|
|
|
from bdevice import FlashDevice
|
|
|
|
|
|
|
|
# Supported instruction set:
|
2020-02-09 16:51:01 +00:00
|
|
|
# 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'
|
2019-12-24 15:16:09 +00:00
|
|
|
# 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):
|
|
|
|
|
2020-06-08 17:37:54 +00:00
|
|
|
def __init__(self, spi, cspins, size=None, verbose=True,
|
|
|
|
sec_size=_SEC_SIZE, block_size=9, cmdset=None):
|
2019-12-24 15:16:09 +00:00
|
|
|
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.
|
2020-02-11 17:07:22 +00:00
|
|
|
# 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
|
2020-02-11 12:16:02 +00:00
|
|
|
cs(1)
|
2020-02-11 17:07:22 +00:00
|
|
|
time.sleep_ms(1) # Meet Tpu 300μs
|
2020-02-09 16:51:01 +00:00
|
|
|
|
2020-02-11 17:07:22 +00:00
|
|
|
size = self.scan(verbose, size) # KiB
|
2020-02-09 16:51:01 +00:00
|
|
|
super().__init__(block_size, len(cspins), size * 1024, sec_size)
|
|
|
|
|
|
|
|
# Select the correct command set
|
2020-06-08 17:37:54 +00:00
|
|
|
if (cmdset is None and size <= 4096) or (cmdset == False):
|
2020-02-09 16:51:01 +00:00
|
|
|
self._cmds = _CMDS3BA
|
|
|
|
self._cmdlen = 4
|
|
|
|
else:
|
|
|
|
self._cmds = _CMDS4BA
|
|
|
|
self._cmdlen = 5
|
|
|
|
|
2019-12-24 15:16:09 +00:00
|
|
|
self.initialise() # Initially cache sector 0
|
|
|
|
|
|
|
|
# **** API SPECIAL METHODS ****
|
2020-02-11 17:07:22 +00:00
|
|
|
# Scan: return chip size in KiB as read from ID.
|
2020-02-09 16:51:01 +00:00
|
|
|
def scan(self, verbose, size):
|
2019-12-24 15:16:09 +00:00
|
|
|
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)
|
2020-02-09 16:51:01 +00:00
|
|
|
scansize = 1 << (mvp[3] - 10)
|
2020-02-11 17:07:22 +00:00
|
|
|
if size is None:
|
2020-02-13 09:09:07 +00:00
|
|
|
size = scansize # Save size of 1st chip
|
|
|
|
if size != scansize: # Mismatch passed size or 1st chip.
|
2020-02-09 16:51:01 +00:00
|
|
|
raise ValueError('Flash size mismatch: expected {}KiB, found {}KiB'.format(size, scansize))
|
2019-12-24 15:16:09 +00:00
|
|
|
if verbose:
|
|
|
|
s = '{} chips detected. Total flash size {}MiB.'
|
2020-02-09 16:51:01 +00:00
|
|
|
n += 1
|
|
|
|
print(s.format(n, (n * size) // 1024))
|
|
|
|
return size
|
2019-12-24 15:16:09 +00:00
|
|
|
|
|
|
|
# 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)
|
2020-02-09 16:51:01 +00:00
|
|
|
mvp[0] = self._cmds[_PP]
|
2019-12-24 15:16:09 +00:00
|
|
|
cs(0)
|
2020-02-09 16:51:01 +00:00
|
|
|
self._spi.write(mvp[:self._cmdlen]) # Start write
|
2019-12-24 15:16:09 +00:00
|
|
|
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
|
2020-02-09 16:51:01 +00:00
|
|
|
mvp[0] = self._cmds[_READ]
|
2019-12-24 15:16:09 +00:00
|
|
|
cs(0)
|
2020-02-09 16:51:01 +00:00
|
|
|
self._spi.write(mvp[:self._cmdlen])
|
2019-12-24 15:16:09 +00:00
|
|
|
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
|
2019-12-26 18:08:59 +00:00
|
|
|
while True: # TODO read status register 2, raise OSError on nonzero.
|
2019-12-24 15:16:09 +00:00
|
|
|
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
|
2020-02-13 14:01:02 +00:00
|
|
|
self._ccs = self._cspins[ca] # Current chip select
|
2020-02-09 16:51:01 +00:00
|
|
|
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
|
2019-12-24 15:16:09 +00:00
|
|
|
pe = (addr & -self._c_bytes) + self._c_bytes # Byte 0 of next chip
|
|
|
|
return min(nbytes, pe - la)
|
|
|
|
|
2019-12-30 09:51:10 +00:00
|
|
|
# Erase sector. Address is start byte address of sector. Optimisation: skip
|
|
|
|
# if sector is already erased.
|
2019-12-24 15:16:09 +00:00
|
|
|
def _sector_erase(self, addr):
|
2019-12-26 18:08:59 +00:00
|
|
|
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)
|
2020-02-09 16:51:01 +00:00
|
|
|
mvp[0] = self._cmds[_SE]
|
2019-12-26 18:08:59 +00:00
|
|
|
cs(0)
|
2020-02-09 16:51:01 +00:00
|
|
|
self._spi.write(mvp[:self._cmdlen]) # Start erase
|
2019-12-26 18:08:59 +00:00
|
|
|
cs(1)
|
|
|
|
self._wait_rdy() # Wait for erase to complete
|