Non-Responsive Flash Memory
October 14, 2008 | 9 Minute Read
T he 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