Interfacing with an SPI EEPROM

copyright, Peter H. Anderson, Baltimore, MD, Feb, '00

Introduction.

This discussion focuses on interfacing an SPI EEPROM with a BX24. I used a Microchip 25LC640 which provides 8K X 8 of memory storage. However, I believe the same code may be used with larger SPI EEPROMs by Atmel, notably the AT25256 (32K by 8) used by NetMedia in both the BasicX-01 and the BX-24.

My interest was sparked by a question related to data logging posted to the BasicX Mail list which required the storing of 16 integers per second for 10 minutes. Thus, the required memory is 2 bytes * 16 integers * 600 seconds or 19,200 bytes. Thus, the Atmel 25256 will provide adequate storage. But, this data logging application also requires that the time to store the 32 bytes of data be but a fraction of a second to allow time the collect the data. I found that I was able to store the 16 integers to EEPROM in about 27 ms total.

In developing this discussion, I began with the simplest exercise of writing and reading a single byte using four of the BX24's 16 general purpose I/O pins (SPI_EEPROM_1.Bas). In SPI_EEPROM_2.Bas, the outputting and inputting of each byte was performed using the ShiftOut() and ShiftIn() commands.

Program SPI_EEPROM_3.Bas expands on this to write and read an array of 16 integers.

Program SPI_EEPROM_1.Bas was then reworked to use the BX24's SPI interface (SPI_EEPROM_4.Bas). SPI_EEPROM_5.Bas is similarly a rework of SPI_EEPROM_3.Bas using the SPI interface.

Interfacing using "Bit Bang".

Programs SPI_EEPROM_1.Bas through _3.Bas use four of the general purpose IO pins;

   BX24     				  SPI EEPROM

     16 <----- MISO --------------------- SO (term 2)
     15 ------ MOSI --------------------> SI (term 5)
     14 ------ SPI_CLK -----------------> SCK (term 6)
     13 ------ SPI_CS ------------------> /CS (term 1)



Program SPIEEPROM_1.Bas.

In the following program, 256 bytes of data are written, byte by byte, to the first 256 locations in the external EEPROM.

Note that all sessions begin by bringing CS low and are terminated by bringing it high.

The manufacturer has gone to great lengths to assure no "accidental" writes, particularly on power up. Thus, in writing data, an 8-bit command, WREN is first sent to enable the write latch. This latch is then cleared after writing to the EEPROM and thus the sending of the WREN command must always be performed prior to the writing of data.

In writing data, the CS is brought low and the WRITE command is sent, followed by the 16-bit address followed by the data. CS is then brought high followed by a 5 ms delay to permit the EEPROM to burn the data.

Note that the 16-bit address permits addressing of 65,536 locations. Valid addresses for the 25LC640 (8K Bytes) are &H0000 - &H2000.

' SPI_EEPROM_1.Bas
'
' Illustrates an interface with the Microchip 25LC640.  
'
' Writes 256 bytes to EEPROM and then reads them byte by byte as
' opposed to the page mode.  8-bit serial output on MOSI and 8-bit
' serial input on MISO uses "bit-bang".  In Program SPI_EEPROM_2.Bas,
' ShiftOut() and  ShiftIn() were used.
'
' copyright, Peter H. Anderson, Baltimore, MD, Feb, '00

Const SPI_WREN as Byte = bx00000110 ' define EEPROM commands
Const SPI_WRDI as Byte = bx00000100
Const SPI_WRITE as Byte = bx00000010
Const SPI_READ as Byte = bx00000011

Const SPI_CS as Byte = 13  ' define the BX24 pins
Const SPI_CLK as Byte = 14
Const MOSI as Byte = 15
Const MISO as Byte = 16

Sub Main()

   Dim Address as Integer, Dat as Byte

   Call OpenSerialPort(1, 19200)

   For Address = 0 to 255 ' write data, byte by byte to EEPROM
     Dat = CByte((Address MOD 10) + 1)
     Call SPIEEPROMWrite(Address, Dat)
   Next

   For Address = 0 to 255 ' read it back and display it
     Dat = SPIEEPROMRead(Address)
     Call PutB(Dat)
     If (((Address+1) MOD 8) <>0) Then ' 8 values to a line
       Call PutByte(Asc(" "))
     Else
        Call NewLine()
     End If
   Next
