TriOS/lib/glob-sdspi.spin

883 lines
55 KiB
Plaintext

{{
Modified Tiny Basic for use with Propeller Demo Board and Hydra.
I2C and SPI driver initialization & interface object derived from Propeller OS.
Copyright (c) 2009 Michael Green. See end of file for terms of use.
}}
'' 2006/09/24 - Corrected action table for i2cRead0Cmd
'' 2006/10/02 - Improved assembly comments. Changed i2cReset, i2cStop
'' 2006/11/02 - Changed data setup time. Changed i2cReset timing
'' 2006/11/03 - Changed read/write method speed to 100KHz
'' 2006/11/04 - Added checkPresence, writeWait, and computeTimes methods
'' 2006/11/06 - Limited boot loading to 32K - 16 (OS uses last 16 bytes)
'' Added ioVerifyCmd and verifyEEPROM method
'' 2006/11/09 - Modified boot and verify to use the minimum of the actual
'' length of the program stored in vbase ($0008) or the
'' specified byte count.
'' Note also that these routines don't know about option bits.
'' 2006/11/10 - Now control block address is passed to start routine
'' 2007/01/09 - Added SPI routines for SD card FAT access
'' 2007/01/13 - Corrected checksum when ioBoot or ioVerify
'' 2007/01/13 - Stores stack marker & clears VAR area on ioBootCmd
'' 2007/02/06 - Stores stack marker & clears VAR area on ioSpiBoot
'' 2007/02/13 - Changed the way verify mode was done, combined code
'' 2007/02/20 - Added ioStopLdr option to stop the loader's cog
'' 2007/02/22 - Corrected bootSDCard. Needs start and initSDCard calls first.
'' 2009/04/04 - spiDoStop modified per Cluso99 to turn off card
'' 2009/07/21 - I2C setup times modified per Nick Mueller's timing tests
'' Default I2C bus timing changed to 400KHz. Thanks Nick.
'' This portion is normally run only once during initialization and the driver remains
'' resident in a cog. These routines can be used completely independently of the rest
'' of the Propeller OS. The start routine here expects the address of a 2 long area
'' to be used for passing information to the I/O routines in the COG. This area should
'' be located in an area of memory not expected to be overlaid by data or a program that
'' might be loaded since the COG routines will be accessing this information after an
'' operation has completed.
'' This object provides an I2C EEPROM read/write routine that can handle both 100KHz and
'' 400KHz bus speeds and EEPROM page sizes of 64, 128, or 256 bytes (or no paging/no delay
'' as with Ramtron serial RAM). The SPIN interpreter can be started after reading, either
'' in the same COG used by these routines or in a free COG. The control information is
'' passed in a 2 long parameter block whose address is passed to the COG when it is started.
'' The parameter block is updated when the operation is completed. Note that these are shown
'' here as they appear in a long value rather than the order of the bytes in memory.
'' -------------------------------------------------------------------
'' | cmd/status | I/O pin / device / address |
'' -------------------------------------------------------------------
'' | byte count | HUB address |
'' -------------------------------------------------------------------
'' The EEPROM address is in the same format used by other routines with the I/O pin pair
'' in bits 21..19, the device address in bits 18..16, and the 64K address in bits 15..0.
'' Note that the I/O pin pair is the number of the SCL pin divided by 2. The SDA pin is
'' always the next higher numbered pin. The command code is in the low order bits of the
'' high order byte of the first long (see ioCmdMask). This is always non-zero to indicate
'' that a command is to be performed by the COG routines. When the command is finished,
'' this is set to zero. The errorFlag bit is set to one if a NAK was read after a write
'' transfer. This is the only error reported by these routines. A read operation and
'' zero-length writes do involve several write transfers for addressing, but the data
'' read transfer has no error checking. When the command is completed, the device address,
'' byte count, and HUB address are all updated to their values at that time. For the
'' verify operation (ioVerifyCmd), an error is reported if the checksum is not zero and
'' the HUB address field is not incremented. It may be used for some other checksum
'' reporting in the future.
'' The pins used for the boot EEPROM I2C bus (at least on Parallax's Demo Board) do not
'' have a pullup on SCL. This requires that SCL be driven both high and low. If the bus
'' used is on pins 28 and 29, SCL is actively driven at all times.
'' These EEPROM read/write routines do not provide for waiting for the write to complete
'' nor do they check for paged writes. All bytes in a multi-byte write must lie within
'' a single EEPROM page since the EEPROM write address counter wraps around at a page
'' boundary. Similarly, for multi-byte reads, all requested bytes must lie within the
'' same device since the sequential read counter wraps around at the device boundary.
'' Command codes are provided for devices with zero, one, or two address bytes following
'' the device selection byte. As for all I2C devices, addressing is done using write
'' mode and the device is reselected in read mode after the last address byte. In the
'' case of ioRead0Cmd, the device is initially selected in read mode. For 8-bit addresses,
'' the device select code is taken from bits 15-8 of the address value. For the case
'' without address bytes, the device select code is taken from bits 7-0 of the address value.
'' These device select codes must have their least significant bit set to zero (for write
'' mode) except in the case of ioRead0Cmd where it must be set to one for proper operation.
'' SPI data is handled a little differently. For ioSpiInit, the 6 bit pin numbers for DO,
'' Clk, DI, and CS are given from MSB to LSB of the 24 bit address field of the command and
'' are used for all further I/O operations (until an ioSpiStop is done).
CON
'' Command code and error information for I2C driver
'' (For convenience in using just OS_loaderInit, these are included here. The "master"
'' copies are considered the ones in OS_loader and these must be kept up-to-date).
ioReadCmd = %00000001 ' Read from EEPROM to HUB RAM (16 bit addresses)
ioWriteCmd = %00000010 ' Write to EEPROM from HUB RAM (16 bit addresses)
ioRead1Cmd = %00000011 ' Read from a device with only 8-bit addresses
ioWrite1Cmd = %00000100 ' Write to a device with only 8-bit addresses
ioRead0Cmd = %00000101 ' Read from a device without address bytes
ioWrite0Cmd = %00000110 ' Write to a device without address bytes
ioBootCmd = %00001000 ' Read from EEPROM to HUB RAM, then start a
' new SPIN interpreter in the COG whose ID is
' supplied in the lower 3 bits of this command
' This COG is stopped before the read is done
' unless it's the one used to execute the loader
ioSpiInit = %00010000 ' Initialize the specified SPI bus and SD card
ioSpiStop = %00010001 ' Change all SD card pins to inputs
ioSpiRead = %00010010 ' Read one or more bytes from the SD card
ioSpiWrite = %00010011 ' Write one or more bytes from the SD card
ioSpiBoot = %00011000 ' Like ioBootCmd, but uses ioSpiRead for loading
ioCmdMask = %00011111 ' Used to mask off command bits
ioSpiMask = %00010000 ' Used to test for SPI command codes
' Options for commands
ioNoStore = %00100000 ' If set, data is not stored into main memory
' If ioBootCmd or ioSpiBoot, no cogs are
' stopped and a new cog is not started.
ioLowSpeed = %01000000 ' If set, I2C runs at 100KHz rather than 400KHz
ioStopLdr = %10000000 ' If set, the loader's cog is stopped after a boot
' Return status
ioWriteErr = %10000000 ' An error occurred during an I2C write (NAK)
ioTestRdy = ioCmdMask << 24 ' Used to test 1st control long for ready
ioTestErr = ioWriteErr << 24 ' Used to test 1st control long for write error
'' Other constants from OS_loader
i2cBootSCL = 28 ' Boot EEPROM SCL pin
bootAddr = i2cBootSCL << 18 ' Address of boot EEPROM
clkfreqVal = $0000 ' Current CLKFREQ value stored here
clksetVal = $0004 ' Current CLKSET value stored here
chksumVal = $0005 ' Checksum over memory stored here
vbase = $0008 ' Length of Spin program loaded (# longs * 4)
dbase = $000A ' Address of start of stack (marker below)
VAR
long cog, control
PUB bootEEPROM(addr) | t, c0, c1 '' Load and run a new SPIN program
if not start(@c0) ' Start up the I/O routines using a
abort ' local control block
long[control][1] := 0 ' Check for the presence of EEPROM
long[control][0] := ioReadCmd << 24 | (addr & $FFFFFF)
repeat while long[control][0] & ioTestRdy ' Wait for check to complete and
if long[control][0] & ioTestErr ' abort if there's an error
abort
repeat t from 0 to 7 ' Stop all COGs except this one and
if (t <> cogid) and (t <> (cog-1)) ' the one with the I2C driver in it
cogstop(t)
t := ioBootCmd | ioStopLdr | cogid ' Tell the I2C driver to load 32K
long[control][1] := $80000000 ' into HUB RAM after stopping
long[control][0] := (t << 24) | (addr & $FFFFFF) ' this calling cog
repeat while long[control][0] & ioTestRdy ' Wait for this to finish
return (long[control][0] & ioTestErr) <> 0 ' Return any error code
PUB readEEPROM(addr,buffer,count) | t '' Read a block from EEPROM to RAM
t := ioReadCmd
repeat while long[control][0] & ioTestRdy ' Wait for previous I/O to finish
long[control][1] := (count << 16) | (buffer & $FFFF)
long[control][0] := (t << 24) | (addr & $FFFFFF)
repeat while long[control][0] & ioTestRdy ' Wait for this to finish
return (long[control][0] & ioTestErr) <> 0 ' Return any error code
PUB writeEEPROM(addr,buffer,count) | t '' Write a block to EEPROM from RAM
t := ioWriteCmd
repeat while long[control][0] & ioTestRdy ' Wait for previous I/O to finish
long[control][1] := (count << 16) | (buffer & $FFFF)
long[control][0] := (t << 24) | (addr & $FFFFFF)
repeat while long[control][0] & ioTestRdy ' Wait for this to finish
return (long[control][0] & ioTestErr) <> 0 ' Return any error code
PUB checkPresence(addr) | t
'' This routine checks to be sure there is an I2C bus and an EEPROM at the
'' specified address. Note that this routine cannot distinguish between a
'' 32Kx8 and a 64Kx8 EEPROM since the 16th address bit is a "don't care"
'' for the 32Kx8 devices. Return true if EEPROM present, false otherwise.
t := ioReadCmd
repeat while long[control][0] & ioTestRdy ' Wait for previous I/O to finish
long[control][1] := 0 ' Attempt to address the device
long[control][0] := (t << 24) | (addr & $FFFFFF)
repeat while long[control][0] & ioTestRdy ' Wait for this to finish
return (long[control][0] & ioTestErr) == 0 ' Return false on error
PUB writeWait(addr) | t '' Wait for EEPROM to complete write
t := cnt
repeat until checkPresence(addr) ' Maximum wait time is 20ms
if (cnt - t) > (clkfreq / 50)
return true ' Return true if a timeout occurred
return false ' Otherwise return false
PUB computeTimes '' Set up timing constants in assembly
' (Done this way to avoid overflow)
i2cDataSet1 := ((clkfreq / 10000) * 900) / 100000 ' Data setup time - 900ns (100KHz)
i2cClkLow1 := ((clkfreq / 10000) * 4700) / 100000 ' Clock low time - 4700ns (100KHz)
i2cClkHigh1 := ((clkfreq / 10000) * 4000) / 100000 ' Clock high time - 4000ns (100KHz)
i2cDataSet4 := ((clkfreq / 10000) * 550) / 100000 ' Data setup time - 550ns (400KHz)
i2cClkLow4 := ((clkfreq / 10000) * 1300) / 100000 ' Clock low time - 1300ns (400KHz)
i2cClkHigh4 := ((clkfreq / 10000) * 1000) / 100000 ' Clock high time - 1000ns (400KHz)
i2cPause := clkfreq / 100000 ' Pause between checks for operations
PUB initSDCard(DO,Clk,DI,CS) | t '' Initialize SD card access
t := cnt
repeat while long[control][0] & ioTestRdy ' Wait for previous I/O to finish
long[control][1] := 0
long[control][0] := ioSpiInit << 24 | DO << 18 | Clk << 12 | DI << 6 | CS
repeat while long[control][0] & ioTestRdy ' Wait for this to finish
return (long[control][0] & ioTestErr) <> 0 ' Return any error code
PUB stopSDCard '' Stop SD card access
repeat while long[control][0] & ioTestRdy ' Wait for previous I/O to finish
long[control][1] := 0
long[control][0] := ioSpiStop << 24
repeat while long[control][0] & ioTestRdy ' Wait for this to finish
return (long[control][0] & ioTestErr) <> 0 ' Return any error code
PUB readSDCard(addr,buffer,count) '' Read block(s) from SD card to RAM
repeat while long[control][0] & ioTestRdy ' Wait for previous I/O to finish
long[control][1] := (count << 16) | (buffer & $FFFF)
long[control][0] := (ioSpiRead << 24) | (addr & $FFFFFF)
repeat while long[control][0] & ioTestRdy ' Wait for this to finish
return (long[control][0] & ioTestErr) <> 0 ' Return any error code
PUB writeSDCard(addr,buffer,count) '' Write block(s) to SD card from RAM
repeat while long[control][0] & ioTestRdy ' Wait for previous I/O to finish
long[control][1] := (count << 16) | (buffer & $FFFF)
long[control][0] := (ioSpiWrite << 24) | (addr & $FFFFFF)
repeat while long[control][0] & ioTestRdy ' Wait for this to finish
return (long[control][0] & ioTestErr) <> 0 ' Return any error code
PUB bootSDCard(addr,count) | t '' Boot from an SD card
if count < 16 ' Must load at least 16 bytes
return true
repeat t from 0 to 7 ' Stop all COGs except this one and
if (t <> cogid) and (t <> (cog-1)) ' the one with the I2C/SPI driver
cogstop(t)
t := ioSpiBoot | ioStopLdr | cogid ' Tell the SD card driver to load
long[control][1] := count << 16 ' into HUB RAM after stopping
long[control][0] := (t << 24) | (addr & $FFFFFF) ' this calling cog
repeat while long[control][0] & ioTestRdy ' Wait for this to finish
return (long[control][0] & ioTestErr) <> 0 ' Return any error code
PUB start(ctrlBlk) | t '' Start the I2C I/O driver (standalone)
control := ctrlBlk '' using address of 2 longs for control
stop ' Stop a previous copy
computeTimes
long[control][0] := 0 ' Initialize the control block
long[control][1] := 0
cog := cognew(@i2cEntryPoint,control) + 1 ' Start a new cog with the I2C driver
return cog > 0 ' Indicate success
PUB stop '' Stop the I2C I/O driver (standalone)
if cog > 0
cogstop(cog - 1)
PUB getControl(i) '' Return a long from the control block
return long[control][i] ' Check for operation completed first
PUB setControl(i,value) '' Set value of a long in the control block
long[control][i] := value ' Always set the first long last
DAT
org 0
i2cEntryPoint mov i2cTemp,i2cPause
add i2cTemp,CNT ' Wait 10us before checking
waitcnt i2cTemp,#0
i2cNewOpFetch rdlong i2cAddr,PAR ' Fetch control information
mov i2cCmd,i2cAddr
shr i2cCmd,#24 ' Isolate command code
mov Options,i2cCmd
and i2cAddr,i2cAddrMask ' Only need address at this point
and i2cCmd,#ioCmdMask wz
if_z jmp #i2cEntryPoint ' Wait for a new operation
mov i2cTemp,PAR
add i2cTemp,#4 ' Now get 2nd long of packet
rdlong i2cCount,i2cTemp
mov i2cBufAdr,i2cCount ' Byte count
rdlong SaveClkFreq,#clkfreqVal ' Save clock frequency and mode
shr i2cCount,#16
and i2cBufAdr,i2cWordMask ' HUB RAM address of buffer
rdbyte SaveClkMode,#clksetVal
movs ShiftData,#0 ' Initialize for saving Preamble
mov StoreLocal,initStore ' on I2C and SPI reads
mov Preamble+0,#0
mov Preamble+1,#0
mov Preamble+2,#0
mov Preamble+3,#0
mov CheckSum,#$EC ' Adjust checksum for stack marker
test Options,#ioNoStore wc
test i2cCmd,#ioBootCmd wz
if_nz_and_nc mov i2cTemp,i2cCmd ' Stop the caller's COG unless
if_nz_and_nc and i2cTemp,#%111 ' it's this one
if_nz_and_nc cogid i2cCogId
if_nz_and_nc cmp i2cCogId,i2cTemp wz
if_nz_and_nc cogstop i2cTemp
test i2cCmd,#ioSpiMask wz ' Check for SPI commands
if_nz jmp #spiEntryPoint
movs :getAction,i2cCmd ' Get command specific action
test i2cCmd,#ioBootCmd wz ' bit sequence. ioBootCmd is
if_nz movs :getAction,#ioReadCmd ' treated as ioReadCmd here
add :getAction,#ActionTbl
mov i2cDataSet,i2cDataSet1
mov i2cClkLow,i2cClkLow1
mov i2cClkHigh,i2cClkHigh1
:getAction mov Action,0-0
test Options,#ioLowSpeed wc ' Set bus speed based on option
if_nc mov i2cDataSet,i2cDataSet4
if_nc mov i2cClkLow,i2cClkLow4
if_nc mov i2cClkHigh,i2cClkHigh4
mov i2cTemp,i2cAddr
shr i2cTemp,#18 ' Determine bit masks for
and i2cTemp,#%11110 ' I/O pins for I2C bus
mov i2cSCL,#1
shl i2cSCL,i2cTemp
mov i2cSDA,i2cSCL ' SDA is next higher pin
shl i2cSDA,#1
test FirstCall,i2cSCL wz ' Is this our first call?
andn FirstCall,i2cSCL ' if so, do a reset
if_nz call #i2cReset
call #i2cStart ' Do a start sequence
test Action,#%000000001 wz
if_z jmp #:skipAction0
mov i2cData,i2cAddr ' Construct a device select
shr i2cData,#15 ' code for EEPROM write mode
and i2cData,#%00001110 ' with 2 address bytes
or i2cData,#%10100000
mov i2cMask,#%10000000
call #i2cWrite ' Send device select code
if_c jmp #:doStop ' Failure if NAK received
:skipAction0 test Action,#%000000010 wz
if_z jmp #:skipAction1
mov i2cData,i2cAddr ' First address byte is most
shr i2cData,#8 ' significant byte of address
mov i2cMask,#%10000000
call #i2cWrite ' Send first address byte
if_c jmp #:doStop ' Failure if NAK received
:skipAction1 test Action,#%000000100 wz
if_z jmp #:skipAction2
mov i2cData,i2cAddr ' Second address byte is least
mov i2cMask,#%10000000 ' significant byte of address
call #i2cWrite ' Send second address byte
if_c jmp #:doStop ' Failure if NAK received
:skipAction2 tjz i2cCount,#:doStop ' If byte count == 0, we're done
test Action,#%000001000 wz
if_nz call #i2cStart ' Do a start sequence if readdressing
:doReadWrite test Action,#%000010000 wz
if_nz rdbyte i2cData,i2cBufAdr ' If writing, fetch the data value
if_nz add i2cBufAdr,#1 ' and increment the hub address
test Action,#%000100000 wz
if_z jmp #:skipAction5
mov i2cData,i2cAddr ' If reading, construct a device select
shr i2cData,#15 ' code for EEPROM read mode with
and i2cData,#%00001110 ' 2 address bytes
or i2cData,#%10100001
:skipAction5 test Action,#%001000000 wz
if_z jmp #:skipAction6
mov i2cData,i2cAddr ' If reading using a single byte address
shr i2cData,#8 ' construct a device select code for
or i2cData,#%00000001 ' read mode given one for write mode
:skipAction6 test Action,#%010000000 wz
if_z jmp #:skipAction7
mov i2cMask,#%10000000 ' Either readdress device for reading
call #i2cWrite ' or write a data value at this point
if_c jmp #:doStop ' Failure if NAK received
:skipAction7 test Action,#%100000000 wz
if_z jmp #:skipAction8
cmp i2cCount,#2 wc ' Carry true if this is the last byte
mov i2cMask,#%10000000
mov i2cData,#0
call #i2cRead
call #StoreData ' Now force carry false to show success
or i2cZero,#0 nr,wc
andn Action,#%011100000 ' No readdressing on subsequent reads
:skipAction8 add i2cAddr,#1
djnz i2cCount,#:doReadWrite ' Repeat for number of bytes requested
:doStop call #i2cStop
if_c or i2cAddr,errorFlag ' Carry true indicates error
jmp #checkEndIO
'' Low level I2C routines. These are designed to work either with a standard I2C bus
'' (with pullups on both SCL and SDA) or the Propellor Demo Board (with a pullup only
'' on SDA). Timing can be set by the caller to 100KHz or 400KHz.
'' Do I2C Reset Sequence. Clock up to 9 cycles. Look for SDA high while SCL
'' is high. Device should respond to next Start Sequence. Leave SCL high.
i2cReset andn dira,i2cSDA ' Pullup drive SDA high
mov i2cBitCnt,#9 ' Number of clock cycles
mov i2cTime,i2cClkLow
add i2cTime,cnt ' Allow for minimum SCL low
:i2cResetClk andn outa,i2cSCL ' Active drive SCL low
or dira,i2cSCL
waitcnt i2cTime,i2cClkHigh
test i2cBootSCLm,i2cSCL wz ' Check for boot I2C bus
if_nz or outa,i2cSCL ' Active drive SCL high
if_nz or dira,i2cSCL
if_z andn dira,i2cSCL ' Pullup drive SCL high
waitcnt i2cTime,i2cClkLow ' Allow minimum SCL high
test i2cSDA,ina wz ' Stop if SDA is high
if_z djnz i2cBitCnt,#:i2cResetClk ' Stop after 9 cycles
i2cReset_ret ret ' Should be ready for Start
'' Do I2C Start Sequence. This assumes that SDA is a floating input and
'' SCL is also floating, but may have to be actively driven high and low.
'' The start sequence is where SDA goes from HIGH to LOW while SCL is HIGH.
i2cStart andn dira,i2cSDA ' Pullup drive SDA high
andn outa,i2cSDA ' SDA set to drive low
mov i2cTime,i2cClkLow
add i2cTime,cnt ' Allow for bus free time
waitcnt i2cTime,i2cClkHigh
test i2cBootSCLm,i2cSCL wz ' Check for boot I2C bus
if_nz or outa,i2cSCL ' Active drive SCL high
if_nz or dira,i2cSCL
if_z andn dira,i2cSCL ' Pullup drive SCL high
waitcnt i2cTime,i2cClkHigh ' Allow for start setup time
or dira,i2cSDA ' Active drive SDA low
waitcnt i2cTime,#0 ' Allow for start hold time
andn outa,i2cSCL ' Active drive SCL low
or dira,i2cSCL
i2cStart_ret ret
'' Do I2C Stop Sequence. This assumes that SCL is low and SDA is indeterminant.
'' The stop sequence is where SDA goes from LOW to HIGH while SCL is HIGH.
'' i2cStart must have been called prior to calling this routine for initialization.
'' The state of the (c) flag is maintained so a write error can be reported.
i2cStop or dira,i2cSDA ' Active drive SDA low
mov i2cTime,i2cClkLow
add i2cTime,cnt ' Wait for minimum clock low
waitcnt i2cTime,i2cClkLow
test i2cBootSCLm,i2cSCL wz ' Check for boot I2C bus
if_nz or outa,i2cSCL ' Active drive SCL high
if_nz or dira,i2cSCL
if_z andn dira,i2cSCL ' Pullup drive SCL high
waitcnt i2cTime,i2cClkHigh ' Wait for minimum setup time
andn dira,i2cSDA ' Pullup drive SDA high
waitcnt i2cTime,#0 ' Allow for bus free time
andn dira,i2cSCL ' Leave SCL and SDA high
i2cStop_ret ret
'' Write I2C data. This assumes that i2cStart has been called and that SCL is low,
'' SDA is indeterminant. The (c) flag will be set on exit from ACK/NAK with ACK == false
'' and NAK == true. Bytes are handled in "little-endian" order so these routines can be
'' used with words or longs although the bits are in msb..lsb order.
i2cWrite mov i2cBitCnt,#8
mov i2cTime,i2cClkLow
add i2cTime,cnt ' Wait for minimum SCL low
:i2cWriteBit waitcnt i2cTime,i2cDataSet
test i2cData,i2cMask wz
if_z or dira,i2cSDA ' Copy data bit to SDA
if_nz andn dira,i2cSDA
waitcnt i2cTime,i2cClkHigh ' Wait for minimum setup time
test i2cBootSCLm,i2cSCL wz ' Check for boot I2C bus
if_nz or outa,i2cSCL ' Active drive SCL high
if_nz or dira,i2cSCL
if_z andn dira,i2cSCL ' Pullup drive SCL high
waitcnt i2cTime,i2cClkLow
andn outa,i2cSCL ' Active drive SCL low
or dira,i2cSCL
ror i2cMask,#1 ' Go do next bit if not done
djnz i2cBitCnt,#:i2cWriteBit
andn dira,i2cSDA ' Switch SDA to input and
waitcnt i2cTime,i2cClkHigh ' wait for minimum SCL low
test i2cBootSCLm,i2cSCL wz ' Check for boot I2C bus
if_nz or outa,i2cSCL ' Active drive SCL high
if_nz or dira,i2cSCL
if_z andn dira,i2cSCL ' Pullup drive SCL high
waitcnt i2cTime,#0 ' Wait for minimum high time
test i2cSDA,ina wc ' Sample SDA (ACK/NAK) then
andn outa,i2cSCL ' active drive SCL low
or dira,i2cSCL
or dira,i2cSDA ' Leave SDA low
rol i2cMask,#16 ' Prepare for multibyte write
i2cWrite_ret ret
'' Read I2C data. This assumes that i2cStart has been called and that SCL is low,
'' SDA is indeterminant. ACK/NAK will be copied from the (c) flag on entry with
'' ACK == low and NAK == high. Bytes are handled in "little-endian" order so these
'' routines can be used with words or longs although the bits are in msb..lsb order.
i2cRead mov i2cBitCnt,#8
andn dira,i2cSDA ' Make sure SDA is set to input
mov i2cTime,i2cClkLow
add i2cTime,cnt ' Wait for minimum SCL low
:i2cReadBit waitcnt i2cTime,i2cClkHigh
test i2cBootSCLm,i2cSCL wz ' Check for boot I2C bus
if_nz or outa,i2cSCL ' Active drive SCL high
if_nz or dira,i2cSCL
if_z andn dira,i2cSCL ' Pullup drive SCL high
waitcnt i2cTime,i2cClkLow ' Wait for minimum clock high
test i2cSDA,ina wz ' Sample SDA for data bits
andn outa,i2cSCL ' Active drive SCL low
or dira,i2cSCL
if_nz or i2cData,i2cMask ' Accumulate data bits
if_z andn i2cData,i2cMask
ror i2cMask,#1 ' Shift the bit mask and
djnz i2cBitCnt,#:i2cReadBit ' continue until done
waitcnt i2cTime,i2cDataSet ' Wait for end of SCL low
if_c andn dira,i2cSDA ' Copy the ACK/NAK bit to SDA
if_nc or dira,i2cSDA
waitcnt i2cTime,i2cClkHigh ' Wait for minimum setup time
test i2cBootSCLm,i2cSCL wz ' Check for boot I2C bus
if_nz or outa,i2cSCL ' Active drive SCL high
if_nz or dira,i2cSCL
if_z andn dira,i2cSCL ' Pullup drive SCL high
waitcnt i2cTime,#0 ' Wait for minimum clock high
andn outa,i2cSCL ' Active drive SCL low
or dira,i2cSCL
or dira,i2cSDA ' Leave SDA low
rol i2cMask,#16 ' Prepare for multibyte read
i2cRead_ret ret
'' SPI routines for Rokicki's SD card FAT file system driver
spiEntryPoint test i2cCmd,#ioBootCmd wc ' Check for boot
if_c jmp #spiDoRead ' (Treat like read)
cmp i2cCmd,#ioSpiStop wc,wz
if_c jmp #spiDoInit ' Decode operation
if_z jmp #spiDoStop
cmp i2cCmd,#ioSpiWrite wc
if_c jmp #spiDoRead
jmp #spiDoWrite
'' Initialize SPI communications. The pin numbers of the 4 I/O pins are
'' provided in the 24 bit address field of the control packet. From MSB to
'' LSB, these are DO - Data Out, Clk - Clock, DI - Data In, CS - Card Select.
spiDoInit movd :moveIt,#spiMaskCS
mov spiBlkCnt,#4
:makeMask mov i2cMask,#1
mov i2cTemp,i2cAddr ' Only use lower 5 bits of
and i2cTemp,#%11111 ' 6 bit shift count field
shl i2cMask,i2cTemp
:moveIt mov 0-0,i2cMask ' Store the bit mask for the pin
cmp spiBlkCnt,#1 wz
if_ne or outa,i2cMask ' Make all pins high outputs
if_ne or dira,i2cMask ' except DO is an input since
if_e andn dira,i2cMask ' input/output is card relative
sub :moveIt,incrDst
ror i2cAddr,#6
djnz spiBlkCnt,#:makeMask
rol i2cAddr,#24 ' Leave i2cAddr unchanged
mov i2cTime,cnt ' Set up a 1 second timeout
mov spiBlkCnt,spiInitCnt
:initRead call #spiRecvByte ' Output a stream of 32K clocks
djnz spiBlkCnt,#:initRead ' in case SD card left in some
mov spiOp,#0 ' undefined state
mov spiParm,#0
call #spiSendCmd ' Send a reset command and deselect
or outa,spiMaskCS ' to get SD card into SPI mode
:waitIdle mov spiOp,#55
call #spiSendCmd ' APP_CMD (Application Specific)
mov spiOp,#41
call #spiSendCmd ' SEND_OP_COND (Initialization)
or outa,spiMaskCS
cmp i2cData,#1 wz ' Wait until response not In Idle
if_e jmp #:waitIdle
tjz i2cData,#i2cGoUpdate ' Initialization complete
or i2cAddr,errorFlag
jmp #i2cGoUpdate ' Could not initialize the card
'' Stop SPI communications. Any previously used I/O pins are set to input mode and
'' the masks for the I/O pins are zeroed. The card is clocked so it turns off.
spiDoStop or outa,spiMaskCS ' Make sure /CS is high
call #spiRecvByte ' Put out a few clocks
call #spiRecvByte ' to turn off the card
andn dira,spiMaskDO
andn dira,spiMaskDI ' Set all the card pins
andn dira,spiMaskCS ' to inputs so they can
andn dira,spiMaskClk ' be used for some other
mov spiMaskDO,#0 ' purpose when the card
mov spiMaskDI,#0 ' is removed. All should
mov spiMaskCS,#0 ' have pullups to +3.3V.
mov spiMaskClk,#0
jmp #i2cGoUpdate
'' Read one or more 512 byte blocks and store the specified number of bytes
'' into the HUB location given. The block number is provided in the 24 bit
'' address field and incremented after every block is read. Partial blocks are
'' allowed and any extra bytes read are discarded.
spiDoRead mov spiOp,#17 ' READ_SINGLE_BLOCK
:readRepeat mov i2cTime,cnt ' Save start of timeout
mov spiParm,i2cAddr
call #spiSendCmd ' Read from specified block
call #spiResponse
mov spiBlkCnt,spiBlkSize ' Transfer a block at a time
:getRead call #spiRecvByte
tjz i2cCount,#:skipStore ' Check for count exhausted
call #StoreData
sub i2cCount,#1
:skipStore djnz spiBlkCnt,#:getRead ' Are we done with the block?
call #spiRecvByte
call #spiRecvByte ' Yes, finish with 16 clocks
add i2cAddr,#1
or outa,spiMaskCS ' Increment address, deselect card
tjnz i2cCount,#:readRepeat ' and check for more blocks to do
checkEndIO test i2cCmd,#ioBootCmd wc
if_nc jmp #i2cGoUpdate ' If not booting, we're done
test i2cAddr,errorFlag wc
and CheckSum,#$FF wz ' If booting, no errors can occur
if_z_and_nc jmp #nowBootSpin ' and checksum must be zero
or i2cAddr,errorFlag
test Options,#noStore wc
if_c jmp #i2cGoUpdate ' Return error status if noStore
stopThisCOG cogid i2cCogId ' If an unrecoverable error occurs,
cogstop i2cCogId ' stop this cog
'' Write one or more 512 byte blocks with the specified number of bytes from
'' the HUB location given. The block number is provided in the 24 bit address
'' field and incremented after every block is written. Partial blocks are
'' allowed and are padded with zeroes.
spiDoWrite mov spiOp,#24 ' WRITE_BLOCK
mov i2cTime,cnt ' Setup timeout
mov spiParm,i2cAddr
call #spiSendCmd ' Write to specified block
mov i2cData,#$FE ' Ask to start data transfer
call #spiSendByte
mov spiBlkCnt,spiBlkSize ' Transfer a block at a time
:putWrite mov i2cData,#0 ' padding with zeroes if needed
tjz i2cCount,#:padWrite ' Check for count exhausted
rdbyte i2cData,i2cBufAdr ' If not, get the next data byte
add i2cBufAdr,#1
sub i2cCount,#1
:padWrite call #spiSendByte
djnz spiBlkCnt,#:putWrite ' Are we done with the block?
call #spiRecvByte
call #spiRecvByte ' Yes, finish with 16 clocks
call #spiResponse
and i2cData,#$1F ' Check the response status
cmp i2cData,#5 wz
if_ne or i2cAddr,errorFlag ' Must be Data Accepted
if_ne jmp #i2cGoUpdate
movs spiWaitData,#0 ' Wait until not busy
call #spiWaitBusy
add i2cAddr,#1
or outa,spiMaskCS ' Increment block address and go
tjnz i2cCount,#spiDoWrite ' to next if more data remains
jmp #i2cGoUpdate
'' Mid level SPI I/O
spiSendCmd andn outa,spiMaskCS ' Send command sequence. Begin by
call #spiRecvByte ' selecting card and clocking
mov i2cData,spiOp
or i2cData,#$40 ' Send command byte (1st 2 bits %01)
call #spiSendByte
mov i2cData,spiParm
shr i2cData,#15 ' Supplied address is sector number
call #spiSendByte
mov i2cData,spiParm ' Send to SD card as byte address,
shr i2cData,#7 ' in multiples of 512 bytes
call #spiSendByte
mov i2cData,spiParm ' Total length of this address is
shl i2cData,#1 ' four bytes
call #spiSendByte
mov i2cData,#0
call #spiSendByte
mov i2cData,#$95 ' CRC code (for 1st command only)
call #spiSendByte
spiResponse movs spiWaitData,#$FF ' Wait for response from card
spiWaitBusy call #spiRecvByte
mov i2cTemp,cnt
sub i2cTemp,i2cTime ' Check for expired timeout (1 sec)
cmp i2cTemp,SaveClkFreq wc
if_nc or i2cAddr,errorFlag
if_nc jmp #i2cGoUpdate
spiWaitData cmp i2cData,#0-0 wz ' Wait for some other response
if_e jmp #spiWaitBusy ' than that specified
spiSendCmd_ret
spiResponse_ret
spiWaitBusy_ret ret
'' Low level byte I/O
spiSendByte mov i2cMask,#%10000000
:sendBit test i2cMask,i2cData wc
andn outa,spiMaskClk ' Send data bytes MSB first
muxc outa,spiMaskDI
or outa,spiMaskClk
shr i2cMask,#1 ' When mask shifted out, we're done
tjnz i2cMask,#:sendBit
or outa,spiMaskDI ' Leave DI in idle (high) state
spiSendByte_ret ret
spiRecvByte mov i2cMask,#%10000000
:recvBit andn outa,spiMaskClk ' Receive data bytes MSB first
or outa,spiMaskClk ' Copy DO to data bit
test spiMaskDO,ina wc
muxc i2cData,i2cMask
shr i2cMask,#1 ' When mask shifted out, we're done
tjnz i2cMask,#:recvBit
and i2cData,#%11111111 ' Eight bits received
spiRecvByte_ret ret
'' For both I2C and SPI, store data on a read operation unless ioNoStore is set.
'' Accumulate a checksum and always save a copy of the first 16 bytes read.
'' If this is an ioBootCmd or ioSpiBoot, adjust the amount to be read based
'' on the value in the program preamble in the word at vbase ($0008).
StoreData test Options,#ioNoStore wc
if_nc wrbyte i2cData,i2cBufAdr ' Store data in specified location
add i2cBufAdr,#1 ' and increment the address
add CheckSum,i2cData ' Accumulate checksum for ioBootCmd
ShiftData shl i2cData,#0-0
StoreLocal or Preamble+0,i2cData ' Store a local copy of the program
add ShiftData,#8 ' preamble for when we're reading
cmp ShiftData,testIns wz ' in a new Spin program
if_z movs ShiftData,#0 ' Pack the data into successive longs
if_z add StoreLocal,incrDst
if_z cmp StoreLocal,testDst wz ' Stop after saving $0010 bytes
if_z mov StoreLocal,noStore
if_z test i2cCmd,#ioBootCmd wc ' If we're reading in a new program,
if_c_and_z mov i2cCount,Preamble+2 ' change i2cCount to vbase adjusted
if_c_and_z and i2cCount,i2cWordMask ' by number of bytes loaded so far.
if_c_and_z sub i2cCount,#16 - 1 ' i2cCount will be decremented again
StoreData_ret ret
'' After reading is finished for a boot, the stack marker is added below dbase
'' and memory is cleared between that and vbase (the end of the loaded program).
'' Memory beyond the stack marker is not cleared. Note that if ioNoStore is set,
'' we go through the motions, but don't actually change memory or the clock.
nowBootSpin test Options,#ioNoStore wc
mov i2cTemp,Preamble+2
shr i2cTemp,#16 ' Get dbase value
sub i2cTemp,#4
if_nc wrlong StackMark,i2cTemp ' Place stack marker at dbase
sub i2cTemp,#4
if_nc wrlong StackMark,i2cTemp
mov i2cOther,Preamble+2 ' Get vbase value
and i2cOther,i2cWordMask
sub i2cTemp,i2cOther
shr i2cTemp,#2 wz ' Compute number of longs between
:zeroIt if_nz_and_nc wrlong i2cZero,i2cOther ' vbase and below stack marker
if_nz_and_nc add i2cOther,#4
if_nz_and_nc djnz i2cTemp,#:zeroIt ' Zero that space (if any)
mov i2cTemp,Preamble
cmp i2cTemp,SaveClkFreq wz ' Is the clock frequency the same?
mov i2cTemp,Preamble+1
and i2cTemp,#$FF ' Is the clock mode the same also?
if_ne jmp #:changeClock
cmp i2cTemp,SaveClkMode wz ' If both same, just go start COG
if_e jmp #:justStartUp
:changeClock and i2cTemp,#$F8 ' Force use of RCFAST clock while
if_nc clkset i2cTemp ' letting requested clock start
mov i2cTemp,time_xtal
:startupDelay djnz i2cTemp,#:startupDelay ' Allow 20ms@20MHz for xtal/pll to settle
mov i2cTemp,Preamble+1
and i2cTemp,#$FF ' Then switch to selected clock
if_nc clkset i2cTemp
:justStartUp mov i2cOther,i2cCmd ' Use the COG supplied as the caller's
and i2cOther,#%111 ' to start up the SPIN interpreter
test Options,#ioStopLdr wz ' If ioStopLdr is set and ioNoStore is
if_nz cogid i2cOther ' clear, then use this cog for SPIN
or i2cOther,interpreter
if_nc coginit i2cOther
'' The operation has completed, with or without errors. Update the control block
'' in main memory and wait for the next operation to be requested.
i2cGoUpdate and i2cBufAdr,i2cWordMask ' Copy updated information
shl i2cCount,#16 ' back to control packet
or i2cCount,i2cBufAdr
mov i2cTemp,PAR
add i2cTemp,#4
wrlong i2cCount,i2cTemp
wrlong i2cAddr,PAR ' Indicate operation is done
jmp #i2cEntryPoint ' and go wait for a new one
'' This action table contains bit sequences for controlling device addressing and read/write
'' mode selection for each of the commands possible. From LSB to MSB, the actions are:
'' 0 - Write the EEPROM device select code for write mode and 2 address bytes
'' 1 - Write the MSB device address or (for ioRead1Cmd/ioWrite1Cmd) a device select code
'' 2 - Write the LSB device address or (for ioRead0Cmd/ioWrite0Cmd) a device select code
'' 3 - Output a Start Sequence prior to reselecting in read mode
'' 4 - Fetch a data value for writing
'' 5 - Construct an EEPROM device select code for read mode and 2 address bytes
'' 6 - Construct a read mode device select code from the MSB of the 16 bit device address
'' 7 - Write the data value or read mode device select code
'' 8 - Read a byte of data from the device and store it
i2cZero
ActionTbl long %0000000000 ' Command not used (indicates done)
long %0110101111,%0010010111 ' Read/Write with 2 bytes of addressing
long %0111001110,%0010010110 ' Read/Write with 1 byte of addressing
long %0100000100,%0010010100 ' Read/Write data only
'' Constants for all routines
i2cWordMask long $0000FFFF
i2cAddrMask long $00FFFFFF
errorFlag long $80000000 ' NAK received during write cycle
speedMask long $40000000 ' One if 100KHz bus, zero if 400KHz
time_xtal long 20 * 20000 / 4 / 1 ' 20ms (@20MHz, 1 inst/loop)
interpreter long ($0004 << 16) | ($F004 << 2) | %0000
i2cBootSCLm long |<i2cBootSCL ' Bit mask for pin 28 SCL use
spiBlkSize ' Number of bytes in an SD card block
incrDst long %10_00000000 ' Used to increment destination field
testIns shl i2cData,#32 ' Used to compare for end of word packing
initStore or Preamble+0,i2cData ' Used to initialize packing instruction
testDst or Preamble+4,i2cData ' Used to check for end of packing buffer
noStore jmp #StoreData_ret ' Used after all data stored into Preamble
spiInitCnt long 32768 / 8 ' Initial SPI clocks produced
StackMark long $FFF9FFFF ' Two of these mark the base of the stack
'' Variables for all routines
Preamble long 0, 0, 0, 0 ' Private copy of program preamble
Action long 0
i2cOther
i2cCogId long 0
i2cCmd long 0
FirstCall long -1 ' One if I2C pins not initialized yet
i2cTemp long 0
i2cCount long 0
i2cBufAdr long 0
i2cAddr long 0
i2cDataSet long 0 ' Minumum data setup time (ticks)
i2cClkLow long 0 ' Minimum clock low time (ticks)
i2cClkHigh long 0 ' Minimum clock high time (ticks)
i2cDataSet1 long 0 ' Minumum data setup time (ticks) 100KHz
i2cClkLow1 long 0 ' Minimum clock low time (ticks) 100KHz
i2cClkHigh1 long 0 ' Minimum clock high time (ticks) 100KHz
i2cDataSet4 long 0 ' Minumum data setup time (ticks) 400KHz
i2cClkLow4 long 0 ' Minimum clock low time (ticks) 400KHz
i2cClkHigh4 long 0 ' Minimum clock high time (ticks) 400KHz
i2cPause long 0 ' Pause before re-fetching next operation
SaveClkFreq long 0 ' Initial clock frequency (clkfreqVal)
SaveClkMode long 0 ' Initial clock mode value (clksetVal)
spiBlkCnt long 0 ' Number of SD card bytes to go in block
CheckSum long 0 ' Checksum of bytes for ioBootCmd
Options long 0 ' Option bits (ioNoStore, ioLowSpeed)
'' Local variables for low level I2C routines
spiOp ' Operation code for SPI command
i2cSCL long 0 ' Bit mask for SCL
spiParm ' Parameter value for SPI command
i2cSDA long 0 ' Bit mask for SDA
i2cTime long 0 ' Used for timekeeping
i2cData long 0 ' Data to be transmitted / received
i2cMask long 0 ' Bit mask for bit to be tx / rx
i2cBitCnt long 0 ' Number of bits to tx / rx
'' Additional local variables for SPI SD Card access
spiMaskDO long 0
spiMaskClk long 0
spiMaskDI long 0
spiMaskCS long 0
fit
{{
TERMS OF USE: MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
}}