Simple SPI Flash program
AGCB
Posts: 344
in Propeller 1
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
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
Modes A-D?
In the SPI_SPIN there are also Modes 4 & 5. How do these relate?
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
Got that to work but not with less than CLOCKDELAY of 7
Aaron
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)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
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: and then the whole 32bit value is shifted out, which is the same as if you shift 4 times a 8 bit value.
Andy
{{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)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
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
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
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.
obex.parallax.com/object/430
obex.parallax.com/object/432
Andy
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.
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.