End Sub     

Sub SPIEEPROMWrite(ByVal Address as Integer, ByVal Dat as Byte)

   Dim N as Integer, DataByte as Byte, H as Byte, L as Byte

   Call PutPin(SPI_CS, 1)
   Call PutPin(SPI_CLK, 0) ' be sure CLK is low
   Call PutPin(MOSI, 0)  

   ' set the enable latch
   Call PutPin(SPI_CS, 0) ' bring CS low to initiate
   Call SPIOut8(SPI_WREN)    ' enable write latch
   Call PutPin(SPI_CS, 1) ' end of session
   
   ' split the address into high and low bytes
   H =  CByte(Address\256)
   L = CByte(Address - (CInt(H)*256))

   Call PutPin(SPI_CS, 0) ' initiate session
   Call SPIOut8(SPI_WRITE) ' send EEPROM command
   Call SPIOut8(H)  ' followed by Hi and Lo bytes
   Call SPIOut8(L)  ' of address
   Call SPIOut8(Dat)  ' send the data

   Call PutPin(SPI_CS, 1) ' end of session
   Call Sleep(0.005)  ' allow time for the EEPROM to burn
End Sub
      
Function SPIEEPROMRead(ByVal Address as Integer) as Byte    
   Dim Val as Byte, H as Byte, L as Byte
   
   Call PutPin(SPI_CS, 0) ' intitiate the session
   Call SPIOut8(SPI_READ) ' send the EEPROM command

   ' split the address into high and low bytes
   H =  CByte(Address\256)
   L = CByte(Address - (CInt(H)*256))
   
   Call SPIOut8(H)  ' send the address
   Call SPIOut8(L)

   Val = SPIIn8()  ' read the data

   Call PutPin(SPI_CS, 1) ' end of session
   SPIEEPROMRead = Val
End Function

Sub SPIOut8(ByVal X as Byte) ' "bit-bang" ser out technique
   Dim N as Integer
   For N = 1 to 8 ' output the data byte most sign byte first     
            
     If((X AND bx10000000) <> 0) Then   ' set up MOSI      
        Call Putpin(MOSI, 1)
     Else
        Call Putpin(MOSI, 0)
     End if

     Call PutPin(SPI_CLK, 1) ' and then provide a clock pulse
     Call PutPin(SPI_CLK, 0)
     X = X * 2   ' next bit in most sign bit position
   Next
End Sub

Function SPIIn8() as Byte ' "bit-bang" ser in
   Dim Val as Byte, N as Byte

   For N = 1 to 8
      Call PutPin(SPI_CLK, 1)
      Val = (Val * 2) + GetPin(MISO) ' read after bringing clk high
      Call PutPin(SPI_CLK, 0)
   Next
   SPIIn8 = Val
End Function

Program SPI_EEPROM_2.Bas.

This program is exactly the same, except that the sending and receipt of a byte is implemented using the ShiftOut() and ShiftIn() commands which were introduced in software version 1.45.

The data which was written to the EEPROM was modified slightly so that I could verify I wasn't reading data which was written using the previous program.

Note that use of the ShiftOut() and ShiftIn() commands provides a substantial speed improvement as it avoids fetching token after token in the SPIOut8() and SPIIn8() routines presented above.

' SPI_EEPROM_2.Bas
'
' Same as SPI_EEPROM_1.Bas except uses ShiftOut() and ShiftIn()
' commands.
'
' copyright, Peter H. Anderson, Baltimore, MD, Feb, '00

Const SPI_WREN as Byte = bx00000110 ' define the EEPROM commands
Const SPI_WRDI as Byte = bx00000100
Const SPI_WRITE as Byte = bx00000010
Const SPI_READ as Byte = bx00000011

Const SPI_CS as Byte = 13  ' define the pins
Const SPI_CLK as Byte = 14
Const MOSI as Byte = 15
Const MISO as Byte = 16

