Shop OBEX P1 Docs P2 Docs Learn Events
Simple SPI Flash program — Parallax Forums

Simple SPI Flash program

I'm trying to learn to use a SPI Flash device (Winbond 25Q32FVSIG) and simply write and read a few addresses to get me started. I'm sure Mike Green's Winbond objects are high class but they are well beyond my abilities at present.

So I was trying to use SPI_SPIN to make a simple R/W program. The demo for that object in the Demo Library is for a temperature sensor and quite a bit different that the flash.

Is this possible?

Is there a simple example to get me started?

Your recommendations and help are greatly appreciated!

Thanks
Aaron

Comments

  • Am I correct in assuming that what one data sheet calls Modes 0-3 are the same as
    Modes A-D?

    In the SPI_SPIN there are also Modes 4 & 5. How do these relate?
  • One thing you might want to check out is the "Unleashing Propeller C3" guide that you can download from Parallax. It's for the C3. And, the C3's SPI architecture is a little different than others but the actual SPI code might help. There are examples for SPI RAM, A/D, Flash and SD.

  • Thanks, that looks like some good info to pour over
  • AribaAriba Posts: 2,690
    You need MSB first for Flash chips, and likely PreClock for Reads = Mode 5 and 0.

    Here is a little demo code (untested) that should read the device ID of the Flash:
    CON
      _clkmode  = xtal1 + pll16x
      _xinfreq  = 5_000_000
    
      MOSI = 0   'Flash Pins (Master=Prop, Slave=FlashChip)
      MISO = 1
      SCLK = 2
      CS   = 3
      
    OBJ
      spi :  "SPI_Spin"
      dbg :  "FullDuplexSerial"
          
    PUB Main : i
      outa[CS] := 1                             'ChipSel High
      dira[CS] := 1
      dbg.start(31,30,0,115200)
      spi.start(1,0)                            'as fast as possible, clk=Low
    
      repeat
        dbg.str(string(13,"Device ID: "))
        outa[CS] := 0
        spi.shiftout(MOSI,SCLK,5,8, $9F)        'Command: READ JEDEC ID
        repeat 3
          i := spi.shiftin(MISO,SCLK,0,8)       'get Manufactor, MemoryType, Capacity
          dbg.tx("$")
          dbg.hex(i,2)
          dbg.tx(" ")
        outa[CS] := 1
        waitcnt(clkfreq*2 + cnt)
    
    See the datasheet of the Flash chip for the values you should get. For the Winbond 25Q32 I think it is: $EF,$40,$16.

    Andy
  • Thanks Andy
    Got that to work but not with less than CLOCKDELAY of 7

    Aaron
  • Here's my attempt at writing/reading. It doesn't work so I would be grateful if you could steer me correct.
    Thanks
    Aaron
    {{Trying to save a value to flash and then after 2 seconds read it back from flash
      So far it doesn't work       Using winbond 25Q32
      }}
    CON
      _clkmode  = xtal1 + pll16x
      _xinfreq  = 5_000_000
    
    
      '     prop pin     conected to Flash pin
      '      ↓                          ↓
      MOSI = 6                      '   5
      MISO = 7                      '   2
      CLK  = 8                      '   6
      CS   = 9                      '   1
    
      WrStat   = $01_00                  ' Write Status / Data
      WrData   = $02_000000              ' Write Data / Address / Data
      WrSRAM   = $02_0000                ' Write SRAM / Address / Data
      RdData   = $03_000000              ' Read Data / Address / (Data)
      RdSRAM   = $03_0000                ' Read SRAM / Address / (Data)
      Status   = $05                     ' Read Status / (Data)
      WrtEna   = $06                     ' Write enable
      
    OBJ
      spi :  "SPI_Spin"
      tv  :  "tv_text"
    
    VAR
      long address  'address to be written/read
      long rcvbuf   'place to store incoming data from flash
      long savdata  'value to send and store in flash (buffer in prop)
          
    PUB Main : i
      address:=$030201    'I think in block 4
      savdata:=10         'data to store
      outa[CS] := 1                             'ChipSel High
      dira[CS] := 1
      
      tv.start(0)
      spi.start(15,0)      'ClockDelay, clk=Low (don't work at less than 7 CLOCKDELAY)
      waitcnt(clkfreq*2 + cnt)
    
              'write data to flash
      outa[CS] := 0
      spi.shiftout(MOSI,Clk,5,8,WrtEna) '(MOSI, Clk, Mode, Bits, Value)  'Command:wrtena
      outa[cs]~~
      
      outa[cs]~    'send address to write at    '(MOSI, Clk, Mode, Bits, Value)
      spi.shiftout(MOSI,Clk,5,8,WrData)   'Command:wrtpage
      spi.shiftout(MOSI,Clk,5,8,address>>16)     'send address MSBFIRST
      spi.shiftout(MOSI,Clk,5,8,address>>8) 
      spi.shiftout(MOSI,Clk,5,8,address>>0) 
      spi.shiftout(MOSI,Clk,5,8,savdata)         'send data
      outa[cs]~~
    
      tv.str(string("data written"))
      waitcnt(clkfreq*2+cnt)        'wait 3 seconds, then read and display data
    
      outa[cs]~                         '(Dpin, Cpin, Mode, Bits, Value)
      spi.shiftout(MOSI,clk,5,8,RdData)   'command: read  
      outa[cs]~~
      
      outa[cs]~      'send address to read
      spi.shiftout(MOSI,Clk,5,8, address>>16) 
      spi.shiftout(MOSI,Clk,5,8, address>>8) 
      spi.shiftout(MOSI,Clk,5,8, address>>0)
      outa[cs]~~
      
      rcvbuf:=0   'clear receive buffer
      
      outa[cs]~  
      rcvbuf:= spi.shiftin(MISO,Clk,0,8)   '(Dpin, Cpin, Mode, Bits)      
      outa[CS]~~
    
      tv.move(3,1)          'move to line 3  (My TV_TEXT mod)
      tv.dec(rcvbuf)
    

  • AribaAriba Posts: 2,690
    Here is what I see after a brief view:

    You define the WrData Command in the highest byte, but send only the lowest byte as the command (which is 00).
    You can send the command and the address in one spi call, then it makes even sense how you have defined the commands:
        outa[cs]~
        spi.shiftout(MOSI,Clk,5,32,WrData | address)
        spi.shiftout(MOSI,Clk,5,8,savdata)
        spi.shiftout(MOSI,Clk,5,8,savdata)   'more bytes to write here, with CS still low
        outa[cs]~~
    
    If you make a page write, you must not deselect the CS until all bytes are written.

    For the Read:
        outa[cs]~
        spi.shiftout(MOSI,Clk,5,32,RdData | address)
        rcvbuf := spi.shiftin(MISO,Clk,5,8)
        outa[cs]~~
    
    As you have it now, you deselect CS after the read command and the address, this can not work. CS must go low before the command, and high after the end of the read(s).


    Hope this helps

    Andy
  • So does this line include the complete address?
    spi.shiftout(MOSI,Clk,5,32,RdData | address)
    
  • AribaAriba Posts: 2,690
    edited 2015-11-08 08:29
    AGCB wrote: »
    So does this line include the complete address?
    spi.shiftout(MOSI,Clk,5,32,RdData | address)
    

    Yes, I think so.
    RdData | address builds a 32bit value with the command in the highest byte and the address in the lower 3 bytes:
    RdData:  $03_000000
    address: $00_aaaaaa   '24bit address
    long:    $03_aaaaaa
    byte num   3  2 1 0   'shifted out MSB first, so byte 3 first
    
    and then the whole 32bit value is shifted out, which is the same as if you shift 4 times a 8 bit value.

    Andy
  • I've modified my program w/ suggested changes and dozens of other tries and still get TV output of $000000. I'm at a loss as to what to try next. Here's the current code
    {{Trying to save a value to flash and then after 1 second read it back from flash
      So far it doesn't work       Using winbond 25Q32
      }}
    CON
      _clkmode  = xtal1 + pll16x
      _xinfreq  = 5_000_000
    
    
      '     prop pin     conected to Flash pin
      '      ↓                          ↓
      MOSI = 6                      '   5
      MISO = 7                      '   2
      CLK  = 8                      '   6
      CS   = 9                      '   1
    
      WrStat   = $01_00                  ' Write Status / Data
      WrData   = $02_000000              ' Write Data / Address / Data  
      RdData   = $03_000000              ' Read Data / Address / (Data)  
      Status   = $05                     ' Read Status / (Data)
      WrtEna   = $06                     ' Write enable
      
    OBJ
      spi :  "SPI_Spin"
      tv  :  "tv_text"
    
    VAR
      long address  'address to be written/read
      long rcvbuf[3]   'place to store incoming data from flash
      long savdata  'value to send and store in flash (buffer in prop)
          
    PUB Main 
      address:=$000201 '%00000000_00000010_00000001   'I think in block 1, sector 1
      savdata:=10         'data to store
      outa[CS] := 1                             'ChipSel High
      dira[CS] := 1
      
      tv.start(0)
      spi.start(15,0)      'ClockDelay, clk=Low (don't work at less than 7 CLOCKDELAY)
      waitcnt(clkfreq + cnt)
    
      outa[CS]~
      spi.shiftout(MOSI,Clk,5,8,WrtEna) '(MOSI, Clk, Mode, Bits, Value)  'Command:wrtena
      outa[cs]~~
      waitcnt(clkfreq/10+cnt)
    
      outa[cs]~
      spi.shiftout(MOSI,Clk,5,8,$c7) 'chip erase
      outa[cs]~~
      waitcnt(clkfreq/10+cnt)
       
    
              'write data to flash
      outa[CS]~
      spi.shiftout(MOSI,Clk,5,8,WrtEna) '(MOSI, Clk, Mode, Bits, Value)  'Command:wrtena
      outa[cs]~~
      waitcnt(clkfreq/10+cnt)
    
      outa[cs]~
      spi.shiftout(MOSI,Clk,5,32,WrData | address)  '8 command bits + 24 address bits = 32    
      spi.shiftout(MOSI,Clk,5,8,savdata>>16)
      spi.shiftout(MOSI,Clk,5,8,savdata>>8)   
      spi.shiftout(MOSI,Clk,5,8,savdata>>0) 
      outa[cs]~~
      waitcnt(clkfreq/10+cnt)
       
      
    
      tv.str(string("data written"))
      waitcnt(clkfreq+cnt)        'wait 1 second, then read and display data
    
     
      rcvbuf:=0   'clear receive buffer
    
      outa[cs]~
      spi.shiftout(MOSI,Clk,5,32,RdData | address)  
      rcvbuf[2] := spi.shiftin(MISO,Clk,0,8)    
      rcvbuf[1] := spi.shiftin(MISO,Clk,0,8)
      rcvbuf[0] := spi.shiftin(MISO,Clk,0,8)   
      outa[cs]~~
    
      tv.move(3,1)          'move to line 3  (My TV_TEXT mod)
      tv.str(string("$"))
      tv.hex(rcvbuf[2],2)
      tv.hex(rcvbuf[1],2)
      tv.hex(rcvbuf[0],2)
      
      
    

  • One thing about a memory program like this is that it ALL has to be perfect or nothing works. Other gadgets that I have learned to use sometimes don't do quite what you want but three's some indication that something is working, maybe just not as you expect.

    I thought of splitting it into 2 programs, Write and read. But that does not change anything. It all still has to be perfect or nothing.

    I probably would have been very happy w/ Mike Green's "Winbond Driver" but I could not find a demo of it and trying to use it myself proved beyond my abilities. If you have used that object for a project, I would be grateful if you would share some of your code.

    The demo for SPI in the library only uses a temperature sensor and has nothing to do w/ addresses or multi byte reads or writes.

    I have also spent much time searching the Obex and the web for examples/code.

    Thanks
    Aaron
  • Peter JakackiPeter Jakacki Posts: 10,193
    edited 2015-11-09 00:10
    I wrote an interface for this chip in Forth (for the P2 actually), took me less than an hour and I can issue all the commands, read and write bytes/words/longs or blocks and even dump from it in various formats. But then again, Forth is interactive so I could test and debug my basic methods before I built on them.
  • This is probably a really good place for a juicy advertisement for Forth
  • AribaAriba Posts: 2,690
    edited 2015-11-09 10:13
    Finally I put a flash chip (25Q80) in the breadboard of my Activityboard to try the following code. It's based on the code you posted, but I changed the SPI methode.
    Using the SPI_Spin object is so horrible inefficient that I wrote my own little SPI function, that does shift-in and shift-out at the same time.

    It shows the flash-ID, erases a sector of the flash, writes some data and reads them back - all works as intended.
    Have you connected two pullup resistors at the Hold and the WE pin of the flash?

    Here is the code:
    CON
      _clkmode  = xtal1 + pll16x
      _xinfreq  = 5_000_000
    
      '     prop pin     conected to Flash pin
      '      ↓                          ↓
      MOSI = 6                      '   5
      MISO = 7                      '   2
      CLK  = 8                      '   6
      CS   = 9                      '   1
    
      WrStat   = $01_00                  ' Write Status / Data
      WrData   = $02_000000              ' Write Data / Address / Data  
      RdData   = $03_000000              ' Read Data / Address / (Data)  
      Status   = $05                     ' Read Status / (Data)
      WrtEna   = $06                     ' Write enable
      SeErase  = $20_000000              ' Sector Erase
      
    OBJ
      tv  :  "tv_text"
    
    VAR
      long address  'address to be written/read
      long rcvbuf[3]   'place to store incoming data from flash
      long savdata  'value to send and store in flash (buffer in prop)
          
    PUB Main 
      address := $10000             'sector 1,  addr 0
      savdata := $123456
    
      outa[CS] := 1                 'ChipSel High
      dira[CS] := 1
      dira[CLK] := 1
      dira[MOSI] := 1
      
      tv.start(0)
      waitcnt(clkfreq + cnt)
    
      tv.str(string(1,"Flash Test"))
      tv.str(string(13,"JEDEC ID: $"))
      outa[CS]~
      spi($9F,8)
      tv.hex(spi(0,8),2)
      tv.hex(spi(0,8),2)
      tv.hex(spi(0,8),2)
      outa[CS]~~
        
      'erase sector
      outa[CS]~
      spi(WrtEna,8)                 'Command:wrtena
      outa[CS]~~
    
      outa[cs]~
      spi(SeErase | address, 32)   'sector erase
      outa[cs]~~
      waitcnt(clkfreq/10+cnt)
    
    
      'write data to flash
      outa[CS]~
      spi(WrtEna,8)                 'Command:wrtena
      outa[CS]~~
    
      outa[CS]~
      spi(WrData | address, 32)     '8 command bits + 24 address bits = 32    
      spi(savdata, 24)
      outa[CS]~~
      waitcnt(clkfreq/10+cnt)
    
      tv.str(string(13,"data written"))
      waitcnt(clkfreq+cnt)          'wait 1 second, then read and display data
    
     
      'read data from flash
      address := $10000             'sector 1,  addr 0
    
      outa[CS]~
      spi(RdData | address, 32)  
      rcvbuf[0] := spi(0,8)    
      rcvbuf[1] := spi(0,8)    
      rcvbuf[2] := spi(0,8)    
      outa[CS]~~
    
      tv.str(string(13,"Readback: $"))
      tv.hex(rcvbuf[0],2)
      tv.hex(rcvbuf[1],2)
      tv.hex(rcvbuf[2],2)
    
      waitcnt(clkfreq+cnt)
    
    
    PRI spi(outdata, bits) : indata
      outdata <<= (32-bits)
      repeat bits
        outdata <-= 1
        outa[MOSI] := outdata
        outa[CLK]~~
        indata := indata<<1 + ina[MISO]
        outa[CLK]~
    
    

    Andy
  • Have you connected two pullup resistors at the Hold and the WE pin of the flash?
    Yes and your code works wonderfully.

    I was about to give up and use a SD card which I have done before but now I will pursue this flash learning theme some more.

    Thanks Andy
    Aaron
  • Andy, I saved your code and renamed it "Aribas code". I'm trying to learn SPI also. I can't tell how data is clocked in by looking at your object yet but I'll study it until I do. Thanks.
  • AribaAriba Posts: 2,690
    lardom wrote: »
    Andy, I saved your code and renamed it "Aribas code". I'm trying to learn SPI also. I can't tell how data is clocked in by looking at your object yet but I'll study it until I do. Thanks.

    The SPI subroutine (methode) shifts up to 32 bit out at the MOSI pin and produces a clock High-Low for every shifted bit. If you shift a variable to the left, you get a gap on the right. Normally this gap is filled with a Zero, but here I fill it with the level at the MISO pin, so the subroutine can be used to shift out and shift in at the same time (a full duplex spi).
    To read a byte you just send dummy data out, I used Zero: spi(0,8). This sends 8 zeroes at MOSI and produces 8 clocks at CLK, and shifts in 8 times the state from MISO.
    If you only need to write (shift out) then you just ignore the shifted in data.

    Hope this helps
    Andy
  • Ariba wrote: »
    The SPI subroutine (methode) shifts up to 32 bit out at the MOSI pin and produces a clock High-Low for every shifted bit. If you shift a variable to the left, you get a gap on the right. Normally this gap is filled with a Zero, but here I fill it with the level at the MISO pin, so the subroutine can be used to shift out and shift in at the same time (a full duplex spi).
    To read a byte you just send dummy data out, I used Zero: spi(0,8). This sends 8 zeroes at MOSI and produces 8 clocks at CLK, and shifts in 8 times the state from MISO.
    If you only need to write (shift out) then you just ignore the shifted in data.

    Hope this helps
    Andy

    I don't understand it the way you do because SPI is a new concept to me. One thing I noticed was there was no baud rate in the CLK. This makes sense because the CLK will not go low again until the bit is shifted out. I'm stuck on the 'rotate left' operator "<-" What does that do?
    I have to learn SPI to operate a pair of nrf24L01 modules which are an inexpensive substitute for Xbee. ( Very little code can be found for the Propeller. I have to study datasheets.)
    I think baud rate will be necessary to synchronize the transmitter to the reciever because it's wireless. The nrf24L01 is a duplex tx/rx transceiver but it will take some time before I can take advantage of that capability.
  • AribaAriba Posts: 2,690
    edited 2015-11-10 17:46
    There are 2 objects for the NRF2401 in the obex:
    obex.parallax.com/object/430
    obex.parallax.com/object/432

    Andy
  • I wrote an interface for this chip in Forth (for the P2 actually), took me less than an hour and I can issue all the commands, read and write bytes/words/longs or blocks and even dump from it in various formats. But then again, Forth is interactive so I could test and debug my basic methods before I built on them.

    Where could inquiring forth minds find those neat spi flash WORDS?



  • Peter JakackiPeter Jakacki Posts: 10,193
    edited 2015-11-11 03:50
    D.P wrote: »
    Where could inquiring forth minds find those neat spi flash WORDS?

    They are in EXTEND-TF2.fth in the Tachyon Dropbox folder under P2 but just for convenience here it is. The SFWR and SFRD are coded in assembler but the source here includes the high-level version in the IFNDEF sections which works just fine.
    ( ********************************* SERIAL FLASH ******************************* )
    
    ( WINBOND 25Q80 )
    #P61	== sfcs
    #P60	== sfck
    #P59	== sfdi
    #P58	== sfdo
    
    pub SFCS  sfcs HIGH ;
    
    IFNDEF SFWR
    ( 7920 cycles for SDWD )
    pub SFWR ( 8b -- )
    	sfck LOW sfcs LOW
    	#24 SHL
    	8 FOR
    	  1 ROL DUP 1 AND IF sfdi HIGH ELSE sfdi LOW THEN
    	  sfck HIGH sfck LOW
    	NEXT
    	DROP
    	;
    }
    
    IFNDEF SFRD
    ( 5696 cycles )
    pub SFRD ( -- 8b )
    	sfck LOW sfcs LOW
    	0 8 FOR 2* sfdo PIN@ 1 AND + sfck HIGH sfck LOW NEXT
    	;
    }
    
    pub SFINS	SFCS SFWR ;
    pub SFWE	6 SFINS ;
    pub SFWD	4 SFINS ;
    
    --- Read multiple bytes and assemble them into a long
    pub SFRDS ( cnt -- long )	0 SWAP FOR 8 SHL SFRD + NEXT ;
    
    --- Read serial Flash serial number
    pub SFSID	$4B SFINS 8 SFRDS 4 SFRDS SFCS ;
    
    --- Read serial Flash Jedec ID
    pub SFJID	$9F SFINS SFRD SFCS ;
    
    pub SFADR ( sfaddr -- )	DUP 16 SHR SFWR DUP 8 SHR SFWR SFWR ;
    
    pub SFRDBLK ( sfaddr dst cnt -- )
    	3 SFINS ROT SFADR
    	ADO SFRD I C! LOOP
    	SFCS
    	;
    
    pub SFERSEC ( addr -- )	SFWE $20 SFINS SFADR SFCS ;
    pub SFER32K ( addr -- )	SFWE $52 SFINS SFADR SFCS ;
    pub SFER64K ( addr -- )	SFWE $D8 SFINS SFADR SFCS ;
    pub SFERCHIP 		SFWE $C7 SFINS SFCS ;
    
    pub SFWRPAGE ( src dst -- ) SFWE $02 SFINS SFADR #256 ADO I C@ SFWR LOOP SFCS ;
    pub SFWRBLK ( src dst cnt -- )  ROT SWAP ADO I OVER SFWRPAGE 3 ms SPINNER #256 + #256 +LOOP DROP ;
    
    pub BACKUP	0 SFER64K #1,000 ms 0 0 $1.0000 SFWRBLK ;
    
    pub SFC@	3 SFINS SFADR SFRD SFCS ;
    pub SFW@	3 SFINS SFADR SFRD SFRD 8 SHL OR SFCS ;
    
    pub SF@ ( addr -- long )		3 SFINS SFADR 0 4 FOR 8 SHL SFRD OR NEXT SFCS ;
    
    --- Select Serial Flash as memory for DUMP words
    pub SF	' SFC@ dmpvec W! ' SFW@ dmpvec 2+ W! ' SF@ dmpvec 4+ W! ;
    
    

  • Ariba wrote: »
    There are 2 objects for the NRF2401 in the obex:
    obex.parallax.com/object/430
    obex.parallax.com/object/432

    Andy

    This, for me, is a crash course but you've helped in a big way. My previous Spin projects were easy.
    I had to become a student of computer architecture. I had to understand hexadecimal addressing and I had to have a clearer understanding of memory addresses and registers.
    It finally occurred to me that in SPI, the data bit is interpreted when the clock is in a specific phase.
    I had 'struggled' with how a wireless connection differed from a hard wired connection. It took a while but the light is going on in my head.
    Thanks.
Sign In or Register to comment.