Saphum

Non-Responsive Flash Memory

October 14, 2008 | 9 Minute Read

The M25P10-A serial flash memory I use in the Ifos project seemed non-responsive when installed in the actual device—at least, my test program was consistently failing. Something was wonky, though, because not even debugging output was reaching the console.

I eventually realized that the test program was originally written without provision for a high-Z RS-485 tranceiver, so it never bothered to enable the transmitter. With that oversight corrected and the debugging output flowing again, the memory itself continued to stonewall every command. The o-scope showed Chip Select, MOSI, and SCK all as they should be, when they should be, with level translation (through the 74HC4050) working fine at speed.

Finally, after re-reading the datasheet and double-checking the Ifos schematic, I discovered the memory’s HOLD line was tied to VSS instead of VCC. Duh. That line is used to inhibit all serial communication with the memory, and it was working exactly as designed.

A little PCB surgery fixed the problem. Memory works great using a very simple driver:

;; ---------------------------------------------------------------------------
;;
;;  PIC Framework
;;
;;  Copyright © 2006-8  Peter Heinrich
;;  All Rights Reserved
;;
;;  $URL: svn://saphum.com/PIC/framework/trunk/m25p.asm $
;;  $Revision: 353 $
;;
;;  Provides a basic wrapper to control the M25P-type serial flash memories.
;;  This is a low-voltage 1-Mbit memory that supports SPI up to 50 MHz.
;;
;;  Due to the nature of flash memory, writes can only change bits from 1 to
;;  0, not 0 to 1.  This means a cell holding 0xff may be reprogrammed to any
;;  value, but one holding 0xa2 (for example) may never change to, say, 0xf6.
;;  Standard operating procedure with flash memory, therefore, is to "erase"
;;  cells to 0xff before storing values in them.  Unfortunately, this isn't
;;  possible on individual locations, but must be done at the sector level,
;;  or for the whole chip at once.  In addition, this procedure is usually
;;  very slow (~1s/sector or ~3s/chip for the M25P10-A), although reads are
;;  very fast and write speed is acceptable.
;;
;;  Like other NOR-based EEPROMs, the M25P10-A supports about ~100k erase/
;;  program cycles per sector.  This makes it a good candidate for static
;;  data or code, but repeatedly changing data risks data loss due to chip
;;  degradation and failure.  This may be mitigated with "wear leveling," but
;;  that is beyond the scope of this simple wrapper.
;;
;; ---------------------------------------------------------------------------
;;  $Author: Peter $
;;  $Date: 2008-08-12 22:45:35 -0700 (Tue, 12 Aug 2008) $
;; ---------------------------------------------------------------------------
 
 
 
   #include "private.inc"
 
   ; Public Methods
   global   M25P.disableWrites
   global   M25P.enableWrites
   global   M25P.eraseAll
   global   M25P.eraseSector
   global   M25P.getId
   global   M25P.getStatus
   global   M25P.powerDown
   global   M25P.powerUp
   global   M25P.readByte
   global   M25P.readBytes
   global   M25P.setStatus
   global   M25P.writeByte
   global   M25P.writeBytes
 
   ; Dependencies
   extern   SPI.io
   extern   Util.Frame
 
 
 
;; ---------------------------------------------------------------------------
.m25p                 code
;; ---------------------------------------------------------------------------
 
;; ----------------------------------------------
;;  void M25P.disableWrites()
;;
;;  Clears the Write Enable Latch bit of the status register, prohibiting
;;  subsequent operations that write to the device.
;;
M25P.disableWrites:
   movlw    0x04
   rcall    beginCommand
   bra      endCommand
 
 
 
;; ----------------------------------------------
;;  void M25P.enableWrites()
;;
;;  Sets the Write Enable Latch bit of the status register, enabling write
;;  operations on the device.
;;
M25P.enableWrites:
   movlw    0x06
   rcall    beginCommand
   bra      endCommand
 
 
 
;; ----------------------------------------------
;;  void M25P.eraseAll()
;;
;;  Resets all memory locations to 0xff, unless one or both Block Protect bits
;;  (BP1, BP0) are set.  In that case, this method does nothing.
;;
;;  This procedure is inherently slow, and may take up to 6 seconds(!) to
;;  complete.  This methods blocks until the Write In Progress (WIP) bit is
;;  reset to 0.
;;
M25P.eraseAll:
   movlw    0xc7
   rcall    beginCommand
   bra      endCommandConfirmWrite
 
 
 