Sub Main()

   Dim Address as Integer, Dat as Byte

   Call OpenSerialPort(1, 19200)

   For Address = 0 to 255  ' write 256 bytes of data
     Dat = CByte((Address MOD 10) + 3) ' byte by byte
     Call SPIEEPROMWrite(Address, Dat)
   Next

   For Address = 0 to 255  ' read back, byte by byte
     Dat = SPIEEPROMRead(Address)
     Call PutB(Dat)   ' 8 bytes to a line
     If (((Address+1) MOD 8) <>0) Then
       Call PutByte(Asc(" "))
     Else
        Call NewLine()
     End If
   Next
End Sub     

Sub SPIEEPROMWrite(ByVal Address as Integer, ByVal Dat as Byte)

   Dim N as Integer, DataByte as Byte, H as Byte, L as Byte
   Call PutPin(SPI_CS, 1)
   Call PutPin(SPI_CLK, 0)
   Call PutPin(MOSI, 0)

   ' set the enable latch
   Call PutPin(SPI_CS, 0) ' initiate session
   Call SPIOut8(SPI_WREN) ' enable write latch
   Call PutPin(SPI_CS, 1) ' terminate session
   
   ' split the address into high and low bytes
   H =  CByte(Address\256)
   L = CByte(Address - (CInt(H)*256))

   Call PutPin(SPI_CS, 0) ' initiate session
   Call SPIOut8(SPI_WRITE) ' write command
   Call SPIOut8(H)  ' address
   Call SPIOut8(L)
   Call SPIOut8(Dat)  ' data

   Call PutPin(SPI_CS, 1) ' end of session
   Call Sleep(0.005)  ' allow data to be burned
End Sub
  
Function SPIEEPROMRead(ByVal Address as Integer) as Byte
   Dim Val as Byte, H as Byte, L as Byte
   
   Call PutPin(SPI_CS, 0) ' initiate the session
   Call SPIOut8(SPI_READ) ' read command

   ' split the address into high and low bytes
   H =  CByte(Address\256)
   L = CByte(Address - (CInt(H)*256))
   
   Call SPIOut8(H)  ' send address
   Call SPIOut8(L)

   Val = SPIIn8()  ' read the byte

   Call PutPin(SPI_CS, 1) ' end of session
   SPIEEPROMRead = Val
End Function

Sub SPIOut8(ByVal X as Byte) ' ser out shift performed
				' usingShiftOut()
   Call ShiftOut(MOSI, SPI_CLK, 8, X)
End Sub


Function SPIIn8() as Byte ' ser in shift performed using ShiftIn()
   Dim Val as Byte
   Val = ShiftIn(MISO, SPI_CLK, 8)
   SPIIn8 = Val
End Function

Program SPI_EEPROM_3.Bas.

This program illustrates writing and reading multiple bytes to the external SPI EEPROM, commonly termed the "page mode".

In writing, the write enable latch is first enabled followed by a command consisting of the "write" command, followed by the two byte address, followed by up to 32 bytes of data. In this mode the address auto increments for each byte.

Limitations on the page write (and read) are that it is limited to 32 bytes and the address auto increment is limited to the lower eight bits. Thus, the user must assure the data being written will not cross a 256 byte page boundary. A sure fire way to avoid this is to write 2, 4, 8. 16 or 32 bytes at a time.

Thus, my original interest was in the storing of 12 integers (24 bytes). To avoid crossing a page boundary I opted to extend this to 16 integers.

In SPIEEPROMWriteIntArray(), the specified array of integers on size NumInts is saved to addresses beginning at the specified address. To avoid crossing a page boundary, NumInts must be 1, 2, 4, 8 or 16.

In SPIEEPROMReadIntArray(), an array of integers of size NumInts is read from addresses beginning at the specified address.

In this routine, the onboard clock is read before and after writing the 16 integers to determine the amount of time required to save 16 integers to EEPROM. I found it was about 27 ms.

Program SPI_EEPROM_3.Bas.
   
' SPI_EEPROM_3.Bas
'
' Illustrates "page" write and read.
' 
' Dummies up 16 integers, saves to EEPROM beginning at address 0000.
' Dummies up 16 integers again and saves to EEPROM beginning at
' address 0020.  Reads data back into integers and displays to the
' terminal. 
'
' copyright, Peter H. Anderson, Baltimore, MD, Feb, '00

Const SPI_WREN as Byte = bx00000110 ' define EEPROM commands
Const SPI_WRDI as Byte = bx00000100
Const SPI_WRITE as Byte = bx00000010
Const SPI_READ as Byte = bx00000011

