'***************************************************************** ' your setup code goes here '***************************************************************** Init: CPU 48 'Option Autorun On 'Option Baudrate 9600 Option Explicit Option Base 0 Const Ver$="0.20 of 24-NOV-2019" '------------------------------------------------------------------- 'The only config you should need to do: 'change the following to the CS pin for the Flash memory Const WB.CS=23 'change the following to the SPI channel where your Flash chip is (it's usually OK to leave it open 'so long as nothing else is competing and closes it after use - e.g. LCD panels) SPI Open 20000000,0,8'20MHz, mode 0, 8 bits... seems happy at 20MHz but drop this down if you get probs '------------------------------------------------------------------- 'mandatory variables in your program: '------------------------------------------------------------------- Dim Integer WB.RDptr,WB.WRptr,WB.Top,WB.ID,x,n Dim WB$ '------------------------------------------------------------------- '***************************************************************** ' your program goes here '***************************************************************** 'A little demonstration code... x=WB.Init() 'single function id's the device, sets mem size and finds first usable address 'your code should check WB.Top after this call. if no Flash found or couldn't be identified, WB.Top=-1 If x<>0 Then Print"Flash chip not recognized: ";Hex$(x,6) End EndIf Dim y$ Dim Integer m Print"Found ";WB$;" (";Hex$(WB.ID,6);")" Print"memory size is";(WB.Top+1)/1024;"KB (0-"+Hex$(WB.Top)+")" Print"First blank address is ";Hex$(WB.WRptr);"h" Input"Format before testing (Y/N)?",y$ If y$="y" then Print "Formatting..." WB.Format Print"First blank address is ";Hex$(WB.WRptr);"h" EndIf Randomize Timer ' delete this line on MMX/Arm etc 'write some random strings to Flash with timings Print Print"","WR Time" Print"--------------------------------------" For n=1 To 10 Print n, Timer=0:x=WB.WriteStr(Date$+" "+Time$+" "+String$(65,48+Rnd*70)):Print Timer;"mS" Next 'read back the strings - demonstrates chr$(255) if attempting to read Print:Print"Reading back all strings..." Print"Addr","Time","Data" Print"--------------------------------------" Do Print Hex$(WB.RDptr,4);"h", Timer=0:y$=WB.ReadStr$():Print Timer,y$ Loop While y$<>Chr$(255) For n=0 To 1023 Step 16 y$="" Print Hex$(n,4);" "; For m=0 To 15 x=WB.Peek(n+m) Print Hex$(x,2);" "; If x<32 Or x>126 Then x=&h2e y$=y$+Chr$(x) Next Print y$ Next Input"Format the Flash (Y/N)?",y$ y$=UCase$(y$) If y$="Y" Then Print"wait a moment" timer=0:WB.Format:Print timer;"mS" EndIf End
old write method: 1 89mS 2 97mS 3 97mS 4 96mS 5 97mS 6 96mS 7 96mS 8 97mS 9 96mS 10 97mS new write method: 1 11mS 2 19mS 3 19mS 4 19mS 5 20mS 6 19mS 7 19mS 8 19mS 9 19mS 10 20mS
73mS 72mS 72mS 71mS 72mS 72mS 72mS 72mS
5mS 5mS 4mS 5mS 5mS 5mS 5mS 4mS 5mS
'***************************************************************** 'Flash Subs & Functions 'Discover the attached flash memory and initialize all the pointers and stuff 'sets up the CS pin and opens the SPI channel 'returns 0 if the chip is IDed with all the variables correctly set up. 'else returns the ID read from the chip = &FFFFFF is open bus (no chip) Function WB.Init() As Integer Local Integer n WB.Top=-1:WB.WRptr=-1 'identify the Flash ram by reading the JEDEC ID and setting some key values Pin(WB.CS)=0 x=SPI(&h9F) WB.ID=65536*SPI(0)+256*SPI(0)+SPI(0) Pin(WB.CS)=1 'if your device is not listed or comes up unknown, you'll have to determine the correct ID 'from the device PDF and add it below. Good news is, it looks like all the Winbond W25* are '4KB sectors and 256B page size so the code *should* work without any changes - no guarantees 'only known problem is the code assumes a 24 bit address which breaks for >16MB devices and maybe 'others which might only provide a 16 bit address - haven't looked at the PDF. 'The Winbond product selector shows these chips but I couldn't find PDFs for them: 'W25Q02JV 256MB 'W25Q01JV 128MB Select Case WB.ID '*** Tested Case &hEF4018:WB.Top=16384:WB$="W25Q128"'16MB Case &hEF4017:WB.Top=8192:WB$="W25Q64"'8MB Case &hEF4016:WB.Top=4096:WB$="W25Q32"'4MB Case &hEF4015:WB.Top=2048:WB$="W25Q16"'2MB Case &hEF4014:WB.Top=1024:WB$="W25Q80"'1MB '*** UnTested Case &hEF4020:WB.Top=65536:WB.Top=16384:WB$="W25Q512"'64MB - With these two devices, the code should work as they default to Case &hEF4019:WB.Top=32768:WB.Top=16384:WB$="W25Q256"'32MB - 3 byte addresses but you'll only be able to use the bottom 16MB. 'May support the higher capacities later if there is a demand. Case &hEF4013:WB.Top=512:WB$="W25Q40"'512KB Case &hEF5012:WB.Top=256:WB$="W25Q20"'256KB Case &hEF6011:WB.Top=128:WB$="W25Q10"'128KB Case &hEF3010:WB.Top=64:WB$="W25X05"'64KB 'Case &hBF2642:WB.Top=4096:WB$="SST26VF032"'Microchip 4MB pin and code compatible - needs work on global enable Case Else SPI Close:WB.Top=-1:WB.Init=WB.ID:Exit Function'can't identify End Select WB.Top=(WB.Top*1024)-1'set the top memory location 'find the first (=lowest address with) FF byte If WB.Peek(0)=255 Then WB.WRptr=0:WB.RDptr=0:Exit Function'blank Flash; early bath 'otherwise find by successive approximation - very fast, searches 4MB in 80mS WB.WRptr=(WB.Top+1)\2:n=WB.WRptr\2'start at the middle and go in smaller and smaller halves Do If WB.Peek(WB.WRptr)=255 Then WB.WRptr=WB.WRptr-n'still in void so go down by half the remainder Else WB.WRptr=WB.WRptr+n'we are in the strings so go up by half the remainder EndIf If n<>1 Then n=n\2'smaller and smaller halves Loop Until WB.Peek(WB.WRptr)=255 And WB.Peek(WB.WRptr-1)<>255'is the byte before the FF !FF? if so, we are done End Function 'set or clear flash write enable flag Sub WB.WREnable(a As Integer) If WB.Top=-1 Then Exit Sub Local Integer x Pin(WB.CS)=0 If a=0 Then x=SPI(4) Else x=SPI(6) EndIf Pin(WB.CS)=1 WB.WaitBusy End Sub 'wait while Flash is busy Sub WB.WaitBusy If WB.Top=-1 Then Exit Sub Do While WB.TestBusy() 'you might want to put a WATCHDOG here. Long operations could break your program Loop End Sub 'test BUSY flag in STAT1 Function WB.TestBusy() As Integer If WB.Top=-1 Then Exit Function WB.TestBusy=(WB.Stat1RD() And 1) End Function Function WB.Stat1RD() As Integer If WB.Top=-1 Then Exit Function Local Integer x Pin(WB.CS)=0 x=SPI(5) WB.Stat1RD=SPI(0) Pin(WB.CS)=1 End Function Function WB.Stat2RD() As Integer If WB.Top=-1 Then Exit Function Local Integer x Pin(WB.CS)=0 x=SPI(&h35) WB.Stat2RD=SPI(0) Pin(WB.CS)=1 End Function Function WB.Stat3RD() As Integer If WB.Top=-1 Then Exit Function Local Integer x Pin(WB.CS)=0 x=SPI(&h15) WB.Stat3RD=SPI(0) Pin(WB.CS)=1 End Function 'send a 3 byte address to Flash Sub WB.Addr(a As Integer) If WB.Top=-1 Then Exit Sub Local Integer x x=SPI((a>>16) And 255) x=SPI((a>>8) And 255) x=SPI(a And 255) End Sub 'set the address for page writing Sub WB.SetPage If WB.Top=-1 Then Exit Sub Local Integer x Pin(WB.CS)=1 WB.WaitBusy WB.WREnable 1 WB.WaitBusy Pin(WB.CS)=0 x=SPI(2) WB.Addr WB.WRptr'setup the address End Sub 'erase the entire chip - Beware; can take several seconds Sub WB.Format() If WB.Top=-1 Then Exit Sub Local Integer x WB.WREnable 1 WB.WaitBusy Pin(WB.CS)=0 x=SPI(&h60)'start the erase - will take some time Pin(WB.CS)=1 WB.WaitBusy x=WB.Init() End Sub 'Peek any address in Flash Function WB.Peek(a As Integer) As Integer If WB.Top=-1 Then Exit Function Local Integer x Pin(WB.CS)=0 x=SPI(3)'read at the given address WB.Addr a WB.Peek=SPI(0)'grab one byte Pin(WB.CS)=1 End Function 'Read a string from the Read Address using the global WB.RDptr variable Function WB.ReadStr$() If WB.Top=-1 Then Exit Function Local Integer x,z Local a$ WB.WaitBusy'wait for the Flash to be idle Pin(WB.CS)=0 x=SPI(3) WB.Addr WB.RDptr For z=1 to 256'stream the data from Flash into a$, 256 covers max length+chr$(0), we bail early if needed x=SPI(0)'get the character Select case x Case 0 'break out on the delimiter Poke Var a$,0,z-1 WB.RDptr=WB.RDptr+Len(a$)+1'bump the address along by the number of bytes we read +1 for the delimiter Exit For Case 255 'break out on EOF, should never happen WB.RDptr=WB.RDptr+Len(a$) a$=Chr$(x)'single char? Exit For Case Else Poke Var a$,z,x End select Next Pin(WB.CS)=1 WB.ReadStr$=a$ End Function 'write a string to the next available position. Return 0 if successful Function WB.WriteStr(a$) As Integer If WB.Top=-1 Then WB.WriteStr=1:Exit Function Local Integer n,x n=Len(a$)+1 If WB.WRptr+n>WB.Top Then WB.WriteStr=1:Exit Function x=256-(WB.WRPtr Mod 256)' remaining space in this page WB.SetPage If n<=x Then 'will fit in current page SPI Write n-1,a$ x=SPI(0) Pin(WB.CS)=1 WB.WrPtr=WB.WrPtr+n Else 'have to split across pages 'first half Local l$ l$=Left$(a$,x) SPI Write len(l$),l$ Pin(WB.CS)=1 WB.WrPtr=WB.WrPtr+x 'second half l$=Mid$(a$,x+1)+Chr$(0) x=Len(l$) WB.SetPage SPI Write x,l$ Pin(WB.CS)=1 WB.WrPtr=WB.WrPtr+x EndIf End Function '*****************************************************************