;; ----------------------------------------------
;;  void M25P.eraseSector( frame[0..2] address )
;;
;;  Sets all bits in the specified sector to 1.  Any address in the sector may
;;  be used to indicate which one is to be cleared.  M25P.enableWrites() must
;;  be called prior to this method.
;;
;;  Note that this is a slow operation, taking up to 3 seconds.  This method
;;  blocks until the write completes.
;;
M25P.eraseSector:
   movlw    0xd8
   rcall    beginCommandAddress
   bra      endCommandConfirmWrite
 
 
 
;; ----------------------------------------------
;;  frame[0], frame[1..2] M25P.getId()
;;
;;  Returns the 1-byte JEDEC manufacturer id (0x20 for STMicroelectronics) and
;;  the 2-byte device identification, which includes the memory type in the
;;  first byte and memory capacity in the second (0x20 and 0x11, respectively,
;;  for the M25P10-A).
;;
;;  Note that this method returns 0 for all values unless the device has the
;;  "X" process technology code.  See M25P.powerUp() for an alternative
;;  identification technique.
;;
M25P.getId:
   movlw    0x9f
   rcall    beginCommand
 
   ; Shift out the identification info.
   call     SPI.io
   movwf    Util.Frame              ; JEDEC manufacturer id
   call     SPI.io
   movwf    Util.Frame + 1          ; memory type
   call     SPI.io
   movwf    Util.Frame + 2          ; memory capacity
 
   bra      endCommand
 
 
 
;; ----------------------------------------------
;;  WREG M25P.getStatus()
;;
;;  Returns the current status byte, whose bits are organized as follows:
;;
;;    X------- SRWD     ; Status Register Write Protect
;;    -000----          ; [unused, always read zero]
;;    ----X--- BP1      ; Block Protect 1
;;    -----X-- BP0      ; Block Protect 0
;;    ------X- WEL      ; Write Enable Latch
;;    -------X WIP      ; Write in Progress
;;
M25P.getStatus:
   movlw    0x05
   rcall    beginCommand
   call     SPI.io
   bra      endCommand
 
 
 
;; ----------------------------------------------
;;  void M25P.powerDown()
;;
;;  Enters the extreme low-power consumption mode of the chip, typically
;;  about 5µA.  When in this mode, the device will not respond to any other
;;  commands besides M25P.powerUp().
;;
M25P.powerDown:
   movlw    0xb9
   rcall    beginCommand
   bra      endCommand
 
 
 
;; ----------------------------------------------
;;  WREG M25P.powerUp()
;;
;;  Restores the chip to regular standby mode, drawing about 50µA.  If the
;;  device is in deep power-down mode, this method must be executed before
;;  other commands will be accepted.
;;
;;  This method returns the 1-byte electronic signature of the chip, which
;;  is 0x10 for the M25P10-A.  The device need not be in low-power mode to
;;  call this method, so the signature may be retrieved at any time.
;;
M25P.powerUp:
   movlw    0xab
   rcall    beginCommandAddress
   call     SPI.io
   bra      endCommand
 
 
 
;; ----------------------------------------------
;;  WREG M25P.readByte( frame[0..2] address )
;;
;;  Returns the 8-bit value stored at the memory address specified.
;;
M25P.readByte:
   movlw    0x03
   rcall    beginCommandAddress
   call     SPI.io
   bra      endCommand
 
 
 
;; ----------------------------------------------
;;  void M25P.readBytes( frame[0..2] address, frame[3] count, FSR0 buffer )
;;
;;  Reads up to a page of memory (256 bytes) and copies the data sequentially
;;  to the memory block whose base address is stored in FSR0.  The first
;;  address to be read doesn't actually have to be at a page boundary.  If the
;;  range extends past the end of physical memory, retrieval will resume at
;;  location 0x00000000.  This differs from M25P.writeBytes(), which always
;;  works within the confines of a single page.
;;
;;  A count parameter of 0 indicates 256 bytes should be read.
;;
M25P.readBytes:
   movlw    0x03
   rcall    beginCommandAddress
 