Const SPI_CS as Byte = 13  ' define pins
Const SPI_CLK as Byte = 14
Const MOSI as Byte = 15
Const MISO as Byte = 16

Sub Main()

   Dim Address as Integer, N as Integer
   Dim IntArray(1 to 16) as Integer
   Dim Tm1 as Single, Tm2 as Single

   Call OpenSerialPort(1, 19200)
   Call PutTime(0, 0, 0.0) ' initialize on-board clock

   For N = 1 to 16
      IntArray(N) = N+100 
   ' Make up some data values to store at address &H0000
   Next

   Tm1 = Timer() ' to determine the time to store 16 integers
   Address = &H0000   

   Call SPIEEPROMWriteIntArray(Address, IntArray, 16) 
   ' write the array
   Tm2 = Timer()
  
   Call PutS(Tm2 - Tm1) ' display time to execute.  27 ms
   Call NewLine()

   For N = 1 to 16
      IntArray(N) = N+200 
   ' Make up some data values some data values to store
   ' at &H0020
   Next

   Address = &H0000 + 32  

   Call SPIEEPROMWriteIntArray(Address, IntArray, 16) 
   ' write the array

   ' Now read the data back and display it

   Address = &H0000
   Call SPIEEPROMReadIntArray(Address, IntArray, 16)   
   Call DisplayIntArray(IntArray, 16)

   Address = &H0000 + 32
   Call SPIEEPROMReadIntArray(Address, IntArray, 16)   
   Call DisplayIntArray(IntArray, 16)

End Sub
   
Sub SPIEEPROMWriteIntArray(ByVal Address as Integer, _
                               ByRef IntArray() as Integer, _
                               ByVal NumInts as Integer)
' saves array of integers of size NumInts beginning at Address

   Dim N as Integer, DataByte as Byte, H as Byte, L as Byte
   Call PutPin(SPI_CS, 1)
   Call PutPin(SPI_CLK, 0)
   Call PutPin(MOSI, 0)

   ' set the enable latch
   Call PutPin(SPI_CS, 0)
   Call SPIOut8(SPI_WREN) ' enable write latch
   Call PutPin(SPI_CS, 1)
   
   ' split the address into high and low bytes
   H =  CByte(Address\256)
   L = CByte(Address - (CInt(H)*256))

   Call PutPin(SPI_CS, 0)
   Call SPIOut8(SPI_WRITE) ' write command
   Call SPIOut8(H)  ' followed by address
   Call SPIOut8(L)

   For N = 1 to NumInts ' Max of 16 Ints
      H =  CByte(IntArray(N)\256) ' splits integers into bytes
      L = CByte(IntArray(N) - (CInt(H)*256))

      Call SPIOut8(H) ' write the high byte of the integer
      Call SPIOut8(L) ' the low byte
   Next

   Call PutPin(SPI_CS, 1) ' end of session
   Call Sleep(0.005)  ' allow some time for EEPROM
End Sub      

Sub SPIEEPROMReadIntArray(ByVal Address as Integer, _
                          ByRef IntArray()as Integer, _
                          ByVal NumIntegers as Integer)
' reads an array of integers from EEPROM beginning at Address
   Dim N as Integer, H as Byte, L as Byte
   
   Call PutPin(SPI_CS, 0) ' initiate the session
   Call SPIOut8(SPI_READ) ' read command

   ' split the address into high and low bytes
   H =  CByte(Address\256)
   L = CByte(Address - (CInt(H)*256))
   
   Call SPIOut8(H)  ' address
   Call SPIOut8(L)

   For N = 1 to NumIntegers ' then fetch bytes, two at a time
      H = SPIIn8()
      L = SPIIn8()
      IntArray(N) = CInt(H) * 256 + CInt(L) ' combine the two bytes
   Next
   Call PutPin(SPI_CS, 1)    ' end of session
End Sub

Sub DisplayIntArray(ByRef IntArray() as Integer, ByVal NumInts as
Integer)
' displays 16 integers, separated by a space
   Dim N as Integer
   For N = 1 to 16
      Call PutI(IntArray(N))
      Call PutByte(Asc(" "))
   Next
   Call NewLine()
End Sub


Sub SPIOut8(ByVal X as Byte)
   Call ShiftOut(MOSI, SPI_CLK, 8, X)
End Sub
  
Function SPIIn8() as Byte
   Dim Val as Byte
   Val = ShiftIn(MISO, SPI_CLK, 8)
   SPIIn8 = Val
End Function

Using the BX24 SPI Interface.

Programs SPI_EEPROM_4.Bas and _5.Bas use BX24's SPI capability.

The following wiring arrangement was used;

     BX24    				SPI EEPROM

     Hole 4 <-- MISO ------------------ SO (term 2)
     Hole 5 --- MOSI -----------------> SI (term 5)
     Hole 3 --- SCK ------------------> SCK (term 6)
  
     Pin 13 ---SPI_EEPROM_CS ---------> /CS (term 1)

The 'holes" refer to the array of seven holes at the top of the BX24. Note that hole 1 is closest to Pin 24. I found it was an easy matter to solder leads to these points.

In using this SPI bus, all SPI devices including the on-board BX24 EEPROM share MISO, MOSI and SCK. A specific device is selected by bringing the /CS on that device low.

Note that a pull-up resistor to +5 VDC is required on the general purpose IO pin used for the CS as the BX24 boots with all general purpose IOs as inputs (high impedance) leaving the /CS on the external device floating. If the external device becomes active as a result, the MISO of the external device will conflict with the MISO lead on the internal EEPROM and the BX24 will be unable to read tokens from the internal EEPROM.

Program SPI_EEPROM_4.Bas.

This program is simply a rework of Programs SPI_EEPROM_1.Bas (and _2.Bas) which uses the BX24's SPI interface.

Note that an SPI channel is first opened which defines the format of the clocking and the CS pin to be used in selecting this device;

   SPISetupByte = 0 ' most sig bit first, sck normally low, etc
   Call OpenSPI(1, SPISetupByte, 13)   

Prior to writing, the write latch is enabled and this single byte is output.

   PutData(1) = SPI_WREN
   Call SPICmd(1, 1, PutData(1), 0, GetData) ' enable the write latch 


The SPI command brings the CS lead low, sends one byte beginning at the address of PutData(1) and then brings CS high.

The data is then sent by filling an array of contiguous bytes consisting of the write command, the high and low bytes of the address and finally, the data and executing the SPICmd();

   PutData(1) = SPI_WRITE

   ' split the address into high and low bytes
   H = CByte(Address\256) ' high byte
   L = CByte(Address - (CInt(H)*256))

   PutData(2) = H
   PutData(3) = L
   PutData(4) = Dat
   Call SPICmd(1, 4, PutData(1), 0, GetData)

In reading data, an array consisting of the read command, the high and low bytes of the address is filled.

   PutData(1) = SPI_READ
     
   ' split the address into high and low bytes
   H =  CByte(Address\256)
   L = CByte(Address - (CInt(H)*256))
   PutData(2) = H
   PutData(3) = L
   Call SPICmd(1, 3, PutData(1), 1, GetData) 
   ' send command plus address and then fetch the data

' SPI_EEPROM_4.Bas
'
' Illustrates an interface with the Microchip 25LC640.  
'
' This is a rework of SPI_EEPROM_1.Bas which uses the BX24's SPI
' Interface.
'
' Writes 256 bytes to EEPROM and then reads them byte by byte as
' opposed to the page mode.   
'
' copyright, Peter H. Anderson, Baltimore, MD, Feb, '00

Const SPI_WREN as Byte = bx00000110 ' define EEPROM commands
Const SPI_WRDI as Byte = bx00000100
Const SPI_WRITE as Byte = bx00000010
Const SPI_READ as Byte = bx00000011

' define SPI Setup Bits
Const SPI_LSB as Byte = &H20 'lsb transmitted first 
Const SPI_CPOL as Byte = &H08 'sck is mostly high 
Const SPI_CPHA as Byte = &H04 'clock phase, see Atmel docs for timing
Const SPI_SCK4 as Byte = &H00 'clock speed f/4 
Const SPI_SCK16 as Byte = &H01 'clock speed f/16 
Const SPI_SCK64 as Byte = &H02 'clock speed f/64 
Const SPI_SCK128 as Byte = &H03 'clock speed f/128 