rdBytes:
   ; Loop over the flash memory range requested.
   call     SPI.io                  ; shift out the next value
   movwf    POSTINC0                ; store the byte and advance pointer
   decfsz   Util.Frame + 3, F       ; count satisfied?
     bra    rdBytes                 ; no, go back for another byte
 
   bra      endCommand
 
 
 
;; ----------------------------------------------
;;  void M25P.setStatus( WREG status )
;;
M25P.setStatus:
   movwf    Util.Frame + 1
   movlw    0x01
   rcall    beginCommand
 
   movf     Util.Frame + 1, W
   call     SPI.io
   bra      endCommandConfirmWrite
 
 
 
;; ----------------------------------------------
;;  void M25P.writeByte( WREG value, frame[0..2] address )
;;
;;  Performs a logical AND of the working register and the memory address
;;  specified.  A call to M25P.enableWrites() must preceed this operation.  A
;;  write attempt to a page protected by the Block Protect bits will be ig-
;;  nored.
;;
;;  This method blocks until the write is complete (typically 1.4 to 5 ms).
;;
M25P.writeByte:
   movwf    Util.Frame + 3
   movlw    0x02
   rcall    beginCommandAddress
 
   movf     Util.Frame + 3, W
   call     SPI.io
   bra      endCommandConfirmWrite
 
 
 
;; ----------------------------------------------
;;  void M25P.writeBytes( frame[0..2] address, frame[3] count, FSR0 buffer )
;;
;;  Performs a logical AND of the data memory pointed to by FSR0 and the Flash
;;  memory starting at the address specified, up to the parameterized count (a
;;  count of 0 indicates 256 bytes should processed).  The Write Enable Latch
;;  must be set prior to this operation.
;;
;;  The write request may extend beyond the page boundary, but the write it-
;;  self never will.  Flash locations past the end of the page will be mapped
;;  to its beginning, resulting in a wrap-around write.  A write attempt to
;;  any page protected by the Block Protect bits will be ignored.
;;
;;  This method blocks until the write completes.
;;
M25P.writeBytes:
   movlw    0x02
   rcall    beginCommandAddress
 
wrBytes:
   ; Loop over the flash memory range requested.
   movf     POSTINC0, W             ; load the next value and advance pointer
   call     SPI.io                  ; shift in the next value
   decfsz   Util.Frame + 3, W       ; count satisfied?
     bra    wrBytes                 ; no, go back for another byte
 
   bra      endCommandConfirmWrite
 
 
 
;; ----------------------------------------------
;;  void beginCommand( WREG command )
;;
;;  Transmits the command byte, usually in advance of numeric parameters.
;;
beginCommand:
   ; Assert chip select and send command.
   bsf      PORTA, RA3              ; assumes CS/ is active-H via RA3
   goto     SPI.io
 
 
 
;; ----------------------------------------------
;;  void beginCommandAddress( WREG command, frame[0..2] address )
;;
;;  Transmits the command byte and 24-bit address specified, usually in advance
;;  of other numeric parameters.  The address should be in little-endian format.
;;
beginCommandAddress:
   ; Send the command.
   bsf      PORTA, RA3              ; assumes CS/ is active-H via RA3
   call     SPI.io
 
   ; Send the memory address in network byte order (big-endian).
   movf     Util.Frame + 2, W       ; upper byte
   call     SPI.io
   movf     Util.Frame + 1, W       ; high byte
   call     SPI.io
   movf     Util.Frame + 0, W       ; low byte
   call     SPI.io
 
   return
 
 
 
;; ----------------------------------------------
;;  void endCommand()
;;
;;  Terminates the current command by de-asserting the chip select line.
;;
endCommand:
   ; De-assert chip select.
   bcf      PORTA, RA3              ; assumes CS/ is active-H via RA3
   return
 
 
 
;; ----------------------------------------------
;;  void endCommandConfirmWrite()
;;
;;  Terminates the current command and blocks until the Write In Progress (WIP)
;;  flag is clear.
;;
endCommandConfirmWrite:
   bcf      PORTA, RA3              ; assume CS/ is active-H via RA3
 
   movlw    0x05
   rcall    beginCommand
 
waitChk:
   ; Check the WIP status bit.
   call     SPI.io                  ; request status register
   btfsc    WREG, 0                 ; is WIP clear?
     bra    waitChk                 ; no, keep waiting
 
   ; Write is complete, so de-assert the chip select line.
   bra      endCommand
 
 
 
   end