Const SPI_CS as Byte = 13  ' define the BX24 pins
' Note.  Pull up this lead to +5 VDC to assure the external EEPROM is
' not active at the same time as the BX24 on-board SPI EEPROM

Sub Main()

   Dim Address as Integer, Dat as Byte, SPISetupByte as Byte

   Call OpenSerialPort(1, 19200)

   SPISetupByte = 0 ' most sig bit first, sck normally low, etc
   Call OpenSPI(1, SPISetupByte, 13)   

   For Address = 0 to 255 ' write data, byte by byte to EEPROM
     Dat = CByte((Address MOD 10) + 1)
     Call SPIEEPROMWrite(Address, Dat)
   Next

   For Address = 0 to 255 ' read it back and display it
     Dat = SPIEEPROMRead(Address)
     Call PutB(Dat)
     If (((Address+1) MOD 8) <>0) Then ' 8 values to a line
       Call PutByte(Asc(" "))
     Else
        Call NewLine()
     End If
   Next
End Sub     

Sub SPIEEPROMWrite(ByVal Address as Integer, ByVal Dat as Byte)

   Dim PutData(1 to 3) as Byte, GetData as Byte, H as Byte, L as Byte

   PutData(1) = SPI_WREN
   Call SPICmd(1, 1, PutData(1), 0, GetData) ' enable the write latch 
 
   
   PutData(1) = SPI_WRITE

   ' split the address into high and low bytes
   H = CByte(Address\256) ' high byte
   L = CByte(Address - (CInt(H)*256))

   PutData(2) = H
   PutData(3) = L
   PutData(4) = Dat
   Call SPICmd(1, 4, PutData(1), 0, GetData)

   Call Sleep(0.005)  ' allow time for the EEPROM to burn
End Sub
      
Function SPIEEPROMRead(ByVal Address as Integer) as Byte    
   Dim PutData(1 to 3) as Byte, GetData as Byte, H as Byte, L as Byte
   
   PutData(1) = SPI_READ
     
   ' split the address into high and low bytes
   H =  CByte(Address\256)
   L = CByte(Address - (CInt(H)*256))
   PutData(2) = H
   PutData(3) = L
   Call SPICmd(1, 3, PutData(1), 1, GetData) 
   ' send command plus address

   ' and then fetch the data
   SPIEEPROMRead = GetData     
End Function

Program SPI_EEPROM_5.Bas.

This is a rework of program SPI_EEPROM_3.Bas which writes 16 integers to EEPROM and later, reads them.

In writing data, an array if filled with the "write" command, the two byte address and the high and low bytes of each of the 16 integers, a total of 35 bytes. This is output;

    Call SPICmd(1, 3+2*CByte(NumInts), PutData(1), 0, GetData)

Note that in this case, NumInts is 16.

In reading data, a three byte array is filled with the "read" command and the high and low bytes of the address. In executing the SPICmd(), after writing these three bytes, 32 bytes are then read into array GetData. Each two bytes of this data is then concatenated into and integer.

Call SPICmd(1, 3, PutData(1), 2*CByte(NumIntegers), GetData(1))
	' read 32 bytes

Note that in this case, NumInts is 16.

' SPI_EEPROM_5.Bas
'
' Illustrates "page" write and read using the BX24 SPI Interface.
' 
' Dummies up 16 integers, saves to EEPROM beginning at address 0000.
' Dummies up 16 integers again and saves to EEPROM beginning at
' address 0020.  Reads data back into integers and displays to the
' terminal. 
'
' copyright, Peter H. Anderson, Baltimore, MD, Feb, '00

Const SPI_WREN as Byte = bx00000110 ' define EEPROM commands
Const SPI_WRDI as Byte = bx00000100
Const SPI_WRITE as Byte = bx00000010
Const SPI_READ as Byte = bx00000011

' define SPI Setup Bits
Const SPI_LSB as Byte = &H20 'lsb transmitted first 
Const SPI_CPOL as Byte = &H08 'sck is mostly high 
Const SPI_CPHA as Byte = &H04 'clock phase, see atmel docs for timing
Const SPI_SCK4 as Byte = &H00 'clock speed f/4 
Const SPI_SCK16 as Byte = &H01 'clock speed f/16 
Const SPI_SCK64 as Byte = &H02 'clock speed f/64 
Const SPI_SCK128 as Byte = &H03 'clock speed f/128 

Const SPI_EEPROM_CS as Byte = 13 ' define pin to control EEPROM

Sub Main()

   Dim Address as Integer, N as Integer
   Dim IntArray(1 to 16) as Integer
   Dim Tm1 as Single, Tm2 as Single
   Dim SPISetupByte as Byte
   
   SPISetupByte = 0
   Call OpenSPI(1, SPISetupByte, SPI_EEPROM_CS) ' open an SPI channel

   Call OpenSerialPort(1, 19200)
   Call PutTime(0, 0, 0.0)

   For N = 1 to 16
      IntArray(N) = N+100 
' Make up some data values to store at address &H0000
   Next

   Tm1 = Timer() ' to determine the time to store 16 integers
   Address = &H0000   

   Call SPIEEPROMWriteIntArray(Address, IntArray, 16) 
				' write the array
   Tm2 = Timer()
  
   Call PutS(Tm2 - Tm1) ' display time.  27 ms
   Call NewLine()

   For N = 1 to 16
      IntArray(N) = N+200 
' Make up some data values some data values to store
' at &H0020
   Next

   Address = &H0000 + 32  

   Call SPIEEPROMWriteIntArray(Address, IntArray, 16) 
   ' write the array

   ' Now read the data back and display it

   Address = &H0000
   Call SPIEEPROMReadIntArray(Address, IntArray, 16)   
   Call DisplayIntArray(IntArray, 16)

   Address = &H0000 + 32
   Call SPIEEPROMReadIntArray(Address, IntArray, 16)   
   Call DisplayIntArray(IntArray, 16)

End Sub
   
Sub SPIEEPROMWriteIntArray(ByVal Address as Integer, _
                               ByRef IntArray() as Integer, _
                               ByVal NumInts as Integer)
' saves array of integers of size NumInts beginning at Address
' limited to 16 integers

   Dim M as Integer, N as Integer, H as Byte, L as Byte
   Dim PutData(1 to 35) as Byte, GetData as Byte

   PutData(1) = SPI_WREN
   Call SPICmd(1, 1, PutData(1), 0, GetData) ' enable write latch
      
   ' split the address into high and low bytes
   H =  CByte(Address\256)
   L = CByte(Address - (CInt(H)*256))

   PutData(1) = SPI_WRITE
   PutData(2) = H
   PutData(3) = L

   M = 4  ' array index
   For N = 1 to NumInts ' for each integer, separate into bytes
      H =  CByte(IntArray(N)\256)
      L = CByte(IntArray(N) - (CInt(H)*256))
      PutData(M) = H
      M = M + 1
      PutData(M) = L
      M = M + 1
   Next
   Call SPICmd(1, 3+2*CByte(NumInts), PutData(1), 0, GetData)
   
   Call Sleep(0.005)  ' allow some time for EEPROM
End Sub
       
Sub SPIEEPROMReadIntArray(ByVal Address as Integer, _
                          ByRef IntArray()as Integer, _
                          ByVal NumIntegers as Integer)
' reads an array of integers from EEPROM beginning at Address
' limited to 16 integers by array GetData
   Dim N as Integer, H as Byte, L as Byte
   Dim PutData(1 to 3) as Byte, GetData(1 to 32) as Byte     

   ' split the address into high and low bytes
   H =  CByte(Address\256)
   L = CByte(Address - (CInt(H)*256))

   PutData(1) = SPI_READ
   PutData(2) = H ' high and low bytes of address
   PutData(3) = L

   Call SPICmd(1, 3, PutData(1), 2*CByte(NumIntegers), GetData(1))
' read 32 bytes
   
   For N = 1 to NumIntegers 
      H = GetData(2*N - 1)
      L = GetData(2*N)
      IntArray(N) = CInt(H) * 256 + CInt(L)      
   Next   
End Sub

Sub DisplayIntArray(ByRef IntArray() as Integer, ByVal NumInts as
Integer)
' displays 16 integers, separated by a space
   Dim N as Integer
   For N = 1 to 16
      Call PutI(IntArray(N))
      Call PutByte(Asc(" "))
   Next
   Call NewLine()
End Sub